dsh-daily-brief 2.0.2 → 2.0.4

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/README.md CHANGED
@@ -27,9 +27,10 @@
27
27
  ## 环境要求
28
28
 
29
29
  - Windows 10/11(弹窗与计划任务依赖 Windows;其他平台只能生成 HTML)。
30
+ - DeepSeek Harness 核心 **≥ 0.2.1-rc.3**(已声明 `engines.dsh`,在 0.2.2-rc.1 上验证)。
30
31
  - 已安装 DeepSeek Harness,并能用 `plugin_manager` 安装本地插件目录。
31
32
  - 本机有 Microsoft Edge 或 Google Chrome(用于独立窗口)。
32
- - Node.js 随 DSH 提供;若要跑测试,需要 Node 18+。
33
+ - Node.js 20+(随 DSH 提供;跑测试也用它)。
33
34
 
34
35
  ## 安装
35
36
 
@@ -176,6 +177,7 @@ Outlook 日历 →「设置 → 共享日历 → 发布日历」→ 复制 ICS
176
177
  | 桌面窗口没出现 | 检查是否安装 Edge/Chrome;查看 `logs\runner.log` 的投递记录 |
177
178
  | 飞书/ManageBac 抓不到条目 | 多为前端渲染或需要登录:改用日历 ICS 订阅,或在「添加来源」里填入 Cookie |
178
179
  | Outlook 邮箱填密码后报错 / 连不上 | Microsoft 已停用 IMAP 密码登录,但不再需要 Azure:按上面「Outlook 怎么办」把邮件转发到 Gmail / QQ,再添加接收邮箱;或直接用 Outlook 日历的 ICS 链接 |
180
+ | 插件市场里安装失败,任务日志是 `pnpm` 且提到 `@napi-rs/canvas-*` / `@img/sharp-*` 或 `EPERM`/`EBUSY` | 失败发生在市场对整个 DSH profile 做 pnpm 重解析时(删宿主自带的原生可选包,被运行中的 DSH 占用),与插件包无关。完全退出 DSH(含托盘)后重装;仍失败就在终端执行 `dsh plugin add dsh-daily-brief` 取真正的错误行 |
179
181
 
180
182
  ## 配置参考(`config.json`)
181
183
 
