React hooks and a provider for the browser WebTransport API. It keeps one connection, datagram reader, and datagram writer behind a small React API and leaves the underlying byte streams intact.
The package pairs naturally with channels-webtransport, but it does not depend on Django or prescribe an application protocol.
0.0.xis experimental. Browser WebTransport and the companion Channels ASGI contract are still evolving.
Backend compatibility:
channels-webtransport0.0.1 currently uses PyWebTransport 0.20.1. PyWebTransport documents that browser clients are not interoperable yet, with the matching Chrome issue still open. The React package works with native browser WebTransport, but a real browser cannot currently reach that Django backend until its transport dependency fixes the protocol mismatch.
bun add react-webtransportWebTransport requires a secure context and an HTTP/3-capable server. For local self-signed certificates, pass a SHA-256 certificate hash through serverCertificateHashes; the certificate must be short-lived enough for the browser to accept it.
import {
WebTransportProvider,
useWebTransport,
useWebTransportDatagrams,
} from "react-webtransport";
function App() {
return (
<WebTransportProvider
url={async () => {
const response = await fetch("/api/webtransport-ticket", { method: "POST" });
const { url } = await response.json();
return url;
}}
>
<RealtimePanel />
</WebTransportProvider>
);
}
function RealtimePanel() {
const { status, connect, reconnect, close } = useWebTransport();
const { send, sendJson } = useWebTransportDatagrams({
onDatagram: (bytes) => console.log("raw", bytes),
onJson: (value) => console.log("json", value),
onJsonError: ({ error, data }) => console.warn("not JSON", error, data),
});
return (
<>
<p>{status}</p>
<button onClick={() => void connect()}>Connect</button>
<button onClick={() => void reconnect()}>Refresh ticket and reconnect</button>
<button onClick={() => void send(new TextEncoder().encode("hello"))}>Send bytes</button>
<button onClick={() => void sendJson({ type: "ping" })}>Send JSON</button>
<button onClick={() => void close()}>Close</button>
</>
);
}autoConnect defaults to true. Set it to false when connection setup must follow a user action. A URL resolver runs for every connect() and reconnect(), making short-lived ticket query parameters straightforward.
Keep an inline options object stable with useMemo; changing url, options, or transportFactory replaces the provider's controller and closes the previous session.
Streams remain raw Web Streams. The package deliberately adds no record framing:
const { createBidirectionalStream } = useWebTransportStreams();
const stream = await createBidirectionalStream();
const writer = stream.writable.getWriter();
await writer.write(new TextEncoder().encode("request bytes"));
await writer.close();
const reader = stream.readable.getReader();
const response = await reader.read();Version 0.0.1 exposes client-created bidirectional and unidirectional streams, matching channels-webtransport 0.0.1. It does not add stream message framing or server-created stream helpers.
Install the optional peer and import the explicit subpath:
bun add @fails-components/webtransportimport { failsTransportFactory } from "react-webtransport/fails";
<WebTransportProvider url={url} transportFactory={failsTransportFactory}>
{children}
</WebTransportProvider>;@fails-components/webtransport can fall back to its own WebSocket protocol. That fallback requires the corresponding fails-components server and is not implemented by channels-webtransport. Native WebTransport remains this package's default and has no extra runtime dependency.
Use a normal Channels-style consumer on the Python side:
from channels_webtransport import AsyncWebTransportConsumer
class EchoConsumer(AsyncWebTransportConsumer):
async def receive_datagram(self, data: bytes) -> None:
await self.send_datagram(data)
async def receive_stream(self, stream: int, data: bytes, end_stream: bool) -> None:
await self.send_stream(stream, data, end_stream=end_stream)Authenticate the WebTransport CONNECT request with scope headers or, more conveniently in a browser, a short-lived ticket embedded in the URL returned by the provider's async resolver.
WebTransportProvider— owns a shared connection.useWebTransport()— state plusconnect,reconnect, andclose.useWebTransportDatagrams()— raw/JSON subscriptions plussendandsendJson.useWebTransportStreams()— raw stream creation methods.WebTransportController— the same lifecycle as an imperative API.nativeTransportFactory— the default browser constructor adapter.react-webtransport/fails— optional fails-components factory.
There is no automatic reconnect, outgoing queue, or replay. Sending while disconnected rejects with WebTransportClientError and code NOT_CONNECTED.
bun install
bun run check
bun run test:e2eThe E2E script installs the Django example, generates a 13-day localhost certificate, starts a browser-compatible HTTP/3 echo server from @fails-components/webtransport, serves the React example with Vite, and drives Chromium through agent-browser. It verifies native WebTransport connection state, raw datagrams, JSON datagrams, and a bidirectional stream over real QUIC. The separate Django example is kept as the intended Channels integration and can be exercised once PyWebTransport's browser incompatibility is resolved upstream.
MIT licensed.