react-realtime-hooks 1.3.4 → 1.4.0

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/README.md CHANGED
@@ -257,6 +257,21 @@ export function ChatSocket() {
257
257
  }
258
258
  ```
259
259
 
260
+ The native `WebSocket` does not emit any event when buffered bytes drain to the network, so by default `bufferedAmount` only refreshes on `send()`, on incoming messages, and on the `open` transition. Pass `bufferedAmountPolling` to surface the live value for backpressure indicators:
261
+
262
+ ```tsx
263
+ const socket = useWebSocket<IncomingMessage, OutgoingMessage>({
264
+ url: "ws://localhost:8080",
265
+ // "raf" reads `bufferedAmount` on every animation frame while open;
266
+ // use `true` for a 100ms interval, or `{ intervalMs: 50 }` for custom.
267
+ bufferedAmountPolling: "raf",
268
+ });
269
+
270
+ // `socket.bufferedAmount` now ticks down as the OS flushes the buffer,
271
+ // even between `send` calls. Identical readings do not trigger React
272
+ // re-renders.
273
+ ```
274
+
260
275
  ### `useEventSource`
261
276
 
262
277
  ```tsx
@@ -400,6 +415,7 @@ export function GatedNotifications() {
400
415
  | `protocols` | `string \| string[]` | `undefined` | WebSocket subprotocols |
401
416
  | `connect` | `boolean` | `true` | Auto-connect on mount |
402
417
  | `binaryType` | `BinaryType` | `"blob"` | Socket binary mode |
418
+ | `bufferedAmountPolling` | `false \| true \| "raf" \| { intervalMs: number }` | `false` | Live polling of `WebSocket.bufferedAmount` for backpressure UIs |
403
419
  | `parseMessage` | `(event) => TIncoming` | raw `event.data` | Incoming parser |
404
420
  | `serializeMessage` | `(message) => ...` | JSON/string passthrough | Outgoing serializer |
405
421
  | `reconnect` | `false \| UseReconnectOptions` | enabled | Reconnect configuration |
@@ -420,7 +436,7 @@ export function GatedNotifications() {
420
436
  | `lastMessageEvent` | `MessageEvent \| null` | Last raw message event |
421
437
  | `lastCloseEvent` | `CloseEvent \| null` | Last close event |
422
438
  | `lastError` | `Event \| null` | Last transport error, or `RealtimeErrorEvent` for parse/heartbeat failures |
423
- | `bufferedAmount` | `number` | Current socket buffer size |
439
+ | `bufferedAmount` | `number` | Current socket buffer size (refresh cadence is controlled by `bufferedAmountPolling`) |
424
440
  | `reconnectState` | reconnect snapshot or `null` | Current reconnect data |
425
441
  | `heartbeatState` | heartbeat snapshot or `null` | Current heartbeat data |
426
442
  | `open` | `() => void` | Manual connect |
package/dist/index.cjs CHANGED
@@ -1317,6 +1317,58 @@ var useWebSocket = (options) => {
1317
1317
  stopHeartbeat();
1318
1318
  }
1319
1319
  }, [state.status, stopHeartbeat]);
1320
+ const rawBufferedAmountPolling = options.bufferedAmountPolling;
1321
+ const bufferedAmountPollingMode = react.useMemo(() => {
1322
+ if (rawBufferedAmountPolling === void 0 || rawBufferedAmountPolling === false) {
1323
+ return null;
1324
+ }
1325
+ if (rawBufferedAmountPolling === true) {
1326
+ return 100;
1327
+ }
1328
+ if (rawBufferedAmountPolling === "raf") {
1329
+ return "raf";
1330
+ }
1331
+ const intervalMs = rawBufferedAmountPolling.intervalMs;
1332
+ return Number.isFinite(intervalMs) && intervalMs > 0 ? intervalMs : null;
1333
+ }, [rawBufferedAmountPolling]);
1334
+ const pollBufferedAmount = useStableCallback(() => {
1335
+ const socket2 = socketRef.current;
1336
+ if (socket2 === null) {
1337
+ return;
1338
+ }
1339
+ const next = socket2.bufferedAmount;
1340
+ if (stateRef.current.bufferedAmount === next) {
1341
+ return;
1342
+ }
1343
+ commitState((current) => ({
1344
+ ...current,
1345
+ bufferedAmount: next
1346
+ }));
1347
+ });
1348
+ react.useEffect(() => {
1349
+ if (bufferedAmountPollingMode === null) {
1350
+ return;
1351
+ }
1352
+ if (state.status !== "open") {
1353
+ return;
1354
+ }
1355
+ if (bufferedAmountPollingMode === "raf") {
1356
+ if (typeof requestAnimationFrame !== "function") {
1357
+ return;
1358
+ }
1359
+ let frame = requestAnimationFrame(function loop() {
1360
+ pollBufferedAmount();
1361
+ frame = requestAnimationFrame(loop);
1362
+ });
1363
+ return () => {
1364
+ cancelAnimationFrame(frame);
1365
+ };
1366
+ }
1367
+ const intervalId = setInterval(pollBufferedAmount, bufferedAmountPollingMode);
1368
+ return () => {
1369
+ clearInterval(intervalId);
1370
+ };
1371
+ }, [bufferedAmountPollingMode, state.status, pollBufferedAmount]);
1320
1372
  const status = (reconnect.status === "scheduled" || reconnect.status === "running") && state.status !== "open" ? "reconnecting" : state.status;
1321
1373
  const snapshot = createConnectionStateSnapshot(status, {
1322
1374
  isSupported: supported,