dsh-plugin-update 0.3.0 → 0.3.1

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/CHANGELOG.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  格式为 Keep-a-Changelog 子集:`Added/Fixed/Changed` 必写,`Deprecated/Removed/Security` 有则透传,`Unreleased` 面板忽略,禁止 git-log 直倒。面板缺日志时中性提示,不挡安装。
4
4
 
5
+ ## [0.3.1] - 2026-10-05
6
+
7
+ ### Added
8
+ - 新增 `dsh-plugin-update/http`:`createHttpCall`(单三电话 + 批量五电话同一内核,`batchPrefix` 独立)+ `mountUpdatePanelHttp`/`mountUpdateEntryHttp`/`mountUpdateBatchPanelHttp`,只传 pluginId + prefix/batchPrefix + baseUrl/routes 即跑通检查/安装/待重启/失败与批量总账/明细/动作;queue/env/session/rows/progress/diag 全透传,传输失败走 `http-transport-failed` 异常通道,取消整批走 `batchCancel` 电话。
9
+
5
10
  ## [0.3.0] - 2026-10-04
6
11
 
7
12
  ### Added
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  给 DSH 插件加「检查更新 / 安装更新」能力的 npm 包。宿主侧一段接线,面板侧挂一个现成组件,装不上时给用户一条可复制的手工命令。
4
4
 
5
- 要求 Node 22 或更高,零运行时依赖。当前版本 `0.3.0`。
5
+ 要求 Node 22 或更高,零运行时依赖。当前版本 `0.3.1`。
6
6
 
7
7
  装上它你会拿到六样东西:
8
8
 
