dsh-multi-router 0.5.4 → 0.5.6
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/CHANGELOG.md +15 -0
- package/README.md +31 -14
- package/THIRD_PARTY_NOTICES.md +31 -0
- package/lib/{chunk-GDUBJ4Z4.js → chunk-CT6ZYIAF.js} +3 -3
- package/lib/{chunk-GDUBJ4Z4.js.map → chunk-CT6ZYIAF.js.map} +2 -2
- package/lib/{chunk-6ZXEVC3R.js → chunk-J3XO7RTF.js} +88 -11
- package/lib/chunk-J3XO7RTF.js.map +7 -0
- package/lib/{chunk-LADXRFL7.js → chunk-U2MU2M5C.js} +11 -4
- package/lib/chunk-U2MU2M5C.js.map +7 -0
- package/lib/{chunk-KCAJ4OAN.js → chunk-XKDXPB5T.js} +283 -33
- package/lib/chunk-XKDXPB5T.js.map +7 -0
- package/lib/cli.js +2 -2
- package/lib/client.js +260 -19
- package/lib/client.js.map +3 -3
- package/lib/clients.js +6 -4
- package/lib/host.js +21 -6
- package/lib/index.js +21 -6
- package/lib/index.js.map +2 -2
- package/lib/manager.js +3 -3
- package/lib/router.js +1 -1
- package/lib/shim.js +2 -2
- package/package.json +1 -1
- package/lib/chunk-6ZXEVC3R.js.map +0 -7
- package/lib/chunk-KCAJ4OAN.js.map +0 -7
- package/lib/chunk-LADXRFL7.js.map +0 -7
|
@@ -2,8 +2,11 @@ import {
|
|
|
2
2
|
Checkins,
|
|
3
3
|
jsonCredential,
|
|
4
4
|
qoderPluginDataDir,
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
readLimitedText,
|
|
6
|
+
refreshWorkBuddyCheckinCredential,
|
|
7
|
+
supportsCheckin,
|
|
8
|
+
workbuddyToken
|
|
9
|
+
} from "./chunk-U2MU2M5C.js";
|
|
7
10
|
import {
|
|
8
11
|
discoverLocalAccounts,
|
|
9
12
|
importAccount,
|
|
@@ -16,7 +19,7 @@ import {
|
|
|
16
19
|
import {
|
|
17
20
|
Telemetry,
|
|
18
21
|
matchModel
|
|
19
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-J3XO7RTF.js";
|
|
20
23
|
import {
|
|
21
24
|
PROVIDERS,
|
|
22
25
|
PROVIDER_NAMES,
|
|
@@ -33,16 +36,16 @@ import {
|
|
|
33
36
|
} from "./chunk-TEZ2FMEO.js";
|
|
34
37
|
|
|
35
38
|
// src/manager.mjs
|
|
36
|
-
import { createHash, randomBytes as
|
|
39
|
+
import { createHash, randomBytes as randomBytes3, timingSafeEqual } from "node:crypto";
|
|
37
40
|
|
|
38
41
|
// package.json
|
|
39
|
-
var version = "0.5.
|
|
42
|
+
var version = "0.5.6";
|
|
40
43
|
|
|
41
44
|
// src/manager.mjs
|
|
42
|
-
import { readFile as
|
|
45
|
+
import { readFile as readFile3, mkdir as mkdir2, mkdtemp as mkdtemp2, rm as rm3 } from "node:fs/promises";
|
|
43
46
|
import { watch } from "node:fs";
|
|
44
47
|
import { dirname as dirname3, join as join4, relative as relative2, resolve } from "node:path";
|
|
45
|
-
import { withFileLock as
|
|
48
|
+
import { withFileLock as withFileLock3 } from "@deepseek-ai/dsh-atomic-write";
|
|
46
49
|
|
|
47
50
|
// upstream/qoder/src/status-paths.ts
|
|
48
51
|
var QODER_STATUS_PATH = "/plugins/dsh-qoder-connect/status";
|
|
@@ -414,7 +417,12 @@ async function refreshGitPlugin(service, profile, row, options2, signal) {
|
|
|
414
417
|
}, { waitMs: 12e4 });
|
|
415
418
|
}
|
|
416
419
|
var PluginUpdates = class {
|
|
417
|
-
constructor({ service = () => void 0, profile = () => void 0, fetchImpl = (...args) => fetch(...args), lookup = registryVersion, refreshGit = refreshGitPlugin } = {}) {
|
|
420
|
+
constructor({ service = () => void 0, profile = () => void 0, fetchImpl = (...args) => fetch(...args), lookup = registryVersion, refreshGit = refreshGitPlugin, stateFile, now = Date.now } = {}) {
|
|
421
|
+
this.stateFile = stateFile;
|
|
422
|
+
this.now = now;
|
|
423
|
+
this.dismissed = {};
|
|
424
|
+
this.lastAttempt = 0;
|
|
425
|
+
this.saveQueue = Promise.resolve();
|
|
418
426
|
this.service = service;
|
|
419
427
|
this.profile = profile;
|
|
420
428
|
this.fetchImpl = fetchImpl;
|
|
@@ -423,10 +431,47 @@ var PluginUpdates = class {
|
|
|
423
431
|
this.lookup = lookup;
|
|
424
432
|
this.refreshGit = refreshGit;
|
|
425
433
|
}
|
|
434
|
+
async load() {
|
|
435
|
+
const saved = this.stateFile && await jsonFile(this.stateFile);
|
|
436
|
+
if (saved?.version === 1) {
|
|
437
|
+
this.lastAttempt = Number.isFinite(saved.lastAttempt) ? saved.lastAttempt : 0;
|
|
438
|
+
this.checkedAt = saved.checkedAt;
|
|
439
|
+
this.dismissed = saved.dismissed && typeof saved.dismissed === "object" && !Array.isArray(saved.dismissed) ? saved.dismissed : {};
|
|
440
|
+
this.rows = Array.isArray(saved.rows) ? saved.rows.filter((r) => ["github", "npm"].includes(r.source?.kind)) : [];
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
persist() {
|
|
444
|
+
if (!this.stateFile) return Promise.resolve();
|
|
445
|
+
const saved = structuredClone({ version: 1, lastAttempt: this.lastAttempt, checkedAt: this.checkedAt, dismissed: this.dismissed, rows: this.rows.filter((r) => ["github", "npm"].includes(r.source.kind)) });
|
|
446
|
+
this.saveQueue = this.saveQueue.catch(() => {
|
|
447
|
+
}).then(() => writePrivateJson(this.stateFile, saved));
|
|
448
|
+
return this.saveQueue;
|
|
449
|
+
}
|
|
450
|
+
reminderKey(row) {
|
|
451
|
+
return JSON.stringify([row.name, row.source, row.version, row.latestVersion ?? row.latestCommit]);
|
|
452
|
+
}
|
|
453
|
+
reminders() {
|
|
454
|
+
return this.rows.filter((r) => r.status === "available" && (!Object.hasOwn(this.dismissed, r.name) || this.dismissed[r.name] !== this.reminderKey(r))).map((r) => ({ key: this.reminderKey(r), name: r.name, version: r.version, latestVersion: r.latestVersion, latestCommit: r.latestCommit }));
|
|
455
|
+
}
|
|
456
|
+
async dismiss(keys) {
|
|
457
|
+
if (!Array.isArray(keys) || keys.length > 200 || keys.some((k) => typeof k !== "string")) throw Error("\u65E0\u6548\u7684\u63D0\u9192\u5217\u8868");
|
|
458
|
+
for (const row of this.reminders()) if (keys.includes(row.key)) Object.defineProperty(this.dismissed, row.name, { value: row.key, enumerable: true, configurable: true, writable: true });
|
|
459
|
+
await this.persist();
|
|
460
|
+
}
|
|
461
|
+
async tick() {
|
|
462
|
+
if (this.flight || !this.state().available) return;
|
|
463
|
+
await this.list();
|
|
464
|
+
if (this.flight || this.now() >= this.lastAttempt && this.now() - this.lastAttempt < 864e5) return;
|
|
465
|
+
this.lastAttempt = this.now();
|
|
466
|
+
await this.persist();
|
|
467
|
+
if (!this.flight && !this.abort.signal.aborted) this.check();
|
|
468
|
+
}
|
|
426
469
|
state() {
|
|
427
470
|
return {
|
|
428
471
|
available: !!this.service()?.listBundles && !!this.profile()?.dir,
|
|
429
472
|
checkedAt: this.checkedAt,
|
|
473
|
+
reminders: this.reminders(),
|
|
474
|
+
autoCheckHours: 24,
|
|
430
475
|
rows: this.rows.map(({ spec, ...row }) => ({ ...row, source: row.source.kind === "unsupported" ? { kind: "unsupported" } : row.source })),
|
|
431
476
|
job: this.job,
|
|
432
477
|
error: this.error
|
|
@@ -502,13 +547,20 @@ var PluginUpdates = class {
|
|
|
502
547
|
}, (e) => {
|
|
503
548
|
this.job.state = this.job.cancelRequested ? "cancelled" : "failed";
|
|
504
549
|
this.job.message = e.message;
|
|
505
|
-
}).finally(() => {
|
|
550
|
+
}).finally(async () => {
|
|
506
551
|
this.job.finishedAt = Date.now();
|
|
552
|
+
try {
|
|
553
|
+
await this.persist();
|
|
554
|
+
} catch {
|
|
555
|
+
this.error = "\u66F4\u65B0\u68C0\u67E5\u8BB0\u5F55\u672A\u80FD\u4FDD\u5B58\uFF0C\u8BF7\u68C0\u67E5\u672C\u5730\u76EE\u5F55\u6743\u9650";
|
|
556
|
+
}
|
|
507
557
|
this.flight = void 0;
|
|
508
558
|
});
|
|
509
559
|
}
|
|
510
560
|
check() {
|
|
511
561
|
this.start("check", async (job) => {
|
|
562
|
+
this.lastAttempt = this.now();
|
|
563
|
+
await this.persist();
|
|
512
564
|
await this.list();
|
|
513
565
|
const rows = this.rows.filter((r) => ["npm", "github"].includes(r.source.kind));
|
|
514
566
|
job.total = rows.length;
|
|
@@ -617,7 +669,8 @@ var PluginUpdates = class {
|
|
|
617
669
|
if (input.registry && !["https://registry.npmjs.org/", "https://registry.npmmirror.com/"].includes(input.registry)) throw Error("\u65E0\u6548\u7684 npm \u6765\u6E90");
|
|
618
670
|
if (input.action === "plugins.list") {
|
|
619
671
|
if (!this.flight) await this.list();
|
|
620
|
-
} else if (input.action === "plugins.
|
|
672
|
+
} else if (input.action === "plugins.dismiss") await this.dismiss(input.keys);
|
|
673
|
+
else if (input.action === "plugins.check") this.check();
|
|
621
674
|
else if (input.action === "plugins.update") this.update(input.names, input.registry);
|
|
622
675
|
else if (input.action === "plugins.install") this.install(input.spec, input.registry);
|
|
623
676
|
else if (input.action === "plugins.cancel") {
|
|
@@ -630,6 +683,141 @@ var PluginUpdates = class {
|
|
|
630
683
|
}
|
|
631
684
|
};
|
|
632
685
|
|
|
686
|
+
// src/workbuddy-login.mjs
|
|
687
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
688
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
689
|
+
import { withFileLock as withFileLock2 } from "@deepseek-ai/dsh-atomic-write";
|
|
690
|
+
var WorkBuddyLogin = class {
|
|
691
|
+
constructor(file, { fetchImpl = (...args) => fetch(...args), now = Date.now } = {}) {
|
|
692
|
+
this.file = file;
|
|
693
|
+
this.fetchImpl = fetchImpl;
|
|
694
|
+
this.now = now;
|
|
695
|
+
this.abort = new AbortController();
|
|
696
|
+
this.abort.signal.addEventListener("abort", () => this.finish(), { once: true });
|
|
697
|
+
}
|
|
698
|
+
async reserve(field, ms) {
|
|
699
|
+
await withFileLock2(this.file, async () => {
|
|
700
|
+
let state = {};
|
|
701
|
+
try {
|
|
702
|
+
state = JSON.parse(await readFile2(this.file, "utf8"));
|
|
703
|
+
} catch (e) {
|
|
704
|
+
if (e.code !== "ENOENT") throw Error("\u767B\u5F55\u51B7\u5374\u8BB0\u5F55\u4E0D\u53EF\u8BFB\uFF0C\u8BF7\u68C0\u67E5\u672C\u673A\u6587\u4EF6");
|
|
705
|
+
}
|
|
706
|
+
if (state.pausedUntil > this.now() || state[field] > this.now()) throw Error("\u8BF7\u6C42\u51B7\u5374\u4E2D\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\uFF1B\u5E73\u53F0\u9650\u5236\u8BF7\u5728\u5B98\u65B9\u5BA2\u6237\u7AEF\u5904\u7406");
|
|
707
|
+
state[field] = this.now() + ms;
|
|
708
|
+
await writePrivateJson(this.file, state);
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
async pause() {
|
|
712
|
+
this.pending = void 0;
|
|
713
|
+
await withFileLock2(this.file, async () => {
|
|
714
|
+
const state = JSON.parse(await readFile2(this.file, "utf8"));
|
|
715
|
+
state.pausedUntil = this.now() + 15 * 6e4;
|
|
716
|
+
await writePrivateJson(this.file, state);
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
async post(path, body) {
|
|
720
|
+
let response;
|
|
721
|
+
try {
|
|
722
|
+
response = await this.fetchImpl(`https://www.workbuddy.cn/v2/plugin/login/${path}`, {
|
|
723
|
+
method: "POST",
|
|
724
|
+
headers: { "Content-Type": "application/json", Accept: "application/json", Origin: "https://www.workbuddy.cn", Referer: "https://www.workbuddy.cn/" },
|
|
725
|
+
body: JSON.stringify(body),
|
|
726
|
+
signal: AbortSignal.any([this.abort.signal, AbortSignal.timeout(3e4)]),
|
|
727
|
+
redirect: "error"
|
|
728
|
+
});
|
|
729
|
+
} catch {
|
|
730
|
+
throw Error("\u767B\u5F55\u8BF7\u6C42\u672A\u5B8C\u6210\uFF0C\u8BF7\u7A0D\u540E\u624B\u52A8\u91CD\u8BD5\uFF1B\u4E0D\u4F1A\u81EA\u52A8\u91CD\u53D1\u77ED\u4FE1");
|
|
731
|
+
}
|
|
732
|
+
if ([403, 429].includes(response.status)) {
|
|
733
|
+
await this.pause();
|
|
734
|
+
throw Error("\u5E73\u53F0\u9650\u5236\u6216\u9700\u8981\u9A8C\u8BC1\uFF0C\u5DF2\u6682\u505C\u767B\u5F55\u8BF7\u6C42\uFF0C\u8BF7\u4F7F\u7528\u5B98\u65B9\u5BA2\u6237\u7AEF");
|
|
735
|
+
}
|
|
736
|
+
if (!response.ok) throw Error("\u5B98\u65B9\u767B\u5F55\u670D\u52A1\u6682\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5");
|
|
737
|
+
let result;
|
|
738
|
+
try {
|
|
739
|
+
result = JSON.parse(await readLimitedText(response, 256 * 1024, "Login"));
|
|
740
|
+
} catch {
|
|
741
|
+
throw Error("\u767B\u5F55\u54CD\u5E94\u65E0\u6CD5\u8BC6\u522B\uFF0C\u8BF7\u4F7F\u7528\u5B98\u65B9\u5BA2\u6237\u7AEF");
|
|
742
|
+
}
|
|
743
|
+
if (result?.code !== 0) {
|
|
744
|
+
if ([403, 429].includes(result?.code) || /频繁|图形验证码|滑块|安全验证|captcha|risk|too many|frequent|验证失败次数/i.test(result?.msg || "")) {
|
|
745
|
+
await this.pause();
|
|
746
|
+
throw Error("\u5E73\u53F0\u9650\u5236\u6216\u9700\u8981\u9A8C\u8BC1\uFF0C\u5DF2\u6682\u505C\u767B\u5F55\u8BF7\u6C42\uFF0C\u8BF7\u4F7F\u7528\u5B98\u65B9\u5BA2\u6237\u7AEF");
|
|
747
|
+
}
|
|
748
|
+
throw Error(path === "token" ? "\u767B\u5F55\u672A\u6210\u529F\uFF0C\u8BF7\u68C0\u67E5\u9A8C\u8BC1\u7801\u662F\u5426\u6B63\u786E\u3001\u8FC7\u671F\uFF1B\u4EC5\u652F\u6301\u5DF2\u6709\u8D26\u53F7" : "\u77ED\u4FE1\u672A\u53D1\u9001\u6210\u529F\uFF0C\u8BF7\u786E\u8BA4\u5DF2\u6709\u8D26\u53F7\u53CA\u624B\u673A\u53F7\uFF0C\u6216\u4F7F\u7528\u5B98\u65B9\u5BA2\u6237\u7AEF");
|
|
749
|
+
}
|
|
750
|
+
return result.data ?? {};
|
|
751
|
+
}
|
|
752
|
+
async send(phone) {
|
|
753
|
+
if (typeof phone !== "string" || !/^1[3-9]\d{9}$/.test(phone)) throw Error("\u8BF7\u8F93\u5165 11 \u4F4D\u4E2D\u56FD\u5927\u9646\u624B\u673A\u53F7");
|
|
754
|
+
if (this.flight) throw Error("\u767B\u5F55\u8BF7\u6C42\u6B63\u5728\u5904\u7406\u4E2D");
|
|
755
|
+
this.flight = (async () => {
|
|
756
|
+
await this.reserve("smsAt", 6e4);
|
|
757
|
+
this.pending = void 0;
|
|
758
|
+
const data = await this.post("send-sms", { phone });
|
|
759
|
+
const expires = Math.min(300, Math.max(1, Number(data.expires_in) || 300)) * 1e3;
|
|
760
|
+
this.pending = { phone, challenge: randomBytes2(24).toString("hex"), expiresAt: this.now() + expires, attempts: 0 };
|
|
761
|
+
clearTimeout(this.expiryTimer);
|
|
762
|
+
this.expiryTimer = setTimeout(() => this.finish(), expires);
|
|
763
|
+
this.expiryTimer.unref();
|
|
764
|
+
return { challenge: this.pending.challenge, expiresAt: this.pending.expiresAt, resendAt: this.now() + 6e4 };
|
|
765
|
+
})();
|
|
766
|
+
try {
|
|
767
|
+
return await this.flight;
|
|
768
|
+
} finally {
|
|
769
|
+
this.flight = void 0;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
async complete(challenge, code, verify = true) {
|
|
773
|
+
if (typeof challenge !== "string" || !this.pending || challenge !== this.pending.challenge || this.pending.expiresAt <= this.now()) throw Error("\u672C\u6B21\u767B\u5F55\u5DF2\u8FC7\u671F\u6216\u63D2\u4EF6\u5DF2\u91CD\u542F\uFF0C\u8BF7\u91CD\u65B0\u83B7\u53D6\u9A8C\u8BC1\u7801");
|
|
774
|
+
if (this.flight) throw Error("\u767B\u5F55\u8BF7\u6C42\u6B63\u5728\u5904\u7406\u4E2D");
|
|
775
|
+
if (this.pending.document) return this.pending;
|
|
776
|
+
if (typeof code !== "string" || !/^\d{4,8}$/.test(code) || typeof verify !== "boolean") throw Error("\u8BF7\u8F93\u5165\u6536\u5230\u7684\u77ED\u4FE1\u9A8C\u8BC1\u7801");
|
|
777
|
+
if (this.pending.attempts >= 5) throw Error("\u672C\u6B21\u9A8C\u8BC1\u7801\u5C1D\u8BD5\u6B21\u6570\u5DF2\u8FBE\u4E0A\u9650\uFF0C\u8BF7\u7A0D\u540E\u91CD\u65B0\u83B7\u53D6");
|
|
778
|
+
this.flight = (async () => {
|
|
779
|
+
await this.reserve("loginAt", 5e3);
|
|
780
|
+
const pending = this.pending;
|
|
781
|
+
pending.attempts++;
|
|
782
|
+
const data = await this.post("token", { login_method: "phone", phone: pending.phone, sms_code: code });
|
|
783
|
+
const accessToken = data.accessToken || data.access_token, refreshToken = data.refreshToken || data.refresh_token;
|
|
784
|
+
let token;
|
|
785
|
+
try {
|
|
786
|
+
token = workbuddyToken(accessToken);
|
|
787
|
+
} catch {
|
|
788
|
+
throw Error("\u767B\u5F55\u8FD4\u56DE\u7684\u4EE4\u724C\u4E0D\u5C5E\u4E8E WorkBuddy \u4E2D\u56FD\u7248\uFF0C\u8BF7\u4F7F\u7528\u5B98\u65B9\u5BA2\u6237\u7AEF");
|
|
789
|
+
}
|
|
790
|
+
if (typeof refreshToken !== "string" || !refreshToken || !Number.isFinite(token.exp) || token.exp * 1e3 <= this.now()) throw Error("\u767B\u5F55\u54CD\u5E94\u7F3A\u5C11\u6709\u6548\u51ED\u8BC1\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55");
|
|
791
|
+
let credential = { accessToken, refreshToken, uid: token.sub, expiresAtMs: token.exp * 1e3, domain: "www.workbuddy.cn", source: "dsh" };
|
|
792
|
+
if (verify) {
|
|
793
|
+
try {
|
|
794
|
+
const rotated = await refreshWorkBuddyCheckinCredential(credential, { fetchImpl: this.fetchImpl, signal: AbortSignal.any([this.abort.signal, AbortSignal.timeout(3e4)]) });
|
|
795
|
+
credential = {
|
|
796
|
+
...credential,
|
|
797
|
+
accessToken: rotated.accessToken,
|
|
798
|
+
refreshToken: rotated.refreshToken || credential.refreshToken,
|
|
799
|
+
expiresAtMs: Date.now() + rotated.expiresInSec * 1e3
|
|
800
|
+
};
|
|
801
|
+
} catch {
|
|
802
|
+
pending.warning = "\u767B\u5F55\u51ED\u8BC1\u5DF2\u4FDD\u5B58\uFF0C\u4F46\u5237\u65B0\u4EE4\u724C\u9A8C\u8BC1\u672A\u901A\u8FC7\uFF1B\u8BF7\u7A0D\u540E\u5728\u8D26\u53F7\u8BE6\u60C5\u68C0\u67E5\u767B\u5F55\u6709\u6548\u671F\u3002";
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
delete pending.phone;
|
|
806
|
+
pending.document = { version: 1, credential };
|
|
807
|
+
return pending;
|
|
808
|
+
})();
|
|
809
|
+
try {
|
|
810
|
+
return await this.flight;
|
|
811
|
+
} finally {
|
|
812
|
+
this.flight = void 0;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
finish() {
|
|
816
|
+
clearTimeout(this.expiryTimer);
|
|
817
|
+
this.pending = void 0;
|
|
818
|
+
}
|
|
819
|
+
};
|
|
820
|
+
|
|
633
821
|
// src/manager.mjs
|
|
634
822
|
var MANAGEMENT_PATH = "/.well-known/dsh-multi-router";
|
|
635
823
|
var revisionOf = (raw) => createHash("sha256").update(JSON.stringify(raw)).digest("hex");
|
|
@@ -681,8 +869,9 @@ var Manager = class {
|
|
|
681
869
|
this.monitorFlights = /* @__PURE__ */ new Map();
|
|
682
870
|
this.loginInfo = /* @__PURE__ */ new Map();
|
|
683
871
|
this.telemetry = new Telemetry(join4(dirname3(file), "usage.json"));
|
|
684
|
-
this.plugins = new PluginUpdates(pluginAccess);
|
|
872
|
+
this.plugins = new PluginUpdates({ ...pluginAccess, stateFile: join4(dirname3(file), "plugin-updates.json") });
|
|
685
873
|
this.checkins = new Checkins(join4(dirname3(file), "checkins.json"), this);
|
|
874
|
+
this.workbuddyLogin = new WorkBuddyLogin(join4(dirname3(file), "workbuddy-login.json"), { fetchImpl: pluginAccess.fetchImpl });
|
|
686
875
|
this.setupFile = join4(dirname3(file), "initial-setup.json");
|
|
687
876
|
router.telemetry = this.telemetry;
|
|
688
877
|
router.monitor = (a) => this.monitorAccount(a);
|
|
@@ -690,16 +879,20 @@ var Manager = class {
|
|
|
690
879
|
router.freeModels = (account) => this.freeModels(account);
|
|
691
880
|
}
|
|
692
881
|
async load() {
|
|
693
|
-
this.raw = JSON.parse(await
|
|
882
|
+
this.raw = JSON.parse(await readFile3(this.file, "utf8"));
|
|
694
883
|
try {
|
|
695
|
-
this.setup = JSON.parse(await
|
|
884
|
+
this.setup = JSON.parse(await readFile3(this.setupFile, "utf8"));
|
|
696
885
|
} catch {
|
|
697
886
|
this.setup = void 0;
|
|
698
887
|
}
|
|
699
888
|
this.config = validateConfig(this.raw, this.file);
|
|
700
889
|
await this.checkins.load();
|
|
890
|
+
await this.plugins.load();
|
|
891
|
+
if (this.plugins.state().available) await this.plugins.list().catch(() => {
|
|
892
|
+
this.plugins.rows = [];
|
|
893
|
+
});
|
|
701
894
|
try {
|
|
702
|
-
const saved = JSON.parse(await
|
|
895
|
+
const saved = JSON.parse(await readFile3(this.catalogFile, "utf8"));
|
|
703
896
|
for (const account of this.config.accounts) {
|
|
704
897
|
const item = saved[account.id];
|
|
705
898
|
if (item?.credentialFile === account.credentialFile && Array.isArray(item.models)) {
|
|
@@ -739,9 +932,9 @@ var Manager = class {
|
|
|
739
932
|
...discovered.status.map((s) => ({ ...s, checkedAt: Date.now() }))
|
|
740
933
|
];
|
|
741
934
|
if (this.closed || !discovered.accounts.length) return;
|
|
742
|
-
await
|
|
935
|
+
await withFileLock3(this.file, async () => {
|
|
743
936
|
if (this.closed) return;
|
|
744
|
-
let raw = JSON.parse(await
|
|
937
|
+
let raw = JSON.parse(await readFile3(this.file, "utf8"));
|
|
745
938
|
let changed = false;
|
|
746
939
|
const saved = /* @__PURE__ */ new Map();
|
|
747
940
|
for (const item of discovered.accounts) {
|
|
@@ -825,9 +1018,9 @@ var Manager = class {
|
|
|
825
1018
|
}
|
|
826
1019
|
async finishInitialSetup() {
|
|
827
1020
|
if (this.setup?.completedAt) return;
|
|
828
|
-
await
|
|
1021
|
+
await withFileLock3(this.setupFile, async () => {
|
|
829
1022
|
try {
|
|
830
|
-
this.setup = JSON.parse(await
|
|
1023
|
+
this.setup = JSON.parse(await readFile3(this.setupFile, "utf8"));
|
|
831
1024
|
} catch {
|
|
832
1025
|
}
|
|
833
1026
|
if (this.setup?.completedAt) return;
|
|
@@ -1009,7 +1202,7 @@ var Manager = class {
|
|
|
1009
1202
|
if (this.closed || !this.config.accounts.some((a) => a.id === account.id && a.credentialFile === account.credentialFile)) return;
|
|
1010
1203
|
this.catalogs.set(account.id, { credentialFile: account.credentialFile, fetchedAt: Date.now(), pricingVersion: 1, models });
|
|
1011
1204
|
this.catalogErrors.delete(account.id);
|
|
1012
|
-
await
|
|
1205
|
+
await withFileLock3(this.catalogFile, () => writePrivateJson(this.catalogFile, Object.fromEntries(this.catalogs)));
|
|
1013
1206
|
} catch (error) {
|
|
1014
1207
|
if (this.closed || !this.config.accounts.some((a) => a.id === account.id)) return;
|
|
1015
1208
|
this.catalogErrors.set(account.id, { status: Number(error.status ?? error.failure?.status) || 502, code: "catalog_fetch_failed", at: Date.now() });
|
|
@@ -1062,6 +1255,63 @@ var Manager = class {
|
|
|
1062
1255
|
}
|
|
1063
1256
|
async action(input) {
|
|
1064
1257
|
if (!input || typeof input !== "object" || Array.isArray(input)) bad("\u8BF7\u6C42\u683C\u5F0F\u65E0\u6548");
|
|
1258
|
+
if (input.action === "workbuddy.sms") {
|
|
1259
|
+
try {
|
|
1260
|
+
const workbuddyLogin = await this.workbuddyLogin.send(input.phone);
|
|
1261
|
+
return { ...this.state(), workbuddyLogin };
|
|
1262
|
+
} catch (e) {
|
|
1263
|
+
bad(e.message);
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
if (input.action === "workbuddy.login") {
|
|
1267
|
+
if (input.revision !== revisionOf(JSON.parse(await readFile3(this.file, "utf8")))) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u91CD\u65B0\u63D0\u4EA4\u767B\u5F55");
|
|
1268
|
+
let login;
|
|
1269
|
+
try {
|
|
1270
|
+
login = await this.workbuddyLogin.complete(input.challenge, input.code, input.verify ?? true);
|
|
1271
|
+
} catch (e) {
|
|
1272
|
+
bad(e.message);
|
|
1273
|
+
}
|
|
1274
|
+
let existing;
|
|
1275
|
+
for (const a of this.config.accounts.filter((a2) => a2.provider === "workbuddy")) {
|
|
1276
|
+
try {
|
|
1277
|
+
if ((await jsonCredential(a.credentialFile)).credential.uid === login.document.credential.uid) {
|
|
1278
|
+
existing = a;
|
|
1279
|
+
break;
|
|
1280
|
+
}
|
|
1281
|
+
} catch {
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
if (input.id && input.id !== existing?.id) bad("\u767B\u5F55\u8EAB\u4EFD\u4E0E\u6B63\u5728\u7F16\u8F91\u7684\u8D26\u53F7\u4E0D\u540C\uFF0C\u8BF7\u4ECE\u6DFB\u52A0\u8D26\u53F7\u5165\u53E3\u4FDD\u5B58");
|
|
1285
|
+
const state = await this.action({
|
|
1286
|
+
action: existing ? "account.credentials" : "account.add",
|
|
1287
|
+
revision: input.revision,
|
|
1288
|
+
id: existing?.id ?? `workbuddy_${randomBytes3(8).toString("hex")}`,
|
|
1289
|
+
provider: "workbuddy",
|
|
1290
|
+
name: input.name,
|
|
1291
|
+
document: login.document
|
|
1292
|
+
});
|
|
1293
|
+
this.workbuddyLogin.finish();
|
|
1294
|
+
return { ...state, loginResult: { warning: login.warning, saved: true } };
|
|
1295
|
+
}
|
|
1296
|
+
if (input.action === "checkins.verify") {
|
|
1297
|
+
const account = this.config.accounts.find((a) => a.id === input.id && a.provider === "workbuddy");
|
|
1298
|
+
if (!account) bad("\u8BF7\u9009\u62E9 WorkBuddy \u4E2D\u56FD\u7248\u8D26\u53F7");
|
|
1299
|
+
if (this.checkins.flight || this.checkins.failed) bad("\u8BF7\u7B49\u5F85\u7B7E\u5230\u5B8C\u6210\uFF0C\u6216\u5148\u4FEE\u590D\u7B7E\u5230\u8BB0\u5F55");
|
|
1300
|
+
try {
|
|
1301
|
+
await this.workbuddyLogin.reserve("verifyAt", 3e4);
|
|
1302
|
+
} catch (e) {
|
|
1303
|
+
bad(e.message);
|
|
1304
|
+
}
|
|
1305
|
+
try {
|
|
1306
|
+
const client = await this.router.client(account);
|
|
1307
|
+
await client.checkin.validate(AbortSignal.timeout(3e4));
|
|
1308
|
+
await this.checkins.credentialsUpdated(account.id);
|
|
1309
|
+
await this.readLogin(account);
|
|
1310
|
+
} catch {
|
|
1311
|
+
bad("\u767B\u5F55\u9A8C\u8BC1\u672A\u901A\u8FC7\uFF1A\u8BF7\u5728\u8D26\u53F7\u4E0E\u767B\u5F55\u4E2D\u66F4\u65B0\u51ED\u8BC1\uFF1B\u4E0D\u4F1A\u81EA\u52A8\u91CD\u8BD5\u6216\u9886\u53D6");
|
|
1312
|
+
}
|
|
1313
|
+
return this.state();
|
|
1314
|
+
}
|
|
1065
1315
|
if (input.action === "checkins.run") {
|
|
1066
1316
|
try {
|
|
1067
1317
|
this.checkins.start(input.ids);
|
|
@@ -1087,14 +1337,14 @@ var Manager = class {
|
|
|
1087
1337
|
return this.state();
|
|
1088
1338
|
}
|
|
1089
1339
|
if (input.action === "setup.dismiss") {
|
|
1090
|
-
await
|
|
1340
|
+
await withFileLock3(this.setupFile, async () => {
|
|
1091
1341
|
this.setup = { ...this.setup, completedAt: this.setup?.completedAt ?? Date.now(), dismissed: true };
|
|
1092
1342
|
await writePrivateJson(this.setupFile, this.setup);
|
|
1093
1343
|
});
|
|
1094
1344
|
return this.state();
|
|
1095
1345
|
}
|
|
1096
1346
|
if (input.action === "account.export") {
|
|
1097
|
-
if (input.revision !== revisionOf(JSON.parse(await
|
|
1347
|
+
if (input.revision !== revisionOf(JSON.parse(await readFile3(this.file, "utf8")))) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u63D0\u53D6\u51ED\u8BC1");
|
|
1098
1348
|
const account = this.config.accounts.find((a) => a.id === input.id);
|
|
1099
1349
|
if (!account) bad("\u8D26\u53F7\u4E0D\u5B58\u5728");
|
|
1100
1350
|
let credential;
|
|
@@ -1116,7 +1366,7 @@ var Manager = class {
|
|
|
1116
1366
|
} };
|
|
1117
1367
|
}
|
|
1118
1368
|
if (input.action === "config.export") {
|
|
1119
|
-
if (input.revision !== revisionOf(JSON.parse(await
|
|
1369
|
+
if (input.revision !== revisionOf(JSON.parse(await readFile3(this.file, "utf8")))) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u5BFC\u51FA");
|
|
1120
1370
|
try {
|
|
1121
1371
|
return { ...this.state(), exported: { filename: `dsh-multi-router-${input.includeCredentials ? "encrypted" : "settings"}-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.json`, document: await exportConfiguration(this, input) } };
|
|
1122
1372
|
} catch (e) {
|
|
@@ -1152,7 +1402,7 @@ var Manager = class {
|
|
|
1152
1402
|
}
|
|
1153
1403
|
if (input.action === "account.capture") {
|
|
1154
1404
|
if (!PROVIDERS.includes(input.provider)) bad("\u672A\u77E5\u4F9B\u5E94\u5546");
|
|
1155
|
-
input = { ...input, action: "account.add", id: `${input.provider.replace("-", "_")}_${
|
|
1405
|
+
input = { ...input, action: "account.add", id: `${input.provider.replace("-", "_")}_${randomBytes3(4).toString("hex")}`, capture: true };
|
|
1156
1406
|
}
|
|
1157
1407
|
if (input.action === "refresh") {
|
|
1158
1408
|
if (input.provider && !PROVIDERS.includes(input.provider)) bad("\u672A\u77E5\u4F9B\u5E94\u5546");
|
|
@@ -1163,8 +1413,8 @@ var Manager = class {
|
|
|
1163
1413
|
await this.checkFreeModels(true);
|
|
1164
1414
|
return this.state();
|
|
1165
1415
|
}
|
|
1166
|
-
return
|
|
1167
|
-
let raw = JSON.parse(await
|
|
1416
|
+
return withFileLock3(this.file, async () => {
|
|
1417
|
+
let raw = JSON.parse(await readFile3(this.file, "utf8"));
|
|
1168
1418
|
if (input.revision !== revisionOf(raw)) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u518D\u4FDD\u5B58");
|
|
1169
1419
|
raw.routes ??= structuredClone(this.config.routes);
|
|
1170
1420
|
let staging;
|
|
@@ -1174,7 +1424,7 @@ var Manager = class {
|
|
|
1174
1424
|
try {
|
|
1175
1425
|
const prepared = await prepareMigration(this, raw, await decodeConfiguration(input), input.mode);
|
|
1176
1426
|
staging = prepared.staging;
|
|
1177
|
-
await writePrivateJson(`${this.file}.before-import-${Date.now()}-${
|
|
1427
|
+
await writePrivateJson(`${this.file}.before-import-${Date.now()}-${randomBytes3(3).toString("hex")}.bak`, raw);
|
|
1178
1428
|
raw = prepared.raw;
|
|
1179
1429
|
} catch (e) {
|
|
1180
1430
|
bad(e.message);
|
|
@@ -1264,8 +1514,8 @@ var Manager = class {
|
|
|
1264
1514
|
if (input.name !== void 0) account.name = input.name;
|
|
1265
1515
|
if (input.enabled !== void 0) account.enabled = input.enabled;
|
|
1266
1516
|
} else if (input.action === "accounts.enabled") {
|
|
1267
|
-
if (!PROVIDERS.includes(input.provider) || typeof input.enabled !== "boolean") bad("\u4F9B\u5E94\u5546\u6216\u8DEF\u7531\u53C2\u4E0E\u5F00\u5173\u65E0\u6548");
|
|
1268
|
-
for (const a of raw.accounts.filter((a2) => a2.provider === input.provider)) a.enabled = input.enabled;
|
|
1517
|
+
if (input.provider !== void 0 && !PROVIDERS.includes(input.provider) || typeof input.enabled !== "boolean") bad("\u4F9B\u5E94\u5546\u6216\u8DEF\u7531\u53C2\u4E0E\u5F00\u5173\u65E0\u6548");
|
|
1518
|
+
for (const a of raw.accounts.filter((a2) => !input.provider || a2.provider === input.provider)) a.enabled = input.enabled;
|
|
1269
1519
|
} else if (input.action === "account.move") {
|
|
1270
1520
|
if (!account || ![-1, 1].includes(input.direction)) bad("\u8D26\u53F7\u6216\u987A\u5E8F\u65E0\u6548");
|
|
1271
1521
|
const group = raw.accounts.filter((a) => a.provider === account.provider), position = group.indexOf(account) + input.direction;
|
|
@@ -1313,7 +1563,7 @@ var Manager = class {
|
|
|
1313
1563
|
} };
|
|
1314
1564
|
} else if (input.action === "route.save") {
|
|
1315
1565
|
if (input.id === void 0 || input.id === "") {
|
|
1316
|
-
input.id = input.previousId || `route_${
|
|
1566
|
+
input.id = input.previousId || `route_${randomBytes3(8).toString("hex")}`;
|
|
1317
1567
|
}
|
|
1318
1568
|
if (!/^[a-zA-Z0-9_.-]{1,128}$/.test(input.id ?? "")) bad("\u8DEF\u7531 ID \u4EC5\u652F\u6301\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u70B9\u3001\u4E0B\u5212\u7EBF\u548C\u77ED\u6A2A\u7EBF");
|
|
1319
1569
|
if (input.previousId && !Object.hasOwn(raw.routes, input.previousId)) bad("\u539F\u8DEF\u7531\u4E0D\u5B58\u5728\uFF0C\u8BF7\u5237\u65B0\u9875\u9762");
|
|
@@ -1390,8 +1640,8 @@ var Manager = class {
|
|
|
1390
1640
|
if (filename && String(filename) !== this.file.split(/[\\/]/).pop()) return;
|
|
1391
1641
|
clearTimeout(timer);
|
|
1392
1642
|
timer = setTimeout(() => {
|
|
1393
|
-
void
|
|
1394
|
-
const raw = JSON.parse(await
|
|
1643
|
+
void withFileLock3(this.file, async () => {
|
|
1644
|
+
const raw = JSON.parse(await readFile3(this.file, "utf8"));
|
|
1395
1645
|
if (this.closed || revisionOf(raw) === this.revision) return;
|
|
1396
1646
|
const config = validateConfig(raw, this.file);
|
|
1397
1647
|
this.raw = raw;
|
|
@@ -1409,7 +1659,7 @@ var Manager = class {
|
|
|
1409
1659
|
}
|
|
1410
1660
|
};
|
|
1411
1661
|
function managementHandler(manager) {
|
|
1412
|
-
const key =
|
|
1662
|
+
const key = randomBytes3(32).toString("base64url");
|
|
1413
1663
|
const json = (res, status, value) => {
|
|
1414
1664
|
res.writeHead(status, { "Content-Type": "application/json", "Cache-Control": "no-store", "X-Content-Type-Options": "nosniff" });
|
|
1415
1665
|
res.end(JSON.stringify(value));
|
|
@@ -1458,4 +1708,4 @@ export {
|
|
|
1458
1708
|
Manager,
|
|
1459
1709
|
managementHandler
|
|
1460
1710
|
};
|
|
1461
|
-
//# sourceMappingURL=chunk-
|
|
1711
|
+
//# sourceMappingURL=chunk-XKDXPB5T.js.map
|