@dsh-plus/notify-email 0.1.11 → 0.1.12

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 CHANGED
@@ -196,14 +196,15 @@ window.__ModuleLoader__.load({
196
196
  //#endregion
197
197
  //#region ../shared/src/client/scope.ts
198
198
  /**
199
- * api 直连实现的命名空间 scope(官方配置页 tab 同款模式)。
199
+ * remote.settings 直连实现的命名空间 scope(官方配置页 tab 同款模式)。
200
200
  * 读:describe 取命名空间视图(脱敏 value/revision + 顶层 writable);
201
201
  * 刷新:remote `settings/document-updated`(按命名空间过滤)与
202
202
  * `connection/reset`;generation 防旧读覆盖新发布。任何页面 origin
203
203
  * (loopback 或 tailnet 信任域名)下行为一致。
204
204
  * effect 归属调用方 fiber(label 需含插件名以便诊断)。
205
205
  */
206
- function createApiScope(api, ns, c, label) {
206
+ function createSettingsScope(c, ns, label) {
207
+ const remote = c.get("remote");
207
208
  let state = {
208
209
  status: "loading",
209
210
  value: void 0,
@@ -218,36 +219,37 @@ window.__ModuleLoader__.load({
218
219
  };
219
220
  const load = async () => {
220
221
  const gen = ++generation;
221
- let response;
222
+ let view;
222
223
  try {
223
- response = await api.describe({});
224
+ const response = await remote.settings.describe();
225
+ if (!response.ok) return;
226
+ view = response.value;
224
227
  } catch {
225
228
  return;
226
229
  }
227
- if (gen !== generation || !response.result.ok) return;
228
- const writable = response.result.value?.writable ?? false;
229
- const view = response.result.value?.namespaces.find((candidate) => candidate.ns === ns);
230
- if (view === void 0) {
230
+ if (gen !== generation) return;
231
+ const namespace = view.namespaces.find((candidate) => candidate.ns === ns);
232
+ if (namespace === void 0) {
231
233
  publish({
232
234
  status: "unavailable",
233
235
  value: void 0,
234
236
  revision: void 0,
235
- writable
237
+ writable: view.writable
236
238
  });
237
239
  return;
238
240
  }
239
241
  publish({
240
242
  status: "ready",
241
- value: view.value,
242
- revision: view.revision,
243
- writable
243
+ value: namespace.value,
244
+ revision: namespace.revision,
245
+ writable: view.writable
244
246
  });
245
247
  };
246
248
  const refresh = (namespace) => {
247
249
  if (namespace !== void 0 && namespace !== ns) return;
248
250
  load();
249
251
  };
250
- const disposers = [c.get("remote").$on("settings/document-updated", refresh), c.on("connection/reset", () => {
252
+ const disposers = [remote.$on("settings/document-updated", refresh), c.on("connection/reset", () => {
251
253
  load();
252
254
  })];
253
255
  c.effect(() => () => {
@@ -265,6 +267,20 @@ window.__ModuleLoader__.load({
265
267
  load
266
268
  };
267
269
  }
270
+ /** 以 remote.settings 构造命名空间绑定的 api 面。 */
271
+ function createNamespaceApi(remote, ns) {
272
+ return {
273
+ update: async (patch, expectedRevision) => {
274
+ const response = await remote.update(ns, patch, expectedRevision);
275
+ if (!response.ok) throw new Error(response.error.message ?? "settings update failed");
276
+ },
277
+ describe: async () => {
278
+ const response = await remote.describe();
279
+ if (!response.ok) throw new Error(response.error.message ?? "settings describe failed");
280
+ return response.value;
281
+ }
282
+ };
283
+ }
268
284
  //#endregion
269
285
  //#region ../shared/src/client/styles.ts
270
286
  /**
@@ -371,7 +387,7 @@ ${extra}
371
387
  * settings 命名空间字面量(纯常量,零依赖,浏览器半可安全引入)。
372
388
  * 服务端经 settingsNamespace() 品牌化后使用(config.ts),浏览器半将其作为
373
389
  * settings.plugin.item keyed 槽位的 key —— 同一字面量两处共用,防止漂移。
374
- * 契约:dsh rc7 起该槽位按「卡片编辑的 settings 命名空间」作 key 分发,
390
+ * 契约:该槽位按「卡片编辑的 settings 命名空间」作 key 分发(0.1.2-alpha.1 基线),
375
391
  * 官方配置页只渲染 key 命中 Host 已注册命名空间的卡片。
376
392
  * @module notify-email/ns
377
393
  */
@@ -397,7 +413,8 @@ ${extra}
397
413
  * 外壳与基础控件走 @dsh-plus/shared/client 套件(CardChrome/TextField/CheckRow),
398
414
  * 本文件只保留业务字段与保存/测试逻辑。交互对齐官方卡片:折叠/展开、
399
415
  * staged draft、未保存标记、保存/放弃;另加「发送测试邮件」。
400
- * 配置读写走官方 settingsScope 传输:value 为 schema 解析后的脱敏视图
416
+ * 配置读写经 ctx.remote.settings 直连(0.1.2-alpha.1 起 connection.api.settings
417
+ * 已移除):value 为 schema 解析后的脱敏视图
401
418
  * (smtp.pass 不出现,passConfigured 由 describe 的 secrets 探测),
402
419
  * 保存经 settings.update 深合并(空 pass 剔除 = 保持不变)。
403
420
  * @module notify-email/client/card
@@ -462,10 +479,10 @@ ${extra}
462
479
  }, [value, draft]);
463
480
  (0, react.useEffect)(() => {
464
481
  let alive = true;
465
- api.describe({}).then((response) => {
466
- if (!alive || !response.result.ok) return;
467
- const view = response.result.value?.namespaces.find((ns) => ns.ns === SETTINGS_NS);
468
- setPassConfigured(view?.secrets.some((secret) => secret.path.join(".") === "smtp.pass" && secret.set) ?? false);
482
+ api.describe().then((view) => {
483
+ if (!alive) return;
484
+ const ns = view.namespaces.find((candidate) => candidate.ns === SETTINGS_NS);
485
+ setPassConfigured(ns?.secrets.some((secret) => secret.path.join(".") === "smtp.pass" && secret.set) ?? false);
469
486
  }).catch(() => {});
470
487
  return () => {
471
488
  alive = false;
@@ -498,12 +515,7 @@ ${extra}
498
515
  const patch = toPatch(draft);
499
516
  if (patch["smtp"]["pass"] === "") delete patch["smtp"]["pass"];
500
517
  const revision = scope.getSnapshot().revision;
501
- api.update({
502
- ns: SETTINGS_NS,
503
- patch,
504
- ...revision !== void 0 ? { expectedRevision: revision } : {}
505
- }).then(async (response) => {
506
- if (!response.result.ok) throw new Error(response.result.error?.message ?? t("saveFailed"));
518
+ api.update(patch, revision).then(async () => {
507
519
  await scope.load();
508
520
  const next = scope.getSnapshot().value;
509
521
  if (next !== void 0) setDraft(draftFromValue(next));
@@ -793,8 +805,8 @@ ${extra}
793
805
  const inject = [
794
806
  "slots",
795
807
  "locale",
796
- "connection",
797
- "remote"
808
+ "remote",
809
+ "remote.settings"
798
810
  ];
799
811
  function apply(ctx) {
800
812
  const c = ctx;
@@ -806,8 +818,8 @@ ${extra}
806
818
  zh,
807
819
  en
808
820
  }), "notify-email: locale");
809
- const api = c.get("connection").api.settings;
810
- const scope = createApiScope(api, SETTINGS_NS, c, "notify-email: settings scope");
821
+ const scope = createSettingsScope(c, SETTINGS_NS, "notify-email: settings scope");
822
+ const api = createNamespaceApi(c.get("remote").settings, SETTINGS_NS);
811
823
  c.slots.inject("settings.plugin.item", () => c.slots.register({
812
824
  name: "settings.plugin.item",
813
825
  key: SETTINGS_NS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-plus/notify-email",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "dsh-plus service+ui plugin: 任务结束/等待决策/出错时向指定邮箱发送邮件通知",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -23,9 +23,8 @@
23
23
  "dsh": {
24
24
  "client": {
25
25
  "inject": [
26
- "@deepseek-ai/dsh-client-runtime",
26
+ "@deepseek-ai/dsh-client-ui-renderer",
27
27
  "@deepseek-ai/dsh-client-locale",
28
- "@deepseek-ai/dsh-client-connection",
29
28
  "@deepseek-ai/dsh-api-remotes"
30
29
  ],
31
30
  "platform": "web"
@@ -33,23 +32,23 @@
33
32
  },
34
33
  "dependencies": {
35
34
  "nodemailer": "9.0.5",
36
- "@dsh-plus/shared": "0.1.5"
35
+ "@dsh-plus/shared": "0.1.6"
37
36
  },
38
37
  "peerDependencies": {
39
38
  "@deepseek-ai/cordis": "^4.0.1",
40
- "@deepseek-ai/dsh-home-paths": "^0.1.0-rc.8",
41
- "@deepseek-ai/dsh-settings": "^0.1.0-rc.8",
39
+ "@deepseek-ai/dsh-home-paths": "^0.1.2-alpha.1",
40
+ "@deepseek-ai/dsh-settings": "^0.1.2-alpha.1",
42
41
  "@deepseek-ai/schemastery": "^3.18.1"
43
42
  },
44
43
  "devDependencies": {
45
44
  "@deepseek-ai/cordis": "4.0.1",
46
- "@deepseek-ai/dsh-agent": "0.1.1-rc.2",
47
- "@deepseek-ai/dsh-home-paths": "0.1.1-rc.2",
48
- "@deepseek-ai/dsh-host-webserver": "0.1.1-rc.2",
49
- "@deepseek-ai/dsh-llm": "0.1.1-rc.2",
50
- "@deepseek-ai/dsh-session": "0.1.1-rc.2",
51
- "@deepseek-ai/dsh-settings": "0.1.1-rc.2",
52
- "@deepseek-ai/dsh-tools": "0.1.1-rc.2",
45
+ "@deepseek-ai/dsh-agent": "0.1.2-alpha.1",
46
+ "@deepseek-ai/dsh-home-paths": "0.1.2-alpha.1",
47
+ "@deepseek-ai/dsh-host-webserver": "0.1.2-alpha.1",
48
+ "@deepseek-ai/dsh-llm": "0.1.2-alpha.1",
49
+ "@deepseek-ai/dsh-session": "0.1.2-alpha.1",
50
+ "@deepseek-ai/dsh-settings": "0.1.2-alpha.1",
51
+ "@deepseek-ai/dsh-tools": "0.1.2-alpha.1",
53
52
  "@deepseek-ai/schemastery": "3.18.1",
54
53
  "@types/nodemailer": "8.0.1",
55
54
  "@types/react": "~18.3.1",
package/src/client/api.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
- * 自定义端点通道:仅剩「发送测试邮件」(配置读写已迁移到官方 settingsScope
3
- * 传输,见 scope.ts 与 card.tsx)。
2
+ * 自定义端点通道:仅剩「发送测试邮件」(配置读写经 ctx.remote.settings
3
+ * 直连,见 shared/client scope.ts 与 card.tsx)。
4
4
  * @module notify-email/client/api
5
5
  */
6
6
 
@@ -3,7 +3,8 @@
3
3
  * 外壳与基础控件走 @dsh-plus/shared/client 套件(CardChrome/TextField/CheckRow),
4
4
  * 本文件只保留业务字段与保存/测试逻辑。交互对齐官方卡片:折叠/展开、
5
5
  * staged draft、未保存标记、保存/放弃;另加「发送测试邮件」。
6
- * 配置读写走官方 settingsScope 传输:value 为 schema 解析后的脱敏视图
6
+ * 配置读写经 ctx.remote.settings 直连(0.1.2-alpha.1 起 connection.api.settings
7
+ * 已移除):value 为 schema 解析后的脱敏视图
7
8
  * (smtp.pass 不出现,passConfigured 由 describe 的 secrets 探测),
8
9
  * 保存经 settings.update 深合并(空 pass 剔除 = 保持不变)。
9
10
  * @module notify-email/client/card
@@ -14,8 +15,8 @@ import {
14
15
  type CardStatusState,
15
16
  CheckRow,
16
17
  IDLE_STATUS,
18
+ type NamespaceSettingsApi,
17
19
  type Scope,
18
- type SettingsApi,
19
20
  TextField,
20
21
  } from '@dsh-plus/shared/client'
21
22
  import { type ReactElement, useEffect, useMemo, useState, useSyncExternalStore } from 'react'
@@ -25,7 +26,7 @@ import { sendTest } from './api.ts'
25
26
  export interface CardProps {
26
27
  t(key: string): string
27
28
  scope: Scope
28
- api: SettingsApi
29
+ api: NamespaceSettingsApi
29
30
  }
30
31
 
31
32
  /** settings 命名空间的脱敏解析值(smtp.pass 被脱敏剥除)。 */
@@ -136,12 +137,12 @@ export function NotifyEmailCard(props: CardProps): ReactElement | null {
136
137
  useEffect(() => {
137
138
  let alive = true
138
139
  api
139
- .describe({})
140
- .then((response) => {
141
- if (!alive || !response.result.ok) return
142
- const view = response.result.value?.namespaces.find((ns) => ns.ns === SETTINGS_NS)
140
+ .describe()
141
+ .then((view) => {
142
+ if (!alive) return
143
+ const ns = view.namespaces.find((candidate) => candidate.ns === SETTINGS_NS)
143
144
  setPassConfigured(
144
- view?.secrets.some((secret) => secret.path.join('.') === 'smtp.pass' && secret.set) ??
145
+ ns?.secrets.some((secret) => secret.path.join('.') === 'smtp.pass' && secret.set) ??
145
146
  false,
146
147
  )
147
148
  })
@@ -190,15 +191,8 @@ export function NotifyEmailCard(props: CardProps): ReactElement | null {
190
191
  }
191
192
  const revision = scope.getSnapshot().revision
192
193
  api
193
- .update({
194
- ns: SETTINGS_NS,
195
- patch,
196
- ...(revision !== undefined ? { expectedRevision: revision } : {}),
197
- })
198
- .then(async (response) => {
199
- if (!response.result.ok) {
200
- throw new Error(response.result.error?.message ?? t('saveFailed'))
201
- }
194
+ .update(patch, revision)
195
+ .then(async () => {
202
196
  await scope.load()
203
197
  const next = scope.getSnapshot().value as ConfigValue | undefined
204
198
  if (next !== undefined) setDraft(draftFromValue(next))
@@ -3,18 +3,24 @@
3
3
  * 构建产物为 window.__ModuleLoader__.load({id, factory}) 形式的 CJS factory
4
4
  * (包装见 tsdown.config.ts);样式沿用官方 data-plugin-css 约定,HMR 据此卸载。
5
5
  *
6
- * 类型说明:浏览器半只用到 slots/locale/connection/remote 的很窄一面,
6
+ * 类型说明:浏览器半只用到 slots/locale/remote 的很窄一面,
7
7
  * scope 与基础控件走 @dsh-plus/shared/client 套件(收编自本插件原实现)。
8
- * rc7 起该插槽为 keyed 槽位:key 必须是卡片编辑的 settings 命名空间
9
- * (即服务端 settingsNamespace() 注册的同一字面量,见 ../ns.ts),
8
+ * 该插槽为 keyed 槽位(0.1.2-alpha.1 基线):key 必须是卡片编辑的 settings
9
+ * 命名空间(即服务端 settingsNamespace() 注册的同一字面量,见 ../ns.ts),
10
10
  * 官方配置页按此 key 与 Host 已注册命名空间配对分发。
11
- * 配置读写经官方 settings RPC 直连(rc7 起第三方命名空间全量开放;
12
- * 不复用 settingsScope 服务——非 loopback 页面下它无数据);
11
+ * 配置读写经 ctx.remote.settings 直连(0.1.2-alpha.1 起 connection.api.settings
12
+ * 已移除;不复用 settingsScope 服务——非 loopback 页面下它固定 memory 模式无数据);
13
13
  * 自定义端点仅剩「发送测试邮件」(api.ts)。
14
14
  * @module @dsh-plus/notify-email/client
15
15
  */
16
16
  import type { Context } from '@deepseek-ai/cordis'
17
- import { cardCss, createApiScope, injectCardStyle } from '@dsh-plus/shared/client'
17
+ import {
18
+ cardCss,
19
+ createNamespaceApi,
20
+ createSettingsScope,
21
+ injectCardStyle,
22
+ type SettingsRemoteFace,
23
+ } from '@dsh-plus/shared/client'
18
24
  import { SETTINGS_NS } from '../ns.ts'
19
25
  import { NotifyEmailCard } from './card.tsx'
20
26
  import { en, NS, zh } from './i18n.ts'
@@ -22,7 +28,7 @@ import { en, NS, zh } from './i18n.ts'
22
28
  export const name = 'dsh-plus-notify-email'
23
29
 
24
30
  /** 浏览器半需要的 cordis 服务 key(loader 据此注入;package.json 的 dsh.client.inject 管包加载顺序)。 */
25
- export const inject = ['slots', 'locale', 'connection', 'remote'] as const
31
+ export const inject = ['slots', 'locale', 'remote', 'remote.settings'] as const
26
32
 
27
33
  interface SlotsLike {
28
34
  inject(key: string, callback: () => unknown): unknown
@@ -35,17 +41,13 @@ interface LocaleLike {
35
41
  }
36
42
 
37
43
  interface RemoteLike {
44
+ settings: SettingsRemoteFace
38
45
  $on(event: string, listener: (payload?: unknown) => void): () => void
39
46
  }
40
47
 
41
- interface ConnectionLike {
42
- api: { settings: Parameters<typeof createApiScope>[0] }
43
- }
44
-
45
48
  interface ClientContext {
46
49
  slots: SlotsLike
47
50
  locale: LocaleLike
48
- get(key: 'connection'): ConnectionLike
49
51
  get(key: 'remote'): RemoteLike
50
52
  on(event: string, listener: () => void): () => void
51
53
  effect(execute: () => () => void, label?: string): unknown
@@ -61,8 +63,8 @@ export function apply(ctx: Context): void {
61
63
  'notify-email: style',
62
64
  )
63
65
  c.effect(() => c.locale.register(NS, { zh, en }), 'notify-email: locale')
64
- const api = c.get('connection').api.settings
65
- const scope = createApiScope(api, SETTINGS_NS, c, 'notify-email: settings scope')
66
+ const scope = createSettingsScope(c, SETTINGS_NS, 'notify-email: settings scope')
67
+ const api = createNamespaceApi(c.get('remote').settings, SETTINGS_NS)
66
68
  c.slots.inject('settings.plugin.item', () =>
67
69
  c.slots.register(
68
70
  {
package/src/config-api.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
- * 自定义端点 HTTP 通道:配置读写已迁移到官方 settings RPC(rc7 起第三方
3
- * 命名空间全量开放),这里只保留官方传输没有的「发送测试邮件」端点。
2
+ * 自定义端点 HTTP 通道:配置读写走官方 settings 命名空间(0.1.2-alpha.1 起
3
+ * 第三方命名空间全量开放),这里只保留官方传输没有的「发送测试邮件」端点。
4
4
  * 仅监听 dsh web 同源(webserver 默认 127.0.0.1),与 GUI 其余面同等暴露面。
5
5
  * @module notify-email/config-api
6
6
  */
package/src/ns.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * settings 命名空间字面量(纯常量,零依赖,浏览器半可安全引入)。
3
3
  * 服务端经 settingsNamespace() 品牌化后使用(config.ts),浏览器半将其作为
4
4
  * settings.plugin.item keyed 槽位的 key —— 同一字面量两处共用,防止漂移。
5
- * 契约:dsh rc7 起该槽位按「卡片编辑的 settings 命名空间」作 key 分发,
5
+ * 契约:该槽位按「卡片编辑的 settings 命名空间」作 key 分发(0.1.2-alpha.1 基线),
6
6
  * 官方配置页只渲染 key 命中 Host 已注册命名空间的卡片。
7
7
  * @module notify-email/ns
8
8
  */