convex_chat_client.rs•6.04 kB
//! A client for the Convex tutorial chat app.
//!
//! Please run this Convex Chat client from an initialized Convex project.
//! Check out the https://docs.convex.dev/get-started - to get started.
//!
//! Once you've initialized a Convex project with the tutorial, run this
//! demo from inside the project's working directory.
//!
//! For example:
//! cd /path/to/convex-rs
//! cargo build --example convex_chat_client
//! cd /path/to/convex-demos/tutorial
//! /path/to/convex-rs/target/debug/examples/convex_chat_client
use std::env;
use colored::Colorize;
use convex::{
ConvexClient,
FunctionResult,
Value,
};
use futures::{
pin_mut,
select_biased,
FutureExt,
StreamExt,
};
use maplit::btreemap;
use tokio::sync::oneshot;
const SETUP_MSG: &str = r"
Please run this Convex Chat client from an initialized Convex project.
Check out the https://docs.convex.dev/get-started - to get started.
Once you've initialized a Convex project with the tutorial, run this
demo from inside the project's working directory.
For example:
cd /path/to/convex-rs
cargo build --example convex_chat_clientt
cd /path/to/convex-demos/tutorial
/path/to/convex-rs/target/debug/examples/convex_chat_client
";
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.init();
// Load the tutorial's VITE_CONVEX_URL from the env file
dotenvy::from_filename(".env.local").ok();
dotenvy::dotenv().ok();
let Ok(deployment_url) = env::var("VITE_CONVEX_URL") else {
panic!("{SETUP_MSG}");
};
println!("Connecting to {deployment_url}");
// Client code used in thread #1
let mut client = ConvexClient::new(&deployment_url).await?;
// Client code used in thread #2
let mut client_ = client.clone();
println!("{}", format!("Hi! What's your name?").red().bold());
let mut sender = readline()?;
if sender.is_empty() {
sender = String::from("Anonymous Person");
}
let sender_clone = sender.clone();
// Thread listening for new messages (use_query demo)
let (cancel_sender, cancel_receiver) = oneshot::channel::<()>();
let handle = tokio::spawn(async move {
let mut subscription = client
.subscribe("messages:list", btreemap! {})
.await
.unwrap();
let cancel_fut = cancel_receiver.fuse();
pin_mut!(cancel_fut);
loop {
select_biased! {
new_val = subscription.next().fuse() => {
let new_val = new_val.expect("Client dropped prematurely");
println!(
"{}",
format!("---------------- Message History ----------------").yellow()
);
if let FunctionResult::Value(Value::Array(array)) = new_val {
for item in array {
if let Value::Object(obj) = item {
if let Some(Value::String(str)) = obj.get("body") {
let author = match obj.get("author") {
Some(Value::String(name)) => name,
_ => "Anonymous Author",
};
let author_string = if author == &sender_clone {
format!("{author}").yellow().bold()
} else {
format!("{author}").red().bold()
};
println!("{author_string}: {str:?}");
}
}
}
}
println!(
"{}",
format!("-------------- End Message History --------------").yellow()
);
},
_ = cancel_fut => {
break
},
}
}
println!("Message listener closed");
});
// Loop for sending messages
loop {
let line = readline()?;
let line = line.trim();
if line.is_empty() {
continue;
}
if line == "quit" || line == "exit" {
println!(
"{}",
format!("------------- Exiting Convex Demo -------------").blue()
);
break;
}
println!("{}", format!("Sending a message").yellow().bold());
let result = client_
.mutation(
"messages:send",
btreemap! {
"body".to_string() => line.into(),
"author".to_string() => sender.clone().into(),
},
)
.await?;
match result {
FunctionResult::Value(Value::Null) => {
println!("{}.", format!("Message sent").green().bold());
},
FunctionResult::Value(v) => {
println!(
"{}",
format!("Unexpected non-null result from messages:send {v:?}")
.red()
.bold()
);
},
FunctionResult::ErrorMessage(err) => {
println!("{}.", err.red().bold());
},
FunctionResult::ConvexError(err) => {
println!("{err:?}");
},
};
}
cancel_sender
.send(())
.expect("Failed to send termination signal");
handle.await?;
Ok(())
}
fn readline() -> anyhow::Result<String> {
let mut buffer = String::new();
std::io::stdin().read_line(&mut buffer)?;
if buffer.ends_with('\n') {
buffer.pop();
if buffer.ends_with('\r') {
buffer.pop();
}
}
Ok(buffer)
}