WebSocket API
In Kinode OS, WebSocket connects are made with a Rust warp server in the core http_server:distro:sys process.
Each connection is assigned a channel_id that can be bound to a given process using a WsRegister message.
The process receives the channel_id for pushing data into the WebSocket, and any subsequent messages from that client will be forwarded to the bound process.
Opening a WebSocket Channel from a Client
To open a WebSocket channel, connect to the main route on the node / and send a WsRegister message as either text or bytes.
The simplest way to connect from a browser is to use the @kinode/client-api like so:
const api = new KinodeEncryptorApi({
nodeId: window.our.node, // this is set if the /our.js script is present in index.html
processId: "my_package:my_package:template.os",
onOpen: (_event, api) => {
console.log('Connected to Kinode')
// Send a message to the node via WebSocket
api.send({ data: 'Hello World' })
},
})
@kinode/client-api is available here: https://www.npmjs.com/package/@kinode/client-api
Simple JavaScript/JSON example:
function getCookie(name) {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.startsWith(name)) {
return cookie.substring(name.length + 1);
}
}
}
const websocket = new WebSocket("http://localhost:8080/");
const message = JSON.stringify({
"auth_token": getCookie(`kinode-auth_${nodeId}`),
"target_process": "my_package:my_package:template.os",
"encrypted": false,
});
websocket.send(message);
Handling Incoming WebSocket Messages
Incoming WebSocket messages will be enums of HttpServerRequest with type WebSocketOpen, WebSocketPush, or WebSocketClose.
You will want to store the channel_id that comes in with WebSocketOpen so that you can push data to that WebSocket.
If you expect to have more than one client connected at a time, then you will most likely want to store the channel IDs in a Set (Rust HashSet).
With a WebSocketPush, the incoming message will be on the LazyLoadBlob, accessible with get_blob().
WebSocketClose will have the channel_id of the closed channel, so that you can remove it from wherever you are storing it.
A full example:
fn handle_http_server_request(
our: &Address,
message_archive: &mut MessageArchive,
source: &Address,
body: &[u8],
channel_ids: &mut HashSet,
) -> anyhow::Result<()> {
let Ok(server_request) = serde_json::from_slice::<HttpServerRequest>(body) else {
// Fail silently if we can't parse the request
return Ok(());
};
match server_request {
HttpServerRequest::WebSocketOpen { channel_id, .. } => {
// Set our channel_id to the newly opened channel
// Note: this code could be improved to support multiple channels
channel_ids.insert(channel_id);
}
HttpServerRequest::WebSocketPush { .. } => {
let Some(blob) = get_blob() else {
return Ok(());
};
handle_chat_request(
our,
message_archive,
our_channel_id,
source,
&blob.bytes,
false,
)?;
}
HttpServerRequest::WebSocketClose(_channel_id) => {
channel_ids.remove(channel_id);
}
HttpServerRequest::Http(IncomingHttpRequest { method, url, bound_path, .. }) => {
// Handle incoming HTTP requests here
}
};
Ok(())
}
Pushing Data to a Client via WebSocket
Pushing data to a connected WebSocket is very simple. Call the send_ws_push function from process_lib:
pub fn send_ws_push(
node: String,
channel_id: u32,
message_type: WsMessageType,
blob: LazyLoadBlob,
) -> anyhow::Result<()>
node will usually be our.node (although you can also send a WS push to another node's http_server!), channel_id is the client you want to send to, message_type will be either WsMessageType::Text or WsMessageType::Binary, and blob will be a standard LazyLoadBlob with an optional mime field and required bytes field.
If you would prefer to send the request without the helper function, this is that what send_ws_push looks like under the hood:
Request::new()
.target(Address::new(
node,
ProcessId::from_str("http_server:distro:sys").unwrap(),
))
.body(
serde_json::json!(HttpServerRequest::WebSocketPush {
channel_id,
message_type,
})
.to_string()
.as_bytes()
.to_vec(),
)
.blob(blob)
.send()?;