@huaqiu/dsh-tool-plugin-update 0.6.1 → 0.6.3
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 +516 -13
- package/lib/client.js.map +1 -1
- package/lib/index.mjs +86 -18
- package/package.json +4 -4
- package/src/client/api.ts +156 -1
- package/src/client/copy.ts +65 -0
- package/src/client/index.ts +63 -0
- package/src/client/update-view.ts +513 -8
- package/src/routes.ts +110 -12
package/src/routes.ts
CHANGED
|
@@ -17,6 +17,21 @@
|
|
|
17
17
|
* POST /plugin-update/ignore → POST /api/v1/plugin-updates/ignore {setVersion}
|
|
18
18
|
* POST /plugin-update/unignore → POST /api/v1/plugin-updates/unignore {setVersion}
|
|
19
19
|
* GET /plugin-update/runtime → GET /api/v1/runtime-update
|
|
20
|
+
* POST /plugin-update/runtime-request
|
|
21
|
+
* → POST /api/v1/runtime-update/request {targetVersion}
|
|
22
|
+
* GET /plugin-update/runtime-source
|
|
23
|
+
* → GET /api/v1/runtime-update/source
|
|
24
|
+
* POST /plugin-update/runtime-source
|
|
25
|
+
* → POST /api/v1/runtime-update/source {url}
|
|
26
|
+
* GET /plugin-update/plugin-source
|
|
27
|
+
* → GET /api/v1/plugin-updates/source
|
|
28
|
+
* POST /plugin-update/plugin-source
|
|
29
|
+
* → POST /api/v1/plugin-updates/source {url}
|
|
30
|
+
*
|
|
31
|
+
* The runtime-update request is user-confirmed in this plugin's review panel:
|
|
32
|
+
* hq-edge validates the version against a fresh manifest, downloads and
|
|
33
|
+
* verifies the bundle, stages it, then hands it to the EDA host and answers
|
|
34
|
+
* `202 Accepted` before shutting itself down to be swapped and relaunched.
|
|
20
35
|
*
|
|
21
36
|
* @module
|
|
22
37
|
*/
|
|
@@ -27,6 +42,23 @@ export const UPDATE_ROUTE_PREFIX = '/api/v1/huaqiu/plugin-update'
|
|
|
27
42
|
/** End-to-end budget for one forwarded request, in milliseconds. */
|
|
28
43
|
export const DEFAULT_FORWARD_TIMEOUT_MS = 30_000
|
|
29
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Budget for `POST /runtime-update/request` — deliberately far larger than
|
|
47
|
+
* {@link DEFAULT_FORWARD_TIMEOUT_MS}.
|
|
48
|
+
*
|
|
49
|
+
* This route is not a quick RPC. Before it answers, hq-edge re-reads the
|
|
50
|
+
* update manifest, downloads the entire runtime bundle (hundreds of MB),
|
|
51
|
+
* verifies its SHA-256, extracts it into the shared staging root and validates
|
|
52
|
+
* the layout + version stamp. Measured end-to-end against a local update
|
|
53
|
+
* server: 56 s (16:49:49 → 16:50:45).
|
|
54
|
+
*
|
|
55
|
+
* Under the generic 30 s budget the forwarder aborts mid-flight and answers
|
|
56
|
+
* `504 hq-edge request timed out after 30000ms`, so the panel reports a failure
|
|
57
|
+
* for an update that is still running perfectly well — and which then goes on
|
|
58
|
+
* to complete, swap the runtime and restart HQ Edge underneath the error toast.
|
|
59
|
+
*/
|
|
60
|
+
export const RUNTIME_REQUEST_TIMEOUT_MS = 600_000
|
|
61
|
+
|
|
30
62
|
export type BaseUrlResolver = () => string | undefined
|
|
31
63
|
|
|
32
64
|
function sendJson(res: ServerResponse, status: number, body: unknown): void {
|
|
@@ -66,21 +98,76 @@ function readBody(req: IncomingMessage): Promise<string> {
|
|
|
66
98
|
})
|
|
67
99
|
}
|
|
68
100
|
|
|
69
|
-
/**
|
|
70
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Map a sub-path + method to { method, path } on the hq-edge API.
|
|
103
|
+
*
|
|
104
|
+
* `timeoutMs` overrides {@link DEFAULT_FORWARD_TIMEOUT_MS} for routes whose
|
|
105
|
+
* upstream work legitimately outlasts it (only `/runtime-request` today).
|
|
106
|
+
*
|
|
107
|
+
* The method is part of the lookup because one sub-path can carry both a read
|
|
108
|
+
* and a write: `/runtime-source` is GET for the current value and POST to
|
|
109
|
+
* change it. A method the route does not declare is `null`, which the
|
|
110
|
+
* forwarder answers 405 rather than forwarding a GET to a POST-only upstream
|
|
111
|
+
* and surfacing hq-edge's 404 as a plugin bug.
|
|
112
|
+
*/
|
|
113
|
+
function routeFor(
|
|
114
|
+
sub: string,
|
|
115
|
+
method: string,
|
|
116
|
+
): { method: string; path: string; timeoutMs?: number } | null {
|
|
117
|
+
if (sub === '/runtime-source') {
|
|
118
|
+
if (method === 'GET') return { method: 'GET', path: '/api/v1/runtime-update/source' }
|
|
119
|
+
if (method === 'POST') return { method: 'POST', path: '/api/v1/runtime-update/source' }
|
|
120
|
+
return null
|
|
121
|
+
}
|
|
122
|
+
if (sub === '/plugin-source') {
|
|
123
|
+
if (method === 'GET') return { method: 'GET', path: '/api/v1/plugin-updates/source' }
|
|
124
|
+
if (method === 'POST') return { method: 'POST', path: '/api/v1/plugin-updates/source' }
|
|
125
|
+
return null
|
|
126
|
+
}
|
|
71
127
|
if (sub === '/report' || sub === '') {
|
|
72
|
-
return { method: 'GET', path: '/api/v1/plugin-updates' }
|
|
128
|
+
return method === 'GET' ? { method: 'GET', path: '/api/v1/plugin-updates' } : null
|
|
73
129
|
}
|
|
74
130
|
if (sub === '/runtime') {
|
|
75
|
-
return { method: 'GET', path: '/api/v1/runtime-update' }
|
|
131
|
+
return method === 'GET' ? { method: 'GET', path: '/api/v1/runtime-update' } : null
|
|
132
|
+
}
|
|
133
|
+
if (sub === '/runtime-request') {
|
|
134
|
+
return method === 'POST'
|
|
135
|
+
? {
|
|
136
|
+
method: 'POST',
|
|
137
|
+
path: '/api/v1/runtime-update/request',
|
|
138
|
+
timeoutMs: RUNTIME_REQUEST_TIMEOUT_MS,
|
|
139
|
+
}
|
|
140
|
+
: null
|
|
141
|
+
}
|
|
142
|
+
if (sub === '/check') {
|
|
143
|
+
return method === 'POST' ? { method: 'POST', path: '/api/v1/plugin-updates/check' } : null
|
|
144
|
+
}
|
|
145
|
+
if (sub === '/install') {
|
|
146
|
+
return method === 'POST' ? { method: 'POST', path: '/api/v1/plugin-updates/install' } : null
|
|
147
|
+
}
|
|
148
|
+
if (sub === '/ignore') {
|
|
149
|
+
return method === 'POST' ? { method: 'POST', path: '/api/v1/plugin-updates/ignore' } : null
|
|
150
|
+
}
|
|
151
|
+
if (sub === '/unignore') {
|
|
152
|
+
return method === 'POST' ? { method: 'POST', path: '/api/v1/plugin-updates/unignore' } : null
|
|
76
153
|
}
|
|
77
|
-
if (sub === '/check') return { method: 'POST', path: '/api/v1/plugin-updates/check' }
|
|
78
|
-
if (sub === '/install') return { method: 'POST', path: '/api/v1/plugin-updates/install' }
|
|
79
|
-
if (sub === '/ignore') return { method: 'POST', path: '/api/v1/plugin-updates/ignore' }
|
|
80
|
-
if (sub === '/unignore') return { method: 'POST', path: '/api/v1/plugin-updates/unignore' }
|
|
81
154
|
return null
|
|
82
155
|
}
|
|
83
156
|
|
|
157
|
+
/** Every sub-path this forwarder serves — used to tell a 404 from a 405. */
|
|
158
|
+
const ROUTE_SUB_PATHS = new Set([
|
|
159
|
+
'',
|
|
160
|
+
'/report',
|
|
161
|
+
'/check',
|
|
162
|
+
'/install',
|
|
163
|
+
'/ignore',
|
|
164
|
+
'/unignore',
|
|
165
|
+
'/runtime',
|
|
166
|
+
'/runtime-request',
|
|
167
|
+
'/runtime-source',
|
|
168
|
+
'/plugin-source',
|
|
169
|
+
])
|
|
170
|
+
|
|
84
171
|
/** Create the prefix-route handler forwarding to hq-edge. */
|
|
85
172
|
export function createUpdateForwarder(
|
|
86
173
|
baseUrlResolver: BaseUrlResolver,
|
|
@@ -92,9 +179,19 @@ export function createUpdateForwarder(
|
|
|
92
179
|
sendJson(res, 404, { error: 'not found' })
|
|
93
180
|
return
|
|
94
181
|
}
|
|
95
|
-
|
|
182
|
+
// HEAD reads like GET: it never carried its own route, and the upstream
|
|
183
|
+
// has no HEAD semantics to preserve.
|
|
184
|
+
const method = req.method === 'HEAD' ? 'GET' : (req.method ?? '')
|
|
185
|
+
const target = routeFor(sub, method)
|
|
96
186
|
if (target === null) {
|
|
97
|
-
|
|
187
|
+
// Either an unknown sub-path (404) or a known one under a method it does
|
|
188
|
+
// not serve (405). Both are client errors caught here, not upstream.
|
|
189
|
+
sendJson(res, sub === '' || ROUTE_SUB_PATHS.has(sub) ? 405 : 404, {
|
|
190
|
+
error:
|
|
191
|
+
sub === '' || ROUTE_SUB_PATHS.has(sub)
|
|
192
|
+
? `method ${req.method} not allowed on ${sub || '/'}`
|
|
193
|
+
: 'not found',
|
|
194
|
+
})
|
|
98
195
|
return
|
|
99
196
|
}
|
|
100
197
|
if (req.method !== 'GET' && req.method !== 'POST' && req.method !== 'HEAD') {
|
|
@@ -115,8 +212,9 @@ export function createUpdateForwarder(
|
|
|
115
212
|
return
|
|
116
213
|
}
|
|
117
214
|
}
|
|
215
|
+
const budget = target.timeoutMs ?? timeoutMs
|
|
118
216
|
const controller = new AbortController()
|
|
119
|
-
const timer = setTimeout(() => controller.abort(),
|
|
217
|
+
const timer = setTimeout(() => controller.abort(), budget)
|
|
120
218
|
try {
|
|
121
219
|
const upstream = await fetch(`${base}${target.path}`, {
|
|
122
220
|
method: target.method,
|
|
@@ -138,7 +236,7 @@ export function createUpdateForwarder(
|
|
|
138
236
|
} catch (err) {
|
|
139
237
|
const message = err instanceof Error ? err.message : String(err)
|
|
140
238
|
if (err instanceof Error && err.name === 'AbortError') {
|
|
141
|
-
sendJson(res, 504, { error: `hq-edge request timed out after ${
|
|
239
|
+
sendJson(res, 504, { error: `hq-edge request timed out after ${budget}ms` })
|
|
142
240
|
return
|
|
143
241
|
}
|
|
144
242
|
sendJson(res, 502, { error: `hq-edge request failed: ${message}` })
|