@honor-claw/yoyo 1.1.3 → 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.
- package/index.ts +25 -25
- package/package.json +5 -5
- package/src/apis/claw-cloud.ts +124 -124
- package/src/apis/helpers.ts +10 -10
- package/src/apis/honor-auth.ts +158 -158
- package/src/apis/http-client.ts +239 -239
- package/src/apis/index.ts +8 -8
- package/src/apis/types.ts +77 -73
- package/src/cloud-channel/channel.ts +117 -117
- package/src/cloud-channel/client.ts +3 -3
- package/src/cloud-channel/index.ts +4 -4
- package/src/cloud-channel/message-handler.ts +50 -42
- package/src/cloud-channel/session-manager.ts +14 -9
- package/src/cloud-channel/types.ts +115 -115
- package/src/commands/env/impl.ts +58 -58
- package/src/commands/env/index.ts +1 -1
- package/src/commands/index.ts +30 -30
- package/src/commands/login/impl.ts +30 -30
- package/src/commands/login/index.ts +1 -1
- package/src/commands/logout/index.ts +1 -1
- package/src/commands/status/index.ts +194 -194
- package/src/gateway-client/client.ts +90 -90
- package/src/gateway-client/device/auth.ts +57 -57
- package/src/gateway-client/device/builder.ts +105 -105
- package/src/gateway-client/device/helpers.ts +40 -40
- package/src/gateway-client/device/identity.ts +251 -251
- package/src/gateway-client/device/index.ts +40 -40
- package/src/gateway-client/device/types.ts +57 -57
- package/src/gateway-client/index.ts +5 -5
- package/src/gateway-client/protocol-client.ts +49 -34
- package/src/honor-auth/browser.ts +2 -2
- package/src/honor-auth/callback-server.ts +109 -109
- package/src/honor-auth/cloud.ts +57 -57
- package/src/honor-auth/config.ts +43 -43
- package/src/honor-auth/index.ts +3 -3
- package/src/honor-auth/token-manager.ts +90 -90
- package/src/honor-auth/types.ts +50 -50
- package/src/index.ts +10 -10
- package/src/modules/claw-configs/config-manager.ts +409 -409
- package/src/modules/claw-configs/hosts.ts +48 -48
- package/src/modules/claw-configs/index.ts +8 -8
- package/src/modules/claw-configs/provider.ts +394 -394
- package/src/modules/claw-configs/types.ts +34 -34
- package/src/modules/device/device-info.ts +1 -1
- package/src/modules/device/index.ts +3 -3
- package/src/modules/device/providers/base.ts +32 -32
- package/src/modules/device/providers/pad.ts +107 -107
- package/src/modules/device/providers/windows.ts +130 -130
- package/src/modules/device/registry.ts +48 -43
- package/src/modules/login/impl.ts +31 -26
- package/src/modules/login/index.ts +6 -6
- package/src/schemas.ts +23 -23
- package/src/services/connection/impl.ts +339 -339
- package/src/services/connection/status-tracker/events.ts +127 -127
- package/src/services/connection/status-tracker/index.ts +31 -31
- package/src/services/connection/status-tracker/storage.ts +133 -133
- package/src/services/connection/status-tracker/tracker.ts +370 -370
- package/src/services/connection/status-tracker/types.ts +131 -131
- package/src/services/connection/types.ts +20 -20
- package/src/types.ts +64 -64
- package/src/utils/id.ts +8 -8
- package/src/utils/jwt.ts +37 -37
- package/src/utils/logger.ts +20 -20
- package/src/utils/proxy.ts +58 -58
- package/src/utils/version.ts +29 -29
- 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 {
|
|
3
|
-
import {
|
|
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.
|
|
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",
|
package/src/apis/claw-cloud.ts
CHANGED
|
@@ -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
|
-
}
|
|
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
|
+
/**
|
|
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
|
+
}
|
package/src/apis/helpers.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/apis/honor-auth.ts
CHANGED
|
@@ -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
|
+
}
|