@honor-claw/yoyo 1.2.1-beta.0 → 1.2.1-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 +2 -1
- package/package.json +2 -3
- package/src/commands/logout/impl.ts +0 -7
- package/src/honor-auth/callback-server.ts +30 -7
- package/src/honor-auth/token-manager.ts +3 -11
- package/src/modules/configs/identity-persist.ts +3 -43
- package/src/modules/device/device-info.ts +4 -14
- package/src/modules/device/identity.ts +6 -18
- package/src/modules/device/providers/base.ts +0 -5
- package/src/modules/device/providers/linux.ts +0 -55
- package/src/modules/device/providers/macos.ts +0 -66
- package/src/modules/device/providers/pad.ts +1 -20
- package/src/modules/device/providers/windows.ts +1 -25
- package/src/modules/login/impl.ts +1 -14
package/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
2
|
+
import { type ZodType } from "zod";
|
|
2
3
|
import { registerCommands } from "./src/commands/index.js";
|
|
3
4
|
import { AGENT_PROMPT } from "./src/modules/prompt/index.js";
|
|
4
5
|
import { setYoyoRuntime } from "./src/runtime.js";
|
|
@@ -10,7 +11,7 @@ const plugin = {
|
|
|
10
11
|
id: "yoyo",
|
|
11
12
|
name: "YOYOClaw",
|
|
12
13
|
description: "OpenClaw Honor Yoyo connection plugin",
|
|
13
|
-
configSchema: YoyoPluginConfigSchema,
|
|
14
|
+
configSchema: YoyoPluginConfigSchema as ZodType,
|
|
14
15
|
register(api: OpenClawPluginApi) {
|
|
15
16
|
setYoyoRuntime(api.runtime);
|
|
16
17
|
setClawLogger(api.logger);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@honor-claw/yoyo",
|
|
3
|
-
"version": "1.2.1-beta.
|
|
3
|
+
"version": "1.2.1-beta.1",
|
|
4
4
|
"description": "OpenClaw Honor Yoyo connection plugin",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -31,7 +31,6 @@
|
|
|
31
31
|
"test:unit:watch": "vitest"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@honor-claw/safe-exec": "0.0.1",
|
|
35
34
|
"http-proxy-agent": "^8.0.0",
|
|
36
35
|
"https-proxy-agent": "^8.0.0",
|
|
37
36
|
"jsonwebtoken": "^9.0.3",
|
|
@@ -48,7 +47,7 @@
|
|
|
48
47
|
"@types/ws": "^8.5.13",
|
|
49
48
|
"@vitest/coverage-v8": "^2.1.8",
|
|
50
49
|
"commander": "^14.0.3",
|
|
51
|
-
"typescript": "^
|
|
50
|
+
"typescript": "^6.0.2",
|
|
52
51
|
"vitest": "^2.1.8"
|
|
53
52
|
},
|
|
54
53
|
"openclaw": {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { type Command } from "commander";
|
|
2
2
|
import { type OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
3
3
|
import { performLogout } from "../../honor-auth/cloud.js";
|
|
4
|
-
import { upgradeIdentityToV2 } from "../../modules/configs/identity-persist.js";
|
|
5
4
|
|
|
6
5
|
export function registerLogoutCommand(api: OpenClawPluginApi, command: Command) {
|
|
7
6
|
const nextCommand = command
|
|
@@ -13,12 +12,6 @@ export function registerLogoutCommand(api: OpenClawPluginApi, command: Command)
|
|
|
13
12
|
try {
|
|
14
13
|
await performLogout();
|
|
15
14
|
|
|
16
|
-
// 升级 Identity 配置到 v2 版本
|
|
17
|
-
const upgraded = await upgradeIdentityToV2();
|
|
18
|
-
if (upgraded) {
|
|
19
|
-
api.logger.info("Identity upgraded from v1 to v2 during logout");
|
|
20
|
-
}
|
|
21
|
-
|
|
22
15
|
console.log(
|
|
23
16
|
"✅ Logout successful, gateway will automatically restart to handle new configuration",
|
|
24
17
|
);
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
* 本地HTTP回调服务器 - 接收OAuth2授权回调
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import * as fs from "fs";
|
|
5
6
|
import { createServer, type IncomingMessage, type ServerResponse } from "http";
|
|
7
|
+
import * as path from "path";
|
|
6
8
|
import { URL } from "url";
|
|
7
9
|
|
|
8
10
|
/**
|
|
@@ -19,6 +21,21 @@ export interface CallbackServerOptions {
|
|
|
19
21
|
onError?: (error: Error) => void;
|
|
20
22
|
}
|
|
21
23
|
|
|
24
|
+
const SUCCESS_ICON_SVG = `<svg width="56" height="56" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M28 56C43.464 56 56 43.464 56 28C56 12.536 43.464 0 28 0C12.536 0 0 12.536 0 28C0 43.464 12.536 56 28 56Z" fill="#28a745"/><path d="M39.6667 20.3333L24.5 35.5L16.3333 27.3333" stroke="white" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
|
|
25
|
+
const FAILURE_ICON_SVG = `<svg width="56" height="56" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M28 56C43.464 56 56 43.464 56 28C56 12.536 43.464 0 28 0C12.536 0 0 12.536 0 28C0 43.464 12.536 56 28 56Z" fill="#dc3545"/><path d="M35 21L21 35M21 21L35 35" stroke="white" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
|
|
26
|
+
|
|
27
|
+
function getResultHtml(message: string, success: boolean): string {
|
|
28
|
+
try {
|
|
29
|
+
const template = fs.readFileSync(path.join(__dirname, "auth-result.html"), "utf-8");
|
|
30
|
+
const icon = success ? SUCCESS_ICON_SVG : FAILURE_ICON_SVG;
|
|
31
|
+
return template.replace("{{MESSAGE}}", message).replace("{{ICON_SVG}}", icon);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
// Fallback to simple text
|
|
34
|
+
console.error("Failed to read auth-result.html:", error);
|
|
35
|
+
return `<h1>${message}</h1>`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
22
39
|
/**
|
|
23
40
|
* 启动本地回调服务器
|
|
24
41
|
*/
|
|
@@ -40,13 +57,15 @@ export function startCallbackServer(options: CallbackServerOptions): Promise<voi
|
|
|
40
57
|
|
|
41
58
|
// 返回成功响应
|
|
42
59
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
43
|
-
res.end("
|
|
60
|
+
res.end(getResultHtml("授权成功", true));
|
|
44
61
|
|
|
45
62
|
// 延迟关闭服务器,确保响应已发送
|
|
46
63
|
setTimeout(() => {
|
|
47
64
|
if (!serverClosed) {
|
|
48
65
|
serverClosed = true;
|
|
49
|
-
if (timeoutId)
|
|
66
|
+
if (timeoutId) {
|
|
67
|
+
clearTimeout(timeoutId);
|
|
68
|
+
}
|
|
50
69
|
server.close();
|
|
51
70
|
}
|
|
52
71
|
}, 100);
|
|
@@ -56,23 +75,25 @@ export function startCallbackServer(options: CallbackServerOptions): Promise<voi
|
|
|
56
75
|
} else if (!hasReceivedCode) {
|
|
57
76
|
// 返回错误响应
|
|
58
77
|
res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
|
|
59
|
-
res.end("
|
|
78
|
+
res.end(getResultHtml("授权失败:未获取到授权码", false));
|
|
60
79
|
} else {
|
|
61
80
|
// 重复请求,返回成功响应但不处理
|
|
62
81
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
63
|
-
res.end("
|
|
82
|
+
res.end(getResultHtml("授权成功", true));
|
|
64
83
|
}
|
|
65
84
|
} catch (error) {
|
|
66
85
|
console.error("authorize callback error:", error);
|
|
67
86
|
res.writeHead(500, { "Content-Type": "text/html; charset=utf-8" });
|
|
68
|
-
res.end("
|
|
87
|
+
res.end(getResultHtml("授权失败:服务器内部错误", false));
|
|
69
88
|
}
|
|
70
89
|
});
|
|
71
90
|
|
|
72
91
|
server.on("error", (error) => {
|
|
73
92
|
if (!serverClosed) {
|
|
74
93
|
serverClosed = true;
|
|
75
|
-
if (timeoutId)
|
|
94
|
+
if (timeoutId) {
|
|
95
|
+
clearTimeout(timeoutId);
|
|
96
|
+
}
|
|
76
97
|
onError?.(error);
|
|
77
98
|
if (!promiseResolved) {
|
|
78
99
|
promiseResolved = true;
|
|
@@ -99,7 +120,9 @@ export function startCallbackServer(options: CallbackServerOptions): Promise<voi
|
|
|
99
120
|
|
|
100
121
|
// 服务器关闭时解析Promise
|
|
101
122
|
server.on("close", () => {
|
|
102
|
-
if (timeoutId)
|
|
123
|
+
if (timeoutId) {
|
|
124
|
+
clearTimeout(timeoutId);
|
|
125
|
+
}
|
|
103
126
|
if (!promiseResolved) {
|
|
104
127
|
promiseResolved = true;
|
|
105
128
|
resolve();
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { createClawCloudClient } from "../apis/claw-cloud.js";
|
|
6
|
-
import { upgradeIdentityToV2 } from "../modules/configs/identity-persist.js";
|
|
7
6
|
import { getConfigManager } from "../modules/configs/index.js";
|
|
8
7
|
import { getDeviceInfo } from "../modules/device/index.js";
|
|
9
8
|
import type { HonorUserInfo } from "../types.js";
|
|
@@ -63,14 +62,7 @@ export async function saveToken(
|
|
|
63
62
|
/**
|
|
64
63
|
* 刷新并保存Token
|
|
65
64
|
*/
|
|
66
|
-
async function refreshToken(
|
|
67
|
-
userId: string,
|
|
68
|
-
options?: { upgradeIdentity?: boolean },
|
|
69
|
-
): Promise<HonorUserInfo | null> {
|
|
70
|
-
if (options?.upgradeIdentity) {
|
|
71
|
-
await upgradeIdentityToV2();
|
|
72
|
-
}
|
|
73
|
-
|
|
65
|
+
async function refreshToken(userId: string): Promise<HonorUserInfo | null> {
|
|
74
66
|
const deviceInfo = await getDeviceInfo();
|
|
75
67
|
const client = createClawCloudClient();
|
|
76
68
|
const tokenInfo = await client.exchangeToken(deviceInfo, { userId });
|
|
@@ -108,7 +100,7 @@ export async function loadToken(): Promise<HonorUserInfo | null> {
|
|
|
108
100
|
|
|
109
101
|
// 配置有 userId,优先使用 env userId 换取 token
|
|
110
102
|
if (userConfig?.userId) {
|
|
111
|
-
return await refreshToken(envUserId || userConfig.userId
|
|
103
|
+
return await refreshToken(envUserId || userConfig.userId);
|
|
112
104
|
}
|
|
113
105
|
|
|
114
106
|
// 有 token 且未过期,直接返回
|
|
@@ -122,7 +114,7 @@ export async function loadToken(): Promise<HonorUserInfo | null> {
|
|
|
122
114
|
// 没有token或者已经过期,拿envUserId重新获取
|
|
123
115
|
if (envUserId) {
|
|
124
116
|
logger.debug?.("[yoyoclaw-auth] token expired, using env userId to exchange token");
|
|
125
|
-
return await refreshToken(envUserId
|
|
117
|
+
return await refreshToken(envUserId);
|
|
126
118
|
}
|
|
127
119
|
|
|
128
120
|
// 没有可用的鉴权信息且token过期了,清除token信息
|
|
@@ -17,9 +17,9 @@ export enum IdentityVersion {
|
|
|
17
17
|
* 完整的设备 Identity 类型
|
|
18
18
|
*/
|
|
19
19
|
export interface PersistedIdentity {
|
|
20
|
-
/**
|
|
20
|
+
/** deviceId (基于公钥指纹) */
|
|
21
21
|
deviceId?: string;
|
|
22
|
-
/** 旧版本 deviceId (
|
|
22
|
+
/** 旧版本 deviceId (v1 缓存兼容读取,不再生成) */
|
|
23
23
|
legacyDeviceId?: string;
|
|
24
24
|
publicKeyPem?: string;
|
|
25
25
|
privateKeyPem?: string;
|
|
@@ -112,7 +112,7 @@ export async function updatePersistedIdentity(
|
|
|
112
112
|
// 合并配置
|
|
113
113
|
const updatedConfig: PersistedIdentity = {
|
|
114
114
|
...existingConfig,
|
|
115
|
-
version: existingConfig?.version || IdentityVersion.
|
|
115
|
+
version: existingConfig?.version || IdentityVersion.NEW,
|
|
116
116
|
};
|
|
117
117
|
|
|
118
118
|
if (identity.legacyDeviceId) {
|
|
@@ -148,46 +148,6 @@ export async function updatePersistedIdentity(
|
|
|
148
148
|
*/
|
|
149
149
|
function createDefaultConfig(): PersistedIdentity {
|
|
150
150
|
return {
|
|
151
|
-
version: IdentityVersion.LEGACY,
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* 升级 Identity 配置到 v2 版本
|
|
157
|
-
* - 如果已经是 v2,不处理
|
|
158
|
-
* - 如果是 v1,升级到 v2 并移除 legacyDeviceId
|
|
159
|
-
* @returns 升级后的配置,如果已经是 v2 则返回 null
|
|
160
|
-
*/
|
|
161
|
-
export async function upgradeIdentityToV2(): Promise<PersistedIdentity | null> {
|
|
162
|
-
const homeDir = resolveEffectiveHomeDir();
|
|
163
|
-
if (!homeDir) {
|
|
164
|
-
useClawLogger().warn("[yoyo-identity] failed to find home dir");
|
|
165
|
-
return null;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// 读取现有配置
|
|
169
|
-
const existingConfig = await loadConfig(homeDir);
|
|
170
|
-
|
|
171
|
-
// 如果已经有配置了,不做处理,这里老版本不删除这个问题就不走缓存咧
|
|
172
|
-
if (existingConfig) {
|
|
173
|
-
useClawLogger().warn("[yoyo-identity] identity exist, upgrade ignored");
|
|
174
|
-
return null;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// 升级到 v2,移除 legacyDeviceId
|
|
178
|
-
const upgradedConfig: PersistedIdentity = {
|
|
179
151
|
version: IdentityVersion.NEW,
|
|
180
152
|
};
|
|
181
|
-
|
|
182
|
-
// 写入升级后的配置
|
|
183
|
-
const configPath = getConfigPath();
|
|
184
|
-
await safeWriteFile({
|
|
185
|
-
rootDir: homeDir,
|
|
186
|
-
relativePath: configPath,
|
|
187
|
-
data: JSON.stringify(upgradedConfig, null, 2),
|
|
188
|
-
encoding: "utf8",
|
|
189
|
-
mkdir: true,
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
return upgradedConfig;
|
|
193
153
|
}
|
|
@@ -19,19 +19,9 @@ export async function getDeviceInfo(): Promise<DeviceInfo> {
|
|
|
19
19
|
// 确保 provider 初始化完成
|
|
20
20
|
await provider.ensureInitialized();
|
|
21
21
|
|
|
22
|
-
// 获取设备 ID
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
try {
|
|
27
|
-
const identity = await loadOrCreateDeviceIdentity();
|
|
28
|
-
deviceId = identity.legacyDeviceId ?? identity.deviceId;
|
|
29
|
-
deviceIdSource = "persisted";
|
|
30
|
-
} catch (error) {
|
|
31
|
-
// 如果 identity 获取失败,降级使用 provider 生成的 ID
|
|
32
|
-
useClawLogger().warn(`[yoyoclaw-device] failed to use identity: ${String(error)}`);
|
|
33
|
-
deviceId = await provider.getDeviceIdAsync();
|
|
34
|
-
}
|
|
22
|
+
// 获取设备 ID:优先使用 legacyDeviceId(v1 缓存兼容),否则使用新的 identity deviceId
|
|
23
|
+
const identity = await loadOrCreateDeviceIdentity();
|
|
24
|
+
const deviceId = identity.legacyDeviceId ?? identity.deviceId;
|
|
35
25
|
|
|
36
26
|
// 获取设备信息,优先级: env > config > provider
|
|
37
27
|
const envVars = getDeviceEnvVars();
|
|
@@ -63,7 +53,7 @@ export async function getDeviceInfo(): Promise<DeviceInfo> {
|
|
|
63
53
|
// 记录 deviceInfo 整体来源
|
|
64
54
|
const source = envVars.brand || envVars.deviceType || envVars.manufacture ? "env" : "config";
|
|
65
55
|
useClawLogger().info(
|
|
66
|
-
`[yoyoclaw-device] device info: ${JSON.stringify(deviceInfo)} (
|
|
56
|
+
`[yoyoclaw-device] device info: ${JSON.stringify(deviceInfo)} (source: ${source})`,
|
|
67
57
|
);
|
|
68
58
|
|
|
69
59
|
// env 与 config 的差异检测(key 映射: brand, manufacture, deviceType -> type)
|
|
@@ -2,10 +2,8 @@ import crypto from "node:crypto";
|
|
|
2
2
|
import {
|
|
3
3
|
getPersistedIdentity,
|
|
4
4
|
updatePersistedIdentity,
|
|
5
|
-
IdentityVersion,
|
|
6
5
|
type PersistedIdentity,
|
|
7
6
|
} from "../configs/identity-persist.js";
|
|
8
|
-
import { getDeviceInfoProvider } from "./providers/index.js";
|
|
9
7
|
import type { DeviceIdentity } from "./types.js";
|
|
10
8
|
|
|
11
9
|
/**
|
|
@@ -55,33 +53,23 @@ function fingerprintPublicKey(publicKeyPem: string): string {
|
|
|
55
53
|
/**
|
|
56
54
|
* Generate a new device identity with ED25519 key pair
|
|
57
55
|
*/
|
|
58
|
-
async function generateIdentity(
|
|
56
|
+
async function generateIdentity(): Promise<DeviceIdentity> {
|
|
59
57
|
const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519");
|
|
60
58
|
const publicKeyPem = publicKey.export({ type: "spki", format: "pem" }).toString();
|
|
61
59
|
const privateKeyPem = privateKey.export({ type: "pkcs8", format: "pem" }).toString();
|
|
62
60
|
const deviceId = fingerprintPublicKey(publicKeyPem);
|
|
63
61
|
|
|
64
|
-
|
|
62
|
+
return {
|
|
65
63
|
deviceId,
|
|
66
64
|
publicKeyPem,
|
|
67
65
|
privateKeyPem,
|
|
68
66
|
createdAtMs: Date.now(),
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (!identity || identity.version === IdentityVersion.LEGACY) {
|
|
72
|
-
// 补充legacyDeviceId
|
|
73
|
-
const provider = getDeviceInfoProvider();
|
|
74
|
-
await provider.ensureInitialized();
|
|
75
|
-
fullIdentity.legacyDeviceId = await provider.getDeviceIdAsync();
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return fullIdentity;
|
|
67
|
+
};
|
|
79
68
|
}
|
|
80
69
|
|
|
81
70
|
/**
|
|
82
|
-
* Load or create device identity
|
|
83
|
-
*
|
|
84
|
-
* @returns Device identity with optional legacyDeviceId
|
|
71
|
+
* Load or create device identity
|
|
72
|
+
* @returns Device identity
|
|
85
73
|
*/
|
|
86
74
|
export async function loadOrCreateDeviceIdentity(): Promise<DeviceIdentity> {
|
|
87
75
|
// 获取缓存的 identity
|
|
@@ -120,7 +108,7 @@ export async function loadOrCreateDeviceIdentity(): Promise<DeviceIdentity> {
|
|
|
120
108
|
}
|
|
121
109
|
|
|
122
110
|
// No existing identity, generate new one
|
|
123
|
-
const identity = await generateIdentity(
|
|
111
|
+
const identity = await generateIdentity();
|
|
124
112
|
await updatePersistedIdentity(identity);
|
|
125
113
|
|
|
126
114
|
return identity;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { createHash } from "crypto";
|
|
2
1
|
import fs from "fs";
|
|
3
2
|
import os from "os";
|
|
4
3
|
import type { DeviceType } from "../../../types.js";
|
|
@@ -26,53 +25,6 @@ export class LinuxDeviceInfoProvider implements DeviceInfoProvider {
|
|
|
26
25
|
// Linux provider 同步执行,无需初始化
|
|
27
26
|
}
|
|
28
27
|
|
|
29
|
-
/**
|
|
30
|
-
* 获取 Linux 设备唯一ID
|
|
31
|
-
*/
|
|
32
|
-
private getLinuxDeviceId(): string {
|
|
33
|
-
// 尝试获取机器ID(systemd-based systems)
|
|
34
|
-
const machineId = readFile("/etc/machine-id");
|
|
35
|
-
if (machineId) {
|
|
36
|
-
return machineId;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// 尝试获取 D-Bus 机器ID
|
|
40
|
-
const dbusMachineId = readFile("/var/lib/dbus/machine-id");
|
|
41
|
-
if (dbusMachineId) {
|
|
42
|
-
return dbusMachineId;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// 尝试读取主板序列号
|
|
46
|
-
const boardSerial = readFile("/sys/class/dmi/id/board_serial");
|
|
47
|
-
if (boardSerial && boardSerial !== "Not Specified") {
|
|
48
|
-
return boardSerial;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// 尝试读取产品序列号
|
|
52
|
-
const productSerial = readFile("/sys/class/dmi/id/product_serial");
|
|
53
|
-
if (productSerial && productSerial !== "Not Specified") {
|
|
54
|
-
return productSerial;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// 尝试从 /proc/cpuinfo 提取序列号
|
|
58
|
-
const cpuinfo = readFile("/proc/cpuinfo");
|
|
59
|
-
if (cpuinfo) {
|
|
60
|
-
const match = cpuinfo.match(/serial\s*:\s*(.+)/i);
|
|
61
|
-
if (match && match[1]) {
|
|
62
|
-
const cpuSerial = match[1].trim();
|
|
63
|
-
if (cpuSerial && cpuSerial !== "0") {
|
|
64
|
-
return cpuSerial;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// 最后备用方案:使用主机名和架构生成哈希
|
|
70
|
-
const hostname = os.hostname();
|
|
71
|
-
const arch = os.arch();
|
|
72
|
-
const raw = `${hostname}_${arch}_${os.platform()}`;
|
|
73
|
-
return createHash("sha256").update(raw, "utf-8").digest("hex");
|
|
74
|
-
}
|
|
75
|
-
|
|
76
28
|
/**
|
|
77
29
|
* 获取 Linux 设备型号
|
|
78
30
|
*/
|
|
@@ -101,13 +53,6 @@ export class LinuxDeviceInfoProvider implements DeviceInfoProvider {
|
|
|
101
53
|
return `${hostname} (${model})`;
|
|
102
54
|
}
|
|
103
55
|
|
|
104
|
-
/**
|
|
105
|
-
* 获取设备唯一ID(异步)
|
|
106
|
-
*/
|
|
107
|
-
async getDeviceIdAsync(): Promise<string> {
|
|
108
|
-
return this.getLinuxDeviceId();
|
|
109
|
-
}
|
|
110
|
-
|
|
111
56
|
/**
|
|
112
57
|
* 获取设备名称
|
|
113
58
|
*/
|
|
@@ -1,25 +1,7 @@
|
|
|
1
|
-
import { createHash } from "crypto";
|
|
2
1
|
import os from "os";
|
|
3
|
-
import { execCommand } from "@honor-claw/safe-exec";
|
|
4
2
|
import type { DeviceType } from "../../../types.js";
|
|
5
3
|
import type { DeviceInfoProvider } from "./base.js";
|
|
6
4
|
|
|
7
|
-
/**
|
|
8
|
-
* 从 system_profiler 输出中提取指定字段值
|
|
9
|
-
*/
|
|
10
|
-
function extractField(output: string, fieldName: string): string {
|
|
11
|
-
const lines = output.split("\n");
|
|
12
|
-
for (const line of lines) {
|
|
13
|
-
if (line.includes(fieldName)) {
|
|
14
|
-
const parts = line.split(":");
|
|
15
|
-
if (parts.length >= 2) {
|
|
16
|
-
return parts.slice(1).join(":").trim();
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
return "";
|
|
21
|
-
}
|
|
22
|
-
|
|
23
5
|
export class MacOSDeviceInfoProvider implements DeviceInfoProvider {
|
|
24
6
|
/**
|
|
25
7
|
* 确保 provider 初始化完成(macOS 无需异步初始化)
|
|
@@ -28,47 +10,6 @@ export class MacOSDeviceInfoProvider implements DeviceInfoProvider {
|
|
|
28
10
|
// macOS provider 同步执行,无需初始化
|
|
29
11
|
}
|
|
30
12
|
|
|
31
|
-
/**
|
|
32
|
-
* 获取 macOS 设备唯一ID
|
|
33
|
-
*/
|
|
34
|
-
private async getMacOSDeviceId(): Promise<string> {
|
|
35
|
-
// 获取硬件信息
|
|
36
|
-
const output = await execCommand("system_profiler", ["SPHardwareDataType"]);
|
|
37
|
-
if (!output) {
|
|
38
|
-
return this.generateFallbackId();
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// 提取硬件 UUID
|
|
42
|
-
const hardwareUuid = extractField(output, "Hardware UUID");
|
|
43
|
-
if (hardwareUuid) {
|
|
44
|
-
return hardwareUuid.replace(/[{}]/g, "");
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// 提取序列号
|
|
48
|
-
const serialNumber = extractField(output, "Serial Number");
|
|
49
|
-
if (serialNumber) {
|
|
50
|
-
return serialNumber;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// 提取 Board ID
|
|
54
|
-
const boardId = extractField(output, "Board ID");
|
|
55
|
-
if (boardId) {
|
|
56
|
-
return boardId;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return this.generateFallbackId();
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* 生成备用 ID
|
|
64
|
-
*/
|
|
65
|
-
private generateFallbackId(): string {
|
|
66
|
-
const hostname = os.hostname();
|
|
67
|
-
const arch = os.arch();
|
|
68
|
-
const raw = `${hostname}_${arch}_macOS`;
|
|
69
|
-
return createHash("sha256").update(raw, "utf-8").digest("hex");
|
|
70
|
-
}
|
|
71
|
-
|
|
72
13
|
/**
|
|
73
14
|
* 获取 macOS 设备型号
|
|
74
15
|
*/
|
|
@@ -87,13 +28,6 @@ export class MacOSDeviceInfoProvider implements DeviceInfoProvider {
|
|
|
87
28
|
return `${hostname} (${model})`;
|
|
88
29
|
}
|
|
89
30
|
|
|
90
|
-
/**
|
|
91
|
-
* 获取设备唯一ID(异步)
|
|
92
|
-
*/
|
|
93
|
-
async getDeviceIdAsync(): Promise<string> {
|
|
94
|
-
return this.getMacOSDeviceId();
|
|
95
|
-
}
|
|
96
|
-
|
|
97
31
|
/**
|
|
98
32
|
* 获取设备名称
|
|
99
33
|
*/
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { execFile } from "child_process";
|
|
2
|
-
import { createHash } from "crypto";
|
|
3
2
|
import fs from "fs";
|
|
4
3
|
import os from "os";
|
|
5
4
|
import { promisify } from "util";
|
|
@@ -12,11 +11,9 @@ const execFileAsync = promisify(execFile);
|
|
|
12
11
|
* 设备信息缓存
|
|
13
12
|
*/
|
|
14
13
|
interface DeviceInfoCache {
|
|
15
|
-
androidId: string;
|
|
16
14
|
brand: string;
|
|
17
15
|
model: string;
|
|
18
16
|
device: string;
|
|
19
|
-
deviceId: string;
|
|
20
17
|
}
|
|
21
18
|
|
|
22
19
|
/**
|
|
@@ -24,11 +21,9 @@ interface DeviceInfoCache {
|
|
|
24
21
|
*/
|
|
25
22
|
export class PadDeviceInfoProvider implements DeviceInfoProvider {
|
|
26
23
|
private cache: DeviceInfoCache = {
|
|
27
|
-
androidId: "",
|
|
28
24
|
brand: "",
|
|
29
25
|
model: "",
|
|
30
26
|
device: "",
|
|
31
|
-
deviceId: "",
|
|
32
27
|
};
|
|
33
28
|
|
|
34
29
|
private initPromise: Promise<void>;
|
|
@@ -65,21 +60,15 @@ export class PadDeviceInfoProvider implements DeviceInfoProvider {
|
|
|
65
60
|
private async _initializeCache(): Promise<void> {
|
|
66
61
|
try {
|
|
67
62
|
// 并行获取所有属性
|
|
68
|
-
const [
|
|
69
|
-
this.execAndroidCmd("/system/bin/settings get secure android_id"),
|
|
63
|
+
const [brand, model, device] = await Promise.all([
|
|
70
64
|
this.execAndroidCmd("/system/bin/getprop ro.product.brand"),
|
|
71
65
|
this.execAndroidCmd("/system/bin/getprop ro.product.model"),
|
|
72
66
|
this.execAndroidCmd("/system/bin/getprop ro.product.device"),
|
|
73
67
|
]);
|
|
74
68
|
|
|
75
|
-
this.cache.androidId = androidId && androidId !== "null" ? androidId : "";
|
|
76
69
|
this.cache.brand = brand || "";
|
|
77
70
|
this.cache.model = model || "";
|
|
78
71
|
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");
|
|
83
72
|
} catch {
|
|
84
73
|
// 初始化失败,保持默认值
|
|
85
74
|
}
|
|
@@ -116,14 +105,6 @@ export class PadDeviceInfoProvider implements DeviceInfoProvider {
|
|
|
116
105
|
return this.getAndroidDeviceModel();
|
|
117
106
|
}
|
|
118
107
|
|
|
119
|
-
/**
|
|
120
|
-
* 获取设备唯一ID(异步)
|
|
121
|
-
*/
|
|
122
|
-
async getDeviceIdAsync(): Promise<string> {
|
|
123
|
-
await this.initPromise;
|
|
124
|
-
return this.cache.deviceId || "invalid";
|
|
125
|
-
}
|
|
126
|
-
|
|
127
108
|
/**
|
|
128
109
|
* 获取设备名称
|
|
129
110
|
*/
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import os from "os";
|
|
2
2
|
import * as Registry from "winreg";
|
|
3
3
|
import type { DeviceType } from "../../../types.js";
|
|
4
|
-
import { uuid } from "../../../utils/id.js";
|
|
5
4
|
import type { DeviceInfoProvider } from "./base.js";
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* 设备信息缓存
|
|
9
8
|
*/
|
|
10
9
|
interface DeviceInfoCache {
|
|
11
|
-
deviceId: string;
|
|
12
10
|
deviceBrand: string;
|
|
13
11
|
}
|
|
14
12
|
|
|
@@ -67,7 +65,6 @@ function registryKeyExistsAsync(hive: number, keyPath: string): Promise<boolean>
|
|
|
67
65
|
|
|
68
66
|
export class WindowsDeviceInfoProvider implements DeviceInfoProvider {
|
|
69
67
|
private cache: DeviceInfoCache = {
|
|
70
|
-
deviceId: "",
|
|
71
68
|
deviceBrand: "",
|
|
72
69
|
};
|
|
73
70
|
|
|
@@ -90,13 +87,8 @@ export class WindowsDeviceInfoProvider implements DeviceInfoProvider {
|
|
|
90
87
|
private async _initializeCache(): Promise<void> {
|
|
91
88
|
try {
|
|
92
89
|
// 并行获取所有注册表值
|
|
93
|
-
const [
|
|
90
|
+
const [systemManufacturer, biosVendor, biosVendor2, systemManufacturer2] =
|
|
94
91
|
await Promise.all([
|
|
95
|
-
getRegistryStringValueAsync(
|
|
96
|
-
Registry.HKLM,
|
|
97
|
-
"\\SOFTWARE\\Microsoft\\Cryptography",
|
|
98
|
-
"MachineGuid",
|
|
99
|
-
),
|
|
100
92
|
// 原有路径
|
|
101
93
|
getRegistryStringValueAsync(
|
|
102
94
|
Registry.HKLM,
|
|
@@ -121,13 +113,6 @@ export class WindowsDeviceInfoProvider implements DeviceInfoProvider {
|
|
|
121
113
|
),
|
|
122
114
|
]);
|
|
123
115
|
|
|
124
|
-
// 缓存 deviceId
|
|
125
|
-
if (machineGuid) {
|
|
126
|
-
this.cache.deviceId = machineGuid;
|
|
127
|
-
} else {
|
|
128
|
-
this.cache.deviceId = uuid();
|
|
129
|
-
}
|
|
130
|
-
|
|
131
116
|
// 缓存 deviceBrand(检查多个来源)
|
|
132
117
|
const manufacturerSources = [
|
|
133
118
|
systemManufacturer,
|
|
@@ -150,7 +135,6 @@ export class WindowsDeviceInfoProvider implements DeviceInfoProvider {
|
|
|
150
135
|
}
|
|
151
136
|
} catch {
|
|
152
137
|
// 初始化失败,使用默认值
|
|
153
|
-
this.cache.deviceId = uuid();
|
|
154
138
|
this.cache.deviceBrand = "";
|
|
155
139
|
}
|
|
156
140
|
}
|
|
@@ -172,14 +156,6 @@ export class WindowsDeviceInfoProvider implements DeviceInfoProvider {
|
|
|
172
156
|
return `${hostname} (${model})`;
|
|
173
157
|
}
|
|
174
158
|
|
|
175
|
-
/**
|
|
176
|
-
* 获取设备唯一 ID(异步)
|
|
177
|
-
*/
|
|
178
|
-
async getDeviceIdAsync(): Promise<string> {
|
|
179
|
-
await this.initPromise;
|
|
180
|
-
return this.cache.deviceId;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
159
|
/**
|
|
184
160
|
* 获取设备名称
|
|
185
161
|
*/
|
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
* 登录命令行的入口,提供登录流程的服务
|
|
3
3
|
*/
|
|
4
4
|
import { performOAuth2AuthWithBrowser } from "../../honor-auth/index.js";
|
|
5
|
-
import {
|
|
5
|
+
import { saveToken } from "../../honor-auth/token-manager.js";
|
|
6
6
|
import type { HonorUserInfo } from "../../types.js";
|
|
7
7
|
import { useClawLogger } from "../../utils/logger.js";
|
|
8
|
-
import { upgradeIdentityToV2 } from "../configs/identity-persist.js";
|
|
9
8
|
import { getDeviceInfo, registerDevice } from "../device/index.js";
|
|
10
9
|
|
|
11
10
|
/**
|
|
@@ -33,18 +32,6 @@ export async function performLogin(options: LoginOptions = {}) {
|
|
|
33
32
|
try {
|
|
34
33
|
logger.debug?.("Starting login process...");
|
|
35
34
|
|
|
36
|
-
// 检查是否是新的登录触发(没有 token)
|
|
37
|
-
const existingToken = await loadToken();
|
|
38
|
-
const isNewLogin = !existingToken?.token;
|
|
39
|
-
|
|
40
|
-
if (isNewLogin) {
|
|
41
|
-
logger.debug?.("New login detected, upgrading identity to v2...");
|
|
42
|
-
const upgraded = await upgradeIdentityToV2();
|
|
43
|
-
if (upgraded) {
|
|
44
|
-
logger.info("Identity upgraded from v1 to v2 during login");
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
35
|
// 步骤 1: 获取设备信息
|
|
49
36
|
const deviceInfo = await getDeviceInfo();
|
|
50
37
|
|