dsh-unlag 1.9.53 → 1.9.55

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.
Files changed (2) hide show
  1. package/index.js +67 -27
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -1309,6 +1309,8 @@ function serveBundlesAtCurrentRevision(ctx) {
1309
1309
  }
1310
1310
  const realWriteHead = res.writeHead
1311
1311
  const realEnd = res.end
1312
+ const realWriteRef = res.write
1313
+ let canHold = true
1312
1314
  const ask = async (url) => {
1313
1315
  let held = null
1314
1316
  let body
@@ -1321,39 +1323,75 @@ function serveBundlesAtCurrentRevision(ctx) {
1321
1323
  /* a frozen request keeps its own address */
1322
1324
  }
1323
1325
  }
1324
- if (typeof realWriteHead === 'function') {
1325
- res.writeHead = (status, ...rest) => {
1326
- if (status === 404) {
1327
- held = { status, headers: rest[0] }
1328
- return res
1329
- }
1330
- return realWriteHead.call(res, status, ...rest)
1331
- }
1332
- }
1333
1326
  /**
1334
1327
  * A held 404 can still be written to in pieces. Passing those pieces through would send the
1335
1328
  * implicit 200 headers Node writes on a first `write`, and the real answer could then never
1336
- * be sent; so the pieces are kept with the body and the socket stays untouched.
1329
+ * be sent; so the pieces are kept with the body and the socket stays untouched. A held answer
1330
+ * also owes the caller the callbacks it passed: dropping one would leave a handler that waits
1331
+ * for the flush awaiting forever, and the answer after it would never run.
1337
1332
  */
1338
1333
  const realWrite = res.write
1339
- if (typeof realWrite === 'function') {
1340
- res.write = (chunk, ...rest) => {
1341
- if (held !== null) {
1342
- if (typeof chunk === 'string' || Buffer.isBuffer(chunk)) parts.push(Buffer.from(chunk))
1343
- return true
1334
+ const answerCallback = (args) => {
1335
+ for (let at = args.length - 1; at >= 0; at -= 1) {
1336
+ if (typeof args[at] === 'function') {
1337
+ const done = args[at]
1338
+ try {
1339
+ process.nextTick(() => {
1340
+ try {
1341
+ done()
1342
+ } catch {
1343
+ /* the caller's own callback failed; the document still needs its script */
1344
+ }
1345
+ })
1346
+ } catch {
1347
+ /* no event loop to defer on; the answer matters more than the callback */
1348
+ }
1349
+ return
1344
1350
  }
1345
- return realWrite.call(res, chunk, ...rest)
1346
1351
  }
1347
1352
  }
1348
- if (typeof realEnd === 'function') {
1349
- res.end = (...args) => {
1350
- if (held !== null) {
1351
- const last = args[0]
1352
- if (typeof last === 'string' || Buffer.isBuffer(last)) parts.push(Buffer.from(last))
1353
- body = parts.length === 0 ? '' : Buffer.concat(parts)
1354
- return res
1353
+ /**
1354
+ * Installing the hold means writing to the response object. A response that refuses that is
1355
+ * answered as the table left it instead of thrown at, because this runs in an async route
1356
+ * handler where a rejection would be an unhandled one.
1357
+ */
1358
+ if (canHold) {
1359
+ try {
1360
+ if (typeof realWrite === 'function') {
1361
+ res.write = (chunk, ...rest) => {
1362
+ if (held !== null) {
1363
+ if (typeof chunk === 'string' || Buffer.isBuffer(chunk)) parts.push(Buffer.from(chunk))
1364
+ answerCallback(rest)
1365
+ return true
1366
+ }
1367
+ return realWrite.call(res, chunk, ...rest)
1368
+ }
1369
+ }
1370
+ if (typeof realEnd === 'function') {
1371
+ res.end = (...args) => {
1372
+ if (held !== null) {
1373
+ const last = args[0]
1374
+ if (typeof last === 'string' || Buffer.isBuffer(last)) parts.push(Buffer.from(last))
1375
+ body = parts.length === 0 ? '' : Buffer.concat(parts)
1376
+ answerCallback(args)
1377
+ return res
1378
+ }
1379
+ return realEnd.apply(res, args)
1380
+ }
1355
1381
  }
1356
- return realEnd.apply(res, args)
1382
+ if (typeof realWriteHead === 'function') {
1383
+ res.writeHead = (status, ...rest) => {
1384
+ if (status === 404) {
1385
+ held = { status, headers: rest[0] }
1386
+ return res
1387
+ }
1388
+ return realWriteHead.call(res, status, ...rest)
1389
+ }
1390
+ }
1391
+ } catch (error) {
1392
+ canHold = false
1393
+ held = null
1394
+ log(`cannot hold a 404 for a moment: ${oneLine(error?.message ?? error)}`)
1357
1395
  }
1358
1396
  }
1359
1397
  try {
@@ -1363,9 +1401,11 @@ function serveBundlesAtCurrentRevision(ctx) {
1363
1401
  log(`cannot answer a bundle request: ${error?.message ?? error}`)
1364
1402
  } finally {
1365
1403
  try {
1366
- res.writeHead = realWriteHead
1367
- if (realWrite !== undefined) res.write = realWrite
1368
- res.end = realEnd
1404
+ if (canHold) {
1405
+ res.writeHead = realWriteHead
1406
+ if (realWriteRef !== undefined) res.write = realWriteRef
1407
+ res.end = realEnd
1408
+ }
1369
1409
  } catch {
1370
1410
  /* the response object owns its own shape */
1371
1411
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-unlag",
3
- "version": "1.9.53",
3
+ "version": "1.9.55",
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",