@dsh-plus/access-gate 0.3.6 → 0.3.8

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
@@ -133,7 +133,10 @@ window.__ModuleLoader__.load({
133
133
  confirm: "Confirm",
134
134
  cancel: "Cancel"
135
135
  };
136
- /** own 覆盖同键合并(返回新对象,入参不可变)。 */
136
+ /**
137
+ * own 覆盖同键合并(返回新对象,入参不可变)。
138
+ * 返回类型保留两侧键集合,调用方据此派生 DictKey。
139
+ */
137
140
  function mergeDict(base, own) {
138
141
  return {
139
142
  ...base,
@@ -369,12 +372,17 @@ ${extra}
369
372
  autoLoginTrustedIps: draft.autoLoginTrustedIps
370
373
  };
371
374
  }
372
- /** reason 枚举 → 文案 key。 */
375
+ /** reason 枚举 → 文案 key(未知 reason 原样展示服务端文本,不当作文案键)。 */
373
376
  const REASON_KEYS = {
374
377
  local: "diagLocal",
375
378
  cookie: "diagCookie",
376
379
  "trusted-ip": "diagTrustedIp"
377
380
  };
381
+ /** reason → 展示文本:已知枚举走本地化,未知回落服务端原串。 */
382
+ function reasonText(t, reason) {
383
+ const key = REASON_KEYS[reason];
384
+ return key === void 0 ? reason : t(key);
385
+ }
378
386
  function verdictLabel(t, diag) {
379
387
  if (!diag.enabled) return t("diagOff");
380
388
  if (diag.verdict === "pass") return t("diagPass");
@@ -554,7 +562,7 @@ ${extra}
554
562
  children: t("diagReason")
555
563
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
556
564
  className: "dag-diagVal",
557
- children: diag.reason !== null ? t(REASON_KEYS[diag.reason] ?? diag.reason) : "—"
565
+ children: diag.reason !== null ? reasonText(t, diag.reason) : "—"
558
566
  })]
559
567
  }),
560
568
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
package/lib/index.d.ts CHANGED
@@ -16,7 +16,7 @@ type AccessGateConfig = Schemastery.TypeT<typeof Config>;
16
16
  //#endregion
17
17
  //#region src/index.d.ts
18
18
  declare const name = "dsh-plus-access-gate";
19
- declare const inject: readonly ["webServer", "connection"];
19
+ declare const inject: readonly ['webServer', 'connection'];
20
20
  declare module '@deepseek-ai/cordis' {
21
21
  interface Context {
22
22
  accessGate: AccessGateService;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-plus/access-gate",
3
- "version": "0.3.6",
3
+ "version": "0.3.8",
4
4
  "description": "dsh-plus service+ui plugin: dsh web 全量访问围栏——与官方 browser-auth 合并:官方 cookie 为唯一凭据,可选 IP 附加围栏,未认证导航渲染 token 输入页(PWA 可恢复),loopback 管理通道",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -31,20 +31,20 @@
31
31
  }
32
32
  },
33
33
  "dependencies": {
34
- "@dsh-plus/shared": "0.1.15"
34
+ "@dsh-plus/shared": "0.1.17"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "@deepseek-ai/cordis": "^4.0.2",
38
- "@deepseek-ai/dsh-home-paths": "^0.1.5-rc.1",
39
- "@deepseek-ai/dsh-settings": "^0.1.5-rc.1",
38
+ "@deepseek-ai/dsh-home-paths": "^0.1.5-rc.2",
39
+ "@deepseek-ai/dsh-settings": "^0.1.5-rc.2",
40
40
  "@deepseek-ai/schemastery": "^3.18.2"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@deepseek-ai/cordis": "4.0.2",
44
- "@deepseek-ai/dsh-client-connection": "0.1.5-rc.1",
45
- "@deepseek-ai/dsh-home-paths": "0.1.5-rc.1",
46
- "@deepseek-ai/dsh-host-webserver": "0.1.5-rc.1",
47
- "@deepseek-ai/dsh-settings": "0.1.5-rc.1",
44
+ "@deepseek-ai/dsh-client-connection": "0.1.5-rc.2",
45
+ "@deepseek-ai/dsh-home-paths": "0.1.5-rc.2",
46
+ "@deepseek-ai/dsh-host-webserver": "0.1.5-rc.2",
47
+ "@deepseek-ai/dsh-settings": "0.1.5-rc.2",
48
48
  "@deepseek-ai/schemastery": "3.18.2",
49
49
  "@types/react": "~18.3.31",
50
50
  "react": "^18.2.0"
@@ -21,9 +21,10 @@ import {
21
21
  type Scope,
22
22
  } from '@dsh-plus/shared/client'
23
23
  import { type ReactElement, useEffect, useMemo, useState, useSyncExternalStore } from 'react'
24
+ import type { DictKey, Translate } from './i18n.ts'
24
25
 
25
26
  export interface CardProps {
26
- t(key: string): string
27
+ t: Translate
27
28
  scope: Scope
28
29
  api: NamespaceSettingsApi
29
30
  }
@@ -77,14 +78,20 @@ function toPatch(draft: Draft): Record<string, unknown> {
77
78
  }
78
79
  }
