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.
@@ -8,22 +8,24 @@ type ParserOptions = {
8
8
  compatibilityMode?: boolean
9
9
  }
10
10
 
11
+ const HTTP2_PREFACE = new TextEncoder().encode("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n");
12
+
11
13
  export class HTTP2Parser {
12
14
  buffer: Uint8Array;
13
15
  settingsAckReceived: boolean;
14
16
  peerSettingsReceived: boolean;
15
17
  connectionWindowSize: number;
16
- streams: Map<number, any>;
18
+ streams: Map<number, unknown>;
17
19
  defaultStreamWindowSize: number;
18
20
  // 发送方向(对端的接收窗口)跟踪
19
21
  sendConnWindow: number;
20
22
  sendStreamWindows: Map<number, number>;
21
23
  peerInitialStreamWindow: number;
22
24
  private sendWindowWaiters: Array<() => void>;
23
- onSettings?: (frameHeader: any) => void;
24
- onData?: (payload: Uint8Array, frameHeader: any) => void;
25
+ onSettings?: (frameHeader: Frame) => void;
26
+ onData?: (payload: Uint8Array, frameHeader: Frame) => void;
25
27
  onEnd?: () => void;
26
- onHeaders?: (headers: Uint8Array, frameHeader: any) => void;
28
+ onHeaders?: (headers: Uint8Array, frameHeader: Frame) => void;
27
29
  onGoaway?: (info: { lastStreamId?: number; errorCode?: number }) => void;
28
30
  onSettingsParsed?: (settings: { maxConcurrentStreams?: number; initialWindowSize?: number }) => void;
29
31
  endFlag: boolean;
@@ -76,53 +78,58 @@ export class HTTP2Parser {
76
78
  }
77
79
 
78
80
  // 处理单个数据块
79
- private _processChunk(chunk: any): void {
81
+ private _processChunk(chunk: Uint8Array | { subarray(): Uint8Array }): void {
80
82
  // chunk 是 Uint8ArrayList 或 Uint8Array
81
- const newData = chunk.subarray ? chunk.subarray() : new Uint8Array(chunk);
82
-
83
- // 累积数据到buffer
83
+ const newData: Uint8Array = 'subarray' in chunk && typeof chunk.subarray === 'function'
84
+ ? chunk.subarray()
85
+ : (chunk as Uint8Array);
86
+
87
+ // 原作者之前的 O(N) 内存拷贝优化被保留,去掉了存在 onEnd 竞态的 setTimeout
84
88
  const newBuffer = new Uint8Array(this.buffer.length + newData.length);
85
89
  newBuffer.set(this.buffer);
86
90
  newBuffer.set(newData, this.buffer.length);
87
91
  this.buffer = newBuffer;
88
-
92
+
89
93
  // 持续处理所有完整的帧
90
- while (this.buffer.length >= 9) {
94
+ let readOffset = 0;
95
+ while (this.buffer.length - readOffset >= 9) {
91
96
  // 判断是否有HTTP/2前导
92
- if (this.buffer.length >= 24 && this.isHttp2Preface(this.buffer)) {
93
- this.buffer = this.buffer.slice(24);
97
+ if (this.buffer.length - readOffset >= 24 && this.isHttp2Preface(this.buffer.subarray(readOffset))) {
98
+ readOffset += 24;
94
99
  // 发送SETTINGS帧
95
100
  const settingFrame = Http2Frame.createSettingsFrame();
96
- this.writer.write(settingFrame as any);
97
- break;
101
+ this.writer.write(settingFrame);
102
+ continue;
98
103
  }
99
- const frameHeader = this._parseFrameHeader(this.buffer);
104
+
105
+ const frameHeader = this._parseFrameHeader(this.buffer.subarray(readOffset));
100
106
  const totalFrameLength = 9 + frameHeader.length;
101
107
 
102
108
  // 检查是否有完整的帧
103
- if (this.buffer.length < totalFrameLength) {
109
+ if (this.buffer.length - readOffset < totalFrameLength) {
104
110
  break;
105
111
  }
106
112
  // 获取完整帧数据
107
- const frameData = this.buffer.slice(0, totalFrameLength);
113
+ const frameData = this.buffer.subarray(readOffset, readOffset + totalFrameLength);
108
114
 
109
115
  // 处理不同类型的帧
110
116
  this._handleFrame(frameHeader, frameData).catch((err) => {
111
117
  console.error("Error handling frame:", err);
112
118
  });
113
119
 
114
- // 移除已处理的帧
115
- this.buffer = this.buffer.slice(totalFrameLength);
120
+ // 移动偏移量
121
+ readOffset += totalFrameLength;
122
+ }
123
+
124
+ if (readOffset > 0) {
125
+ this.buffer = this.buffer.slice(readOffset);
116
126
  }
117
127
  }
118
128
 
119
129
  private isHttp2Preface(buffer: Uint8Array): boolean {
120
- const PREFACE = new TextEncoder().encode(
121
- "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
122
- );
123
- if (buffer.length < PREFACE.length) return false;
124
- for (let i = 0; i < PREFACE.length; i++) {
125
- if (buffer[i] !== PREFACE[i]) return false;
130
+ if (buffer.length < HTTP2_PREFACE.length) return false;
131
+ for (let i = 0; i < HTTP2_PREFACE.length; i++) {
132
+ if (buffer[i] !== HTTP2_PREFACE[i]) return false;
126
133
  }
127
134
  return true;
128
135
  }
@@ -209,7 +216,7 @@ export class HTTP2Parser {
209
216
  async waitForSendWindow(streamId: number, minBytes: number = 1, timeoutMs: number = 30000): Promise<void> {
210
217
  const start = Date.now();
211
218
  return new Promise((resolve, reject) => {
212
- let interval: NodeJS.Timeout | null = null;
219
+ let interval: ReturnType<typeof setInterval> | null = null;
213
220
  let settled = false;
214
221
  const check = () => {
215
222
  const { conn, stream } = this.getSendWindows(streamId);
@@ -319,7 +326,7 @@ export class HTTP2Parser {
319
326
  this.peerSettingsReceived = true;
320
327
  // 唤醒等待窗口(以防部分实现通过 SETTINGS 改变有效窗口)
321
328
  const waiters = this.sendWindowWaiters.splice(0);
322
- waiters.forEach(fn => { try { fn(); } catch {} });
329
+ waiters.forEach(fn => { try { fn(); } catch (e) { console.debug('waiter error', e); } });
323
330
  }
324
331
  break;
325
332
 
@@ -336,7 +343,7 @@ export class HTTP2Parser {
336
343
  frameHeader.streamId,
337
344
  frameHeader.length ?? 0
338
345
  );
339
- this.writer.write(streamWindowUpdate as any);
346
+ this.writer.write(streamWindowUpdate);
340
347
  }
341
348
 
342
349
  // 更新连接级别的窗口
@@ -344,7 +351,7 @@ export class HTTP2Parser {
344
351
  0,
345
352
  frameHeader.length ?? 0
346
353
  );
347
- this.writer.write(connWindowUpdate as any);
354
+ this.writer.write(connWindowUpdate);
348
355
  } catch (err) {
349
356
  console.error("[HTTP2] Error sending window update:", err);
350
357
  }
@@ -381,8 +388,7 @@ export class HTTP2Parser {
381
388
  // 处理窗口更新帧
382
389
  this.handleWindowUpdateFrame(
383
390
  frameHeader,
384
- frameData,
385
- frameHeader.streamId
391
+ frameData
386
392
  );
387
393
  // 更新发送窗口(对端接收窗口)
388
394
  try {
@@ -394,8 +400,8 @@ export class HTTP2Parser {
394
400
  this.sendStreamWindows.set(frameHeader.streamId, cur + inc);
395
401
  }
396
402
  const waiters = this.sendWindowWaiters.splice(0);
397
- waiters.forEach(fn => { try { fn(); } catch {} });
398
- } catch (e) {}
403
+ waiters.forEach(fn => { try { fn(); } catch (e) { console.debug('waiter error', e); } });
404
+ } catch { /* ignore WINDOW_UPDATE parse errors */ }
399
405
  break;
400
406
  case FRAME_TYPES.PING:
401
407
  // 处理PING帧
@@ -415,7 +421,7 @@ export class HTTP2Parser {
415
421
  console.warn('[HTTP2] GOAWAY received');
416
422
  info = {};
417
423
  }
418
- } catch {}
424
+ } catch { /* ignore GOAWAY parse errors */ }
419
425
  try {
420
426
  this.onGoaway?.(info ?? {});
421
427
  } catch (err) {
@@ -473,7 +479,7 @@ export class HTTP2Parser {
473
479
  // 反馈PONG帧
474
480
  const pongFrame = Http2Frame.createPongFrame(frameData.slice(9));
475
481
  try {
476
- this.writer.write(pongFrame as any);
482
+ this.writer.write(pongFrame);
477
483
  } catch (error) {
478
484
  console.error("Error sending PONG frame:", error);
479
485
  throw error;
@@ -489,7 +495,7 @@ export class HTTP2Parser {
489
495
  return;
490
496
  }
491
497
  // 如果是0 ,则不设置超时
492
- let timeout: NodeJS.Timeout | null = null;
498
+ let timeout: ReturnType<typeof setTimeout> | null = null;
493
499
  if (waitTime > 0) {
494
500
  timeout = setTimeout(() => {
495
501
  clearInterval(interval);
@@ -561,8 +567,7 @@ export class HTTP2Parser {
561
567
  // 处理 WINDOW_UPDATE 帧
562
568
  handleWindowUpdateFrame(
563
569
  frameHeader: Frame,
564
- payload: Uint8Array,
565
- streamId: number
570
+ payload: Uint8Array
566
571
  ) {
567
572
  try {
568
573
  const windowUpdate = this.parseWindowUpdateFrame(payload, frameHeader);
@@ -41,13 +41,13 @@ export class StreamWriter {
41
41
  private lastBackpressureCheck = 0 // 添加时间戳缓存
42
42
  private bytesDrained = 0 // 统计下游实际消化的字节数
43
43
  private lastDrainEventAt = 0
44
- private watchdogTimer: any
44
+ private watchdogTimer: ReturnType<typeof setTimeout> | undefined
45
45
  private stallStartAt = 0
46
46
  private lastBytesDrainedSeen = 0
47
47
  private lastBpWarnAt = 0
48
48
  private isHandlingError = false // 防止重复错误处理
49
49
 
50
- private log?: { trace?: (...args: any[]) => void }
50
+ private log?: { trace?: (...args: unknown[]) => void }
51
51
 
52
52
  constructor(
53
53
  private stream: Stream,
@@ -58,7 +58,7 @@ export class StreamWriter {
58
58
  throw new Error('StreamWriter requires a valid stream object')
59
59
  }
60
60
 
61
- this.log = { trace: (...args: any[]) => console.debug('[StreamWriter]', ...args) }
61
+ this.log = { trace: (...args: unknown[]) => console.debug('[StreamWriter]', ...args) }
62
62
 
63
63
  if (options){
64
64
  this.options = {
@@ -172,10 +172,10 @@ export class StreamWriter {
172
172
  // 等待 drain 事件
173
173
  await this.stream.onDrain()
174
174
  }
175
- } catch (err: any) {
175
+ } catch (err: unknown) {
176
176
  // Gracefully handle stream closing errors - 不要传递到 handleError
177
- const errMsg = err.message?.toLowerCase() || ''
178
- if (err.name === 'StreamStateError' ||
177
+ const errMsg = (err instanceof Error ? err.message : '').toLowerCase()
178
+ if ((err instanceof Error && err.name === 'StreamStateError') ||
179
179
  errMsg.includes('closing') ||
180
180
  errMsg.includes('closed') ||
181
181
  errMsg.includes('write to a stream that is closed')) {
@@ -188,6 +188,7 @@ export class StreamWriter {
188
188
  }
189
189
 
190
190
  private createTransform() {
191
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
191
192
  const self = this;
192
193
  return async function* (source: AsyncIterable<Uint8Array>) {
193
194
  for await (const chunk of source) {
@@ -197,8 +198,8 @@ export class StreamWriter {
197
198
  // 因此这里统计的 bytesDrained 更接近实际被 sink 消费的字节数
198
199
  try {
199
200
  // 在下游消费后再扣减待消费队列,避免误判“next 没取”
200
- if ((self.p as any)?._queueSize != null) {
201
- (self.p as any)._queueSize = Math.max(0, (self.p as any)._queueSize - chunk.byteLength)
201
+ if (self.p._queueSize != null) {
202
+ self.p._queueSize = Math.max(0, self.p._queueSize - chunk.byteLength)
202
203
  }
203
204
  self.bytesDrained += chunk.byteLength
204
205
  const now = Date.now()
@@ -218,9 +219,10 @@ export class StreamWriter {
218
219
  }
219
220
 
220
221
  // 简单的卡顿看门狗:当队列长期高位且 bytesDrained 无进展时发出 stalled 事件
222
+ // 使用递归 setTimeout 而非 setInterval,避免标签页后台恢复时回调堆积导致 Violation
221
223
  private startWatchdog(intervalMs: number = 500, stallMs: number = 1500) {
222
224
  if (this.watchdogTimer) return
223
- this.watchdogTimer = setInterval(() => {
225
+ const tick = () => {
224
226
  if (this.abortController.signal.aborted) return
225
227
  const baseThreshold = this.options.bufferSize! * 0.7
226
228
  const q = this.queueSize
@@ -229,7 +231,13 @@ export class StreamWriter {
229
231
  if (this.lastBytesDrainedSeen === this.bytesDrained) {
230
232
  if (!this.stallStartAt) this.stallStartAt = now
231
233
  if (now - this.stallStartAt >= stallMs) {
232
- this.dispatchEvent(new CustomEvent('stalled', { detail: { queueSize: q, drained: this.bytesDrained, sinceMs: now - this.stallStartAt } }))
234
+ // 异步触发事件,让当前 tick 立即返回,避免同步事件处理器阻塞主线程
235
+ const detail = { queueSize: q, drained: this.bytesDrained, sinceMs: now - this.stallStartAt }
236
+ setTimeout(() => {
237
+ if (!this.abortController.signal.aborted) {
238
+ this.dispatchEvent(new CustomEvent('stalled', { detail }))
239
+ }
240
+ }, 0)
233
241
  // 避免持续触发,推进起点
234
242
  this.stallStartAt = now
235
243
  }
@@ -242,10 +250,13 @@ export class StreamWriter {
242
250
  // 队列回落,重置
243
251
  this.stallStartAt = 0
244
252
  }
245
- }, intervalMs)
253
+ // 本次 tick 完成后再安排下一次,不会因主线程繁忙而堆积
254
+ this.watchdogTimer = setTimeout(tick, intervalMs)
255
+ }
256
+ this.watchdogTimer = setTimeout(tick, intervalMs)
246
257
  }
247
258
 
248
- async write(data: ArrayBuffer | Blob | string): Promise<void> {
259
+ async write(data: ArrayBuffer | Uint8Array | Blob | string): Promise<void> {
249
260
  // 静默处理 aborted 状态,避免在正常的流关闭场景下抛出错误
250
261
  if (this.abortController.signal.aborted) {
251
262
  return Promise.resolve()
@@ -272,9 +283,10 @@ export class StreamWriter {
272
283
  })
273
284
  }
274
285
 
275
- private async convertToBuffer(data: ArrayBuffer | Blob | string): Promise<ArrayBuffer> {
286
+ private async convertToBuffer(data: ArrayBuffer | Uint8Array | Blob | string): Promise<ArrayBuffer> {
276
287
  if (data instanceof Blob) return data.arrayBuffer()
277
- if (typeof data === 'string') return new TextEncoder().encode(data).buffer
288
+ if (typeof data === 'string') return new TextEncoder().encode(data).buffer as ArrayBuffer
289
+ if (data instanceof Uint8Array) return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) as ArrayBuffer
278
290
  return data
279
291
  }
280
292
 
@@ -438,9 +450,9 @@ export class StreamWriter {
438
450
  if (this.stream && typeof this.stream.close === 'function') {
439
451
  try {
440
452
  await this.stream.close()
441
- } catch (err: any) {
453
+ } catch (err: unknown) {
442
454
  // 忽略关闭已关闭流的错误
443
- const errMsg = err.message?.toLowerCase() || ''
455
+ const errMsg = (err instanceof Error ? err.message : '').toLowerCase()
444
456
  if (!errMsg.includes('closed') && !errMsg.includes('closing')) {
445
457
  this.log?.trace?.('Stream close error:', err)
446
458
  }
@@ -461,15 +473,16 @@ export class StreamWriter {
461
473
  // 先检查流状态,避免在已关闭的流上调用 abort
462
474
  if (this.stream && typeof this.stream.abort === 'function') {
463
475
  // 检查流的状态,避免操作已关闭的流
464
- const streamState = (this.stream as any).status || (this.stream as any).state
476
+ const streamState = (this.stream as { status?: string; state?: string }).status ||
477
+ (this.stream as { status?: string; state?: string }).state
465
478
  if (streamState !== 'closed' && streamState !== 'closing') {
466
479
  this.stream.abort(new Error(reason))
467
480
  }
468
481
  }
469
- } catch (err: any) {
482
+ } catch (err: unknown) {
470
483
  // Stream may already be closed, ignore all stream-related errors
471
484
  // 完全忽略流操作错误,避免在错误处理中再次抛出错误
472
- const errMsg = err.message?.toLowerCase() || ''
485
+ const errMsg = (err instanceof Error ? err.message : '').toLowerCase()
473
486
  if (!errMsg.includes('closed') && !errMsg.includes('closing') && !errMsg.includes('write')) {
474
487
  this.log?.trace?.('Stream abort error:', err)
475
488
  }
@@ -494,17 +507,17 @@ export class StreamWriter {
494
507
 
495
508
  // 立即拒绝所有待处理的写入任务,避免它们继续执行
496
509
  const pendingTasks = this.writeQueue.splice(0)
497
- pendingTasks.forEach(task => {
510
+ pendingTasks.forEach(() => {
498
511
  // 这些任务的 Promise 会在执行时因为检查到 aborted 而被拒绝
499
512
  })
500
513
 
501
514
  try {
502
515
  this.p.end()
503
- } catch (err) {
516
+ } catch {
504
517
  // Ignore errors when ending pushable
505
518
  }
506
519
 
507
- if (this.watchdogTimer) { clearInterval(this.watchdogTimer); this.watchdogTimer = undefined }
520
+ if (this.watchdogTimer) { clearTimeout(this.watchdogTimer); this.watchdogTimer = undefined }
508
521
  }
509
522
 
510
523
  // 等待内部队列被下游完全消费(用于在结束前确保尽量发送完数据)
@@ -526,7 +539,7 @@ export class StreamWriter {
526
539
  }
527
540
 
528
541
  // 事件系统
529
- private listeners = new Map<string, Function[]>()
542
+ private listeners = new Map<string, ((event: CustomEvent) => void)[]>()
530
543
 
531
544
  addEventListener(type: string, callback: (event: CustomEvent) => void) {
532
545
  const handlers = this.listeners.get(type) || []
@@ -10,7 +10,7 @@ type Frame = {
10
10
  type: Byte;
11
11
  flags: Byte;
12
12
  streamId: number;
13
- payload?: any;
13
+ payload?: unknown;
14
14
  }
15
15
 
16
16
  type FrameSettingPayload = { identifier: number; value: number }[]