Live data over WebSocket

get_driver().websocket(url, callback) opens a connection and calls the callback with a WebsocketMessage:

  • Connection - when connected, carries the WebsocketConnection used to send messages,
  • Message - a text message from the server, parsed as JSON,
  • Binary - a binary message from the server,
  • Close - when the connection was lost or an attempt to connect failed, with the close code and reason.

The connection is kept open as long as the returned DropResource exists. Returning it from when_connect ties the connection to the part of the page which shows the data:

    use vertigo::{
        bind, dom, get_driver, transaction, JsJson, Value, WebsocketConnection, WebsocketMessage,
    };

    let connection = Value::new(None::<WebsocketConnection>);
    let messages = Value::new(Vec::<String>::new());
    let draft = Value::new(String::new());

    // Connects when the messages get mounted and disconnects when they get unmounted
    let live_messages = messages
        .to_computed()
        .when_connect(bind!(connection, messages, || {
            get_driver().websocket(
                "wss://example.com/chat",
                bind!(connection, messages, |message| match message {
                    WebsocketMessage::Connection(new_connection) => {
                        connection.set(Some(new_connection))
                    }
                    WebsocketMessage::Message(JsJson::String(text)) => {
                        messages.change(|messages| messages.push(text))
                    }
                    WebsocketMessage::Message(_) | WebsocketMessage::Binary(_) => {}
                    WebsocketMessage::Close(_) => connection.set(None),
                }),
            )
        }));

    let list = live_messages.render_value(|messages| {
        dom! {
            <ul>{..messages.into_iter().map(|message| dom! { <li>{message}</li> })}</ul>
        }
    });

    let status = connection.map(|connection| match connection {
        Some(_) => "Connected",
        None => "Connecting...",
    });

    let on_input = bind!(draft, |text: String| draft.set(text));

    let on_submit = bind!(connection, draft, || {
        transaction(|ctx| {
            if let Some(connection) = connection.get(ctx) {
                connection.send(draft.get(ctx));
                draft.set(String::new());
            }
        })
    });

    dom! {
        <div>
            <p>{status}</p>
            {list}
            <form {on_submit}>
                <input value={&draft} {on_input} />
                <button type="submit">"Send"</button>
            </form>
        </div>
    }

Text messages are JSON in both directions. send serializes the value, so above the server receives a JSON string, and a text message from the server is parsed as JSON before it reaches the callback. For structured messages use types deriving AutoJsJson and convert with from_json. Raw bytes go out with send_binary and come in as Binary.

When the connection gets lost, it's reestablished automatically. The callback gets Close, and then Connection again, with a new WebsocketConnection. The wait between attempts grows while they keep failing (0.5-1 s, doubled up to 15-30 s). To change it, use get_driver().websocket_with_options(url, WebsocketOptions { .. }, callback). To give up, call stop_reconnecting() on the WebsocketClose carried by Close.

Server-pushed collections

For lists kept in sync with the server, take a look at WsCollection, which subscribes to a query over a WebSocket and keeps the results up to date. It requires a server implementing its protocol.