@@ -233,6 +233,9 @@ export async function runBriefing(options = {}) {
233
233
  const config = store.loadConfig();
234
234
  const now = options.now ? new Date(options.now) : new Date();
235
235
  if (Number.isNaN(now.getTime())) throw new Error(`无效的 --now 时间:${options.now}`);
236
+ // A pinned --now run stays on that clock; wall time is only the default, so a
237
+ // replayed schedule never publishes early because the machine clock is late.
238
+ const clockNow = () => (options.now ? now.getTime() : Date.now());
236
239
  const dryRun = Boolean(options.dryRun);
237
240
  const deliver = options.deliver !== false && !dryRun;
238
241
  const force = Boolean(options.force);
@@ -301,7 +304,7 @@ export async function runBriefing(options = {}) {
301
304
  // Preparing can take longer than the lead time (slow sources). If the
302
305
  // publish instant has arrived, deliver here instead of waiting for a
303
306
  // trigger that IgnoreNew may have already swallowed.
304
- if (Date.now() < plan.publishAt.getTime() - PUBLISH_EARLY_MS) {
307
+ if (clockNow() < plan.publishAt.getTime() - PUBLISH_EARLY_MS) {
305
308
  return {
306
309
  ok: true,
307
310
  phase,
@@ -1,14 +1,52 @@
1
1
  /**
2
- * Loopback HTTP surface used by the client panel (relative `api/daily-brief/*`).
3
- * Every mutating call must carry the private header; all calls are loopback-only.
2
+ * Host HTTP surface used by the client panel (relative `api/daily-brief/*`).
3
+ *
4
+ * The plugin owns this route, so it also owns the request policy. On hosts that
5
+ * expose DSH's Connection service every call passes the same Host/Origin fence
6
+ * and browser-session check as the built-in `/api` bridge
7
+ * (`connection.requestRejection`). That covers both the Web GUI and the Desktop
8
+ * renderer: the `dsh-app` scheme forwards an authenticated, same-origin request
9
+ * for the panel, so no plugin-specific header is needed.
10
+ *
11
+ * Hosts without that service keep the historical local-only gate, now strict:
12
+ * the request must come from loopback *and* carry `x-daily-brief-client: 1`.
4
13
  */
5
14
  const PREFIX = '/api/daily-brief';
15
+ const CLIENT_HEADER = 'x-daily-brief-client';
6
16
 
7
17
  function isLoopback(req) {
8
18
  const address = req.socket?.remoteAddress || '';
9
19
  return address === '127.0.0.1' || address === '::1' || address === '::ffff:127.0.0.1';
10
20
  }
11
21
 
22
+ /**
23
+ * Decide whether one request may reach the panel API.
24
+ * @returns `undefined` when admitted, otherwise the status and error to answer with.
25
+ */
26
+ function admissionFailure(ctx, req) {
27
+ let connection;
28
+ try {
29
+ connection = typeof ctx.get === 'function' ? ctx.get('connection') : ctx.connection;
30
+ } catch {
31
+ connection = undefined;
32
+ }
33
+ if (connection && typeof connection.requestRejection === 'function') {
34
+ try {
35
+ const status = connection.requestRejection(req);
36
+ if (!status) return undefined;
37
+ return {
38
+ status,
39
+ error: status === 401 ? '需要 DSH 网页会话认证' : '请求来源不受信任',
40
+ };
41
+ } catch {
42
+ return { status: 503, error: '暂时无法校验请求来源' };
43
+ }
44
+ }
45
+ if (!isLoopback(req)) return { status: 403, error: '仅限本机访问' };
46
+ if (req.headers[CLIENT_HEADER] !== '1') return { status: 403, error: '客户端标识无效' };
47
+ return undefined;
48
+ }
49
+
12
50
  function sendJson(res, status, body) {
13
51
  const payload = JSON.stringify(body ?? null);
14
52
  res.statusCode = status;
@@ -55,12 +93,13 @@ export function registerRoutes(ctx, service) {
55
93
  const sub = url.pathname.slice(PREFIX.length) || '/';
56
94
  const method = req.method || 'GET';
57
95
 
58
- if (!isLoopback(req)) {
59
- sendJson(res, 403, { error: '仅限本机访问' });
96
+ const failure = admissionFailure(ctx, req);
97
+ if (failure) {
98
+ sendJson(res, failure.status, { error: failure.error });
60
99
  return;
61
100
  }
62
101
 
63
- // The exported page is opened by the user directly; it needs no header.
102
+ // `/artifact` has no panel client of its own; admission above is the guard.
64
103
  if (sub === '/artifact' && method === 'GET') {
65
104
  const id = url.searchParams.get('id') || 'latest';
66
105
  const briefing = await service.getBriefing(id);
@@ -88,13 +127,6 @@ export function registerRoutes(ctx, service) {
88
127
  return;
89
128
  }
90
129
 
91
- // The Desktop renderer reaches these routes through the dsh-app scheme handler,
92
- // which cannot attach custom headers on preflighted reads; loopback is the guard.
93
- if (req.headers['x-daily-brief-client'] !== undefined && req.headers['x-daily-brief-client'] !== '1') {
94
- sendJson(res, 403, { error: '客户端标识无效' });
95
- return;
96
- }
97
-
98
130
  try {
99
131
  if (sub === '/state' && method === 'GET') {
100
132
  sendJson(res, 200, await service.getState());
package/package.json CHANGED
@@ -1,10 +1,14 @@
1
1
  {
2
2
  "name": "dsh-daily-brief",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
7
7
  "type": "module",
8
+ "engines": {
9
+ "node": ">=20",
10
+ "dsh": ">=0.2.1-rc.3"
11
+ },
8
12
  "description": "Daily briefing plugin for DeepSeek Harness: X, RSS/Atom/JSON/ICS, YouTube, Bilibili, Steam, Minecraft and status pages, mailboxes and calendars in one persona-styled card with a scheduled Windows popup.",
9
13
  "license": "MIT",
10
14
  "repository": {