Documentation for Redweb 0.15.0. Install that exact version when following these examples.
Server-side TSX for custom socket routes
Added in Redweb 0.15.0 with redweb-client 0.3.0, installed automatically by Redweb.
Earlier Redweb 0.14.0/client 0.2.0 packages do not provide this extension.
Contributors editing both packages can use the optional npm-link workflow in the repository's docs/CLIENT_DEVELOPMENT.md.
A live page can attach to a registered custom SocketRoute using
@page('/', { socket: MatchRoute }). There is no new view class, no second renderer,
and no application-specific browser module. Its connection carries ordinary typed
commands and reserved redweb:* rendering/completion messages.
Typed controls
defineSocketContract(...).handler(type, callback) still returns a handler class.
That class can now be used as a JSX binding:
<form rw-submit={Join}>
<input name="room" required />
<button>Join room</button>
</form>
<button rw-click={Move.with({ cell, revision })}>Move</button>.with() snapshots JSON data; it never invokes the handler during rendering.
TypeScript checks the payload's input type. Incoming wire data is independently
validated by the contract, including asynchronous schemas. Form fields merge over
the bound object payload. A click sends its bound payload, or an empty object when
none was supplied. Bound values are untrusted inputs, not server-held secrets or
authorization capabilities. Do not embed private data in them.
Bindings are server-owned identities, not arbitrary function closures. Rendering
rejects references to handlers absent from the attached route, even if a different
handler has the same message name. Existing string rw-click/rw-submit actions
remain unchanged on ordinary live pages. Socket-bound pages dispatch only their
custom route handlers; do not mix local @action()/rw-bind commands into them.
Server-owned client state
On an attached connection, socket.page(PageClass) returns that connection's
concrete page after checking its class, ownership and active lifetime:
if (socket.page) {
socket.page(GamePage).game = games.snapshot(room, account);
}Assigning @state() fields uses the existing reactive renderer and keyed TSX.
Raw connections have no page accessor and keep their existing socket protocol.
Shared domain state belongs in a room/service; each private page receives only its
authorized perspective. Components remain ordinary page-owned Redweb components.
Configuration and boundaries
Register the route once in defineApp({ pages: [GamePage], sockets: [MatchRoute] }).
Socket-bound pages must be live, connection-scoped and render TSX. Their route must
support protocol version 1 with the default redwebVersion query parameter and
explicitly allow duplicate connections (independent tabs). redweb:* handler names
are reserved. Page-enabled routes use ordered, bounded message dispatch and track
in-flight work for shutdown. JSON commands are supported; binary input is rejected
on page-attached connections. Raw route connections retain binary handling.
The adapter preserves route admission, origin and placement policies, then checks
the page token, exact route, page identity and page origin. Use authenticate for
page identity and authorize for current permissions/session validity. Authorization
runs before commands, again after contract validation, and before render publication.
Revocation and disconnect cancel ownership even when validation is still running.
The page token is not a substitute for credentials.
Initial page attachment completes before custom initialization hooks or commands. Do not call page commands from initialization hooks expecting those hooks themselves to finish first. Each page owns its connection; the same token cannot attach twice.
Reconnect reauthenticates, invokes existing connected() hooks, and sends a fresh
rendering snapshot. Game-seat recovery remains in that server hook. Commands are
not queued or replayed. Page-session expiration requires a reload; game persistence
is not provided by page retention.
Completion and client runtime
After successful handler dispatch, Redweb sends a correlated redweb:result.
The generated runtime waits specifically for that terminal type or a protocol error,
not intermediate correlated progress messages. Direct client users can opt into
the same behavior using client.request(type, payload, { responseType }); omitting
the option preserves the existing any-correlated-response behavior.
For an expected application rejection, send a safe correlated error with
socket.sendProtocolError('GAME_REJECTED', safeMessage, { requestId }) and return
false from the handler. Only literal false declines successful dispatch;
undefined remains success. The browser shows error feedback, preserves the form,
and keeps its connection usable. Returning false without sending a correlated
reply leaves a requesting client waiting until its deadline. Unexpected exceptions
retain the existing sanitized handler-failure behavior and close the connection.
This browser support lives in redweb-client's existing feedback/transport modules. The server still owns validation, authorization, game revisions, room fan-out and private rendering. Disabled controls are presentation, never permissions.