@web-auto/webauto 0.1.15 → 0.1.17
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.
|
@@ -919,6 +919,9 @@ import path5 from "node:path";
|
|
|
919
919
|
import { promises as fs3 } from "node:fs";
|
|
920
920
|
var DEFAULT_HOST = "127.0.0.1";
|
|
921
921
|
var DEFAULT_PORT = 7716;
|
|
922
|
+
var DEFAULT_SNAPSHOT_TIMEOUT_MS = 12e3;
|
|
923
|
+
var DEFAULT_ACTION_TIMEOUT_MS = 12e3;
|
|
924
|
+
var DEFAULT_WAIT_PROBE_TIMEOUT_MS = 3e3;
|
|
922
925
|
function normalizePathForPlatform(raw, platform = process.platform) {
|
|
923
926
|
const input = String(raw || "").trim();
|
|
924
927
|
const isWinPath = platform === "win32" || /^[A-Za-z]:[\\/]/.test(input);
|
|
@@ -994,6 +997,21 @@ function toActionError(input, error, extra = {}) {
|
|
|
994
997
|
};
|
|
995
998
|
return payload;
|
|
996
999
|
}
|
|
1000
|
+
async function withTimeout(promise, timeoutMs, label) {
|
|
1001
|
+
const ms = readInt(timeoutMs, 0);
|
|
1002
|
+
if (ms <= 0) return promise;
|
|
1003
|
+
let timer = null;
|
|
1004
|
+
try {
|
|
1005
|
+
return await Promise.race([
|
|
1006
|
+
promise,
|
|
1007
|
+
new Promise((_resolve, reject) => {
|
|
1008
|
+
timer = setTimeout(() => reject(new Error(`${label}_timeout:${ms}`)), ms);
|
|
1009
|
+
})
|
|
1010
|
+
]);
|
|
1011
|
+
} finally {
|
|
1012
|
+
if (timer) clearTimeout(timer);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
997
1015
|
async function writeControlFile(host, port) {
|
|
998
1016
|
const payload = {
|
|
999
1017
|
pid: process.pid,
|
|
@@ -1386,7 +1404,11 @@ var UiCliBridge = class {
|
|
|
1386
1404
|
let snapshot;
|
|
1387
1405
|
if (includeSnapshot) {
|
|
1388
1406
|
try {
|
|
1389
|
-
snapshot = await
|
|
1407
|
+
snapshot = await withTimeout(
|
|
1408
|
+
win2.webContents.executeJavaScript(buildSnapshotScript(), true),
|
|
1409
|
+
readInt(process.env.WEBAUTO_UI_CLI_SNAPSHOT_TIMEOUT_MS, DEFAULT_SNAPSHOT_TIMEOUT_MS),
|
|
1410
|
+
"snapshot"
|
|
1411
|
+
);
|
|
1390
1412
|
} catch (err) {
|
|
1391
1413
|
return {
|
|
1392
1414
|
ok: false,
|
|
@@ -1418,7 +1440,12 @@ var UiCliBridge = class {
|
|
|
1418
1440
|
const win2 = this.options.getWindow();
|
|
1419
1441
|
if (!isUiReady(win2)) return toActionError(input, "window_not_ready");
|
|
1420
1442
|
try {
|
|
1421
|
-
const
|
|
1443
|
+
const timeoutMs = readInt(input?.timeoutMs, readInt(process.env.WEBAUTO_UI_CLI_ACTION_TIMEOUT_MS, DEFAULT_ACTION_TIMEOUT_MS));
|
|
1444
|
+
const out = await withTimeout(
|
|
1445
|
+
win2.webContents.executeJavaScript(buildActionScript(input), true),
|
|
1446
|
+
timeoutMs,
|
|
1447
|
+
"action"
|
|
1448
|
+
);
|
|
1422
1449
|
return out && typeof out === "object" ? out : toActionError(input, "empty_result");
|
|
1423
1450
|
} catch (err) {
|
|
1424
1451
|
return toActionError(input, err?.message || String(err), { details: err?.stack || null });
|
|
@@ -1449,7 +1476,11 @@ var UiCliBridge = class {
|
|
|
1449
1476
|
const disabled = Boolean(el && 'disabled' in el && el.disabled === true);
|
|
1450
1477
|
return { exists: Boolean(el), visible, text, value, disabled };
|
|
1451
1478
|
})()`;
|
|
1452
|
-
const state = await
|
|
1479
|
+
const state = await withTimeout(
|
|
1480
|
+
win2.webContents.executeJavaScript(checkScript, true),
|
|
1481
|
+
readInt(process.env.WEBAUTO_UI_CLI_WAIT_PROBE_TIMEOUT_MS, DEFAULT_WAIT_PROBE_TIMEOUT_MS),
|
|
1482
|
+
"wait_probe"
|
|
1483
|
+
);
|
|
1453
1484
|
const exists = Boolean(state?.exists);
|
|
1454
1485
|
const visible = Boolean(state?.visible);
|
|
1455
1486
|
const text = String(state?.text || "");
|
|
@@ -2870,10 +2870,6 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2870
2870
|
}
|
|
2871
2871
|
async function saveTask(runImmediately = false) {
|
|
2872
2872
|
const data = collectFormData();
|
|
2873
|
-
if (runImmediately && data.scheduleMode === "immediate") {
|
|
2874
|
-
await runWithoutSave();
|
|
2875
|
-
return;
|
|
2876
|
-
}
|
|
2877
2873
|
saveBtn.disabled = true;
|
|
2878
2874
|
runBtn.disabled = true;
|
|
2879
2875
|
runEphemeralBtn.disabled = true;
|
|
@@ -3212,6 +3208,7 @@ function renderDashboard(root, ctx2) {
|
|
|
3212
3208
|
let stoppedAt = null;
|
|
3213
3209
|
let elapsedTimer = null;
|
|
3214
3210
|
let statePollTimer = null;
|
|
3211
|
+
let accountLabelPollTimer = null;
|
|
3215
3212
|
let unsubscribeState = null;
|
|
3216
3213
|
let unsubscribeCmd = null;
|
|
3217
3214
|
const contextRun = ctx2?.xhsCurrentRun && typeof ctx2.xhsCurrentRun === "object" ? ctx2.xhsCurrentRun : null;
|
|
@@ -3225,6 +3222,10 @@ function renderDashboard(root, ctx2) {
|
|
|
3225
3222
|
const maxLogs = 500;
|
|
3226
3223
|
const maxRecentErrors = 8;
|
|
3227
3224
|
const maxLikedLinks = 30;
|
|
3225
|
+
const accountLabelByProfile = /* @__PURE__ */ new Map();
|
|
3226
|
+
let accountLabelRefreshInFlight = false;
|
|
3227
|
+
let accountLabelRefreshedAt = 0;
|
|
3228
|
+
const accountLabelRefreshTtlMs = 15e3;
|
|
3228
3229
|
const initialTaskId = String(contextRun?.taskId || ctx2?.activeTaskConfigId || "").trim();
|
|
3229
3230
|
if (initialTaskId) {
|
|
3230
3231
|
taskConfigId.textContent = initialTaskId;
|
|
@@ -3241,14 +3242,49 @@ function renderDashboard(root, ctx2) {
|
|
|
3241
3242
|
const text = String(value ?? "").trim();
|
|
3242
3243
|
return text.length > 0 && text !== "-";
|
|
3243
3244
|
};
|
|
3245
|
+
const resolveAccountLabel = (profileIdLike) => {
|
|
3246
|
+
const profileId = String(profileIdLike || "").trim();
|
|
3247
|
+
if (!profileId) return "-";
|
|
3248
|
+
return accountLabelByProfile.get(profileId) || profileId;
|
|
3249
|
+
};
|
|
3250
|
+
const applyAccountLabel = (profileIdLike) => {
|
|
3251
|
+
const profileId = String(profileIdLike || "").trim();
|
|
3252
|
+
if (!profileId) return;
|
|
3253
|
+
activeProfileId = profileId;
|
|
3254
|
+
taskAccount.textContent = resolveAccountLabel(profileId);
|
|
3255
|
+
if (!accountLabelByProfile.has(profileId)) {
|
|
3256
|
+
void refreshAccountLabels(true);
|
|
3257
|
+
}
|
|
3258
|
+
};
|
|
3259
|
+
async function refreshAccountLabels(force = false) {
|
|
3260
|
+
if (accountLabelRefreshInFlight) return;
|
|
3261
|
+
if (!force && Date.now() - accountLabelRefreshedAt < accountLabelRefreshTtlMs) return;
|
|
3262
|
+
if (typeof ctx2.api?.cmdRunJson !== "function") return;
|
|
3263
|
+
if (typeof ctx2.api?.pathJoin !== "function") return;
|
|
3264
|
+
accountLabelRefreshInFlight = true;
|
|
3265
|
+
try {
|
|
3266
|
+
const rows = await listAccountProfiles(ctx2.api, { platform: "xiaohongshu" });
|
|
3267
|
+
accountLabelByProfile.clear();
|
|
3268
|
+
for (const row of rows) {
|
|
3269
|
+
const profileId = String(row?.profileId || "").trim();
|
|
3270
|
+
if (!profileId) continue;
|
|
3271
|
+
const label = String(row?.alias || row?.name || profileId).trim() || profileId;
|
|
3272
|
+
accountLabelByProfile.set(profileId, label);
|
|
3273
|
+
}
|
|
3274
|
+
accountLabelRefreshedAt = Date.now();
|
|
3275
|
+
if (activeProfileId) {
|
|
3276
|
+
taskAccount.textContent = resolveAccountLabel(activeProfileId);
|
|
3277
|
+
}
|
|
3278
|
+
} catch {
|
|
3279
|
+
} finally {
|
|
3280
|
+
accountLabelRefreshInFlight = false;
|
|
3281
|
+
}
|
|
3282
|
+
}
|
|
3244
3283
|
if (contextRun) {
|
|
3245
3284
|
if (hasRenderableValue(contextRun.keyword)) taskKeyword.textContent = String(contextRun.keyword);
|
|
3246
3285
|
if (Number(contextRun.target) > 0) taskTarget.textContent = String(Number(contextRun.target));
|
|
3247
3286
|
if (hasRenderableValue(contextRun.profileId)) {
|
|
3248
|
-
|
|
3249
|
-
const profileId = String(contextRun.profileId);
|
|
3250
|
-
taskAccount.textContent = aliases[profileId] || profileId;
|
|
3251
|
-
activeProfileId = profileId;
|
|
3287
|
+
applyAccountLabel(contextRun.profileId);
|
|
3252
3288
|
}
|
|
3253
3289
|
if (hasRenderableValue(contextRun.taskId)) taskConfigId.textContent = String(contextRun.taskId);
|
|
3254
3290
|
const startedAtTs = Date.parse(String(contextRun.startedAt || ""));
|
|
@@ -3454,6 +3490,18 @@ function renderDashboard(root, ctx2) {
|
|
|
3454
3490
|
clearInterval(statePollTimer);
|
|
3455
3491
|
statePollTimer = null;
|
|
3456
3492
|
}
|
|
3493
|
+
function startAccountLabelPoll() {
|
|
3494
|
+
if (accountLabelPollTimer) return;
|
|
3495
|
+
accountLabelPollTimer = setInterval(() => {
|
|
3496
|
+
if (paused) return;
|
|
3497
|
+
void refreshAccountLabels(false);
|
|
3498
|
+
}, 3e4);
|
|
3499
|
+
}
|
|
3500
|
+
function stopAccountLabelPoll() {
|
|
3501
|
+
if (!accountLabelPollTimer) return;
|
|
3502
|
+
clearInterval(accountLabelPollTimer);
|
|
3503
|
+
accountLabelPollTimer = null;
|
|
3504
|
+
}
|
|
3457
3505
|
function addLog(line, type = "info") {
|
|
3458
3506
|
const ts = (/* @__PURE__ */ new Date()).toLocaleTimeString("zh-CN", { hour12: false });
|
|
3459
3507
|
const logLine = createEl("div", { className: "log-line" });
|
|
@@ -3549,9 +3597,7 @@ function renderDashboard(root, ctx2) {
|
|
|
3549
3597
|
taskTarget.textContent = String(state.target);
|
|
3550
3598
|
}
|
|
3551
3599
|
if (state.profileId) {
|
|
3552
|
-
|
|
3553
|
-
taskAccount.textContent = aliases[state.profileId] || state.profileId;
|
|
3554
|
-
activeProfileId = String(state.profileId || "").trim();
|
|
3600
|
+
applyAccountLabel(state.profileId);
|
|
3555
3601
|
}
|
|
3556
3602
|
const taskId = String(state.taskId || state.scheduleTaskId || state.configTaskId || "").trim();
|
|
3557
3603
|
if (taskId) {
|
|
@@ -3636,7 +3682,7 @@ function renderDashboard(root, ctx2) {
|
|
|
3636
3682
|
resetDashboardForNewRun("\u65B0\u4EFB\u52A1\u542F\u52A8", ts);
|
|
3637
3683
|
if (payload.keyword) taskKeyword.textContent = String(payload.keyword);
|
|
3638
3684
|
if (payload.maxNotes) taskTarget.textContent = String(payload.maxNotes);
|
|
3639
|
-
if (payload.profileId)
|
|
3685
|
+
if (payload.profileId) applyAccountLabel(payload.profileId);
|
|
3640
3686
|
if (payload.taskId) {
|
|
3641
3687
|
const taskId = String(payload.taskId || "").trim();
|
|
3642
3688
|
if (taskId) {
|
|
@@ -3788,9 +3834,7 @@ function renderDashboard(root, ctx2) {
|
|
|
3788
3834
|
if (summary.keyword) taskKeyword.textContent = String(summary.keyword);
|
|
3789
3835
|
if (assigned) taskTarget.textContent = String(assigned);
|
|
3790
3836
|
if (profile?.profileId) {
|
|
3791
|
-
|
|
3792
|
-
taskAccount.textContent = aliases[profile.profileId] || profile.profileId;
|
|
3793
|
-
activeProfileId = String(profile.profileId || "").trim();
|
|
3837
|
+
applyAccountLabel(profile.profileId);
|
|
3794
3838
|
}
|
|
3795
3839
|
const runId = String(profile?.runId || summary?.runId || "").trim();
|
|
3796
3840
|
if (runId) {
|
|
@@ -3930,8 +3974,7 @@ function renderDashboard(root, ctx2) {
|
|
|
3930
3974
|
taskTarget.textContent = String(config.target || 50);
|
|
3931
3975
|
}
|
|
3932
3976
|
if (!hasRenderableValue(contextRun?.profileId) && config.lastProfileId) {
|
|
3933
|
-
|
|
3934
|
-
taskAccount.textContent = aliases[config.lastProfileId] || config.lastProfileId;
|
|
3977
|
+
applyAccountLabel(config.lastProfileId);
|
|
3935
3978
|
}
|
|
3936
3979
|
const taskId = String(contextRun?.taskId || config.taskId || ctx2?.activeTaskConfigId || "").trim();
|
|
3937
3980
|
if (taskId) {
|
|
@@ -4008,14 +4051,17 @@ function renderDashboard(root, ctx2) {
|
|
|
4008
4051
|
}
|
|
4009
4052
|
};
|
|
4010
4053
|
renderRunSummary();
|
|
4054
|
+
void refreshAccountLabels(true);
|
|
4011
4055
|
loadTaskInfo();
|
|
4012
4056
|
subscribeToUpdates();
|
|
4013
4057
|
fetchCurrentState();
|
|
4014
4058
|
startStatePoll();
|
|
4059
|
+
startAccountLabelPoll();
|
|
4015
4060
|
startElapsedTimer();
|
|
4016
4061
|
return () => {
|
|
4017
4062
|
stopElapsedTimer();
|
|
4018
4063
|
stopStatePoll();
|
|
4064
|
+
stopAccountLabelPoll();
|
|
4019
4065
|
if (unsubscribeState) unsubscribeState();
|
|
4020
4066
|
if (unsubscribeCmd) unsubscribeCmd();
|
|
4021
4067
|
if (unsubscribeBus) unsubscribeBus();
|
|
@@ -11,6 +11,13 @@ const APP_ROOT = path.resolve(__dirname, '..');
|
|
|
11
11
|
const ROOT = path.resolve(APP_ROOT, '..', '..');
|
|
12
12
|
const DEFAULT_HOST = process.env.WEBAUTO_UI_CLI_HOST || '127.0.0.1';
|
|
13
13
|
const DEFAULT_PORT = Number(process.env.WEBAUTO_UI_CLI_PORT || 7716);
|
|
14
|
+
const readEnvPositiveInt = (name, fallback) => {
|
|
15
|
+
const n = Number(process.env[name]);
|
|
16
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : fallback;
|
|
17
|
+
};
|
|
18
|
+
const DEFAULT_HTTP_TIMEOUT_MS = readEnvPositiveInt('WEBAUTO_UI_CLI_HTTP_TIMEOUT_MS', 25_000);
|
|
19
|
+
const DEFAULT_HTTP_RETRIES = readEnvPositiveInt('WEBAUTO_UI_CLI_HTTP_RETRIES', 1);
|
|
20
|
+
const DEFAULT_START_HEALTH_TIMEOUT_MS = readEnvPositiveInt('WEBAUTO_UI_CLI_START_HEALTH_TIMEOUT_MS', 8_000);
|
|
14
21
|
|
|
15
22
|
function normalizePathForPlatform(raw, platform = process.platform) {
|
|
16
23
|
const input = String(raw || '').trim();
|
|
@@ -121,9 +128,37 @@ function resolveEndpoint() {
|
|
|
121
128
|
|
|
122
129
|
async function requestJson(endpoint, pathname, init = {}) {
|
|
123
130
|
const url = `http://${endpoint.host}:${endpoint.port}${pathname}`;
|
|
124
|
-
const
|
|
125
|
-
const
|
|
126
|
-
|
|
131
|
+
const timeoutMs = parseIntSafe(init?.timeoutMs, DEFAULT_HTTP_TIMEOUT_MS);
|
|
132
|
+
const retries = Math.max(0, parseIntSafe(init?.retries, DEFAULT_HTTP_RETRIES));
|
|
133
|
+
const retryDelayMs = parseIntSafe(init?.retryDelayMs, 300);
|
|
134
|
+
const requestInit = { ...init };
|
|
135
|
+
delete requestInit.timeoutMs;
|
|
136
|
+
delete requestInit.retries;
|
|
137
|
+
delete requestInit.retryDelayMs;
|
|
138
|
+
|
|
139
|
+
let lastErr = null;
|
|
140
|
+
for (let attempt = 0; attempt <= retries; attempt += 1) {
|
|
141
|
+
const controller = new AbortController();
|
|
142
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
143
|
+
try {
|
|
144
|
+
const res = await fetch(url, { ...requestInit, signal: controller.signal });
|
|
145
|
+
clearTimeout(timeout);
|
|
146
|
+
const json = await res.json().catch(() => ({}));
|
|
147
|
+
return { ok: res.ok, status: res.status, json };
|
|
148
|
+
} catch (err) {
|
|
149
|
+
clearTimeout(timeout);
|
|
150
|
+
lastErr = err;
|
|
151
|
+
if (attempt < retries) {
|
|
152
|
+
await sleep(retryDelayMs * (attempt + 1));
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const msg = lastErr?.name === 'AbortError'
|
|
159
|
+
? `request_timeout:${pathname}:${timeoutMs}`
|
|
160
|
+
: (lastErr?.message || String(lastErr));
|
|
161
|
+
throw new Error(msg);
|
|
127
162
|
}
|
|
128
163
|
|
|
129
164
|
function sleep(ms) {
|
|
@@ -134,7 +169,7 @@ async function waitForHealth(endpoint, timeoutMs = 30_000) {
|
|
|
134
169
|
const started = Date.now();
|
|
135
170
|
while (Date.now() - started <= timeoutMs) {
|
|
136
171
|
try {
|
|
137
|
-
const ret = await requestJson(endpoint, '/health');
|
|
172
|
+
const ret = await requestJson(endpoint, '/health', { timeoutMs: 2500, retries: 0 });
|
|
138
173
|
if (ret.ok && ret.json?.ok) return ret.json;
|
|
139
174
|
} catch {
|
|
140
175
|
// keep polling
|
|
@@ -145,7 +180,7 @@ async function waitForHealth(endpoint, timeoutMs = 30_000) {
|
|
|
145
180
|
}
|
|
146
181
|
|
|
147
182
|
async function startConsoleIfNeeded(endpoint) {
|
|
148
|
-
const health = await waitForHealth(endpoint,
|
|
183
|
+
const health = await waitForHealth(endpoint, 3000);
|
|
149
184
|
if (health) return health;
|
|
150
185
|
|
|
151
186
|
const uiConsoleScript = path.join(APP_ROOT, 'entry', 'ui-console.mjs');
|
|
@@ -186,10 +221,17 @@ function printOutput(payload) {
|
|
|
186
221
|
}
|
|
187
222
|
|
|
188
223
|
async function sendAction(endpoint, payload) {
|
|
224
|
+
const waitBudgetMs = payload?.action === 'wait'
|
|
225
|
+
? parseIntSafe(payload?.timeoutMs, 15_000) + 5_000
|
|
226
|
+
: 0;
|
|
227
|
+
const timeoutMs = Math.max(DEFAULT_HTTP_TIMEOUT_MS, waitBudgetMs);
|
|
228
|
+
const retries = payload?.action === 'wait' ? 0 : DEFAULT_HTTP_RETRIES;
|
|
189
229
|
return requestJson(endpoint, '/action', {
|
|
190
230
|
method: 'POST',
|
|
191
231
|
headers: { 'Content-Type': 'application/json' },
|
|
192
232
|
body: JSON.stringify(payload),
|
|
233
|
+
timeoutMs,
|
|
234
|
+
retries,
|
|
193
235
|
});
|
|
194
236
|
}
|
|
195
237
|
|
|
@@ -629,7 +671,8 @@ async function main() {
|
|
|
629
671
|
}
|
|
630
672
|
|
|
631
673
|
if (cmd === 'start') {
|
|
632
|
-
const
|
|
674
|
+
const startWaitMs = parseIntSafe(args.timeout, DEFAULT_START_HEALTH_TIMEOUT_MS);
|
|
675
|
+
const status = await waitForHealth(endpoint, startWaitMs);
|
|
633
676
|
if (!status) throw new Error('ui cli bridge not healthy');
|
|
634
677
|
printOutput({ ok: true, endpoint, status });
|
|
635
678
|
return;
|
|
@@ -637,7 +680,10 @@ async function main() {
|
|
|
637
680
|
|
|
638
681
|
if (cmd === 'status' || cmd === 'snapshot') {
|
|
639
682
|
const pathName = cmd === 'snapshot' ? '/snapshot' : '/status';
|
|
640
|
-
const ret = await requestJson(endpoint, pathName
|
|
683
|
+
const ret = await requestJson(endpoint, pathName, {
|
|
684
|
+
timeoutMs: parseIntSafe(args.timeout, DEFAULT_HTTP_TIMEOUT_MS),
|
|
685
|
+
retries: DEFAULT_HTTP_RETRIES,
|
|
686
|
+
});
|
|
641
687
|
if (!ret.ok) throw new Error(ret.json?.error || `http_${ret.status}`);
|
|
642
688
|
printOutput(ret.json);
|
|
643
689
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@web-auto/webauto",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"webauto": "bin/webauto.mjs"
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
},
|
|
51
51
|
"scripts": {
|
|
52
52
|
"postinstall": "node scripts/postinstall-resources.mjs",
|
|
53
|
+
"prepack": "npm run build",
|
|
53
54
|
"start:mcp:browser": "npx @browsermcp/mcp@latest",
|
|
54
55
|
"build": "npm run prebuild && npm run build:services && npm run self-check:post-build && npm run ui:test && npm --prefix apps/desktop-console run build",
|
|
55
56
|
"build:services": "tsc -p tsconfig.services.json",
|