@riceawa/dsh-lan-gateway 0.3.0 → 0.5.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/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 * list slot (kind list, root scope, empty owner share) in its own package.\n * The published package ships no `src/`, so the entry is re-declared here —\n * the runtime slot is 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: 'list'; 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 authRequired?: 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}\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}\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.authRequired': '非 LAN 访问需要密码',\n 'hint.authRequired': '公网来源必须登录后才能访问',\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.authRequired': 'Password required for non-LAN',\n 'hint.authRequired': 'Internet sources must sign in before reaching the GUI',\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'\n\ninterface FieldDef {\n field: keyof LanGatewaySettings\n kind: FieldKind\n optional?: boolean\n options?: readonly string[]\n}\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: 'authRequired', 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]\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 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 '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 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 {def.options?.map(option => <option key={option} value={option}>{option}</option>)}\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 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.headText}>\n <span style={styles.name}>{t.title}</span>\n <span style={styles.description}>{t.description}</span>\n </span>\n <span style={styles.status}>{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 </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 alignItems: 'center',\n gap: '12px',\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 headText: { display: 'flex', flexDirection: 'column', gap: '4px', flex: 1, minWidth: 0 },\n name: { fontSize: '15px', fontWeight: 600, lineHeight: 1.4, color: L.labelPrimary },\n description: { fontSize: '13px', lineHeight: 1.5, color: L.labelTertiary },\n status: { fontSize: '11px', color: L.labelTertiary, whiteSpace: 'nowrap' },\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 ctx.slots.inject('settings.plugin.item', function* () {\n yield ctx.slots.register({\n name: 'settings.plugin.item',\n id: 'lan-gateway',\n order: 30,\n }, LanGatewayCard)\n })\n}\n"],"mappings":"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"}
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}\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}\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.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.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'\n\ninterface FieldDef {\n field: keyof LanGatewaySettings\n kind: FieldKind\n optional?: boolean\n options?: readonly string[]\n}\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]\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 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 '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 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 {def.options?.map(option => <option key={option} value={option}>{option}</option>)}\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 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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"}
package/lib/index.d.ts CHANGED
@@ -16,9 +16,15 @@ interface WebServerSurface {
16
16
  handler: (req: IncomingMessage, res: ServerResponse) => void | Promise<void>;
17
17
  }): () => void;
18
18
  }
19
+ /** Minimal surface of the dsh client-connection service (session-capable bases). */
20
+ interface UpstreamConnectionSurface {
21
+ /** A root URL for the upstream origin carrying the process launch token. */
22
+ authenticatedUrl(baseUrl: string): string;
23
+ }
19
24
  declare module '@deepseek-ai/cordis' {
20
25
  interface Context {
21
26
  webServer: WebServerSurface;
27
+ connection: UpstreamConnectionSurface;
22
28
  }
23
29
  }
24
30
  /** One command result returned to the model. */
@@ -31,7 +37,7 @@ interface GatewayController {
31
37
  status(): ToolResult;
32
38
  enable(): Promise<ToolResult>;
33
39
  disable(): Promise<ToolResult>;
34
- setPassword(password: string | undefined): ToolResult;
40
+ setPassword(password: string | undefined): Promise<ToolResult>;
35
41
  rotateSecret(): ToolResult;
36
42
  regenerateTls(): Promise<ToolResult>;
37
43
  }
@@ -43,10 +49,20 @@ interface Config {
43
49
  gatewayPort: number;
44
50
  /** Explicit dsh target port; defaults to the live `ctx.webServer.port`. */
45
51
  dshTargetPort?: number;
46
- /** LAN CIDRs treated as password-free. */
52
+ /** LAN CIDRs that may be treated as trusted when `lanPasswordless` is on. */
47
53
  lanCidrs: string[];
48
- /** Whether non-LAN sources must authenticate. */
49
- authRequired: boolean;
54
+ /**
55
+ * Opt-in (default false): let LAN/loopback sources skip the gateway login.
56
+ * Only allowed against a session-capable dsh base, where upstream auth still
57
+ * gates every request via the relayed shared session.
58
+ */
59
+ lanPasswordless: boolean;
60
+ /**
61
+ * Removed capability: authentication is always required. Retained only so an
62
+ * explicit legacy `authRequired: false` is rejected loudly instead of
63
+ * silently ignored.
64
+ */
65
+ authRequired?: boolean;
50
66
  /** Session cookie lifetime in days. */
51
67
  cookieMaxAgeDays: number;
52
68
  /** Cookie name. */
@@ -63,9 +79,40 @@ interface Config {
63
79
  tlsSelfSignedHosts?: string;
64
80
  /** Self-signed certificate validity in days (default 825 ≈ 27 months). */
65
81
  tlsCertMaxAgeDays: number;
82
+ /**
83
+ * Escape hatch (default false): permit plaintext HTTP. Never derived from
84
+ * `X-Forwarded-Proto` — the operator declares it.
85
+ */
86
+ allowInsecurePlaintext: boolean;
87
+ /**
88
+ * An identifier for a trusted TLS-terminating proxy in front of the gateway.
89
+ * Declaring one marks the ingress encrypted (Secure cookies, passes the
90
+ * encrypted-ingress gate) without this listener sending HSTS.
91
+ */
92
+ trustedTerminator?: string;
66
93
  }
67
94
  /** Schemastery configuration validated by the Loader. */
68
95
  declare const Config: z<Config>;
96
+ /** Facts the fail-closed start guard needs to judge a config. */
97
+ interface StartFacts {
98
+ /** Whether the dsh base enforces browser-session auth (auto-detected). */
99
+ upstreamSessionAvailable: boolean;
100
+ }
101
+ /**
102
+ * The fail-closed problems that prevent a config from enabling the listener.
103
+ * Returns every problem (not just the first) so the operator sees the full
104
+ * migration at once. Exported for tests.
105
+ */
106
+ declare function gatewayStartProblems(cfg: Config, facts: StartFacts): string[];
107
+ /**
108
+ * Same-origin loopback fence for the native `/lan-gateway/config` route. The
109
+ * gateway refuses to relay this prefix, so the only way in is the native
110
+ * loopback listener itself (a genuine local user, or a local process that could
111
+ * already read `~/.dsh`). Host must be loopback (also blocks DNS rebinding),
112
+ * cross-site fetches are refused, an Origin must match the Host the browser
113
+ * used, and a state-changing method must carry that Origin. Exported for tests.
114
+ */
115
+ declare function isTrustedConfigRequest(req: IncomingMessage): boolean;
69
116
  declare function apply(ctx: Context, config: Config): void;
70
117
  //#endregion
71
- export { Config, GatewayController, ToolResult, WebServerSurface, apply, inject, name };
118
+ export { Config, GatewayController, StartFacts, ToolResult, UpstreamConnectionSurface, WebServerSurface, apply, gatewayStartProblems, inject, isTrustedConfigRequest, name };