//! 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 { 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) }