Skip to content

Conversation

@kixelated
Copy link
Owner

@kixelated kixelated commented Nov 17, 2025

Fix the example

Summary by CodeRabbit

Release Notes

  • New Features

    • Reinstated WebTransport client flow with complete connection lifecycle handling and stream operations
    • Added datagram stream support for WebTransport
  • Chores

    • Reorganized project structure into monorepo setup with workspace configuration
    • Integrated Biome code quality tooling and linting
    • Added Bun package manager support to development environment
    • Updated frame encoding for stream reset and stop-sending operations

@coderabbitai
Copy link

coderabbitai bot commented Nov 17, 2025

Walkthrough

Reinstates the full WebTransport client flow in web-transport-ws/examples/client.ts. Adds node_modules to .gitignore. Introduces repository tooling/config files: biome.jsonc, package.json (workspaces), and web-demo/package.json name/private fields. Adds Bun to devShell via flake.nix and JS checks/fixes to justfile. Minor runtime/logging change in web-demo/client.js (template literals). In web-transport-ws: frame.ts encodes code for reset/stop frames and collapses Any type, session.ts exports a new Datagrams class and refactors stream handling, index.ts, stream.ts, varint.ts, and tsconfig.json contain formatting/semantics-preserving edits.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Fix the example for guest' is vague and does not clearly convey the scope or nature of the changes. While the PR includes changes to web-transport-ws/examples/client.ts, it also encompasses a large monorepo restructuring with new tooling (biome, bun), workspace setup, and functional code changes across multiple files. Clarify the title to better reflect the primary objective. Consider: 'Reinstate WebTransport client example flow' or 'Add monorepo structure and biome linting setup' depending on the main intent.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-guest

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example:

"Create a concise high-level summary as a bullet-point list. Then include a Markdown table showing lines added and removed by each contributing author."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@guest271314
Copy link
Contributor

I'll read and test this later this evening.

@kixelated kixelated enabled auto-merge (squash) November 17, 2025 16:58
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web-transport-ws/src/session.ts (1)

410-454: close() does not mark the session as closed, leading to inconsistent state

Right now #close assumes this.#closed holds an Error to propagate into streams, but close():

  • Calls this.#close(code, reason) without first setting this.#closed, so stream.error(this.#closed) uses undefined during application‑initiated closes.
  • Leaves this.#closed falsy until the WebSocket onclose event, so methods that check this.#closed (createBidirectionalStream, createUnidirectionalStream, etc.) can still succeed after close() has been called, which is surprising.

Consider setting #closed inside close() before delegating to #close, so the state becomes closed immediately and streams receive a meaningful error. For example:

-	close(info?: { closeCode?: number; reason?: string }) {
-		if (this.#closed) return;
-
-		const code = info?.closeCode ?? 0;
-		const reason = info?.reason ?? "";
-
-		this.#sendPriorityFrame({
-			type: "connection_close",
-			code: VarInt.from(code),
-			reason,
-		});
-
-		setTimeout(() => {
-			this.#ws.close();
-		}, 100);
-
-		this.#close(code, reason);
-	}
+	close(info?: { closeCode?: number; reason?: string }) {
+		if (this.#closed) return;
+
+		const code = info?.closeCode ?? 0;
+		const reason = info?.reason ?? "";
+
+		this.#sendPriorityFrame({
+			type: "connection_close",
+			code: VarInt.from(code),
+			reason,
+		});
+
+		setTimeout(() => {
+			this.#ws.close();
+		}, 100);
+
+		// Mark the session as closed so subsequent operations see the closed state
+		this.#closed = new Error(`Connection closed: ${code} ${reason}`);
+		this.#close(code, reason);
+	}

This keeps the existing error/close handling for WebSocket‑initiated closures while ensuring application‑initiated closes behave consistently.

🧹 Nitpick comments (3)
justfile (1)

47-49: JS/TS Biome steps look good; consider tightening install behavior

Adding bun install + bun run check/fix into check/fix plugs JS/TS into the existing Rust pipeline nicely. Two optional tweaks you might consider:

  • Use a CI‑style install (bun install --ci or equivalent) to avoid accidental lockfile drift.
  • If these targets run frequently locally, you may want to rely on cached installs (or a separate just target) instead of reinstalling on every check/fix.

Also applies to: 77-79

web-transport-ws/src/frame.ts (1)

