rpc_dart_websocket 0.6.0
rpc_dart_websocket: ^0.6.0 copied to clipboard
Web-safe subset of rpc_dart_transports providing WebSocket caller/responder transports (caller + server) for Dart2JS/WASM builds.
0.6.0 #
- Breaking:
headersProvider:andreconnectFactory:on the caller are replaced byRpcClientConnection; buildheaders:inside its factory, which runs for every connection.- A failed connect is a gRPC status, and a disconnected transport refuses in one shape.
- A stopped server refuses connections instead of abandoning them.
- The endpoint callbacks are refused.
createWithContractspasseslogControllerthrough.
- New:
maxConnections.bindRpcWebSocketHttpServerbounds the upgrade request.- The caller transport reports when it is ready.
- Half-open detection where the platform drops ping.
frameProgressTimeout(default 60 s,nulldisables): while a frame is unfinished its bytes must keep arriving within the window or the connection is closed, on the server upgrade path and the client connect alike.pingIntervalfires only on total silence, so a peer dripping one byte at a time used to hold its connection past any keepalive; between frames there is no deadline, and an idle connection is a legitimate one.- A one-time warning when neither
allowedOriginsnorallowUpgradeis configured, to the new optionallogger:parameter (stderr fallback): an ungated server upgrades any origin, cross-site WebSocket hijacking included, and the gate cannot be defaulted because the allowed origins are the application's to know. The warning makes an ungated deployment a visible choice.
- Hardening:
connect()is bounded by default.- An unfinished message is bounded before
dart:iobuffers it, and a message in thousands of fragments is refused. - Inbound pings are rate-limited.
- Reading stops while 1 MiB of answers is unsent.
- Credentials and close reasons no longer reach the log unescaped.
- Several inputs that killed the server no longer do: a repeated
Sec-WebSocket-Keyand a refused frame during the handshake. - A raw socket closed under an established channel is survived.
- Fixed:
RpcClientConnectionreconnects after a server restart.- A peer client can answer calls.
- A reused peer stream id gets its own answer.
- An ended connection returns its
maxConnectionsslot. - A refused request with a stalled body receives its 400/403. The refusal drain cancelled the request-body subscription before writing the status, and cancelling a dart:io request stream tears the connection down — the peer read a TCP reset instead of the answer. The response is written first, the cancellation after.
0.5.0 #
Added #
connect()takesheadersandconnectTimeout. Neither had any way through: an auth header could not be attached to the handshake at all, and a server that accepted the TCP connection and then never completed the upgrade hung the caller forever. Both are re-applied on reconnect.RpcWebSocketNonBinaryFrame— the error raised for a TEXT frame on a binary channel, markedIRpcAdvisoryChannelErrorso it stops at the connection instead of failing every call in flight.
Fixed #
- A call landing inside a reconnect is refused rather than served by a dead
socket.
send, reads andsendDirectObjectall now see the window.
Changed #
- Requires rpc_dart 6.1, for
IRpcAdvisoryChannelError.
0.4.0 #
Breaking #
permessage-deflateis off by default, on the server and in what the client offers. It is a decompression bomb with no output bound: 0.25 MiB uploaded expanded to 516 MiB of RSS (2071x), against 2.7x with it off. The inflation happens insidedart:io, which exposes no limit, so the only lever is not negotiating the extension. Re-enable it deliberately if both peers are trusted.pingIntervaldefaults to 30s on the server. A peer that completes the handshake and then goes silent used to hold its connection, endpoint and contracts forever.- Requires rpc_dart 6. See its changelog — notably that a handler's bare
Exceptiontext no longer reaches the caller, and that a response ending without agrpc-statusnow raisesUNAVAILABLEinstead of ending cleanly.
Security #
- A server can refuse a cross-origin handshake (opt-in): a browser will happily let any page open a WebSocket to your server.
- A duplicated
Originheader no longer kills the server process. - The drain on a refused upgrade is bounded, so refusing a connection is cheaper than accepting one rather than more expensive.
- The inbound message ceiling is pinned by a test rather than claimed in a
doc comment: a 64 MiB message closes the connection with 4400 and the reason
naming the buffer overflow, three 8 MiB messages are accepted. What bounds it
is
maxMessageLengthBytes; the peak allocation cannot be helped, becausedart:iobuffers a whole message before delivering it, so what matters is that the connection closes — a surviving one is an unlimited supply of peaks.
Fixed #
- A dead call's teardown acted on a live one after a reconnect, because the stream id had been reused.
- Concurrent
reconnect()orphaned a socket per attempt. - Keepalive on both halves, which is the only way to notice a half-open path; neither caller noticed the peer had died.
- Graceful drain on
stop()(opt-in), and the server stops accepting before it drains. - A framing violation hung up without saying so, and a non-binary frame was dropped silently instead of reported.
- The peer's close code reaches the caller as a gRPC status instead of a
blanket
UNAVAILABLE. close()deadlocked when nothing listened toincoming.- A failed
start()left the server claiming to run, and a call after a failed reconnect killed the isolate. - A socket that arrives after the transport has closed is closed, not leaked.
- The endpoint is released when
onEndpointCreatedthrows. - The transport no longer hides its security policy from the endpoint, and a throwing observability callback no longer kills the server.
0.3.0 #
Fixed #
- Per-connection endpoints are closed when their connection ends; they were never released, so every connection leaked one.
Changed #
- Requires rpc_dart 5. See its changelog: flow control is on by default, an
expired deadline is now
RpcDeadlineExceededExceptionon every shape, and a stream that ends without a trailer raisesUNAVAILABLE.
0.2.3 #
- Fixed:
RpcWebSocketCallerTransportno longer permanently closes itself when the underlying socket drops gracefully (server-side FIN) and areconnectFactoryis configured. Previously theonDonecascade flipped the transport to closed, so a laterreconnect()returned "Transport closed" — defeating reconnect after a server-initiated drop. The stableincomingMessagesstream now survives the drop so subscribers persist andreconnect()can re-attach. This is the recovery path therpc_dart_logclient relies on. Without areconnectFactorythe transport still closes fully on drop (unchanged). - Expanded test coverage of the flagship streaming transport:
test/websocket_rpc_contract_test.dart: full typed RPC contract end-to-end over a realdart:ioWebSocket server (RpcWebSocketServer+RpcResponderEndpoint/RpcCallerEndpoint, ephemeral localhost port). Covers unary, server-stream (ordered delivery + completion), client-stream (aggregation), bidirectional (ordered echo), typedRpcStatusExceptionpropagation, mid-stream cancellation (no hang, transport stays usable), concurrent calls/streams (no cross-wiring), and a high-volume ordered stream.test/websocket_reconnect_test.dart: reconnect coverage (previously zero). Provesreconnect()re-establishes the socket, the stableincomingMessagesstream survives for pre-existing subscribers, requests issued after reconnect reach a freshly-bound server, full endpoint-level RPC recovers after a server swap, the no-factory/already-closed paths report correctly, and a regression guard for the graceful-drop fix above.
0.2.2 #
- Web-correctness verification: the client transport (
RpcWebSocketCallerTransport/RpcWebSocketChannel) is confirmed dart2js-safe. Nodart:ioon the client path; the frame multiplexer header uses only 32-bitByteDataops (nosetUint64/getUint64), so it is safe under dart2js' 32-bit integer semantics. - Added
test/websocket_web_smoke_test.dart: frame round-trip plus caller/responder round-trips over an in-memoryWebSocketChannelpair (nodart:io). Passes on VM, node, and chrome (fvm dart test -p vm|node|chrome). - Added
asyncandstream_channeldev dependencies for the in-memory channel pair used by the web smoke test.
0.2.1 #
RpcWebSocketServer: addedonPeerEndpointCreatedcallback — when set, each accepted connection creates anRpcPeerEndpointinstead ofRpcResponderEndpoint, enabling bidirectional server-initiated calls over WebSocket.RpcWebSocketCallerTransport.connect(): now awaitsWebSocketChannel.readybefore returning, so connection errors are reported at connect time rather than leaking as unhandled stream errors.- Updated to
rpc_dart: ^3.1.0.
0.2.0 #
- Updated to
rpc_dart: ^3.0.0. - Migrated to 3-layer transport architecture (
IRpcChannel/IRpcMultiplexedChannel/RpcChannelTransport). - WebSocket metadata encoding switched to binary CBOR for consistency with core.
0.1.0 #
- Initial release: WebSocket caller/responder transports for rpc_dart (web-safe, works on Dart2JS/WASM).