dsh-unlag 1.9.36 → 1.9.38
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/client.js +3 -2
- package/index.js +31 -4
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -15,8 +15,8 @@ window.__ModuleLoader__.load({
|
|
|
15
15
|
* repeating one generic sentence.
|
|
16
16
|
*/
|
|
17
17
|
const REASONS = zh
|
|
18
|
-
? { list: '读不到启动信息', slow: '启动信息读得太慢', row: '启动信息不完整', src: '启动脚本没有地址', graph: '已装插件清单读不到', plugin: '有插件已不在运行', script: '启动脚本读不到', empty: '启动脚本是空的', covers: '启动脚本缺插件', parse: '启动脚本内容不完整', verify: '插件后端没有应答' }
|
|
19
|
-
: { list: 'startup info is unavailable', slow: 'the startup list answered too slowly', row: 'startup info is incomplete', src: 'a startup script has no address', graph: 'the plugin list cannot be read', plugin: 'a plugin is no longer running', script: 'a startup script cannot be read', empty: 'a startup script is empty', covers: 'a startup script misses a plugin', parse: 'a startup script is incomplete', verify: 'the plugin is not answering' };
|
|
18
|
+
? { list: '读不到启动信息', slow: '启动信息读得太慢', row: '启动信息不完整', src: '启动脚本没有地址', graph: '已装插件清单读不到', plugin: '有插件已不在运行', script: '启动脚本读不到', empty: '启动脚本是空的', covers: '启动脚本缺插件', parse: '启动脚本内容不完整', size: '启动脚本太大', verify: '插件后端没有应答' }
|
|
19
|
+
: { list: 'startup info is unavailable', slow: 'the startup list answered too slowly', row: 'startup info is incomplete', src: 'a startup script has no address', graph: 'the plugin list cannot be read', plugin: 'a plugin is no longer running', script: 'a startup script cannot be read', empty: 'a startup script is empty', covers: 'a startup script misses a plugin', parse: 'a startup script is incomplete', size: 'a startup script is too large', verify: 'the plugin is not answering' };
|
|
20
20
|
|
|
21
21
|
/** The last reason a readiness check refused for, or null after a check that passed. */
|
|
22
22
|
let holdReason = null;
|
|
@@ -244,6 +244,7 @@ window.__ModuleLoader__.load({
|
|
|
244
244
|
missing: 'script', // ui-copy-ignore
|
|
245
245
|
network: 'script', // ui-copy-ignore
|
|
246
246
|
parse: 'parse', // ui-copy-ignore
|
|
247
|
+
size: 'script', // ui-copy-ignore
|
|
247
248
|
register: 'plugin', // ui-copy-ignore
|
|
248
249
|
slow: 'slow', // ui-copy-ignore
|
|
249
250
|
dependencies: 'covers',
|
package/index.js
CHANGED
|
@@ -91,6 +91,14 @@ const DEFAULT_ROOTS = [join(homedir(), '.dsh', 'plugins')]
|
|
|
91
91
|
const MAX_DEFERRALS = 40
|
|
92
92
|
const MAX_STARTUP_ROWS = 64
|
|
93
93
|
const MAX_STARTUP_ROW_CHARS = 4096
|
|
94
|
+
/**
|
|
95
|
+
* The largest one startup script may be before a check refuses it. A real client bundle is far
|
|
96
|
+
* below this; a body past it is either a mistake or an attempt to make a check read (and compile)
|
|
97
|
+
* an unbounded amount of text inside the process that owns every running turn.
|
|
98
|
+
*/
|
|
99
|
+
const MAX_SCRIPT_BYTES = 8 * 1024 * 1024
|
|
100
|
+
/** The same bound for one whole startup list, so a list of large bodies cannot add up either. */
|
|
101
|
+
const MAX_TOTAL_SCRIPT_BYTES = 32 * 1024 * 1024
|
|
94
102
|
|
|
95
103
|
/**
|
|
96
104
|
* The most plugin ids one bundle address may name. A real startup list is far below this; a made-up
|
|
@@ -98,6 +106,18 @@ const MAX_STARTUP_ROW_CHARS = 4096
|
|
|
98
106
|
* is work the request itself must not be able to grow without bound.
|
|
99
107
|
*/
|
|
100
108
|
const MAX_BUNDLE_IDS = 128
|
|
109
|
+
/**
|
|
110
|
+
* How long one stale-bundle answer may spend before it gives the request back to the app's own
|
|
111
|
+
* route. Each row is bounded on its own, but a request can name many rows and the route answers
|
|
112
|
+
* whatever a socket asks: without a whole-request bound one made-up address holds a connection for
|
|
113
|
+
* as long as its rows add up.
|
|
114
|
+
*/
|
|
115
|
+
const MAX_ANSWER_MS = 15_000
|
|
116
|
+
/** The same bound for a test that must reach it quickly. */
|
|
117
|
+
function answerBudgetMs() {
|
|
118
|
+
const set = Number(process.env.DSH_UNLAG_STALE_MS ?? '')
|
|
119
|
+
return set > 0 ? set : MAX_ANSWER_MS
|
|
120
|
+
}
|
|
101
121
|
/**
|
|
102
122
|
* How long one startup check may spend reading the list. Each address is bounded on its own, but a
|
|
103
123
|
* list of them is not: dozens of slow addresses would otherwise hold a press past the moment the
|
|
@@ -966,10 +986,11 @@ async function currentEntryBody(clientModules, graph, id) {
|
|
|
966
986
|
* answering with them in the requested order registers exactly the plugins the document expects,
|
|
967
987
|
* so a boot that would otherwise 404 and end the app comes up instead.
|
|
968
988
|
*/
|
|
969
|
-
async function regroupedBundleAnswer(clientModules, graph, request) {
|
|
989
|
+
async function regroupedBundleAnswer(clientModules, graph, request, deadline = Date.now() + answerBudgetMs()) {
|
|
970
990
|
if (request.sourceMap) return null
|
|
971
991
|
const parts = []
|
|
972
992
|
for (const id of request.ids) {
|
|
993
|
+
if (Date.now() > deadline) return null
|
|
973
994
|
const body = await currentEntryBody(clientModules, graph, id)
|
|
974
995
|
if (body === null) return null
|
|
975
996
|
parts.push(body)
|
|
@@ -997,7 +1018,7 @@ async function regroupedBundleAnswer(clientModules, graph, request) {
|
|
|
997
1018
|
* booting from an old startup list that meets one ends the whole app: a boot that comes up without
|
|
998
1019
|
* a plugin it no longer has beats that.
|
|
999
1020
|
*/
|
|
1000
|
-
async function survivingBundleAnswer(clientModules, graph, request) {
|
|
1021
|
+
async function survivingBundleAnswer(clientModules, graph, request, deadline = Date.now() + answerBudgetMs()) {
|
|
1001
1022
|
if (request.sourceMap) return null
|
|
1002
1023
|
const entries = Array.isArray(graph?.entries) ? graph.entries : []
|
|
1003
1024
|
const served = new Set(entries.filter((row) => typeof row?.id === 'string').map((row) => row.id))
|
|
@@ -1005,6 +1026,7 @@ async function survivingBundleAnswer(clientModules, graph, request) {
|
|
|
1005
1026
|
if (gone.length === 0) return null
|
|
1006
1027
|
const parts = []
|
|
1007
1028
|
for (const id of request.ids) {
|
|
1029
|
+
if (Date.now() > deadline) return null
|
|
1008
1030
|
if (served.has(id)) {
|
|
1009
1031
|
const body = await currentEntryBody(clientModules, graph, id)
|
|
1010
1032
|
if (body === null) return null
|
|
@@ -1023,7 +1045,7 @@ async function survivingBundleAnswer(clientModules, graph, request) {
|
|
|
1023
1045
|
* that really contains every plugin it claims is handed back; anything unsure answers null, so the
|
|
1024
1046
|
* route then behaves exactly as before and an unknown id still ends as a plain 404.
|
|
1025
1047
|
*/
|
|
1026
|
-
async function currentBundleAnswer(clientModules, method, url) {
|
|
1048
|
+
async function currentBundleAnswer(clientModules, method, url, deadline = Date.now() + answerBudgetMs()) {
|
|
1027
1049
|
const request = bundleRequest(url)
|
|
1028
1050
|
if (request === null) return null
|
|
1029
1051
|
const graph = clientModules.graph()
|
|
@@ -1049,7 +1071,7 @@ async function currentBundleAnswer(clientModules, method, url) {
|
|
|
1049
1071
|
}
|
|
1050
1072
|
}
|
|
1051
1073
|
}
|
|
1052
|
-
return (await regroupedBundleAnswer(clientModules, graph, request)) ?? survivingBundleAnswer(clientModules, graph, request)
|
|
1074
|
+
return (await regroupedBundleAnswer(clientModules, graph, request, deadline)) ?? survivingBundleAnswer(clientModules, graph, request, deadline)
|
|
1053
1075
|
}
|
|
1054
1076
|
|
|
1055
1077
|
/** Answer one stale request over the plain request/response route an app registers. */
|
|
@@ -1468,6 +1490,7 @@ export async function verifyStartupRows(graph, fetchBody, rows, budgetMs = MAX_V
|
|
|
1468
1490
|
const listed = new Set()
|
|
1469
1491
|
const bodies = []
|
|
1470
1492
|
const began = Date.now()
|
|
1493
|
+
let totalBytes = 0
|
|
1471
1494
|
for (const item of wanted) {
|
|
1472
1495
|
if (Date.now() - began > budgetMs) {
|
|
1473
1496
|
return { status: 'blocked', reason: 'slow', detail: 'the startup list took longer than this check waits' }
|
|
@@ -1485,6 +1508,10 @@ export async function verifyStartupRows(graph, fetchBody, rows, budgetMs = MAX_V
|
|
|
1485
1508
|
}
|
|
1486
1509
|
const bytes = Buffer.from(await response.arrayBuffer())
|
|
1487
1510
|
if (bytes.length === 0) return { status: 'blocked', reason: 'empty', detail: `${item.url} answers an empty body` }
|
|
1511
|
+
totalBytes += bytes.length
|
|
1512
|
+
if (bytes.length > MAX_SCRIPT_BYTES || totalBytes > MAX_TOTAL_SCRIPT_BYTES) {
|
|
1513
|
+
return { status: 'blocked', reason: 'size', detail: `${item.url} answers ${String(bytes.length)} bytes; this check reads at most ${String(MAX_SCRIPT_BYTES)} per script and ${String(MAX_TOTAL_SCRIPT_BYTES)} in all` }
|
|
1514
|
+
}
|
|
1488
1515
|
const text = bytes.toString('utf8')
|
|
1489
1516
|
const complaint = parseAsScript(text, item.url)
|
|
1490
1517
|
if (complaint !== null) return { status: 'blocked', reason: 'parse', detail: `${item.url}: ${complaint}` }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-unlag",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.38",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A press applies plugin edits right away and brings a laggy interface back; automatic reloading stays off unless you turn it on, so the press is the only trigger, and nothing restarts.",
|
|
6
6
|
"license": "MIT",
|