@blamejs/core 0.8.12 → 0.8.15
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 +6 -0
- package/README.md +3 -1
- package/index.js +12 -1
- package/lib/a2a.js +272 -0
- package/lib/ai-input.js +151 -0
- package/lib/audit.js +6 -0
- package/lib/dark-patterns.js +357 -0
- package/lib/framework-error.js +34 -0
- package/lib/graphql-federation.js +176 -0
- package/lib/http-client.js +230 -33
- package/lib/mcp.js +301 -0
- package/lib/middleware/sse.js +18 -20
- package/lib/request-helpers.js +34 -0
- package/lib/router.js +28 -0
- package/lib/sse.js +349 -0
- package/lib/vault/index.js +4 -0
- package/lib/vault/seal-pem-file.js +283 -0
- package/lib/websocket.js +15 -0
- package/package.json +2 -2
- package/sbom.cyclonedx.json +6 -6
package/lib/http-client.js
CHANGED
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
* maxResponseBytes, // for buffer mode (default 16 MiB control,
|
|
21
21
|
* // 1 GiB GET — operators with > 1 GiB
|
|
22
22
|
* // stored objects must use stream mode)
|
|
23
|
+
* onChunk, // (chunk: Buffer) => void — fires for each
|
|
24
|
+
* // response chunk in BOTH buffer and stream
|
|
25
|
+
* // modes. Use to hash bytes during pipe-to-disk
|
|
26
|
+
* // without an extra Transform pass.
|
|
23
27
|
* signal, // AbortSignal — propagated to req/stream
|
|
24
28
|
* errorClass, // FrameworkError subclass
|
|
25
29
|
* observer, // optional (stage, info) => void hook
|
|
@@ -372,6 +376,7 @@ function _toH2Headers(method, u, headers) {
|
|
|
372
376
|
h2Headers[":path"] = u.pathname + (u.search || "");
|
|
373
377
|
h2Headers[":scheme"] = u.protocol === "https:" ? "https" : "http";
|
|
374
378
|
h2Headers[":authority"] = u.host;
|
|
379
|
+
var sawAcceptEncoding = false;
|
|
375
380
|
for (var k in headers) {
|
|
376
381
|
if (!Object.prototype.hasOwnProperty.call(headers, k)) continue;
|
|
377
382
|
var lk = k.toLowerCase();
|
|
@@ -379,8 +384,12 @@ function _toH2Headers(method, u, headers) {
|
|
|
379
384
|
if (lk === "connection" || lk === "host" ||
|
|
380
385
|
lk === "keep-alive" || lk === "transfer-encoding" ||
|
|
381
386
|
lk === "upgrade" || lk === "proxy-connection") continue;
|
|
387
|
+
if (lk === "accept-encoding") sawAcceptEncoding = true;
|
|
382
388
|
h2Headers[lk] = headers[k];
|
|
383
389
|
}
|
|
390
|
+
// CVE-2026-22036 mitigation — same identity default as the h1 path.
|
|
391
|
+
// Refuse compressed responses unless the operator explicitly opts in.
|
|
392
|
+
if (!sawAcceptEncoding) h2Headers["accept-encoding"] = "identity";
|
|
384
393
|
return h2Headers;
|
|
385
394
|
}
|
|
386
395
|
|
|
@@ -426,20 +435,78 @@ function _attachJarCookie(headers, jar, url) {
|
|
|
426
435
|
// Mirrors the wire format that lib/middleware/body-parser.js's multipart
|
|
427
436
|
// parser accepts so round-trip from one blamejs app's outbound to
|
|
428
437
|
// another's inbound is exact.
|
|
438
|
+
//
|
|
439
|
+
// Two output shapes:
|
|
440
|
+
//
|
|
441
|
+
// - { boundary, body: Buffer, contentLength }
|
|
442
|
+
// When every file entry is a Buffer / string (size known
|
|
443
|
+
// up front) and no operator opted into streaming, the result
|
|
444
|
+
// is a fully-materialized body. Smaller payloads avoid the
|
|
445
|
+
// streaming overhead and let HTTP/1.1 KeepAlive reuse with a
|
|
446
|
+
// known Content-Length.
|
|
447
|
+
//
|
|
448
|
+
// - { boundary, body: Readable, contentLength }
|
|
449
|
+
// When at least one file entry is `{ filePath }` / `{ stream }`
|
|
450
|
+
// OR opts.streaming === true, the result is a Readable that
|
|
451
|
+
// emits boundary headers + content + CRLF in order. Avoids the
|
|
452
|
+
// Buffer.concat() OOM class on large uploads. contentLength is
|
|
453
|
+
// a finite number when every source's size is statically
|
|
454
|
+
// resolvable (Buffer length, fs.statSync().size, opts.size on
|
|
455
|
+
// a stream entry); null otherwise — caller falls back to
|
|
456
|
+
// chunked transfer.
|
|
457
|
+
//
|
|
458
|
+
// File entry shapes (all require `field`):
|
|
459
|
+
//
|
|
460
|
+
// { field, content: Buffer | string } — in-memory (existing)
|
|
461
|
+
// { field, filePath: string } — stream-from-disk
|
|
462
|
+
// { field, stream: Readable, size?: number } — operator-supplied stream
|
|
463
|
+
//
|
|
464
|
+
// `filename` and `contentType` apply to all three shapes; for
|
|
465
|
+
// `filePath` entries, `filename` defaults to path.basename(filePath).
|
|
429
466
|
function _buildMultipartBody(spec) {
|
|
430
467
|
var boundary = "----blamejs-mp-" + crypto.generateToken(C.BYTES.bytes(16));
|
|
431
468
|
var CRLF = "\r\n";
|
|
432
|
-
var
|
|
469
|
+
var fs = require("fs"); // allow:inline-require — only on multipart paths that touch the filesystem
|
|
470
|
+
var path = require("path"); // allow:inline-require — same
|
|
471
|
+
var nodeStream = require("stream"); // allow:inline-require — Readable subclass only when streaming
|
|
472
|
+
|
|
473
|
+
// Each entry is { headerBytes, source } where source is one of:
|
|
474
|
+
// { kind: "buffer", buf: Buffer }
|
|
475
|
+
// { kind: "filePath", filePath: string, size: number }
|
|
476
|
+
// { kind: "stream", stream: Readable, size: number | null }
|
|
477
|
+
var entries = [];
|
|
478
|
+
var anyStreaming = false;
|
|
479
|
+
var totalSize = 0;
|
|
480
|
+
var sizeKnown = true;
|
|
481
|
+
|
|
482
|
+
function _entryHeaderBytes(disposition, contentType) {
|
|
483
|
+
var head = "--" + boundary + CRLF + disposition + CRLF;
|
|
484
|
+
if (contentType) head += "Content-Type: " + contentType + CRLF;
|
|
485
|
+
head += CRLF;
|
|
486
|
+
return Buffer.from(head, "utf8");
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function _addEntry(headerBytes, source) {
|
|
490
|
+
entries.push({ header: headerBytes, source: source });
|
|
491
|
+
totalSize += headerBytes.length;
|
|
492
|
+
if (source.kind === "buffer") {
|
|
493
|
+
totalSize += source.buf.length;
|
|
494
|
+
} else if (typeof source.size === "number" && isFinite(source.size) && source.size >= 0) {
|
|
495
|
+
totalSize += source.size;
|
|
496
|
+
} else {
|
|
497
|
+
sizeKnown = false;
|
|
498
|
+
}
|
|
499
|
+
totalSize += CRLF.length;
|
|
500
|
+
}
|
|
433
501
|
|
|
434
502
|
function _pushField(name, value) {
|
|
435
503
|
if (typeof name !== "string" || name.length === 0) {
|
|
436
504
|
throw new Error("multipart: field name must be a non-empty string");
|
|
437
505
|
}
|
|
438
|
-
var
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
parts.push(Buffer.from(CRLF, "utf8"));
|
|
506
|
+
var disposition = 'Content-Disposition: form-data; name="' + name + '"';
|
|
507
|
+
var head = _entryHeaderBytes(disposition, null);
|
|
508
|
+
var bodyBuf = Buffer.isBuffer(value) ? value : Buffer.from(String(value), "utf8");
|
|
509
|
+
_addEntry(head, { kind: "buffer", buf: bodyBuf });
|
|
443
510
|
}
|
|
444
511
|
|
|
445
512
|
function _pushFile(file) {
|
|
@@ -447,21 +514,50 @@ function _buildMultipartBody(spec) {
|
|
|
447
514
|
if (typeof file.field !== "string" || file.field.length === 0) {
|
|
448
515
|
throw new Error("multipart: file.field must be a non-empty string");
|
|
449
516
|
}
|
|
450
|
-
var
|
|
451
|
-
|
|
517
|
+
var hasContent = file.content !== undefined && file.content !== null;
|
|
518
|
+
var hasFilePath = typeof file.filePath === "string" && file.filePath.length > 0;
|
|
519
|
+
var hasStream = file.stream && typeof file.stream.pipe === "function";
|
|
520
|
+
var sourceCount = (hasContent ? 1 : 0) + (hasFilePath ? 1 : 0) + (hasStream ? 1 : 0);
|
|
521
|
+
if (sourceCount === 0) {
|
|
522
|
+
throw new Error("multipart: file entry requires one of { content, filePath, stream }");
|
|
523
|
+
}
|
|
524
|
+
if (sourceCount > 1) {
|
|
525
|
+
throw new Error("multipart: file entry must have exactly one of { content, filePath, stream }");
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
var filename;
|
|
529
|
+
if (typeof file.filename === "string" && file.filename.length > 0) {
|
|
530
|
+
filename = file.filename;
|
|
531
|
+
} else if (hasFilePath) {
|
|
532
|
+
filename = path.basename(file.filePath);
|
|
533
|
+
} else {
|
|
534
|
+
filename = "blob";
|
|
535
|
+
}
|
|
452
536
|
var mimeType = file.contentType || file.mimeType || "application/octet-stream";
|
|
453
|
-
var
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
537
|
+
var disposition = 'Content-Disposition: form-data; name="' + file.field + '"' +
|
|
538
|
+
'; filename="' + filename.replace(/"/g, "%22") + '"';
|
|
539
|
+
var head = _entryHeaderBytes(disposition, mimeType);
|
|
540
|
+
|
|
541
|
+
if (hasContent) {
|
|
542
|
+
var content = file.content;
|
|
543
|
+
if (typeof content === "string") content = Buffer.from(content, "utf8");
|
|
544
|
+
if (!Buffer.isBuffer(content)) {
|
|
545
|
+
throw new Error("multipart: file.content must be a Buffer or string");
|
|
546
|
+
}
|
|
547
|
+
_addEntry(head, { kind: "buffer", buf: content });
|
|
548
|
+
} else if (hasFilePath) {
|
|
549
|
+
anyStreaming = true;
|
|
550
|
+
var st;
|
|
551
|
+
try { st = fs.statSync(file.filePath); }
|
|
552
|
+
catch (e) { throw new Error("multipart: file.filePath not readable: " + e.message); }
|
|
553
|
+
if (!st.isFile()) throw new Error("multipart: file.filePath is not a regular file");
|
|
554
|
+
_addEntry(head, { kind: "filePath", filePath: file.filePath, size: st.size });
|
|
555
|
+
} else {
|
|
556
|
+
anyStreaming = true;
|
|
557
|
+
var streamSize = (typeof file.size === "number" && isFinite(file.size) && file.size >= 0)
|
|
558
|
+
? file.size : null;
|
|
559
|
+
_addEntry(head, { kind: "stream", stream: file.stream, size: streamSize });
|
|
457
560
|
}
|
|
458
|
-
var head = "--" + boundary + CRLF +
|
|
459
|
-
'Content-Disposition: form-data; name="' + file.field + '"' +
|
|
460
|
-
'; filename="' + filename.replace(/"/g, "%22") + '"' + CRLF +
|
|
461
|
-
"Content-Type: " + mimeType + CRLF + CRLF;
|
|
462
|
-
parts.push(Buffer.from(head, "utf8"));
|
|
463
|
-
parts.push(content);
|
|
464
|
-
parts.push(Buffer.from(CRLF, "utf8"));
|
|
465
561
|
}
|
|
466
562
|
|
|
467
563
|
if (spec && spec.fields && typeof spec.fields === "object") {
|
|
@@ -479,8 +575,54 @@ function _buildMultipartBody(spec) {
|
|
|
479
575
|
if (spec && Array.isArray(spec.files)) {
|
|
480
576
|
for (var fi = 0; fi < spec.files.length; fi++) _pushFile(spec.files[fi]);
|
|
481
577
|
}
|
|
482
|
-
|
|
483
|
-
|
|
578
|
+
var trailer = Buffer.from("--" + boundary + "--" + CRLF, "utf8");
|
|
579
|
+
totalSize += trailer.length;
|
|
580
|
+
|
|
581
|
+
// All-buffer fast path — return a fully-materialized body when no
|
|
582
|
+
// streaming sources are involved AND the operator didn't ask for
|
|
583
|
+
// streaming explicitly. Existing callers that pass small in-memory
|
|
584
|
+
// payloads keep the buffer codepath.
|
|
585
|
+
if (!anyStreaming && !(spec && spec.streaming === true)) {
|
|
586
|
+
var parts = [];
|
|
587
|
+
for (var ei = 0; ei < entries.length; ei++) {
|
|
588
|
+
parts.push(entries[ei].header);
|
|
589
|
+
parts.push(entries[ei].source.buf);
|
|
590
|
+
parts.push(Buffer.from(CRLF, "utf8"));
|
|
591
|
+
}
|
|
592
|
+
parts.push(trailer);
|
|
593
|
+
return { boundary: boundary, body: Buffer.concat(parts), contentLength: totalSize };
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Streaming path — produce a Readable from an async iterator that
|
|
597
|
+
// yields the bytes for each entry in order.
|
|
598
|
+
var crlfBuf = Buffer.from(CRLF, "utf8");
|
|
599
|
+
async function* _iter() {
|
|
600
|
+
for (var ix = 0; ix < entries.length; ix++) {
|
|
601
|
+
var entry = entries[ix];
|
|
602
|
+
yield entry.header;
|
|
603
|
+
if (entry.source.kind === "buffer") {
|
|
604
|
+
yield entry.source.buf;
|
|
605
|
+
} else if (entry.source.kind === "filePath") {
|
|
606
|
+
var rs = fs.createReadStream(entry.source.filePath);
|
|
607
|
+
try {
|
|
608
|
+
for await (var chunk of rs) yield chunk;
|
|
609
|
+
} finally {
|
|
610
|
+
try { rs.destroy(); } catch (_e) { /* best-effort cleanup */ }
|
|
611
|
+
}
|
|
612
|
+
} else {
|
|
613
|
+
// operator-supplied stream
|
|
614
|
+
for await (var chunk2 of entry.source.stream) yield chunk2;
|
|
615
|
+
}
|
|
616
|
+
yield crlfBuf;
|
|
617
|
+
}
|
|
618
|
+
yield trailer;
|
|
619
|
+
}
|
|
620
|
+
var body = nodeStream.Readable.from(_iter());
|
|
621
|
+
return {
|
|
622
|
+
boundary: boundary,
|
|
623
|
+
body: body,
|
|
624
|
+
contentLength: sizeKnown ? totalSize : null,
|
|
625
|
+
};
|
|
484
626
|
}
|
|
485
627
|
|
|
486
628
|
// Headers stripped on cross-origin redirect to defend against accidental
|
|
@@ -525,6 +667,10 @@ function request(opts) {
|
|
|
525
667
|
return Promise.reject(_makeError(opts.errorClass, "BAD_ARG",
|
|
526
668
|
"onDownloadProgress must be a function", true));
|
|
527
669
|
}
|
|
670
|
+
if (opts.onChunk !== undefined && typeof opts.onChunk !== "function") {
|
|
671
|
+
return Promise.reject(_makeError(opts.errorClass, "BAD_ARG",
|
|
672
|
+
"onChunk must be a function (chunk: Buffer) -> void", true));
|
|
673
|
+
}
|
|
528
674
|
if (opts.jar !== undefined && opts.jar !== null) {
|
|
529
675
|
if (typeof opts.jar !== "object" ||
|
|
530
676
|
typeof opts.jar.cookieHeaderFor !== "function" ||
|
|
@@ -567,13 +713,20 @@ function request(opts) {
|
|
|
567
713
|
catch (e) {
|
|
568
714
|
return Promise.reject(_makeError(opts.errorClass, "BAD_ARG", e.message, true));
|
|
569
715
|
}
|
|
716
|
+
var mpHeaders = Object.assign({}, opts.headers || {}, {
|
|
717
|
+
"Content-Type": "multipart/form-data; boundary=" + built.boundary,
|
|
718
|
+
});
|
|
719
|
+
// Content-Length is set when the framework can statically resolve
|
|
720
|
+
// every source's byte size. Otherwise the framework omits the
|
|
721
|
+
// header and Node's HTTP layer falls back to chunked transfer —
|
|
722
|
+
// valid HTTP/1.1, requires no operator opt-in.
|
|
723
|
+
if (typeof built.contentLength === "number" && isFinite(built.contentLength)) {
|
|
724
|
+
mpHeaders["Content-Length"] = String(built.contentLength);
|
|
725
|
+
}
|
|
570
726
|
opts = Object.assign({}, opts, {
|
|
571
|
-
method:
|
|
572
|
-
body:
|
|
573
|
-
headers:
|
|
574
|
-
"Content-Type": "multipart/form-data; boundary=" + built.boundary,
|
|
575
|
-
"Content-Length": String(built.body.length),
|
|
576
|
-
}),
|
|
727
|
+
method: opts.method || "POST",
|
|
728
|
+
body: built.body,
|
|
729
|
+
headers: mpHeaders,
|
|
577
730
|
multipart: undefined,
|
|
578
731
|
});
|
|
579
732
|
}
|
|
@@ -872,6 +1025,17 @@ function _requestH1(transport, u, opts) {
|
|
|
872
1025
|
if (Buffer.isBuffer(opts.body)) {
|
|
873
1026
|
headers["Content-Length"] = opts.body.length;
|
|
874
1027
|
}
|
|
1028
|
+
// CVE-2026-22036 mitigation — refuse compressed responses by
|
|
1029
|
+
// default. The framework's http-client returns raw bytes capped
|
|
1030
|
+
// at maxResponseBytes; if a server sends gzip/br/zstd the cap is
|
|
1031
|
+
// on-wire bytes only, and any operator-side decompression is the
|
|
1032
|
+
// operator's responsibility to bound. Identity by default closes
|
|
1033
|
+
// the decompression-bomb amplification class. Operators who DO
|
|
1034
|
+
// want compressed responses opt in by passing an explicit
|
|
1035
|
+
// Accept-Encoding header (lowercase or canonical form).
|
|
1036
|
+
if (!headers["Accept-Encoding"] && !headers["accept-encoding"]) {
|
|
1037
|
+
headers["Accept-Encoding"] = "identity";
|
|
1038
|
+
}
|
|
875
1039
|
|
|
876
1040
|
var reqOpts = {
|
|
877
1041
|
method: method,
|
|
@@ -894,6 +1058,7 @@ function _requestH1(transport, u, opts) {
|
|
|
894
1058
|
|
|
895
1059
|
var onUploadProgress = typeof opts.onUploadProgress === "function" ? opts.onUploadProgress : null;
|
|
896
1060
|
var onDownloadProgress = typeof opts.onDownloadProgress === "function" ? opts.onDownloadProgress : null;
|
|
1061
|
+
var onChunk = typeof opts.onChunk === "function" ? opts.onChunk : null;
|
|
897
1062
|
|
|
898
1063
|
var req = transport.lib.request(reqOpts, function (res) {
|
|
899
1064
|
if (observer) observer("response:headers", { statusCode: res.statusCode, headers: res.headers });
|
|
@@ -927,13 +1092,24 @@ function _requestH1(transport, u, opts) {
|
|
|
927
1092
|
"HTTP " + res.statusCode + " " + (res.statusMessage || ""),
|
|
928
1093
|
_isPermanentStatus(res.statusCode), res.statusCode));
|
|
929
1094
|
}
|
|
930
|
-
if (onDownloadProgress) {
|
|
931
|
-
// Wrap the stream so chunks emit progress to the
|
|
932
|
-
// The framework's contract is to hand back the
|
|
933
|
-
// unmodified; fix-up via a passthrough keeps
|
|
934
|
-
// observing the chunk sizes.
|
|
1095
|
+
if (onDownloadProgress || onChunk) {
|
|
1096
|
+
// Wrap the stream so chunks emit progress + onChunk to the
|
|
1097
|
+
// operator. The framework's contract is to hand back the
|
|
1098
|
+
// response stream unmodified; fix-up via a passthrough keeps
|
|
1099
|
+
// that contract while observing the chunk sizes. onChunk
|
|
1100
|
+
// gets the buffer itself (for hash-as-you-go); a throw from
|
|
1101
|
+
// it is caught and dropped so a hash-mismatch detector can
|
|
1102
|
+
// raise without breaking the response stream — caller
|
|
1103
|
+
// surfaces the error through their own pipe handler.
|
|
935
1104
|
var passthrough = new nodeStream.PassThrough();
|
|
936
|
-
res.on("data", function (chunk) {
|
|
1105
|
+
res.on("data", function (chunk) {
|
|
1106
|
+
_emitDownload(chunk.length);
|
|
1107
|
+
if (onChunk) {
|
|
1108
|
+
try { onChunk(chunk); }
|
|
1109
|
+
catch (_e) { /* operator-supplied hook — drop-silent */ }
|
|
1110
|
+
}
|
|
1111
|
+
passthrough.write(chunk);
|
|
1112
|
+
});
|
|
937
1113
|
res.on("end", function () { passthrough.end(); });
|
|
938
1114
|
res.on("error", function (e) { passthrough.destroy(e); });
|
|
939
1115
|
return _resolve({ statusCode: res.statusCode, headers: res.headers, body: passthrough });
|
|
@@ -955,6 +1131,10 @@ function _requestH1(transport, u, opts) {
|
|
|
955
1131
|
return;
|
|
956
1132
|
}
|
|
957
1133
|
_emitDownload(chunk.length);
|
|
1134
|
+
if (onChunk) {
|
|
1135
|
+
try { onChunk(chunk); }
|
|
1136
|
+
catch (_e) { /* operator-supplied hook — drop-silent */ }
|
|
1137
|
+
}
|
|
958
1138
|
});
|
|
959
1139
|
res.on("end", function () {
|
|
960
1140
|
if (capExceeded) return;
|
|
@@ -1072,6 +1252,7 @@ function _requestH2(transport, u, opts) {
|
|
|
1072
1252
|
(method === "GET" ? DEFAULT_GET_CAP : DEFAULT_CONTROL_PLANE_CAP);
|
|
1073
1253
|
var observer = typeof opts.observer === "function" ? opts.observer : null;
|
|
1074
1254
|
var startedAt = Date.now();
|
|
1255
|
+
var onChunkH2 = typeof opts.onChunk === "function" ? opts.onChunk : null;
|
|
1075
1256
|
|
|
1076
1257
|
var signal = safeAsync.withTimeoutSignal(opts.signal || null, opts.timeoutMs);
|
|
1077
1258
|
if (signal && signal.aborted) {
|
|
@@ -1128,6 +1309,17 @@ function _requestH2(transport, u, opts) {
|
|
|
1128
1309
|
return _reject(_makeError(opts.errorClass, "HTTP_ERROR",
|
|
1129
1310
|
"HTTP " + statusCode, _isPermanentStatus(statusCode), statusCode));
|
|
1130
1311
|
}
|
|
1312
|
+
if (onChunkH2) {
|
|
1313
|
+
var passthroughH2 = new nodeStream.PassThrough();
|
|
1314
|
+
stream.on("data", function (chunk) {
|
|
1315
|
+
try { onChunkH2(chunk); }
|
|
1316
|
+
catch (_e) { /* operator-supplied hook — drop-silent */ }
|
|
1317
|
+
passthroughH2.write(chunk);
|
|
1318
|
+
});
|
|
1319
|
+
stream.on("end", function () { passthroughH2.end(); });
|
|
1320
|
+
stream.on("error", function (e) { passthroughH2.destroy(e); });
|
|
1321
|
+
return _resolve({ statusCode: statusCode, headers: responseHeaders, body: passthroughH2 });
|
|
1322
|
+
}
|
|
1131
1323
|
return _resolve({ statusCode: statusCode, headers: responseHeaders, body: stream });
|
|
1132
1324
|
}
|
|
1133
1325
|
|
|
@@ -1142,6 +1334,11 @@ function _requestH2(transport, u, opts) {
|
|
|
1142
1334
|
try { stream.close(http2.constants.NGHTTP2_CANCEL); } catch (_e2) { /* best-effort h2 stream cancel */ }
|
|
1143
1335
|
_reject(_makeError(opts.errorClass, "RESPONSE_TOO_LARGE",
|
|
1144
1336
|
"response body exceeds " + maxResponseBytes + " bytes", true));
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
1339
|
+
if (onChunkH2) {
|
|
1340
|
+
try { onChunkH2(chunk); }
|
|
1341
|
+
catch (_e) { /* operator-supplied hook — drop-silent */ }
|
|
1145
1342
|
}
|
|
1146
1343
|
});
|
|
1147
1344
|
stream.on("end", function () {
|