dsh-unlag 1.9.21 → 1.9.23
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/README.md +2 -0
- package/client.js +9 -1
- package/index.js +67 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -65,6 +65,8 @@ dsh plugin install dsh-unlag
|
|
|
65
65
|
|
|
66
66
|
官方的插件资源路由对未知修订返回 404。本插件对旧页面记着的旧修订返回当前内容:桌面版在窗口启动时固定了启动清单,页面按旧修订请求脚本是常态,返回 404 会让新页面起不来、App 直接退出。这是本插件的兼容措施,不是官方行为;不需要时可以整个关掉(设置里两行都关)。
|
|
67
67
|
|
|
68
|
+
同一原因下的第二种情况:启动清单里记着的插件在配置里已经被关掉或删掉了,官方路由对那一行只会 404。本插件改为返回一段空脚本占位,页面起来时不再加载这个插件,App 照常起来;日志会写明哪个插件被留下,`/dsh-unlag/verify` 的结论里也带 `leftOut` 字段。
|
|
69
|
+
|
|
68
70
|
## 排错
|
|
69
71
|
|
|
70
72
|
- 日志:`~/.dsh/unlag-reload.log`,换代码/跳过/被拦各一行,含原因。
|
package/client.js
CHANGED
|
@@ -606,7 +606,15 @@ window.__ModuleLoader__.load({
|
|
|
606
606
|
: BUTTON_STYLE;
|
|
607
607
|
|
|
608
608
|
const onClick = async () => {
|
|
609
|
-
|
|
609
|
+
// A press already running is joined, and the press that joined says so instead of nothing.
|
|
610
|
+
if (pressInFlight) {
|
|
611
|
+
setNotice(COPY.applying);
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
if (pending) {
|
|
615
|
+
setNotice(COPY.applying);
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
610
618
|
// A press the page already answered is not answered twice. Saying so beats a dead button.
|
|
611
619
|
if (Date.now() - stampRead() < 3000) { // ui-copy-ignore
|
|
612
620
|
setNotice(COPY.applying);
|
package/index.js
CHANGED
|
@@ -940,6 +940,35 @@ async function regroupedBundleAnswer(clientModules, graph, request) {
|
|
|
940
940
|
return { body, contentType: 'text/javascript; charset=utf-8', ids: request.ids }
|
|
941
941
|
}
|
|
942
942
|
|
|
943
|
+
/**
|
|
944
|
+
* A stale request can name a plugin the profile no longer has at all: it was turned off or removed
|
|
945
|
+
* after the document that asks was made. Every plugin in the request is still served on its own, so
|
|
946
|
+
* the ones that remain are answered and the ones that are gone take a comment's place, which a
|
|
947
|
+
* script can hold without registering anything. The app's own answer here is 404, and a document
|
|
948
|
+
* booting from an old startup list that meets one ends the whole app: a boot that comes up without
|
|
949
|
+
* a plugin it no longer has beats that.
|
|
950
|
+
*/
|
|
951
|
+
async function survivingBundleAnswer(clientModules, graph, request) {
|
|
952
|
+
if (request.sourceMap) return null
|
|
953
|
+
const entries = Array.isArray(graph?.entries) ? graph.entries : []
|
|
954
|
+
const served = new Set(entries.filter((row) => typeof row?.id === 'string').map((row) => row.id))
|
|
955
|
+
const gone = request.ids.filter((id) => !served.has(id))
|
|
956
|
+
if (gone.length === 0) return null
|
|
957
|
+
const parts = []
|
|
958
|
+
for (const id of request.ids) {
|
|
959
|
+
if (served.has(id)) {
|
|
960
|
+
const body = await currentEntryBody(clientModules, graph, id)
|
|
961
|
+
if (body === null) return null
|
|
962
|
+
parts.push(body)
|
|
963
|
+
continue
|
|
964
|
+
}
|
|
965
|
+
parts.push(Buffer.from(`/* ${id.replace(/[^a-z0-9@/._-]/giu, '')} is not part of the plugin set served now */`))
|
|
966
|
+
}
|
|
967
|
+
if (parts.length === 0) return null
|
|
968
|
+
const body = Buffer.concat(parts.flatMap((part, index) => (index === parts.length - 1 ? [part] : [part, Buffer.from('\n;\n')])))
|
|
969
|
+
return { body, contentType: 'text/javascript; charset=utf-8', ids: request.ids, gone }
|
|
970
|
+
}
|
|
971
|
+
|
|
943
972
|
/**
|
|
944
973
|
* The plugin set one stale request names, answered at the revision the app serves now. Only a body
|
|
945
974
|
* that really contains every plugin it claims is handed back; anything unsure answers null, so the
|
|
@@ -971,7 +1000,7 @@ async function currentBundleAnswer(clientModules, method, url) {
|
|
|
971
1000
|
}
|
|
972
1001
|
}
|
|
973
1002
|
}
|
|
974
|
-
return regroupedBundleAnswer(clientModules, graph, request)
|
|
1003
|
+
return (await regroupedBundleAnswer(clientModules, graph, request)) ?? survivingBundleAnswer(clientModules, graph, request)
|
|
975
1004
|
}
|
|
976
1005
|
|
|
977
1006
|
/** Answer one stale request over the plain request/response route an app registers. */
|
|
@@ -988,7 +1017,16 @@ async function serveCurrentBundle(clientModules, req, res) {
|
|
|
988
1017
|
'content-length': String(answer.body.length),
|
|
989
1018
|
})
|
|
990
1019
|
res.end(method === 'HEAD' ? undefined : answer.body)
|
|
991
|
-
|
|
1020
|
+
if (Array.isArray(answer.gone) && answer.gone.length > 0) {
|
|
1021
|
+
const kept = answer.ids.filter((id) => !answer.gone.includes(id))
|
|
1022
|
+
log(
|
|
1023
|
+
kept.length === 0
|
|
1024
|
+
? `answered an older set with an empty script in place of ${answer.gone.join(', ')}: the app would have ended on the table's 404`
|
|
1025
|
+
: `served ${kept.join(', ')} at the current revision and left out ${answer.gone.join(', ')}: a page asked for an older set`,
|
|
1026
|
+
)
|
|
1027
|
+
} else {
|
|
1028
|
+
log(`served ${answer.ids.join(', ')} at the current revision: a page asked for an older one`)
|
|
1029
|
+
}
|
|
992
1030
|
return true
|
|
993
1031
|
} catch (error) {
|
|
994
1032
|
log(`cannot serve a bundle from an older revision: ${error?.message ?? error}`)
|
|
@@ -1037,7 +1075,16 @@ async function serveCurrentBundleResource(clientModules, request) {
|
|
|
1037
1075
|
if (method !== 'GET' && method !== 'HEAD') return null
|
|
1038
1076
|
const answer = await currentBundleAnswer(clientModules, method, request?.url)
|
|
1039
1077
|
if (answer === null) return null
|
|
1040
|
-
|
|
1078
|
+
if (Array.isArray(answer.gone) && answer.gone.length > 0) {
|
|
1079
|
+
const kept = answer.ids.filter((id) => !answer.gone.includes(id))
|
|
1080
|
+
log(
|
|
1081
|
+
kept.length === 0
|
|
1082
|
+
? `answered an older set with an empty script in place of ${answer.gone.join(', ')}: the app would have ended on the table's 404`
|
|
1083
|
+
: `served ${kept.join(', ')} at the current revision and left out ${answer.gone.join(', ')}: a page asked for an older set`,
|
|
1084
|
+
)
|
|
1085
|
+
} else {
|
|
1086
|
+
log(`served ${answer.ids.join(', ')} at the current revision: a page asked for an older one`)
|
|
1087
|
+
}
|
|
1041
1088
|
return new Response(method === 'HEAD' ? null : answer.body, {
|
|
1042
1089
|
status: 200,
|
|
1043
1090
|
headers: {
|
|
@@ -1361,10 +1408,19 @@ export async function verifyStartupRows(graph, fetchBody, rows) {
|
|
|
1361
1408
|
wanted.push({ url, ids: request === null ? [] : request.ids })
|
|
1362
1409
|
}
|
|
1363
1410
|
if (wanted.length === 0) return { status: 'blocked', reason: 'list', detail: 'the startup list names no script' }
|
|
1411
|
+
/**
|
|
1412
|
+
* A row can name a plugin the profile no longer has: the document that made it was composed
|
|
1413
|
+
* before that plugin was turned off. The route answers such a row with a stand-in that registers
|
|
1414
|
+
* nothing, so the boot comes up without the plugin; the check reads the same way and does not
|
|
1415
|
+
* demand a registration the set cannot give.
|
|
1416
|
+
*/
|
|
1417
|
+
const served = new Set(entries.keys())
|
|
1418
|
+
const leftOut = new Set()
|
|
1364
1419
|
const listed = new Set()
|
|
1365
1420
|
const bodies = []
|
|
1366
1421
|
for (const item of wanted) {
|
|
1367
|
-
for (const id of item.ids) listed.add(id)
|
|
1422
|
+
for (const id of item.ids) if (served.has(id)) listed.add(id)
|
|
1423
|
+
for (const id of item.ids) if (!served.has(id)) leftOut.add(id)
|
|
1368
1424
|
let response = null
|
|
1369
1425
|
try {
|
|
1370
1426
|
response = await fetchBody(item.url)
|
|
@@ -1380,8 +1436,9 @@ export async function verifyStartupRows(graph, fetchBody, rows) {
|
|
|
1380
1436
|
const complaint = parseAsScript(text, item.url)
|
|
1381
1437
|
if (complaint !== null) return { status: 'blocked', reason: 'parse', detail: `${item.url}: ${complaint}` }
|
|
1382
1438
|
const registered = registeredIds(text)
|
|
1383
|
-
const
|
|
1384
|
-
|
|
1439
|
+
const required = item.ids.filter((id) => served.has(id))
|
|
1440
|
+
const missing = required.filter((id) => !registered.has(id))
|
|
1441
|
+
if (required.length > 0 && missing.length > 0) {
|
|
1385
1442
|
return { status: 'blocked', reason: 'register', detail: `${item.url} does not register ${missing.join(', ')}` }
|
|
1386
1443
|
}
|
|
1387
1444
|
bodies.push({
|
|
@@ -1418,6 +1475,7 @@ export async function verifyStartupRows(graph, fetchBody, rows) {
|
|
|
1418
1475
|
reason: 'ok', // ui-copy-ignore
|
|
1419
1476
|
revision: typeof graph?.rev === 'string' ? graph.rev : null,
|
|
1420
1477
|
scripts: bodies.map((body) => ({ url: body.url, digest: body.digest, ids: body.ids })),
|
|
1478
|
+
...(leftOut.size > 0 ? { leftOut: [...leftOut] } : {}),
|
|
1421
1479
|
}
|
|
1422
1480
|
}
|
|
1423
1481
|
|
|
@@ -1508,6 +1566,9 @@ function serveButton(ctx, controller, config) {
|
|
|
1508
1566
|
} catch (error) {
|
|
1509
1567
|
verdict = { status: 'blocked', reason: 'list', detail: String(error?.message ?? error) }
|
|
1510
1568
|
}
|
|
1569
|
+
if (Array.isArray(verdict?.leftOut) && verdict.leftOut.length > 0) {
|
|
1570
|
+
log(`a startup row names a plugin this app no longer serves: ${verdict.leftOut.join(', ')}; the page comes up without it`)
|
|
1571
|
+
}
|
|
1511
1572
|
sendJson(res, 200, verdict)
|
|
1512
1573
|
} catch (error) {
|
|
1513
1574
|
log(`cannot answer ${BASE_PATH}/verify: ${error?.message ?? error}`)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-unlag",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.23",
|
|
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",
|