grpc-libp2p-client 0.0.37 → 0.0.39
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/dist/dc-http2/frame.cjs.js +6 -5
- package/dist/dc-http2/frame.cjs.js.map +1 -1
- package/dist/dc-http2/frame.d.ts +1 -1
- package/dist/dc-http2/frame.esm.js +6 -5
- package/dist/dc-http2/frame.esm.js.map +1 -1
- package/dist/dc-http2/hpack.cjs.js +3 -3
- package/dist/dc-http2/hpack.cjs.js.map +1 -1
- package/dist/dc-http2/hpack.esm.js +3 -3
- package/dist/dc-http2/hpack.esm.js.map +1 -1
- package/dist/dc-http2/parser.cjs.js +39 -28
- package/dist/dc-http2/parser.cjs.js.map +1 -1
- package/dist/dc-http2/parser.d.ts +7 -7
- package/dist/dc-http2/parser.esm.js +39 -28
- package/dist/dc-http2/parser.esm.js.map +1 -1
- package/dist/dc-http2/stream.cjs.js +26 -12
- package/dist/dc-http2/stream.cjs.js.map +1 -1
- package/dist/dc-http2/stream.d.ts +1 -1
- package/dist/dc-http2/stream.esm.js +26 -12
- package/dist/dc-http2/stream.esm.js.map +1 -1
- package/dist/grpc.js +245 -269
- package/dist/grpc.js.map +1 -1
- package/dist/grpc.min.js +1 -1
- package/dist/grpc.min.js.map +1 -1
- package/dist/index.cjs.js +103 -103
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +103 -103
- package/dist/index.esm.js.map +1 -1
- package/dist/stats.html +1 -1
- package/package.json +4 -4
- package/src/dc-http2/encoder.ts +3 -2
- package/src/dc-http2/hpack.ts +3 -3
- package/src/dc-http2/parser.ts +43 -38
- package/src/dc-http2/stream.ts +36 -23
- package/src/dc-http2/types.ts +1 -1
- package/src/index.ts +57 -85
package/dist/index.esm.js
CHANGED
|
@@ -355,7 +355,7 @@ class HPACK {
|
|
|
355
355
|
}
|
|
356
356
|
// Huffman编码实现
|
|
357
357
|
huffmanEncode(bytes) {
|
|
358
|
-
|
|
358
|
+
const result = [];
|
|
359
359
|
let current = 0;
|
|
360
360
|
let bits = 0;
|
|
361
361
|
for (let i = 0; i < bytes.length; i++) {
|
|
@@ -448,7 +448,7 @@ class HPACK {
|
|
|
448
448
|
try {
|
|
449
449
|
result = this.decode(bytes);
|
|
450
450
|
}
|
|
451
|
-
catch
|
|
451
|
+
catch {
|
|
452
452
|
result = '';
|
|
453
453
|
}
|
|
454
454
|
}
|
|
@@ -456,7 +456,7 @@ class HPACK {
|
|
|
456
456
|
try {
|
|
457
457
|
result = new TextDecoder().decode(bytes);
|
|
458
458
|
}
|
|
459
|
-
catch
|
|
459
|
+
catch {
|
|
460
460
|
result = '';
|
|
461
461
|
}
|
|
462
462
|
}
|
|
@@ -502,7 +502,8 @@ class FrameEncoder {
|
|
|
502
502
|
// 编码SETTINGS帧
|
|
503
503
|
static encodeSettingsFrame(frame) {
|
|
504
504
|
// 计算payload总长度:每个设置项占6字节
|
|
505
|
-
const
|
|
505
|
+
const settingsPayload = frame.payload;
|
|
506
|
+
const payloadLength = settingsPayload.length * 6;
|
|
506
507
|
// 分配缓冲区:9字节头部 + payload长度
|
|
507
508
|
const buffer = new Uint8Array(9 + payloadLength);
|
|
508
509
|
// 编码帧头部(前9个字节)
|
|
@@ -513,7 +514,7 @@ class FrameEncoder {
|
|
|
513
514
|
streamId: frame.streamId
|
|
514
515
|
});
|
|
515
516
|
// 编码payload
|
|
516
|
-
_encodeSettingsPayload(buffer,
|
|
517
|
+
_encodeSettingsPayload(buffer, settingsPayload);
|
|
517
518
|
return buffer;
|
|
518
519
|
}
|
|
519
520
|
// 编码SETTINGS ACK帧
|
|
@@ -583,11 +584,11 @@ const defaultSettings = {
|
|
|
583
584
|
[SETTINGS_PARAMETERS.MAX_FRAME_SIZE]: 16 << 10, // 16k
|
|
584
585
|
[SETTINGS_PARAMETERS.MAX_HEADER_LIST_SIZE]: 8192
|
|
585
586
|
};
|
|
586
|
-
const HTTP2_PREFACE = 'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n';
|
|
587
|
+
const HTTP2_PREFACE$1 = 'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n';
|
|
587
588
|
class Http2Frame {
|
|
588
589
|
// 创建并编码PREFACE帧
|
|
589
590
|
static createPreface() {
|
|
590
|
-
return new TextEncoder().encode(HTTP2_PREFACE);
|
|
591
|
+
return new TextEncoder().encode(HTTP2_PREFACE$1);
|
|
591
592
|
}
|
|
592
593
|
static createPongFrame(payload) {
|
|
593
594
|
return Http2Frame.createFrame(0x6, 0x1, 0, payload);
|
|
@@ -804,6 +805,7 @@ function _createPayload(settings) {
|
|
|
804
805
|
return payload;
|
|
805
806
|
}
|
|
806
807
|
|
|
808
|
+
const HTTP2_PREFACE = new TextEncoder().encode("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n");
|
|
807
809
|
class HTTP2Parser {
|
|
808
810
|
constructor(writer, options) {
|
|
809
811
|
this.buffer = new Uint8Array(0);
|
|
@@ -851,44 +853,49 @@ class HTTP2Parser {
|
|
|
851
853
|
// 处理单个数据块
|
|
852
854
|
_processChunk(chunk) {
|
|
853
855
|
// chunk 是 Uint8ArrayList 或 Uint8Array
|
|
854
|
-
const newData =
|
|
855
|
-
|
|
856
|
+
const newData = 'subarray' in chunk && typeof chunk.subarray === 'function'
|
|
857
|
+
? chunk.subarray()
|
|
858
|
+
: chunk;
|
|
859
|
+
// 原作者之前的 O(N) 内存拷贝优化被保留,去掉了存在 onEnd 竞态的 setTimeout
|
|
856
860
|
const newBuffer = new Uint8Array(this.buffer.length + newData.length);
|
|
857
861
|
newBuffer.set(this.buffer);
|
|
858
862
|
newBuffer.set(newData, this.buffer.length);
|
|
859
863
|
this.buffer = newBuffer;
|
|
860
864
|
// 持续处理所有完整的帧
|
|
861
|
-
|
|
865
|
+
let readOffset = 0;
|
|
866
|
+
while (this.buffer.length - readOffset >= 9) {
|
|
862
867
|
// 判断是否有HTTP/2前导
|
|
863
|
-
if (this.buffer.length >= 24 && this.isHttp2Preface(this.buffer)) {
|
|
864
|
-
|
|
868
|
+
if (this.buffer.length - readOffset >= 24 && this.isHttp2Preface(this.buffer.subarray(readOffset))) {
|
|
869
|
+
readOffset += 24;
|
|
865
870
|
// 发送SETTINGS帧
|
|
866
871
|
const settingFrame = Http2Frame.createSettingsFrame();
|
|
867
872
|
this.writer.write(settingFrame);
|
|
868
|
-
|
|
873
|
+
continue;
|
|
869
874
|
}
|
|
870
|
-
const frameHeader = this._parseFrameHeader(this.buffer);
|
|
875
|
+
const frameHeader = this._parseFrameHeader(this.buffer.subarray(readOffset));
|
|
871
876
|
const totalFrameLength = 9 + frameHeader.length;
|
|
872
877
|
// 检查是否有完整的帧
|
|
873
|
-
if (this.buffer.length < totalFrameLength) {
|
|
878
|
+
if (this.buffer.length - readOffset < totalFrameLength) {
|
|
874
879
|
break;
|
|
875
880
|
}
|
|
876
881
|
// 获取完整帧数据
|
|
877
|
-
const frameData = this.buffer.
|
|
882
|
+
const frameData = this.buffer.subarray(readOffset, readOffset + totalFrameLength);
|
|
878
883
|
// 处理不同类型的帧
|
|
879
884
|
this._handleFrame(frameHeader, frameData).catch((err) => {
|
|
880
885
|
console.error("Error handling frame:", err);
|
|
881
886
|
});
|
|
882
|
-
//
|
|
883
|
-
|
|
887
|
+
// 移动偏移量
|
|
888
|
+
readOffset += totalFrameLength;
|
|
889
|
+
}
|
|
890
|
+
if (readOffset > 0) {
|
|
891
|
+
this.buffer = this.buffer.slice(readOffset);
|
|
884
892
|
}
|
|
885
893
|
}
|
|
886
894
|
isHttp2Preface(buffer) {
|
|
887
|
-
|
|
888
|
-
if (buffer.length < PREFACE.length)
|
|
895
|
+
if (buffer.length < HTTP2_PREFACE.length)
|
|
889
896
|
return false;
|
|
890
|
-
for (let i = 0; i <
|
|
891
|
-
if (buffer[i] !==
|
|
897
|
+
for (let i = 0; i < HTTP2_PREFACE.length; i++) {
|
|
898
|
+
if (buffer[i] !== HTTP2_PREFACE[i])
|
|
892
899
|
return false;
|
|
893
900
|
}
|
|
894
901
|
return true;
|
|
@@ -1075,7 +1082,9 @@ class HTTP2Parser {
|
|
|
1075
1082
|
waiters.forEach(fn => { try {
|
|
1076
1083
|
fn();
|
|
1077
1084
|
}
|
|
1078
|
-
catch {
|
|
1085
|
+
catch (e) {
|
|
1086
|
+
console.debug('waiter error', e);
|
|
1087
|
+
} });
|
|
1079
1088
|
}
|
|
1080
1089
|
break;
|
|
1081
1090
|
case FRAME_TYPES.DATA:
|
|
@@ -1124,7 +1133,7 @@ class HTTP2Parser {
|
|
|
1124
1133
|
break;
|
|
1125
1134
|
case FRAME_TYPES.WINDOW_UPDATE:
|
|
1126
1135
|
// 处理窗口更新帧
|
|
1127
|
-
this.handleWindowUpdateFrame(frameHeader, frameData
|
|
1136
|
+
this.handleWindowUpdateFrame(frameHeader, frameData);
|
|
1128
1137
|
// 更新发送窗口(对端接收窗口)
|
|
1129
1138
|
try {
|
|
1130
1139
|
const inc = this.parseWindowUpdateFrame(frameData, frameHeader).windowSizeIncrement;
|
|
@@ -1139,9 +1148,11 @@ class HTTP2Parser {
|
|
|
1139
1148
|
waiters.forEach(fn => { try {
|
|
1140
1149
|
fn();
|
|
1141
1150
|
}
|
|
1142
|
-
catch {
|
|
1151
|
+
catch (e) {
|
|
1152
|
+
console.debug('waiter error', e);
|
|
1153
|
+
} });
|
|
1143
1154
|
}
|
|
1144
|
-
catch
|
|
1155
|
+
catch { /* ignore WINDOW_UPDATE parse errors */ }
|
|
1145
1156
|
break;
|
|
1146
1157
|
case FRAME_TYPES.PING:
|
|
1147
1158
|
// 处理PING帧
|
|
@@ -1163,7 +1174,7 @@ class HTTP2Parser {
|
|
|
1163
1174
|
info = {};
|
|
1164
1175
|
}
|
|
1165
1176
|
}
|
|
1166
|
-
catch { }
|
|
1177
|
+
catch { /* ignore GOAWAY parse errors */ }
|
|
1167
1178
|
try {
|
|
1168
1179
|
this.onGoaway?.(info ?? {});
|
|
1169
1180
|
}
|
|
@@ -1296,7 +1307,7 @@ class HTTP2Parser {
|
|
|
1296
1307
|
};
|
|
1297
1308
|
}
|
|
1298
1309
|
// 处理 WINDOW_UPDATE 帧
|
|
1299
|
-
handleWindowUpdateFrame(frameHeader, payload
|
|
1310
|
+
handleWindowUpdateFrame(frameHeader, payload) {
|
|
1300
1311
|
try {
|
|
1301
1312
|
const windowUpdate = this.parseWindowUpdateFrame(payload, frameHeader);
|
|
1302
1313
|
this.connectionWindowSize += windowUpdate.windowSizeIncrement;
|
|
@@ -1437,8 +1448,8 @@ class StreamWriter {
|
|
|
1437
1448
|
}
|
|
1438
1449
|
catch (err) {
|
|
1439
1450
|
// Gracefully handle stream closing errors - 不要传递到 handleError
|
|
1440
|
-
const errMsg = err.message
|
|
1441
|
-
if (err.name === 'StreamStateError' ||
|
|
1451
|
+
const errMsg = (err instanceof Error ? err.message : '').toLowerCase();
|
|
1452
|
+
if ((err instanceof Error && err.name === 'StreamStateError') ||
|
|
1442
1453
|
errMsg.includes('closing') ||
|
|
1443
1454
|
errMsg.includes('closed') ||
|
|
1444
1455
|
errMsg.includes('write to a stream that is closed')) {
|
|
@@ -1450,6 +1461,7 @@ class StreamWriter {
|
|
|
1450
1461
|
}
|
|
1451
1462
|
}
|
|
1452
1463
|
createTransform() {
|
|
1464
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
1453
1465
|
const self = this;
|
|
1454
1466
|
return async function* (source) {
|
|
1455
1467
|
for await (const chunk of source) {
|
|
@@ -1459,7 +1471,7 @@ class StreamWriter {
|
|
|
1459
1471
|
// 因此这里统计的 bytesDrained 更接近实际被 sink 消费的字节数
|
|
1460
1472
|
try {
|
|
1461
1473
|
// 在下游消费后再扣减待消费队列,避免误判“next 没取”
|
|
1462
|
-
if (self.p
|
|
1474
|
+
if (self.p._queueSize != null) {
|
|
1463
1475
|
self.p._queueSize = Math.max(0, self.p._queueSize - chunk.byteLength);
|
|
1464
1476
|
}
|
|
1465
1477
|
self.bytesDrained += chunk.byteLength;
|
|
@@ -1479,10 +1491,11 @@ class StreamWriter {
|
|
|
1479
1491
|
};
|
|
1480
1492
|
}
|
|
1481
1493
|
// 简单的卡顿看门狗:当队列长期高位且 bytesDrained 无进展时发出 stalled 事件
|
|
1494
|
+
// 使用递归 setTimeout 而非 setInterval,避免标签页后台恢复时回调堆积导致 Violation
|
|
1482
1495
|
startWatchdog(intervalMs = 500, stallMs = 1500) {
|
|
1483
1496
|
if (this.watchdogTimer)
|
|
1484
1497
|
return;
|
|
1485
|
-
|
|
1498
|
+
const tick = () => {
|
|
1486
1499
|
if (this.abortController.signal.aborted)
|
|
1487
1500
|
return;
|
|
1488
1501
|
const baseThreshold = this.options.bufferSize * 0.7;
|
|
@@ -1493,7 +1506,13 @@ class StreamWriter {
|
|
|
1493
1506
|
if (!this.stallStartAt)
|
|
1494
1507
|
this.stallStartAt = now;
|
|
1495
1508
|
if (now - this.stallStartAt >= stallMs) {
|
|
1496
|
-
|
|
1509
|
+
// 异步触发事件,让当前 tick 立即返回,避免同步事件处理器阻塞主线程
|
|
1510
|
+
const detail = { queueSize: q, drained: this.bytesDrained, sinceMs: now - this.stallStartAt };
|
|
1511
|
+
setTimeout(() => {
|
|
1512
|
+
if (!this.abortController.signal.aborted) {
|
|
1513
|
+
this.dispatchEvent(new CustomEvent('stalled', { detail }));
|
|
1514
|
+
}
|
|
1515
|
+
}, 0);
|
|
1497
1516
|
// 避免持续触发,推进起点
|
|
1498
1517
|
this.stallStartAt = now;
|
|
1499
1518
|
}
|
|
@@ -1508,7 +1527,10 @@ class StreamWriter {
|
|
|
1508
1527
|
// 队列回落,重置
|
|
1509
1528
|
this.stallStartAt = 0;
|
|
1510
1529
|
}
|
|
1511
|
-
|
|
1530
|
+
// 本次 tick 完成后再安排下一次,不会因主线程繁忙而堆积
|
|
1531
|
+
this.watchdogTimer = setTimeout(tick, intervalMs);
|
|
1532
|
+
};
|
|
1533
|
+
this.watchdogTimer = setTimeout(tick, intervalMs);
|
|
1512
1534
|
}
|
|
1513
1535
|
async write(data) {
|
|
1514
1536
|
// 静默处理 aborted 状态,避免在正常的流关闭场景下抛出错误
|
|
@@ -1540,6 +1562,8 @@ class StreamWriter {
|
|
|
1540
1562
|
return data.arrayBuffer();
|
|
1541
1563
|
if (typeof data === 'string')
|
|
1542
1564
|
return new TextEncoder().encode(data).buffer;
|
|
1565
|
+
if (data instanceof Uint8Array)
|
|
1566
|
+
return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
1543
1567
|
return data;
|
|
1544
1568
|
}
|
|
1545
1569
|
async writeChunks(buffer) {
|
|
@@ -1682,7 +1706,7 @@ class StreamWriter {
|
|
|
1682
1706
|
}
|
|
1683
1707
|
catch (err) {
|
|
1684
1708
|
// 忽略关闭已关闭流的错误
|
|
1685
|
-
const errMsg = err.message
|
|
1709
|
+
const errMsg = (err instanceof Error ? err.message : '').toLowerCase();
|
|
1686
1710
|
if (!errMsg.includes('closed') && !errMsg.includes('closing')) {
|
|
1687
1711
|
this.log?.trace?.('Stream close error:', err);
|
|
1688
1712
|
}
|
|
@@ -1700,7 +1724,8 @@ class StreamWriter {
|
|
|
1700
1724
|
// 先检查流状态,避免在已关闭的流上调用 abort
|
|
1701
1725
|
if (this.stream && typeof this.stream.abort === 'function') {
|
|
1702
1726
|
// 检查流的状态,避免操作已关闭的流
|
|
1703
|
-
const streamState = this.stream.status ||
|
|
1727
|
+
const streamState = this.stream.status ||
|
|
1728
|
+
this.stream.state;
|
|
1704
1729
|
if (streamState !== 'closed' && streamState !== 'closing') {
|
|
1705
1730
|
this.stream.abort(new Error(reason));
|
|
1706
1731
|
}
|
|
@@ -1709,7 +1734,7 @@ class StreamWriter {
|
|
|
1709
1734
|
catch (err) {
|
|
1710
1735
|
// Stream may already be closed, ignore all stream-related errors
|
|
1711
1736
|
// 完全忽略流操作错误,避免在错误处理中再次抛出错误
|
|
1712
|
-
const errMsg = err.message
|
|
1737
|
+
const errMsg = (err instanceof Error ? err.message : '').toLowerCase();
|
|
1713
1738
|
if (!errMsg.includes('closed') && !errMsg.includes('closing') && !errMsg.includes('write')) {
|
|
1714
1739
|
this.log?.trace?.('Stream abort error:', err);
|
|
1715
1740
|
}
|
|
@@ -1731,17 +1756,17 @@ class StreamWriter {
|
|
|
1731
1756
|
}
|
|
1732
1757
|
// 立即拒绝所有待处理的写入任务,避免它们继续执行
|
|
1733
1758
|
const pendingTasks = this.writeQueue.splice(0);
|
|
1734
|
-
pendingTasks.forEach(
|
|
1759
|
+
pendingTasks.forEach(() => {
|
|
1735
1760
|
// 这些任务的 Promise 会在执行时因为检查到 aborted 而被拒绝
|
|
1736
1761
|
});
|
|
1737
1762
|
try {
|
|
1738
1763
|
this.p.end();
|
|
1739
1764
|
}
|
|
1740
|
-
catch
|
|
1765
|
+
catch {
|
|
1741
1766
|
// Ignore errors when ending pushable
|
|
1742
1767
|
}
|
|
1743
1768
|
if (this.watchdogTimer) {
|
|
1744
|
-
|
|
1769
|
+
clearTimeout(this.watchdogTimer);
|
|
1745
1770
|
this.watchdogTimer = undefined;
|
|
1746
1771
|
}
|
|
1747
1772
|
}
|
|
@@ -1877,6 +1902,7 @@ class Libp2pGrpcClient {
|
|
|
1877
1902
|
}
|
|
1878
1903
|
await new Promise((resolve, reject) => {
|
|
1879
1904
|
let settled = false;
|
|
1905
|
+
// eslint-disable-next-line prefer-const
|
|
1880
1906
|
let timeoutId;
|
|
1881
1907
|
const cleanup = () => {
|
|
1882
1908
|
const idx = state.waiters.indexOf(waiter);
|
|
@@ -1959,7 +1985,7 @@ class Libp2pGrpcClient {
|
|
|
1959
1985
|
if (pooled) {
|
|
1960
1986
|
const conn = pooled.connection;
|
|
1961
1987
|
if (conn) {
|
|
1962
|
-
const status = conn
|
|
1988
|
+
const status = conn.status;
|
|
1963
1989
|
if (!status || status === "open") {
|
|
1964
1990
|
return conn;
|
|
1965
1991
|
}
|
|
@@ -1986,17 +2012,9 @@ class Libp2pGrpcClient {
|
|
|
1986
2012
|
}
|
|
1987
2013
|
};
|
|
1988
2014
|
try {
|
|
1989
|
-
|
|
1990
|
-
if (typeof anyConn.addEventListener === "function") {
|
|
1991
|
-
anyConn.addEventListener("close", removeFromPool, {
|
|
1992
|
-
once: true,
|
|
1993
|
-
});
|
|
1994
|
-
}
|
|
1995
|
-
else if (typeof anyConn.once === "function") {
|
|
1996
|
-
anyConn.once("close", removeFromPool);
|
|
1997
|
-
}
|
|
2015
|
+
conn.addEventListener("close", removeFromPool, { once: true });
|
|
1998
2016
|
}
|
|
1999
|
-
catch { }
|
|
2017
|
+
catch { /* ignore event listener registration errors */ }
|
|
2000
2018
|
}
|
|
2001
2019
|
}
|
|
2002
2020
|
return conn;
|
|
@@ -2066,14 +2084,14 @@ class Libp2pGrpcClient {
|
|
|
2066
2084
|
});
|
|
2067
2085
|
try {
|
|
2068
2086
|
writer.addEventListener("backpressure", (e) => {
|
|
2069
|
-
const d = e
|
|
2087
|
+
const d = e.detail || {};
|
|
2070
2088
|
console.warn(`[unary stream ${streamId}] backpressure current=${d.currentSize} avg=${d.averageSize} threshold=${d.threshold}`);
|
|
2071
2089
|
});
|
|
2072
|
-
writer.addEventListener("drain", (
|
|
2073
|
-
|
|
2090
|
+
writer.addEventListener("drain", () => {
|
|
2091
|
+
// drain event - no action needed
|
|
2074
2092
|
});
|
|
2075
2093
|
writer.addEventListener("stalled", (e) => {
|
|
2076
|
-
const d = e
|
|
2094
|
+
const d = e.detail || {};
|
|
2077
2095
|
console.warn(`[unary stream ${streamId}] stalled queue=${d.queueSize} drained=${d.drained} since=${d.sinceMs}ms — sending PING`);
|
|
2078
2096
|
try {
|
|
2079
2097
|
const payload = new Uint8Array(8);
|
|
@@ -2081,10 +2099,10 @@ class Libp2pGrpcClient {
|
|
|
2081
2099
|
const ping = Http2Frame.createFrame(0x6, 0x0, 0, payload);
|
|
2082
2100
|
writer.write(ping);
|
|
2083
2101
|
}
|
|
2084
|
-
catch { }
|
|
2102
|
+
catch { /* ignore ping write errors */ }
|
|
2085
2103
|
});
|
|
2086
2104
|
}
|
|
2087
|
-
catch { }
|
|
2105
|
+
catch { /* ignore addEventListener errors */ }
|
|
2088
2106
|
const parser = new HTTP2Parser(writer);
|
|
2089
2107
|
parser.onGoaway = (info) => {
|
|
2090
2108
|
console.warn("[unaryCall] GOAWAY received from server", info);
|
|
@@ -2095,7 +2113,7 @@ class Libp2pGrpcClient {
|
|
|
2095
2113
|
exitFlag = true;
|
|
2096
2114
|
errMsg = `GOAWAY received: code=${info.errorCode}`;
|
|
2097
2115
|
try {
|
|
2098
|
-
connection?.close
|
|
2116
|
+
connection?.close();
|
|
2099
2117
|
}
|
|
2100
2118
|
catch (err) {
|
|
2101
2119
|
console.warn("Error closing connection after GOAWAY:", err);
|
|
@@ -2120,7 +2138,6 @@ class Libp2pGrpcClient {
|
|
|
2120
2138
|
if (payload.length < 5) {
|
|
2121
2139
|
return;
|
|
2122
2140
|
}
|
|
2123
|
-
const compressionFlag = payload[0]; // 压缩标志
|
|
2124
2141
|
const lengthBytes = payload.slice(1, 5); // 消息长度的4字节
|
|
2125
2142
|
responseDataExpectedLength = new DataView(lengthBytes.buffer, lengthBytes.byteOffset).getUint32(0, false); // big-endian
|
|
2126
2143
|
if (responseDataExpectedLength < 0) {
|
|
@@ -2197,7 +2214,7 @@ class Libp2pGrpcClient {
|
|
|
2197
2214
|
const ackSettingFrame = Http2Frame.createSettingsAckFrame();
|
|
2198
2215
|
writer.write(ackSettingFrame);
|
|
2199
2216
|
};
|
|
2200
|
-
parser.onHeaders = (headers
|
|
2217
|
+
parser.onHeaders = (headers) => {
|
|
2201
2218
|
const plainHeaders = hpack.decodeHeaderFields(headers);
|
|
2202
2219
|
if (plainHeaders.get("grpc-status") === "0") {
|
|
2203
2220
|
// 成功状态
|
|
@@ -2222,21 +2239,12 @@ class Libp2pGrpcClient {
|
|
|
2222
2239
|
const settingFrme = Http2Frame.createSettingsFrame();
|
|
2223
2240
|
await writer.write(settingFrme);
|
|
2224
2241
|
// 等待对端 SETTINGS 或 ACK,择一即可,避免偶发握手竞态
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
})(),
|
|
2232
|
-
(async () => {
|
|
2233
|
-
await parser.waitForSettingsAck();
|
|
2234
|
-
progressed = true;
|
|
2235
|
-
})(),
|
|
2236
|
-
new Promise((res) => setTimeout(res, 300)),
|
|
2237
|
-
]);
|
|
2238
|
-
// 即使未等到,也继续;多数实现会随后发送
|
|
2239
|
-
}
|
|
2242
|
+
await Promise.race([
|
|
2243
|
+
parser.waitForPeerSettings(1000),
|
|
2244
|
+
parser.waitForSettingsAck(),
|
|
2245
|
+
new Promise((res) => setTimeout(res, 300)),
|
|
2246
|
+
]);
|
|
2247
|
+
// 即使未等到,也继续;多数实现会随后发送
|
|
2240
2248
|
// 创建头部帧
|
|
2241
2249
|
const headerFrame = Http2Frame.createHeadersFrame(streamId, method, true, this.token);
|
|
2242
2250
|
await writer.write(headerFrame);
|
|
@@ -2264,9 +2272,9 @@ class Libp2pGrpcClient {
|
|
|
2264
2272
|
checkResponse();
|
|
2265
2273
|
});
|
|
2266
2274
|
try {
|
|
2267
|
-
await writer.flush
|
|
2275
|
+
await writer.flush(timeout);
|
|
2268
2276
|
}
|
|
2269
|
-
catch { }
|
|
2277
|
+
catch { /* ignore flush errors */ }
|
|
2270
2278
|
await writer.end();
|
|
2271
2279
|
}
|
|
2272
2280
|
catch (err) {
|
|
@@ -2366,7 +2374,7 @@ class Libp2pGrpcClient {
|
|
|
2366
2374
|
if (options?.freshConnection) {
|
|
2367
2375
|
try {
|
|
2368
2376
|
this.connectionPool.delete(this.peerAddr.toString());
|
|
2369
|
-
await this.node
|
|
2377
|
+
await this.node.hangUp(this.peerAddr);
|
|
2370
2378
|
console.warn("[Call] hangUp existing connection before dialing due to freshConnection=true");
|
|
2371
2379
|
}
|
|
2372
2380
|
catch (err) {
|
|
@@ -2399,14 +2407,14 @@ class Libp2pGrpcClient {
|
|
|
2399
2407
|
});
|
|
2400
2408
|
try {
|
|
2401
2409
|
writer.addEventListener("backpressure", (e) => {
|
|
2402
|
-
const d = e
|
|
2410
|
+
const d = e.detail || {};
|
|
2403
2411
|
console.warn(`[stream ${streamId}] backpressure current=${d.currentSize} avg=${d.averageSize} threshold=${d.threshold}`);
|
|
2404
2412
|
});
|
|
2405
|
-
writer.addEventListener("drain", (
|
|
2406
|
-
|
|
2413
|
+
writer.addEventListener("drain", () => {
|
|
2414
|
+
// drain event - no action needed
|
|
2407
2415
|
});
|
|
2408
2416
|
writer.addEventListener("stalled", (e) => {
|
|
2409
|
-
const d = e
|
|
2417
|
+
const d = e.detail || {};
|
|
2410
2418
|
console.warn(`[stream ${streamId}] stalled queue=${d.queueSize} drained=${d.drained} since=${d.sinceMs}ms — sending PING`);
|
|
2411
2419
|
try {
|
|
2412
2420
|
const payload = new Uint8Array(8);
|
|
@@ -2414,10 +2422,10 @@ class Libp2pGrpcClient {
|
|
|
2414
2422
|
const ping = Http2Frame.createFrame(0x6, 0x0, 0, payload);
|
|
2415
2423
|
writer.write(ping);
|
|
2416
2424
|
}
|
|
2417
|
-
catch { /*
|
|
2425
|
+
catch { /* ignore ping write errors */ }
|
|
2418
2426
|
});
|
|
2419
2427
|
}
|
|
2420
|
-
catch { /*
|
|
2428
|
+
catch { /* ignore addEventListener errors */ }
|
|
2421
2429
|
const parser = new HTTP2Parser(writer, {
|
|
2422
2430
|
compatibilityMode: !useFlowControl,
|
|
2423
2431
|
});
|
|
@@ -2434,7 +2442,7 @@ class Libp2pGrpcClient {
|
|
|
2434
2442
|
}
|
|
2435
2443
|
internalController.abort();
|
|
2436
2444
|
try {
|
|
2437
|
-
connection?.close
|
|
2445
|
+
connection?.close();
|
|
2438
2446
|
}
|
|
2439
2447
|
catch (err) {
|
|
2440
2448
|
console.warn("Error closing connection after GOAWAY:", err);
|
|
@@ -2469,7 +2477,7 @@ class Libp2pGrpcClient {
|
|
|
2469
2477
|
}
|
|
2470
2478
|
};
|
|
2471
2479
|
// 在各个回调中检查是否已中止
|
|
2472
|
-
parser.onData = async (payload
|
|
2480
|
+
parser.onData = async (payload) => {
|
|
2473
2481
|
// 检查是否已中止
|
|
2474
2482
|
if (internalController.signal.aborted) {
|
|
2475
2483
|
return;
|
|
@@ -2489,7 +2497,6 @@ class Libp2pGrpcClient {
|
|
|
2489
2497
|
// 如果还没有读取消息长度,且缓冲区有足够数据
|
|
2490
2498
|
if (expectedMessageLength === -1 && messageBuffer.length >= 5) {
|
|
2491
2499
|
// 读取 gRPC 消息头:1字节压缩标志 + 4字节长度
|
|
2492
|
-
const compressionFlag = messageBuffer[0];
|
|
2493
2500
|
const lengthBytes = messageBuffer.slice(1, 5);
|
|
2494
2501
|
expectedMessageLength = new DataView(lengthBytes.buffer, lengthBytes.byteOffset).getUint32(0, false); // big-endian
|
|
2495
2502
|
}
|
|
@@ -2526,7 +2533,7 @@ class Libp2pGrpcClient {
|
|
|
2526
2533
|
const ackSettingFrame = Http2Frame.createSettingsAckFrame();
|
|
2527
2534
|
writer.write(ackSettingFrame);
|
|
2528
2535
|
};
|
|
2529
|
-
parser.onHeaders = (headers
|
|
2536
|
+
parser.onHeaders = (headers) => {
|
|
2530
2537
|
// 检查是否已中止
|
|
2531
2538
|
if (internalController.signal.aborted)
|
|
2532
2539
|
return;
|
|
@@ -2572,16 +2579,9 @@ class Libp2pGrpcClient {
|
|
|
2572
2579
|
}
|
|
2573
2580
|
// 等待对端 SETTINGS 或 ACK,择一即可,避免偶发握手竞态
|
|
2574
2581
|
{
|
|
2575
|
-
let progressed = false;
|
|
2576
2582
|
await Promise.race([
|
|
2577
|
-
(
|
|
2578
|
-
|
|
2579
|
-
progressed = true;
|
|
2580
|
-
})(),
|
|
2581
|
-
(async () => {
|
|
2582
|
-
await parser.waitForSettingsAck();
|
|
2583
|
-
progressed = true;
|
|
2584
|
-
})(),
|
|
2583
|
+
parser.waitForPeerSettings(1000),
|
|
2584
|
+
parser.waitForSettingsAck(),
|
|
2585
2585
|
new Promise((res) => setTimeout(res, 300)),
|
|
2586
2586
|
]);
|
|
2587
2587
|
// 即使未等到,也继续;多数实现会随后发送
|
|
@@ -2618,7 +2618,6 @@ class Libp2pGrpcClient {
|
|
|
2618
2618
|
}
|
|
2619
2619
|
// 动态批量处理逻辑 - 在处理过程中动态补充新数据
|
|
2620
2620
|
const batchSize = options?.batchSize || 10;
|
|
2621
|
-
const maxBatchWaitMs = options?.maxBatchWaitMs || 50;
|
|
2622
2621
|
// 动态批处理器
|
|
2623
2622
|
const processingQueue = [];
|
|
2624
2623
|
let isProcessing = false;
|
|
@@ -2749,9 +2748,9 @@ class Libp2pGrpcClient {
|
|
|
2749
2748
|
await writeFrame(finalFrame);
|
|
2750
2749
|
// 在结束前尽量冲刷内部队列,避免服务器看到部分数据 + context canceled
|
|
2751
2750
|
try {
|
|
2752
|
-
await writer.flush
|
|
2751
|
+
await writer.flush(timeout);
|
|
2753
2752
|
}
|
|
2754
|
-
catch { }
|
|
2753
|
+
catch { /* ignore flush errors */ }
|
|
2755
2754
|
await writer.end();
|
|
2756
2755
|
}
|
|
2757
2756
|
// 检查是否已中止
|
|
@@ -2798,15 +2797,16 @@ class Libp2pGrpcClient {
|
|
|
2798
2797
|
if (options?.freshConnection) {
|
|
2799
2798
|
try {
|
|
2800
2799
|
// 通过 libp2p 连接管理器关闭到该 peer 的连接
|
|
2801
|
-
|
|
2800
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2801
|
+
const conns = this.node.getConnections?.(this.peerAddr) || [];
|
|
2802
2802
|
for (const c of conns) {
|
|
2803
2803
|
try {
|
|
2804
2804
|
await c.close?.();
|
|
2805
2805
|
}
|
|
2806
|
-
catch { }
|
|
2806
|
+
catch { /* ignore close errors */ }
|
|
2807
2807
|
}
|
|
2808
2808
|
}
|
|
2809
|
-
catch { }
|
|
2809
|
+
catch { /* ignore connection cleanup errors */ }
|
|
2810
2810
|
}
|
|
2811
2811
|
if (streamSlotAcquired && state) {
|
|
2812
2812
|
state.activeStreams = Math.max(0, state.activeStreams - 1);
|