@riceawa/dsh-lan-gateway 0.5.4 → 0.6.0
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 +37 -21
- package/lib/client.js +190 -146
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +129 -8
- package/lib/index.js +1424 -968
- package/package.json +26 -14
- package/src/auth.ts +7 -14
- package/src/client/index.ts +39 -29
- package/src/client/lan-gateway-card.tsx +93 -145
- package/src/config-fields.ts +137 -0
- package/src/gateway.ts +203 -240
- package/src/index.ts +401 -157
- package/src/login.ts +1 -17
- package/src/request-policy.ts +315 -0
- package/src/state.ts +16 -5
- package/src/tls.ts +23 -0
- package/src/upstream-session.ts +24 -8
package/lib/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":["useState"],"sources":["../src/client/lan-gateway-card.tsx","../src/client/index.ts"],"sourcesContent":["/**\n * The lan-gateway settings card shown in the official DSH Settings → Plugins\n * page (the `settings.plugin.item` slot).\n *\n * ModLens-style: the card carries NO injected services. It reads and writes\n * the loopback-only `/lan-gateway/config` host route (the browser never sees\n * the settings seam or any secret), so the client bundle's only dependency is\n * the `slots` service that every plugin already has.\n *\n * @module @riceawa/dsh-lan-gateway/client/card\n */\n\nimport { useEffect, useState, type ChangeEvent, type ReactNode } from 'react'\nimport type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\n\n/**\n * The official Settings → Plugins page declares the `settings.plugin.item`\n * slot keyed by the settings namespace each card edits (newer DSH releases;\n * older releases dispatched it as a list slot by `id`). The published package\n * ships no `src/`, so the entry is re-declared here — the runtime slot is\n * real; this only restores the compile-time table.\n */\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface SlotMap {\n /** One plugin's card inside the plugin configuration section. */\n 'settings.plugin.item': { kind: 'keyed'; scope: 'root'; owner: { children?: never } }\n }\n}\n\n/** Props the renderer binds for this card (unused — the card is self-loading). */\nexport type LanGatewayCardProps = PropsRuntime<'settings.plugin.item'>\n\n/** The wire shape of the `lan-gateway` config section. */\nexport interface LanGatewaySettings {\n enabled?: boolean\n gatewayPort?: number\n dshTargetPort?: number\n lanCidrs?: string[]\n lanPasswordless?: boolean\n cookieMaxAgeDays?: number\n tlsEnabled?: boolean\n tlsMode?: 'self-signed' | 'custom'\n tlsCertPath?: string\n tlsKeyPath?: string\n tlsSelfSignedHosts?: string\n tlsCertMaxAgeDays?: number\n allowInsecurePlaintext?: boolean\n trustedTerminator?: string\n secureCookies?: boolean\n}\n\n/** GET /lan-gateway/config response. */\ninterface RouteState {\n config: LanGatewaySettings\n running: boolean\n port: number\n tls: string\n lastError: string | null\n}\n\n/* ------------------------------------------------------------------ */\n/* Bilingual copy (ModLens-style: two small sets, picked by browser) */\n/* ------------------------------------------------------------------ */\n\ninterface Labels {\n title: string\n description: string\n unsaved: string\n save: string\n saving: string\n discard: string\n reset: string\n overridden: string\n readOnly: string\n saveFailed: string\n loadFailed: string\n emptyMeansClear: string\n running: string\n stopped: string\n tls: string\n lastError: string\n [key: `field.${string}`]: string\n [key: `hint.${string}`]: string\n [key: `opt.${string}`]: string\n}\n\nconst LABELS: Record<'zh' | 'en', Labels> = {\n zh: {\n title: 'LAN 网关',\n description: '远程访问开关、端口、TLS 证书、受信网段等网关设置',\n unsaved: '未保存',\n save: '保存',\n saving: '保存中…',\n discard: '放弃',\n reset: '重置',\n overridden: '已覆盖',\n readOnly: '网关设置当前不可用(读不到配置路由)。',\n saveFailed: '保存未生效,请检查输入后重试。',\n loadFailed: '加载网关配置失败。',\n emptyMeansClear: '留空 = 使用默认',\n running: '运行中',\n stopped: '已停止',\n tls: 'TLS',\n lastError: '上次错误',\n 'field.enabled': '启用网关',\n 'hint.enabled': '启动时监听 0.0.0.0 网关端口',\n 'field.gatewayPort': '网关端口',\n 'hint.gatewayPort': '绑定到 0.0.0.0 的监听端口(默认 3081)',\n 'field.dshTargetPort': 'dsh 目标端口',\n 'hint.dshTargetPort': '留空则自动跟随 dsh web 端口(默认 3080)',\n 'field.lanCidrs': '免密 LAN 网段',\n 'hint.lanCidrs': '逗号分隔的 CIDR,如 10.0.0.0/8, 192.168.0.0/16',\n 'field.lanPasswordless': 'LAN 免登录',\n 'hint.lanPasswordless': 'LAN/回环来源跳过网关登录页,但仍共用同一上游会话(需 dsh ≥ 0.1.2)',\n 'field.allowInsecurePlaintext': '允许明文 HTTP',\n 'hint.allowInsecurePlaintext': '危险:关闭 TLS 或受信终止代理时仍启动监听,密码与会话将以明文传输',\n 'field.trustedTerminator': '受信 TLS 终止代理',\n 'hint.trustedTerminator': '可选:声明前置代理标识,视为加密入口(如 nginx)。留空 = 未声明',\n 'field.secureCookies': '会话 cookie 的 Secure 属性',\n 'hint.secureCookies': '自动 = TLS 或已声明受信终止代理时加 Secure。受信代理只做明文鉴权、浏览器走 http 访问时须设为 false,否则浏览器拒收 Secure cookie,登录会无限弹回登录页',\n 'opt.auto': '自动',\n 'opt.true': '始终 Secure',\n 'opt.false': '不加 Secure(明文浏览器入口)',\n 'field.cookieMaxAgeDays': '会话有效期(天)',\n 'hint.cookieMaxAgeDays': '登录 cookie 的存活天数(默认 7)',\n 'field.tlsEnabled': '启用 TLS(HTTPS)',\n 'hint.tlsEnabled': '以 HTTPS 提供网关服务',\n 'field.tlsMode': '证书来源',\n 'hint.tlsMode': 'self-signed = 自动生成自签名证书;custom = 使用自己的证书',\n 'field.tlsSelfSignedHosts': '自签名证书域名/IP',\n 'hint.tlsSelfSignedHosts': '逗号分隔,写入证书 SAN,如 localhost, 192.168.1.5',\n 'field.tlsCertPath': '证书文件路径(custom)',\n 'hint.tlsCertPath': 'PEM 格式证书(或证书链)的绝对路径',\n 'field.tlsKeyPath': '私钥文件路径(custom)',\n 'hint.tlsKeyPath': '与证书配套的 PEM 私钥绝对路径',\n 'field.tlsCertMaxAgeDays': '自签名证书有效期(天)',\n 'hint.tlsCertMaxAgeDays': '默认 825(约 27 个月)',\n },\n en: {\n title: 'LAN Gateway',\n description: 'Remote-access switch, port, TLS certificate, trusted CIDRs and more',\n unsaved: 'Unsaved',\n save: 'Save',\n saving: 'Saving…',\n discard: 'Discard',\n reset: 'Reset',\n overridden: 'overridden',\n readOnly: 'Gateway settings unavailable (config route unreachable).',\n saveFailed: 'The save did not land — check the inputs and retry.',\n loadFailed: 'Failed to load gateway configuration.',\n emptyMeansClear: 'Empty = default',\n running: 'Running',\n stopped: 'Stopped',\n tls: 'TLS',\n lastError: 'Last error',\n 'field.enabled': 'Enable gateway',\n 'hint.enabled': 'Listen on the gateway port at boot',\n 'field.gatewayPort': 'Gateway port',\n 'hint.gatewayPort': 'Port bound on 0.0.0.0 (default 3081)',\n 'field.dshTargetPort': 'dsh target port',\n 'hint.dshTargetPort': 'Leave empty to follow the dsh web port (default 3080)',\n 'field.lanCidrs': 'Password-free LAN CIDRs',\n 'hint.lanCidrs': 'Comma separated CIDRs, e.g. 10.0.0.0/8, 192.168.0.0/16',\n 'field.lanPasswordless': 'LAN skip login',\n 'hint.lanPasswordless': 'LAN/loopback sources skip the gateway login page but still ride one shared upstream session (needs dsh >= 0.1.2)',\n 'field.allowInsecurePlaintext': 'Allow plaintext HTTP',\n 'hint.allowInsecurePlaintext': 'Dangerous: start the listener even without TLS or a trusted terminator; passwords and sessions travel in clear',\n 'field.trustedTerminator': 'Trusted TLS terminator',\n 'hint.trustedTerminator': 'Optional identifier for a front proxy (e.g. nginx) treated as the encrypted ingress. Empty = none declared',\n 'field.secureCookies': 'Session cookie Secure attribute',\n 'hint.secureCookies': 'Auto = Secure when TLS or a trusted terminator is declared. Set false when the trusted proxy only authenticates over plaintext and browsers reach it over http — otherwise browsers drop the Secure cookie and every login bounces back to the login page',\n 'opt.auto': 'Auto',\n 'opt.true': 'Always Secure',\n 'opt.false': 'No Secure (plaintext browser ingress)',\n 'field.cookieMaxAgeDays': 'Session lifetime (days)',\n 'hint.cookieMaxAgeDays': 'Login cookie lifetime (default 7)',\n 'field.tlsEnabled': 'Enable TLS (HTTPS)',\n 'hint.tlsEnabled': 'Serve the gateway over HTTPS',\n 'field.tlsMode': 'Certificate source',\n 'hint.tlsMode': 'self-signed = auto-generated certificate; custom = your own files',\n 'field.tlsSelfSignedHosts': 'Self-signed hosts (SANs)',\n 'hint.tlsSelfSignedHosts': 'Comma separated DNS/IP names, e.g. localhost, 192.168.1.5',\n 'field.tlsCertPath': 'Certificate path (custom)',\n 'hint.tlsCertPath': 'Absolute path to a PEM certificate (or chain)',\n 'field.tlsKeyPath': 'Private key path (custom)',\n 'hint.tlsKeyPath': 'Absolute path to the matching PEM private key',\n 'field.tlsCertMaxAgeDays': 'Self-signed validity (days)',\n 'hint.tlsCertMaxAgeDays': 'Default 825 (about 27 months)',\n },\n}\n\nfunction labels(): Labels {\n const lang = (typeof navigator !== 'undefined' ? navigator.language : 'en').toLowerCase()\n return lang.startsWith('zh') ? LABELS.zh : LABELS.en\n}\n\n/* ------------------------------------------------------------------ */\n/* Field model */\n/* ------------------------------------------------------------------ */\n\ntype FieldKind = 'boolean' | 'number' | 'text' | 'cidrs' | 'select' | 'tristate'\n\ninterface FieldDef {\n field: keyof LanGatewaySettings\n kind: FieldKind\n optional?: boolean\n options?: readonly string[]\n}\n\n/**\n * The card's field table and its two value codecs are exported for tests: the\n * tri-state codec is the load-bearing part of the settings round-trip (an\n * unset value must stay distinguishable from an explicit false, or the\n * plaintext-proxy escape hatch silently reverts).\n */\nexport { TRISTATE_OPTIONS, FIELDS, formatValue, parseValue }\nexport type { FieldDef, Write }\n\n/** The three states of a tri-state field, in display order. */\nconst TRISTATE_OPTIONS = ['auto', 'true', 'false'] as const\n\nconst FIELDS: readonly FieldDef[] = [\n { field: 'enabled', kind: 'boolean' },\n { field: 'gatewayPort', kind: 'number' },\n { field: 'dshTargetPort', kind: 'number', optional: true },\n { field: 'lanCidrs', kind: 'cidrs' },\n { field: 'lanPasswordless', kind: 'boolean' },\n { field: 'cookieMaxAgeDays', kind: 'number' },\n { field: 'tlsEnabled', kind: 'boolean' },\n { field: 'tlsMode', kind: 'select', options: ['self-signed', 'custom'] },\n { field: 'tlsSelfSignedHosts', kind: 'text' },\n { field: 'tlsCertPath', kind: 'text', optional: true },\n { field: 'tlsKeyPath', kind: 'text', optional: true },\n { field: 'tlsCertMaxAgeDays', kind: 'number' },\n { field: 'allowInsecurePlaintext', kind: 'boolean' },\n { field: 'trustedTerminator', kind: 'text', optional: true },\n { field: 'secureCookies', kind: 'tristate' },\n]\n\nfunction formatValue(def: FieldDef, value: unknown): string {\n switch (def.kind) {\n case 'boolean': return value === true ? 'true' : 'false'\n case 'number': return typeof value === 'number' ? String(value) : ''\n case 'cidrs': return Array.isArray(value) ? value.join(', ') : ''\n case 'select': return typeof value === 'string' ? value : (def.options?.[0] ?? '')\n // Tri-state: an unset value is a distinct third state (\"auto\"), never \"false\".\n case 'tristate': return value === true ? 'true' : value === false ? 'false' : 'auto'\n case 'text': return typeof value === 'string' ? value : ''\n }\n}\n\ntype Write = { kind: 'set'; value: unknown } | { kind: 'clear' }\n\n/** Parse draft text into a value for the POST body; undefined blocks saving. */\nfunction parseValue(def: FieldDef, text: string): Write | undefined {\n const trimmed = text.trim()\n switch (def.kind) {\n case 'boolean':\n if (trimmed === 'true') return { kind: 'set', value: true }\n if (trimmed === 'false') return { kind: 'set', value: false }\n return undefined\n case 'number':\n if (trimmed === '') return def.optional ? { kind: 'clear' } : undefined\n if (!/^\\d+$/.test(trimmed)) return undefined\n return { kind: 'set', value: Number(trimmed) }\n case 'cidrs': {\n const cidrs = trimmed.split(',').map(s => s.trim()).filter(s => s !== '')\n return cidrs.length === 0 ? { kind: 'clear' } : { kind: 'set', value: cidrs }\n }\n case 'select':\n return def.options?.includes(trimmed) ? { kind: 'set', value: trimmed } : undefined\n case 'tristate':\n // 'auto' clears the key so it re-inherits the composition layer (and the\n // resolution rule), which is what an unset tri-state means.\n if (trimmed === 'auto') return { kind: 'clear' }\n if (trimmed === 'true') return { kind: 'set', value: true }\n if (trimmed === 'false') return { kind: 'set', value: false }\n return undefined\n case 'text':\n return trimmed === '' ? (def.optional ? { kind: 'clear' } : undefined) : { kind: 'set', value: trimmed }\n }\n}\n\n/* ------------------------------------------------------------------ */\n/* Card */\n/* ------------------------------------------------------------------ */\n\n/**\n * Render the LAN gateway card. Self-loading: fetches the config route on\n * mount, posts the edited config on save.\n * @param _props - unused; the card needs no injected face.\n * @returns the card, or nothing while the route is unreachable.\n */\nexport function LanGatewayCard(_props: LanGatewayCardProps): ReactNode {\n const t = labels()\n const [open, setOpen] = useState(false)\n const [route, setRoute] = useState<RouteState | null>(null)\n const [loadFailed, setLoadFailed] = useState(false)\n const [drafts, setDrafts] = useState<Partial<Record<string, string>>>({})\n const [saving, setSaving] = useState(false)\n const [failed, setFailed] = useState<string | null>(null)\n\n useEffect(() => {\n let cancelled = false\n fetch('/lan-gateway/config')\n .then(async (response) => {\n if (cancelled) return\n if (!response.ok) throw new Error(`HTTP ${response.status}`)\n setRoute(await response.json() as RouteState)\n })\n .catch(() => {\n if (!cancelled) setLoadFailed(true)\n })\n return () => { cancelled = true }\n }, [])\n\n if (loadFailed) return null\n if (route === null) return null\n\n const { config } = route\n const draftOf = (field: keyof LanGatewaySettings): string =>\n drafts[field] ?? formatValue(FIELDS.find(f => f.field === field)!, config[field])\n\n const stage = (field: string, text: string): void => {\n setDrafts(prev => ({ ...prev, [field]: text }))\n setFailed(null)\n }\n\n const resetField = (def: FieldDef): void => {\n setDrafts(prev => {\n const next = { ...prev }\n delete next[def.field]\n return next\n })\n }\n\n const discard = (): void => {\n setDrafts({})\n setFailed(null)\n }\n\n const invalid = (): boolean =>\n Object.entries(drafts).some(([field, text]) => {\n const def = FIELDS.find(f => f.field === field)\n return def === undefined || parseValue(def, text ?? '') === undefined\n })\n\n const dirty = Object.keys(drafts).length > 0\n\n const save = async (): Promise<void> => {\n if (!dirty || saving || invalid()) return\n setSaving(true)\n setFailed(null)\n try {\n // Build the next full config: the loaded one with drafts applied.\n const next: Record<string, unknown> = {}\n for (const def of FIELDS) {\n const text = drafts[def.field] ?? formatValue(def, config[def.field])\n const write = parseValue(def, text)\n if (write === undefined) continue\n next[def.field] = write.kind === 'clear' ? null : write.value\n }\n const response = await fetch('/lan-gateway/config', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(next),\n })\n const body = await response.json().catch(() => ({})) as Partial<RouteState> & { error?: string }\n if (!response.ok) {\n setFailed(body.error ?? `HTTP ${response.status}`)\n return\n }\n if (body.config !== undefined) {\n setRoute({\n config: body.config,\n running: body.running ?? false,\n port: body.port ?? 0,\n tls: body.tls ?? '',\n lastError: body.lastError ?? null,\n })\n }\n setDrafts({})\n } catch {\n setFailed(t.saveFailed)\n } finally {\n setSaving(false)\n }\n }\n\n const renderControl = (def: FieldDef): ReactNode => {\n const field = def.field\n const label = t[`field.${field}`]\n const hint = t[`hint.${field}`]\n const text = draftOf(field)\n switch (def.kind) {\n case 'boolean':\n return (\n <div style={styles.field}>\n <label style={styles.checkRow}>\n <input\n type=\"checkbox\"\n checked={text === 'true'}\n disabled={saving}\n onChange={(e: ChangeEvent<HTMLInputElement>) =>\n stage(field, e.target.checked ? 'true' : 'false')}\n />\n <span style={styles.label}>{label}</span>\n <button\n type=\"button\"\n style={styles.reset}\n disabled={saving || !drafts[field]}\n onClick={() => resetField(def)}\n >\n {t.reset}\n </button>\n </label>\n <span style={styles.hint}>{hint}</span>\n </div>\n )\n case 'select':\n case 'tristate': {\n // A tri-state renders as a three-way select because neither a checkbox\n // (cannot express \"unset\") nor a text field (cannot express false)\n // distinguishes auto from an explicit false.\n const options = def.kind === 'tristate' ? TRISTATE_OPTIONS : (def.options ?? [])\n return (\n <div style={styles.field}>\n <label style={styles.label} htmlFor={`lan-gw-${field}`}>{label}</label>\n <select\n id={`lan-gw-${field}`}\n style={styles.input}\n value={text}\n disabled={saving}\n onChange={(e: ChangeEvent<HTMLSelectElement>) => stage(field, e.target.value)}\n >\n {options.map(option => (\n <option key={option} value={option}>\n {def.kind === 'tristate' ? t[`opt.${option}`] : option}\n </option>\n ))}\n </select>\n <span style={styles.hint}>{hint}</span>\n <button\n type=\"button\"\n style={styles.reset}\n disabled={saving || !drafts[field]}\n onClick={() => resetField(def)}\n >\n {t.reset}\n </button>\n </div>\n )\n }\n default:\n return (\n <div style={styles.field}>\n <label style={styles.label} htmlFor={`lan-gw-${field}`}>{label}</label>\n <input\n id={`lan-gw-${field}`}\n style={styles.input}\n type={def.kind === 'number' ? 'number' : 'text'}\n value={text}\n disabled={saving}\n placeholder={def.optional ? t.emptyMeansClear : undefined}\n onChange={(e: ChangeEvent<HTMLInputElement>) => stage(field, e.target.value)}\n />\n <span style={styles.hint}>{hint}</span>\n </div>\n )\n }\n }\n\n const statusLine = `${route.running ? t.running : t.stopped} · ${t.tls}: ${route.tls} · :${route.port}`\n\n return (\n <li style={open ? { ...styles.card, ...styles.cardOpen } : styles.card}>\n <button\n type=\"button\"\n style={styles.header}\n aria-expanded={open}\n onClick={() => { setOpen(!open) }}\n >\n <span style={styles.headerTop}>\n <span style={styles.name}>{t.title}</span>\n <span style={styles.status} title={statusLine}>{statusLine}</span>\n {dirty ? <span style={styles.pending}>{t.unsaved}</span> : null}\n <span style={open ? { ...styles.chevron, ...styles.chevronOpen } : styles.chevron}>{open ? '▾' : '▸'}</span>\n </span>\n <span style={styles.description}>{t.description}</span>\n </button>\n {open\n ? (\n <div style={styles.body}>\n {route.lastError ? <p style={styles.error} role=\"status\">{t.lastError}: {route.lastError}</p> : null}\n {FIELDS.map(def => <div key={def.field}>{renderControl(def)}</div>)}\n <div style={styles.footer}>\n {failed ? <p style={styles.error} role=\"status\">{failed}</p> : null}\n <button\n type=\"button\"\n style={styles.discard}\n disabled={!dirty || saving}\n onClick={discard}\n >\n {t.discard}\n </button>\n <button\n type=\"button\"\n style={styles.save}\n disabled={!dirty || invalid() || saving}\n onClick={() => { void save() }}\n >\n {saving ? t.saving : t.save}\n </button>\n </div>\n </div>\n )\n : null}\n </li>\n )\n}\n\n/* ------------------------------------------------------------------ */\n/* Styling — the official DSH theme tokens (light/dark aware), with */\n/* neutral fallbacks so the card never renders black-on-black or */\n/* white-on-white even if a token is missing. */\n/* ------------------------------------------------------------------ */\n\n/** Theme token with a fallback for token-less environments. */\nfunction tk(token: string, fallback: string): string {\n return `var(${token}, ${fallback})`\n}\n\nconst L = {\n border: tk('--dsw-alias-border-l2', 'rgba(127,127,127,0.35)'),\n bg: tk('--dsw-alias-bg-layer-3', 'transparent'),\n bgOpen: tk('--dsw-alias-bg-layer-2', 'transparent'),\n labelPrimary: tk('--dsw-alias-label-primary', 'inherit'),\n labelSecondary: tk('--dsw-alias-label-secondary', 'inherit'),\n labelTertiary: tk('--dsw-alias-label-tertiary', 'rgba(127,127,127,0.8)'),\n labelDimmed: tk('--dsw-alias-label-dimmed', 'rgba(127,127,127,0.6)'),\n error: tk('--dsw-alias-label-error', '#d1242f'),\n brand: tk('--dsw-alias-brand-primary', '#4f6ef7'),\n badgeBg: tk('--dsw-alias-bg-module-platform', 'rgba(127,127,127,0.14)'),\n}\n\nconst styles: Record<string, React.CSSProperties> = {\n card: {\n listStyle: 'none',\n border: `1px solid ${L.border}`,\n borderRadius: '12px',\n background: L.bg,\n transition: 'border-color .16s, background .16s',\n overflow: 'hidden',\n },\n cardOpen: {\n background: L.bgOpen,\n borderColor: L.labelDimmed,\n },\n header: {\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'stretch',\n gap: '6px',\n width: '100%',\n padding: '14px 16px',\n border: 0,\n background: 'none',\n font: 'inherit',\n color: 'inherit',\n textAlign: 'left',\n cursor: 'pointer',\n },\n headerTop: { display: 'flex', alignItems: 'center', gap: '12px', width: '100%' },\n name: { flex: '1 1 auto', minWidth: 0, fontSize: '15px', fontWeight: 600, lineHeight: 1.4, color: L.labelPrimary },\n // The status carries a verbose TLS cert summary; cap it and ellipsize so it\n // can never swallow the row or squeeze the title (the old nowrap alone\n // caused the description to be pushed into a thin wrapping column).\n status: {\n flex: '0 1 auto',\n minWidth: 0,\n maxWidth: '60%',\n fontSize: '11px',\n lineHeight: 1.4,\n color: L.labelTertiary,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n },\n description: {\n display: 'block',\n fontSize: '13px',\n lineHeight: 1.5,\n color: L.labelTertiary,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n },\n pending: {\n flex: 'none',\n borderRadius: '999px',\n padding: '1px 8px',\n fontSize: '11px',\n lineHeight: '17px',\n fontWeight: 500,\n whiteSpace: 'nowrap',\n background: L.badgeBg,\n color: L.labelSecondary,\n },\n chevron: { flex: 'none', color: L.labelTertiary, fontSize: '12px', transition: 'transform .16s' },\n chevronOpen: { transform: 'rotate(180deg)' },\n body: {\n borderTop: `1px solid ${L.border}`,\n margin: '0 16px',\n paddingBottom: '8px',\n display: 'flex',\n flexDirection: 'column',\n },\n field: {\n display: 'flex',\n flexDirection: 'column',\n gap: '6px',\n padding: '12px 0',\n },\n label: { fontSize: '13px', fontWeight: 500, lineHeight: 1.5, color: L.labelPrimary },\n hint: { margin: 0, fontSize: '12px', lineHeight: 1.5, color: L.labelTertiary },\n checkRow: { display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' },\n reset: {\n border: 'none',\n background: 'none',\n padding: 0,\n font: 'inherit',\n fontSize: '12px',\n lineHeight: 1.5,\n color: L.labelSecondary,\n cursor: 'pointer',\n alignSelf: 'flex-start',\n },\n input: {\n height: '34px',\n padding: '0 12px',\n border: `1px solid ${L.border}`,\n borderRadius: '8px',\n background: L.bg,\n font: 'inherit',\n fontSize: '13px',\n lineHeight: 1.5,\n color: L.labelPrimary,\n },\n footer: {\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'flex-end',\n gap: '8px',\n padding: '12px 0 4px',\n borderTop: `1px solid ${L.border}`,\n },\n error: { flex: 1, minWidth: 0, margin: 0, fontSize: '12px', lineHeight: 1.5, color: L.error },\n discard: {\n appearance: 'none',\n border: `1px solid ${L.border}`,\n borderRadius: '8px',\n padding: '5px 14px',\n font: 'inherit',\n fontSize: '13px',\n lineHeight: 1.5,\n background: 'none',\n color: L.labelSecondary,\n cursor: 'pointer',\n },\n save: {\n appearance: 'none',\n border: '1px solid transparent',\n borderRadius: '8px',\n padding: '5px 14px',\n font: 'inherit',\n fontSize: '13px',\n lineHeight: 1.5,\n background: L.labelPrimary,\n color: L.bg,\n cursor: 'pointer',\n },\n}\n","/**\n * @riceawa/dsh-lan-gateway — browser half.\n *\n * Two jobs:\n * 1. Insecure-origin UUID shim: the gateway can serve the GUI over plain HTTP\n * on LAN addresses, where browsers lack `crypto.randomUUID()`. This bundle\n * installs a getRandomValues-backed `randomUUID` on the Crypto prototype at\n * module scope. With TLS enabled the origin is secure and the shim is a\n * no-op.\n * 2. Settings card: registers the LAN gateway card into the official\n * Settings → Plugins page (`settings.plugin.item` slot), editing the\n * `lan-gateway` settings namespace so port, CIDRs, auth, and TLS are\n * adjustable from the GUI.\n */\n\n/** RFC 4122 v4 UUID from crypto.getRandomValues (available on insecure origins). */\nfunction uuidFromRandomValues(): string {\n const bytes = globalThis.crypto.getRandomValues(new Uint8Array(16))\n const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)\n view.setUint8(6, (view.getUint8(6) & 0x0f) | 0x40)\n view.setUint8(8, (view.getUint8(8) & 0x3f) | 0x80)\n const hex = Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('')\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`\n}\n\n/**\n * Install `randomUUID` on the browser Crypto prototype when the platform\n * lacks it. Idempotent; re-checks every call.\n * @returns `true` when the shim was installed by this call.\n */\nexport function installRandomUuidShim(): boolean {\n const cryptoObj = globalThis.crypto\n if (cryptoObj === undefined) return false\n if (typeof cryptoObj.randomUUID === 'function') return false\n if (typeof cryptoObj.getRandomValues !== 'function') return false\n try {\n // Browsers expose crypto.randomUUID through the Crypto prototype; patching\n // the prototype (not the instance) covers `crypto.randomUUID()` everywhere.\n const proto = Object.getPrototypeOf(cryptoObj) as { randomUUID?: unknown } | null\n if (proto !== null && typeof proto.randomUUID !== 'function') {\n Object.defineProperty(proto, 'randomUUID', {\n value: uuidFromRandomValues,\n writable: true,\n configurable: true,\n })\n return true\n }\n // Fallback: direct instance property (sandboxed/odd environments).\n Object.defineProperty(cryptoObj, 'randomUUID', {\n value: uuidFromRandomValues,\n writable: true,\n configurable: true,\n })\n return true\n } catch {\n return false\n }\n}\n\n// Module scope: the shim is live as soon as this bundle is evaluated, before\n// any RPC/session code runs. apply() re-runs it as a belt-and-braces re-check.\ninstallRandomUuidShim()\n\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\nimport { LanGatewayCard } from './lan-gateway-card.tsx'\n\nexport const name = 'dsh-lan-gateway'\n\n/** Only the slots service: the card itself is self-loading (ModLens-style). */\nexport const inject = ['slots']\n\n/**\n * Mount the settings card and the UUID shim.\n * @param ctx - the browser plugin context.\n */\nexport function apply(ctx: ClientContext): void {\n installRandomUuidShim()\n\n // The card rides the official Plugins → Configurable tab. Like ModLens, it\n // registers with no inject face and fetches its own loopback config route,\n // so it has no settings/locale/connection service dependencies.\n //\n // The `settings.plugin.item` slot is keyed BY the settings namespace the\n // card edits (rc.8 contract): the configurable tab only dispatches entries\n // whose `options.key` is both present and served by the Host's settings\n // describe mirror. Registering with `id` alone throws\n // `keyed slot \"settings.plugin.item\" requires options.key` and the card\n // silently disappears from Settings → Plugins.\n //\n // `id`/`order` ride the legacy list-slot shape (older DSH versions\n // dispatched this slot by id): harmless metadata on the keyed slot, and\n // what keeps the card mounting if this plugin ever loads into an older\n // deployment. Spread from a typed constant so the keyed registration type\n // stays exact.\n const legacyListOptions = { id: 'lan-gateway', order: 30 } as const\n ctx.slots.inject('settings.plugin.item', function* () {\n yield ctx.slots.register({\n name: 'settings.plugin.item',\n key: 'lan-gateway',\n ...legacyListOptions,\n }, LanGatewayCard)\n })\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":["useState"],"sources":["../src/config-fields.ts","../src/client/lan-gateway-card.tsx","../src/client/index.ts"],"sourcesContent":["/**\n * The `lan-gateway` configuration field table: which settings keys exist, how\n * each one is rendered, and how a draft string becomes a config value or a\n * clear.\n *\n * The host and the browser card share this module. The host derives the key set\n * its config route accepts and the set an empty value clears from it; the card\n * renders its controls from it. A field therefore cannot be editable on one\n * side and unknown on the other, which is what let the card silently rewrite\n * keys it never showed.\n *\n * Because both halves import it, this module must stay dependency-free and\n * side-effect-free: no schemastery (the host schema remains the validating\n * authority, not this table), no node built-ins, no DOM, no I/O.\n *\n * @module @riceawa/dsh-lan-gateway/config-fields\n */\n\n/**\n * The wire shape of the `lan-gateway` config section, as the Settings card sees\n * it. Deliberately narrower than the host's `Config`: `cookieName` and the\n * retired `authRequired` are not card-editable. The config route applies a\n * patch, so a key absent here is left untouched rather than reset.\n */\nexport interface LanGatewaySettings {\n enabled?: boolean\n gatewayPort?: number\n dshTargetPort?: number\n lanCidrs?: string[]\n lanPasswordless?: boolean\n cookieMaxAgeDays?: number\n tlsEnabled?: boolean\n tlsMode?: 'self-signed' | 'custom'\n tlsCertPath?: string\n tlsKeyPath?: string\n tlsSelfSignedHosts?: string\n tlsCertMaxAgeDays?: number\n allowInsecurePlaintext?: boolean\n trustedTerminator?: string\n secureCookies?: boolean\n}\n\n/** A settings key this table knows how to edit. */\nexport type ConfigFieldKey = keyof LanGatewaySettings\n\n/** How a field is rendered and parsed. */\nexport type FieldKind = 'boolean' | 'number' | 'text' | 'cidrs' | 'select' | 'tristate'\n\nexport interface FieldDef {\n readonly field: ConfigFieldKey\n readonly kind: FieldKind\n /** An empty draft clears the key back to the composition layer. */\n readonly optional?: boolean\n /** The accepted values of a `select` field. */\n readonly options?: readonly string[]\n}\n\n/** The three states of a tri-state field, in display order. */\nexport const TRISTATE_OPTIONS = ['auto', 'true', 'false'] as const\n\n/**\n * The editable settings, in display order. Adding a config key means adding it\n * here (the host whitelist and the card's controls both follow), to the\n * `Config` schema in `index.ts`, and to `listenerKey` when it changes listener\n * behavior.\n */\nexport const FIELDS: readonly FieldDef[] = [\n { field: 'enabled', kind: 'boolean' },\n { field: 'gatewayPort', kind: 'number' },\n { field: 'dshTargetPort', kind: 'number', optional: true },\n { field: 'lanCidrs', kind: 'cidrs' },\n { field: 'lanPasswordless', kind: 'boolean' },\n { field: 'cookieMaxAgeDays', kind: 'number' },\n { field: 'tlsEnabled', kind: 'boolean' },\n { field: 'tlsMode', kind: 'select', options: ['self-signed', 'custom'] },\n { field: 'tlsSelfSignedHosts', kind: 'text' },\n { field: 'tlsCertPath', kind: 'text', optional: true },\n { field: 'tlsKeyPath', kind: 'text', optional: true },\n { field: 'tlsCertMaxAgeDays', kind: 'number' },\n { field: 'allowInsecurePlaintext', kind: 'boolean' },\n { field: 'trustedTerminator', kind: 'text', optional: true },\n { field: 'secureCookies', kind: 'tristate' },\n]\n\n/** Every settings key the config route accepts; anything else is ignored. */\nexport const CONFIG_FIELD_KEYS: ReadonlySet<string> = new Set(FIELDS.map(def => def.field as string))\n\n/** Keys an empty submitted value clears back to the composition layer. */\nexport const OPTIONAL_CONFIG_KEYS: ReadonlySet<string> = new Set(\n FIELDS.filter(def => def.optional === true).map(def => def.field as string),\n)\n\n/** Render a stored value as draft text. */\nexport function formatValue(def: FieldDef, value: unknown): string {\n switch (def.kind) {\n case 'boolean': return value === true ? 'true' : 'false'\n case 'number': return typeof value === 'number' ? String(value) : ''\n case 'cidrs': return Array.isArray(value) ? value.join(', ') : ''\n case 'select': return typeof value === 'string' ? value : (def.options?.[0] ?? '')\n // Tri-state: an unset value is a distinct third state (\"auto\"), never \"false\".\n case 'tristate': return value === true ? 'true' : value === false ? 'false' : 'auto'\n case 'text': return typeof value === 'string' ? value : ''\n }\n}\n\n/** One field's contribution to a config patch. */\nexport type Write = { kind: 'set'; value: unknown } | { kind: 'clear' }\n\n/** Parse draft text into a value for the POST body; undefined blocks saving. */\nexport function parseValue(def: FieldDef, text: string): Write | undefined {\n const trimmed = text.trim()\n switch (def.kind) {\n case 'boolean':\n if (trimmed === 'true') return { kind: 'set', value: true }\n if (trimmed === 'false') return { kind: 'set', value: false }\n return undefined\n case 'number':\n if (trimmed === '') return def.optional ? { kind: 'clear' } : undefined\n if (!/^\\d+$/.test(trimmed)) return undefined\n return { kind: 'set', value: Number(trimmed) }\n case 'cidrs': {\n const cidrs = trimmed.split(',').map(s => s.trim()).filter(s => s !== '')\n return cidrs.length === 0 ? { kind: 'clear' } : { kind: 'set', value: cidrs }\n }\n case 'select':\n return def.options?.includes(trimmed) ? { kind: 'set', value: trimmed } : undefined\n case 'tristate':\n // 'auto' clears the key so it re-inherits the composition layer (and the\n // resolution rule), which is what an unset tri-state means.\n if (trimmed === 'auto') return { kind: 'clear' }\n if (trimmed === 'true') return { kind: 'set', value: true }\n if (trimmed === 'false') return { kind: 'set', value: false }\n return undefined\n case 'text':\n return trimmed === '' ? (def.optional ? { kind: 'clear' } : undefined) : { kind: 'set', value: trimmed }\n }\n}\n","/**\n * The lan-gateway settings card, rendered by the official DSH Plugins page\n * through its `plugins.item` slot.\n *\n * ModLens-style: the card carries NO injected services. It reads and writes\n * the loopback-only `/lan-gateway/config` host route (the browser never sees\n * the settings seam or any secret), so the only platform service it needs is\n * the `slots` service every plugin already has.\n *\n * @module @riceawa/dsh-lan-gateway/client/card\n */\n\nimport { useEffect, useState, type ChangeEvent, type ReactNode } from 'react'\nimport type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\n// Type-only. The Plugins page owns the `plugins.item` contract, and its own\n// doc says a registrant merges that contract with `import type` instead of\n// importing the package at runtime. Taking the contract from its owner is also\n// what turns the next upstream rename of this slot into a compile error here,\n// rather than a card that quietly stops rendering.\nimport type {} from '@deepseek-ai/dsh-client-ui-plugin-manager/client'\nimport {\n FIELDS,\n TRISTATE_OPTIONS,\n formatValue,\n parseValue,\n type FieldDef,\n type LanGatewaySettings,\n} from '../config-fields.ts'\n\n/**\n * Props the renderer binds for this card. The Plugins page asks for either the\n * card's one-liner (`summary`) or the body of its own page (`page`), and draws\n * the page's title, icon, and crumb itself. The card needs no injected face —\n * it fetches its own route.\n */\nexport type LanGatewayCardProps = PropsRuntime<'plugins.item'>\n\n/**\n * The card's field table and value codecs live in `config-fields.ts`, shared\n * with the host: the host's config route decides which submitted keys are\n * editable and which empty value means \"clear\", and a table duplicated here\n * would let the two disagree about a field the card can render but the route\n * would refuse. Re-exported so the existing tests keep their import path.\n */\nexport { FIELDS, TRISTATE_OPTIONS, formatValue, parseValue }\nexport type { FieldDef, LanGatewaySettings }\n\n/** GET /lan-gateway/config response. */\ninterface RouteState {\n config: LanGatewaySettings\n running: boolean\n port: number\n tls: string\n lastError: string | null\n}\n\n/* ------------------------------------------------------------------ */\n/* Bilingual copy (ModLens-style: two small sets, picked by browser) */\n/* ------------------------------------------------------------------ */\n\ninterface Labels {\n title: string\n description: string\n unsaved: string\n save: string\n saving: string\n discard: string\n reset: string\n readOnly: string\n saveFailed: string\n loadFailed: string\n emptyMeansClear: string\n running: string\n stopped: string\n tls: string\n lastError: string\n [key: `field.${string}`]: string\n [key: `hint.${string}`]: string\n [key: `opt.${string}`]: string\n}\n\nconst LABELS: Record<'zh' | 'en', Labels> = {\n zh: {\n title: 'LAN 网关',\n description: '远程访问开关、端口、TLS 证书、受信网段等网关设置',\n unsaved: '未保存',\n save: '保存',\n saving: '保存中…',\n discard: '放弃',\n reset: '重置',\n readOnly: '网关设置只能在宿主机本机打开 dsh web 时修改:配置路由仅监听回环地址,经网关远程访问的浏览器会被拒绝。远程请改用 lan_gateway 工具。',\n saveFailed: '保存未生效,请检查输入后重试。',\n loadFailed: '无法读取网关配置',\n emptyMeansClear: '留空 = 使用默认',\n running: '运行中',\n stopped: '已停止',\n tls: 'TLS',\n lastError: '上次错误',\n 'field.enabled': '启用网关',\n 'hint.enabled': '启动时监听 0.0.0.0 网关端口',\n 'field.gatewayPort': '网关端口',\n 'hint.gatewayPort': '绑定到 0.0.0.0 的监听端口(默认 3081)',\n 'field.dshTargetPort': 'dsh 目标端口',\n 'hint.dshTargetPort': '留空则自动跟随 dsh web 端口(默认 3080)',\n 'field.lanCidrs': '免密 LAN 网段',\n 'hint.lanCidrs': '逗号分隔的 CIDR,如 10.0.0.0/8, 192.168.0.0/16',\n 'field.lanPasswordless': 'LAN 免登录',\n 'hint.lanPasswordless': 'LAN/回环来源跳过网关登录页,但仍共用同一上游会话(需 dsh ≥ 0.1.2)',\n 'field.allowInsecurePlaintext': '允许明文 HTTP',\n 'hint.allowInsecurePlaintext': '危险:关闭 TLS 或受信终止代理时仍启动监听,密码与会话将以明文传输',\n 'field.trustedTerminator': '受信 TLS 终止代理',\n 'hint.trustedTerminator': '可选:声明前置代理标识,视为加密入口(如 nginx)。留空 = 未声明。注意:登录限流以 TCP 源地址为键,代理之后所有浏览器共用一个额度(5 次/分钟)',\n 'field.secureCookies': '会话 cookie 的 Secure 属性',\n 'hint.secureCookies': '自动 = TLS 或已声明受信终止代理时加 Secure。受信代理只做明文鉴权、浏览器走 http 访问时须设为 false,否则浏览器拒收 Secure cookie,登录会无限弹回登录页',\n 'opt.auto': '自动',\n 'opt.true': '始终 Secure',\n 'opt.false': '不加 Secure(明文浏览器入口)',\n 'field.cookieMaxAgeDays': '会话有效期(天)',\n 'hint.cookieMaxAgeDays': '登录 cookie 的存活天数(默认 7)',\n 'field.tlsEnabled': '启用 TLS(HTTPS)',\n 'hint.tlsEnabled': '以 HTTPS 提供网关服务',\n 'field.tlsMode': '证书来源',\n 'hint.tlsMode': 'self-signed = 自动生成自签名证书;custom = 使用自己的证书',\n 'field.tlsSelfSignedHosts': '自签名证书域名/IP',\n 'hint.tlsSelfSignedHosts': '逗号分隔,写入证书 SAN,如 localhost, 192.168.1.5。仅影响下次换发:已有证书沿用至到期,改动不会立刻生效',\n 'field.tlsCertPath': '证书文件路径(custom)',\n 'hint.tlsCertPath': 'PEM 格式证书(或证书链)的绝对路径',\n 'field.tlsKeyPath': '私钥文件路径(custom)',\n 'hint.tlsKeyPath': '与证书配套的 PEM 私钥绝对路径',\n 'field.tlsCertMaxAgeDays': '自签名证书有效期(天)',\n 'hint.tlsCertMaxAgeDays': '默认 825(约 27 个月)。仅影响下次换发:已有证书沿用至到期',\n },\n en: {\n title: 'LAN Gateway',\n description: 'Remote-access switch, port, TLS certificate, trusted CIDRs and more',\n unsaved: 'Unsaved',\n save: 'Save',\n saving: 'Saving…',\n discard: 'Discard',\n reset: 'Reset',\n readOnly: 'Gateway settings can only be changed where dsh web runs locally: the config route listens on loopback only, so a browser reaching dsh through the gateway is refused. Use the lan_gateway tool remotely.',\n saveFailed: 'The save did not land — check the inputs and retry.',\n loadFailed: 'Cannot read the gateway configuration',\n emptyMeansClear: 'Empty = default',\n running: 'Running',\n stopped: 'Stopped',\n tls: 'TLS',\n lastError: 'Last error',\n 'field.enabled': 'Enable gateway',\n 'hint.enabled': 'Listen on the gateway port at boot',\n 'field.gatewayPort': 'Gateway port',\n 'hint.gatewayPort': 'Port bound on 0.0.0.0 (default 3081)',\n 'field.dshTargetPort': 'dsh target port',\n 'hint.dshTargetPort': 'Leave empty to follow the dsh web port (default 3080)',\n 'field.lanCidrs': 'Password-free LAN CIDRs',\n 'hint.lanCidrs': 'Comma separated CIDRs, e.g. 10.0.0.0/8, 192.168.0.0/16',\n 'field.lanPasswordless': 'LAN skip login',\n 'hint.lanPasswordless': 'LAN/loopback sources skip the gateway login page but still ride one shared upstream session (needs dsh >= 0.1.2)',\n 'field.allowInsecurePlaintext': 'Allow plaintext HTTP',\n 'hint.allowInsecurePlaintext': 'Dangerous: start the listener even without TLS or a trusted terminator; passwords and sessions travel in clear',\n 'field.trustedTerminator': 'Trusted TLS terminator',\n 'hint.trustedTerminator': 'Optional identifier for a front proxy (e.g. nginx) treated as the encrypted ingress. Empty = none declared. Note: login rate limiting keys on the TCP source address, so behind a proxy every browser shares one budget (5/min)',\n 'field.secureCookies': 'Session cookie Secure attribute',\n 'hint.secureCookies': 'Auto = Secure when TLS or a trusted terminator is declared. Set false when the trusted proxy only authenticates over plaintext and browsers reach it over http — otherwise browsers drop the Secure cookie and every login bounces back to the login page',\n 'opt.auto': 'Auto',\n 'opt.true': 'Always Secure',\n 'opt.false': 'No Secure (plaintext browser ingress)',\n 'field.cookieMaxAgeDays': 'Session lifetime (days)',\n 'hint.cookieMaxAgeDays': 'Login cookie lifetime (default 7)',\n 'field.tlsEnabled': 'Enable TLS (HTTPS)',\n 'hint.tlsEnabled': 'Serve the gateway over HTTPS',\n 'field.tlsMode': 'Certificate source',\n 'hint.tlsMode': 'self-signed = auto-generated certificate; custom = your own files',\n 'field.tlsSelfSignedHosts': 'Self-signed hosts (SANs)',\n 'hint.tlsSelfSignedHosts': 'Comma separated DNS/IP names, e.g. localhost, 192.168.1.5. Applies to the next issuance only: an existing certificate is reused until it expires',\n 'field.tlsCertPath': 'Certificate path (custom)',\n 'hint.tlsCertPath': 'Absolute path to a PEM certificate (or chain)',\n 'field.tlsKeyPath': 'Private key path (custom)',\n 'hint.tlsKeyPath': 'Absolute path to the matching PEM private key',\n 'field.tlsCertMaxAgeDays': 'Self-signed validity (days)',\n 'hint.tlsCertMaxAgeDays': 'Default 825 (about 27 months). Applies to the next issuance only: an existing certificate is reused until it expires',\n },\n}\n\nfunction labels(): Labels {\n const lang = (typeof navigator !== 'undefined' ? navigator.language : 'en').toLowerCase()\n return lang.startsWith('zh') ? LABELS.zh : LABELS.en\n}\n\n/**\n * The card's title in the browser's language, for the Plugins page's list\n * entry. A thunk so the label follows the page's locale without re-registering.\n * @returns the localized card title.\n */\nexport function cardTitle(): string {\n return labels().title\n}\n\n/* ------------------------------------------------------------------ */\n/* Card */\n/* ------------------------------------------------------------------ */\n\n/**\n * Render the LAN gateway card. Self-loading: fetches the config route on\n * mount, posts the edited config on save.\n *\n * `view` swaps between the card's one-liner and its page body, so the branch\n * sits after the hooks: the Plugins page re-renders one contribution under the\n * other view when the card is opened.\n * @param props - the view the Plugins page is asking for.\n * @returns the one-liner, the card, or nothing while the route is unreachable.\n */\nexport function LanGatewayCard(props: LanGatewayCardProps): ReactNode {\n const t = labels()\n const [open, setOpen] = useState(false)\n const [route, setRoute] = useState<RouteState | null>(null)\n const [loadFailed, setLoadFailed] = useState(false)\n const [drafts, setDrafts] = useState<Partial<Record<string, string>>>({})\n const [saving, setSaving] = useState(false)\n const [failed, setFailed] = useState<string | null>(null)\n\n useEffect(() => {\n let cancelled = false\n fetch('/lan-gateway/config')\n .then(async (response) => {\n if (cancelled) return\n if (!response.ok) throw new Error(`HTTP ${response.status}`)\n setRoute(await response.json() as RouteState)\n })\n .catch(() => {\n if (!cancelled) setLoadFailed(true)\n })\n return () => { cancelled = true }\n }, [])\n\n // The Plugins page lists this plugin as one card and opens its own page on\n // demand: `summary` is the one-liner the list shows, `page` the body. The\n // hooks above run for both views, because the same contribution flips\n // between them.\n if (props.view === 'summary') return t.description\n\n // A remote browser reaches this card through the gateway, which answers 403\n // for the plugin's own prefix by design, so the route is unreachable exactly\n // where a user is most likely to go looking for the setting. Rendering\n // nothing left them with a blank entry and no way to tell a missing card from\n // a broken one; say what is wrong and where the card does work instead.\n if (loadFailed) {\n return (\n <div style={styles.card}>\n <div style={styles.header}>\n <span style={styles.headerTop}>\n <span style={styles.name}>{t.title}</span>\n </span>\n <span style={styles.description}>{t.loadFailed}</span>\n </div>\n <div style={styles.body}>\n <p style={styles.hint}>{t.readOnly}</p>\n </div>\n </div>\n )\n }\n if (route === null) return null\n\n const { config } = route\n const draftOf = (field: keyof LanGatewaySettings): string =>\n drafts[field] ?? formatValue(FIELDS.find(f => f.field === field)!, config[field])\n\n const stage = (field: string, text: string): void => {\n setDrafts(prev => ({ ...prev, [field]: text }))\n setFailed(null)\n }\n\n const resetField = (def: FieldDef): void => {\n setDrafts(prev => {\n const next = { ...prev }\n delete next[def.field]\n return next\n })\n }\n\n const discard = (): void => {\n setDrafts({})\n setFailed(null)\n }\n\n const invalid = (): boolean =>\n Object.entries(drafts).some(([field, text]) => {\n const def = FIELDS.find(f => f.field === field)\n return def === undefined || parseValue(def, text ?? '') === undefined\n })\n\n const dirty = Object.keys(drafts).length > 0\n\n const save = async (): Promise<void> => {\n if (!dirty || saving || invalid()) return\n setSaving(true)\n setFailed(null)\n try {\n // A patch of the edited fields only, never the whole config: the card\n // cannot express every key the section may hold (a custom `cookieName`,\n // say), and posting a synthesized full config made the route treat those\n // keys as submitted — resetting each one to its schema default.\n const patch: Record<string, unknown> = {}\n for (const [field, text] of Object.entries(drafts)) {\n const def = FIELDS.find(f => f.field === field)\n if (def === undefined) continue\n const write = parseValue(def, text ?? '')\n if (write === undefined) continue\n patch[field] = write.kind === 'clear' ? null : write.value\n }\n const response = await fetch('/lan-gateway/config', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(patch),\n })\n const body = await response.json().catch(() => ({})) as Partial<RouteState> & { error?: string }\n if (!response.ok) {\n setFailed(body.error ?? `HTTP ${response.status}`)\n return\n }\n if (body.config !== undefined) {\n setRoute({\n config: body.config,\n running: body.running ?? false,\n port: body.port ?? 0,\n tls: body.tls ?? '',\n lastError: body.lastError ?? null,\n })\n }\n setDrafts({})\n } catch {\n setFailed(t.saveFailed)\n } finally {\n setSaving(false)\n }\n }\n\n const renderControl = (def: FieldDef): ReactNode => {\n const field = def.field\n const label = t[`field.${field}`]\n const hint = t[`hint.${field}`]\n const text = draftOf(field)\n switch (def.kind) {\n case 'boolean':\n return (\n <div style={styles.field}>\n <label style={styles.checkRow}>\n <input\n type=\"checkbox\"\n checked={text === 'true'}\n disabled={saving}\n onChange={(e: ChangeEvent<HTMLInputElement>) =>\n stage(field, e.target.checked ? 'true' : 'false')}\n />\n <span style={styles.label}>{label}</span>\n <button\n type=\"button\"\n style={styles.reset}\n disabled={saving || !drafts[field]}\n onClick={() => resetField(def)}\n >\n {t.reset}\n </button>\n </label>\n <span style={styles.hint}>{hint}</span>\n </div>\n )\n case 'select':\n case 'tristate': {\n // A tri-state renders as a three-way select because neither a checkbox\n // (cannot express \"unset\") nor a text field (cannot express false)\n // distinguishes auto from an explicit false.\n const options = def.kind === 'tristate' ? TRISTATE_OPTIONS : (def.options ?? [])\n return (\n <div style={styles.field}>\n <label style={styles.label} htmlFor={`lan-gw-${field}`}>{label}</label>\n <select\n id={`lan-gw-${field}`}\n style={styles.input}\n value={text}\n disabled={saving}\n onChange={(e: ChangeEvent<HTMLSelectElement>) => stage(field, e.target.value)}\n >\n {options.map(option => (\n <option key={option} value={option}>\n {def.kind === 'tristate' ? t[`opt.${option}`] : option}\n </option>\n ))}\n </select>\n <span style={styles.hint}>{hint}</span>\n <button\n type=\"button\"\n style={styles.reset}\n disabled={saving || !drafts[field]}\n onClick={() => resetField(def)}\n >\n {t.reset}\n </button>\n </div>\n )\n }\n default:\n return (\n <div style={styles.field}>\n <label style={styles.label} htmlFor={`lan-gw-${field}`}>{label}</label>\n <input\n id={`lan-gw-${field}`}\n style={styles.input}\n type={def.kind === 'number' ? 'number' : 'text'}\n value={text}\n disabled={saving}\n placeholder={def.optional ? t.emptyMeansClear : undefined}\n onChange={(e: ChangeEvent<HTMLInputElement>) => stage(field, e.target.value)}\n />\n <span style={styles.hint}>{hint}</span>\n </div>\n )\n }\n }\n\n const statusLine = `${route.running ? t.running : t.stopped} · ${t.tls}: ${route.tls} · :${route.port}`\n\n return (\n <div style={open ? { ...styles.card, ...styles.cardOpen } : styles.card}>\n <button\n type=\"button\"\n style={styles.header}\n aria-expanded={open}\n onClick={() => { setOpen(!open) }}\n >\n <span style={styles.headerTop}>\n <span style={styles.name}>{t.title}</span>\n <span style={styles.status} title={statusLine}>{statusLine}</span>\n {dirty ? <span style={styles.pending}>{t.unsaved}</span> : null}\n <span style={open ? { ...styles.chevron, ...styles.chevronOpen } : styles.chevron}>{open ? '▾' : '▸'}</span>\n </span>\n <span style={styles.description}>{t.description}</span>\n </button>\n {open\n ? (\n <div style={styles.body}>\n {route.lastError ? <p style={styles.error} role=\"status\">{t.lastError}: {route.lastError}</p> : null}\n {FIELDS.map(def => <div key={def.field}>{renderControl(def)}</div>)}\n <div style={styles.footer}>\n {failed ? <p style={styles.error} role=\"status\">{failed}</p> : null}\n <button\n type=\"button\"\n style={styles.discard}\n disabled={!dirty || saving}\n onClick={discard}\n >\n {t.discard}\n </button>\n <button\n type=\"button\"\n style={styles.save}\n disabled={!dirty || invalid() || saving}\n onClick={() => { void save() }}\n >\n {saving ? t.saving : t.save}\n </button>\n </div>\n </div>\n )\n : null}\n </div>\n )\n}\n\n/* ------------------------------------------------------------------ */\n/* Styling — the official DSH theme tokens (light/dark aware), with */\n/* neutral fallbacks so the card never renders black-on-black or */\n/* white-on-white even if a token is missing. */\n/* ------------------------------------------------------------------ */\n\n/** Theme token with a fallback for token-less environments. */\nfunction tk(token: string, fallback: string): string {\n return `var(${token}, ${fallback})`\n}\n\nconst L = {\n border: tk('--dsw-alias-border-l2', 'rgba(127,127,127,0.35)'),\n bg: tk('--dsw-alias-bg-layer-3', 'transparent'),\n bgOpen: tk('--dsw-alias-bg-layer-2', 'transparent'),\n labelPrimary: tk('--dsw-alias-label-primary', 'inherit'),\n labelSecondary: tk('--dsw-alias-label-secondary', 'inherit'),\n labelTertiary: tk('--dsw-alias-label-tertiary', 'rgba(127,127,127,0.8)'),\n labelDimmed: tk('--dsw-alias-label-dimmed', 'rgba(127,127,127,0.6)'),\n error: tk('--dsw-alias-label-error', '#d1242f'),\n brand: tk('--dsw-alias-brand-primary', '#4f6ef7'),\n badgeBg: tk('--dsw-alias-bg-module-platform', 'rgba(127,127,127,0.14)'),\n}\n\nconst styles: Record<string, React.CSSProperties> = {\n card: {\n listStyle: 'none',\n border: `1px solid ${L.border}`,\n borderRadius: '12px',\n background: L.bg,\n transition: 'border-color .16s, background .16s',\n overflow: 'hidden',\n },\n cardOpen: {\n background: L.bgOpen,\n borderColor: L.labelDimmed,\n },\n header: {\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'stretch',\n gap: '6px',\n width: '100%',\n padding: '14px 16px',\n border: 0,\n background: 'none',\n font: 'inherit',\n color: 'inherit',\n textAlign: 'left',\n cursor: 'pointer',\n },\n headerTop: { display: 'flex', alignItems: 'center', gap: '12px', width: '100%' },\n name: { flex: '1 1 auto', minWidth: 0, fontSize: '15px', fontWeight: 600, lineHeight: 1.4, color: L.labelPrimary },\n // The status carries a verbose TLS cert summary; cap it and ellipsize so it\n // can never swallow the row or squeeze the title (the old nowrap alone\n // caused the description to be pushed into a thin wrapping column).\n status: {\n flex: '0 1 auto',\n minWidth: 0,\n maxWidth: '60%',\n fontSize: '11px',\n lineHeight: 1.4,\n color: L.labelTertiary,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n },\n description: {\n display: 'block',\n fontSize: '13px',\n lineHeight: 1.5,\n color: L.labelTertiary,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n },\n pending: {\n flex: 'none',\n borderRadius: '999px',\n padding: '1px 8px',\n fontSize: '11px',\n lineHeight: '17px',\n fontWeight: 500,\n whiteSpace: 'nowrap',\n background: L.badgeBg,\n color: L.labelSecondary,\n },\n chevron: { flex: 'none', color: L.labelTertiary, fontSize: '12px', transition: 'transform .16s' },\n chevronOpen: { transform: 'rotate(180deg)' },\n body: {\n borderTop: `1px solid ${L.border}`,\n margin: '0 16px',\n paddingBottom: '8px',\n display: 'flex',\n flexDirection: 'column',\n },\n field: {\n display: 'flex',\n flexDirection: 'column',\n gap: '6px',\n padding: '12px 0',\n },\n label: { fontSize: '13px', fontWeight: 500, lineHeight: 1.5, color: L.labelPrimary },\n hint: { margin: 0, fontSize: '12px', lineHeight: 1.5, color: L.labelTertiary },\n checkRow: { display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' },\n reset: {\n border: 'none',\n background: 'none',\n padding: 0,\n font: 'inherit',\n fontSize: '12px',\n lineHeight: 1.5,\n color: L.labelSecondary,\n cursor: 'pointer',\n alignSelf: 'flex-start',\n },\n input: {\n height: '34px',\n padding: '0 12px',\n border: `1px solid ${L.border}`,\n borderRadius: '8px',\n background: L.bg,\n font: 'inherit',\n fontSize: '13px',\n lineHeight: 1.5,\n color: L.labelPrimary,\n },\n footer: {\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'flex-end',\n gap: '8px',\n padding: '12px 0 4px',\n borderTop: `1px solid ${L.border}`,\n },\n error: { flex: 1, minWidth: 0, margin: 0, fontSize: '12px', lineHeight: 1.5, color: L.error },\n discard: {\n appearance: 'none',\n border: `1px solid ${L.border}`,\n borderRadius: '8px',\n padding: '5px 14px',\n font: 'inherit',\n fontSize: '13px',\n lineHeight: 1.5,\n background: 'none',\n color: L.labelSecondary,\n cursor: 'pointer',\n },\n save: {\n appearance: 'none',\n border: '1px solid transparent',\n borderRadius: '8px',\n padding: '5px 14px',\n font: 'inherit',\n fontSize: '13px',\n lineHeight: 1.5,\n background: L.labelPrimary,\n color: L.bg,\n cursor: 'pointer',\n },\n}\n","/**\n * @riceawa/dsh-lan-gateway — browser half.\n *\n * Two jobs:\n * 1. Insecure-origin UUID shim: the gateway can serve the GUI over plain HTTP\n * on LAN addresses, where browsers lack `crypto.randomUUID()`. This bundle\n * installs a getRandomValues-backed `randomUUID` on the Crypto prototype at\n * module scope. With TLS enabled the origin is secure and the shim is a\n * no-op.\n * 2. Settings card: registers the LAN gateway card into the official Plugins\n * page (`plugins.item` slot) so port, CIDRs, auth, and TLS stay adjustable\n * from the GUI.\n */\n\n/** RFC 4122 v4 UUID from crypto.getRandomValues (available on insecure origins). */\nfunction uuidFromRandomValues(): string {\n const bytes = globalThis.crypto.getRandomValues(new Uint8Array(16))\n const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)\n view.setUint8(6, (view.getUint8(6) & 0x0f) | 0x40)\n view.setUint8(8, (view.getUint8(8) & 0x3f) | 0x80)\n const hex = Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('')\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`\n}\n\n/**\n * Install `randomUUID` on the browser Crypto prototype when the platform\n * lacks it. Idempotent; re-checks every call.\n * @returns `true` when the shim was installed by this call.\n */\nexport function installRandomUuidShim(): boolean {\n const cryptoObj = globalThis.crypto\n if (cryptoObj === undefined) return false\n if (typeof cryptoObj.randomUUID === 'function') return false\n if (typeof cryptoObj.getRandomValues !== 'function') return false\n try {\n // Browsers expose crypto.randomUUID through the Crypto prototype; patching\n // the prototype (not the instance) covers `crypto.randomUUID()` everywhere.\n const proto = Object.getPrototypeOf(cryptoObj) as { randomUUID?: unknown } | null\n if (proto !== null && typeof proto.randomUUID !== 'function') {\n Object.defineProperty(proto, 'randomUUID', {\n value: uuidFromRandomValues,\n writable: true,\n configurable: true,\n })\n return true\n }\n // Fallback: direct instance property (sandboxed/odd environments).\n Object.defineProperty(cryptoObj, 'randomUUID', {\n value: uuidFromRandomValues,\n writable: true,\n configurable: true,\n })\n return true\n } catch {\n return false\n }\n}\n\n// Module scope: the shim is live as soon as this bundle is evaluated, before\n// any RPC/session code runs. apply() re-runs it as a belt-and-braces re-check.\ninstallRandomUuidShim()\n\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\n// Type-only, and never bundled: the Plugins page's slot contract (`plugins.item`)\n// and the settings domain's `configForms` service both resolve from the web\n// shell's frozen module table. Importing them is what subjects the registration\n// below to the platform's own contract instead of a local copy that drifts the\n// next time upstream renames a slot.\nimport type {} from '@deepseek-ai/dsh-client-ui-plugin-manager/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport { LanGatewayCard, cardTitle } from './lan-gateway-card.tsx'\n\nexport const name = 'dsh-lan-gateway'\n\n/**\n * The profile entry id this plugin's bundle patch composes it under, and the\n * settings namespace dsh ≥ 0.1.7 addresses every write by.\n */\nconst ENTRY_ID = 'dsh-lan-gateway'\n\n/** The slots service the card rides, and the settings mirror the gate reads. */\nexport const inject = ['slots', 'configForms']\n\n/**\n * Mount the settings card and the UUID shim.\n * @param ctx - the browser plugin context.\n */\nexport function apply(ctx: ClientContext): void {\n installRandomUuidShim()\n\n // The card rides the official Plugins page. Like ModLens it registers with no\n // inject face and fetches its own loopback config route, so it depends on no\n // settings, locale, or connection service.\n //\n // dsh 0.1.7 replaced the namespace-keyed `settings.plugin.item` slot with the\n // list slot `plugins.item`, which is where a host-plane plugin's own\n // configuration page belongs (\"one companion package per host-plane\n // namespace\"); the page renders the contribution as the card's one-liner and,\n // once opened, as the body of the plugin's own page. Registering into the\n // retired slot left the card invisible on 0.1.7.\n //\n // `whileServed` keeps the entry off the page until the Host's settings mirror\n // serves this plugin's entry. That is the one gate worth having: without a\n // Loader entry there is nothing to write to, and the card would appear only\n // to fail every save with the route's 409.\n ctx.effect(() => ctx.configForms.whileServed([ENTRY_ID], () =>\n ctx.slots.inject('plugins.item', () => ctx.slots.register({\n name: 'plugins.item',\n id: ENTRY_ID,\n order: 30,\n label: () => cardTitle(),\n }, LanGatewayCard))))\n}\n"],"mappings":"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"}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import z from "@deepseek-ai/schemastery";
|
|
2
|
-
import { IncomingMessage, ServerResponse } from "node:http";
|
|
3
2
|
import { Context } from "@deepseek-ai/cordis";
|
|
3
|
+
import { IncomingMessage, ServerResponse } from "http";
|
|
4
4
|
//#region src/index.d.ts
|
|
5
5
|
/** Stable Cordis plugin name. */
|
|
6
6
|
declare const name = "dsh-lan-gateway";
|
|
@@ -60,12 +60,17 @@ interface Config {
|
|
|
60
60
|
/**
|
|
61
61
|
* Removed capability: authentication is always required. Retained only so an
|
|
62
62
|
* explicit legacy `authRequired: false` is rejected loudly instead of
|
|
63
|
-
* silently ignored.
|
|
63
|
+
* silently ignored. Not card-editable and never written back to the user
|
|
64
|
+
* section — the config route builds its patch from the editable key set.
|
|
64
65
|
*/
|
|
65
66
|
authRequired?: boolean;
|
|
66
67
|
/** Session cookie lifetime in days. */
|
|
67
68
|
cookieMaxAgeDays: number;
|
|
68
|
-
/**
|
|
69
|
+
/**
|
|
70
|
+
* Cookie name. Deliberately not card-editable — see the editable key set in
|
|
71
|
+
* `config-fields.ts`; the config route applies a patch, so an operator's
|
|
72
|
+
* custom name survives every save from the Settings card.
|
|
73
|
+
*/
|
|
69
74
|
cookieName: string;
|
|
70
75
|
/** Whether the gateway listener speaks TLS. */
|
|
71
76
|
tlsEnabled: boolean;
|
|
@@ -75,7 +80,13 @@ interface Config {
|
|
|
75
80
|
tlsCertPath?: string;
|
|
76
81
|
/** Custom mode: path to the PEM private key. */
|
|
77
82
|
tlsKeyPath?: string;
|
|
78
|
-
/**
|
|
83
|
+
/**
|
|
84
|
+
* Self-signed mode: comma/space separated DNS names and IPs for the SANs.
|
|
85
|
+
*
|
|
86
|
+
* Read when a certificate is generated, not when it is served: changing it
|
|
87
|
+
* does not replace a certificate that already exists and is still valid. Use
|
|
88
|
+
* `lan_gateway tls-regenerate` for that.
|
|
89
|
+
*/
|
|
79
90
|
tlsSelfSignedHosts?: string;
|
|
80
91
|
/**
|
|
81
92
|
* Self-signed certificate validity in days (default 825 ≈ 27 months).
|
|
@@ -88,6 +99,8 @@ interface Config {
|
|
|
88
99
|
* self-signed certificate is either clicked through or trusted by hand,
|
|
89
100
|
* there is nothing to gain from the shorter window and a re-trust to lose
|
|
90
101
|
* every time it lapses.
|
|
102
|
+
*
|
|
103
|
+
* Like `tlsSelfSignedHosts`, this applies to the next generation only.
|
|
91
104
|
*/
|
|
92
105
|
tlsCertMaxAgeDays: number;
|
|
93
106
|
/**
|
|
@@ -99,6 +112,11 @@ interface Config {
|
|
|
99
112
|
* An identifier for a trusted TLS-terminating proxy in front of the gateway.
|
|
100
113
|
* Declaring one marks the ingress encrypted (Secure cookies, passes the
|
|
101
114
|
* encrypted-ingress gate) without this listener sending HSTS.
|
|
115
|
+
*
|
|
116
|
+
* Note the coupling with login rate limiting: the limiter is keyed by
|
|
117
|
+
* `socket.remoteAddress`, and `X-Forwarded-For` is deliberately untrusted, so
|
|
118
|
+
* behind such a proxy every browser shares one bucket — the login budget
|
|
119
|
+
* becomes per-deployment, not per-client.
|
|
102
120
|
*/
|
|
103
121
|
trustedTerminator?: string;
|
|
104
122
|
/**
|
|
@@ -110,8 +128,90 @@ interface Config {
|
|
|
110
128
|
*/
|
|
111
129
|
secureCookies?: boolean;
|
|
112
130
|
}
|
|
113
|
-
/**
|
|
114
|
-
|
|
131
|
+
/** A `.volatile()` config field as the Loader hands it to `apply`. */
|
|
132
|
+
interface ConfigRef<T> {
|
|
133
|
+
/** The current value; updated in place when the profile entry is written. */
|
|
134
|
+
get(): T;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* The config object `apply` receives: dsh 0.1.7 hands every `.volatile()` field
|
|
138
|
+
* over as a reference rather than a value, so the plugin reads its live config
|
|
139
|
+
* through `readConfig`. A field declared optional and left unset resolves to
|
|
140
|
+
* `undefined` instead of a reference, hence the union inside `ConfigRef`.
|
|
141
|
+
*/
|
|
142
|
+
type ConfigRefs = { [K in keyof Required<Config>]: ConfigRef<Config[K]>; };
|
|
143
|
+
/**
|
|
144
|
+
* Schemastery configuration validated by the Loader.
|
|
145
|
+
*
|
|
146
|
+
* Every field is `.volatile()`, which is what lets the Settings service write
|
|
147
|
+
* it: 0.1.7 projects only volatile fields into forms and refuses an edit to any
|
|
148
|
+
* other path (`not volatile`). The mark also changes the runtime shape — a
|
|
149
|
+
* volatile field arrives as a reference (see `ConfigRefs`), never as the plain
|
|
150
|
+
* value the rest of this file expects — so read it through `readConfig`.
|
|
151
|
+
*/
|
|
152
|
+
declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
153
|
+
enabled: z<boolean, boolean, "volatile-defined">;
|
|
154
|
+
gatewayPort: z<number, number, "volatile-defined">;
|
|
155
|
+
dshTargetPort: z<number, number, "volatile">;
|
|
156
|
+
lanCidrs: z<NoInfer<string[]>, NoInfer<string[]>, "volatile-defined">;
|
|
157
|
+
lanPasswordless: z<boolean, boolean, "volatile-defined">;
|
|
158
|
+
authRequired: z<boolean, boolean, "volatile-defined">;
|
|
159
|
+
cookieMaxAgeDays: z<number, number, "volatile-defined">;
|
|
160
|
+
cookieName: z<string, string, "volatile-defined">;
|
|
161
|
+
tlsEnabled: z<boolean, boolean, "volatile-defined">;
|
|
162
|
+
tlsMode: z<"self-signed" | "custom", "self-signed" | "custom", "volatile-defined">;
|
|
163
|
+
tlsCertPath: z<string, string, "volatile">;
|
|
164
|
+
tlsKeyPath: z<string, string, "volatile">;
|
|
165
|
+
tlsSelfSignedHosts: z<string, string, "volatile-defined">;
|
|
166
|
+
tlsCertMaxAgeDays: z<number, number, "volatile-defined">;
|
|
167
|
+
allowInsecurePlaintext: z<boolean, boolean, "volatile-defined">;
|
|
168
|
+
trustedTerminator: z<string, string, "volatile">;
|
|
169
|
+
secureCookies: z<boolean, boolean, "volatile">;
|
|
170
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
171
|
+
enabled: z<boolean, boolean, "volatile-defined">;
|
|
172
|
+
gatewayPort: z<number, number, "volatile-defined">;
|
|
173
|
+
dshTargetPort: z<number, number, "volatile">;
|
|
174
|
+
lanCidrs: z<NoInfer<string[]>, NoInfer<string[]>, "volatile-defined">;
|
|
175
|
+
lanPasswordless: z<boolean, boolean, "volatile-defined">;
|
|
176
|
+
authRequired: z<boolean, boolean, "volatile-defined">;
|
|
177
|
+
cookieMaxAgeDays: z<number, number, "volatile-defined">;
|
|
178
|
+
cookieName: z<string, string, "volatile-defined">;
|
|
179
|
+
tlsEnabled: z<boolean, boolean, "volatile-defined">;
|
|
180
|
+
tlsMode: z<"self-signed" | "custom", "self-signed" | "custom", "volatile-defined">;
|
|
181
|
+
tlsCertPath: z<string, string, "volatile">;
|
|
182
|
+
tlsKeyPath: z<string, string, "volatile">;
|
|
183
|
+
tlsSelfSignedHosts: z<string, string, "volatile-defined">;
|
|
184
|
+
tlsCertMaxAgeDays: z<number, number, "volatile-defined">;
|
|
185
|
+
allowInsecurePlaintext: z<boolean, boolean, "volatile-defined">;
|
|
186
|
+
trustedTerminator: z<string, string, "volatile">;
|
|
187
|
+
secureCookies: z<boolean, boolean, "volatile">;
|
|
188
|
+
}>>, "plain">;
|
|
189
|
+
/**
|
|
190
|
+
* Unwrap the config references into the plain values every other function in
|
|
191
|
+
* this file reads. Called on each access rather than once, because a settings
|
|
192
|
+
* write updates the references in place.
|
|
193
|
+
* @param refs - the config object handed to `apply`.
|
|
194
|
+
* @returns one detached plain snapshot.
|
|
195
|
+
*/
|
|
196
|
+
declare function readConfig(refs: ConfigRefs): Config;
|
|
197
|
+
/**
|
|
198
|
+
* Build the reference-shaped config `apply` receives, exactly as the Loader
|
|
199
|
+
* builds it. Exported for tests that drive `apply` directly.
|
|
200
|
+
* @param raw - a config object; missing fields take their schema defaults.
|
|
201
|
+
* @returns one reference per volatile field.
|
|
202
|
+
*/
|
|
203
|
+
declare function configRefs(raw: object): ConfigRefs;
|
|
204
|
+
/**
|
|
205
|
+
* Validate a raw config object the way the Loader does, and unwrap it.
|
|
206
|
+
*
|
|
207
|
+
* `Config` marks every field volatile, so a validation hands the values back as
|
|
208
|
+
* references (typed deeply-readonly by schemastery); this returns the plain
|
|
209
|
+
* shape the rest of the file reads. Used to judge a config the Settings card is
|
|
210
|
+
* about to save, before it is persisted.
|
|
211
|
+
* @param raw - a config object; missing fields take their schema defaults.
|
|
212
|
+
* @returns the validated plain config.
|
|
213
|
+
*/
|
|
214
|
+
declare function validateConfig(raw: object): Config;
|
|
115
215
|
/** Facts the fail-closed start guard needs to judge a config. */
|
|
116
216
|
interface StartFacts {
|
|
117
217
|
/** Whether the dsh base enforces browser-session auth (auto-detected). */
|
|
@@ -144,6 +244,27 @@ declare function resolveSecureCookies(cfg: Pick<Config, 'secureCookies' | 'tlsEn
|
|
|
144
244
|
* used, and a state-changing method must carry that Origin. Exported for tests.
|
|
145
245
|
*/
|
|
146
246
|
declare function isTrustedConfigRequest(req: IncomingMessage): boolean;
|
|
147
|
-
|
|
247
|
+
/**
|
|
248
|
+
* Turn a submitted config patch into the next user section: only keys the card
|
|
249
|
+
* can edit, only real values, and `null` (or an emptied optional) removes the
|
|
250
|
+
* key rather than storing it.
|
|
251
|
+
*
|
|
252
|
+
* The patch is built from the *submitted* object, never from a schema call's
|
|
253
|
+
* output. Schemastery fills defaults into whatever it validates and passes
|
|
254
|
+
* unknown keys through, so deriving the section from `Config(submitted)` wrote
|
|
255
|
+
* `authRequired: true` (a capability that exists only to be refused) and any
|
|
256
|
+
* stray key into the user's settings on every save — and, because it also
|
|
257
|
+
* materialized `cookieName`, reset an operator's custom cookie name to the
|
|
258
|
+
* schema default.
|
|
259
|
+
*
|
|
260
|
+
* A `null` value is the card's clear: the key is dropped from the patch, which
|
|
261
|
+
* leaves it absent from the section, so it re-inherits the composition layer.
|
|
262
|
+
*/
|
|
263
|
+
declare function buildConfigPatch(submitted: Record<string, unknown>): {
|
|
264
|
+
patch: Record<string, unknown>;
|
|
265
|
+
clear: string[];
|
|
266
|
+
unknown: string[];
|
|
267
|
+
};
|
|
268
|
+
declare function apply(ctx: Context, config: ConfigRefs): void;
|
|
148
269
|
//#endregion
|
|
149
|
-
export { Config, GatewayController, StartFacts, ToolResult, UpstreamConnectionSurface, WebServerSurface, apply, gatewayStartProblems, inject, isTrustedConfigRequest, name, resolveSecureCookies };
|
|
270
|
+
export { Config, ConfigRef, ConfigRefs, GatewayController, StartFacts, ToolResult, UpstreamConnectionSurface, WebServerSurface, apply, buildConfigPatch, configRefs, gatewayStartProblems, inject, isTrustedConfigRequest, name, readConfig, resolveSecureCookies, validateConfig };
|