@@ -144,6 +144,21 @@ mountUpdatePanel(slot, { pluginId: 'my-notes-plugin', prefix: 'notes', call: hos
144
144
 
145
145
  类型定义随包分发(`dsh-plugin-update/panel` 的 `.d.ts`),不用自编译;面板离线可读,与包版本绑定。
146
146
 
147
+ 整组件 HTTP 版(无 `host.call` 环境即跑:浏览器面板经 HTTP POST 直达宿主网关,只传三样):
148
+
149
+ ```js
150
+ import { mountUpdatePanelHttp } from 'dsh-plugin-update/http'
151
+
152
+ mountUpdatePanelHttp(document.querySelector('#upd'), {
153
+ pluginId: 'my-plugin',
154
+ prefix: 'myplug',
155
+ baseUrl: 'https://host.local:3000/upd',
156
+ showOthers: false,
157
+ pollMs: 1000,
158
+ })
159
+ // 离开时 panel.unmount():只停轮询,安装在宿主侧继续跑。
160
+ ```
161
+
147
162
  ### 第 2.5 节:更新入口件(配置页上那一颗按钮)
148
163
 
149
164
  目标只有一句:**让用户不用点开就知道有没有事。** 一行挂上:
@@ -221,6 +236,19 @@ const panel = mountUpdateBatchPanel(el, {
221
236
  })
222
237
  ```
223
238
 
239
+ 批量面板 HTTP 版(无 `host.call` 环境即跑:同一传输内核,五电话走同一映射,取消走 `batchCancel`):
240
+
241
+ ```js
242
+ import { mountUpdateBatchPanelHttp } from 'dsh-plugin-update/http'
243
+
244
+ mountUpdateBatchPanelHttp(document.querySelector('#batch'), {
245
+ batchPrefix: 'life',
246
+ baseUrl: 'https://host.local:3000/upd',
247
+ pollMs: 1500,
248
+ })
249
+ // 取消整批走 batchCancel 电话;unmount 只停轮询,宿主侧照跑。
250
+ ```
251
+
224
252
  三条硬约束(都在实现里):**一行只回答一个问题**(这家的下一步是什么,状态词全中文可执行);
225
253
  **行内动作只作用于该行**,「全部更新」是宏而不是第二个状态机;**待重启与失败常驻横幅**,不藏进展开里。
226
254
 
@@ -250,7 +278,7 @@ const panel = mountUpdateBatchPanel(el, {
250
278
 
251
279
  宿主种类与安装出口都由本包自己探测和选择,**升级依赖即可,宿主侧与面板侧都不用改代码**:
252
280
 
253
- 1. 依赖版本提到 `^0.3.0`。
281
+ 1. 依赖版本提到 `^0.3.1`。
254
282
  2. 重新装 / 发一版你自己的插件,让新依赖进当前使用范围(运行时用的是 `node_modules` 里那份)。
255
283
  3. 如果你自己接过一版宿主安装出口,把它删掉——它会挡在本包的路由前面。
256
284
 
package/dist/http.d.ts ADDED
@@ -0,0 +1,127 @@
1
+ import { assertPollInterval } from './client.js';
2
+ import { type UpdatePanelContainer, type UpdatePanelController, type UpdatePanelOptions } from './panel.js';
3
+ import { type UpdateEntryController, type UpdateEntryOptions } from './entry.js';
4
+ import { type BatchPanelContainer, type BatchPanelController, type BatchPanelOptions } from './panel-batch.js';
5
+ /** 传输失败码:永不进面板 14 稳定码分支,只走抛异常通道。 */
6
+ export declare const HTTP_TRANSPORT_FAILED = "http-transport-failed";
7
+ /** 抓取超时默认:查 15s / 装 16min(#30 冻结,调用方可覆写 + AbortSignal 透入)。 */
8
+ export declare const DEFAULT_HTTP_CHECK_TIMEOUT_MS = 15000;
9
+ export declare const DEFAULT_HTTP_INSTALL_TIMEOUT_MS: number;
10
+ export declare const DEFAULT_HTTP_STATUS_TIMEOUT_MS = 15000;
11
+ /** 单插件三电话动作名(与 src/config.ts PHONE_ACTIONS 同字面,不另起字面)。 */
12
+ export declare const HTTP_PHONE_ACTIONS: readonly ["updateStatus", "updateCheck", "updateInstall"];
13
+ export type HttpPhoneAction = (typeof HTTP_PHONE_ACTIONS)[number];
14
+ /** 批量五电话动作名(与 src/panel-batch.ts buildBatchPhoneNames 同字面,不另起字面;同一内核)。 */
15
+ export declare const HTTP_BATCH_ACTIONS: readonly ["batchStatus", "batchCheck", "batchInstall", "batchResume", "batchCancel"];
16
+ export type HttpBatchAction = (typeof HTTP_BATCH_ACTIONS)[number];
17
+ /** 逐电话覆写表:键为全电话名(prefix.动作)或短动作名;未知键拒绝。 */
18
+ export type HttpRoutes = Record<string, string>;
19
+ /** 抓取函数形状:与 globalThis.fetch 同形(测试给内存假件)。 */
20
+ export type HttpFetch = (url: string, init: {
21
+ method: string;
22
+ headers: Record<string, string>;
23
+ body: string;
24
+ signal?: unknown;
25
+ }) => Promise<unknown>;
26
+ export interface HttpTimeouts {
27
+ /** 查/状态超时毫秒(默认 15s)。 */
28
+ checkMs?: number;
29
+ /** 装超时毫秒(默认 16min)。 */
30
+ installMs?: number;
31
+ /** 状态超时毫秒(默认 15s,与查同值)。 */
32
+ statusMs?: number;
33
+ }
34
+ export interface CreateHttpCallOptions {
35
+ /** 单插件电话前缀(与宿主侧一致;电话名从它算出,不写字面量)。 */
36
+ prefix: string;
37
+ /** 批量电话前缀(独立于单插件前缀;给了即同时校验批量五电话键,同一内核)。 */
38
+ batchPrefix?: string;
39
+ /** 网关根:默认路径 = 网关根 + 全电话名(首尾斜杠归一)。 */
40
+ baseUrl: string;
41
+ /** 电话级覆写表(routes[全电话名] ?? routes[短动作] ?? 默认拼接)。 */
42
+ routes?: HttpRoutes;
43
+ /** 抓取注入点(不传即 globalThis.fetch;测试给假件)。 */
44
+ fetch?: HttpFetch;
45
+ /** 同上别名(与 fetch 二选一,fetch 优先)。 */
46
+ fetchImpl?: HttpFetch;
47
+ /** 超时覆写:数字即全电话统一;对象即按电话分设。 */
48
+ timeout?: number | HttpTimeouts;
49
+ /** 同上别名(与 timeout 二选一,timeout 优先)。 */
50
+ timeoutMs?: number | HttpTimeouts;
51
+ }
52
+ /** 覆写表未知键拒绝(创建即抛, fail-fast)。 */
53
+ export declare function assertHttpRoutes(routes: HttpRoutes | undefined, prefix: string, batchPrefix?: string): void;
54
+ /** 电话名 → POST 路径(默认拼接 + 电话级覆写 + 缺键回落 + 斜杠归一)。 */
55
+ export declare function resolveHttpPath(options: {
56
+ prefix: string;
57
+ batchPrefix?: string;
58
+ baseUrl: string;
59
+ routes?: HttpRoutes;
60
+ }, phoneName: string): string;
61
+ /** 传输失败:断网/非 2xx/JSON 非法/超时,永不进稳定码分支。 */
62
+ export declare function httpTransportFailed(reason: string, detail?: unknown): Error & {
63
+ code: string;
64
+ detail: string;
65
+ };
66
+ /** diag 宽容读:只留 16 键(DIAG_KEYS),detail 脱敏;diagnostic 原话丢弃。 */
67
+ export declare function pickHttpDiag(raw: unknown): Record<string, unknown> | undefined;
68
+ /** 回包整形:diagnostic 丢弃,diag 过滤,其余(queue/env/session/rows/progress/未知键)原样透传。
69
+ *
70
+ * 批量行内同理:rows[i].diagnostic 丢弃,rows[i].diag 只留 16 键且 detail 脱敏;
71
+ * 行内 queue/manual/snapshot/phoneNames 等原样透传(排队/续跑语义不裁剪)。 */
72
+ export declare function shapeHttpReply(parsed: unknown): Record<string, unknown>;
73
+ /** 建 HTTP 通话函数:(phoneName, args) => Promise<reply>,回包全透传,失败抛异常。 */
74
+ export declare function createHttpCall(options: CreateHttpCallOptions): (phoneName: string, args: Record<string, unknown>) => Promise<Record<string, unknown>>;
75
+ export interface UpdatePanelHttpOptions extends Omit<UpdatePanelOptions, "call" | "prefix"> {
76
+ /** 单插件电话前缀(与宿主侧一致)。 */
77
+ prefix?: string;
78
+ /** 网关根(必填)。 */
79
+ baseUrl: string;
80
+ /** 电话级覆写表(可选)。 */
81
+ routes?: HttpRoutes;
82
+ /** 抓取注入点(可选)。 */
83
+ fetch?: HttpFetch;
84
+ fetchImpl?: HttpFetch;
85
+ /** 超时覆写(可选)。 */
86
+ timeout?: number | HttpTimeouts;
87
+ timeoutMs?: number | HttpTimeouts;
88
+ }
89
+ /** 面板整组件 HTTP 直挂:只传三样即跑,内核复用 mountUpdatePanel。 */
90
+ export declare function mountUpdatePanelHttp(container: UpdatePanelContainer, options: UpdatePanelHttpOptions): UpdatePanelController;
91
+ export interface UpdateEntryHttpOptions extends Omit<UpdateEntryOptions, "call" | "prefix"> {
92
+ /** 单插件电话前缀(与宿主侧一致)。 */
93
+ prefix?: string;
94
+ /** 网关根(必填)。 */
95
+ baseUrl: string;
96
+ /** 电话级覆写表(可选)。 */
97
+ routes?: HttpRoutes;
98
+ /** 抓取注入点(可选)。 */
99
+ fetch?: HttpFetch;
100
+ fetchImpl?: HttpFetch;
101
+ /** 超时覆写(可选)。 */
102
+ timeout?: number | HttpTimeouts;
103
+ timeoutMs?: number | HttpTimeouts;
104
+ }
105
+ /** 入口件 HTTP 直挂:内部源码级禁安装电话,内核复用 mountUpdateEntry。 */
106
+ export declare function mountUpdateEntryHttp(container: UpdatePanelContainer, options: UpdateEntryHttpOptions): UpdateEntryController;
107
+ export interface UpdateBatchPanelHttpOptions extends Omit<BatchPanelOptions, "call" | "prefix"> {
108
+ /** 批量电话前缀(与宿主侧一致;五电话名从它算出,不写字面量)。 */
109
+ batchPrefix: string;
110
+ /** 单插件电话前缀(可选;给了即同一内核同时认单三电话,便于同网关共用一张 routes 表)。 */
111
+ prefix?: string;
112
+ /** 网关根(必填)。 */
113
+ baseUrl: string;
114
+ /** 电话级覆写表(可选;键为全电话名或短动作名,未知键拒绝)。 */
115
+ routes?: HttpRoutes;
116
+ /** 抓取注入点(可选)。 */
117
+ fetch?: HttpFetch;
118
+ fetchImpl?: HttpFetch;
119
+ /** 超时覆写(可选;batchInstall 走装时限 16min,其余走查/状态 15s)。 */
120
+ timeout?: number | HttpTimeouts;
121
+ timeoutMs?: number | HttpTimeouts;
122
+ }
123
+ /** 批量面板 HTTP 直挂:只传批量前缀加网关根即跑,内核复用 mountUpdateBatchPanel。
124
+ *
125
+ * 取消整批走 batchCancel 电话而不走中止等待(语义不混淆);卸载只停轮询,宿主侧照跑。 */
126
+ export declare function mountUpdateBatchPanelHttp(container: BatchPanelContainer, options: UpdateBatchPanelHttpOptions): BatchPanelController;
127
+ export { assertPollInterval };
package/dist/http.js ADDED
@@ -0,0 +1,300 @@
1
+ // AUTO-GENERATED by node build.mjs — DO NOT EDIT. Source: src/http.ts
2
+ import { DEFAULT_PREFIX, assertPrefix } from "./config.js";
3
+ import { assertPollInterval } from "./client.js";
4
+ import { DIAG_KEYS } from "./diag.js";
5
+ import { sanitizeDetail } from "./redaction.js";
6
+ import { mountUpdatePanel } from "./panel.js";
7
+ import { mountUpdateEntry } from "./entry.js";
8
+ import { mountUpdateBatchPanel } from "./panel-batch.js";
9
+ const HTTP_TRANSPORT_FAILED = "http-transport-failed";
10
+ const DEFAULT_HTTP_CHECK_TIMEOUT_MS = 15e3;
11
+ const DEFAULT_HTTP_INSTALL_TIMEOUT_MS = 16 * 6e4;
12
+ const DEFAULT_HTTP_STATUS_TIMEOUT_MS = 15e3;
13
+ const HTTP_PHONE_ACTIONS = ["updateStatus", "updateCheck", "updateInstall"];
14
+ const HTTP_BATCH_ACTIONS = ["batchStatus", "batchCheck", "batchInstall", "batchResume", "batchCancel"];
15
+ function isAbsoluteUrl(value) {
16
+ return /^https?:\/\//i.test(value);
17
+ }
18
+ function stripRightSlash(value) {
19
+ return String(value ?? "").replace(/\/+$/, "");
20
+ }
21
+ function joinBase(baseUrl, rel) {
22
+ const b = stripRightSlash(baseUrl);
23
+ let r = String(rel ?? "");
24
+ if (!r) return b;
25
+ if (!r.startsWith("/")) r = "/" + r;
26
+ return b + r;
27
+ }
28
+ function shortActionOf(phoneName) {
29
+ const name = String(phoneName ?? "");
30
+ const at = name.lastIndexOf(".");
31
+ return at >= 0 ? name.slice(at + 1) : name;
32
+ }
33
+ function allowedRouteKeys(prefix, batchPrefix) {
34
+ const out = /* @__PURE__ */ new Set();
35
+ for (const a of HTTP_PHONE_ACTIONS) {
36
+ out.add(a);
37
+ out.add(prefix + "." + a);
38
+ }
39
+ if (batchPrefix) {
40
+ for (const a of HTTP_BATCH_ACTIONS) {
41
+ out.add(a);
42
+ out.add(batchPrefix + "." + a);
43
+ }
44
+ }
45
+ return out;
46
+ }
47
+ function assertHttpRoutes(routes, prefix, batchPrefix) {
48
+ if (!routes) return;
49
+ const allowed = allowedRouteKeys(prefix, batchPrefix);
50
+ for (const key of Object.keys(routes)) {
51
+ if (!allowed.has(key)) {
52
+ throw new Error("[dsh-plugin-update] routes \u672A\u77E5\u952E\u62D2\u7EDD\uFF1A" + JSON.stringify(key));
53
+ }
54
+ const value = routes[key];
55
+ if (typeof value !== "string" || !value) {
56
+ throw new Error("[dsh-plugin-update] routes \u503C\u975E\u6CD5\uFF1A" + JSON.stringify(key));
57
+ }
58
+ }
59
+ }
60
+ function resolveHttpPath(options, phoneName) {
61
+ if (typeof phoneName !== "string" || !phoneName) {
62
+ throw new Error("[dsh-plugin-update] \u7535\u8BDD\u540D\u975E\u6CD5\uFF1A\u987B\u4E3A\u975E\u7A7A\u5B57\u7B26\u4E32");
63
+ }
64
+ const routes = options.routes ?? {};
65
+ assertHttpRoutes(routes, options.prefix, options.batchPrefix);
66
+ const short = shortActionOf(phoneName);
67
+ const hit = routes[phoneName] ?? routes[short];
68
+ if (hit !== void 0) {
69
+ if (isAbsoluteUrl(hit)) return hit;
70
+ return joinBase(options.baseUrl, hit);
71
+ }
72
+ return joinBase(options.baseUrl, phoneName);
73
+ }
74
+ function httpTransportFailed(reason, detail) {
75
+ const clean = sanitizeDetail(String(detail ?? reason ?? ""));
76
+ const err = new Error("[dsh-plugin-update] http-transport-failed: " + reason);
77
+ err.code = HTTP_TRANSPORT_FAILED;
78
+ err.detail = clean;
79
+ return err;
80
+ }
81
+ function isRecord(value) {
82
+ return !!value && typeof value === "object" && !Array.isArray(value);
83
+ }
84
+ function pickHttpDiag(raw) {
85
+ if (!isRecord(raw)) return void 0;
86
+ const out = {};
87
+ let kept = 0;
88
+ for (const key of DIAG_KEYS) {
89
+ const value = raw[key];
90
+ if (typeof value === "string" || typeof value === "number") {
91
+ out[key] = key === "detail" ? sanitizeDetail(String(value)) : value;
92
+ kept++;
93
+ }
94
+ }
95
+ return kept > 0 ? out : void 0;
96
+ }
97
+ function shapeHttpReply(parsed) {
98
+ if (!isRecord(parsed)) {
99
+ throw httpTransportFailed("\u56DE\u5305\u5F62\u72B6\u975E\u6CD5", String(parsed).slice(0, 120));
100
+ }
101
+ const out = { ...parsed };
102
+ if ("diagnostic" in out) delete out["diagnostic"];
103
+ if ("diag" in out) {
104
+ const kept = pickHttpDiag(out["diag"]);
105
+ if (kept) out["diag"] = kept;
106
+ else delete out["diag"];
107
+ }
108
+ if (Array.isArray(out["rows"])) {
109
+ out["rows"] = out["rows"].map((row) => {
110
+ if (!isRecord(row)) return row;
111
+ const copy = { ...row };
112
+ if ("diagnostic" in copy) delete copy["diagnostic"];
113
+ if ("diag" in copy) {
114
+ const kept = pickHttpDiag(copy["diag"]);
115
+ if (kept) copy["diag"] = kept;
116
+ else delete copy["diag"];
117
+ }
118
+ return copy;
119
+ });
120
+ }
121
+ return out;
122
+ }
123
+ function timeoutForPhone(phoneName, timeout) {
124
+ if (typeof timeout === "number" && Number.isFinite(timeout) && timeout > 0) return timeout;
125
+ const t = timeout ?? {};
126
+ if (phoneName.endsWith(".updateInstall") || phoneName.endsWith(".batchInstall")) {
127
+ if (typeof t.installMs === "number" && Number.isFinite(t.installMs) && t.installMs > 0) return t.installMs;
128
+ return DEFAULT_HTTP_INSTALL_TIMEOUT_MS;
129
+ }
130
+ if (phoneName.endsWith(".updateCheck") || phoneName.endsWith(".batchCheck")) {
131
+ if (typeof t.checkMs === "number" && Number.isFinite(t.checkMs) && t.checkMs > 0) return t.checkMs;
132
+ return DEFAULT_HTTP_CHECK_TIMEOUT_MS;
133
+ }
134
+ if (typeof t.statusMs === "number" && Number.isFinite(t.statusMs) && t.statusMs > 0) return t.statusMs;
135
+ if (typeof t.checkMs === "number" && Number.isFinite(t.checkMs) && t.checkMs > 0) return t.checkMs;
136
+ return DEFAULT_HTTP_STATUS_TIMEOUT_MS;
137
+ }
138
+ function resolveFetchImpl(options) {
139
+ const impl = options.fetch ?? options.fetchImpl ?? globalThis.fetch;
140
+ if (typeof impl !== "function") {
141
+ throw new Error("[dsh-plugin-update] HTTP \u4F20\u8F93\u7F3A\u5C11\u6293\u53D6\u51FD\u6570\uFF1A\u8BF7\u4F20 fetch \u6216\u5728\u6709 globalThis.fetch \u7684\u73AF\u5883\u8FD0\u884C");
142
+ }
143
+ return impl;
144
+ }
145
+ function createHttpCall(options) {
146
+ if (!options || typeof options !== "object") {
147
+ throw new Error("[dsh-plugin-update] \u5EFA HTTP \u4F20\u8F93\u7F3A\u5C11\u914D\u7F6E\uFF1A\u524D\u7F00 prefix \u4E0E\u7F51\u5173\u6839 baseUrl \u5FC5\u586B");
148
+ }
149
+ const prefix = options.prefix;
150
+ if (typeof prefix !== "string" || !prefix) {
151
+ throw new Error("[dsh-plugin-update] \u7535\u8BDD\u540D\u524D\u7F00 prefix \u975E\u6CD5\uFF1A\u987B\u4E3A\u975E\u7A7A\u5B57\u7B26\u4E32");
152
+ }
153
+ if (prefix.includes(".") || prefix.includes("/") || prefix.includes("\\") || /\s/.test(prefix)) {
154
+ throw new Error("[dsh-plugin-update] \u7535\u8BDD\u540D\u524D\u7F00 prefix \u975E\u6CD5\uFF1A\u4E0D\u5F97\u542B\u6709\u70B9\u3001\u8DEF\u5F84\u5206\u9694\u7B26\u6216\u7A7A\u767D");
155
+ }
156
+ const baseUrl = options.baseUrl;
157
+ if (typeof baseUrl !== "string" || !baseUrl.trim()) {
158
+ throw new Error("[dsh-plugin-update] \u7F51\u5173\u6839 baseUrl \u5FC5\u586B\uFF1A\u987B\u4E3A\u975E\u7A7A\u5B57\u7B26\u4E32");
159
+ }
160
+ const routes = options.routes ?? {};
161
+ assertHttpRoutes(routes, prefix, options.batchPrefix);
162
+ const fetchImpl = resolveFetchImpl(options);
163
+ const timeout = options.timeout ?? options.timeoutMs;
164
+ return async function httpCall(phoneName, args) {
165
+ if (typeof phoneName !== "string" || !phoneName) {
166
+ throw new Error("[dsh-plugin-update] \u7535\u8BDD\u540D\u975E\u6CD5\uFF1A\u987B\u4E3A\u975E\u7A7A\u5B57\u7B26\u4E32");
167
+ }
168
+ const url = resolveHttpPath({ prefix, batchPrefix: options.batchPrefix, baseUrl, routes }, phoneName);
169
+ const safeArgs = args && typeof args === "object" ? args : {};
170
+ const timeoutMs = timeoutForPhone(phoneName, timeout);
171
+ const body = JSON.stringify(stripSignal(safeArgs));
172
+ const AbortCtor = globalThis.AbortController;
173
+ const controller = typeof AbortCtor === "function" ? new AbortCtor() : null;
174
+ const callerSignal = safeArgs["signal"];
175
+ const onCallerAbort = () => {
176
+ try {
177
+ controller?.abort();
178
+ } catch {
179
+ }
180
+ };
181
+ if (callerSignal && typeof callerSignal.addEventListener === "function") {
182
+ try {
183
+ callerSignal.addEventListener.call(callerSignal, "abort", onCallerAbort, { once: true });
184
+ } catch {
185
+ }
186
+ }
187
+ let res;
188
+ const fetchPromise = fetchImpl(url, {
189
+ method: "POST",
190
+ headers: { "content-type": "application/json" },
191
+ body,
192
+ ...controller ? { signal: controller.signal } : {}
193
+ });
194
+ let timeoutTimer = null;
195
+ void fetchPromise.catch(() => {
196
+ });
197
+ try {
198
+ res = await Promise.race([
199
+ fetchPromise,
200
+ new Promise((_, reject) => {
201
+ timeoutTimer = setTimeout(() => {
202
+ try {
203
+ controller?.abort();
204
+ } catch {
205
+ }
206
+ reject(httpTransportFailed("\u8D85\u65F6", "\u6293\u53D6\u8D85\u65F6\uFF08" + String(timeoutMs) + "ms\uFF09\uFF1A" + phoneName));
207
+ }, timeoutMs);
208
+ })
209
+ ]);
210
+ } catch (e) {
211
+ if (timeoutTimer) clearTimeout(timeoutTimer);
212
+ if (e && typeof e === "object" && e.code === HTTP_TRANSPORT_FAILED) throw e;
213
+ throw httpTransportFailed(isAbortError(e) ? "\u8D85\u65F6\u6216\u53D6\u6D88" : "\u65AD\u7F51\u6216\u6293\u53D6\u5931\u8D25", e instanceof Error ? e.message : String(e));
214
+ }
215
+ if (timeoutTimer) clearTimeout(timeoutTimer);
216
+ const ok = res.ok === true;
217
+ if (!ok) {
218
+ const status = res.status;
219
+ const text2 = await readReplyText(res).catch(() => "");
220
+ throw httpTransportFailed("\u975E 2xx\uFF08" + String(status ?? "unknown") + "\uFF09", "HTTP " + String(status ?? "?") + ": " + String(text2).slice(0, 120));
221
+ }
222
+ const text = await readReplyText(res);
223
+ let parsed;
224
+ try {
225
+ parsed = JSON.parse(text);
226
+ } catch {
227
+ throw httpTransportFailed("JSON \u975E\u6CD5", String(text).slice(0, 120));
228
+ }
229
+ return shapeHttpReply(parsed);
230
+ };
231
+ }
232
+ function stripSignal(args) {
233
+ if (!("signal" in args)) return args;
234
+ const out = { ...args };
235
+ delete out["signal"];
236
+ return out;
237
+ }
238
+ function isAbortError(e) {
239
+ const name = e.name;
240
+ return name === "AbortError" || name === "TimeoutError";
241
+ }
242
+ async function readReplyText(res) {
243
+ const textFn = res.text;
244
+ if (typeof textFn === "function") return String(await textFn.call(res));
245
+ const jsonFn = res.json;
246
+ if (typeof jsonFn === "function") return JSON.stringify(await jsonFn.call(res));
247
+ throw httpTransportFailed("\u56DE\u5305\u5F62\u72B6\u975E\u6CD5", "\u6293\u53D6\u56DE\u5305\u65E2\u65E0 text() \u4E5F\u65E0 json()");
248
+ }
249
+ function mountUpdatePanelHttp(container, options) {
250
+ if (!options || typeof options !== "object") {
251
+ throw new Error("[dsh-plugin-update] \u6302 HTTP \u9762\u677F\u7F3A\u5C11\u914D\u7F6E\uFF1A\u63D2\u4EF6\u6807\u8BC6 pluginId \u4E0E\u7F51\u5173\u6839 baseUrl \u5FC5\u586B");
252
+ }
253
+ const prefix = options.prefix ?? DEFAULT_PREFIX;
254
+ const call = createHttpCall({ prefix, baseUrl: options.baseUrl, routes: options.routes, fetch: options.fetch, fetchImpl: options.fetchImpl, timeout: options.timeout, timeoutMs: options.timeoutMs });
255
+ const { baseUrl: _b, routes: _r, fetch: _f, fetchImpl: _fi, timeout: _t, timeoutMs: _tm, prefix: _p, ...panelRest } = options;
256
+ return mountUpdatePanel(container, { ...panelRest, pluginId: options.pluginId, prefix, call });
257
+ }
258
+ function mountUpdateEntryHttp(container, options) {
259
+ if (!options || typeof options !== "object") {
260
+ throw new Error("[dsh-plugin-update] \u6302 HTTP \u5165\u53E3\u4EF6\u7F3A\u5C11\u914D\u7F6E\uFF1A\u63D2\u4EF6\u6807\u8BC6 pluginId \u4E0E\u7F51\u5173\u6839 baseUrl \u5FC5\u586B");
261
+ }
262
+ const prefix = options.prefix ?? DEFAULT_PREFIX;
263
+ const rawCall = createHttpCall({ prefix, baseUrl: options.baseUrl, routes: options.routes, fetch: options.fetch, fetchImpl: options.fetchImpl, timeout: options.timeout, timeoutMs: options.timeoutMs });
264
+ const guarded = async (name, args) => {
265
+ if (shortActionOf(name) === "updateInstall") {
266
+ throw new Error("[dsh-plugin-update] \u5165\u53E3\u4EF6\u53EA\u5141\u8BB8\u53EA\u8BFB\u7535\u8BDD\uFF0CupdateInstall \u7981\u6B62\uFF08\u6E90\u7801\u7EA7\uFF09");
267
+ }
268
+ return rawCall(name, args);
269
+ };
270
+ const { baseUrl: _b, routes: _r, fetch: _f, fetchImpl: _fi, timeout: _t, timeoutMs: _tm, prefix: _p, ...entryRest } = options;
271
+ return mountUpdateEntry(container, { ...entryRest, pluginId: options.pluginId, prefix, call: guarded });
272
+ }
273
+ function mountUpdateBatchPanelHttp(container, options) {
274
+ if (!options || typeof options !== "object") {
275
+ throw new Error("[dsh-plugin-update] \u6302 HTTP \u6279\u91CF\u9762\u677F\u7F3A\u5C11\u914D\u7F6E\uFF1A\u6279\u91CF\u524D\u7F00 batchPrefix \u4E0E\u7F51\u5173\u6839 baseUrl \u5FC5\u586B");
276
+ }
277
+ const batchPrefix = assertPrefix(options.batchPrefix, "\u6279\u91CF\u7535\u8BDD\u524D\u7F00 batchPrefix");
278
+ const kernelPrefix = typeof options.prefix === "string" && options.prefix ? assertPrefix(options.prefix, "\u7535\u8BDD\u540D\u524D\u7F00 prefix") : batchPrefix;
279
+ const call = createHttpCall({ prefix: kernelPrefix, batchPrefix, baseUrl: options.baseUrl, routes: options.routes, fetch: options.fetch, fetchImpl: options.fetchImpl, timeout: options.timeout, timeoutMs: options.timeoutMs });
280
+ const { baseUrl: _b, routes: _r, fetch: _f, fetchImpl: _fi, timeout: _t, timeoutMs: _tm, prefix: _p, batchPrefix: _bp, ...batchRest } = options;
281
+ return mountUpdateBatchPanel(container, { ...batchRest, prefix: batchPrefix, call });
282
+ }
283
+ export {
284
+ DEFAULT_HTTP_CHECK_TIMEOUT_MS,
285
+ DEFAULT_HTTP_INSTALL_TIMEOUT_MS,
286
+ DEFAULT_HTTP_STATUS_TIMEOUT_MS,
287
+ HTTP_BATCH_ACTIONS,
288
+ HTTP_PHONE_ACTIONS,
289
+ HTTP_TRANSPORT_FAILED,
290
+ assertHttpRoutes,
291
+ assertPollInterval,
292
+ createHttpCall,
293
+ httpTransportFailed,
294
+ mountUpdateBatchPanelHttp,
295
+ mountUpdateEntryHttp,
296
+ mountUpdatePanelHttp,
297
+ pickHttpDiag,
298
+ resolveHttpPath,
299
+ shapeHttpReply
300
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-update",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "可复用的更新系统装成的 npm 包:任何 DSH 插件照文档能集成检查更新等功能(地图 #579)",
5
5
  "license": "MIT",
6
6
  "author": "王辰浩",
@@ -38,6 +38,10 @@
38
38
  "types": "./dist/entry.d.ts",
39
39
  "default": "./dist/entry.js"
40
40
  },
41
+ "./http": {
42
+ "types": "./dist/http.d.ts",
43
+ "default": "./dist/http.js"
44
+ },
41
45
  "./package.json": "./package.json"
42
46
  },
43
47
  "files": [