Releases: h3js/crossws
v0.4.5
🚀 Enhancements
createWebSocketProxy util (#184)
New createWebSocketProxy util returns crossws hooks that forward every incoming peer to an upstream ws:// or wss:// target. Text and binary messages, close, and error events are relayed bi-directionally, and client messages sent before the upstream is ready are buffered (up to a configurable maxBufferSize) and flushed once connected.
import crossws from "crossws/adapters/<adapter>";
import { createWebSocketProxy } from "crossws";
const ws = crossws({
hooks: createWebSocketProxy("wss://echo.websocket.org"),
});The target can also be a (peer) => url resolver for dynamic routing, and the returned hooks can be extended (for auth, logging, header forwarding, etc.) like any other crossws hooks. See the proxy guide for auth, dynamic targets, and SSRF hardening notes.
fromNodeUpgradeHandler util + socket.io support (#185)
New fromNodeUpgradeHandler util (exported from crossws/adapters/node) wraps a plain Node.js (req, socket, head) upgrade handler as a crossws hooks object. This makes it possible to mount existing ws, socket.io, or express-ws based WebSocket servers through the Node adapter while they keep full ownership of the socket lifecycle.
import { WebSocketServer } from "ws";
import { fromNodeUpgradeHandler } from "crossws/adapters/node";
import { serve } from "crossws/server/node";
const wss = new WebSocketServer({ noServer: true });
wss.on("connection", (ws) => {
ws.on("message", (data) => ws.send(data));
});
serve({
fetch: () => new Response("ok"),
websocket: fromNodeUpgradeHandler((req, socket, head) => {
wss.handleUpgrade(req, socket, head, (ws) => {
wss.emit("connection", ws, req);
});
}),
});Because the wrapped handler takes ownership of the socket, crossws lifecycle hooks (open/message/close/error) are not invoked for connections routed through it — the underlying library manages them. A full socket.io example combining WebSocket and HTTP long-polling transports is included in the repo.
Other
- New
bunnyadapter (#179)
🩹 Fixes
- node: Do not url-encode upgrade response headers (ffbed40)
📦 Build
- Export
ServerWithWSOptionsandWSOptionstypes (#180)
🏡 Chore
- Update deps (ae08ca9)
- Update deps (3d83ed0)
- Update undici in tests (6291e7c)
- Migrate to oxlint and oxfmt (6a1a257)
- Update docs (02427f9)
✅ Tests
- node: Add regression test for
EADDRINUSEhandling (#183)
❤️ Contributors
- Pooya Parsa (@pi0)
- Neko (@nekomeowww)
- Sandro Circi (@sandros94)
v0.4.4
v0.4.3
v0.4.2
🩹 Fixes
- cloudflare: Send close frame (#177)
📦 Refactors
- Update dependencies (8761d2e)
📖 Documentation
❤️ Contributors
- Joseph Lee (@jclab-joseph)
- Pooya Parsa (@pi0)
- Nick Perez (@nperez0111)
- Rijk Van Zanten (@rijkvanzanten)
- Sébastien Chopin (@atinux)
- Abeer0 (@iiio2)
v0.4.1
v0.4.0
Noticeable Changes
🪄 Unified server powered by srvx (experimental)
Previously, you had to use manual adapter integrations or listhen for setting up a functional websocket server.
Powered by our new standard server building block srvx (experimental) and conditional exports, you can write a single server entry that magically works and natively integrates with any supported runtime.
// Magically works with Bun, Deno, and Node.js!
// (also Cloudflare or SSE as fallback)
import { serve } from "crossws/server";
serve({
websocket: {
message: console.log
}
});🗂️ Namespaced pub/sub peers
Relevant PR: #162
In the previous versions of crossws, there was one peers global Set in the server. Publishing to the same "topic", published to all peers subscribed to the same topic, even if they were connected to different WebSocket endpoints (as a result, we had to make sure topics are unique).
While it works in simple servers with a single set of hooks, when using dynamic hooks (using resolver), it makes sense that each peer only subscribes and publishes to relevant peers, and topics don't overlap.
With the new version, connected websocket peers have an assigned namespace. By default, the request "pathname" is used for namespace, so peers connected to /_ws/room1 and /_ws/room2 are isolated from each other.
It is possible to have customized namespace logic based on request using the adapter option getNamespace: (req: Request) => "<namespace>". The default behavior is req => new URL(req.url).pathname. You can use () => "default" for behavior similar to previous versions.
It is also possible to use a dynamically determined namespace per upgrade connection by returning { namespace } from the upgrade(req) hook.
Global ws.publish(topic, message) still publishes to all peers, but can specify a namespace using ws.publish(topic, message, { namespace })
🔥 Better cloudflare support
cloudflare and cloudflare-durable adapters are merged (#165) into single cloudflare. In case a Durable object is not exported or not resolvable, crossws will fallback to a simple WebSocket upgrade without pub/sub support.
With this release, Cloudflare (durable) also supports global publish() via RPC (#166). With this enhancement, all adapters now support global publish!
⚠️ Other Breaking Changes
⚠️ Support returning context from upgrade hook (#163)⚠️ Do not automatically accept firstsec-webSocket-protocolheader (#142)⚠️ Always passRequestas first param to resolve (#160)⚠️ Always terminate upgrade if aResponseis returned (#164)⚠️ Remove uncrypto dependency (Requires Node.js >= 20) (#153)
🚀 Enhancements
- Emulate full
Requestinterface (#156) - Universal server powered by srvx (experimental) (#158)
- Expose
PeerContextinterface for type augmentation (#159)
💅 Refactors
- Simplify console inspected values (aa49668)
- Throw error when running adapters in an incompatible environment (b5fcf2a)
- Narrow down upgrade return type for
upgradehook (d843cd0) - cloudflare: Show warning when pub/sub is not supported (#144)
📖 Documentation
- Change org from
unjstoh3js(#155) - Add docs for augmenting
PeerContexttype (#161) - Prepare for v0.4 (#168)
📦 Build
❤️ Contributors
v0.3.5
v0.3.4
🚀 Enhancements
- cloudflare: Support
resolveDurableStub(#130)
🩹 Fixes
- Global publish via first subscribed peer (#103)
- Define
request.contextas read-only (#133) - node, uws: Send data as blob only if it is not string (#124)
- bun: Pass
codeandreasontoclosehook (#132)
💅 Types
📖 Documentation
- node: Check
upgrade === "websocket"in example (#131) - Add dynamic example for changeable resolve (e5daf22)
❤️ Contributors
- Pooya Parsa (@pi0)
- Sandro Circi (@sandros94)
- Flo (@TecToast)
- Tee Ming (@eltigerchino)
v0.3.3
v0.3.2
Note
🔑 This release includes enhancements to make authentication and session handling easier.
- You can validate the
requestparam in theupgradehook andthrowaResponseas an error to terminate the upgrade (#91) - You can access a shared context from
request.contextin theupgradehook and frompeer.contextin the other hooks to preserve session data (#110, #111 (Note: context can be volatile in some environments likecloudflare-durable)
🩹 Fixes
- types:
peer.requestalways has.headersif defined (e915f8d) - types: Mark
peer.requestas always defined (8fbb59b)
📖 Documentation
🏡 Chore
- examples: Fix typo (#107)
❤️ Contributors
- Pooya Parsa (@pi0)
- Luke Hagar (@LukeHagar)
- 39sho (@39sho)
- Sandro Circi (@sandros94)
- Jamaluddin Rumi (@jamaluddinrumi)