79
80
 
80
- /** reason 枚举 → 文案 key。 */
81
- const REASON_KEYS: Record<string, string> = {
81
+ /** reason 枚举 → 文案 key(未知 reason 原样展示服务端文本,不当作文案键)。 */
82
+ const REASON_KEYS: Record<string, DictKey | undefined> = {
82
83
  local: 'diagLocal',
83
84
  cookie: 'diagCookie',
84
85
  'trusted-ip': 'diagTrustedIp',
85
86
  }
86
87
 
87
- function verdictLabel(t: (key: string) => string, diag: Diag): string {
88
+ /** reason → 展示文本:已知枚举走本地化,未知回落服务端原串。 */
89
+ function reasonText(t: Translate, reason: string): string {
90
+ const key = REASON_KEYS[reason]
91
+ return key === undefined ? reason : t(key)
92
+ }
93
+
94
+ function verdictLabel(t: Translate, diag: Diag): string {
88
95
  if (!diag.enabled) return t('diagOff')
89
96
  if (diag.verdict === 'pass') return t('diagPass')
90
97
  if (diag.verdict === 'token-page') return t('diagTokenPage')
@@ -251,7 +258,7 @@ export function AccessGateCard(props: CardProps): ReactElement | null {
251
258
  <div className="dag-diagRow">
252
259
  <span className="dag-diagKey">{t('diagReason')}</span>
253
260
  <span className="dag-diagVal">
254
- {diag.reason !== null ? t(REASON_KEYS[diag.reason] ?? diag.reason) : '—'}
261
+ {diag.reason !== null ? reasonText(t, diag.reason) : '—'}
255
262
  </span>
256
263
  </div>
257
264
  <div className="dag-diagRow">
@@ -16,11 +16,11 @@ import type { Context } from '@deepseek-ai/cordis'
16
16
  import {
17
17
  createNamespaceApi,
18
18
  createSettingsScope,
19
- type SettingsRemoteFace,
19
+ type PluginClientContext,
20
20
  } from '@dsh-plus/shared/client'
21
21
  import { SETTINGS_NS } from '../ns.ts'
22
22
  import { AccessGateCard } from './card.tsx'
23
- import { en, NS, zh } from './i18n.ts'
23
+ import { type DictKey, en, NS, zh } from './i18n.ts'
24
24
  import { injectStyle } from './styles.ts'
25
25
 
26
26
  export const name = 'dsh-plus-access-gate'
@@ -28,28 +28,8 @@ export const name = 'dsh-plus-access-gate'
28
28
  /** 浏览器半需要的 cordis 服务 key(loader 据此注入;package.json 的 dsh.client.inject 管包加载顺序)。 */
29
29
  export const inject = ['slots', 'locale', 'remote', 'remote.settings'] as const
30
30
 
31
- interface SlotsLike {
32
- inject(key: string, callback: () => unknown): unknown
33
- register(options: Record<string, unknown>, component: unknown): () => void
34
- }
35
-
36
- interface LocaleLike {
37
- register(ns: string, dict: { zh: Record<string, string>; en: Record<string, string> }): () => void
38
- bind(ns: string): (key: string) => string
39
- }
40
-
41
- interface RemoteLike {
42
- settings: SettingsRemoteFace
43
- $on(event: string, listener: (payload?: unknown) => void): () => void
44
- }
45
-
46
- interface ClientContext {
47
- slots: SlotsLike
48
- locale: LocaleLike
49
- get(key: 'remote'): RemoteLike
50
- on(event: string, listener: () => void): () => void
51
- effect(execute: () => () => void, label?: string): unknown
52
- }
31
+ /** 宿主窄面收编在 @dsh-plus/shared/client;TKey 传入本包 DictKey 使键名受检。 */
32
+ type ClientContext = PluginClientContext<DictKey>
53
33
 
54
34
  export function apply(ctx: Context): void {
55
35
  const c = ctx as unknown as ClientContext
@@ -42,7 +42,7 @@ const ownZh = {
42
42
  diagAutoLoginNoSecret: '不可用(签名密钥读取失败,已降级)',
43
43
  diagOff: '围栏未启用',
44
44
  diagInvalid: '白名单存在无法解析的条目:',
45
- }
45
+ } as const
46
46
 
47
47
  const ownEn = {
48
48
  title: 'Access Control',
@@ -80,7 +80,13 @@ const ownEn = {
80
80
  diagAutoLoginNoSecret: 'unavailable (signing secret unreadable, degraded)',
81
81
  diagOff: 'gate disabled',
82
82
  diagInvalid: 'Allowlist entries that failed to parse:',
83
- }
83
+ } as const
84
84
 
85
- export const zh: Record<string, string> = mergeDict(commonZh, ownZh)
86
- export const en: Record<string, string> = mergeDict(commonEn, ownEn)
85
+ export type DictKey = keyof typeof commonZh | keyof typeof ownZh
86
+
87
+ /** 卡片/面板共用的翻译函数类型(slot 注入的 bind 结果按此消费)。 */
88
+ export type Translate = (key: DictKey) => string
89
+
90
+ // 标注为 Record<DictKey, string>:en 缺任一键即编译期报错(中英强制对齐)。
91
+ export const zh: Record<DictKey, string> = mergeDict(commonZh, ownZh)
92
+ export const en: Record<DictKey, string> = mergeDict(commonEn, ownEn)
package/src/decision.ts CHANGED
@@ -29,12 +29,18 @@ export type GateVerdict =
29
29
  /** 判定输出。 */
30
30
  export interface GateDecision {
31
31
  verdict: GateVerdict
32
- /** 还原出的客户端 IP(有 XFF 或非 loopback 直连时存在)。 */
33
- clientIp?: string
32
+ /**
33
+ * 还原出的客户端 IP(有 XFF 或非 loopback 直连时存在)。
34
+ *
35
+ * 三个可选字段并上 `| undefined`:构造处用条件表达式直接给值
36
+ * (如 `const invalidEntries = ... ? x : undefined`),而消费方一律按
37
+ * 「缺席或 undefined 等价」处理(routes.ts 用 `?? []` 兜底)。
38
+ */
39
+ clientIp?: string | undefined
34
40
  /** 放行原因(诊断/日志用):local / cookie / trusted-ip。 */
35
- reason?: 'local' | 'cookie' | 'trusted-ip'
41
+ reason?: 'local' | 'cookie' | 'trusted-ip' | undefined
36
42
  /** 无效白名单条目(卡片诊断用,判定时忽略它们)。 */
37
- invalidEntries?: string[]
43
+ invalidEntries?: string[] | undefined
38
44
  }
39
45
 
40
46
  /** 判定输入的请求事实(headers 小写键,node:http 原生形态)。 */
@@ -42,7 +48,9 @@ export interface GateRequest {
42
48
  readonly url: string
43
49
  readonly method: string
44
50
  readonly headers: Record<string, unknown>
45
- readonly remoteAddress?: string
51
+ // 并上 undefined:来源是 `req.socket?.remoteAddress`(socket 可能缺席),
52
+ // 消费方 isLoopbackAddress 本就接受 string | undefined。
53
+ readonly remoteAddress?: string | undefined
46
54
  }
47
55
 
48
56
  /** 取 XFF 最左条目(serve 单跳覆盖语义下即真实客户端 IP)。 */
@@ -41,7 +41,9 @@ type UpgradeRoute = { path: string; handler: UpgradeHandler }
41
41
  export interface GateWebServer {
42
42
  match(pathname: string): unknown
43
43
  upgrades: Map<string, UpgradeRoute>
44
- fallback?: HttpHandler
44
+ // 并上 undefined:本插件需在卸载时把 fallback 还原为「原本就没有」的状态
45
+ // (`server.fallback = originalFallback`,其值可能为 undefined)。
46
+ fallback?: HttpHandler | undefined
45
47
  registerFallback(handler: HttpHandler): unknown
46
48
  registerUpgrade(route: UpgradeRoute): unknown
47
49
  [key: string]: unknown