@dshtrading/client-ui-updater 0.6.1 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -130,7 +130,7 @@ window.__ModuleLoader__.load({
130
130
  }
131
131
  //#endregion
132
132
  //#region \0dsh-css:/home/runner/work/dsh-trading/dsh-trading/packages/client-ui-updater/src/client/updater-panel.module.css.mjs
133
- const css$1 = ".SgPada_root{--upd-text-primary:var(--dsw-alias-text-primary,#1a1e24);--upd-text-secondary:var(--dsw-alias-text-secondary,#5f6672);--upd-text-muted:var(--dsw-alias-text-muted,#8e95a3);--upd-border:var(--dsw-alias-border-l2,#e3e6ea);--upd-bg-hover:var(--dsw-alias-fill-hover,#0000000a);--upd-bg-active:var(--dsw-alias-primary-bg,#2563eb0f);--upd-primary:var(--dsw-alias-primary,#2563eb);--upd-ok:#16a34a;--upd-danger:#dc2626;color:var(--upd-text-primary);flex-direction:column;gap:16px;padding:0;font-size:13px;display:flex}body[data-ds-dark-theme] .SgPada_root,[data-theme=dark] .SgPada_root,:root[data-theme=dark] .SgPada_root,html.dark .SgPada_root,body.dark .SgPada_root{--upd-text-primary:var(--dsw-alias-text-primary,#f3f4f6);--upd-text-secondary:var(--dsw-alias-text-secondary,#9ca3af);--upd-text-muted:var(--dsw-alias-text-muted,#6b7280);--upd-border:var(--dsw-alias-border-l2,#ffffff1a);--upd-bg-hover:var(--dsw-alias-fill-hover,#ffffff0f);--upd-bg-active:#2563eb29;--upd-primary:#3b82f6;--upd-ok:#4ade80;--upd-danger:#f87171}.SgPada_lead{color:var(--upd-text-secondary);margin:0;font-size:13.5px;line-height:1.6}.SgPada_versions{border:1px solid var(--upd-border);background:var(--upd-bg-hover);border-radius:8px;flex-wrap:wrap;gap:6px 24px;padding:10px 12px;display:flex}.SgPada_versionItem{flex-direction:column;gap:2px;display:flex}.SgPada_versionLabel{color:var(--upd-text-muted);font-size:11.5px}.SgPada_versionValue{font-variant-numeric:tabular-nums;font-size:13px;font-weight:600}.SgPada_statusRow{flex-wrap:wrap;align-items:center;gap:12px;display:flex}.SgPada_statusText{color:var(--upd-text-secondary)}.SgPada_statusTextOk{color:var(--upd-ok)}.SgPada_statusTextAvailable{color:var(--upd-primary);font-weight:600}.SgPada_statusTextError{color:var(--upd-danger)}.SgPada_availableCard{border:1px solid var(--upd-primary);background:var(--upd-bg-active);border-radius:8px;flex-direction:column;gap:10px;padding:14px;display:flex}.SgPada_availableTitle{color:var(--upd-primary);margin:0;font-size:14px;font-weight:600}.SgPada_availableMeta{color:var(--upd-text-muted);margin:0;font-size:12px}.SgPada_notesTitle{color:var(--upd-text-secondary);margin:0;font-size:12.5px;font-weight:600}.SgPada_notes{border:1px solid var(--upd-border);background:var(--upd-bg-hover);color:var(--upd-text-secondary);white-space:pre-wrap;word-break:break-word;border-radius:6px;max-height:260px;margin:0;padding:10px 12px;font-family:inherit;font-size:12.5px;line-height:1.6;overflow:auto}.SgPada_actions{flex-wrap:wrap;align-items:center;gap:10px;display:flex}.SgPada_primaryButton{appearance:none;background:var(--upd-primary);color:#fff;cursor:pointer;border:none;border-radius:6px;padding:7px 16px;font-size:13px;font-weight:500}.SgPada_primaryButton:hover{filter:brightness(1.08)}.SgPada_primaryButton:disabled{opacity:.55;cursor:default;filter:none}.SgPada_secondaryButton{appearance:none;border:1px solid var(--upd-border);color:var(--upd-text-secondary);cursor:pointer;background:0 0;border-radius:6px;padding:6px 14px;font-size:13px}.SgPada_secondaryButton:hover{background:var(--upd-bg-hover)}.SgPada_secondaryButton:disabled{opacity:.55;cursor:default}.SgPada_link{color:var(--upd-primary);font-size:12.5px;text-decoration:none}.SgPada_link:hover{text-decoration:underline}.SgPada_progressWrap{flex-direction:column;gap:6px;display:flex}.SgPada_progressLabel{color:var(--upd-text-secondary);justify-content:space-between;font-size:12.5px;display:flex}.SgPada_progressTrack{background:var(--upd-bg-hover);border:1px solid var(--upd-border);border-radius:3px;height:6px;overflow:hidden}.SgPada_progressFill{background:var(--upd-primary);border-radius:3px;height:100%;transition:width .24s}.SgPada_progressIndeterminate{width:40%;animation:1.1s ease-in-out infinite alternate SgPada_upd-slide}@keyframes SgPada_upd-slide{0%{margin-left:0}to{margin-left:60%}}.SgPada_doneCard{border:1px solid var(--upd-ok);border-radius:8px;flex-direction:column;gap:8px;padding:14px;display:flex}.SgPada_doneTitle{color:var(--upd-ok);margin:0;font-size:14px;font-weight:600}.SgPada_doneMeta{color:var(--upd-text-secondary);margin:0;font-size:12.5px}.SgPada_errorText{color:var(--upd-danger);word-break:break-word;margin:0;font-size:12.5px}";
133
+ const css$1 = ".SgPada_root{--upd-text-primary:var(--dsw-alias-text-primary,#1a1e24);--upd-text-secondary:var(--dsw-alias-text-secondary,#5f6672);--upd-text-muted:var(--dsw-alias-text-muted,#8e95a3);--upd-border:var(--dsw-alias-border-l2,#e3e6ea);--upd-bg-hover:var(--dsw-alias-fill-hover,#0000000a);--upd-bg-active:var(--dsw-alias-primary-bg,#2563eb0f);--upd-primary:var(--dsw-alias-primary,#2563eb);--upd-ok:#16a34a;--upd-danger:#dc2626;color:var(--upd-text-primary);flex-direction:column;gap:16px;padding:0;font-size:13px;display:flex}body[data-ds-dark-theme] .SgPada_root,[data-theme=dark] .SgPada_root,:root[data-theme=dark] .SgPada_root,html.dark .SgPada_root,body.dark .SgPada_root{--upd-text-primary:var(--dsw-alias-text-primary,#f3f4f6);--upd-text-secondary:var(--dsw-alias-text-secondary,#9ca3af);--upd-text-muted:var(--dsw-alias-text-muted,#6b7280);--upd-border:var(--dsw-alias-border-l2,#ffffff1a);--upd-bg-hover:var(--dsw-alias-fill-hover,#ffffff0f);--upd-bg-active:#2563eb29;--upd-primary:#3b82f6;--upd-ok:#4ade80;--upd-danger:#f87171}.SgPada_lead{color:var(--upd-text-secondary);margin:0;font-size:13.5px;line-height:1.6}.SgPada_versions{border:1px solid var(--upd-border);background:var(--upd-bg-hover);border-radius:8px;flex-wrap:wrap;gap:6px 24px;padding:10px 12px;display:flex}.SgPada_versionItem{flex-direction:column;gap:2px;display:flex}.SgPada_versionLabel{color:var(--upd-text-muted);font-size:11.5px}.SgPada_versionValue{font-variant-numeric:tabular-nums;font-size:13px;font-weight:600}.SgPada_statusRow{flex-wrap:wrap;align-items:center;gap:12px;display:flex}.SgPada_statusText{color:var(--upd-text-secondary)}.SgPada_statusTextOk{color:var(--upd-ok)}.SgPada_statusTextError{color:var(--upd-danger)}.SgPada_availableCard{border:1px solid var(--upd-primary);background:var(--upd-bg-active);border-radius:8px;flex-direction:column;gap:10px;padding:14px;display:flex}.SgPada_availableTitle{color:var(--upd-primary);margin:0;font-size:14px;font-weight:600}.SgPada_availableMeta{color:var(--upd-text-muted);margin:0;font-size:12px}.SgPada_notesTitle{color:var(--upd-text-secondary);margin:0;font-size:12.5px;font-weight:600}.SgPada_notes{border:1px solid var(--upd-border);background:var(--upd-bg-hover);color:var(--upd-text-secondary);white-space:pre-wrap;word-break:break-word;border-radius:6px;max-height:260px;margin:0;padding:10px 12px;font-family:inherit;font-size:12.5px;line-height:1.6;overflow:auto}.SgPada_actions{flex-wrap:wrap;align-items:center;gap:10px;display:flex}.SgPada_primaryButton{appearance:none;background:var(--upd-primary);color:#fff;cursor:pointer;border:none;border-radius:6px;padding:7px 16px;font-size:13px;font-weight:500}.SgPada_primaryButton:hover{filter:brightness(1.08)}.SgPada_primaryButton:disabled{opacity:.55;cursor:default;filter:none}.SgPada_secondaryButton{appearance:none;border:1px solid var(--upd-border);color:var(--upd-text-secondary);cursor:pointer;background:0 0;border-radius:6px;padding:6px 14px;font-size:13px}.SgPada_secondaryButton:hover{background:var(--upd-bg-hover)}.SgPada_secondaryButton:disabled{opacity:.55;cursor:default}.SgPada_link{color:var(--upd-primary);font-size:12.5px;text-decoration:none}.SgPada_link:hover{text-decoration:underline}.SgPada_progressWrap{flex-direction:column;gap:6px;display:flex}.SgPada_progressLabel{color:var(--upd-text-secondary);justify-content:space-between;font-size:12.5px;display:flex}.SgPada_progressTrack{background:var(--upd-bg-hover);border:1px solid var(--upd-border);border-radius:3px;height:6px;overflow:hidden}.SgPada_progressFill{background:var(--upd-primary);border-radius:3px;height:100%;transition:width .24s}.SgPada_progressIndeterminate{width:40%;animation:1.1s ease-in-out infinite alternate SgPada_upd-slide}@keyframes SgPada_upd-slide{0%{margin-left:0}to{margin-left:60%}}.SgPada_doneCard{border:1px solid var(--upd-ok);border-radius:8px;flex-direction:column;gap:8px;padding:14px;display:flex}.SgPada_doneTitle{color:var(--upd-ok);margin:0;font-size:14px;font-weight:600}.SgPada_doneMeta{color:var(--upd-text-secondary);margin:0;font-size:12.5px}.SgPada_errorText{color:var(--upd-danger);word-break:break-word;margin:0;font-size:12.5px}";
134
134
  const tagId$1 = "@dshtrading/client-ui-updater/updater-panel.module.css";
135
135
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
136
136
  const tag = document.createElement("style");
@@ -140,36 +140,35 @@ window.__ModuleLoader__.load({
140
140
  document.head.appendChild(tag);
141
141
  }
142
142
  var updater_panel_module_css_default = {
143
- "doneTitle": "SgPada_doneTitle",
144
- "primaryButton": "SgPada_primaryButton",
145
- "secondaryButton": "SgPada_secondaryButton",
146
- "progressFill": "SgPada_progressFill",
147
- "root": "SgPada_root",
148
- "progressTrack": "SgPada_progressTrack",
149
- "progressLabel": "SgPada_progressLabel",
150
- "doneCard": "SgPada_doneCard",
151
- "statusRow": "SgPada_statusRow",
143
+ "actions": "SgPada_actions",
144
+ "statusTextError": "SgPada_statusTextError",
152
145
  "versions": "SgPada_versions",
153
- "availableCard": "SgPada_availableCard",
154
- "versionItem": "SgPada_versionItem",
155
- "lead": "SgPada_lead",
156
- "versionLabel": "SgPada_versionLabel",
146
+ "statusTextOk": "SgPada_statusTextOk",
147
+ "errorText": "SgPada_errorText",
157
148
  "availableMeta": "SgPada_availableMeta",
158
- "statusTextError": "SgPada_statusTextError",
149
+ "availableCard": "SgPada_availableCard",
150
+ "doneCard": "SgPada_doneCard",
151
+ "doneMeta": "SgPada_doneMeta",
159
152
  "availableTitle": "SgPada_availableTitle",
160
- "actions": "SgPada_actions",
161
- "notesTitle": "SgPada_notesTitle",
153
+ "statusRow": "SgPada_statusRow",
154
+ "link": "SgPada_link",
162
155
  "progressWrap": "SgPada_progressWrap",
163
- "statusText": "SgPada_statusText",
164
- "doneMeta": "SgPada_doneMeta",
165
- "progressIndeterminate": "SgPada_progressIndeterminate",
166
- "statusTextAvailable": "SgPada_statusTextAvailable",
156
+ "progressTrack": "SgPada_progressTrack",
157
+ "primaryButton": "SgPada_primaryButton",
158
+ "versionValue": "SgPada_versionValue",
159
+ "notesTitle": "SgPada_notesTitle",
160
+ "progressLabel": "SgPada_progressLabel",
167
161
  "upd-slide": "SgPada_upd-slide",
168
- "errorText": "SgPada_errorText",
169
- "statusTextOk": "SgPada_statusTextOk",
162
+ "versionLabel": "SgPada_versionLabel",
163
+ "versionItem": "SgPada_versionItem",
164
+ "doneTitle": "SgPada_doneTitle",
170
165
  "notes": "SgPada_notes",
171
- "link": "SgPada_link",
172
- "versionValue": "SgPada_versionValue"
166
+ "progressFill": "SgPada_progressFill",
167
+ "progressIndeterminate": "SgPada_progressIndeterminate",
168
+ "lead": "SgPada_lead",
169
+ "root": "SgPada_root",
170
+ "secondaryButton": "SgPada_secondaryButton",
171
+ "statusText": "SgPada_statusText"
173
172
  };
174
173
  //#endregion
175
174
  //#region src/client/UpdaterPanel.tsx
@@ -453,13 +452,13 @@ window.__ModuleLoader__.load({
453
452
  document.head.appendChild(tag);
454
453
  }
455
454
  var updater_dialog_module_css_default = {
456
- "overlay": "Sd___q_overlay",
457
- "title": "Sd___q_title",
458
455
  "close": "Sd___q_close",
456
+ "header": "Sd___q_header",
459
457
  "body": "Sd___q_body",
460
- "mask": "Sd___q_mask",
461
458
  "panel": "Sd___q_panel",
462
- "header": "Sd___q_header"
459
+ "overlay": "Sd___q_overlay",
460
+ "mask": "Sd___q_mask",
461
+ "title": "Sd___q_title"
463
462
  };
464
463
  //#endregion
465
464
  //#region src/client/UpdaterDialog.tsx
package/lib/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["useState","useRef","useCallback","css","useState","useCallback","css"],"sources":["../src/client/locales.ts","../src/client/contract.ts","../src/client/api.ts","../src/client/UpdaterPanel.tsx","../src/client/UpdaterDialog.tsx","../src/client/index.ts"],"sourcesContent":["/**\n * client-ui-updater 词典(纯数据模块:零运行时依赖)。\n *\n * 单一来源:本包 client apply 注册 zh/en(typed register 编译期校验键位);\n * packages/dsh-i18n 语言包构建期 import 本模块注册 zh-CN;scripts/i18n-audit.mjs\n * 静态加载本模块做 zh/en 键对齐与占位符对齐门禁。\n */\nimport type { UpdaterLocaleKey } from './contract.ts'\n\nexport const zh: Record<UpdaterLocaleKey, string> = {\n nav: '软件更新',\n lead: '检测 GitHub Releases 上的新版本,增量更新只下载变动的交易插件包,无需重装应用。',\n currentVersion: '交易插件版本',\n desktopApp: '桌面应用版本',\n unsupported: '当前环境不支持自动增量更新(仅 DSH Trading 桌面版支持)。可以前往发布页手动下载完整安装包。',\n viewReleases: '在 GitHub 查看发布',\n checkNow: '检查更新',\n checking: '正在检查…',\n lastCheck: '上次检查',\n never: '尚未检查',\n upToDate: '已是最新版本。',\n available: '发现新版本 {version}',\n publishedAt: '发布于 {date}',\n notesTitle: '更新说明',\n applyNow: '立即更新',\n payloadMissing: '该版本未附带增量更新包,请前往发布页下载完整安装包。',\n 'phase.prepare': '正在准备…',\n 'phase.download': '正在下载增量包…',\n 'phase.verify': '正在校验增量包…',\n 'phase.install': '正在安装更新…',\n progress: '{percent}%',\n done: '已更新到 {version}',\n restartHint: '重启 DSH Trading 后生效。',\n restartNow: '重启应用',\n restartManual: '请手动重启 DSH Trading 以完成更新。',\n checkError: '检查失败:{message}',\n retry: '重试',\n close: '关闭',\n}\n\nexport const en: Record<UpdaterLocaleKey, string> = {\n nav: 'Software updates',\n lead: 'Detects new versions published on GitHub Releases and applies incremental updates — only the changed trading plugin packages are downloaded, no reinstall needed.',\n currentVersion: 'Trading plugins',\n desktopApp: 'Desktop app',\n unsupported: 'Incremental updates are only available inside the DSH Trading desktop app. You can download a full installer from the releases page.',\n viewReleases: 'View releases on GitHub',\n checkNow: 'Check for updates',\n checking: 'Checking…',\n lastCheck: 'Last checked',\n never: 'Never checked',\n upToDate: 'You are up to date.',\n available: 'New version {version} available',\n publishedAt: 'Published {date}',\n notesTitle: 'Release notes',\n applyNow: 'Update now',\n payloadMissing: 'This release does not carry the incremental update payload. Please download a full installer from the releases page.',\n 'phase.prepare': 'Preparing…',\n 'phase.download': 'Downloading update payload…',\n 'phase.verify': 'Verifying update payload…',\n 'phase.install': 'Installing update…',\n progress: '{percent}%',\n done: 'Updated to {version}',\n restartHint: 'Restart DSH Trading to activate.',\n restartNow: 'Restart app',\n restartManual: 'Please restart DSH Trading manually to finish the update.',\n checkError: 'Check failed: {message}',\n retry: 'Retry',\n close: 'Close',\n}\n","/**\n * Locale contract for the software-update dialog. The zh dictionary in\n * locales.ts is the key source of truth; this union + the LocaleNamespaceMap\n * merge give the framework-injected t seat its key types.\n *\n * Type-only imports pull the host slot augmentations (shell.overlay) into the\n * program so the dialog registers against a declared slot.\n */\nimport type {} from '@deepseek-ai/dsh-client-ui-slots'\nimport type {} from '@deepseek-ai/dsh-client-ui-layout/client'\n\nexport type UpdaterLocaleKey =\n | 'nav'\n | 'lead'\n | 'currentVersion'\n | 'desktopApp'\n | 'unsupported'\n | 'viewReleases'\n | 'checkNow'\n | 'checking'\n | 'lastCheck'\n | 'never'\n | 'upToDate'\n | 'available'\n | 'publishedAt'\n | 'notesTitle'\n | 'applyNow'\n | 'payloadMissing'\n | 'phase.prepare'\n | 'phase.download'\n | 'phase.verify'\n | 'phase.install'\n | 'progress'\n | 'done'\n | 'restartHint'\n | 'restartNow'\n | 'restartManual'\n | 'checkError'\n | 'retry'\n | 'close'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** Software-update dialog copy (check / notes / incremental apply). */\n 'dshtrading.updater': UpdaterLocaleKey\n }\n}\n\n/**\n * Window event contract with the trading shell sidebar (client-ui-trading\n * MarketDock update entry). Cross-plugin coupling is intentionally a DOM\n * custom event, not an import (client plugins never import each other's\n * modules).\n *\n * - UPDATE_AVAILABLE_EVENT: fired by the dialog whenever the host snapshot\n * changes. Detail: { available: boolean, version?: string }.\n * - UPDATER_OPEN_EVENT: fired by the sidebar update entry to ask this plugin\n * to open its dialog. No detail.\n */\nexport const UPDATE_AVAILABLE_EVENT = 'dshtrading-update-available'\nexport const UPDATER_OPEN_EVENT = 'dshtrading-updater-open'\n","/**\n * Updater bridge client: same-origin fetch wrappers over\n * /dshtrading/api/updater (the node half registers the route behind the\n * browser-auth fence; same-origin fetch carries the auth cookie by default).\n * Error semantics mirror the tasks bridge: non-2xx or { ok:false } envelopes\n * become rejections carrying the business code.\n */\nimport type { UpdaterSnapshot } from '../updater-service.ts'\n\nexport class UpdaterBridgeError extends Error {\n constructor(readonly status: number, message: string, readonly code?: string) {\n super(message)\n }\n}\n\nasync function updaterJson<T>(path: string, init?: RequestInit): Promise<T> {\n const response = await fetch(path, init)\n if (!response.ok) {\n const body = await response.json().catch(() => undefined) as { code?: string; message?: string } | undefined\n const detail = typeof body?.message === 'string' && body.message !== '' ? ': ' + body.message : ''\n throw new UpdaterBridgeError(response.status, 'updater ' + path + ' failed: ' + String(response.status) + detail, body?.code)\n }\n const wire = await response.json() as T\n if (wire !== null && typeof wire === 'object' && (wire as { ok?: unknown }).ok === false) {\n const business = wire as { code?: string; message?: string }\n throw new UpdaterBridgeError(200, (business.code ?? 'UPDATER_UNKNOWN') + ': ' + (business.message ?? 'updater bridge error'), business.code)\n }\n return wire\n}\n\n/** Current updater snapshot (environment + check + apply state). */\nexport async function fetchUpdaterState(): Promise<UpdaterSnapshot> {\n return updaterJson<UpdaterSnapshot>('/dshtrading/api/updater/state', { headers: { accept: 'application/json' } })\n}\n\n/** Run a GitHub release check; force=true bypasses the host-side TTL cache. */\nexport async function requestUpdaterCheck(force: boolean): Promise<UpdaterSnapshot> {\n return updaterJson<UpdaterSnapshot>(`/dshtrading/api/updater/check?force=${force ? '1' : '0'}`, { headers: { accept: 'application/json' } })\n}\n\n/** Start the incremental apply pipeline; returns the running snapshot. */\nexport async function requestUpdaterApply(): Promise<UpdaterSnapshot> {\n return updaterJson<UpdaterSnapshot>('/dshtrading/api/updater/apply', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: '{}',\n })\n}\n\n/** Electron shell bridge exposed by the desktop preload (absent in browsers). */\ninterface DesktopBridge {\n relaunch?: () => void\n}\n\n/** Resolve the desktop shell bridge, if this page runs inside DSH Trading. */\nexport function desktopBridge(): DesktopBridge | undefined {\n if (typeof window === 'undefined' || !('desktop' in window)) return undefined\n return (window as { desktop?: DesktopBridge }).desktop\n}\n","/**\n * Software-update dialog body (rendered inside UpdaterDialog). Pure view over\n * /dshtrading/api/updater: renders the current versions, the latest GitHub\n * release with its notes, and drives the incremental apply + restart flow.\n * Polls the host snapshot while mounted (faster while an apply is running);\n * no SSE dependency.\n */\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { UpdaterSnapshot } from '../updater-service.ts'\nimport { desktopBridge, fetchUpdaterState, requestUpdaterApply, requestUpdaterCheck } from './api.ts'\nimport { UPDATE_AVAILABLE_EVENT } from './contract.ts'\nimport css from './updater-panel.module.css'\n\nexport type UpdaterPanelProps = PropsLocale<'dshtrading.updater'>\n\n/** Poll cadences: lazy when idle, tight while an apply pipeline is running. */\nconst POLL_IDLE_MS = 30_000\nconst POLL_RUNNING_MS = 1_200\n\n/** ISO date (yyyy-mm-dd) for the published line; empty when unknown. */\nfunction formatDay(iso: string | undefined): string {\n if (iso === undefined) return ''\n const parsed = new Date(iso)\n if (Number.isNaN(parsed.getTime())) return ''\n return parsed.toISOString().slice(0, 10)\n}\n\nexport function UpdaterPanel({ t }: UpdaterPanelProps) {\n const [state, setState] = useState<UpdaterSnapshot | undefined>(undefined)\n const [actionError, setActionError] = useState<string | undefined>(undefined)\n const [checkBusy, setCheckBusy] = useState(false)\n const applyBusyRef = useRef(false)\n\n const running = state?.apply.phase === 'running'\n\n const refresh = useCallback(async () => {\n try {\n setState(await fetchUpdaterState())\n } catch {\n // Bridge absent (headless profile / old deployment): keep the page quiet.\n }\n }, [])\n\n useEffect(() => { void refresh() }, [refresh])\n\n useEffect(() => {\n const interval = setInterval(() => { void refresh() }, running ? POLL_RUNNING_MS : POLL_IDLE_MS)\n return () => { clearInterval(interval) }\n }, [refresh, running])\n\n // Sidebar badge sync: mirror availability onto the window event the trading\n // shell sidebar listens to (see UPDATE_AVAILABLE_EVENT in contract.ts).\n useEffect(() => {\n if (state === undefined) return\n const available = state.check.available === true && state.apply.phase !== 'done'\n window.dispatchEvent(new CustomEvent(UPDATE_AVAILABLE_EVENT, {\n detail: { available, version: state.check.latest?.version },\n }))\n }, [state])\n\n const handleCheck = async (): Promise<void> => {\n setCheckBusy(true)\n setActionError(undefined)\n try {\n setState(await requestUpdaterCheck(true))\n } catch (error) {\n setActionError(error instanceof Error ? error.message : String(error))\n } finally {\n setCheckBusy(false)\n }\n }\n\n const handleApply = async (): Promise<void> => {\n if (applyBusyRef.current) return\n applyBusyRef.current = true\n setActionError(undefined)\n try {\n setState(await requestUpdaterApply())\n // Pipeline started: tighten polling immediately instead of waiting a tick.\n void refresh()\n } catch (error) {\n setActionError(error instanceof Error ? error.message : String(error))\n } finally {\n applyBusyRef.current = false\n }\n }\n\n const handleRestart = (): void => {\n desktopBridge()?.relaunch?.()\n }\n\n const environment = state?.environment\n const check = state?.check\n const apply = state?.apply\n const progress = state?.progress\n const latest = check?.latest\n const releasesUrl = latest?.url ?? 'https://github.com/zhu1090093659/dsh-trading/releases'\n const publishedDay = formatDay(latest?.publishedAt)\n const hasUpdate = check?.available === true && apply?.phase !== 'done'\n const applyable = hasUpdate && latest?.payloadAvailable === true\n\n return (\n <div className={css.root}>\n <p className={css.lead}>{t('lead')}</p>\n\n <div className={css.versions}>\n <div className={css.versionItem}>\n <span className={css.versionLabel}>{t('currentVersion')}</span>\n <span className={css.versionValue}>{environment?.familyVersion ?? '—'}</span>\n </div>\n {environment?.appVersion !== undefined && (\n <div className={css.versionItem}>\n <span className={css.versionLabel}>{t('desktopApp')}</span>\n <span className={css.versionValue}>{environment.appVersion}</span>\n </div>\n )}\n </div>\n\n {environment !== undefined && !environment.supported && (\n <p className={css.statusText}>{t('unsupported')}</p>\n )}\n\n {environment?.supported === true && (\n <>\n <div className={css.statusRow}>\n {check?.status === 'error' && (\n <span className={`${css.statusText} ${css.statusTextError}`}>\n {t('checkError', { message: check.error ?? '' })}\n </span>\n )}\n {check?.status === 'ok' && !hasUpdate && apply?.phase !== 'done' && (\n <span className={`${css.statusText} ${css.statusTextOk}`}>{t('upToDate')}</span>\n )}\n {check?.at !== undefined && (\n <span className={css.statusText}>\n {t('lastCheck')}\n :\n {' '}\n {check.at.slice(0, 19).replace('T', ' ')}\n </span>\n )}\n {check?.at === undefined && check?.status !== 'ok' && (\n <span className={css.statusText}>{t('never')}</span>\n )}\n <button\n type=\"button\"\n className={css.secondaryButton}\n disabled={checkBusy || running}\n onClick={() => { void handleCheck() }}\n >\n {checkBusy ? t('checking') : t('checkNow')}\n </button>\n {actionError !== undefined && (\n <button type=\"button\" className={css.secondaryButton} onClick={() => { void handleCheck() }}>\n {t('retry')}\n </button>\n )}\n </div>\n\n {running && (\n <div className={css.progressWrap}>\n <span className={css.progressLabel}>\n <span>\n {progress?.step === 'download'\n ? t('phase.download')\n : progress?.step === 'verify'\n ? t('phase.verify')\n : progress?.step === 'install'\n ? t('phase.install')\n : t('phase.prepare')}\n </span>\n {progress?.percent !== undefined && (\n <span>{t('progress', { percent: progress.percent })}</span>\n )}\n </span>\n <div\n className={css.progressTrack}\n role=\"progressbar\"\n aria-valuemin={0}\n aria-valuemax={100}\n aria-valuenow={progress?.percent ?? undefined}\n >\n <div\n className={`${css.progressFill} ${progress?.percent === undefined ? css.progressIndeterminate : ''}`}\n style={progress?.percent === undefined ? undefined : { width: `${progress.percent}%` }}\n />\n </div>\n </div>\n )}\n\n {hasUpdate && latest !== undefined && !running && (\n <div className={css.availableCard}>\n <p className={css.availableTitle}>{t('available', { version: latest.version })}</p>\n {publishedDay !== '' && (\n <p className={css.availableMeta}>{t('publishedAt', { date: publishedDay })}</p>\n )}\n {latest.notes.trim() !== '' && (\n <>\n <span className={css.notesTitle}>{t('notesTitle')}</span>\n <pre className={css.notes}>{latest.notes}</pre>\n </>\n )}\n <div className={css.actions}>\n {latest.payloadAvailable && (\n <button\n type=\"button\"\n className={css.primaryButton}\n onClick={() => { void handleApply() }}\n >\n {t('applyNow')}\n </button>\n )}\n <a className={css.link} href={releasesUrl} target=\"_blank\" rel=\"noreferrer\">\n {t('viewReleases')}\n </a>\n </div>\n {!latest.payloadAvailable && (\n <p className={css.doneMeta}>{t('payloadMissing')}</p>\n )}\n </div>\n )}\n\n {apply?.phase === 'done' && apply.targetVersion !== undefined && (\n <div className={css.doneCard}>\n <p className={css.doneTitle}>{t('done', { version: apply.targetVersion })}</p>\n <p className={css.doneMeta}>{t('restartHint')}</p>\n <div className={css.actions}>\n {desktopBridge()?.relaunch !== undefined && (\n <button type=\"button\" className={css.primaryButton} onClick={handleRestart}>\n {t('restartNow')}\n </button>\n )}\n {desktopBridge()?.relaunch === undefined && (\n <span className={css.doneMeta}>{t('restartManual')}</span>\n )}\n <a className={css.link} href={releasesUrl} target=\"_blank\" rel=\"noreferrer\">\n {t('viewReleases')}\n </a>\n </div>\n </div>\n )}\n\n {apply?.phase === 'error' && apply.error !== undefined && (\n <p className={css.errorText}>{apply.error}</p>\n )}\n </>\n )}\n\n {environment === undefined || !environment.supported ? (\n <a className={css.link} href={releasesUrl} target=\"_blank\" rel=\"noreferrer\">\n {t('viewReleases')}\n </a>\n ) : null}\n </div>\n )\n}\n","/**\n * Software-update dialog (the 'shell.overlay' entry registered by this plugin).\n * The sidebar's update entry dispatches UPDATER_OPEN_EVENT; this component owns\n * the modal chrome (mask click + Escape close) around UpdaterPanel. Frame-wide\n * floating layer: fixed overlay opts back into pointer events.\n */\nimport { useCallback, useEffect, useState } from 'react'\nimport type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'\nimport { UPDATER_OPEN_EVENT } from './contract.ts'\nimport { UpdaterPanel } from './UpdaterPanel.tsx'\nimport css from './updater-dialog.module.css'\n\nexport type UpdaterDialogProps = PropsLocale<'dshtrading.updater'>\n\nexport function UpdaterDialog({ t }: UpdaterDialogProps) {\n const [open, setOpen] = useState(false)\n\n useEffect(() => {\n const onOpen = (): void => { setOpen(true) }\n window.addEventListener(UPDATER_OPEN_EVENT, onOpen)\n return () => { window.removeEventListener(UPDATER_OPEN_EVENT, onOpen) }\n }, [])\n\n const close = useCallback((): void => { setOpen(false) }, [])\n\n useEffect(() => {\n if (!open) return\n const onKeyDown = (event: KeyboardEvent): void => {\n if (event.key === 'Escape') close()\n }\n document.addEventListener('keydown', onKeyDown)\n return () => { document.removeEventListener('keydown', onKeyDown) }\n }, [open, close])\n\n if (!open) return null\n\n return (\n <div className={css.overlay} role=\"presentation\">\n <div className={css.mask} aria-hidden=\"true\" onClick={close} />\n <div className={css.panel} role=\"dialog\" aria-modal=\"true\" aria-label={t('nav')}>\n <div className={css.header}>\n <span className={css.title}>{t('nav')}</span>\n <button type=\"button\" className={css.close} aria-label={t('close')} title={t('close')} onClick={close}>\n <svg viewBox=\"0 0 16 16\" width={14} height={14} aria-hidden=\"true\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"M4.25 4.25l7.5 7.5M11.75 4.25l-7.5 7.5\" />\n </svg>\n </button>\n </div>\n <div className={css.body}>\n <UpdaterPanel t={t} />\n </div>\n </div>\n </div>\n )\n}\n","/**\n * dsh-trading auto-update, browser half — the software-update dialog on the\n * host's frame-wide shell.overlay layer. The entry point is the update button\n * beside Settings in the trading sidebar's bottom bar (client-ui-trading\n * dispatches UPDATER_OPEN_EVENT); this plugin owns the modal chrome and body.\n *\n * The dialog talks to the node half over /dshtrading/api/updater (same-origin\n * fetch inside the browser-auth fence). In environments without incremental\n * support (dev checkouts, headless profiles) it degrades to an information\n * surface linking the GitHub releases page.\n */\nimport type { Context as ClientContext } from '@deepseek-ai/cordis'\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\nimport type {} from './contract.ts'\nimport { en, zh } from './locales.ts'\nimport { UpdaterDialog } from './UpdaterDialog.tsx'\n\nimport { UPDATE_AVAILABLE_EVENT, UPDATER_OPEN_EVENT } from './contract.ts'\n/** This dialog's locale namespace. */\nconst NS = 'dshtrading.updater'\n\n/** Required services (cordis fiber inject). */\nexport const inject = ['slots', 'locale']\n\n/**\n * Register the update dialog on shell.overlay (no settings.section: the entry\n * moved beside Settings in the sidebar bottom bar). The sidebar asks this\n * plugin to open through the UPDATER_OPEN_EVENT window event — cross-plugin\n * coupling is a DOM event, never an import.\n */\nexport function apply(ctx: ClientContext): void {\n ctx.locale.bind(NS)\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-trading-updater: dictionaries')\n\n ctx.slots.inject('shell.overlay', () => ctx.slots.register({\n name: 'shell.overlay',\n id: 'dshtrading-updater-dialog',\n order: 90,\n locale: NS,\n }, UpdaterDialog))\n}\n\nexport { UPDATE_AVAILABLE_EVENT, UPDATER_OPEN_EVENT } from './contract.ts'\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":["useState","useRef","useCallback","css","useState","useCallback","css"],"sources":["../src/client/locales.ts","../src/client/contract.ts","../src/client/api.ts","../src/client/UpdaterPanel.tsx","../src/client/UpdaterDialog.tsx","../src/client/index.ts"],"sourcesContent":["/**\n * client-ui-updater 词典(纯数据模块:零运行时依赖)。\n *\n * 单一来源:本包 client apply 注册 zh/en(typed register 编译期校验键位);\n * packages/dsh-i18n 语言包构建期 import 本模块注册 zh-CN;scripts/i18n-audit.mjs\n * 静态加载本模块做 zh/en 键对齐与占位符对齐门禁。\n */\nimport type { UpdaterLocaleKey } from './contract.ts'\n\nexport const zh: Record<UpdaterLocaleKey, string> = {\n nav: '软件更新',\n lead: '检测 GitHub Releases 上的新版本,增量更新只下载变动的交易插件包,无需重装应用。',\n currentVersion: '交易插件版本',\n desktopApp: '桌面应用版本',\n unsupported: '当前环境不支持自动增量更新(仅 DSH Trading 桌面版支持)。可以前往发布页手动下载完整安装包。',\n viewReleases: '在 GitHub 查看发布',\n checkNow: '检查更新',\n checking: '正在检查…',\n lastCheck: '上次检查',\n never: '尚未检查',\n upToDate: '已是最新版本。',\n available: '发现新版本 {version}',\n publishedAt: '发布于 {date}',\n notesTitle: '更新说明',\n applyNow: '立即更新',\n payloadMissing: '该版本未附带增量更新包,请前往发布页下载完整安装包。',\n 'phase.prepare': '正在准备…',\n 'phase.download': '正在下载增量包…',\n 'phase.verify': '正在校验增量包…',\n 'phase.install': '正在安装更新…',\n progress: '{percent}%',\n done: '已更新到 {version}',\n restartHint: '重启 DSH Trading 后生效。',\n restartNow: '重启应用',\n restartManual: '请手动重启 DSH Trading 以完成更新。',\n checkError: '检查失败:{message}',\n retry: '重试',\n close: '关闭',\n}\n\nexport const en: Record<UpdaterLocaleKey, string> = {\n nav: 'Software updates',\n lead: 'Detects new versions published on GitHub Releases and applies incremental updates — only the changed trading plugin packages are downloaded, no reinstall needed.',\n currentVersion: 'Trading plugins',\n desktopApp: 'Desktop app',\n unsupported: 'Incremental updates are only available inside the DSH Trading desktop app. You can download a full installer from the releases page.',\n viewReleases: 'View releases on GitHub',\n checkNow: 'Check for updates',\n checking: 'Checking…',\n lastCheck: 'Last checked',\n never: 'Never checked',\n upToDate: 'You are up to date.',\n available: 'New version {version} available',\n publishedAt: 'Published {date}',\n notesTitle: 'Release notes',\n applyNow: 'Update now',\n payloadMissing: 'This release does not carry the incremental update payload. Please download a full installer from the releases page.',\n 'phase.prepare': 'Preparing…',\n 'phase.download': 'Downloading update payload…',\n 'phase.verify': 'Verifying update payload…',\n 'phase.install': 'Installing update…',\n progress: '{percent}%',\n done: 'Updated to {version}',\n restartHint: 'Restart DSH Trading to activate.',\n restartNow: 'Restart app',\n restartManual: 'Please restart DSH Trading manually to finish the update.',\n checkError: 'Check failed: {message}',\n retry: 'Retry',\n close: 'Close',\n}\n","/**\n * Locale contract for the software-update dialog. The zh dictionary in\n * locales.ts is the key source of truth; this union + the LocaleNamespaceMap\n * merge give the framework-injected t seat its key types.\n *\n * Type-only imports pull the host slot augmentations (shell.overlay) into the\n * program so the dialog registers against a declared slot.\n */\nimport type {} from '@deepseek-ai/dsh-client-ui-slots'\nimport type {} from '@deepseek-ai/dsh-client-ui-layout/client'\n\nexport type UpdaterLocaleKey =\n | 'nav'\n | 'lead'\n | 'currentVersion'\n | 'desktopApp'\n | 'unsupported'\n | 'viewReleases'\n | 'checkNow'\n | 'checking'\n | 'lastCheck'\n | 'never'\n | 'upToDate'\n | 'available'\n | 'publishedAt'\n | 'notesTitle'\n | 'applyNow'\n | 'payloadMissing'\n | 'phase.prepare'\n | 'phase.download'\n | 'phase.verify'\n | 'phase.install'\n | 'progress'\n | 'done'\n | 'restartHint'\n | 'restartNow'\n | 'restartManual'\n | 'checkError'\n | 'retry'\n | 'close'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** Software-update dialog copy (check / notes / incremental apply). */\n 'dshtrading.updater': UpdaterLocaleKey\n }\n}\n\n/**\n * Window event contract with the trading shell sidebar (client-ui-trading\n * MarketDock update entry). Cross-plugin coupling is intentionally a DOM\n * custom event, not an import (client plugins never import each other's\n * modules).\n *\n * - UPDATE_AVAILABLE_EVENT: fired by the dialog whenever the host snapshot\n * changes. Detail: { available: boolean, version?: string }.\n * - UPDATER_OPEN_EVENT: fired by the sidebar update entry to ask this plugin\n * to open its dialog. No detail.\n */\nexport const UPDATE_AVAILABLE_EVENT = 'dshtrading-update-available'\nexport const UPDATER_OPEN_EVENT = 'dshtrading-updater-open'\n","/**\n * Updater bridge client: same-origin fetch wrappers over\n * /dshtrading/api/updater (the node half registers the route behind the\n * browser-auth fence; same-origin fetch carries the auth cookie by default).\n * Error semantics mirror the tasks bridge: non-2xx or { ok:false } envelopes\n * become rejections carrying the business code.\n */\nimport type { UpdaterSnapshot } from '../updater-service.ts'\n\nexport class UpdaterBridgeError extends Error {\n constructor(readonly status: number, message: string, readonly code?: string) {\n super(message)\n }\n}\n\nasync function updaterJson<T>(path: string, init?: RequestInit): Promise<T> {\n const response = await fetch(path, init)\n if (!response.ok) {\n const body = await response.json().catch(() => undefined) as { code?: string; message?: string } | undefined\n const detail = typeof body?.message === 'string' && body.message !== '' ? ': ' + body.message : ''\n throw new UpdaterBridgeError(response.status, 'updater ' + path + ' failed: ' + String(response.status) + detail, body?.code)\n }\n const wire = await response.json() as T\n if (wire !== null && typeof wire === 'object' && (wire as { ok?: unknown }).ok === false) {\n const business = wire as { code?: string; message?: string }\n throw new UpdaterBridgeError(200, (business.code ?? 'UPDATER_UNKNOWN') + ': ' + (business.message ?? 'updater bridge error'), business.code)\n }\n return wire\n}\n\n/** Current updater snapshot (environment + check + apply state). */\nexport async function fetchUpdaterState(): Promise<UpdaterSnapshot> {\n return updaterJson<UpdaterSnapshot>('/dshtrading/api/updater/state', { headers: { accept: 'application/json' } })\n}\n\n/** Run a GitHub release check; force=true bypasses the host-side TTL cache. */\nexport async function requestUpdaterCheck(force: boolean): Promise<UpdaterSnapshot> {\n return updaterJson<UpdaterSnapshot>(`/dshtrading/api/updater/check?force=${force ? '1' : '0'}`, { headers: { accept: 'application/json' } })\n}\n\n/** Start the incremental apply pipeline; returns the running snapshot. */\nexport async function requestUpdaterApply(): Promise<UpdaterSnapshot> {\n return updaterJson<UpdaterSnapshot>('/dshtrading/api/updater/apply', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: '{}',\n })\n}\n\n/** Electron shell bridge exposed by the desktop preload (absent in browsers). */\ninterface DesktopBridge {\n relaunch?: () => void\n}\n\n/** Resolve the desktop shell bridge, if this page runs inside DSH Trading. */\nexport function desktopBridge(): DesktopBridge | undefined {\n if (typeof window === 'undefined' || !('desktop' in window)) return undefined\n return (window as { desktop?: DesktopBridge }).desktop\n}\n","/**\n * Software-update dialog body (rendered inside UpdaterDialog). Pure view over\n * /dshtrading/api/updater: renders the current versions, the latest GitHub\n * release with its notes, and drives the incremental apply + restart flow.\n * Polls the host snapshot while mounted (faster while an apply is running);\n * no SSE dependency.\n */\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { UpdaterSnapshot } from '../updater-service.ts'\nimport { desktopBridge, fetchUpdaterState, requestUpdaterApply, requestUpdaterCheck } from './api.ts'\nimport { UPDATE_AVAILABLE_EVENT } from './contract.ts'\nimport css from './updater-panel.module.css'\n\nexport type UpdaterPanelProps = PropsLocale<'dshtrading.updater'>\n\n/** Poll cadences: lazy when idle, tight while an apply pipeline is running. */\nconst POLL_IDLE_MS = 30_000\nconst POLL_RUNNING_MS = 1_200\n\n/** ISO date (yyyy-mm-dd) for the published line; empty when unknown. */\nfunction formatDay(iso: string | undefined): string {\n if (iso === undefined) return ''\n const parsed = new Date(iso)\n if (Number.isNaN(parsed.getTime())) return ''\n return parsed.toISOString().slice(0, 10)\n}\n\nexport function UpdaterPanel({ t }: UpdaterPanelProps) {\n const [state, setState] = useState<UpdaterSnapshot | undefined>(undefined)\n const [actionError, setActionError] = useState<string | undefined>(undefined)\n const [checkBusy, setCheckBusy] = useState(false)\n const applyBusyRef = useRef(false)\n\n const running = state?.apply.phase === 'running'\n\n const refresh = useCallback(async () => {\n try {\n setState(await fetchUpdaterState())\n } catch {\n // Bridge absent (headless profile / old deployment): keep the page quiet.\n }\n }, [])\n\n useEffect(() => { void refresh() }, [refresh])\n\n useEffect(() => {\n const interval = setInterval(() => { void refresh() }, running ? POLL_RUNNING_MS : POLL_IDLE_MS)\n return () => { clearInterval(interval) }\n }, [refresh, running])\n\n // Sidebar badge sync: mirror availability onto the window event the trading\n // shell sidebar listens to (see UPDATE_AVAILABLE_EVENT in contract.ts).\n useEffect(() => {\n if (state === undefined) return\n const available = state.check.available === true && state.apply.phase !== 'done'\n window.dispatchEvent(new CustomEvent(UPDATE_AVAILABLE_EVENT, {\n detail: { available, version: state.check.latest?.version },\n }))\n }, [state])\n\n const handleCheck = async (): Promise<void> => {\n setCheckBusy(true)\n setActionError(undefined)\n try {\n setState(await requestUpdaterCheck(true))\n } catch (error) {\n setActionError(error instanceof Error ? error.message : String(error))\n } finally {\n setCheckBusy(false)\n }\n }\n\n const handleApply = async (): Promise<void> => {\n if (applyBusyRef.current) return\n applyBusyRef.current = true\n setActionError(undefined)\n try {\n setState(await requestUpdaterApply())\n // Pipeline started: tighten polling immediately instead of waiting a tick.\n void refresh()\n } catch (error) {\n setActionError(error instanceof Error ? error.message : String(error))\n } finally {\n applyBusyRef.current = false\n }\n }\n\n const handleRestart = (): void => {\n desktopBridge()?.relaunch?.()\n }\n\n const environment = state?.environment\n const check = state?.check\n const apply = state?.apply\n const progress = state?.progress\n const latest = check?.latest\n const releasesUrl = latest?.url ?? 'https://github.com/zhu1090093659/dsh-trading/releases'\n const publishedDay = formatDay(latest?.publishedAt)\n const hasUpdate = check?.available === true && apply?.phase !== 'done'\n const applyable = hasUpdate && latest?.payloadAvailable === true\n\n return (\n <div className={css.root}>\n <p className={css.lead}>{t('lead')}</p>\n\n <div className={css.versions}>\n <div className={css.versionItem}>\n <span className={css.versionLabel}>{t('currentVersion')}</span>\n <span className={css.versionValue}>{environment?.familyVersion ?? '—'}</span>\n </div>\n {environment?.appVersion !== undefined && (\n <div className={css.versionItem}>\n <span className={css.versionLabel}>{t('desktopApp')}</span>\n <span className={css.versionValue}>{environment.appVersion}</span>\n </div>\n )}\n </div>\n\n {environment !== undefined && !environment.supported && (\n <p className={css.statusText}>{t('unsupported')}</p>\n )}\n\n {environment?.supported === true && (\n <>\n <div className={css.statusRow}>\n {check?.status === 'error' && (\n <span className={`${css.statusText} ${css.statusTextError}`}>\n {t('checkError', { message: check.error ?? '' })}\n </span>\n )}\n {check?.status === 'ok' && !hasUpdate && apply?.phase !== 'done' && (\n <span className={`${css.statusText} ${css.statusTextOk}`}>{t('upToDate')}</span>\n )}\n {check?.at !== undefined && (\n <span className={css.statusText}>\n {t('lastCheck')}\n :\n {' '}\n {check.at.slice(0, 19).replace('T', ' ')}\n </span>\n )}\n {check?.at === undefined && check?.status !== 'ok' && (\n <span className={css.statusText}>{t('never')}</span>\n )}\n <button\n type=\"button\"\n className={css.secondaryButton}\n disabled={checkBusy || running}\n onClick={() => { void handleCheck() }}\n >\n {checkBusy ? t('checking') : t('checkNow')}\n </button>\n {actionError !== undefined && (\n <button type=\"button\" className={css.secondaryButton} onClick={() => { void handleCheck() }}>\n {t('retry')}\n </button>\n )}\n </div>\n\n {running && (\n <div className={css.progressWrap}>\n <span className={css.progressLabel}>\n <span>\n {progress?.step === 'download'\n ? t('phase.download')\n : progress?.step === 'verify'\n ? t('phase.verify')\n : progress?.step === 'install'\n ? t('phase.install')\n : t('phase.prepare')}\n </span>\n {progress?.percent !== undefined && (\n <span>{t('progress', { percent: progress.percent })}</span>\n )}\n </span>\n <div\n className={css.progressTrack}\n role=\"progressbar\"\n aria-valuemin={0}\n aria-valuemax={100}\n aria-valuenow={progress?.percent ?? undefined}\n >\n <div\n className={`${css.progressFill} ${progress?.percent === undefined ? css.progressIndeterminate : ''}`}\n style={progress?.percent === undefined ? undefined : { width: `${progress.percent}%` }}\n />\n </div>\n </div>\n )}\n\n {hasUpdate && latest !== undefined && !running && (\n <div className={css.availableCard}>\n <p className={css.availableTitle}>{t('available', { version: latest.version })}</p>\n {publishedDay !== '' && (\n <p className={css.availableMeta}>{t('publishedAt', { date: publishedDay })}</p>\n )}\n {latest.notes.trim() !== '' && (\n <>\n <span className={css.notesTitle}>{t('notesTitle')}</span>\n <pre className={css.notes}>{latest.notes}</pre>\n </>\n )}\n <div className={css.actions}>\n {latest.payloadAvailable && (\n <button\n type=\"button\"\n className={css.primaryButton}\n onClick={() => { void handleApply() }}\n >\n {t('applyNow')}\n </button>\n )}\n <a className={css.link} href={releasesUrl} target=\"_blank\" rel=\"noreferrer\">\n {t('viewReleases')}\n </a>\n </div>\n {!latest.payloadAvailable && (\n <p className={css.doneMeta}>{t('payloadMissing')}</p>\n )}\n </div>\n )}\n\n {apply?.phase === 'done' && apply.targetVersion !== undefined && (\n <div className={css.doneCard}>\n <p className={css.doneTitle}>{t('done', { version: apply.targetVersion })}</p>\n <p className={css.doneMeta}>{t('restartHint')}</p>\n <div className={css.actions}>\n {desktopBridge()?.relaunch !== undefined && (\n <button type=\"button\" className={css.primaryButton} onClick={handleRestart}>\n {t('restartNow')}\n </button>\n )}\n {desktopBridge()?.relaunch === undefined && (\n <span className={css.doneMeta}>{t('restartManual')}</span>\n )}\n <a className={css.link} href={releasesUrl} target=\"_blank\" rel=\"noreferrer\">\n {t('viewReleases')}\n </a>\n </div>\n </div>\n )}\n\n {apply?.phase === 'error' && apply.error !== undefined && (\n <p className={css.errorText}>{apply.error}</p>\n )}\n </>\n )}\n\n {environment === undefined || !environment.supported ? (\n <a className={css.link} href={releasesUrl} target=\"_blank\" rel=\"noreferrer\">\n {t('viewReleases')}\n </a>\n ) : null}\n </div>\n )\n}\n","/**\n * Software-update dialog (the 'shell.overlay' entry registered by this plugin).\n * The sidebar's update entry dispatches UPDATER_OPEN_EVENT; this component owns\n * the modal chrome (mask click + Escape close) around UpdaterPanel. Frame-wide\n * floating layer: fixed overlay opts back into pointer events.\n */\nimport { useCallback, useEffect, useState } from 'react'\nimport type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'\nimport { UPDATER_OPEN_EVENT } from './contract.ts'\nimport { UpdaterPanel } from './UpdaterPanel.tsx'\nimport css from './updater-dialog.module.css'\n\nexport type UpdaterDialogProps = PropsLocale<'dshtrading.updater'>\n\nexport function UpdaterDialog({ t }: UpdaterDialogProps) {\n const [open, setOpen] = useState(false)\n\n useEffect(() => {\n const onOpen = (): void => { setOpen(true) }\n window.addEventListener(UPDATER_OPEN_EVENT, onOpen)\n return () => { window.removeEventListener(UPDATER_OPEN_EVENT, onOpen) }\n }, [])\n\n const close = useCallback((): void => { setOpen(false) }, [])\n\n useEffect(() => {\n if (!open) return\n const onKeyDown = (event: KeyboardEvent): void => {\n if (event.key === 'Escape') close()\n }\n document.addEventListener('keydown', onKeyDown)\n return () => { document.removeEventListener('keydown', onKeyDown) }\n }, [open, close])\n\n if (!open) return null\n\n return (\n <div className={css.overlay} role=\"presentation\">\n <div className={css.mask} aria-hidden=\"true\" onClick={close} />\n <div className={css.panel} role=\"dialog\" aria-modal=\"true\" aria-label={t('nav')}>\n <div className={css.header}>\n <span className={css.title}>{t('nav')}</span>\n <button type=\"button\" className={css.close} aria-label={t('close')} title={t('close')} onClick={close}>\n <svg viewBox=\"0 0 16 16\" width={14} height={14} aria-hidden=\"true\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"M4.25 4.25l7.5 7.5M11.75 4.25l-7.5 7.5\" />\n </svg>\n </button>\n </div>\n <div className={css.body}>\n <UpdaterPanel t={t} />\n </div>\n </div>\n </div>\n )\n}\n","/**\n * dsh-trading auto-update, browser half — the software-update dialog on the\n * host's frame-wide shell.overlay layer. The entry point is the update button\n * beside Settings in the trading sidebar's bottom bar (client-ui-trading\n * dispatches UPDATER_OPEN_EVENT); this plugin owns the modal chrome and body.\n *\n * The dialog talks to the node half over /dshtrading/api/updater (same-origin\n * fetch inside the browser-auth fence). In environments without incremental\n * support (dev checkouts, headless profiles) it degrades to an information\n * surface linking the GitHub releases page.\n */\nimport type { Context as ClientContext } from '@deepseek-ai/cordis'\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\nimport type {} from './contract.ts'\nimport { en, zh } from './locales.ts'\nimport { UpdaterDialog } from './UpdaterDialog.tsx'\n\nimport { UPDATE_AVAILABLE_EVENT, UPDATER_OPEN_EVENT } from './contract.ts'\n/** This dialog's locale namespace. */\nconst NS = 'dshtrading.updater'\n\n/** Required services (cordis fiber inject). */\nexport const inject = ['slots', 'locale']\n\n/**\n * Register the update dialog on shell.overlay (no settings.section: the entry\n * moved beside Settings in the sidebar bottom bar). The sidebar asks this\n * plugin to open through the UPDATER_OPEN_EVENT window event — cross-plugin\n * coupling is a DOM event, never an import.\n */\nexport function apply(ctx: ClientContext): void {\n ctx.locale.bind(NS)\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-trading-updater: dictionaries')\n\n ctx.slots.inject('shell.overlay', () => ctx.slots.register({\n name: 'shell.overlay',\n id: 'dshtrading-updater-dialog',\n order: 90,\n locale: NS,\n }, UpdaterDialog))\n}\n\nexport { UPDATE_AVAILABLE_EVENT, UPDATER_OPEN_EVENT } from './contract.ts'\n"],"mappings":"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"}
package/lib/github.js CHANGED
@@ -1,4 +1,27 @@
1
1
  //#region src/github.ts
2
+ /**
3
+ * GitHub Releases client for the update channel. Detection uses the public
4
+ * REST API (repos/<owner>/<repo>/releases/latest) which already excludes
5
+ * drafts and prereleases — the release pipeline only publishes stable tags.
6
+ * No token: the repo is public and the cadence (startup + 6h) sits far below
7
+ * the unauthenticated rate limit.
8
+ */
9
+ /**
10
+ * GitHub refused the request because this address is over its request budget.
11
+ * The unauthenticated core limit answers 403 with `x-ratelimit-remaining: 0`
12
+ * (measured 2026-10-15 on this deployment: 60/60 used, reset header present),
13
+ * and the secondary limit answers 429 with `retry-after`. Carries the reset
14
+ * instant so the caller can back off instead of retrying into the same wall.
15
+ */
16
+ var GithubRateLimitError = class extends Error {
17
+ /** Epoch ms when the budget resets; undefined when GitHub reported no hint. */
18
+ resetAt;
19
+ constructor(repo, status, resetAt) {
20
+ super(`GitHub API ${status} rate limit for ${repo} releases/latest`);
21
+ this.name = "GithubRateLimitError";
22
+ this.resetAt = resetAt;
23
+ }
24
+ };
2
25
  const GITHUB_API_BASE = "https://api.github.com";
3
26
  /** Update payload manifest asset name for a tag, e.g. updates-manifest-v0.2.0.json. */
4
27
  function manifestAssetName(tagName) {
@@ -19,6 +42,23 @@ function parseAsset(raw) {
19
42
  };
20
43
  }
21
44
  /**
45
+ * When the next request may succeed again, from the headers GitHub sends with a
46
+ * refused-for-budget response: `retry-after` (seconds, secondary limit) wins
47
+ * over the absolute `x-ratelimit-reset` (epoch seconds, primary limit).
48
+ */
49
+ function rateLimitResetAt(response) {
50
+ const retryAfter = response.headers.get("retry-after");
51
+ if (retryAfter !== null) {
52
+ const seconds = Number(retryAfter);
53
+ if (Number.isFinite(seconds) && seconds >= 0) return Date.now() + seconds * 1e3;
54
+ }
55
+ const reset = response.headers.get("x-ratelimit-reset");
56
+ if (reset !== null) {
57
+ const seconds = Number(reset);
58
+ if (Number.isFinite(seconds) && seconds > 0) return seconds * 1e3;
59
+ }
60
+ }
61
+ /**
22
62
  * Fetch the latest published release. Returns undefined when the repo has no
23
63
  * full releases yet (GitHub answers 404 for /releases/latest then).
24
64
  */
@@ -39,7 +79,10 @@ async function fetchLatestRelease(options) {
39
79
  signal: controller.signal
40
80
  });
41
81
  if (response.status === 404) return void 0;
42
- if (!response.ok) throw new Error(`GitHub API ${response.status} for ${options.repo} releases/latest`);
82
+ if (!response.ok) {
83
+ if (response.status === 429 || response.status === 403 && response.headers.get("x-ratelimit-remaining") === "0") throw new GithubRateLimitError(options.repo, response.status, rateLimitResetAt(response));
84
+ throw new Error(`GitHub API ${response.status} for ${options.repo} releases/latest`);
85
+ }
43
86
  const body = await response.json();
44
87
  const tagName = typeof body.tag_name === "string" ? body.tag_name : "";
45
88
  const assets = Array.isArray(body.assets) ? body.assets.map(parseAsset).filter((asset) => asset !== void 0) : [];
@@ -82,4 +125,4 @@ function parseUpdateManifest(text) {
82
125
  };
83
126
  }
84
127
  //#endregion
85
- export { GITHUB_API_BASE, fetchLatestRelease, manifestAssetName, parseUpdateManifest, payloadAssetName };
128
+ export { GITHUB_API_BASE, GithubRateLimitError, fetchLatestRelease, manifestAssetName, parseUpdateManifest, payloadAssetName };
@@ -86,6 +86,8 @@ declare class UpdaterService {
86
86
  private readonly checkTtlMs;
87
87
  private readonly log;
88
88
  private checkState;
89
+ /** Epoch ms before which a non-forced check must not ask GitHub again. */
90
+ private rateLimitedUntil;
89
91
  private applyState;
90
92
  private progress;
91
93
  private timers;
@@ -101,6 +103,11 @@ declare class UpdaterService {
101
103
  * and double as the periodic tick; force=true bypasses the cache (UI button).
102
104
  */
103
105
  check(force: boolean): Promise<UpdaterSnapshot>;
106
+ /**
107
+ * Whether a non-forced check can reuse current state: a fresh `ok` result, or
108
+ * an active rate-limit cooldown (a known-refused budget must not be re-probed
109
+ * on every launch).
110
+ */
104
111
  private checkFresh;
105
112
  private latestOf;
106
113
  /**
@@ -1,4 +1,4 @@
1
- import { fetchLatestRelease, manifestAssetName, parseUpdateManifest, payloadAssetName } from "./github.js";
1
+ import { GithubRateLimitError, fetchLatestRelease, manifestAssetName, parseUpdateManifest, payloadAssetName } from "./github.js";
2
2
  import { isNewerVersion } from "./semver.js";
3
3
  import path from "node:path";
4
4
  import fs from "node:fs";
@@ -33,6 +33,13 @@ var UpdaterError = class extends Error {
33
33
  const AUTO_CHECK_MS_DEFAULT = 216e5;
34
34
  const INITIAL_DELAY_MS_DEFAULT = 15e3;
35
35
  const CHECK_TTL_MS_DEFAULT = 18e5;
36
+ /**
37
+ * Quiet period after GitHub answers "over budget" without a reset hint. The
38
+ * unauthenticated budget is per address and refills hourly, and every launch of
39
+ * the app runs one check — retrying immediately turns one exhausted budget into
40
+ * a failed-looking log line per launch (measured 2026-10-15).
41
+ */
42
+ const RATE_LIMIT_COOLDOWN_MS_DEFAULT = 9e5;
36
43
  /** Windows rename retries for files/dirs briefly held open (AV scans etc.). */
37
44
  const SWAP_RETRY_ATTEMPTS = 4;
38
45
  const SWAP_RETRY_DELAY_MS = 300;
@@ -90,6 +97,8 @@ var UpdaterService = class {
90
97
  checkTtlMs;
91
98
  log;
92
99
  checkState;
100
+ /** Epoch ms before which a non-forced check must not ask GitHub again. */
101
+ rateLimitedUntil = 0;
93
102
  applyState = applyIdle();
94
103
  progress = void 0;
95
104
  timers = [];
@@ -147,6 +156,7 @@ var UpdaterService = class {
147
156
  });
148
157
  const latest = release === void 0 ? void 0 : this.latestOf(release);
149
158
  const available = latest?.version !== void 0 && isNewerVersion(latest.version, this.env.familyVersion);
159
+ this.rateLimitedUntil = 0;
150
160
  this.checkState = {
151
161
  at: new Date(this.now()).toISOString(),
152
162
  status: "ok",
@@ -156,21 +166,33 @@ var UpdaterService = class {
156
166
  };
157
167
  this.persistCheck();
158
168
  } catch (error) {
169
+ const message = error instanceof Error ? error.message : String(error);
170
+ if (error instanceof GithubRateLimitError) {
171
+ const until = error.resetAt ?? this.now() + RATE_LIMIT_COOLDOWN_MS_DEFAULT;
172
+ this.rateLimitedUntil = Math.max(this.rateLimitedUntil, until);
173
+ }
159
174
  this.checkState = {
160
175
  at: new Date(this.now()).toISOString(),
161
176
  status: "error",
162
- error: error instanceof Error ? error.message : String(error),
177
+ error: message,
163
178
  available: this.checkState.available,
164
179
  latest: this.checkState.latest
165
180
  };
166
181
  this.persistCheck();
167
- this.log("check failed: " + (error instanceof Error ? error.message : String(error)));
182
+ if (error instanceof GithubRateLimitError) this.log("check rate limited: " + message + ", next attempt after " + new Date(this.rateLimitedUntil).toISOString());
183
+ else this.log("check failed: " + message);
168
184
  } finally {
169
185
  this.checkInFlight = false;
170
186
  }
171
187
  return this.snapshot();
172
188
  }
189
+ /**
190
+ * Whether a non-forced check can reuse current state: a fresh `ok` result, or
191
+ * an active rate-limit cooldown (a known-refused budget must not be re-probed
192
+ * on every launch).
193
+ */
173
194
  checkFresh() {
195
+ if (this.now() < this.rateLimitedUntil) return true;
174
196
  if (this.checkState.status !== "ok" || this.checkState.at === void 0) return false;
175
197
  const at = Date.parse(this.checkState.at);
176
198
  if (!Number.isFinite(at)) return false;
@@ -460,7 +482,9 @@ var UpdaterService = class {
460
482
  }
461
483
  loadPersistedCheck() {
462
484
  try {
463
- const lastCheck = JSON.parse(fs.readFileSync(this.stateFile().file, "utf8")).lastCheck;
485
+ const raw = JSON.parse(fs.readFileSync(this.stateFile().file, "utf8"));
486
+ if (typeof raw.rateLimitedUntil === "number" && Number.isFinite(raw.rateLimitedUntil) && raw.rateLimitedUntil > 0) this.rateLimitedUntil = raw.rateLimitedUntil;
487
+ const lastCheck = raw.lastCheck;
464
488
  if (lastCheck !== void 0 && typeof lastCheck === "object" && "status" in lastCheck) return {
465
489
  at: lastCheck.at,
466
490
  status: lastCheck.status,
@@ -483,7 +507,8 @@ var UpdaterService = class {
483
507
  fs.mkdirSync(dir, { recursive: true });
484
508
  const state = {
485
509
  version: 1,
486
- lastCheck: this.checkState
510
+ lastCheck: this.checkState,
511
+ ...this.rateLimitedUntil > 0 ? { rateLimitedUntil: this.rateLimitedUntil } : {}
487
512
  };
488
513
  const tmp = file + ".tmp";
489
514
  fs.writeFileSync(tmp, JSON.stringify(state, null, 2) + "\n");
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dshtrading/client-ui-updater",
3
3
  "description": "dsh-trading auto-update plugin: GitHub Releases version check with release notes, incremental update of the @dshtrading/* plugin family inside a desktop-seeded profile (vendor payload zip, per-package swap), and the software-update dialog opened from the trading sidebar's update entry. Browser half registers the shell.overlay dialog; node half registers the /dshtrading/api/updater routes and the update engine.",
4
- "version": "0.6.1",
4
+ "version": "0.6.2",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "./lib/index.d.ts",
@@ -11,7 +11,6 @@
11
11
  "default": "./lib/index.js"
12
12
  },
13
13
  "./client": {
14
- "types": "./lib/types/client/index.d.ts",
15
14
  "default": "./lib/client.js"
16
15
  },
17
16
  "./locales": {
@@ -26,13 +25,13 @@
26
25
  "license": "PolyForm-Noncommercial-1.0.0",
27
26
  "dependencies": {
28
27
  "fflate": "^0.8.2",
29
- "@dshtrading/dsh-home": "^0.6.1"
28
+ "@dshtrading/dsh-home": "^0.6.2"
30
29
  },
31
30
  "peerDependencies": {
32
31
  "@deepseek-ai/cordis": ">=4.0.0",
33
- "@deepseek-ai/dsh-client-locale": ">=0.2.0-rc.2",
34
- "@deepseek-ai/dsh-client-ui-layout": ">=0.2.0-rc.2",
35
- "@deepseek-ai/dsh-client-ui-slots": ">=0.2.0-rc.2",
32
+ "@deepseek-ai/dsh-client-locale": ">=0.2.1-rc.2",
33
+ "@deepseek-ai/dsh-client-ui-layout": ">=0.2.1-rc.2",
34
+ "@deepseek-ai/dsh-client-ui-slots": ">=0.2.1-rc.2",
36
35
  "react": "^18.2.0",
37
36
  "@types/react": "~18.3.1"
38
37
  },
@@ -46,10 +45,10 @@
46
45
  }
47
46
  },
48
47
  "devDependencies": {
49
- "@deepseek-ai/cordis": "^4.0.4",
50
- "@deepseek-ai/dsh-client-locale": "0.2.0-rc.2",
51
- "@deepseek-ai/dsh-client-ui-layout": "0.2.0-rc.2",
52
- "@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.2",
48
+ "@deepseek-ai/cordis": "^4.0.5",
49
+ "@deepseek-ai/dsh-client-locale": "0.2.1-rc.2",
50
+ "@deepseek-ai/dsh-client-ui-layout": "0.2.1-rc.2",
51
+ "@deepseek-ai/dsh-client-ui-slots": "0.2.1-rc.2",
53
52
  "@types/node": "^24.0.0",
54
53
  "@types/react": "~18.3.1",
55
54
  "lightningcss": "^1.0.0",