hiwork-core 0.0.18 → 0.0.19
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/lib/client.js +380 -2
- package/lib/index.js +871 -13
- package/lib/pms-client.js +572 -0
- package/lib/pms-todo-bridge.js +640 -0
- package/lib/types/client/contracts.d.ts +22 -0
- package/lib/types/client/pms-todo.d.ts +357 -0
- package/lib/types/pms-client.d.ts +293 -0
- package/lib/types/pms-todo-bridge.d.ts +426 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -3137,7 +3137,7 @@ function isUnloading(ctx) {
|
|
|
3137
3137
|
}
|
|
3138
3138
|
|
|
3139
3139
|
// src/version.ts
|
|
3140
|
-
var PKG_VERSION = false ? "dev" : "0.0.
|
|
3140
|
+
var PKG_VERSION = false ? "dev" : "0.0.19";
|
|
3141
3141
|
|
|
3142
3142
|
// src/ai-router-client.ts
|
|
3143
3143
|
var AUTH_AUTHORIZE_PATH = "/api/auth/authorize";
|
|
@@ -5446,6 +5446,850 @@ function registerWorkbenchRpc(ctx, deps, store) {
|
|
|
5446
5446
|
};
|
|
5447
5447
|
}
|
|
5448
5448
|
|
|
5449
|
+
// src/pms-client.ts
|
|
5450
|
+
var PMS_BASE_URL = "https://pms.hivery.cn";
|
|
5451
|
+
var AUTH_LOGIN_PATH = "/api/auth/login";
|
|
5452
|
+
var AUTH_REFRESH_PATH = "/api/auth/refresh";
|
|
5453
|
+
var TODO_TODAY_PATH = "/api/todo/today";
|
|
5454
|
+
var TODO_STATISTICS_PATH = "/api/todo/statistics";
|
|
5455
|
+
var TASK_LIST_PATH = "/api/task/tasks";
|
|
5456
|
+
var PMS_REQUEST_TIMEOUT_MS = 1e4;
|
|
5457
|
+
var PMS_ACCESS_TOKEN_TTL_MS = 10 * 60 * 60 * 1e3;
|
|
5458
|
+
var PMS_RESULT_SUCCESS_CODE = 200;
|
|
5459
|
+
var PMS_RESULT_UNAUTHORIZED_CODE = 401;
|
|
5460
|
+
var PMS_AUTH_FAILURE_KEYWORDS = [
|
|
5461
|
+
"\u7528\u6237\u540D\u6216\u5BC6\u7801\u9519\u8BEF",
|
|
5462
|
+
"\u7528\u6237\u540D\u4E0D\u80FD\u4E3A\u7A7A",
|
|
5463
|
+
"\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A",
|
|
5464
|
+
"\u7528\u6237\u5DF2\u88AB\u7981\u7528",
|
|
5465
|
+
"\u7528\u6237\u4E0D\u5B58\u5728\u6216\u5DF2\u88AB\u7981\u7528",
|
|
5466
|
+
"\u5237\u65B0Token\u65E0\u6548\u6216\u5DF2\u8FC7\u671F",
|
|
5467
|
+
"\u65E0\u6CD5\u4ECEToken\u4E2D\u83B7\u53D6\u7528\u6237\u4FE1\u606F"
|
|
5468
|
+
];
|
|
5469
|
+
function isAuthFailureMessage(message) {
|
|
5470
|
+
if (typeof message !== "string") return false;
|
|
5471
|
+
return PMS_AUTH_FAILURE_KEYWORDS.some((keyword) => message.includes(keyword));
|
|
5472
|
+
}
|
|
5473
|
+
var PMS_PERMISSION_DENIED_MESSAGES = ["\u60A8\u6CA1\u6709\u8BE5\u64CD\u4F5C\u7684\u6743\u9650\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u5206\u914D\u6743\u9650"];
|
|
5474
|
+
function isPermissionDeniedMessage(message) {
|
|
5475
|
+
if (typeof message !== "string") return false;
|
|
5476
|
+
return PMS_PERMISSION_DENIED_MESSAGES.some((text) => message.includes(text));
|
|
5477
|
+
}
|
|
5478
|
+
var PmsError = class extends Error {
|
|
5479
|
+
constructor(code, message, status) {
|
|
5480
|
+
super(message);
|
|
5481
|
+
this.code = code;
|
|
5482
|
+
this.status = status;
|
|
5483
|
+
this.name = "PmsError";
|
|
5484
|
+
}
|
|
5485
|
+
};
|
|
5486
|
+
var PMS_UNAUTHORIZED_MESSAGE = "\u767B\u5F55\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u8F93\u5165 PMS \u8D26\u53F7\u5BC6\u7801";
|
|
5487
|
+
var PMS_GENERIC_FAILURE_MESSAGE = "PMS \u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
|
|
5488
|
+
var PMS_FORBIDDEN_MESSAGE = "\u5728 HiWork \u91CC\u6CA1\u6709\u5B8C\u6210\u4EFB\u52A1\u7684\u6743\u9650\uFF0C\u8BF7\u5230 PMS \u64CD\u4F5C";
|
|
5489
|
+
var PMS_TASK_STATUS_COMPLETED = 2;
|
|
5490
|
+
var PMS_TASK_OPEN_STATUSES = [0, 1, 3];
|
|
5491
|
+
var PMS_TASK_PAGE_SIZE = 50;
|
|
5492
|
+
var PmsClient = class {
|
|
5493
|
+
baseURL;
|
|
5494
|
+
timeoutMs;
|
|
5495
|
+
now;
|
|
5496
|
+
log;
|
|
5497
|
+
constructor(options = {}) {
|
|
5498
|
+
this.baseURL = (options.baseURL ?? PMS_BASE_URL).replace(/\/+$/, "");
|
|
5499
|
+
this.timeoutMs = options.timeoutMs ?? PMS_REQUEST_TIMEOUT_MS;
|
|
5500
|
+
this.now = options.now ?? (() => Date.now());
|
|
5501
|
+
this.log = options.log;
|
|
5502
|
+
}
|
|
5503
|
+
/**
|
|
5504
|
+
* `POST /api/auth/login`:用账号密码换 JWT(access + refresh)。
|
|
5505
|
+
*
|
|
5506
|
+
* 失败语义(由 {@link PmsClient.call} 统一给出):
|
|
5507
|
+
* - 账号密码不对 → `UNAUTHORIZED`(HTTP 401 **或** 信封 `code:401`,两者同义);
|
|
5508
|
+
* - 其余(网络 / 超时 / 5xx / 非业务信封)→ `UPSTREAM_UNAVAILABLE`。
|
|
5509
|
+
* **本方法不重试**,调用方也不许替它重试(§7:避免用错密码连续触发 PMS 的失败锁定)。
|
|
5510
|
+
*/
|
|
5511
|
+
async login(username, password) {
|
|
5512
|
+
const envelope = await this.call(AUTH_LOGIN_PATH, { method: "POST", body: { username, password } });
|
|
5513
|
+
return this.tokenSet(envelope.data);
|
|
5514
|
+
}
|
|
5515
|
+
/**
|
|
5516
|
+
* `POST /api/auth/refresh`:用 refresh token 续一次 access token。
|
|
5517
|
+
*
|
|
5518
|
+
* refresh token 失效(7 天到期 / 已被 PMS 撤销)→ `UNAUTHORIZED`,此时**只能**重新输入账号密码;
|
|
5519
|
+
* 网络问题 → `UPSTREAM_UNAVAILABLE`(那是「稍后重试」,不是「要重新登录」——两者对用户是两件事)。
|
|
5520
|
+
*/
|
|
5521
|
+
async refresh(refreshToken) {
|
|
5522
|
+
const envelope = await this.call(AUTH_REFRESH_PATH, { method: "POST", body: { refreshToken } });
|
|
5523
|
+
return this.tokenSet(envelope.data);
|
|
5524
|
+
}
|
|
5525
|
+
/**
|
|
5526
|
+
* `GET /api/todo/today`:今日待办(⚠️ 口径比名字宽:**今天 + 明天 + 无截止日期**都算,spec §D4)。
|
|
5527
|
+
*
|
|
5528
|
+
* `data` 必须是数组;不是数组时按**上游不可用**处理(绝不读成「今天没有待办」——那是把中间层的
|
|
5529
|
+
* 维护页当成空清单,用户会以为自己清空了所有待办)。缺 `id` 的条目跳过:没有 id 就没法勾选,
|
|
5530
|
+
* 渲染出来只会是一个点了没反应的复选框。
|
|
5531
|
+
*/
|
|
5532
|
+
async today(accessToken) {
|
|
5533
|
+
const envelope = await this.call(TODO_TODAY_PATH, { method: "GET", accessToken });
|
|
5534
|
+
if (!Array.isArray(envelope.data)) {
|
|
5535
|
+
this.log?.("hiwork-core: PMS \u4ECA\u65E5\u5F85\u529E\u54CD\u5E94\u4E0D\u662F\u6570\u7EC4\uFF08\u53EF\u80FD\u7684\u4E2D\u95F4\u5C42\uFF09\uFF0C\u6309\u4E0A\u6E38\u4E0D\u53EF\u7528\u5904\u7406");
|
|
5536
|
+
throw new PmsError("UPSTREAM_UNAVAILABLE", PMS_GENERIC_FAILURE_MESSAGE);
|
|
5537
|
+
}
|
|
5538
|
+
const items = [];
|
|
5539
|
+
for (const raw of envelope.data) {
|
|
5540
|
+
const item = toTodoItem(raw);
|
|
5541
|
+
if (item === void 0) {
|
|
5542
|
+
this.log?.("hiwork-core: PMS \u4ECA\u65E5\u5F85\u529E\u91CC\u6709\u4E00\u6761\u7F3A id \u7684\u6761\u76EE\uFF0C\u5DF2\u8DF3\u8FC7");
|
|
5543
|
+
continue;
|
|
5544
|
+
}
|
|
5545
|
+
items.push(item);
|
|
5546
|
+
}
|
|
5547
|
+
return items;
|
|
5548
|
+
}
|
|
5549
|
+
/** `GET /api/todo/statistics`:total / pending / completed / overdue 四个计数。 */
|
|
5550
|
+
async statistics(accessToken) {
|
|
5551
|
+
const envelope = await this.call(TODO_STATISTICS_PATH, { method: "GET", accessToken });
|
|
5552
|
+
if (!isRecord3(envelope.data)) {
|
|
5553
|
+
this.log?.("hiwork-core: PMS \u5F85\u529E\u7EDF\u8BA1\u54CD\u5E94\u4E0D\u662F\u5BF9\u8C61\uFF08\u53EF\u80FD\u7684\u4E2D\u95F4\u5C42\uFF09\uFF0C\u6309\u4E0A\u6E38\u4E0D\u53EF\u7528\u5904\u7406");
|
|
5554
|
+
throw new PmsError("UPSTREAM_UNAVAILABLE", PMS_GENERIC_FAILURE_MESSAGE);
|
|
5555
|
+
}
|
|
5556
|
+
return {
|
|
5557
|
+
total: countOf(envelope.data["totalCount"]),
|
|
5558
|
+
pending: countOf(envelope.data["pendingCount"]),
|
|
5559
|
+
completed: countOf(envelope.data["completedCount"]),
|
|
5560
|
+
overdue: countOf(envelope.data["overdueCount"])
|
|
5561
|
+
};
|
|
5562
|
+
}
|
|
5563
|
+
/**
|
|
5564
|
+
* `PUT /api/todo/{id}/status?status=PENDING|COMPLETED`:勾完成 / 取消完成。
|
|
5565
|
+
*
|
|
5566
|
+
* `status` 走 **query**(PMS 侧就是这么定的),所以必须 `encodeURIComponent`——虽然本层只放行
|
|
5567
|
+
* 两个字面量、编码后与原值相同,但「拼进 URL 的值一律编码」是纪律:少一次编码在参数集合变化时
|
|
5568
|
+
* 就是一个注入面。
|
|
5569
|
+
*
|
|
5570
|
+
* ⚠️ 副作用(spec §D5):勾完成时若来源是项目任务且该任务下再无 PENDING 待办,**PMS 会把项目
|
|
5571
|
+
* 任务一并标记完成**;反向不成立。本层只负责发请求,提示文案属 UI 的职责。
|
|
5572
|
+
*
|
|
5573
|
+
* 返回值以服务端回声为准(`data.id` / `data.status`),`data` 为空(PMS 这个接口可能回 void)
|
|
5574
|
+
* 时回退成请求里的 id / status——调用方要的是「我勾的是哪条、现在是什么状态」,两者都知道。
|
|
5575
|
+
*/
|
|
5576
|
+
async setStatus(accessToken, id, status) {
|
|
5577
|
+
const path = `/api/todo/${encodeURIComponent(String(id))}/status?status=${encodeURIComponent(status)}`;
|
|
5578
|
+
const envelope = await this.call(path, { method: "PUT", accessToken });
|
|
5579
|
+
const data = envelope.data;
|
|
5580
|
+
const completedTime = isRecord3(data) ? textOf(data["completedTime"]) : void 0;
|
|
5581
|
+
return {
|
|
5582
|
+
id: isRecord3(data) && isInteger(data["id"]) ? data["id"] : id,
|
|
5583
|
+
status: isRecord3(data) && isTodoStatusValue(data["status"]) ? data["status"] : status,
|
|
5584
|
+
...completedTime === void 0 ? {} : { completedTime }
|
|
5585
|
+
};
|
|
5586
|
+
}
|
|
5587
|
+
/**
|
|
5588
|
+
* `GET /api/task/tasks?assigneeId={userId}&status=0&status=1&status=3¤t=1&size=50`
|
|
5589
|
+
* (spec §3 D7):**我的未完成项目任务**。
|
|
5590
|
+
*
|
|
5591
|
+
* 为什么必须直接查任务而不是只读待办:PMS 只在**任务创建那一刻**生成待办
|
|
5592
|
+
* (`TaskService.createFromTask`),转派、走 SQL 批量建的历史任务、以及机制上线前的任务都
|
|
5593
|
+
* 没有待办——只读 `todo_task` 会让这些任务在面板上凭空消失(§3 D7 的缺口表)。
|
|
5594
|
+
*
|
|
5595
|
+
* `status` 在 PMS 侧是 `List<Integer>`,所以是**多个同名参数**而不是逗号分隔(`status=0&status=1&status=3`);
|
|
5596
|
+
* 顺序按 {@link PMS_TASK_OPEN_STATUSES},`assigneeId` 是**当前登录人的 PMS userId**
|
|
5597
|
+
* (由调用方从凭据记录里取,见 `src/pms-todo-bridge.ts`)。
|
|
5598
|
+
*
|
|
5599
|
+
* ⚠️ 响应的 `data` 是**分页对象**(`{records, total, current, size}`)**不是数组**:
|
|
5600
|
+
* 不是对象或 `records` 不是数组时按**上游不可用**处理(绝不读成「我没有任务」——那会让用户
|
|
5601
|
+
* 以为活都干完了,而这正是本次最要避免的误读)。
|
|
5602
|
+
*
|
|
5603
|
+
* @param accessToken - Host 内存里的 PMS JWT。
|
|
5604
|
+
* @param userId - 我的 PMS userId(凭据记录里的 `userId`)。
|
|
5605
|
+
* @param opts - 见 {@link PmsMyTasksOptions}。
|
|
5606
|
+
*/
|
|
5607
|
+
async myTasks(accessToken, userId, opts = {}) {
|
|
5608
|
+
const size = isPositiveInteger(opts.size) ? opts.size : PMS_TASK_PAGE_SIZE;
|
|
5609
|
+
const query = new URLSearchParams();
|
|
5610
|
+
query.append("assigneeId", String(userId));
|
|
5611
|
+
for (const status of PMS_TASK_OPEN_STATUSES) query.append("status", String(status));
|
|
5612
|
+
query.append("current", "1");
|
|
5613
|
+
query.append("size", String(size));
|
|
5614
|
+
const path = `${TASK_LIST_PATH}?${query.toString()}`;
|
|
5615
|
+
const envelope = await this.call(path, { method: "GET", accessToken });
|
|
5616
|
+
const data = envelope.data;
|
|
5617
|
+
if (!isRecord3(data) || !Array.isArray(data["records"])) {
|
|
5618
|
+
this.log?.(`hiwork-core: PMS \u6211\u7684\u4EFB\u52A1\u54CD\u5E94\u7684 data \u4E0D\u662F\u5206\u9875\u5BF9\u8C61\uFF08\u53EF\u80FD\u7684\u4E2D\u95F4\u5C42\uFF09\uFF0C\u6309\u4E0A\u6E38\u4E0D\u53EF\u7528\u5904\u7406`);
|
|
5619
|
+
throw new PmsError("UPSTREAM_UNAVAILABLE", PMS_GENERIC_FAILURE_MESSAGE);
|
|
5620
|
+
}
|
|
5621
|
+
const items = [];
|
|
5622
|
+
for (const raw of data["records"]) {
|
|
5623
|
+
const item = toTaskItem(raw);
|
|
5624
|
+
if (item === void 0) {
|
|
5625
|
+
this.log?.("hiwork-core: PMS \u6211\u7684\u4EFB\u52A1\u91CC\u6709\u4E00\u6761\u7F3A id \u7684\u4EFB\u52A1\uFF0C\u5DF2\u8DF3\u8FC7");
|
|
5626
|
+
continue;
|
|
5627
|
+
}
|
|
5628
|
+
items.push(item);
|
|
5629
|
+
}
|
|
5630
|
+
return {
|
|
5631
|
+
items,
|
|
5632
|
+
// 分页元数据缺失时给一个**自洽**的回退:总数 = 这次拿到的条数,页码 1,页大小 = 请求值。
|
|
5633
|
+
total: countOr(data["total"], items.length),
|
|
5634
|
+
current: countOr(data["current"], 1),
|
|
5635
|
+
size: countOr(data["size"], size)
|
|
5636
|
+
};
|
|
5637
|
+
}
|
|
5638
|
+
/**
|
|
5639
|
+
* `PUT /api/task/tasks/{taskId}/status?status=2`:**完成一条项目任务**(spec §3 D7)。
|
|
5640
|
+
*
|
|
5641
|
+
* 与 {@link PmsClient.setStatus} 同款:`status` 走 **query** 且被编码;要求业务 `code === 200`,
|
|
5642
|
+
* `data` 容许为 `null`(PMS 这个接口的返回类型是 `Result<Void>`,实测就是 null),此时回退成
|
|
5643
|
+
* 请求里的 id / 状态数字——调用方要的是「我完成的是哪条、现在是什么状态」,两者都知道。
|
|
5644
|
+
*
|
|
5645
|
+
* ⚠️ 权限:这个端点带 `@PreAuthorize("hasAuthority('task:list:edit')")`,没有该权限的角色会失败
|
|
5646
|
+
* (`FORBIDDEN`,见 {@link PMS_FORBIDDEN_MESSAGE})。这与待办那条不同——**不要**把它当成「凭据
|
|
5647
|
+
* 过期」让用户重新登录。
|
|
5648
|
+
*/
|
|
5649
|
+
async completeTask(accessToken, taskId) {
|
|
5650
|
+
const path = `${TASK_LIST_PATH}/${encodeURIComponent(String(taskId))}/status?status=${encodeURIComponent(String(PMS_TASK_STATUS_COMPLETED))}`;
|
|
5651
|
+
const envelope = await this.call(path, { method: "PUT", accessToken });
|
|
5652
|
+
const data = envelope.data;
|
|
5653
|
+
return {
|
|
5654
|
+
id: isRecord3(data) && isInteger(data["id"]) ? data["id"] : taskId,
|
|
5655
|
+
status: isRecord3(data) && isInteger(data["status"]) ? data["status"] : PMS_TASK_STATUS_COMPLETED
|
|
5656
|
+
};
|
|
5657
|
+
}
|
|
5658
|
+
/**
|
|
5659
|
+
* 发一次调用并解开 `Result` 信封。**所有错误都在这里收敛**,返回值只有 `data`。
|
|
5660
|
+
*
|
|
5661
|
+
* 顺序是刻意的:**先看 HTTP 401,再解信封**——PMS 未登录时回的是 Spring Security 形状
|
|
5662
|
+
* (`{path, code:"AUTH_001", message}`),它的 `message` 是给浏览器看跳登录页的,既不是业务文案
|
|
5663
|
+
* 也可能带 `path` 等实现细节。把它当业务信封解析,轻则文案变成一句英文,重则把「凭据失效」
|
|
5664
|
+
* 误判成「上游故障」而让用户不知道要重新输入密码。
|
|
5665
|
+
*/
|
|
5666
|
+
async call(path, opts) {
|
|
5667
|
+
let response;
|
|
5668
|
+
try {
|
|
5669
|
+
const headers = {
|
|
5670
|
+
"user-agent": `hiwork-core/${PKG_VERSION}`,
|
|
5671
|
+
accept: "application/json"
|
|
5672
|
+
};
|
|
5673
|
+
if (opts.body !== void 0) headers["content-type"] = "application/json";
|
|
5674
|
+
if (opts.accessToken !== void 0) headers["authorization"] = `Bearer ${opts.accessToken}`;
|
|
5675
|
+
const init = {
|
|
5676
|
+
method: opts.method,
|
|
5677
|
+
headers,
|
|
5678
|
+
signal: AbortSignal.timeout(this.timeoutMs)
|
|
5679
|
+
};
|
|
5680
|
+
if (opts.body !== void 0) init.body = JSON.stringify(opts.body);
|
|
5681
|
+
response = await fetch(`${this.baseURL}${path}`, init);
|
|
5682
|
+
} catch {
|
|
5683
|
+
throw new PmsError("UPSTREAM_UNAVAILABLE", PMS_GENERIC_FAILURE_MESSAGE);
|
|
5684
|
+
}
|
|
5685
|
+
if (response.status === 401) {
|
|
5686
|
+
throw new PmsError("UNAUTHORIZED", PMS_UNAUTHORIZED_MESSAGE, 401);
|
|
5687
|
+
}
|
|
5688
|
+
if (response.status === 403) {
|
|
5689
|
+
this.log?.(`hiwork-core: PMS ${path} \u62D2\u7EDD\u6388\u6743\uFF08HTTP 403\uFF09`);
|
|
5690
|
+
throw new PmsError("FORBIDDEN", PMS_FORBIDDEN_MESSAGE, 403);
|
|
5691
|
+
}
|
|
5692
|
+
const payload = await readJson2(response);
|
|
5693
|
+
if (!isRecord3(payload)) {
|
|
5694
|
+
this.log?.(`hiwork-core: PMS ${path} \u8FD4\u56DE\u4E86\u975E JSON \u54CD\u5E94\uFF08HTTP ${response.status}\uFF09`);
|
|
5695
|
+
throw new PmsError("UPSTREAM_UNAVAILABLE", PMS_GENERIC_FAILURE_MESSAGE, response.status);
|
|
5696
|
+
}
|
|
5697
|
+
const code = payload["code"];
|
|
5698
|
+
if (typeof code !== "number" || !Number.isFinite(code)) {
|
|
5699
|
+
this.log?.(`hiwork-core: PMS ${path} \u54CD\u5E94\u7F3A\u4E1A\u52A1 code\uFF08\u53EF\u80FD\u7684\u4E2D\u95F4\u5C42\uFF09`);
|
|
5700
|
+
throw new PmsError("UPSTREAM_UNAVAILABLE", PMS_GENERIC_FAILURE_MESSAGE, response.status);
|
|
5701
|
+
}
|
|
5702
|
+
if (code === PMS_RESULT_UNAUTHORIZED_CODE || isAuthFailureMessage(payload["message"])) {
|
|
5703
|
+
this.log?.(`hiwork-core: PMS ${path} \u62D2\u7EDD\u51ED\u636E\uFF08code=${code}\uFF09`);
|
|
5704
|
+
throw new PmsError("UNAUTHORIZED", PMS_UNAUTHORIZED_MESSAGE, response.status);
|
|
5705
|
+
}
|
|
5706
|
+
if (code !== PMS_RESULT_SUCCESS_CODE) {
|
|
5707
|
+
if (isPermissionDeniedMessage(payload["message"])) {
|
|
5708
|
+
this.log?.(`hiwork-core: PMS ${path} \u62D2\u7EDD\u6388\u6743\uFF08code=${code}\uFF09`);
|
|
5709
|
+
throw new PmsError("FORBIDDEN", PMS_FORBIDDEN_MESSAGE, response.status);
|
|
5710
|
+
}
|
|
5711
|
+
this.log?.(`hiwork-core: PMS ${path} \u8FD4\u56DE\u4E1A\u52A1\u5931\u8D25\uFF08code=${code}\uFF09`);
|
|
5712
|
+
throw new PmsError("UPSTREAM_UNAVAILABLE", PMS_GENERIC_FAILURE_MESSAGE, response.status);
|
|
5713
|
+
}
|
|
5714
|
+
return { code, data: payload["data"] };
|
|
5715
|
+
}
|
|
5716
|
+
/** 登录 / 刷新的 `data` → {@link PmsTokenSet}。200 却不给 token 视为契约破坏(上游不可用)。 */
|
|
5717
|
+
tokenSet(data) {
|
|
5718
|
+
if (!isRecord3(data)) {
|
|
5719
|
+
this.log?.("hiwork-core: PMS \u8BA4\u8BC1\u54CD\u5E94\u7F3A data\uFF08\u5951\u7EA6\u7834\u574F\uFF09\uFF0C\u6309\u4E0A\u6E38\u4E0D\u53EF\u7528\u5904\u7406");
|
|
5720
|
+
throw new PmsError("UPSTREAM_UNAVAILABLE", PMS_GENERIC_FAILURE_MESSAGE);
|
|
5721
|
+
}
|
|
5722
|
+
const accessToken = textOf(data["token"]);
|
|
5723
|
+
if (accessToken === void 0) {
|
|
5724
|
+
this.log?.("hiwork-core: PMS \u8BA4\u8BC1\u54CD\u5E94\u7F3A token\uFF08\u5951\u7EA6\u7834\u574F\uFF09\uFF0C\u6309\u4E0A\u6E38\u4E0D\u53EF\u7528\u5904\u7406");
|
|
5725
|
+
throw new PmsError("UPSTREAM_UNAVAILABLE", PMS_GENERIC_FAILURE_MESSAGE);
|
|
5726
|
+
}
|
|
5727
|
+
const refreshToken = textOf(data["refreshToken"]);
|
|
5728
|
+
const expiresIn = data["expiresIn"];
|
|
5729
|
+
const ttlMs = typeof expiresIn === "number" && Number.isFinite(expiresIn) && expiresIn > 0 ? expiresIn * 1e3 : PMS_ACCESS_TOKEN_TTL_MS;
|
|
5730
|
+
const userId = isInteger(data["userId"]) ? data["userId"] : void 0;
|
|
5731
|
+
return {
|
|
5732
|
+
accessToken,
|
|
5733
|
+
...refreshToken === void 0 ? {} : { refreshToken },
|
|
5734
|
+
...userId === void 0 ? {} : { userId },
|
|
5735
|
+
expiresAt: this.now() + ttlMs
|
|
5736
|
+
};
|
|
5737
|
+
}
|
|
5738
|
+
};
|
|
5739
|
+
function isRecord3(value) {
|
|
5740
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5741
|
+
}
|
|
5742
|
+
function textOf(value) {
|
|
5743
|
+
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
5744
|
+
}
|
|
5745
|
+
function isInteger(value) {
|
|
5746
|
+
return typeof value === "number" && Number.isSafeInteger(value);
|
|
5747
|
+
}
|
|
5748
|
+
function isPositiveInteger(value) {
|
|
5749
|
+
return isInteger(value) && value > 0;
|
|
5750
|
+
}
|
|
5751
|
+
function isTodoStatusValue(value) {
|
|
5752
|
+
return value === "PENDING" || value === "COMPLETED";
|
|
5753
|
+
}
|
|
5754
|
+
function countOf(value) {
|
|
5755
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
5756
|
+
}
|
|
5757
|
+
function countOr(value, fallback) {
|
|
5758
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
5759
|
+
}
|
|
5760
|
+
function numberOf(value) {
|
|
5761
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
5762
|
+
}
|
|
5763
|
+
function toTodoItem(raw) {
|
|
5764
|
+
if (!isRecord3(raw)) return void 0;
|
|
5765
|
+
const id = raw["id"];
|
|
5766
|
+
if (!isInteger(id)) return void 0;
|
|
5767
|
+
const item = {
|
|
5768
|
+
id,
|
|
5769
|
+
title: textOf(raw["title"]) ?? "",
|
|
5770
|
+
status: textOf(raw["status"]) ?? ""
|
|
5771
|
+
};
|
|
5772
|
+
const description = textOf(raw["description"]);
|
|
5773
|
+
if (description !== void 0) item.description = description;
|
|
5774
|
+
const category = textOf(raw["category"]);
|
|
5775
|
+
if (category !== void 0) item.category = category;
|
|
5776
|
+
const priority = textOf(raw["priority"]);
|
|
5777
|
+
if (priority !== void 0) item.priority = priority;
|
|
5778
|
+
const startDate = textOf(raw["startDate"]);
|
|
5779
|
+
if (startDate !== void 0) item.startDate = startDate;
|
|
5780
|
+
const dueDate = textOf(raw["dueDate"]);
|
|
5781
|
+
if (dueDate !== void 0) item.dueDate = dueDate;
|
|
5782
|
+
const completedTime = textOf(raw["completedTime"]);
|
|
5783
|
+
if (completedTime !== void 0) item.completedTime = completedTime;
|
|
5784
|
+
if (typeof raw["isOverdue"] === "boolean") item.isOverdue = raw["isOverdue"];
|
|
5785
|
+
if (typeof raw["isDueSoon"] === "boolean") item.isDueSoon = raw["isDueSoon"];
|
|
5786
|
+
return item;
|
|
5787
|
+
}
|
|
5788
|
+
function toTaskItem(raw) {
|
|
5789
|
+
if (!isRecord3(raw)) return void 0;
|
|
5790
|
+
const id = raw["id"];
|
|
5791
|
+
if (!isInteger(id)) return void 0;
|
|
5792
|
+
const item = {
|
|
5793
|
+
id,
|
|
5794
|
+
taskName: textOf(raw["taskName"]) ?? "",
|
|
5795
|
+
description: textOf(raw["description"]) ?? "",
|
|
5796
|
+
projectId: numberOf(raw["projectId"]),
|
|
5797
|
+
projectName: textOf(raw["projectName"]) ?? "",
|
|
5798
|
+
assigneeId: numberOf(raw["assigneeId"]),
|
|
5799
|
+
assigneeName: textOf(raw["assigneeName"]) ?? "",
|
|
5800
|
+
deadline: textOf(raw["deadline"]) ?? "",
|
|
5801
|
+
status: numberOf(raw["status"]),
|
|
5802
|
+
statusName: textOf(raw["statusName"]) ?? "",
|
|
5803
|
+
priority: numberOf(raw["priority"]),
|
|
5804
|
+
priorityName: textOf(raw["priorityName"]) ?? "",
|
|
5805
|
+
taskType: textOf(raw["taskType"]) ?? "",
|
|
5806
|
+
taskTypeName: textOf(raw["taskTypeName"]) ?? "",
|
|
5807
|
+
auditStatus: numberOf(raw["auditStatus"]),
|
|
5808
|
+
auditStatusName: textOf(raw["auditStatusName"]) ?? ""
|
|
5809
|
+
};
|
|
5810
|
+
const projectStageName = textOf(raw["projectStageName"]);
|
|
5811
|
+
if (projectStageName !== void 0) item.projectStageName = projectStageName;
|
|
5812
|
+
const requirementName = textOf(raw["requirementName"]);
|
|
5813
|
+
if (requirementName !== void 0) item.requirementName = requirementName;
|
|
5814
|
+
const moduleName = textOf(raw["moduleName"]);
|
|
5815
|
+
if (moduleName !== void 0) item.moduleName = moduleName;
|
|
5816
|
+
const estimatedHours = raw["estimatedHours"];
|
|
5817
|
+
if (typeof estimatedHours === "number" && Number.isFinite(estimatedHours)) item.estimatedHours = estimatedHours;
|
|
5818
|
+
const actualHours = raw["actualHours"];
|
|
5819
|
+
if (typeof actualHours === "number" && Number.isFinite(actualHours)) item.actualHours = actualHours;
|
|
5820
|
+
return item;
|
|
5821
|
+
}
|
|
5822
|
+
async function readJson2(response) {
|
|
5823
|
+
try {
|
|
5824
|
+
return await response.json();
|
|
5825
|
+
} catch {
|
|
5826
|
+
return void 0;
|
|
5827
|
+
}
|
|
5828
|
+
}
|
|
5829
|
+
|
|
5830
|
+
// src/pms-todo-bridge.ts
|
|
5831
|
+
var PMS_TODO_RPC_CHANNEL = "/hiwork-pms-todo";
|
|
5832
|
+
var PMS_TODO_RPC_ENDPOINTS = [
|
|
5833
|
+
"status",
|
|
5834
|
+
"login",
|
|
5835
|
+
"logout",
|
|
5836
|
+
"today",
|
|
5837
|
+
"statistics",
|
|
5838
|
+
"setStatus",
|
|
5839
|
+
"myTasks",
|
|
5840
|
+
"completeTask"
|
|
5841
|
+
];
|
|
5842
|
+
var PMS_CREDENTIALS_REF = "HIWORK_PMS_CREDENTIALS";
|
|
5843
|
+
var PMS_TODO_ACCESS_TOKEN_TTL_MS = 10 * 60 * 60 * 1e3;
|
|
5844
|
+
var PMS_TODO_NOT_CONFIGURED_MESSAGE = "\u5C1A\u672A\u914D\u7F6E PMS \u8D26\u53F7\uFF0C\u8BF7\u5148\u8F93\u5165\u8D26\u53F7\u5BC6\u7801";
|
|
5845
|
+
var PMS_TODO_UNAUTHORIZED_MESSAGE = PMS_UNAUTHORIZED_MESSAGE;
|
|
5846
|
+
var PMS_TODO_FORBIDDEN_MESSAGE = PMS_FORBIDDEN_MESSAGE;
|
|
5847
|
+
var PMS_TODO_GENERIC_FAILURE_MESSAGE = PMS_GENERIC_FAILURE_MESSAGE;
|
|
5848
|
+
var PMS_TODO_CANCELLED_MESSAGE = "PMS \u5F85\u529E\u8BF7\u6C42\u5DF2\u53D6\u6D88\u3002";
|
|
5849
|
+
var PmsTodoRequestError = class extends Error {
|
|
5850
|
+
code = "BAD_REQUEST";
|
|
5851
|
+
constructor(message) {
|
|
5852
|
+
super(message);
|
|
5853
|
+
this.name = "PmsTodoRequestError";
|
|
5854
|
+
}
|
|
5855
|
+
};
|
|
5856
|
+
function isPmsTodoRpcEndpoint(endpoint) {
|
|
5857
|
+
return PMS_TODO_RPC_ENDPOINTS.includes(endpoint);
|
|
5858
|
+
}
|
|
5859
|
+
function asRecord(endpoint, raw) {
|
|
5860
|
+
if (raw === void 0 || raw === null) return {};
|
|
5861
|
+
if (typeof raw !== "object" || Array.isArray(raw)) {
|
|
5862
|
+
throw new PmsTodoRequestError(`PMS \u5F85\u529E\u7AEF\u70B9 '${endpoint}' \u7684\u8BF7\u6C42\u53C2\u6570\u4E0D\u5408\u6CD5\u3002`);
|
|
5863
|
+
}
|
|
5864
|
+
return raw;
|
|
5865
|
+
}
|
|
5866
|
+
function rejectUnknownKeys(endpoint, record, allowed) {
|
|
5867
|
+
for (const key of Object.keys(record)) {
|
|
5868
|
+
if (!allowed.includes(key)) {
|
|
5869
|
+
throw new PmsTodoRequestError(`PMS \u5F85\u529E\u7AEF\u70B9 '${endpoint}' \u7684\u8BF7\u6C42\u5305\u542B\u672A\u77E5\u5B57\u6BB5 '${key}'\u3002`);
|
|
5870
|
+
}
|
|
5871
|
+
}
|
|
5872
|
+
}
|
|
5873
|
+
function parsePmsTodoPayload(endpoint, raw) {
|
|
5874
|
+
const record = asRecord(endpoint, raw);
|
|
5875
|
+
if (endpoint === "status" || endpoint === "logout" || endpoint === "today" || endpoint === "statistics" || endpoint === "myTasks") {
|
|
5876
|
+
rejectUnknownKeys(endpoint, record, []);
|
|
5877
|
+
return { endpoint };
|
|
5878
|
+
}
|
|
5879
|
+
if (endpoint === "login") {
|
|
5880
|
+
rejectUnknownKeys(endpoint, record, ["username", "password", "remember"]);
|
|
5881
|
+
const username = typeof record["username"] === "string" ? record["username"].trim() : "";
|
|
5882
|
+
if (username === "") throw new PmsTodoRequestError("PMS \u767B\u5F55\u8BF7\u6C42\u7F3A\u5C11 username\u3002");
|
|
5883
|
+
const password = record["password"];
|
|
5884
|
+
if (typeof password !== "string" || password === "") {
|
|
5885
|
+
throw new PmsTodoRequestError("PMS \u767B\u5F55\u8BF7\u6C42\u7F3A\u5C11 password\u3002");
|
|
5886
|
+
}
|
|
5887
|
+
const remember = record["remember"];
|
|
5888
|
+
if (remember !== void 0 && typeof remember !== "boolean") {
|
|
5889
|
+
throw new PmsTodoRequestError("PMS \u767B\u5F55\u8BF7\u6C42\u7684 remember \u5FC5\u987B\u662F\u5E03\u5C14\u503C\u3002");
|
|
5890
|
+
}
|
|
5891
|
+
return { endpoint, username, password, remember: remember === true };
|
|
5892
|
+
}
|
|
5893
|
+
if (endpoint === "completeTask") {
|
|
5894
|
+
rejectUnknownKeys(endpoint, record, ["id"]);
|
|
5895
|
+
const id2 = record["id"];
|
|
5896
|
+
if (typeof id2 !== "number" || !Number.isSafeInteger(id2) || id2 <= 0) {
|
|
5897
|
+
throw new PmsTodoRequestError("PMS \u4EFB\u52A1 completeTask \u7684 id \u5FC5\u987B\u662F\u6B63\u6574\u6570\u3002");
|
|
5898
|
+
}
|
|
5899
|
+
return { endpoint, id: id2 };
|
|
5900
|
+
}
|
|
5901
|
+
rejectUnknownKeys(endpoint, record, ["id", "status"]);
|
|
5902
|
+
const id = record["id"];
|
|
5903
|
+
if (typeof id !== "number" || !Number.isSafeInteger(id) || id <= 0) {
|
|
5904
|
+
throw new PmsTodoRequestError("PMS \u5F85\u529E setStatus \u7684 id \u5FC5\u987B\u662F\u6B63\u6574\u6570\u3002");
|
|
5905
|
+
}
|
|
5906
|
+
const status = record["status"];
|
|
5907
|
+
if (status !== "PENDING" && status !== "COMPLETED") {
|
|
5908
|
+
throw new PmsTodoRequestError("PMS \u5F85\u529E setStatus \u7684 status \u53EA\u80FD\u662F 'PENDING' \u6216 'COMPLETED'\u3002");
|
|
5909
|
+
}
|
|
5910
|
+
return { endpoint, id, status };
|
|
5911
|
+
}
|
|
5912
|
+
function pmsTodoErrorOf(error) {
|
|
5913
|
+
if (error instanceof PmsTodoRequestError) return { code: error.code, message: error.message, details: {} };
|
|
5914
|
+
if (error instanceof PmsTodoError) {
|
|
5915
|
+
return { code: error.code, message: error.message, details: {} };
|
|
5916
|
+
}
|
|
5917
|
+
if (error instanceof PmsError) {
|
|
5918
|
+
if (error.code === "UNAUTHORIZED") {
|
|
5919
|
+
return { code: "UNAUTHORIZED", message: PMS_TODO_UNAUTHORIZED_MESSAGE, details: {} };
|
|
5920
|
+
}
|
|
5921
|
+
if (error.code === "FORBIDDEN") {
|
|
5922
|
+
return { code: "FORBIDDEN", message: PMS_TODO_FORBIDDEN_MESSAGE, details: {} };
|
|
5923
|
+
}
|
|
5924
|
+
return { code: "UPSTREAM_UNAVAILABLE", message: displayPmsTodoMessage(error.code, error.message), details: {} };
|
|
5925
|
+
}
|
|
5926
|
+
return { code: "INTERNAL", message: PMS_TODO_GENERIC_FAILURE_MESSAGE, details: {} };
|
|
5927
|
+
}
|
|
5928
|
+
var PMS_TODO_MESSAGE_FALLBACK = {
|
|
5929
|
+
UNAUTHORIZED: PMS_TODO_UNAUTHORIZED_MESSAGE,
|
|
5930
|
+
FORBIDDEN: PMS_TODO_FORBIDDEN_MESSAGE,
|
|
5931
|
+
UPSTREAM_UNAVAILABLE: PMS_TODO_GENERIC_FAILURE_MESSAGE
|
|
5932
|
+
};
|
|
5933
|
+
function displayPmsTodoMessage(code, message) {
|
|
5934
|
+
const text = typeof message === "string" ? message.trim() : "";
|
|
5935
|
+
if (text === "") return PMS_TODO_MESSAGE_FALLBACK[code] ?? PMS_TODO_GENERIC_FAILURE_MESSAGE;
|
|
5936
|
+
const leaksImplementationDetail = /https?:\/\//i.test(text) || /\bHTTP\s*\d{3}\b/i.test(text) || /\bbaseURL\b/i.test(text);
|
|
5937
|
+
if (leaksImplementationDetail) return PMS_TODO_MESSAGE_FALLBACK[code] ?? PMS_TODO_GENERIC_FAILURE_MESSAGE;
|
|
5938
|
+
return text;
|
|
5939
|
+
}
|
|
5940
|
+
var PmsTodoError = class extends Error {
|
|
5941
|
+
constructor(code, message) {
|
|
5942
|
+
super(message);
|
|
5943
|
+
this.code = code;
|
|
5944
|
+
this.name = "PmsTodoError";
|
|
5945
|
+
}
|
|
5946
|
+
};
|
|
5947
|
+
function errorName(error) {
|
|
5948
|
+
return error instanceof Error ? error.name : typeof error;
|
|
5949
|
+
}
|
|
5950
|
+
function parseCredentialsRecord(raw) {
|
|
5951
|
+
let parsed;
|
|
5952
|
+
try {
|
|
5953
|
+
parsed = JSON.parse(raw);
|
|
5954
|
+
} catch {
|
|
5955
|
+
return void 0;
|
|
5956
|
+
}
|
|
5957
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return void 0;
|
|
5958
|
+
const record = parsed;
|
|
5959
|
+
const username = typeof record["username"] === "string" ? record["username"].trim() : "";
|
|
5960
|
+
if (username === "") return void 0;
|
|
5961
|
+
const password = typeof record["password"] === "string" && record["password"] !== "" ? record["password"] : void 0;
|
|
5962
|
+
const refreshToken = typeof record["refreshToken"] === "string" && record["refreshToken"] !== "" ? record["refreshToken"] : void 0;
|
|
5963
|
+
const expiresAt = typeof record["expiresAt"] === "number" && Number.isFinite(record["expiresAt"]) ? record["expiresAt"] : void 0;
|
|
5964
|
+
const userId = typeof record["userId"] === "number" && Number.isSafeInteger(record["userId"]) ? record["userId"] : void 0;
|
|
5965
|
+
return {
|
|
5966
|
+
username,
|
|
5967
|
+
...password === void 0 ? {} : { password },
|
|
5968
|
+
...refreshToken === void 0 ? {} : { refreshToken },
|
|
5969
|
+
...expiresAt === void 0 ? {} : { expiresAt },
|
|
5970
|
+
...userId === void 0 ? {} : { userId }
|
|
5971
|
+
};
|
|
5972
|
+
}
|
|
5973
|
+
function serializeCredentialsRecord(record) {
|
|
5974
|
+
return JSON.stringify({
|
|
5975
|
+
username: record.username,
|
|
5976
|
+
...record.password === void 0 ? {} : { password: record.password },
|
|
5977
|
+
...record.refreshToken === void 0 ? {} : { refreshToken: record.refreshToken },
|
|
5978
|
+
...record.expiresAt === void 0 ? {} : { expiresAt: record.expiresAt },
|
|
5979
|
+
...record.userId === void 0 ? {} : { userId: record.userId }
|
|
5980
|
+
});
|
|
5981
|
+
}
|
|
5982
|
+
var PmsTodoStore = class {
|
|
5983
|
+
constructor(deps) {
|
|
5984
|
+
this.deps = deps;
|
|
5985
|
+
}
|
|
5986
|
+
/** 内存里的凭据记录(不 remember 时它是这份凭据唯一的家)。 */
|
|
5987
|
+
record;
|
|
5988
|
+
/** 是否已经从 credentials 服务读过一次(避免每次调用都读盘)。 */
|
|
5989
|
+
loaded = false;
|
|
5990
|
+
/**
|
|
5991
|
+
* 当前会话是否**属于磁盘上的那条记录**(remember=true 的登录、或启动时从记录里恢复)。
|
|
5992
|
+
* 只有它为真时,refresh 换来的新 token 才回写记录——否则一次「不记住」的登录会顺手把
|
|
5993
|
+
* refresh token 写给下一个人(登出后磁盘上仍有可用凭据,这正是不 remember 的相反面)。
|
|
5994
|
+
*/
|
|
5995
|
+
persisted = false;
|
|
5996
|
+
/** access token(**只在内存**)。 */
|
|
5997
|
+
access;
|
|
5998
|
+
/** 世代号:`login` / `logout` 各 +1,用来作废它之前发起的在飞请求(换账号的语义同工作台)。 */
|
|
5999
|
+
generation = 0;
|
|
6000
|
+
/** 在飞请求**连同世代与端点**记账:同一世代 + 同一端点才允许合并。 */
|
|
6001
|
+
inFlight;
|
|
6002
|
+
now() {
|
|
6003
|
+
return this.deps.now?.() ?? Date.now();
|
|
6004
|
+
}
|
|
6005
|
+
/** 当前状态(同步语义的取数,异步实现:可能读一次 credentials)。**永不抛**。 */
|
|
6006
|
+
async status() {
|
|
6007
|
+
const record = await this.currentRecord();
|
|
6008
|
+
return this.statusOf(record);
|
|
6009
|
+
}
|
|
6010
|
+
/**
|
|
6011
|
+
* 用账号密码登录并**换取** JWT。
|
|
6012
|
+
*
|
|
6013
|
+
* ⚠️ **绝不自动重试**(spec §7 的硬要求):PMS 对连续失败登录有锁定策略,而「自动重试」正是
|
|
6014
|
+
* 最容易不小心连打两次的地方——用户手一抖打错一位密码,第二次尝试就会把账号推向锁定。
|
|
6015
|
+
* 所以本方法**只发一次**请求,失败即返回错误(UI 引导用户自己再输一次)。
|
|
6016
|
+
*
|
|
6017
|
+
* `remember` 为真才落盘(§4.4)。落盘写的是**完整**记录(含密码),因为下次启动时要靠它
|
|
6018
|
+
* 继续换 token——这不是「记住密码」的 UI 便利,而是 v1 认证方案本身(§3 D2)。
|
|
6019
|
+
*/
|
|
6020
|
+
async login(input) {
|
|
6021
|
+
const tokens = await this.deps.pms.login(input.username, input.password);
|
|
6022
|
+
const record = {
|
|
6023
|
+
username: input.username,
|
|
6024
|
+
password: input.password,
|
|
6025
|
+
...tokens.refreshToken === void 0 ? {} : { refreshToken: tokens.refreshToken },
|
|
6026
|
+
expiresAt: tokens.expiresAt,
|
|
6027
|
+
// §3 D7:登录响应里的 `userId` 必须**在这里**捕获——「我的任务」要用它当 `assigneeId`,
|
|
6028
|
+
// 而除了登录/刷新没有别的地方能拿到它(不猜、不按用户名反查)。
|
|
6029
|
+
...tokens.userId === void 0 ? {} : { userId: tokens.userId }
|
|
6030
|
+
};
|
|
6031
|
+
this.generation += 1;
|
|
6032
|
+
this.record = record;
|
|
6033
|
+
this.loaded = true;
|
|
6034
|
+
this.access = { token: tokens.accessToken, expiresAt: tokens.expiresAt };
|
|
6035
|
+
this.persisted = input.remember;
|
|
6036
|
+
if (input.remember) {
|
|
6037
|
+
await this.persist(record);
|
|
6038
|
+
} else {
|
|
6039
|
+
await this.forgetStored();
|
|
6040
|
+
}
|
|
6041
|
+
this.deps.log?.("hiwork-core: PMS \u8D26\u53F7\u5DF2\u767B\u5F55\uFF08\u51ED\u636E\u53EA\u5B58\u4E8E Host\uFF09");
|
|
6042
|
+
return this.statusOf(record);
|
|
6043
|
+
}
|
|
6044
|
+
/**
|
|
6045
|
+
* 登出:清内存 + 清凭据记录(spec §4.1 的 `logout`)。
|
|
6046
|
+
*
|
|
6047
|
+
* 不清 PMS 侧的服务端会话:PMS 的 JWT 是无状态签发的(没有「撤销单个 token」的接口),
|
|
6048
|
+
* 我们能做且该做的是**不再持有**它——删掉本地凭据后,下一次取数一定要求重新输入。
|
|
6049
|
+
*/
|
|
6050
|
+
async logout() {
|
|
6051
|
+
this.generation += 1;
|
|
6052
|
+
this.record = void 0;
|
|
6053
|
+
this.loaded = true;
|
|
6054
|
+
this.persisted = false;
|
|
6055
|
+
this.access = void 0;
|
|
6056
|
+
await this.forgetStored();
|
|
6057
|
+
this.deps.log?.("hiwork-core: PMS \u51ED\u636E\u5DF2\u6E05\u9664");
|
|
6058
|
+
return this.statusOf(void 0);
|
|
6059
|
+
}
|
|
6060
|
+
/** `GET /api/todo/today` + 本次拉取时刻。同一时刻的并发调用合并成一次 PMS 请求。 */
|
|
6061
|
+
async today() {
|
|
6062
|
+
return await this.dedupe("today", async () => {
|
|
6063
|
+
const token = await this.authorize();
|
|
6064
|
+
const items = await this.deps.pms.today(token);
|
|
6065
|
+
return { items, fetchedAt: new Date(this.now()).toISOString() };
|
|
6066
|
+
});
|
|
6067
|
+
}
|
|
6068
|
+
/** `GET /api/todo/statistics`。并发调用同样合并。 */
|
|
6069
|
+
async statistics() {
|
|
6070
|
+
return await this.dedupe("statistics", async () => {
|
|
6071
|
+
const token = await this.authorize();
|
|
6072
|
+
return await this.deps.pms.statistics(token);
|
|
6073
|
+
});
|
|
6074
|
+
}
|
|
6075
|
+
/**
|
|
6076
|
+
* `PUT /api/todo/{id}/status`。
|
|
6077
|
+
*
|
|
6078
|
+
* **不合并**并发调用:这是命令(用户点了两次就是两次动作),合并会让第二次点击被悄悄吞掉。
|
|
6079
|
+
* 顺序也不由本层控制(谁先到谁先发),UI 侧要的是「失败就回滚那一条」,不是「服务端按我提交
|
|
6080
|
+
* 的顺序处理」——那个语义 PMS 也不提供。
|
|
6081
|
+
*/
|
|
6082
|
+
async setStatus(input) {
|
|
6083
|
+
const token = await this.authorize();
|
|
6084
|
+
return await this.deps.pms.setStatus(token, input.id, input.status);
|
|
6085
|
+
}
|
|
6086
|
+
/**
|
|
6087
|
+
* `GET /api/task/tasks`:**我的未完成项目任务**(spec §3 D7)。
|
|
6088
|
+
*
|
|
6089
|
+
* `userId` 只从凭据记录里取(客户端传不进来,见 `parsePmsTodoPayload` 的注释)——这是
|
|
6090
|
+
* 「只能查自己」唯一可靠的落点:端点白名单里也没有任何「按 id 查别人」的形状。
|
|
6091
|
+
*
|
|
6092
|
+
* 记录里**没有** `userId`(2026-09-18 之前登录的旧记录)时收敛成 `UNAUTHORIZED`:用户重新登录
|
|
6093
|
+
* 一次即可拿到它。这里刻意**不**回退成「按用户名去查用户列表」之类的动作——那需要额外的接口与
|
|
6094
|
+
* 权限,而且会在 Host 里凭空多一条拿别人数据的路径。
|
|
6095
|
+
*
|
|
6096
|
+
* 并发调用合并(同 `today`):这是**取数**,同一时刻问两遍没有意义。
|
|
6097
|
+
*/
|
|
6098
|
+
async myTasks(opts = {}) {
|
|
6099
|
+
return await this.dedupe("myTasks", async () => {
|
|
6100
|
+
const token = await this.authorize();
|
|
6101
|
+
const userId = (await this.currentRecord())?.userId;
|
|
6102
|
+
if (userId === void 0) {
|
|
6103
|
+
this.deps.log?.("hiwork-core: PMS \u51ED\u636E\u8BB0\u5F55\u91CC\u6CA1\u6709 userId\uFF0C\u6211\u7684\u4EFB\u52A1\u9700\u8981\u91CD\u65B0\u767B\u5F55\u4E00\u6B21");
|
|
6104
|
+
throw new PmsTodoError("UNAUTHORIZED", PMS_TODO_UNAUTHORIZED_MESSAGE);
|
|
6105
|
+
}
|
|
6106
|
+
return await this.deps.pms.myTasks(token, userId, opts);
|
|
6107
|
+
});
|
|
6108
|
+
}
|
|
6109
|
+
/**
|
|
6110
|
+
* `PUT /api/task/tasks/{id}/status?status=2`:完成一条项目任务。
|
|
6111
|
+
*
|
|
6112
|
+
* **不合并**并发调用(与 {@link PmsTodoStore.setStatus} 同理):这是命令,用户点了两次就是两次
|
|
6113
|
+
* 动作,合并会让第二次点击被悄悄吞掉。
|
|
6114
|
+
*
|
|
6115
|
+
* ⚠️ 权限:没有 `task:list:edit` 的角色会拿到 `FORBIDDEN`(`PmsError` → RPC 面的同名码),
|
|
6116
|
+
* UI 按 §3 D7 提示「请到 PMS 操作」——**不是**重新登录。
|
|
6117
|
+
*/
|
|
6118
|
+
async completeTask(input) {
|
|
6119
|
+
const token = await this.authorize();
|
|
6120
|
+
return await this.deps.pms.completeTask(token, input.id);
|
|
6121
|
+
}
|
|
6122
|
+
/**
|
|
6123
|
+
* 取一个能用的 access token(凭证获取的唯一入口,spec §3 D3 说 v2 只替换这里)。
|
|
6124
|
+
*
|
|
6125
|
+
* 顺序是刻意的:
|
|
6126
|
+
* 1. 内存里有未过期的 access token → 直接用(零网络请求);
|
|
6127
|
+
* 2. 过期了 → 先试 `refresh(refreshToken)`(§D2:refresh 有效就完全不必打扰用户);
|
|
6128
|
+
* refresh 成功会**顺手**把新的 access / refresh token 写回内存(原来是持久化的会话则一并落盘);
|
|
6129
|
+
* 3. refresh 失败(401 / 记录里根本没有 refresh token)→ `UNAUTHORIZED`,UI 要求重新输入。
|
|
6130
|
+
*
|
|
6131
|
+
* ⚠️ 这里**没有**「拿存下来的密码重新登录」这一支,而且是刻意的:§7 要求凭据失效时
|
|
6132
|
+
* **不自动重试**——用存下来的密码自动重登 = 每次 token 过期都自动替用户登一次,密码一旦改成
|
|
6133
|
+
* 别的,就会变成一条稳定的「定时用错密码打 PMS」的流水,直到账号被锁。密码只在用户**明确**
|
|
6134
|
+
* 提交一次 `login` 时才用(那时它也确实是用户刚输入的)。
|
|
6135
|
+
*/
|
|
6136
|
+
async authorize() {
|
|
6137
|
+
const access = this.access;
|
|
6138
|
+
if (access !== void 0 && this.now() < access.expiresAt) return access.token;
|
|
6139
|
+
const record = await this.currentRecord();
|
|
6140
|
+
if (record === void 0) {
|
|
6141
|
+
throw new PmsTodoError("NOT_CONFIGURED", PMS_TODO_NOT_CONFIGURED_MESSAGE);
|
|
6142
|
+
}
|
|
6143
|
+
const refreshToken = record.refreshToken;
|
|
6144
|
+
if (refreshToken === void 0) {
|
|
6145
|
+
throw new PmsTodoError("UNAUTHORIZED", PMS_TODO_UNAUTHORIZED_MESSAGE);
|
|
6146
|
+
}
|
|
6147
|
+
const tokens = await this.deps.pms.refresh(refreshToken);
|
|
6148
|
+
const next = {
|
|
6149
|
+
...record,
|
|
6150
|
+
...tokens.refreshToken === void 0 ? {} : { refreshToken: tokens.refreshToken },
|
|
6151
|
+
expiresAt: tokens.expiresAt
|
|
6152
|
+
};
|
|
6153
|
+
this.record = next;
|
|
6154
|
+
this.access = { token: tokens.accessToken, expiresAt: tokens.expiresAt };
|
|
6155
|
+
if (this.persisted) await this.persist(next);
|
|
6156
|
+
return tokens.accessToken;
|
|
6157
|
+
}
|
|
6158
|
+
/**
|
|
6159
|
+
* 状态投影(不读盘、不发网络)。
|
|
6160
|
+
*
|
|
6161
|
+
* `expiresAt` **只报还没过期的那个时刻**:记录里那份可能是上一次会话留下的(进程重启后内存里
|
|
6162
|
+
* 没有 access token,只有磁盘上的旧 `expiresAt`),把它照实报出去,界面就会显示一个已经过去的
|
|
6163
|
+
* 「有效期至」,看着像还能用。过期了就不写这个键——refresh 会顺手把它换成新的。
|
|
6164
|
+
*/
|
|
6165
|
+
statusOf(record) {
|
|
6166
|
+
const now = this.now();
|
|
6167
|
+
const access = this.access;
|
|
6168
|
+
const hasAccess = access !== void 0 && now < access.expiresAt;
|
|
6169
|
+
const hasRefresh = record?.refreshToken !== void 0 && record.refreshToken !== "";
|
|
6170
|
+
const expiresAt = access?.expiresAt ?? record?.expiresAt;
|
|
6171
|
+
const freshExpiry = expiresAt !== void 0 && expiresAt > now ? expiresAt : void 0;
|
|
6172
|
+
return {
|
|
6173
|
+
configured: record !== void 0,
|
|
6174
|
+
loggedIn: hasAccess || hasRefresh,
|
|
6175
|
+
...record === void 0 ? {} : { username: record.username },
|
|
6176
|
+
...freshExpiry === void 0 ? {} : { expiresAt: new Date(freshExpiry).toISOString() }
|
|
6177
|
+
};
|
|
6178
|
+
}
|
|
6179
|
+
/**
|
|
6180
|
+
* 当前凭据记录:内存优先,首次调用时从 credentials 服务读一次。
|
|
6181
|
+
*
|
|
6182
|
+
* 「读一次就记住 `loaded`」是有意的:凭据的写入方只有本类(`login` / `logout` / refresh 回写),
|
|
6183
|
+
* 因此在同一进程里,读盘只需要一次。跨进程变更(别的进程写了同一个 ref)本就不可观测,
|
|
6184
|
+
* 硬要每次重新读只会让「不 remember 的登录」被磁盘上的旧记录当场覆盖。
|
|
6185
|
+
*/
|
|
6186
|
+
async currentRecord() {
|
|
6187
|
+
if (this.loaded) return this.record;
|
|
6188
|
+
this.loaded = true;
|
|
6189
|
+
try {
|
|
6190
|
+
const hit = await this.deps.credentials.resolve(PMS_CREDENTIALS_REF);
|
|
6191
|
+
if (hit === void 0 || hit.value === "") return void 0;
|
|
6192
|
+
const record = parseCredentialsRecord(hit.value);
|
|
6193
|
+
this.record = record;
|
|
6194
|
+
this.persisted = record !== void 0;
|
|
6195
|
+
return record;
|
|
6196
|
+
} catch (error) {
|
|
6197
|
+
this.deps.log?.(`hiwork-core: PMS \u51ED\u636E\u8BFB\u53D6\u5931\u8D25\uFF08${errorName(error)}\uFF09`);
|
|
6198
|
+
return void 0;
|
|
6199
|
+
}
|
|
6200
|
+
}
|
|
6201
|
+
/**
|
|
6202
|
+
* 落盘(**唯一**写 credentials 的地方,只在 remember 为真时被调用)。
|
|
6203
|
+
*
|
|
6204
|
+
* 失败**不**把一次成功的登录判为失败:会话已经拿到了,内存里照常可用;密码存不下的后果只是
|
|
6205
|
+
* 「重启后要重新输入」。反过来把登录判为失败会让用户以为密码错了。
|
|
6206
|
+
*/
|
|
6207
|
+
async persist(record) {
|
|
6208
|
+
try {
|
|
6209
|
+
await this.deps.credentials.set(PMS_CREDENTIALS_REF, serializeCredentialsRecord(record));
|
|
6210
|
+
this.persisted = true;
|
|
6211
|
+
} catch (error) {
|
|
6212
|
+
this.deps.log?.(`hiwork-core: PMS \u51ED\u636E\u843D\u76D8\u5931\u8D25\uFF08${errorName(error)}\uFF09`);
|
|
6213
|
+
}
|
|
6214
|
+
}
|
|
6215
|
+
/**
|
|
6216
|
+
* 抹掉磁盘上的记录(logout / 不记住的登录)。
|
|
6217
|
+
*
|
|
6218
|
+
* 先 `resolve` 一次再 `unset`:没有记录时一次写都不做(测试与磁盘都清清爽爽),有记录时
|
|
6219
|
+
* **必须**清掉——否则一条旧的 account+refresh token 会在下次启动时被当成「这个人配置过凭据」,
|
|
6220
|
+
* 用户明明刚选了「不记住」却在重启后仍是登录态。
|
|
6221
|
+
*/
|
|
6222
|
+
async forgetStored() {
|
|
6223
|
+
try {
|
|
6224
|
+
const hit = await this.deps.credentials.resolve(PMS_CREDENTIALS_REF);
|
|
6225
|
+
if (hit === void 0 || hit.value === "") return;
|
|
6226
|
+
await this.deps.credentials.unset(PMS_CREDENTIALS_REF);
|
|
6227
|
+
} catch (error) {
|
|
6228
|
+
this.deps.log?.(`hiwork-core: PMS \u51ED\u636E\u6E05\u9664\u5931\u8D25\uFF08${errorName(error)}\uFF09`);
|
|
6229
|
+
}
|
|
6230
|
+
}
|
|
6231
|
+
/**
|
|
6232
|
+
* 并发合并:同一世代 + 同一端点的在飞请求共用一次调用与同一个返回值。
|
|
6233
|
+
*
|
|
6234
|
+
* 世代判据与 `src/client/managed-workbench.ts` 的 `dedupe` 同形:`login` / `logout` 之后
|
|
6235
|
+
* 账上那条属于**上一个人**,谁都不许复用(复用等于把上一个人的待办显示给新账号)。
|
|
6236
|
+
*/
|
|
6237
|
+
async dedupe(key, task) {
|
|
6238
|
+
const inFlight = this.inFlight;
|
|
6239
|
+
if (inFlight !== void 0 && inFlight.generation === this.generation && inFlight.key === key) {
|
|
6240
|
+
return await inFlight.task;
|
|
6241
|
+
}
|
|
6242
|
+
const generation = this.generation;
|
|
6243
|
+
const promise = task();
|
|
6244
|
+
this.inFlight = { generation, key, task: promise };
|
|
6245
|
+
try {
|
|
6246
|
+
return await promise;
|
|
6247
|
+
} finally {
|
|
6248
|
+
if (this.inFlight?.task === promise) this.inFlight = void 0;
|
|
6249
|
+
}
|
|
6250
|
+
}
|
|
6251
|
+
};
|
|
6252
|
+
async function handlePmsTodoEndpoint(endpoint, rawPayload, deps, store, signal) {
|
|
6253
|
+
try {
|
|
6254
|
+
if (signal?.aborted === true) return failure7("BAD_REQUEST", PMS_TODO_CANCELLED_MESSAGE);
|
|
6255
|
+
if (!isPmsTodoRpcEndpoint(endpoint)) {
|
|
6256
|
+
throw new PmsTodoRequestError(`\u672A\u77E5\u7684 PMS \u5F85\u529E\u7AEF\u70B9 '${endpoint}'`);
|
|
6257
|
+
}
|
|
6258
|
+
const payload = parsePmsTodoPayload(endpoint, rawPayload);
|
|
6259
|
+
switch (payload.endpoint) {
|
|
6260
|
+
case "status":
|
|
6261
|
+
return { ok: true, value: await store.status() };
|
|
6262
|
+
case "login":
|
|
6263
|
+
return { ok: true, value: await store.login({ username: payload.username, password: payload.password, remember: payload.remember }) };
|
|
6264
|
+
case "logout":
|
|
6265
|
+
return { ok: true, value: await store.logout() };
|
|
6266
|
+
case "today":
|
|
6267
|
+
return { ok: true, value: await store.today() };
|
|
6268
|
+
case "statistics":
|
|
6269
|
+
return { ok: true, value: await store.statistics() };
|
|
6270
|
+
case "setStatus":
|
|
6271
|
+
return { ok: true, value: await store.setStatus({ id: payload.id, status: payload.status }) };
|
|
6272
|
+
case "myTasks":
|
|
6273
|
+
return { ok: true, value: await store.myTasks() };
|
|
6274
|
+
case "completeTask":
|
|
6275
|
+
return { ok: true, value: await store.completeTask({ id: payload.id }) };
|
|
6276
|
+
}
|
|
6277
|
+
} catch (error) {
|
|
6278
|
+
deps.log?.(`hiwork-core: PMS \u5F85\u529E RPC '${endpoint}' \u5931\u8D25\uFF08${errorName(error)}\uFF09`);
|
|
6279
|
+
return { ok: false, error: pmsTodoErrorOf(error) };
|
|
6280
|
+
}
|
|
6281
|
+
}
|
|
6282
|
+
function failure7(code, message) {
|
|
6283
|
+
return { ok: false, error: { code, message, details: {} } };
|
|
6284
|
+
}
|
|
6285
|
+
function registerPmsTodoRpc(ctx, deps, store) {
|
|
6286
|
+
const handler = (endpoint, payload, signal) => handlePmsTodoEndpoint(endpoint, payload, deps, store, signal);
|
|
6287
|
+
const remove = ctx.connection.rpc.handle(PMS_TODO_RPC_CHANNEL, handler, { authority: "loopback" });
|
|
6288
|
+
return async () => {
|
|
6289
|
+
await remove();
|
|
6290
|
+
};
|
|
6291
|
+
}
|
|
6292
|
+
|
|
5449
6293
|
// src/plugin-updates-rpc.ts
|
|
5450
6294
|
var PLUGIN_UPDATES_RPC_CHANNEL = "/hiwork-plugin-updates";
|
|
5451
6295
|
var PLUGIN_UPDATES_RPC_ENDPOINTS = ["snapshot", "apply"];
|
|
@@ -5460,7 +6304,7 @@ var PLUGIN_UPDATES_GENERIC_FAILURE_MESSAGE = "\u63D2\u4EF6\u66F4\u65B0\u72B6\u60
|
|
|
5460
6304
|
function isPluginUpdatesRpcEndpoint(endpoint) {
|
|
5461
6305
|
return PLUGIN_UPDATES_RPC_ENDPOINTS.includes(endpoint);
|
|
5462
6306
|
}
|
|
5463
|
-
function
|
|
6307
|
+
function isRecord4(value) {
|
|
5464
6308
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5465
6309
|
}
|
|
5466
6310
|
function textOrUndefined4(value) {
|
|
@@ -5482,7 +6326,7 @@ function parsePluginUpdatesPayload(endpoint, raw) {
|
|
|
5482
6326
|
return {};
|
|
5483
6327
|
}
|
|
5484
6328
|
function toPackageUpdate(raw) {
|
|
5485
|
-
if (!
|
|
6329
|
+
if (!isRecord4(raw)) return void 0;
|
|
5486
6330
|
const packageName = textOrUndefined4(raw["packageName"]);
|
|
5487
6331
|
if (packageName === void 0) return void 0;
|
|
5488
6332
|
return {
|
|
@@ -5493,7 +6337,7 @@ function toPackageUpdate(raw) {
|
|
|
5493
6337
|
};
|
|
5494
6338
|
}
|
|
5495
6339
|
function toSnapshot(raw) {
|
|
5496
|
-
if (!
|
|
6340
|
+
if (!isRecord4(raw)) return void 0;
|
|
5497
6341
|
const packages = [];
|
|
5498
6342
|
for (const item of Array.isArray(raw["packages"]) ? raw["packages"] : []) {
|
|
5499
6343
|
const normalized = toPackageUpdate(item);
|
|
@@ -5509,7 +6353,7 @@ function toSnapshot(raw) {
|
|
|
5509
6353
|
}
|
|
5510
6354
|
async function handlePluginUpdatesEndpoint(endpoint, rawPayload, deps, signal) {
|
|
5511
6355
|
try {
|
|
5512
|
-
if (isAborted7(signal)) return
|
|
6356
|
+
if (isAborted7(signal)) return failure8("cancelled", "\u63D2\u4EF6\u66F4\u65B0\u8BF7\u6C42\u5DF2\u53D6\u6D88\u3002");
|
|
5513
6357
|
if (!isPluginUpdatesRpcEndpoint(endpoint)) {
|
|
5514
6358
|
throw new PluginUpdatesRequestError("bad-request", `\u672A\u77E5\u7684\u63D2\u4EF6\u66F4\u65B0\u7AEF\u70B9 '${endpoint}'`);
|
|
5515
6359
|
}
|
|
@@ -5525,12 +6369,12 @@ async function handlePluginUpdatesEndpoint(endpoint, rawPayload, deps, signal) {
|
|
|
5525
6369
|
await plugins.applyNow();
|
|
5526
6370
|
return { ok: true, value: { requested: true } };
|
|
5527
6371
|
} catch (error) {
|
|
5528
|
-
if (error instanceof PluginUpdatesRequestError) return
|
|
6372
|
+
if (error instanceof PluginUpdatesRequestError) return failure8(error.code, error.message);
|
|
5529
6373
|
deps.log?.(`hiwork-core: \u63D2\u4EF6\u66F4\u65B0 RPC '${endpoint}' \u5931\u8D25\uFF08${error instanceof Error ? error.name : typeof error}\uFF09`);
|
|
5530
|
-
return
|
|
6374
|
+
return failure8("internal", PLUGIN_UPDATES_GENERIC_FAILURE_MESSAGE);
|
|
5531
6375
|
}
|
|
5532
6376
|
}
|
|
5533
|
-
function
|
|
6377
|
+
function failure8(code, message) {
|
|
5534
6378
|
return { ok: false, error: { code, message, details: {} } };
|
|
5535
6379
|
}
|
|
5536
6380
|
function registerPluginUpdatesRpc(ctx, deps) {
|
|
@@ -5820,7 +6664,7 @@ function parseDeviceInfo(message) {
|
|
|
5820
6664
|
...version === void 0 || version === "" ? {} : { appVersion: version }
|
|
5821
6665
|
};
|
|
5822
6666
|
}
|
|
5823
|
-
function
|
|
6667
|
+
function errorName2(error) {
|
|
5824
6668
|
return error instanceof Error ? error.name : typeof error;
|
|
5825
6669
|
}
|
|
5826
6670
|
function registerIpcMessageHandler(ports) {
|
|
@@ -5847,12 +6691,12 @@ function registerIpcMessageHandler(ports) {
|
|
|
5847
6691
|
authInFlight = true;
|
|
5848
6692
|
try {
|
|
5849
6693
|
void Promise.resolve(ports.complete(input)).catch((error) => {
|
|
5850
|
-
log(`hiwork-core: \u6D4F\u89C8\u5668\u6388\u6743\u56DE\u8C03\u5904\u7406\u5F02\u5E38\uFF08${
|
|
6694
|
+
log(`hiwork-core: \u6D4F\u89C8\u5668\u6388\u6743\u56DE\u8C03\u5904\u7406\u5F02\u5E38\uFF08${errorName2(error)}\uFF09`);
|
|
5851
6695
|
}).finally(() => {
|
|
5852
6696
|
authInFlight = false;
|
|
5853
6697
|
});
|
|
5854
6698
|
} catch (error) {
|
|
5855
|
-
log(`hiwork-core: \u6D4F\u89C8\u5668\u6388\u6743\u56DE\u8C03\u5206\u53D1\u5931\u8D25\uFF08${
|
|
6699
|
+
log(`hiwork-core: \u6D4F\u89C8\u5668\u6388\u6743\u56DE\u8C03\u5206\u53D1\u5931\u8D25\uFF08${errorName2(error)}\uFF09`);
|
|
5856
6700
|
authInFlight = false;
|
|
5857
6701
|
}
|
|
5858
6702
|
return;
|
|
@@ -5900,12 +6744,12 @@ async function projectLoginMode(opts) {
|
|
|
5900
6744
|
}
|
|
5901
6745
|
}
|
|
5902
6746
|
} catch (error) {
|
|
5903
|
-
opts.log?.(`hiwork-core: \u5BA2\u6237\u7AEF\u7B56\u7565\u62C9\u53D6\u5931\u8D25\uFF0C\u767B\u5F55\u65B9\u5F0F\u56DE\u9000 both\uFF08${
|
|
6747
|
+
opts.log?.(`hiwork-core: \u5BA2\u6237\u7AEF\u7B56\u7565\u62C9\u53D6\u5931\u8D25\uFF0C\u767B\u5F55\u65B9\u5F0F\u56DE\u9000 both\uFF08${errorName2(error)}\uFF09`);
|
|
5904
6748
|
}
|
|
5905
6749
|
try {
|
|
5906
6750
|
await opts.state.patch({ set: { loginMode, passwordLogin } });
|
|
5907
6751
|
} catch (error) {
|
|
5908
|
-
opts.log?.(`hiwork-core: \u5BA2\u6237\u7AEF\u7B56\u7565\u5199\u5165\u5931\u8D25\uFF08${
|
|
6752
|
+
opts.log?.(`hiwork-core: \u5BA2\u6237\u7AEF\u7B56\u7565\u5199\u5165\u5931\u8D25\uFF08${errorName2(error)}\uFF09`);
|
|
5909
6753
|
}
|
|
5910
6754
|
return loginMode;
|
|
5911
6755
|
}
|
|
@@ -6127,6 +6971,20 @@ function apply(ctx, config) {
|
|
|
6127
6971
|
clearInterval(timer);
|
|
6128
6972
|
};
|
|
6129
6973
|
}, "hiwork-core: workbench warmup");
|
|
6974
|
+
const pmsTodoDeps = {
|
|
6975
|
+
credentials: ports.credentials,
|
|
6976
|
+
pms: new PmsClient({ log: (message) => {
|
|
6977
|
+
ctx.logger.warn(message);
|
|
6978
|
+
} }),
|
|
6979
|
+
log: (message) => {
|
|
6980
|
+
ctx.logger.warn(message);
|
|
6981
|
+
}
|
|
6982
|
+
};
|
|
6983
|
+
const pmsTodo = new PmsTodoStore(pmsTodoDeps);
|
|
6984
|
+
ctx.effect(
|
|
6985
|
+
() => registerPmsTodoRpc({ connection, logger: ctx.logger }, pmsTodoDeps, pmsTodo),
|
|
6986
|
+
"hiwork-core: pms todo rpc"
|
|
6987
|
+
);
|
|
6130
6988
|
ctx.effect(() => provideOfficeCredentials(ctx, {
|
|
6131
6989
|
resolveSession: async () => (await ports.credentials.resolve(SESSION_REF))?.value,
|
|
6132
6990
|
gateway: () => {
|