47-60: Frame layout and type surface are tidy; consider dropping unused Padding/Ping

The stream and connection_close paths correctly rely on WebSocket message boundaries (no offsets/lengths/reason size), and the new Any union aligns with what encode/decode actually handle. Padding and Ping interfaces remain exported but are no longer part of Any and cannot be emitted/decoded via this API; if those frame types are truly deprecated here, you might want to remove the interfaces to avoid confusion.

Also applies to: 82-93, 135-146, 148-158, 39-45

web-transport-ws/src/session.ts (1)

43-47: ReadableStream controller wiring is sound; runtime check is mostly defensive

Capturing the ReadableStreamDefaultController instances for incomingBidirectionalStreams and incomingUnidirectionalStreams via start matches how you treat per‑stream readers elsewhere. The explicit ReadableStream didn't call start guard is effectively an assertion (start is synchronous per spec), so it’s unlikely to ever fire unless a non‑conforming polyfill is in use.

Also applies to: 78-92

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6316e1a and 60e8389.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (15)
  • .gitignore (1 hunks)
  • biome.jsonc (1 hunks)
  • flake.nix (1 hunks)
  • justfile (2 hunks)
  • package.json (1 hunks)
  • web-demo/client.js (1 hunks)
  • web-demo/package.json (1 hunks)
  • web-transport-ws/examples/client.ts (1 hunks)
  • web-transport-ws/package.json (1 hunks)
  • web-transport-ws/src/frame.ts (8 hunks)
  • web-transport-ws/src/index.ts (1 hunks)
  • web-transport-ws/src/session.ts (17 hunks)
  • web-transport-ws/src/stream.ts (1 hunks)
  • web-transport-ws/src/varint.ts (1 hunks)
  • web-transport-ws/tsconfig.json (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • web-transport-ws/tsconfig.json
  • web-transport-ws/package.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • web-transport-ws/examples/client.ts
🧰 Additional context used
🧬 Code graph analysis (3)
web-transport-ws/src/stream.ts (2)
web-transport-ws/src/varint.ts (1)
  • VarInt (1-97)
web-transport-ws/src/stream.rs (1)
  • dir (26-32)
web-transport-ws/src/session.ts (2)
web-transport-ws/src/varint.ts (1)
  • VarInt (1-97)
web-transport-ws/src/frame.ts (2)
  • StopSending (26-30)
  • Any (47-47)
web-transport-ws/src/frame.ts (3)
web-transport-ws/src/stream.ts (1)
  • Id (10-49)
web-transport-ws/src/varint.ts (3)
  • VarInt (1-97)
  • encode (27-50)
  • decode (52-96)
web-transport-ws/src/session.ts (7)
  • frame (139-153)
  • frame (155-248)
  • frame (250-257)
  • frame (259-272)
  • frame (274-282)
  • frame (284-287)
  • code (410-435)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (14)
.gitignore (1)

4-4: Ignoring node_modules is appropriate for the new JS/TS tooling.

Adding node_modules here is consistent with introducing workspaces and avoids committing install artifacts from each package.

flake.nix (1)

20-36: Bun in devShell matches the JS/TS workflow.

Including pkgs.bun in tools is a good fit for the new JS tooling; devShell remains straightforward and focused.

web-demo/client.js (1)

62-69: Template literal logging change is fine.

Switching to template literals keeps the logs readable without changing behavior; the example remains clear and minimal.

package.json (1)

1-16: Monorepo root and Biome tooling setup look consistent.

Workspace definitions, private: true, type: "module", and the Biome check/fix scripts form a coherent root configuration for the two packages.

web-demo/package.json (1)

2-7: Package metadata for web-demo is appropriate.

Setting name and private: true is a good fit for a workspace-only demo package; no issues with the existing devDependencies.

web-transport-ws/src/index.ts (1)

4-9: Polyfill guard and global installation look correct.

install() only patches globalThis.WebTransport when absent and returns a boolean indicating whether the polyfill was installed; this matches typical polyfill behavior.

biome.jsonc (1)

1-39: Biome configuration is coherent and matches the tooling direction

Schema, VCS options, formatter defaults, and the targeted rule overrides (a11y media captions, Node import protocol, Tailwind/ts-ignore) all look consistent with the repo’s needs and the new JS/TS checks.

web-transport-ws/src/frame.ts (1)

64-80: RESET_STREAM / STOP_SENDING code encoding/decoding is now consistent

The updated encode branches now write both the stream ID and the code varint for reset_stream and stop_sending, and decode mirrors that layout. The buffer sizing with 1 + 8 + 8 (type + max varint sizes) matches VarInt.MAX_SIZE and avoids reallocation.

Also applies to: 107-119, 121-133

web-transport-ws/src/stream.ts (1)

3-48: Stream ID packing/unpacking logic looks correct

The Dir flags, Id.create bit layout, and canRecv/canSend checks are consistent with the expected uni/bi and initiator semantics and match the Rust counterpart.

web-transport-ws/src/session.ts (5)

5-24: Datagrams stub is a reasonable placeholder

The Datagrams class satisfies WebTransportDatagramDuplexStream structurally and gives callers something type‑safe to hold onto, while clearly marked with a TODO. Given the current WebSocket transport focus, this is a pragmatic interim step.

Also applies to: 48-50


52-58: Option handling aligns with what WebSocket transport can support

Rejecting requireUnreliable and warning on serverCertificateHashes makes the limitations of the WebSocket‑based implementation explicit instead of silently ignoring options, which is preferable.


128-137: Centralized close/error handling improves robustness

Routing WebSocket error/close events and connection_close frames through #close (with a dedicated #closeReason for application‑level close frames) ensures that the closed promise resolves and active streams are failed consistently instead of leaking.

Also applies to: 147-147


155-247: Stream handling and STOP_SENDING/RESET_STREAM signaling look consistent

The #handleStreamFrame, #handleResetStream, and #handleStopSending paths correctly:

  • Validate stream direction via canRecv.
  • Lazily create incoming streams and queue them on the appropriate incoming*Streams controller.
  • Propagate STOP_SENDING/RESET_STREAM using the varint code field and clean up the stream maps.

The early return for streams we initiated (serverInitiated === this.#isServer) is effectively a no‑op guard and doesn’t appear to affect correctness.


274-287: Send path changes are reasonable

Using Uint8Array directly with WebSocket.send and applying simple backpressure on bufferedAmount keeps the send path straightforward, and the updated abort/close handlers for bidirectional and unidirectional streams properly send RESET or FIN and clean up #sendStreams.

Also applies to: 296-335, 363-404

Comment on lines +31 to 50
if (dst.buffer.byteLength < dst.byteLength + size) {
throw new Error("destination buffer too small");
}

const view = new DataView(dst.buffer, dst.byteOffset + dst.byteLength, size)
const view = new DataView(dst.buffer, dst.byteOffset + dst.byteLength, size);

if (size === 1) {
view.setUint8(0, Number(x))
view.setUint8(0, Number(x));
} else if (size === 2) {
view.setUint16(0, (0b01 << 14) | Number(x), false)
view.setUint16(0, (0b01 << 14) | Number(x), false);
} else if (size === 4) {
view.setUint32(0, (0b10 << 30) | Number(x), false)
view.setUint32(0, (0b10 << 30) | Number(x), false);
} else if (size === 8) {
view.setBigUint64(0, (0b11n << 62n) | x, false)
view.setBigUint64(0, (0b11n << 62n) | x, false);
} else {
throw new Error("VarInt value too large")
throw new Error("VarInt value too large");
}

return new Uint8Array(dst.buffer, dst.byteOffset, dst.byteLength + size)
return new Uint8Array(dst.buffer, dst.byteOffset, dst.byteLength + size);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Capacity check in encode should account for byteOffset.

The current check:

if (dst.buffer.byteLength < dst.byteLength + size) {
  throw new Error("destination buffer too small");
}

ignores dst.byteOffset. If dst is a subview over a larger ArrayBuffer, this can mis-detect available space and still allow a DataView constructor RangeError.

Consider tightening the check to use the actual end offset of the appended data:

-    if (dst.buffer.byteLength < dst.byteLength + size) {
-      throw new Error("destination buffer too small");
-    }
+    if (dst.byteOffset + dst.byteLength + size > dst.buffer.byteLength) {
+      throw new Error("destination buffer too small");
+    }

This keeps behavior the same when dst.byteOffset === 0 (your likely case) while making encode robust if it’s ever called with a sliced view.

🤖 Prompt for AI Agents
In web-transport-ws/src/varint.ts around lines 31 to 50, the capacity check in
encode ignores dst.byteOffset and can allow a DataView RangeError for sliced
ArrayBuffers; change the condition to compare the buffer's length against the
actual end offset (dst.byteOffset + dst.byteLength + size) so you throw
"destination buffer too small" when that end would exceed dst.buffer.byteLength,
keeping the rest of the logic (DataView creation, writes, and returned
Uint8Array) unchanged.

@kixelated kixelated merged commit 62fd026 into main Nov 17, 2025
1 check passed
@kixelated kixelated deleted the fix-guest branch November 17, 2025 17:04
@github-actions github-actions bot mentioned this pull request Nov 17, 2025
@guest271314
Copy link
Contributor

client.ts

// import { WebSocket } from "ws";

https://nodejs.org/en/learn/getting-started/websocket

Native WebSocket Client in Node.js
Introduction
Since Node.js v21, the WebSocket API has been enhanced using the Undici library, introducing a built-in WebSocket client. This simplifies real-time communication for Node.js applications. In Node.js v22.4.0 release, the WebSocket API was marked as stable, indicating it's ready for production use.

bun build --target=node --outfile=client1.js examples/client.ts 
node client1.js
Connecting to http://localhost:3000...
✓ Connected successfully

--- Sending unidirectional stream ---
✓ Sent: "Hello from Node.js client!"

--- Creating bidirectional stream ---
✓ Sent: "ping"
✓ Received: "ping"

--- Listening for incoming streams ---
✓ Received uni stream: "Hello from Node.js client!"

--- Closing connection ---
Transport closed with info: { closeCode: 0, reason: 'Client finished' }
✓ Connection closed: Client finished
deno -A client1.js
Connecting to http://localhost:3000...
✓ Connected successfully

--- Sending unidirectional stream ---
✓ Sent: "Hello from Node.js client!"

--- Creating bidirectional stream ---
✓ Sent: "ping"
✓ Received: "ping"

--- Listening for incoming streams ---
✓ Received uni stream: "Hello from Node.js client!"

--- Closing connection ---
Transport closed with info: { closeCode: 0, reason: "Client finished" }
✓ Connection closed: Client finished
 bun client1.js
Connecting to http://localhost:3000...
✓ Connected successfully

--- Sending unidirectional stream ---
✓ Sent: "Hello from Node.js client!"

--- Creating bidirectional stream ---
✓ Sent: "ping"
✓ Received: "ping"

--- Listening for incoming streams ---
✓ Received uni stream: "Hello from Node.js client!"

--- Closing connection ---
Transport closed with info: {
  closeCode: 0,
  reason: "Client finished",
}
✓ Connection closed: Client finished
WebSocket server listening on ws://127.0.0.1:3000
New connection from: 127.0.0.1:44494
WebSocket connection established
Accepted unidirectional stream
Created unidirectional stream to echo back
Received 26 bytes on unidirectional stream: Hello from Node.js client!
Echoing back 26 bytes on unidirectional stream: Hello from Node.js client!
Unidirectional stream closed
Accepted bidirectional stream
Received 4 bytes on bidirectional stream
Echoing back 4 bytes on bidirectional stream: ping
Bidirectional stream closed
Connection error: connection closed: 0: Client finished
New connection from: 127.0.0.1:50344
WebSocket connection established
Accepted bidirectional stream
Connection error: connection closed
New connection from: 127.0.0.1:36696
WebSocket connection established
Accepted unidirectional stream
Created unidirectional stream to echo back
Received 26 bytes on unidirectional stream: Hello from Node.js client!
Echoing back 26 bytes on unidirectional stream: Hello from Node.js client!
Unidirectional stream closed
Accepted bidirectional stream
Received 4 bytes on bidirectional stream
Echoing back 4 bytes on bidirectional stream: ping
Bidirectional stream closed
Connection error: connection closed: 0: Client finished
New connection from: 127.0.0.1:54288
WebSocket connection established
Accepted unidirectional stream
Created unidirectional stream to echo back
Received 26 bytes on unidirectional stream: Hello from Node.js client!
Echoing back 26 bytes on unidirectional stream: Hello from Node.js client!
Unidirectional stream closed
Accepted bidirectional stream
Received 4 bytes on bidirectional stream
Echoing back 4 bytes on bidirectional stream: ping
Bidirectional stream closed
Connection error: connection closed: 0: Client finished
^C

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants