@honor-claw/yoyo 1.1.4-beta.6 → 1.1.4-beta.9
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/package.json +1 -1
- package/src/apis/claw-cloud.ts +74 -29
- package/src/apis/http-client.ts +2 -3
- package/src/apis/types.ts +36 -0
- package/src/cloud-channel/client.ts +2 -13
- package/src/cloud-channel/types.ts +1 -1
- package/src/commands/login/impl.ts +2 -7
- package/src/commands/status/index.ts +0 -30
- package/src/honor-auth/browser.ts +8 -8
- package/src/honor-auth/cloud.ts +5 -19
- package/src/honor-auth/honor-auth-client.ts +1 -57
- package/src/honor-auth/index.ts +1 -0
- package/src/honor-auth/token-manager.ts +89 -24
- package/src/honor-auth/types.ts +5 -20
- package/src/modules/configs/types.ts +1 -1
- package/src/modules/device/providers/pad.ts +63 -38
- package/src/modules/device/registry.ts +2 -14
- package/src/modules/login/impl.ts +5 -31
- package/src/types.ts +1 -3
- package/src/utils/env.ts +10 -2
package/package.json
CHANGED
package/src/apis/claw-cloud.ts
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
* Claw Cloud API 接口封装
|
|
3
|
-
*/
|
|
1
|
+
import { takeApiHost } from "../modules/configs/hosts.js";
|
|
4
2
|
import type { GatewayAuthConfig } from "../modules/configs/types.js";
|
|
5
3
|
import type { DeviceInfo, HonorUserInfo } from "../types.js";
|
|
6
4
|
import { uuid } from "../utils/id.js";
|
|
7
|
-
import { generateAuthorization } from "../utils/jwt.js";
|
|
8
5
|
import { HttpClient, type HttpResponse, type HttpClientOptions } from "./http-client.js";
|
|
9
6
|
import type {
|
|
10
7
|
DeviceRegistryHeaders,
|
|
@@ -13,7 +10,13 @@ import type {
|
|
|
13
10
|
UserLogoutHeaders,
|
|
14
11
|
UserLogoutRequest,
|
|
15
12
|
UserLogoutResponse,
|
|
13
|
+
ExchangeTokenRequest,
|
|
14
|
+
ExchangeTokenHeaders,
|
|
15
|
+
TokenResponseV2,
|
|
16
|
+
HttpApiWrapper,
|
|
17
|
+
ExchangeTokenParams,
|
|
16
18
|
} from "./types.js";
|
|
19
|
+
import { isOKResponse } from "./helpers.js";
|
|
17
20
|
|
|
18
21
|
/**
|
|
19
22
|
* Claw Cloud API 客户端
|
|
@@ -40,17 +43,10 @@ export class ClawCloudClient {
|
|
|
40
43
|
// 构造请求头
|
|
41
44
|
const headers: DeviceRegistryHeaders = {
|
|
42
45
|
"x-trace-id": uuid(),
|
|
46
|
+
"x-jwt-token": userInfo.token,
|
|
47
|
+
"x-device-id": deviceInfo.deviceId,
|
|
43
48
|
};
|
|
44
49
|
|
|
45
|
-
if (userInfo.userId) {
|
|
46
|
-
headers["x-agw-userId"] = userInfo.userId;
|
|
47
|
-
headers["authorization"] = generateAuthorization(userInfo.userId);
|
|
48
|
-
headers["x-udid"] = deviceInfo.deviceId;
|
|
49
|
-
} else if (userInfo.token) {
|
|
50
|
-
headers["x-jwt-token"] = userInfo.token;
|
|
51
|
-
headers["x-device-id"] = deviceInfo.deviceId;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
50
|
const requestBody: DeviceRegistryRequest = {
|
|
55
51
|
businessTag: "YOYO_CLAW",
|
|
56
52
|
role: "yoyoclaw",
|
|
@@ -81,24 +77,16 @@ export class ClawCloudClient {
|
|
|
81
77
|
// 构造请求头
|
|
82
78
|
const headers: UserLogoutHeaders = {
|
|
83
79
|
"x-trace-id": uuid(),
|
|
80
|
+
"x-jwt-token": userInfo.token,
|
|
81
|
+
"x-device-id": deviceInfo.deviceId,
|
|
84
82
|
};
|
|
85
83
|
|
|
86
|
-
// 如果有 userId,添加 x-agw-userId
|
|
87
|
-
if (userInfo.userId) {
|
|
88
|
-
headers["x-agw-userId"] = userInfo.userId;
|
|
89
|
-
headers["x-udid"] = deviceInfo.deviceId;
|
|
90
|
-
headers["authorization"] = generateAuthorization(userInfo.userId);
|
|
91
|
-
} else if (userInfo.token) {
|
|
92
|
-
headers["x-jwt-token"] = userInfo.token;
|
|
93
|
-
headers["x-device-id"] = deviceInfo.deviceId;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
84
|
const requestBody: UserLogoutRequest = {
|
|
97
85
|
businessTag: "YOYO_CLAW",
|
|
98
86
|
deviceInfo: {
|
|
99
87
|
...deviceInfo,
|
|
100
|
-
manufacture:
|
|
101
|
-
brand:
|
|
88
|
+
manufacture: deviceInfo.manufacture,
|
|
89
|
+
brand: deviceInfo.brand,
|
|
102
90
|
},
|
|
103
91
|
};
|
|
104
92
|
|
|
@@ -107,14 +95,71 @@ export class ClawCloudClient {
|
|
|
107
95
|
body: requestBody,
|
|
108
96
|
});
|
|
109
97
|
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 使用授权码换取Token
|
|
101
|
+
* @param deviceInfo 设备信息
|
|
102
|
+
* @param params 支持两种模式:
|
|
103
|
+
* 1. userId模式: { userId: string } - 通过 x-agw-userId header 认证
|
|
104
|
+
* 2. auth模式: { code: string; authConfig: HonorAuthConfig } - 通过 code + clientId 认证
|
|
105
|
+
* @returns Token响应
|
|
106
|
+
*/
|
|
107
|
+
async exchangeToken(
|
|
108
|
+
deviceInfo: DeviceInfo,
|
|
109
|
+
params: ExchangeTokenParams,
|
|
110
|
+
): Promise<TokenResponseV2> {
|
|
111
|
+
// 构建请求头
|
|
112
|
+
const headers: ExchangeTokenHeaders = {
|
|
113
|
+
"Content-Type": "application/json",
|
|
114
|
+
"x-device-id": deviceInfo.deviceId,
|
|
115
|
+
"x-trace-id": uuid(),
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
let data: ExchangeTokenRequest = {};
|
|
119
|
+
|
|
120
|
+
if ("userId" in params) {
|
|
121
|
+
// userId模式: 通过 header 认证,body为空
|
|
122
|
+
headers["x-agw-userId"] = params.userId;
|
|
123
|
+
} else {
|
|
124
|
+
// auth模式: 通过 code + clientId 认证
|
|
125
|
+
data = {
|
|
126
|
+
clientId: params.authConfig.clientId,
|
|
127
|
+
code: params.code,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const response = await this.httpClient.post<HttpApiWrapper<TokenResponseV2>>(
|
|
132
|
+
"/v1/user/jwtToken",
|
|
133
|
+
{
|
|
134
|
+
body: data,
|
|
135
|
+
headers: headers as unknown as Record<string, string>,
|
|
136
|
+
timeout: 15000,
|
|
137
|
+
},
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
if (isOKResponse(response) && response.data.data) {
|
|
141
|
+
return response.data.data;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
throw new Error(`failed to get token: ${response.data?.cnMessage}`);
|
|
145
|
+
}
|
|
110
146
|
}
|
|
111
147
|
|
|
112
148
|
/**
|
|
113
149
|
* 创建 Claw Cloud 客户端实例
|
|
114
150
|
*/
|
|
115
|
-
export function createClawCloudClient(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
151
|
+
export function createClawCloudClient(): ClawCloudClient {
|
|
152
|
+
const hosts = takeApiHost();
|
|
153
|
+
const baseUrl = `https://${hosts.clawCloud}/aicloud/yoyo-claw-service`;
|
|
154
|
+
|
|
155
|
+
// 如果有灰度标签,设置默认headers
|
|
156
|
+
const options = hosts.grayTag
|
|
157
|
+
? {
|
|
158
|
+
defaultHeaders: {
|
|
159
|
+
"x-gray": hosts.grayTag,
|
|
160
|
+
},
|
|
161
|
+
}
|
|
162
|
+
: undefined;
|
|
163
|
+
|
|
119
164
|
return new ClawCloudClient(baseUrl, options);
|
|
120
165
|
}
|
package/src/apis/http-client.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { request, type Dispatcher, ProxyAgent } from "undici";
|
|
7
|
+
import { wrapError } from "../utils/error.js";
|
|
7
8
|
import { getProxyUrl, shouldUseProxy } from "../utils/proxy.js";
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -170,9 +171,7 @@ export class HttpClient {
|
|
|
170
171
|
if (error instanceof HttpError) {
|
|
171
172
|
throw error;
|
|
172
173
|
}
|
|
173
|
-
throw
|
|
174
|
-
`HTTP request failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
175
|
-
);
|
|
174
|
+
throw wrapError(error, "HTTP request failed");
|
|
176
175
|
}
|
|
177
176
|
}
|
|
178
177
|
|
package/src/apis/types.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* 设备注册接口相关类型定义
|
|
3
3
|
*/
|
|
4
4
|
import type { ClawDeviceInfo, DeviceRole } from "../types.js";
|
|
5
|
+
import type { HonorAuthConfig } from "../honor-auth/types.js";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* 设备注册请求体
|
|
@@ -75,3 +76,38 @@ export interface UserLogoutHeaders {
|
|
|
75
76
|
authorization?: string;
|
|
76
77
|
"x-agw-userId"?: string;
|
|
77
78
|
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Token响应
|
|
82
|
+
*/
|
|
83
|
+
export type TokenResponseV2 = {
|
|
84
|
+
jwtToken?: string;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 换取Token请求体
|
|
89
|
+
*/
|
|
90
|
+
export interface ExchangeTokenRequest {
|
|
91
|
+
clientId?: string;
|
|
92
|
+
code?: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 换取Token参数 - 支持两种模式
|
|
97
|
+
* 1. userId模式: 通过 x-agw-userId header 认证,body为空
|
|
98
|
+
* 2. auth模式: 通过 code + clientId 认证
|
|
99
|
+
*/
|
|
100
|
+
export type ExchangeTokenParams =
|
|
101
|
+
| { userId: string }
|
|
102
|
+
| { code: string; authConfig: HonorAuthConfig };
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 换取Token请求头
|
|
106
|
+
*/
|
|
107
|
+
export interface ExchangeTokenHeaders {
|
|
108
|
+
"Content-Type": "application/json";
|
|
109
|
+
"x-device-id": string;
|
|
110
|
+
"x-trace-id": string;
|
|
111
|
+
'x-agw-userId'?: string;
|
|
112
|
+
"x-gray"?: string;
|
|
113
|
+
}
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import WebSocket from "ws";
|
|
6
6
|
import { uuid } from "../utils/id.js";
|
|
7
|
-
import { generateAuthorization } from "../utils/jwt.js";
|
|
8
7
|
import { useClawLogger } from "../utils/logger.js";
|
|
9
8
|
import { createWebSocketProxyAgent } from "../utils/proxy.js";
|
|
10
9
|
import { rawDataToString } from "../utils/ws.js";
|
|
@@ -69,20 +68,10 @@ export class ClawCloudSocketClient {
|
|
|
69
68
|
const headers: Record<string, string> = {
|
|
70
69
|
["x-role"]: "yoyoclaw",
|
|
71
70
|
["x-trace-id"]: traceId,
|
|
71
|
+
"x-jwt-token": userInfo.token,
|
|
72
|
+
"x-device-id": deviceInfo.deviceId,
|
|
72
73
|
};
|
|
73
74
|
|
|
74
|
-
if (deviceInfo.deviceId) {
|
|
75
|
-
headers["x-device-id"] = deviceInfo.deviceId;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (userInfo.userId) {
|
|
79
|
-
headers["x-agw-userId"] = userInfo.userId;
|
|
80
|
-
headers["authorization"] = generateAuthorization(userInfo.userId);
|
|
81
|
-
} else if (userInfo.token) {
|
|
82
|
-
// v2版本,基于jwt token
|
|
83
|
-
headers["x-jwt-token"] = userInfo.token;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
75
|
if (deviceInfo.port !== undefined) {
|
|
87
76
|
headers["x-port"] = String(deviceInfo.port);
|
|
88
77
|
}
|
|
@@ -19,7 +19,7 @@ export interface YOYOClawServiceEvent {
|
|
|
19
19
|
sourceDeviceInfo?: ClawDeviceInfo;
|
|
20
20
|
targetRole?: DeviceRole;
|
|
21
21
|
targetDeviceId: string;
|
|
22
|
-
traceInfo:
|
|
22
|
+
traceInfo: object;
|
|
23
23
|
port: number | string;
|
|
24
24
|
data?: string; // openclaw原生消息
|
|
25
25
|
msgType: "userMessage" | "devicePairMessage" | "deviceUnPairMessage" | "fetchContexts" | "updateContexts";
|
|
@@ -1,24 +1,19 @@
|
|
|
1
1
|
import { type Command } from "commander";
|
|
2
2
|
import { type OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
3
3
|
import { performLogin } from "../../modules/login/impl.js";
|
|
4
|
-
import { isBetaVersion } from "../../utils/version.js";
|
|
5
4
|
|
|
6
5
|
export function registerLoginCommand(api: OpenClawPluginApi, command: Command) {
|
|
7
6
|
let nextCommand = command.command("login").description("login to yoyoclaw and register devices");
|
|
8
7
|
|
|
9
|
-
if (isBetaVersion()) {
|
|
10
|
-
nextCommand = nextCommand.option("--skip-auth", "debug mode, no auth required");
|
|
11
|
-
}
|
|
12
|
-
|
|
13
8
|
nextCommand = nextCommand
|
|
14
9
|
.option("-u, --uid <userId>", "user ID for direct login")
|
|
15
10
|
.option("--token <token>", "token for direct login")
|
|
16
11
|
.action(async (options) => {
|
|
17
|
-
const { uid: userId, token
|
|
12
|
+
const { uid: userId, token } = options;
|
|
18
13
|
|
|
19
14
|
api.logger.debug?.("honor login CLI command called");
|
|
20
15
|
|
|
21
|
-
await performLogin({ userId, token
|
|
16
|
+
await performLogin({ userId, token });
|
|
22
17
|
});
|
|
23
18
|
|
|
24
19
|
return nextCommand;
|
|
@@ -111,36 +111,6 @@ function displayDetailedStatus(statusData: ConnectionStatusData | null): void {
|
|
|
111
111
|
console.log(` ${formatDateTime(statusData.updatedAt)}`);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
/**
|
|
115
|
-
* Get emoji for connection status
|
|
116
|
-
*/
|
|
117
|
-
function getStateEmoji(status: "idle" | "connecting" | "connected"): string {
|
|
118
|
-
switch (status) {
|
|
119
|
-
case "connected":
|
|
120
|
-
return "✅";
|
|
121
|
-
case "connecting":
|
|
122
|
-
return "⏳";
|
|
123
|
-
case "idle":
|
|
124
|
-
default:
|
|
125
|
-
return "⚪";
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Get text for connection status
|
|
131
|
-
*/
|
|
132
|
-
function getStateText(status: "idle" | "connecting" | "connected"): string {
|
|
133
|
-
switch (status) {
|
|
134
|
-
case "connected":
|
|
135
|
-
return "Connected";
|
|
136
|
-
case "connecting":
|
|
137
|
-
return "Connecting";
|
|
138
|
-
case "idle":
|
|
139
|
-
default:
|
|
140
|
-
return "Disconnected";
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
114
|
/**
|
|
145
115
|
* Format date time (YYYY-MM-DD HH:mm:ss)
|
|
146
116
|
*/
|
|
@@ -7,12 +7,13 @@ import { startCallbackServer } from "./callback-server.js";
|
|
|
7
7
|
import { getConfig } from "./config.js";
|
|
8
8
|
import { saveToken } from "./token-manager.js";
|
|
9
9
|
import type { HonorAuthConfig } from "./types.js";
|
|
10
|
+
import { createClawCloudClient } from "../apis/claw-cloud.js";
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* 执行OAuth2授权流程(自动打开浏览器)
|
|
13
14
|
*/
|
|
14
15
|
export async function performOAuth2AuthWithBrowser(
|
|
15
|
-
|
|
16
|
+
deviceInfo: DeviceInfo,
|
|
16
17
|
configOverrides?: Partial<HonorAuthConfig>,
|
|
17
18
|
) {
|
|
18
19
|
const config = getConfig(configOverrides);
|
|
@@ -63,19 +64,18 @@ export async function performOAuth2AuthWithBrowser(
|
|
|
63
64
|
|
|
64
65
|
// 使用授权码换取Token
|
|
65
66
|
try {
|
|
66
|
-
const
|
|
67
|
+
const client = createClawCloudClient();
|
|
68
|
+
const tokenInfo = await client.exchangeToken(deviceInfo, { code: receivedCode, authConfig: config });
|
|
67
69
|
|
|
68
|
-
if (!tokenInfo?.
|
|
69
|
-
throw new Error("failed to get
|
|
70
|
+
if (!tokenInfo?.jwtToken) {
|
|
71
|
+
throw new Error("failed to get jwt token");
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
// 保存Token
|
|
73
|
-
await saveToken(tokenInfo, config.saveToFile !== false);
|
|
75
|
+
await saveToken({ jwtToken: tokenInfo.jwtToken }, config.saveToFile !== false);
|
|
74
76
|
|
|
75
77
|
return {
|
|
76
|
-
|
|
77
|
-
token: tokenInfo.token,
|
|
78
|
-
userName: tokenInfo.userInfo.displayName,
|
|
78
|
+
token: tokenInfo.jwtToken
|
|
79
79
|
} as HonorUserInfo;
|
|
80
80
|
} catch (error) {
|
|
81
81
|
throw wrapError(error, "get token failed");
|
package/src/honor-auth/cloud.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createClawCloudClient } from "../apis/claw-cloud.js";
|
|
2
2
|
import { isOKResponse } from "../apis/index.js";
|
|
3
|
-
import { getConfigManager
|
|
3
|
+
import { getConfigManager } from "../modules/configs/index.js";
|
|
4
4
|
import { getDeviceInfo } from "../modules/device/device-info.js";
|
|
5
5
|
import type { HonorUserInfo } from "../types.js";
|
|
6
|
+
import { wrapError } from "../utils/error.js";
|
|
6
7
|
import { clearToken } from "./token-manager.js";
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -13,7 +14,7 @@ export async function performLogout(): Promise<void> {
|
|
|
13
14
|
const configManager = getConfigManager();
|
|
14
15
|
const userConfig = configManager.getUserConfig();
|
|
15
16
|
|
|
16
|
-
if (!userConfig
|
|
17
|
+
if (!userConfig?.token) {
|
|
17
18
|
console.log("⚠️ Not logged in");
|
|
18
19
|
return;
|
|
19
20
|
}
|
|
@@ -22,24 +23,10 @@ export async function performLogout(): Promise<void> {
|
|
|
22
23
|
const deviceInfo = await getDeviceInfo();
|
|
23
24
|
|
|
24
25
|
// 调用登出接口
|
|
25
|
-
const
|
|
26
|
-
const baseUrl = `https://${hosts.clawCloud}/aicloud/yoyo-claw-service`;
|
|
27
|
-
|
|
28
|
-
// 如果有灰度标签,设置默认headers
|
|
29
|
-
const options = hosts.grayTag
|
|
30
|
-
? {
|
|
31
|
-
defaultHeaders: {
|
|
32
|
-
"x-gray": hosts.grayTag,
|
|
33
|
-
},
|
|
34
|
-
}
|
|
35
|
-
: undefined;
|
|
36
|
-
|
|
37
|
-
const client = createClawCloudClient(baseUrl, options);
|
|
26
|
+
const client = createClawCloudClient();
|
|
38
27
|
|
|
39
28
|
const userInfo: HonorUserInfo = {
|
|
40
|
-
userId: userConfig.userId,
|
|
41
29
|
token: userConfig.token,
|
|
42
|
-
userName: userConfig.userName,
|
|
43
30
|
};
|
|
44
31
|
|
|
45
32
|
const response = await client.logoutDevice(deviceInfo, userInfo);
|
|
@@ -51,7 +38,6 @@ export async function performLogout(): Promise<void> {
|
|
|
51
38
|
// 清除Token
|
|
52
39
|
await clearToken();
|
|
53
40
|
} catch (error) {
|
|
54
|
-
|
|
55
|
-
throw new Error(`failed to logout: ${errorMessage}`);
|
|
41
|
+
throw wrapError(error, "failed to logout");
|
|
56
42
|
}
|
|
57
43
|
}
|
|
@@ -4,13 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { createHash, randomBytes } from "crypto";
|
|
7
|
-
import type {
|
|
8
|
-
import { takeApiHost } from "../modules/configs/hosts.js";
|
|
9
|
-
import { wrapError } from "../utils/error.js";
|
|
10
|
-
import { uuid } from "../utils/id.js";
|
|
11
|
-
import { isOKResponse } from "../apis/helpers.js";
|
|
12
|
-
import { HttpClient, type HttpClientOptions } from "../apis/http-client.js";
|
|
13
|
-
import type { HttpApiWrapper } from "../apis/types.js";
|
|
7
|
+
import type { HonorAuthConfig } from "./types.js";
|
|
14
8
|
|
|
15
9
|
/**
|
|
16
10
|
* PKCE参数
|
|
@@ -24,16 +18,10 @@ export interface PKCEParams {
|
|
|
24
18
|
* 荣耀认证客户端
|
|
25
19
|
*/
|
|
26
20
|
export class HonorAuthClient {
|
|
27
|
-
private httpClient: HttpClient;
|
|
28
21
|
private config: HonorAuthConfig;
|
|
29
22
|
|
|
30
23
|
constructor(config: HonorAuthConfig) {
|
|
31
24
|
this.config = config;
|
|
32
|
-
// 从 HonorAuthConfig 中提取 HttpClientOptions
|
|
33
|
-
const httpClientOptions: HttpClientOptions = {
|
|
34
|
-
proxy: config.proxy,
|
|
35
|
-
};
|
|
36
|
-
this.httpClient = new HttpClient(config.authHost, httpClientOptions);
|
|
37
25
|
}
|
|
38
26
|
|
|
39
27
|
/**
|
|
@@ -99,50 +87,6 @@ export class HonorAuthClient {
|
|
|
99
87
|
return `${this.config.authHost}/oauth2/v3/authorize?${params.toString()}`;
|
|
100
88
|
}
|
|
101
89
|
|
|
102
|
-
/**
|
|
103
|
-
* 使用授权码换取Token
|
|
104
|
-
*/
|
|
105
|
-
async exchangeToken(code: string) {
|
|
106
|
-
const hosts = takeApiHost();
|
|
107
|
-
const tokenUrl = `https://${hosts.ics}/honorboard/auth/v1/oauth/jwtToken`;
|
|
108
|
-
|
|
109
|
-
const data = {
|
|
110
|
-
clientId: this.config.clientId,
|
|
111
|
-
code,
|
|
112
|
-
redirectUri: this.config.redirectUri,
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
try {
|
|
116
|
-
// 构建请求头,包含灰度标签(如果有)
|
|
117
|
-
const headers: Record<string, string> = {
|
|
118
|
-
"Content-Type": "application/json",
|
|
119
|
-
"x-origin-udid": this.config.clientId,
|
|
120
|
-
"x-app-pkg": "com.hihonor.pc.openclaw",
|
|
121
|
-
"x-request-nonce": uuid(),
|
|
122
|
-
"x-timestamp": String(Date.now()),
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
// 添加灰度标签
|
|
126
|
-
if (hosts.grayTag) {
|
|
127
|
-
headers["x-gray"] = hosts.grayTag;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const response = await this.httpClient.post<HttpApiWrapper<TokenResponse>>(tokenUrl, {
|
|
131
|
-
body: data,
|
|
132
|
-
headers,
|
|
133
|
-
timeout: 15000,
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
if (isOKResponse(response)) {
|
|
137
|
-
return response.data.data;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
throw new Error(`failed to get token: ${response.data?.cnMessage}`);
|
|
141
|
-
} catch (error) {
|
|
142
|
-
throw wrapError(error, "failed to get token");
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
90
|
/**
|
|
147
91
|
* 获取配置
|
|
148
92
|
*/
|
package/src/honor-auth/index.ts
CHANGED
|
@@ -2,65 +2,130 @@
|
|
|
2
2
|
* Token管理器 - 负责token的读写缓存
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { createClawCloudClient } from "../apis/claw-cloud.js";
|
|
5
6
|
import { getConfigManager } from "../modules/configs/index.js";
|
|
7
|
+
import { getDeviceInfo } from "../modules/device/index.js";
|
|
6
8
|
import type { HonorUserInfo } from "../types.js";
|
|
9
|
+
import { getEnvUserId } from "../utils/env.js";
|
|
10
|
+
import { wrapError } from "../utils/error.js";
|
|
7
11
|
import { useClawLogger } from "../utils/logger.js";
|
|
8
|
-
import
|
|
12
|
+
import { SaveTokenParams } from "./types.js";
|
|
9
13
|
|
|
10
14
|
/**
|
|
11
|
-
* 保存Token到openclaw
|
|
15
|
+
* 保存Token到openclaw配置文件(支持UserId/token双模)
|
|
16
|
+
* - 如果有 jwtToken,直接保存
|
|
17
|
+
* - 如果只有 userId,调用 exchangeToken 通过 userId 换取 jwtToken
|
|
12
18
|
*/
|
|
13
|
-
export async function saveToken(
|
|
19
|
+
export async function saveToken(
|
|
20
|
+
params: SaveTokenParams,
|
|
21
|
+
saveToFile: boolean = true,
|
|
22
|
+
): Promise<HonorUserInfo> {
|
|
14
23
|
try {
|
|
24
|
+
const configManager = getConfigManager();
|
|
25
|
+
let jwtToken = params.jwtToken;
|
|
26
|
+
|
|
27
|
+
// 如果没有 jwtToken 但有 userId,通过 exchangeToken 获取
|
|
28
|
+
if (!jwtToken && params.userId) {
|
|
29
|
+
const deviceInfo = params.deviceInfo || (await getDeviceInfo());
|
|
30
|
+
const client = createClawCloudClient();
|
|
31
|
+
const tokenInfo = await client.exchangeToken(deviceInfo, { userId: params.userId });
|
|
32
|
+
jwtToken = tokenInfo.jwtToken;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!jwtToken) {
|
|
36
|
+
throw new Error("no token available");
|
|
37
|
+
}
|
|
38
|
+
|
|
15
39
|
if (!saveToFile) {
|
|
16
40
|
useClawLogger().info("💾 token got, but not to save");
|
|
17
|
-
return;
|
|
41
|
+
return { token: jwtToken };
|
|
18
42
|
}
|
|
19
43
|
|
|
20
|
-
const configManager = getConfigManager();
|
|
21
|
-
|
|
22
44
|
// 计算过期时间(假设token有效期为30天)
|
|
23
45
|
const expired = Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60;
|
|
24
46
|
|
|
25
47
|
// 更新用户配置
|
|
26
48
|
await configManager.updateUserConfig({
|
|
27
|
-
token:
|
|
49
|
+
token: jwtToken,
|
|
50
|
+
userId: params.userId,
|
|
28
51
|
expired,
|
|
29
|
-
userId: token.userInfo?.userId,
|
|
30
|
-
userName: token.userInfo?.displayName,
|
|
31
52
|
});
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
token: jwtToken,
|
|
56
|
+
};
|
|
32
57
|
} catch (error) {
|
|
33
|
-
throw
|
|
58
|
+
throw wrapError(error, "保存Token失败");
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 刷新并保存Token
|
|
64
|
+
*/
|
|
65
|
+
async function refreshToken(userId: string): Promise<HonorUserInfo | null> {
|
|
66
|
+
const deviceInfo = await getDeviceInfo();
|
|
67
|
+
const client = createClawCloudClient();
|
|
68
|
+
const tokenInfo = await client.exchangeToken(deviceInfo, { userId });
|
|
69
|
+
|
|
70
|
+
if (tokenInfo.jwtToken) {
|
|
71
|
+
const expired = Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60;
|
|
72
|
+
await getConfigManager().updateUserConfig({
|
|
73
|
+
token: tokenInfo.jwtToken,
|
|
74
|
+
userId: undefined,
|
|
75
|
+
userName: undefined,
|
|
76
|
+
expired,
|
|
77
|
+
});
|
|
78
|
+
return { token: tokenInfo.jwtToken };
|
|
34
79
|
}
|
|
80
|
+
await getConfigManager().clearUserConfig();
|
|
81
|
+
return null;
|
|
35
82
|
}
|
|
36
83
|
|
|
37
84
|
/**
|
|
38
85
|
* 从openclaw配置文件加载Token
|
|
86
|
+
* - 配置有 userId 时,优先使用 env userId 换取 token
|
|
87
|
+
* - 有 token 且未过期,直接返回
|
|
39
88
|
*/
|
|
40
89
|
export async function loadToken(): Promise<HonorUserInfo | null> {
|
|
90
|
+
const logger = useClawLogger();
|
|
91
|
+
|
|
41
92
|
try {
|
|
42
93
|
const configManager = getConfigManager();
|
|
43
94
|
const userConfig = configManager.getUserConfig();
|
|
95
|
+
const envUserId = getEnvUserId();
|
|
44
96
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
97
|
+
logger.debug?.(
|
|
98
|
+
`[yoyoclaw-auth] env userId: ${envUserId ? "present" : "absent"}, config userId: ${userConfig?.userId ? "present" : "absent"}, config token: ${userConfig?.token ? "present" : "absent"}`,
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// 配置有 userId,优先使用 env userId 换取 token
|
|
102
|
+
if (userConfig?.userId) {
|
|
103
|
+
return await refreshToken(envUserId || userConfig.userId);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 有 token 且未过期,直接返回
|
|
107
|
+
if (userConfig?.token) {
|
|
108
|
+
if (!userConfig.expired || userConfig.expired >= Math.floor(Date.now() / 1000)) {
|
|
109
|
+
return { token: userConfig.token };
|
|
110
|
+
}
|
|
111
|
+
logger.debug?.("[yoyoclaw-auth] cached token expired");
|
|
48
112
|
}
|
|
49
113
|
|
|
50
|
-
//
|
|
51
|
-
if (
|
|
52
|
-
|
|
114
|
+
// 没有token或者已经过期,拿envUserId重新获取
|
|
115
|
+
if (envUserId) {
|
|
116
|
+
logger.debug?.("[yoyoclaw-auth] token expired, using env userId to exchange token");
|
|
117
|
+
return await refreshToken(envUserId);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 没有可用的鉴权信息且token过期了,清除token信息
|
|
121
|
+
if (userConfig?.token) {
|
|
122
|
+
logger.debug?.("[yoyoclaw-auth] clearing expired token");
|
|
53
123
|
await configManager.clearUserConfig();
|
|
54
|
-
return null;
|
|
55
124
|
}
|
|
56
125
|
|
|
57
|
-
return
|
|
58
|
-
userId: userConfig.userId || "",
|
|
59
|
-
token: userConfig.token,
|
|
60
|
-
userName: userConfig.userName,
|
|
61
|
-
};
|
|
126
|
+
return null;
|
|
62
127
|
} catch (error) {
|
|
63
|
-
throw
|
|
128
|
+
throw wrapError(error, "加载Token失败");
|
|
64
129
|
}
|
|
65
130
|
}
|
|
66
131
|
|
|
@@ -73,6 +138,6 @@ export async function clearToken(): Promise<void> {
|
|
|
73
138
|
await configManager.clearUserConfig();
|
|
74
139
|
useClawLogger().info("[yoyoclaw-auth] token cleared");
|
|
75
140
|
} catch (error) {
|
|
76
|
-
throw
|
|
141
|
+
throw wrapError(error, "清除Token失败");
|
|
77
142
|
}
|
|
78
143
|
}
|
package/src/honor-auth/types.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* 荣耀OAuth2认证相关类型定义
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { DeviceInfo } from "../types.js";
|
|
6
|
+
|
|
5
7
|
/**
|
|
6
8
|
* 荣耀认证配置
|
|
7
9
|
*/
|
|
@@ -26,25 +28,8 @@ export interface HonorAuthConfig {
|
|
|
26
28
|
saveToFile?: boolean;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
*/
|
|
32
|
-
export type TokenResponse = {
|
|
33
|
-
token?: string;
|
|
34
|
-
userInfo?: ApiUserInfo;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Token响应
|
|
39
|
-
*/
|
|
40
|
-
export type TokenResponseV2 = {
|
|
31
|
+
export interface SaveTokenParams {
|
|
32
|
+
userId?: string;
|
|
41
33
|
jwtToken?: string;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* 用户信息
|
|
46
|
-
*/
|
|
47
|
-
export interface ApiUserInfo {
|
|
48
|
-
userId: string;
|
|
49
|
-
displayName: string;
|
|
34
|
+
deviceInfo?: DeviceInfo;
|
|
50
35
|
}
|
|
@@ -1,72 +1,99 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFile } from "child_process";
|
|
2
2
|
import { createHash } from "crypto";
|
|
3
3
|
import fs from "fs";
|
|
4
4
|
import os from "os";
|
|
5
|
+
import { promisify } from "util";
|
|
5
6
|
import type { DeviceType } from "../../../types.js";
|
|
6
7
|
import type { DeviceInfoProvider } from "./base.js";
|
|
7
8
|
|
|
9
|
+
const execFileAsync = promisify(execFile);
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 设备信息缓存
|
|
13
|
+
*/
|
|
14
|
+
interface DeviceInfoCache {
|
|
15
|
+
androidId: string;
|
|
16
|
+
brand: string;
|
|
17
|
+
model: string;
|
|
18
|
+
device: string;
|
|
19
|
+
deviceId: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
8
22
|
/**
|
|
9
23
|
* Pad 设备信息提供者
|
|
10
24
|
*/
|
|
11
25
|
export class PadDeviceInfoProvider implements DeviceInfoProvider {
|
|
26
|
+
private cache: DeviceInfoCache = {
|
|
27
|
+
androidId: "",
|
|
28
|
+
brand: "",
|
|
29
|
+
model: "",
|
|
30
|
+
device: "",
|
|
31
|
+
deviceId: "",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
private initPromise: Promise<void>;
|
|
35
|
+
|
|
36
|
+
constructor() {
|
|
37
|
+
this.initPromise = this._initializeCache();
|
|
38
|
+
}
|
|
39
|
+
|
|
12
40
|
/**
|
|
13
|
-
* 确保 provider
|
|
41
|
+
* 确保 provider 初始化完成(异步预取设备信息)
|
|
14
42
|
*/
|
|
15
43
|
async ensureInitialized(): Promise<void> {
|
|
16
|
-
|
|
44
|
+
await this.initPromise;
|
|
17
45
|
}
|
|
18
46
|
|
|
19
47
|
/**
|
|
20
|
-
*
|
|
48
|
+
* 异步执行 Android shell 命令
|
|
21
49
|
*/
|
|
22
|
-
private
|
|
50
|
+
private async execAndroidCmd(cmd: string, timeout = 5000): Promise<string> {
|
|
23
51
|
try {
|
|
24
|
-
const
|
|
52
|
+
const { stdout } = await execFileAsync("/system/bin/sh", ["-c", cmd], {
|
|
53
|
+
timeout,
|
|
25
54
|
encoding: "utf-8",
|
|
26
|
-
timeout: 5000,
|
|
27
55
|
});
|
|
28
|
-
|
|
29
|
-
return value || "";
|
|
56
|
+
return stdout.trim();
|
|
30
57
|
} catch {
|
|
31
58
|
return "";
|
|
32
59
|
}
|
|
33
60
|
}
|
|
34
61
|
|
|
35
62
|
/**
|
|
36
|
-
*
|
|
63
|
+
* 初始化缓存(异步预取所有设备信息)
|
|
37
64
|
*/
|
|
38
|
-
private
|
|
65
|
+
private async _initializeCache(): Promise<void> {
|
|
39
66
|
try {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
67
|
+
// 并行获取所有属性
|
|
68
|
+
const [androidId, brand, model, device] = await Promise.all([
|
|
69
|
+
this.execAndroidCmd("/system/bin/settings get secure android_id"),
|
|
70
|
+
this.execAndroidCmd("/system/bin/getprop ro.product.brand"),
|
|
71
|
+
this.execAndroidCmd("/system/bin/getprop ro.product.model"),
|
|
72
|
+
this.execAndroidCmd("/system/bin/getprop ro.product.device"),
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
this.cache.androidId = androidId && androidId !== "null" ? androidId : "";
|
|
76
|
+
this.cache.brand = brand || "";
|
|
77
|
+
this.cache.model = model || "";
|
|
78
|
+
this.cache.device = device || "";
|
|
79
|
+
|
|
80
|
+
// 计算 deviceId
|
|
81
|
+
const raw = `${this.cache.androidId}_${this.cache.brand}_${this.cache.model}_${this.cache.device}`;
|
|
82
|
+
this.cache.deviceId = createHash("sha256").update(raw, "utf-8").digest("hex");
|
|
46
83
|
} catch {
|
|
47
|
-
|
|
84
|
+
// 初始化失败,保持默认值
|
|
48
85
|
}
|
|
49
86
|
}
|
|
50
87
|
|
|
51
88
|
/**
|
|
52
|
-
* 获取 Android
|
|
53
|
-
*/
|
|
54
|
-
private getAndroidDeviceId(): string {
|
|
55
|
-
const androidId = this.getAndroidId();
|
|
56
|
-
const brand = this.getAndroidProp("ro.product.brand");
|
|
57
|
-
const model = this.getAndroidProp("ro.product.model");
|
|
58
|
-
const device = this.getAndroidProp("ro.product.device");
|
|
59
|
-
|
|
60
|
-
const raw = `${androidId}_${brand}_${model}_${device}`;
|
|
61
|
-
return createHash("sha256").update(raw, "utf-8").digest("hex");
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* 获取 Android 设备型号
|
|
89
|
+
* 获取 Android 设备型号(兜底读取 build.prop)
|
|
66
90
|
*/
|
|
67
91
|
private getAndroidDeviceModel(): string {
|
|
92
|
+
if (this.cache.model) {
|
|
93
|
+
return this.cache.model;
|
|
94
|
+
}
|
|
95
|
+
|
|
68
96
|
try {
|
|
69
|
-
// 尝试直接解析系统属性文件
|
|
70
97
|
const buildPropPath = "/system/build.prop";
|
|
71
98
|
if (fs.existsSync(buildPropPath)) {
|
|
72
99
|
const content = fs.readFileSync(buildPropPath, "utf8");
|
|
@@ -79,7 +106,6 @@ export class PadDeviceInfoProvider implements DeviceInfoProvider {
|
|
|
79
106
|
// ignore
|
|
80
107
|
}
|
|
81
108
|
|
|
82
|
-
// 兜底方案:使用架构信息
|
|
83
109
|
return `Android(${os.arch()})`;
|
|
84
110
|
}
|
|
85
111
|
|
|
@@ -94,8 +120,8 @@ export class PadDeviceInfoProvider implements DeviceInfoProvider {
|
|
|
94
120
|
* 获取设备唯一ID(异步)
|
|
95
121
|
*/
|
|
96
122
|
async getDeviceIdAsync(): Promise<string> {
|
|
97
|
-
|
|
98
|
-
return deviceId || "invalid";
|
|
123
|
+
await this.initPromise;
|
|
124
|
+
return this.cache.deviceId || "invalid";
|
|
99
125
|
}
|
|
100
126
|
|
|
101
127
|
/**
|
|
@@ -123,8 +149,7 @@ export class PadDeviceInfoProvider implements DeviceInfoProvider {
|
|
|
123
149
|
* 获取设备品牌
|
|
124
150
|
*/
|
|
125
151
|
getDeviceBrand(): string {
|
|
126
|
-
const brand = this.
|
|
127
|
-
|
|
152
|
+
const brand = this.cache.brand;
|
|
128
153
|
return brand.toLowerCase() === "honor" ? "HONOR" : "";
|
|
129
154
|
}
|
|
130
155
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createClawCloudClient } from "../../apis/claw-cloud.js";
|
|
2
2
|
import { isOKResponse } from "../../apis/index.js";
|
|
3
3
|
import type { DeviceInfo, HonorUserInfo } from "../../types.js";
|
|
4
|
-
import { getConfigManager
|
|
4
|
+
import { getConfigManager } from "../configs/index.js";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* 注册设备到 Claw Cloud
|
|
@@ -16,19 +16,7 @@ export async function registerDevice(deviceInfo: DeviceInfo, userInfo: HonorUser
|
|
|
16
16
|
const gatewayAuthConfig = configManager.getGatewayAuthConfig();
|
|
17
17
|
|
|
18
18
|
// 创建 Claw Cloud 客户端
|
|
19
|
-
const
|
|
20
|
-
const baseUrl = `https://${hosts.clawCloud}/aicloud/yoyo-claw-service`;
|
|
21
|
-
|
|
22
|
-
// 如果有灰度标签,设置默认headers
|
|
23
|
-
const options = hosts.grayTag
|
|
24
|
-
? {
|
|
25
|
-
defaultHeaders: {
|
|
26
|
-
"x-gray": hosts.grayTag,
|
|
27
|
-
},
|
|
28
|
-
}
|
|
29
|
-
: undefined;
|
|
30
|
-
|
|
31
|
-
const client = createClawCloudClient(baseUrl, options);
|
|
19
|
+
const client = createClawCloudClient();
|
|
32
20
|
|
|
33
21
|
// 调用注册接口,传入认证信息
|
|
34
22
|
const response = await client.registerDevice(deviceInfo, userInfo, gatewayAuthConfig);
|
|
@@ -12,8 +12,6 @@ import { getDeviceInfo, registerDevice } from "../device/index.js";
|
|
|
12
12
|
* 登录选项
|
|
13
13
|
*/
|
|
14
14
|
export interface LoginOptions {
|
|
15
|
-
/** 是否跳过认证流程,默认为 false */
|
|
16
|
-
noAuth?: boolean;
|
|
17
15
|
/** 用户 ID,如果提供则直接使用此 ID 登录 */
|
|
18
16
|
userId?: string;
|
|
19
17
|
/** Token,如果提供则直接使用此 Token 登录 */
|
|
@@ -30,7 +28,7 @@ export interface LoginOptions {
|
|
|
30
28
|
* @param options 登录选项
|
|
31
29
|
*/
|
|
32
30
|
export async function performLogin(options: LoginOptions = {}) {
|
|
33
|
-
const {
|
|
31
|
+
const { userId } = options;
|
|
34
32
|
const logger = useClawLogger();
|
|
35
33
|
try {
|
|
36
34
|
logger.debug?.("Starting login process...");
|
|
@@ -53,43 +51,19 @@ export async function performLogin(options: LoginOptions = {}) {
|
|
|
53
51
|
let userInfo: HonorUserInfo;
|
|
54
52
|
// 检查是否提供了 userId 参数
|
|
55
53
|
if (userId) {
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
logger.debug?.("Using provided userId for direct login...");
|
|
59
|
-
userInfo = {
|
|
54
|
+
// 保存 Token,内部会基于userId换token
|
|
55
|
+
userInfo = await saveToken({
|
|
60
56
|
userId,
|
|
61
|
-
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
// 保存 Token
|
|
65
|
-
await saveToken({
|
|
66
|
-
token: authToken,
|
|
67
|
-
userInfo: { userId, displayName: "" },
|
|
57
|
+
deviceInfo,
|
|
68
58
|
});
|
|
69
59
|
logger.debug?.("Using provided user info");
|
|
70
|
-
logger.debug?.(`User: ${
|
|
71
|
-
logger.debug?.(`Device ID: ${deviceInfo?.deviceId}`);
|
|
72
|
-
} else if (noAuth) {
|
|
73
|
-
logger.debug?.("Skipping OAuth2 auth, using test user info...");
|
|
74
|
-
userInfo = {
|
|
75
|
-
userId: "test",
|
|
76
|
-
token: "",
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
// 保存 Token
|
|
80
|
-
await saveToken({
|
|
81
|
-
token: "test",
|
|
82
|
-
userInfo: { userId: "test", displayName: "Test User" },
|
|
83
|
-
});
|
|
84
|
-
logger.debug?.("Using test user info");
|
|
85
|
-
logger.debug?.(`User: ${userInfo.userId}`);
|
|
60
|
+
logger.debug?.(`User: ${userId}`);
|
|
86
61
|
logger.debug?.(`Device ID: ${deviceInfo?.deviceId}`);
|
|
87
62
|
} else {
|
|
88
63
|
logger.debug?.("Starting OAuth2 authentication...");
|
|
89
64
|
userInfo = await performOAuth2AuthWithBrowser(deviceInfo);
|
|
90
65
|
|
|
91
66
|
logger.debug?.("OAuth2 authentication successful");
|
|
92
|
-
logger.debug?.(`User: ${userInfo?.userId || "Unknown"}`);
|
|
93
67
|
logger.debug?.(`Device ID: ${deviceInfo?.deviceId}`);
|
|
94
68
|
}
|
|
95
69
|
|
package/src/types.ts
CHANGED
package/src/utils/env.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 环境变量工具模块
|
|
3
3
|
*/
|
|
4
|
-
|
|
5
4
|
const DEVICE_ENV_VARS = {
|
|
6
5
|
brand: "YOYO_CLAW_BRAND",
|
|
7
6
|
manufacture: "YOYO_CLAW_MANUFACTURER",
|
|
8
7
|
deviceType: "YOYO_CLAW_DEVICE_TYPE",
|
|
8
|
+
userId: "YOYO_CLAW_USER_ID",
|
|
9
9
|
} as const;
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -15,6 +15,7 @@ export interface DeviceEnvVars {
|
|
|
15
15
|
brand?: string;
|
|
16
16
|
manufacture?: string;
|
|
17
17
|
deviceType?: string;
|
|
18
|
+
userId?: string;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
/**
|
|
@@ -24,6 +25,13 @@ export function getDeviceEnvVars(): DeviceEnvVars {
|
|
|
24
25
|
return {
|
|
25
26
|
brand: process.env[DEVICE_ENV_VARS.brand],
|
|
26
27
|
manufacture: process.env[DEVICE_ENV_VARS.manufacture],
|
|
27
|
-
deviceType: process.env[DEVICE_ENV_VARS.deviceType]
|
|
28
|
+
deviceType: process.env[DEVICE_ENV_VARS.deviceType]
|
|
28
29
|
};
|
|
29
30
|
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 获取用户ID
|
|
34
|
+
*/
|
|
35
|
+
export function getEnvUserId(): string | undefined {
|
|
36
|
+
return process.env[DEVICE_ENV_VARS.userId];
|
|
37
|
+
}
|