From b54d8d7eeb982428a0f0541446d5045c101ae831 Mon Sep 17 00:00:00 2001 From: Ernest Okot Date: Mon, 11 May 2026 19:56:12 +0300 Subject: [PATCH] handle WebSocket implementations missing bufferedAmount MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit React Native's WebSocket implementation does not expose bufferedAmount (it's undefined). The current code in WebSocketMultiaddrConnection.sendData compares it directly with a number, which evaluates undefined < 4MB to false, marking every write as needing drain. The eventual 'close' event then makes pEvent reject with undefined, surfacing in Upgrader._encryptOutbound as the cryptic TypeError: Cannot read property 'message' of undefined. Same guard applied to checkBufferedAmount, which otherwise starts a polling task that never terminates. Testing Reproducer: a React Native Expo project using @libp2p/websockets to dial a Node.js relay. Before the fix, every dial fails at mss.select with the undefined-message error. After the fix, dial completes and noise handshake proceeds normally. Refs - React Native WebSocket source: https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/WebSocket/WebSocket.js — bufferedAmount is not implemented. - MDN: bufferedAmount is required for browsers but not all WebSocket implementations conform. --- .../src/websocket-to-conn.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/transport-websockets/src/websocket-to-conn.ts b/packages/transport-websockets/src/websocket-to-conn.ts index 45ccfd9d3c..1672b36217 100644 --- a/packages/transport-websockets/src/websocket-to-conn.ts +++ b/packages/transport-websockets/src/websocket-to-conn.ts @@ -62,7 +62,14 @@ class WebSocketMultiaddrConnection extends AbstractMultiaddrConnection { this.websocket.send(buf) } - const canSendMore = this.websocket.bufferedAmount < this.maxBufferedAmount + // Some WebSocket implementations (notably React Native) don't expose + // `bufferedAmount`. When unavailable, treat it as 0; we can't track + // backpressure, but at least writes proceed instead of blocking forever + // waiting for a 'drain' event, the implementation never emits. + const bufferedAmount = typeof this.websocket.bufferedAmount === 'number' + ? this.websocket.bufferedAmount + : 0 + const canSendMore = bufferedAmount < this.maxBufferedAmount if (!canSendMore) { this.checkBufferedAmountTask.start() @@ -92,9 +99,11 @@ class WebSocketMultiaddrConnection extends AbstractMultiaddrConnection { } private checkBufferedAmount (): void { - this.log('buffered amount now %d', this.websocket.bufferedAmount) - - if (this.websocket.bufferedAmount === 0) { + const bufferedAmount = typeof this.websocket.bufferedAmount === 'number' + ? this.websocket.bufferedAmount + : 0 + this.log('buffered amount now %d', bufferedAmount) + if (bufferedAmount === 0) { this.checkBufferedAmountTask.stop() this.safeDispatchEvent('drain') }