WebSocket
WebSocket allows the server and browser to maintain a persistent, two-way connection. Unlike regular HTTP where the browser always initiates requests, WebSocket lets the server push messages to clients at any time.
Common use cases: real-time notifications, live chat, dashboards that update automatically, multiplayer games.
Finch's WebSocket support is built around three classes:
SocketManager— manages all active WebSocket connections and dispatches messages to the correct route handler.SocketEvent— defines callbacks foronConnect,onMessage, andonDisconnecton a specific path.SocketController— the HTTP controller that upgrades a regular HTTP request to a WebSocket connection.
Setup
1. Define a SocketManager
Create the SocketManager in app.dart. It takes the app instance, a root SocketEvent (for lifecycle events), and a map of named route handlers:
final socketManager = SocketManager(
app,
event: SocketEvent(
onConnect: (socket) {
// Called when a client connects
// Notify all other clients about the new connection
app.socketManager?.sendToAll(
'A user connected. Total: ${app.socketManager?.countClients}',
path: 'output',
);
// Send a confirmation to the newly connected client
socket.send(
{'message': 'Successfully connected to socket!'},
path: 'connected',
);
},
onMessage: (socket, data) {
// Called for messages not matched by any named route
},
onDisconnect: (socket) {
// Called when a client disconnects
var count = app.socketManager?.countClients ?? 0;
app.socketManager?.sendToAll(
'A user disconnected. Total: ${count - 1}',
path: 'output',
);
},
),
routes: _getSocketRoutes(),
);
2. Define Named Routes
Socket routes are a Map<String, SocketEvent>. Each key is a "path" (a logical channel name). When a client sends a message to that path, the corresponding onMessage callback fires:
Map<String, SocketEvent> _getSocketRoutes() {
return {
// Client sends to path 'test' — server replies with request headers
'test': SocketEvent(
onMessage: (socket, data) {
socket.send([socket.rq.headers], path: 'test');
},
),
// Client sends to path 'time' — server replies with current time
'time': SocketEvent(
onMessage: (socket, data) {
socket.send(DateTime.now().toString(), path: 'output');
},
),
};
}
3. Create a Socket Controller
A SocketController is a regular Finch controller. Its job is to hand the HTTP request over to socketManager so the upgrade can happen:
class WebSocketController extends Controller {
Future<String> socket() async {
// Transfer the request to the SocketManager for WebSocket upgrade
await socketManager.requestHandle(rq);
return rq.renderSocket(); // returns 'Socket is requested!'
}
}
4. Register the WebSocket Route
The WebSocket route must accept Methods.ALL because the upgrade handshake uses a GET request with special headers:
FinchRoute(
key: 'root.ws',
path: '/ws',
methods: Methods.ALL,
index: webSocketController.socket,
),
Sending Messages from the Server
The socket object inside any SocketEvent callback gives you methods to send messages:
// Send to the specific client that triggered the event
socket.send(data, path: 'channelName');
// Send to all connected clients
app.socketManager?.sendToAll(data, path: 'channelName');
// Send to all clients except the current one
app.socketManager?.sendToAllExcept(socket, data, path: 'channelName');
The path in a send call determines which handler on the client side receives it. On the JavaScript side, listen for messages on the same path name.
Client-Side JavaScript Example
const ws = new WebSocket('ws://localhost:8080/ws');
ws.onopen = () => console.log('Connected');
// Listen for messages on the 'connected' path
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.path === 'connected') {
console.log('Server says:', msg.data.message);
}
if (msg.path === 'output') {
console.log('Output:', msg.data);
}
};
// Send a message to the 'time' route on the server
ws.send(JSON.stringify({ path: 'time', data: {} }));
How Many Clients Are Connected?
int count = app.socketManager?.countClients ?? 0;
SocketManager
The SocketManager class is used to manage WebSocket connections in your Finch application. It contains all the necessary properties to manage WebSocket connections. Here is an example of how to use it:
final socketManager = SocketManager(
app,
event: SocketEvent(
onConnect: (socket) {
app.socketManager?.sendToAll(
"New user connected! count: ${app.socketManager?.countClients}",
path: "output",
);
socket.send(
{'message': 'Soccuess connect to socket!'},
path: 'connected',
);
},
onMessage: (socket, data) {},
onDisconnect: (socket) {
var count = app.socketManager?.countClients ?? 0;
app.socketManager?.sendToAll(
"User disconnected! count: ${count - 1}",
path: "output",
);
},
),
routes: getSocketRoute(),
);
SocketEvent
The SocketEvent class is used to define the events that can be triggered in a WebSocket connection. It contains all the necessary properties to define the events. Here is an example of how to use it:
'time': SocketEvent(
onMessage: (socket, data) {
socket.send(DateTime.now().toString(), path: 'output');
},
),
WebSocket Route
WebSocket routes are defined in the getSocketRoute function. This function returns a Map<String, SocketEvent> that contains all the WebSocket routes. Here is an example of how to use it:
Map<String, SocketEvent> getSocketRoute() {
return {
'test': SocketEvent(
onMessage: (socket, data) {
socket.send([socket.rq.headers], path: 'test');
},
),
};
}
Adding your socket routes to FinchApp
getSocketRoute() is a function that returns a Map<String, SocketEvent>. You can add it to your FinchApp instance by using the addSocketRoute method. Here is an example of how to use it:
class SocketController exatends Controller {
Future<String> socket() async {
await socketManager.requestHandle(rq);
return rq.renderSocket();
}
}
FinchRoute(
key: 'root.ws',
path: '/ws',
methods: Methods.ALL,
index: socketController.socket,
),