@honor-claw/yoyo 1.2.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/skills/yoyo-control/SKILL.md +12 -2
- package/skills/yoyo-control/references/airplane-mode.md +197 -0
- package/skills/yoyo-control/references/capture-screenshot.md +28 -20
- package/skills/yoyo-control/references/clean-dirty.md +163 -0
- package/skills/yoyo-control/references/express-logistics-search.md +172 -0
- package/skills/yoyo-control/references/flashlight.md +183 -0
- package/skills/yoyo-control/references/flight-monitor-create.md +233 -0
- package/skills/yoyo-control/references/flight-monitor-search.md +229 -0
- package/skills/yoyo-control/references/scan-code.md +161 -0
- package/skills/yoyo-control/references/wlan.md +93 -24
- package/skills/yoyo-control/scripts/time_infer.py +99 -0
- 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
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import hmac
|
|
3
|
+
import hashlib
|
|
4
|
+
import base64
|
|
5
|
+
import json
|
|
6
|
+
import requests
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
import argparse
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def gen_headers():
|
|
12
|
+
# ak / sk 建议线上不要写死在代码里,放到环境变量或配置文件中
|
|
13
|
+
ak = "DB1178355DDE4CD2"
|
|
14
|
+
sk = "4764F7B34264E8F4343AC7363AA6D4"
|
|
15
|
+
|
|
16
|
+
# 对应:var ts = Math.floor(Date.now()).toString();
|
|
17
|
+
# Date.now() 是毫秒,这里同样用毫秒时间戳
|
|
18
|
+
ts = str(int(time.time() * 1000))
|
|
19
|
+
|
|
20
|
+
# 对应:CryptoJS.HmacSHA256(ts + ak, sk).toString().toUpperCase()
|
|
21
|
+
msg = (ts + ak).encode("utf-8")
|
|
22
|
+
key = sk.encode("utf-8")
|
|
23
|
+
digest = hmac.new(key, msg, hashlib.sha256).hexdigest().upper()
|
|
24
|
+
|
|
25
|
+
# 对应:new Buffer(...).toString('base64');
|
|
26
|
+
# 即对十六进制字符串做 base64 编码
|
|
27
|
+
sign = base64.b64encode(digest.encode("utf-8")).decode("utf-8")
|
|
28
|
+
|
|
29
|
+
headers = {
|
|
30
|
+
"accessKey": ak,
|
|
31
|
+
"ts": ts,
|
|
32
|
+
"sign": sign,
|
|
33
|
+
"Content-Type": "application/json",
|
|
34
|
+
}
|
|
35
|
+
return headers
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_current_local_time_str():
|
|
39
|
+
"""
|
|
40
|
+
生成形如:2025-12-23 12:48:00.000 的时间字符串
|
|
41
|
+
使用当前系统时间
|
|
42
|
+
"""
|
|
43
|
+
now = datetime.now()
|
|
44
|
+
# %f 是微秒,取前 3 位当毫秒
|
|
45
|
+
return now.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def parse_args():
|
|
49
|
+
parser = argparse.ArgumentParser(
|
|
50
|
+
description="调用 time-agent 接口的命令行工具"
|
|
51
|
+
)
|
|
52
|
+
parser.add_argument(
|
|
53
|
+
"--query",
|
|
54
|
+
# required=True,
|
|
55
|
+
default="大小周下周六休息7点",
|
|
56
|
+
help="语义查询内容(必填)",
|
|
57
|
+
)
|
|
58
|
+
parser.add_argument(
|
|
59
|
+
"--type",
|
|
60
|
+
dest="req_type",
|
|
61
|
+
default="time",
|
|
62
|
+
help="payload.data.type,默认值为 'time'",
|
|
63
|
+
)
|
|
64
|
+
parser.add_argument(
|
|
65
|
+
"--timeZone",
|
|
66
|
+
dest="time_zone",
|
|
67
|
+
default="+0800",
|
|
68
|
+
help="payload.data.timeZone,默认值为 '+0800'",
|
|
69
|
+
)
|
|
70
|
+
return parser.parse_args()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def main():
|
|
74
|
+
url = "https://ai-model-access-drcn.rnd.honor.com/time-agent/v1/models/model/predict"
|
|
75
|
+
args = parse_args()
|
|
76
|
+
|
|
77
|
+
payload = {
|
|
78
|
+
"data": {
|
|
79
|
+
"type": args.req_type,
|
|
80
|
+
"timeZone": args.time_zone,
|
|
81
|
+
"query": args.query,
|
|
82
|
+
"localTime": get_current_local_time_str()
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
headers = gen_headers()
|
|
87
|
+
|
|
88
|
+
resp = requests.post(url, headers=headers, data=json.dumps(payload))
|
|
89
|
+
print("Status code:", resp.status_code)
|
|
90
|
+
try:
|
|
91
|
+
datas = resp.json()
|
|
92
|
+
res = json.dumps(datas.get("data", {}), separators=(",", ":"), ensure_ascii=False)
|
|
93
|
+
print({"data_time": res})
|
|
94
|
+
except Exception:
|
|
95
|
+
print("Response text:", resp.text)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
if __name__ == "__main__":
|
|
99
|
+
main()
|
|
@@ -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
|
*/
|