creditdaddy 1.1.1 → 1.2.0
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/LICENSE +21 -21
- package/README.md +306 -289
- package/bin/creditdaddy.js +8 -4
- package/package.json +29 -28
- package/src/accounts.js +161 -161
- package/src/authDevice.js +205 -205
- package/src/catpawClient.js +103 -103
- package/src/catpawLocal.js +229 -229
- package/src/checkin.js +357 -340
- package/src/constants.js +97 -95
- package/src/daemon.js +130 -14
- package/src/localDetect.js +147 -147
- package/src/mirasimClient.js +189 -189
- package/src/panel.html +465 -142
- package/src/providers.js +190 -181
- package/src/qoderApp.js +93 -7
- package/src/qoderClient.js +237 -237
- package/src/qoderUmid.js +198 -198
- package/src/store.js +238 -238
- package/src/tenrouter.js +18 -8
- package/src/tenrouterAccounts.js +163 -163
- package/src/traeClient.js +258 -0
- package/src/traeLocal.js +337 -0
- package/src/transfer.js +160 -160
- package/src/usageSync.js +505 -505
- package/src/workbuddyLocal.js +133 -133
- package/src/zcodeAuth.js +131 -131
- package/src/zcodeAutoClaim.js +97 -97
- package/src/zcodeClient.js +33 -2
- package/src/zcodeGateway.js +26 -30
- package/src/zcodeLocal.js +334 -319
- package/src/zcrypto.js +160 -160
package/src/qoderUmid.js
CHANGED
|
@@ -1,198 +1,198 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Qoder 设备身份组件 —— 给没有 Qoder 客户端的 Linux(fnOS / NAS)提供国际版签到所需的风控身份。
|
|
3
|
-
*
|
|
4
|
-
* Qoder 国际版只向带 Cosy-MachineToken / Code / Type 的请求下发「每天领 100 Credits」,
|
|
5
|
-
* 这三个值由 Qoder 的 UMID 程序(runtime-info)生成。桌面端客户端自带它;官方 CLI
|
|
6
|
-
* @qoder-ai/qodercli 则把各平台的 UMID 二进制以 base64 内嵌在 bundle/qodercli.js 里,Linux x64 / arm64 都有。
|
|
7
|
-
*
|
|
8
|
-
* 这里在用户确认后从 npm 仓库下载官方 qodercli 包(校验 npm 的 sha512 integrity),
|
|
9
|
-
* 按本机 CPU 架构取出对应的 ELF,试跑一次确认能产出身份,再存到 ~/.creditdaddy/qoder-umid/。
|
|
10
|
-
* 不随 CreditDaddy 分发该二进制;调用方式与 qodercli 一致:runtime-info <env>,第一行输出 JSON。
|
|
11
|
-
* env:国际版 4(SINGAPORE,qodercli 的全球区取值),国内版 0(ONLINE)。
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import fs from 'node:fs';
|
|
15
|
-
import path from 'node:path';
|
|
16
|
-
import crypto from 'node:crypto';
|
|
17
|
-
import zlib from 'node:zlib';
|
|
18
|
-
import { execFile } from 'node:child_process';
|
|
19
|
-
import { dataDir } from './store.js';
|
|
20
|
-
import { logger } from './logger.js';
|
|
21
|
-
|
|
22
|
-
const PKG = '@qoder-ai/qodercli';
|
|
23
|
-
const REGISTRIES = ['https://registry.npmmirror.com', 'https://registry.npmjs.org'];
|
|
24
|
-
const AUTH_REGISTRY = 'https://registry.npmjs.org'; // integrity 的权威来源(镜像只用于加速下载)
|
|
25
|
-
const ELF_MACHINE = { x64: 62, arm64: 183 };
|
|
26
|
-
const BUNDLE_ENTRY = 'package/bundle/qodercli.js';
|
|
27
|
-
const FETCH_TIMEOUT_MS = 180_000;
|
|
28
|
-
const META_TIMEOUT_MS = 15_000;
|
|
29
|
-
export const CLI_RISK_ENV = { qoder: 4, 'qoder-cn': 0 };
|
|
30
|
-
|
|
31
|
-
export function umidSupported(platform = process.platform, arch = process.arch) {
|
|
32
|
-
return platform === 'linux' && Boolean(ELF_MACHINE[arch]);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const dir = () => path.join(dataDir(), 'qoder-umid');
|
|
36
|
-
const binPath = () => path.join(dir(), 'runtime-info');
|
|
37
|
-
const manifestPath = () => path.join(dir(), 'manifest.json');
|
|
38
|
-
|
|
39
|
-
/** 已安装的组件:{ path, version, arch, sha256, installedAt, registry };未安装返回 null */
|
|
40
|
-
export function installedUmid() {
|
|
41
|
-
if (!umidSupported()) return null;
|
|
42
|
-
try {
|
|
43
|
-
const m = JSON.parse(fs.readFileSync(manifestPath(), 'utf8'));
|
|
44
|
-
if (!fs.existsSync(binPath()) || m.arch !== process.arch) return null;
|
|
45
|
-
return { ...m, path: binPath() };
|
|
46
|
-
} catch { return null; }
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// ── 纯函数(便于单测) ──
|
|
50
|
-
|
|
51
|
-
/** 最小 tar 解析:按名字取一个普通文件的内容 */
|
|
52
|
-
export function tarEntry(tar, wanted) {
|
|
53
|
-
let off = 0;
|
|
54
|
-
const cstr = (b, s, n) => { const x = b.subarray(s, s + n); const z = x.indexOf(0); return x.subarray(0, z < 0 ? n : z).toString('utf8'); };
|
|
55
|
-
let longName = null;
|
|
56
|
-
while (off + 512 <= tar.length) {
|
|
57
|
-
const h = tar.subarray(off, off + 512);
|
|
58
|
-
if (h.every((b) => b === 0)) break;
|
|
59
|
-
const size = parseInt(cstr(h, 124, 12).trim() || '0', 8);
|
|
60
|
-
const type = String.fromCharCode(h[156] || 48);
|
|
61
|
-
const prefix = cstr(h, 345, 155);
|
|
62
|
-
let name = longName || (prefix ? prefix + '/' : '') + cstr(h, 0, 100);
|
|
63
|
-
longName = null;
|
|
64
|
-
const body = tar.subarray(off + 512, off + 512 + size);
|
|
65
|
-
off += 512 + Math.ceil(size / 512) * 512;
|
|
66
|
-
if (type === 'L') { longName = body.toString('utf8').replace(/\0+$/, ''); continue; }
|
|
67
|
-
if (type === 'x') { // pax 扩展头里的 path 覆盖下一个条目的名字
|
|
68
|
-
const m = /\d+ path=([^\n]+)\n/.exec(body.toString('utf8'));
|
|
69
|
-
if (m) longName = m[1];
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
if ((type === '0' || type === '\0') && name === wanted) return body;
|
|
73
|
-
}
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** 在 qodercli bundle 文本里找内嵌的 ELF(base64 字面量),按 e_machine 挑出本机架构 */
|
|
78
|
-
export function extractElf(jsText, machine) {
|
|
79
|
-
const MARK = '"f0VMRgIB'; // "\x7fELF" + 64 位 + 小端 的 base64 开头
|
|
80
|
-
let i = jsText.indexOf(MARK);
|
|
81
|
-
while (i >= 0) {
|
|
82
|
-
const start = i + 1;
|
|
83
|
-
const end = jsText.indexOf('"', start);
|
|
84
|
-
if (end < 0) break;
|
|
85
|
-
const head = Buffer.from(jsText.slice(start, start + 28), 'base64');
|
|
86
|
-
if (head.length >= 20 && head.readUInt16LE(18) === machine) {
|
|
87
|
-
return Buffer.from(jsText.slice(start, end), 'base64');
|
|
88
|
-
}
|
|
89
|
-
i = jsText.indexOf(MARK, end);
|
|
90
|
-
}
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** npm integrity(sha512-<base64>)校验 */
|
|
95
|
-
export function checkIntegrity(buf, integrity) {
|
|
96
|
-
const m = /^sha512-(.+)$/.exec(String(integrity || '').split(/\s+/)[0]);
|
|
97
|
-
if (!m) return false;
|
|
98
|
-
return crypto.createHash('sha512').update(buf).digest('base64') === m[1];
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// ── 安装 ──
|
|
102
|
-
|
|
103
|
-
async function fetchBuf(url, timeoutMs = FETCH_TIMEOUT_MS) {
|
|
104
|
-
const res = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
|
|
105
|
-
if (!res.ok) throw new Error(`HTTP ${res.status}:${url}`);
|
|
106
|
-
return Buffer.from(await res.arrayBuffer());
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* 从 npmjs 拉权威 integrity。镜像(npmmirror)的元数据与 tarball 同源,
|
|
111
|
-
* 镜像被污染时它自己的 integrity 校验形同虚设,所以校验值以 npmjs 为准;
|
|
112
|
-
* npmjs 不可达时返回 null,退回镜像自身 integrity(与旧行为一致)。
|
|
113
|
-
*/
|
|
114
|
-
async function authoritativeIntegrity() {
|
|
115
|
-
try {
|
|
116
|
-
const meta = JSON.parse((await fetchBuf(`${AUTH_REGISTRY}/${PKG.replace('/', '%2f')}/latest`, META_TIMEOUT_MS)).toString('utf8'));
|
|
117
|
-
if (meta?.dist?.integrity && meta?.version) return { integrity: meta.dist.integrity, version: meta.version };
|
|
118
|
-
} catch {}
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function testRun(file) {
|
|
123
|
-
return new Promise((resolve, reject) => {
|
|
124
|
-
execFile(file, [String(CLI_RISK_ENV.qoder)], { timeout: 30_000, maxBuffer: 1 << 20 }, (err, stdout) => {
|
|
125
|
-
try {
|
|
126
|
-
const j = JSON.parse(String(stdout).split('\n')[0]);
|
|
127
|
-
if (typeof j.machineToken === 'string' && j.machineToken && j.machineCode && j.machineType) return resolve(j);
|
|
128
|
-
} catch {}
|
|
129
|
-
reject(new Error('组件试运行失败' + (err ? ':' + err.message : '(输出里没有设备身份)')));
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
let installing = null;
|
|
135
|
-
|
|
136
|
-
/** 下载官方 qodercli 包并提取本机架构的 UMID;并发调用共用同一次安装 */
|
|
137
|
-
export function installUmid() {
|
|
138
|
-
if (!installing) installing = doInstall().finally(() => { installing = null; });
|
|
139
|
-
return installing;
|
|
140
|
-
}
|
|
141
|
-
export const umidInstalling = () => Boolean(installing);
|
|
142
|
-
|
|
143
|
-
async function doInstall() {
|
|
144
|
-
if (!umidSupported()) throw new Error(`当前平台(${process.platform}/${process.arch})不需要或不支持该组件`);
|
|
145
|
-
const authority = await authoritativeIntegrity();
|
|
146
|
-
const errors = [];
|
|
147
|
-
for (const registry of REGISTRIES) {
|
|
148
|
-
try {
|
|
149
|
-
const meta = JSON.parse((await fetchBuf(`${registry}/${PKG.replace('/', '%2f')}/latest`, META_TIMEOUT_MS)).toString('utf8'));
|
|
150
|
-
const { tarball, integrity } = meta.dist || {};
|
|
151
|
-
if (!tarball || !integrity) throw new Error('包元数据缺少 tarball / integrity');
|
|
152
|
-
// 镜像版本与 npmjs 不一致时没有权威校验值可比,跳过镜像直接用官方源
|
|
153
|
-
if (authority && registry !== AUTH_REGISTRY && meta.version !== authority.version) {
|
|
154
|
-
throw new Error(`镜像版本(${meta.version})与 npmjs(${authority.version})不一致`);
|
|
155
|
-
}
|
|
156
|
-
const expected = authority && authority.version === meta.version ? authority.integrity : integrity;
|
|
157
|
-
logger.info('UMID', `下载 ${PKG}@${meta.version}(${registry})`);
|
|
158
|
-
const tgz = await fetchBuf(tarball);
|
|
159
|
-
if (!checkIntegrity(tgz, expected)) throw new Error('下载内容与 npm integrity 不一致');
|
|
160
|
-
const bundle = tarEntry(zlib.gunzipSync(tgz), BUNDLE_ENTRY);
|
|
161
|
-
if (!bundle) throw new Error(`包里没有 ${BUNDLE_ENTRY}(qodercli 结构可能变化)`);
|
|
162
|
-
const elf = extractElf(bundle.toString('latin1'), ELF_MACHINE[process.arch]);
|
|
163
|
-
if (!elf) throw new Error(`qodercli ${meta.version} 里没有 ${process.arch} 的 UMID 程序(结构可能变化)`);
|
|
164
|
-
|
|
165
|
-
fs.mkdirSync(dir(), { recursive: true, mode: 0o700 });
|
|
166
|
-
const tmp = binPath() + '.' + process.pid + '.tmp';
|
|
167
|
-
fs.writeFileSync(tmp, elf, { mode: 0o755 });
|
|
168
|
-
try { await testRun(tmp); } catch (e) { fs.rmSync(tmp, { force: true }); throw e; }
|
|
169
|
-
fs.renameSync(tmp, binPath());
|
|
170
|
-
const manifest = {
|
|
171
|
-
version: meta.version, arch: process.arch, registry,
|
|
172
|
-
sha256: crypto.createHash('sha256').update(elf).digest('hex'),
|
|
173
|
-
installedAt: new Date().toISOString(),
|
|
174
|
-
};
|
|
175
|
-
fs.writeFileSync(manifestPath(), JSON.stringify(manifest, null, 2), { mode: 0o600 });
|
|
176
|
-
logger.info('UMID', `设备身份组件已安装:qodercli ${meta.version} / ${process.arch}`);
|
|
177
|
-
return { ...manifest, path: binPath() };
|
|
178
|
-
} catch (e) {
|
|
179
|
-
errors.push(`${registry}:${e.message}`);
|
|
180
|
-
logger.warn('UMID', `从 ${registry} 安装失败:${e.message}`);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
throw new Error('设备身份组件安装失败 —— ' + errors.join(';'));
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
export function removeUmid() {
|
|
187
|
-
fs.rmSync(dir(), { recursive: true, force: true });
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/** 面板 / 状态接口用的摘要 */
|
|
191
|
-
export function umidInfo() {
|
|
192
|
-
const m = installedUmid();
|
|
193
|
-
return {
|
|
194
|
-
supported: umidSupported(),
|
|
195
|
-
installing: umidInstalling(),
|
|
196
|
-
installed: m ? { version: m.version, arch: m.arch, installedAt: m.installedAt } : null,
|
|
197
|
-
};
|
|
198
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Qoder 设备身份组件 —— 给没有 Qoder 客户端的 Linux(fnOS / NAS)提供国际版签到所需的风控身份。
|
|
3
|
+
*
|
|
4
|
+
* Qoder 国际版只向带 Cosy-MachineToken / Code / Type 的请求下发「每天领 100 Credits」,
|
|
5
|
+
* 这三个值由 Qoder 的 UMID 程序(runtime-info)生成。桌面端客户端自带它;官方 CLI
|
|
6
|
+
* @qoder-ai/qodercli 则把各平台的 UMID 二进制以 base64 内嵌在 bundle/qodercli.js 里,Linux x64 / arm64 都有。
|
|
7
|
+
*
|
|
8
|
+
* 这里在用户确认后从 npm 仓库下载官方 qodercli 包(校验 npm 的 sha512 integrity),
|
|
9
|
+
* 按本机 CPU 架构取出对应的 ELF,试跑一次确认能产出身份,再存到 ~/.creditdaddy/qoder-umid/。
|
|
10
|
+
* 不随 CreditDaddy 分发该二进制;调用方式与 qodercli 一致:runtime-info <env>,第一行输出 JSON。
|
|
11
|
+
* env:国际版 4(SINGAPORE,qodercli 的全球区取值),国内版 0(ONLINE)。
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import fs from 'node:fs';
|
|
15
|
+
import path from 'node:path';
|
|
16
|
+
import crypto from 'node:crypto';
|
|
17
|
+
import zlib from 'node:zlib';
|
|
18
|
+
import { execFile } from 'node:child_process';
|
|
19
|
+
import { dataDir } from './store.js';
|
|
20
|
+
import { logger } from './logger.js';
|
|
21
|
+
|
|
22
|
+
const PKG = '@qoder-ai/qodercli';
|
|
23
|
+
const REGISTRIES = ['https://registry.npmmirror.com', 'https://registry.npmjs.org'];
|
|
24
|
+
const AUTH_REGISTRY = 'https://registry.npmjs.org'; // integrity 的权威来源(镜像只用于加速下载)
|
|
25
|
+
const ELF_MACHINE = { x64: 62, arm64: 183 };
|
|
26
|
+
const BUNDLE_ENTRY = 'package/bundle/qodercli.js';
|
|
27
|
+
const FETCH_TIMEOUT_MS = 180_000;
|
|
28
|
+
const META_TIMEOUT_MS = 15_000;
|
|
29
|
+
export const CLI_RISK_ENV = { qoder: 4, 'qoder-cn': 0 };
|
|
30
|
+
|
|
31
|
+
export function umidSupported(platform = process.platform, arch = process.arch) {
|
|
32
|
+
return platform === 'linux' && Boolean(ELF_MACHINE[arch]);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const dir = () => path.join(dataDir(), 'qoder-umid');
|
|
36
|
+
const binPath = () => path.join(dir(), 'runtime-info');
|
|
37
|
+
const manifestPath = () => path.join(dir(), 'manifest.json');
|
|
38
|
+
|
|
39
|
+
/** 已安装的组件:{ path, version, arch, sha256, installedAt, registry };未安装返回 null */
|
|
40
|
+
export function installedUmid() {
|
|
41
|
+
if (!umidSupported()) return null;
|
|
42
|
+
try {
|
|
43
|
+
const m = JSON.parse(fs.readFileSync(manifestPath(), 'utf8'));
|
|
44
|
+
if (!fs.existsSync(binPath()) || m.arch !== process.arch) return null;
|
|
45
|
+
return { ...m, path: binPath() };
|
|
46
|
+
} catch { return null; }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ── 纯函数(便于单测) ──
|
|
50
|
+
|
|
51
|
+
/** 最小 tar 解析:按名字取一个普通文件的内容 */
|
|
52
|
+
export function tarEntry(tar, wanted) {
|
|
53
|
+
let off = 0;
|
|
54
|
+
const cstr = (b, s, n) => { const x = b.subarray(s, s + n); const z = x.indexOf(0); return x.subarray(0, z < 0 ? n : z).toString('utf8'); };
|
|
55
|
+
let longName = null;
|
|
56
|
+
while (off + 512 <= tar.length) {
|
|
57
|
+
const h = tar.subarray(off, off + 512);
|
|
58
|
+
if (h.every((b) => b === 0)) break;
|
|
59
|
+
const size = parseInt(cstr(h, 124, 12).trim() || '0', 8);
|
|
60
|
+
const type = String.fromCharCode(h[156] || 48);
|
|
61
|
+
const prefix = cstr(h, 345, 155);
|
|
62
|
+
let name = longName || (prefix ? prefix + '/' : '') + cstr(h, 0, 100);
|
|
63
|
+
longName = null;
|
|
64
|
+
const body = tar.subarray(off + 512, off + 512 + size);
|
|
65
|
+
off += 512 + Math.ceil(size / 512) * 512;
|
|
66
|
+
if (type === 'L') { longName = body.toString('utf8').replace(/\0+$/, ''); continue; }
|
|
67
|
+
if (type === 'x') { // pax 扩展头里的 path 覆盖下一个条目的名字
|
|
68
|
+
const m = /\d+ path=([^\n]+)\n/.exec(body.toString('utf8'));
|
|
69
|
+
if (m) longName = m[1];
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if ((type === '0' || type === '\0') && name === wanted) return body;
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** 在 qodercli bundle 文本里找内嵌的 ELF(base64 字面量),按 e_machine 挑出本机架构 */
|
|
78
|
+
export function extractElf(jsText, machine) {
|
|
79
|
+
const MARK = '"f0VMRgIB'; // "\x7fELF" + 64 位 + 小端 的 base64 开头
|
|
80
|
+
let i = jsText.indexOf(MARK);
|
|
81
|
+
while (i >= 0) {
|
|
82
|
+
const start = i + 1;
|
|
83
|
+
const end = jsText.indexOf('"', start);
|
|
84
|
+
if (end < 0) break;
|
|
85
|
+
const head = Buffer.from(jsText.slice(start, start + 28), 'base64');
|
|
86
|
+
if (head.length >= 20 && head.readUInt16LE(18) === machine) {
|
|
87
|
+
return Buffer.from(jsText.slice(start, end), 'base64');
|
|
88
|
+
}
|
|
89
|
+
i = jsText.indexOf(MARK, end);
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** npm integrity(sha512-<base64>)校验 */
|
|
95
|
+
export function checkIntegrity(buf, integrity) {
|
|
96
|
+
const m = /^sha512-(.+)$/.exec(String(integrity || '').split(/\s+/)[0]);
|
|
97
|
+
if (!m) return false;
|
|
98
|
+
return crypto.createHash('sha512').update(buf).digest('base64') === m[1];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ── 安装 ──
|
|
102
|
+
|
|
103
|
+
async function fetchBuf(url, timeoutMs = FETCH_TIMEOUT_MS) {
|
|
104
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
|
|
105
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}:${url}`);
|
|
106
|
+
return Buffer.from(await res.arrayBuffer());
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 从 npmjs 拉权威 integrity。镜像(npmmirror)的元数据与 tarball 同源,
|
|
111
|
+
* 镜像被污染时它自己的 integrity 校验形同虚设,所以校验值以 npmjs 为准;
|
|
112
|
+
* npmjs 不可达时返回 null,退回镜像自身 integrity(与旧行为一致)。
|
|
113
|
+
*/
|
|
114
|
+
async function authoritativeIntegrity() {
|
|
115
|
+
try {
|
|
116
|
+
const meta = JSON.parse((await fetchBuf(`${AUTH_REGISTRY}/${PKG.replace('/', '%2f')}/latest`, META_TIMEOUT_MS)).toString('utf8'));
|
|
117
|
+
if (meta?.dist?.integrity && meta?.version) return { integrity: meta.dist.integrity, version: meta.version };
|
|
118
|
+
} catch {}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function testRun(file) {
|
|
123
|
+
return new Promise((resolve, reject) => {
|
|
124
|
+
execFile(file, [String(CLI_RISK_ENV.qoder)], { timeout: 30_000, maxBuffer: 1 << 20 }, (err, stdout) => {
|
|
125
|
+
try {
|
|
126
|
+
const j = JSON.parse(String(stdout).split('\n')[0]);
|
|
127
|
+
if (typeof j.machineToken === 'string' && j.machineToken && j.machineCode && j.machineType) return resolve(j);
|
|
128
|
+
} catch {}
|
|
129
|
+
reject(new Error('组件试运行失败' + (err ? ':' + err.message : '(输出里没有设备身份)')));
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let installing = null;
|
|
135
|
+
|
|
136
|
+
/** 下载官方 qodercli 包并提取本机架构的 UMID;并发调用共用同一次安装 */
|
|
137
|
+
export function installUmid() {
|
|
138
|
+
if (!installing) installing = doInstall().finally(() => { installing = null; });
|
|
139
|
+
return installing;
|
|
140
|
+
}
|
|
141
|
+
export const umidInstalling = () => Boolean(installing);
|
|
142
|
+
|
|
143
|
+
async function doInstall() {
|
|
144
|
+
if (!umidSupported()) throw new Error(`当前平台(${process.platform}/${process.arch})不需要或不支持该组件`);
|
|
145
|
+
const authority = await authoritativeIntegrity();
|
|
146
|
+
const errors = [];
|
|
147
|
+
for (const registry of REGISTRIES) {
|
|
148
|
+
try {
|
|
149
|
+
const meta = JSON.parse((await fetchBuf(`${registry}/${PKG.replace('/', '%2f')}/latest`, META_TIMEOUT_MS)).toString('utf8'));
|
|
150
|
+
const { tarball, integrity } = meta.dist || {};
|
|
151
|
+
if (!tarball || !integrity) throw new Error('包元数据缺少 tarball / integrity');
|
|
152
|
+
// 镜像版本与 npmjs 不一致时没有权威校验值可比,跳过镜像直接用官方源
|
|
153
|
+
if (authority && registry !== AUTH_REGISTRY && meta.version !== authority.version) {
|
|
154
|
+
throw new Error(`镜像版本(${meta.version})与 npmjs(${authority.version})不一致`);
|
|
155
|
+
}
|
|
156
|
+
const expected = authority && authority.version === meta.version ? authority.integrity : integrity;
|
|
157
|
+
logger.info('UMID', `下载 ${PKG}@${meta.version}(${registry})`);
|
|
158
|
+
const tgz = await fetchBuf(tarball);
|
|
159
|
+
if (!checkIntegrity(tgz, expected)) throw new Error('下载内容与 npm integrity 不一致');
|
|
160
|
+
const bundle = tarEntry(zlib.gunzipSync(tgz), BUNDLE_ENTRY);
|
|
161
|
+
if (!bundle) throw new Error(`包里没有 ${BUNDLE_ENTRY}(qodercli 结构可能变化)`);
|
|
162
|
+
const elf = extractElf(bundle.toString('latin1'), ELF_MACHINE[process.arch]);
|
|
163
|
+
if (!elf) throw new Error(`qodercli ${meta.version} 里没有 ${process.arch} 的 UMID 程序(结构可能变化)`);
|
|
164
|
+
|
|
165
|
+
fs.mkdirSync(dir(), { recursive: true, mode: 0o700 });
|
|
166
|
+
const tmp = binPath() + '.' + process.pid + '.tmp';
|
|
167
|
+
fs.writeFileSync(tmp, elf, { mode: 0o755 });
|
|
168
|
+
try { await testRun(tmp); } catch (e) { fs.rmSync(tmp, { force: true }); throw e; }
|
|
169
|
+
fs.renameSync(tmp, binPath());
|
|
170
|
+
const manifest = {
|
|
171
|
+
version: meta.version, arch: process.arch, registry,
|
|
172
|
+
sha256: crypto.createHash('sha256').update(elf).digest('hex'),
|
|
173
|
+
installedAt: new Date().toISOString(),
|
|
174
|
+
};
|
|
175
|
+
fs.writeFileSync(manifestPath(), JSON.stringify(manifest, null, 2), { mode: 0o600 });
|
|
176
|
+
logger.info('UMID', `设备身份组件已安装:qodercli ${meta.version} / ${process.arch}`);
|
|
177
|
+
return { ...manifest, path: binPath() };
|
|
178
|
+
} catch (e) {
|
|
179
|
+
errors.push(`${registry}:${e.message}`);
|
|
180
|
+
logger.warn('UMID', `从 ${registry} 安装失败:${e.message}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
throw new Error('设备身份组件安装失败 —— ' + errors.join(';'));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function removeUmid() {
|
|
187
|
+
fs.rmSync(dir(), { recursive: true, force: true });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** 面板 / 状态接口用的摘要 */
|
|
191
|
+
export function umidInfo() {
|
|
192
|
+
const m = installedUmid();
|
|
193
|
+
return {
|
|
194
|
+
supported: umidSupported(),
|
|
195
|
+
installing: umidInstalling(),
|
|
196
|
+
installed: m ? { version: m.version, arch: m.arch, installedAt: m.installedAt } : null,
|
|
197
|
+
};
|
|
198
|
+
}
|