fix(examples): port toy-chat and tic-tac-toe-gui to the generated API

examples/ is its own cargo workspace and only examples/basic is a member
of the root one, so nothing built from the repo root ever compiled these
two. They still used waku_new, set_event_callback, WakuEvent and the
WakuNodeHandle typestate, none of which exist any more.

Both collapse their App<State> typestate: the node no longer carries one,
so neither can the app wrapping it. Event handling shrinks to a single
add_on_received_message_listener -- the filter on content topic stays,
but the enum match, the JSON parse and the panics on unexpected variants
go, since a listener only receives what it registered for. Payloads are
base64-decoded by the caller now.

The store DTOs (StoreQueryRequest, StoreResponse, StoreWakuMessageResponse)
come back as general/store.rs: they are plain serde shapes, and deleting
node/store.rs took them out along with the FFI call they sat next to.
toy-chat needs them to build the store query JSON and read the response.

CI gains a check over examples/Cargo.toml. Without it these rot silently
on the next API change, which is exactly how they got here.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Ivan FB 2026-07-16 01:28:54 +02:00
parent 6a107e66cc
commit dd4d093945
No known key found for this signature in database
GPG Key ID: DF0C67A04C543270
9 changed files with 381 additions and 211 deletions

View File

@ -38,6 +38,14 @@ jobs:
continue-on-error: false
with:
command: check
# examples/ is its own workspace and only examples/basic is a member of
# the root one, so a plain `cargo check` here never sees toy-chat or
# tic-tac-toe-gui. They break silently on any API change without this.
- uses: actions-rs/cargo@v1
continue-on-error: false
with:
command: check
args: --manifest-path examples/Cargo.toml --workspace
test:
name: Test

172
examples/Cargo.lock generated
View File

@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "ab_glyph"
@ -163,7 +163,7 @@ dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
"zerocopy 0.7.35",
]
[[package]]
@ -740,6 +740,7 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
name = "basic"
version = "0.1.0"
dependencies = [
"base64 0.21.7",
"futures",
"serde_json",
"tokio",
@ -747,28 +748,6 @@ dependencies = [
"waku-bindings",
]
[[package]]
name = "bindgen"
version = "0.64.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4"
dependencies = [
"bitflags 1.3.2",
"cexpr",
"clang-sys",
"lazy_static",
"lazycell",
"log",
"peeking_take_while",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn 1.0.109",
"which",
]
[[package]]
name = "bitflags"
version = "1.3.2"
@ -967,15 +946,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
@ -1017,6 +987,33 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "ciborium"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
dependencies = [
"ciborium-io",
"ciborium-ll",
"serde",
]
[[package]]
name = "ciborium-io"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
[[package]]
name = "ciborium-ll"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
dependencies = [
"ciborium-io",
"half 2.7.1",
]
[[package]]
name = "cipher"
version = "0.4.4"
@ -1027,17 +1024,6 @@ dependencies = [
"inout",
]
[[package]]
name = "clang-sys"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
dependencies = [
"glob",
"libc",
"libloading 0.8.6",
]
[[package]]
name = "clap"
version = "2.34.0"
@ -1941,6 +1927,17 @@ dependencies = [
"miniz_oxide 0.8.0",
]
[[package]]
name = "flume"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
dependencies = [
"futures-core",
"futures-sink",
"spin",
]
[[package]]
name = "fnv"
version = "1.0.7"
@ -2182,12 +2179,6 @@ dependencies = [
"xml-rs",
]
[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "glow"
version = "0.12.3"
@ -2281,6 +2272,17 @@ version = "1.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403"
[[package]]
name = "half"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
dependencies = [
"cfg-if",
"crunchy",
"zerocopy 0.8.27",
]
[[package]]
name = "hashbrown"
version = "0.11.2"
@ -2712,12 +2714,6 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "leb128"
version = "0.2.5"
@ -2885,12 +2881,6 @@ dependencies = [
"autocfg",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.7.4"
@ -3078,16 +3068,6 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "num"
version = "0.4.0"
@ -3473,12 +3453,6 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "peeking_take_while"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]]
name = "percent-encoding"
version = "2.3.1"
@ -3623,7 +3597,7 @@ version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
dependencies = [
"zerocopy",
"zerocopy 0.7.35",
]
[[package]]
@ -4156,7 +4130,7 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5"
dependencies = [
"half",
"half 1.8.3",
"serde",
]
@ -4392,6 +4366,15 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "spin"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e"
dependencies = [
"lock_api",
]
[[package]]
name = "spki"
version = "0.6.0"
@ -4572,6 +4555,7 @@ name = "tic-tac-toe-gui"
version = "0.1.0"
dependencies = [
"ark-std",
"base64 0.21.7",
"ctrlc",
"eframe",
"egui",
@ -4731,6 +4715,7 @@ dependencies = [
name = "toy-chat"
version = "0.1.0"
dependencies = [
"base64 0.21.7",
"chrono",
"crossterm",
"prost",
@ -4974,8 +4959,11 @@ dependencies = [
name = "waku-sys"
version = "1.0.0"
dependencies = [
"bindgen",
"cc",
"ciborium",
"flume",
"serde",
"tokio",
]
[[package]]
@ -6008,7 +5996,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"byteorder",
"zerocopy-derive",
"zerocopy-derive 0.7.35",
]
[[package]]
name = "zerocopy"
version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
dependencies = [
"zerocopy-derive 0.8.27",
]
[[package]]
@ -6022,6 +6019,17 @@ dependencies = [
"syn 2.0.89",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.89",
]
[[package]]
name = "zerofrom"
version = "0.1.5"

View File

@ -5,6 +5,7 @@ edition = "2021"
[dependencies]
waku = { path = "../../waku-bindings", package = "waku-bindings" }
base64 = "0.21"
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
ark-std = "0.4"

View File

@ -6,12 +6,16 @@ use std::time::Duration;
use tokio::task;
use tokio::sync::mpsc;
use base64::Engine;
use waku::{
waku_new, Encoding, WakuEvent, LibwakuResponse, WakuContentTopic,
WakuMessage, WakuNodeConfig, WakuNodeHandle, Initialized, Running,
Encoding, LogosDeliveryCtx, WakuContentTopic,
WakuMessage, WakuNodeConfig,
general::pubsubtopic::PubsubTopic,
};
const NODE_TIMEOUT: Duration = Duration::from_secs(30);
const PUBLISH_TIMEOUT_MS: u32 = 10_000;
#[derive(Serialize, Deserialize, PartialEq, Debug, Copy, Clone)]
enum Player {
X,
@ -25,17 +29,17 @@ struct GameState {
moves_left: usize,
}
struct TicTacToeApp<State> {
struct TicTacToeApp {
game_state: Arc<Mutex<GameState>>,
waku: WakuNodeHandle<State>,
waku: LogosDeliveryCtx,
game_topic: PubsubTopic,
tx: mpsc::Sender<String>, // Sender to send `msg` to main thread
player_role: Option<Player>, // Store the player's role (X or O)
}
impl TicTacToeApp<Initialized> {
impl TicTacToeApp {
fn new(
waku: WakuNodeHandle<Initialized>,
waku: LogosDeliveryCtx,
game_topic: PubsubTopic,
game_state: Arc<Mutex<GameState>>,
tx: mpsc::Sender<String>,
@ -49,56 +53,48 @@ impl TicTacToeApp<Initialized> {
}
}
async fn start(self) -> TicTacToeApp<Running> {
async fn start(self) -> TicTacToeApp {
let tx_clone = self.tx.clone();
let game_content_topic = WakuContentTopic::new("waku", "2", "tictactoegame", Encoding::Proto);
let game_content_topic =
WakuContentTopic::new("waku", "2", "tictactoegame", Encoding::Proto).to_string();
let my_closure = move |response| {
if let LibwakuResponse::Success(v) = response {
let event: WakuEvent =
serde_json::from_str(v.unwrap().as_str()).expect("Parsing event to succeed");
match event {
WakuEvent::WakuMessage(evt) => {
let message = evt.waku_message;
// Filter: only process messages for our game content topic
if message.content_topic != game_content_topic {
return; // Skip messages from other apps
}
let payload = message.payload.to_vec();
match from_utf8(&payload) {
Ok(msg) => {
// Lock succeeded, proceed to send the message
if tx_clone.blocking_send(msg.to_string()).is_err() {
eprintln!("Failed to send message to async task");
}
}
Err(e) => {
eprintln!("Failed to decode payload as UTF-8: {}", e);
// Handle the error as needed, or just log and skip
}
}
},
WakuEvent::RelayTopicHealthChange(_evt) => {
// dbg!("Relay topic change evt", evt);
},
WakuEvent::ConnectionChange(_evt) => {
// dbg!("Conn change evt", evt);
},
WakuEvent::Unrecognized(err) => panic!("Unrecognized waku event: {:?}", err),
_ => panic!("event case not expected"),
};
// Establish a listener that handles the incoming messages
self.waku.add_on_received_message_listener(move |event| {
// Filter: only process messages for our game content topic
if event.waku_message.content_topic != game_content_topic {
return; // Skip messages from other apps
}
};
// Establish a closure that handles the incoming messages
self.waku.set_event_callback(my_closure).expect("set event call back working");
let payload = match base64::engine::general_purpose::STANDARD
.decode(&event.waku_message.payload)
{
Ok(payload) => payload,
Err(e) => {
eprintln!("Failed to decode payload as base64: {}", e);
return;
}
};
match from_utf8(&payload) {
Ok(msg) => {
// Lock succeeded, proceed to send the message
if tx_clone.blocking_send(msg.to_string()).is_err() {
eprintln!("Failed to send message to async task");
}
}
Err(e) => {
eprintln!("Failed to decode payload as UTF-8: {}", e);
// Handle the error as needed, or just log and skip
}
}
});
// Start the waku node
let waku = self.waku.start().await.expect("waku should start");
self.waku.start_node_async().await.expect("waku should start");
// Subscribe to desired topic using the relay protocol
waku.relay_subscribe(&self.game_topic).await.expect("waku should subscribe");
self.waku
.waku_relay_subscribe_async(String::from(&self.game_topic))
.await
.expect("waku should subscribe");
// Example filter subscription. This is needed in edge nodes (resource-restricted devices)
// Nodes usually use either relay or lightpush/filter protocols
@ -125,7 +121,7 @@ impl TicTacToeApp<Initialized> {
TicTacToeApp {
game_state: self.game_state,
waku,
waku: self.waku,
game_topic: self.game_topic,
tx: self.tx,
player_role: self.player_role,
@ -133,7 +129,7 @@ impl TicTacToeApp<Initialized> {
}
}
impl TicTacToeApp<Running> {
impl TicTacToeApp {
async fn send_game_state(&self, game_state: &GameState) {
let serialized_game_state = serde_json::to_string(game_state).unwrap();
let content_topic = WakuContentTopic::new("waku", "2", "tictactoegame", Encoding::Proto);
@ -146,7 +142,16 @@ impl TicTacToeApp<Running> {
false,
);
if let Ok(msg_hash) = self.waku.relay_publish_message(&message, &self.game_topic, None).await {
let message = serde_json::to_string(&message).expect("message should serialise");
if let Ok(msg_hash) = self
.waku
.waku_relay_publish_async(
String::from(&self.game_topic),
message,
PUBLISH_TIMEOUT_MS,
)
.await
{
dbg!(format!("message hash published: {}", msg_hash));
}
@ -239,7 +244,7 @@ impl TicTacToeApp<Running> {
}
}
impl eframe::App for TicTacToeApp<Running> {
impl eframe::App for TicTacToeApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
// Request a repaint every second
@ -344,7 +349,7 @@ async fn main() -> eframe::Result<()> {
let game_topic = PubsubTopic::new("/waku/2/rs/16/32");
// Create a Waku instance
let waku = waku_new(Some(WakuNodeConfig {
let config = serde_json::to_string(&WakuNodeConfig {
tcp_port: Some(60010),
cluster_id: Some(16),
shards: vec![1, 32, 64, 128, 256],
@ -363,8 +368,12 @@ async fn main() -> eframe::Result<()> {
// discv5_enr_auto_update: Some(false),
..Default::default()
})).await
.expect("should instantiate");
})
.expect("config should serialise");
let waku = LogosDeliveryCtx::new_async(config, NODE_TIMEOUT)
.await
.expect("should instantiate");
let game_state = GameState {
board: [[None; 3]; 3],

View File

@ -9,6 +9,7 @@ authors = [
[dependencies]
waku = { path = "../../waku-bindings", package = "waku-bindings" }
base64 = "0.21"
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tui = "0.19"

View File

@ -22,9 +22,10 @@ use tui::{
Frame, Terminal,
};
use unicode_width::UnicodeWidthStr;
use base64::Engine;
use waku::{
general::pubsubtopic::PubsubTopic, general::Result, waku_new, Initialized, LibwakuResponse, Running, WakuEvent,
WakuMessage, WakuNodeConfig, WakuNodeHandle,
general::pubsubtopic::PubsubTopic, general::Result, LogosDeliveryCtx, StoreQueryRequest,
StoreResponse, WakuMessage, WakuNodeConfig,
};
enum InputMode {
@ -36,8 +37,12 @@ const STORE_NODE: &str = "/dns4/store-01.do-ams3.status.staging.status.im/tcp/30
const DEFAULT_PUBSUB_TOPIC: &str = "/waku/2/rs/16/32";
const NODE_TIMEOUT: Duration = Duration::from_secs(30);
const STORE_TIMEOUT_MS: i32 = 10_000;
const PUBLISH_TIMEOUT_MS: u32 = 10_000;
/// App holds the state of the application
struct App<State> {
struct App {
/// Current value of the input box
input: String,
nick: String,
@ -45,13 +50,13 @@ struct App<State> {
input_mode: InputMode,
/// History of recorded messages
messages: Arc<RwLock<Vec<Chat2Message>>>,
waku: WakuNodeHandle<State>,
waku: LogosDeliveryCtx,
}
impl App<Initialized> {
async fn new(nick: String) -> Result<App<Initialized>> {
impl App {
async fn new(nick: String) -> Result<App> {
let pubsub_topic = PubsubTopic::new(DEFAULT_PUBSUB_TOPIC);
let waku = waku_new(Some(WakuNodeConfig {
let config = serde_json::to_string(&WakuNodeConfig {
tcp_port: Some(60010),
cluster_id: Some(16),
shards: vec![1, 32, 64, 128, 256],
@ -70,8 +75,11 @@ impl App<Initialized> {
// discv5_enr_auto_update: Some(false),
..Default::default()
})).await?;
})
.map_err(|e| e.to_string())?;
let waku = LogosDeliveryCtx::new_async(config, NODE_TIMEOUT).await?;
Ok(App {
input: String::new(),
input_mode: InputMode::Normal,
@ -81,61 +89,46 @@ impl App<Initialized> {
})
}
async fn start_waku_node(self) -> Result<App<Running>> {
async fn start_waku_node(self) -> Result<App> {
let shared_messages = Arc::clone(&self.messages);
let toy_chat_topic = TOY_CHAT_CONTENT_TOPIC.to_string();
self.waku.set_event_callback(move|response| {
if let LibwakuResponse::Success(v) = response {
let event: WakuEvent =
serde_json::from_str(v.unwrap().as_str()).expect("failed parsing event in set_event_callback");
match event {
WakuEvent::WakuMessage(evt) => {
if evt.waku_message.content_topic != TOY_CHAT_CONTENT_TOPIC {
return; // skip the messages that don't belong to the toy chat
}
match <Chat2Message as Message>::decode(evt.waku_message.payload()) {
Ok(chat_message) => {
// Add the new message to the front
{
let mut messages_lock = shared_messages.write().unwrap();
messages_lock.insert(0, chat_message); // Insert at the front (index 0)
}
}
Err(e) => {
let mut out = std::io::stderr();
write!(out, "{e:?}").unwrap();
}
}
},
WakuEvent::RelayTopicHealthChange(_evt) => {
// dbg!("Relay topic change evt", evt);
},
WakuEvent::ConnectionChange(_evt) => {
// dbg!("Conn change evt", evt);
},
WakuEvent::Unrecognized(err) => eprintln!("Unrecognized waku event: {:?}", err),
_ => eprintln!("event case not expected"),
};
self.waku.add_on_received_message_listener(move |event| {
if event.waku_message.content_topic != toy_chat_topic {
return; // skip the messages that don't belong to the toy chat
}
})?;
let waku = self.waku.start().await?;
let payload = match base64::engine::general_purpose::STANDARD
.decode(&event.waku_message.payload)
{
Ok(payload) => payload,
Err(e) => {
let mut out = std::io::stderr();
write!(out, "{e:?}").unwrap();
return;
}
};
Ok(App {
input: self.input,
nick: self.nick,
input_mode: self.input_mode,
messages: self.messages,
waku,
})
match <Chat2Message as Message>::decode(payload.as_slice()) {
Ok(chat_message) => {
// Add the new message to the front
{
let mut messages_lock = shared_messages.write().unwrap();
messages_lock.insert(0, chat_message); // Insert at the front (index 0)
}
}
Err(e) => {
let mut out = std::io::stderr();
write!(out, "{e:?}").unwrap();
}
}
});
self.waku.start_node_async().await?;
Ok(self)
}
}
impl App<Running> {
async fn retrieve_history(&mut self) {
let one_day_in_secs = 60 * 60 * 24;
@ -143,22 +136,29 @@ impl App<Running> {
- Duration::from_secs(one_day_in_secs))
.as_nanos() as u64;
let include_data = true;
let query = StoreQueryRequest::new()
.with_include_data(true)
.with_content_topics(vec![TOY_CHAT_CONTENT_TOPIC.clone()])
.with_time_start(Some(time_start));
let query = serde_json::to_string(&query).expect("store query should serialise");
let messages = self.waku.store_query(None,
vec![TOY_CHAT_CONTENT_TOPIC.clone()],
STORE_NODE,
include_data,
Some(time_start),
None,
None).await.unwrap();
let response = self
.waku
.waku_store_query_async(query, STORE_NODE.to_string(), STORE_TIMEOUT_MS)
.await
.unwrap();
let response: StoreResponse =
serde_json::from_str(&response).expect("could not parse store resp");
let messages: Vec<_> = messages
let messages: Vec<_> = response
.messages
.into_iter()
// we expect messages because the query was passed with include_data == true
.filter(|item| item.message.is_some())
.map(|store_resp_msg| {
<Chat2Message as Message>::decode(store_resp_msg.message.unwrap().payload())
<Chat2Message as Message>::decode(
store_resp_msg.message.unwrap().payload.as_slice(),
)
.expect("Toy chat messages should be decodeable")
})
.collect();
@ -211,10 +211,12 @@ impl App<Running> {
handle.block_on(async {
// Assuming `self` is available in the current context
let pubsub_topic = PubsubTopic::new(DEFAULT_PUBSUB_TOPIC);
if let Err(e) = self.waku.relay_publish_message(
&waku_message,
&pubsub_topic,
None,
let waku_message = serde_json::to_string(&waku_message)
.expect("message should serialise");
if let Err(e) = self.waku.waku_relay_publish_async(
String::from(&pubsub_topic),
waku_message,
PUBLISH_TIMEOUT_MS,
).await {
let mut out = std::io::stderr();
write!(out, "{e:?}").unwrap();
@ -240,7 +242,10 @@ impl App<Running> {
}
async fn stop_app(self) {
self.waku.stop().await.expect("the node should stop properly");
self.waku
.stop_node_async()
.await
.expect("the node should stop properly");
}
}
@ -277,7 +282,7 @@ async fn main() -> std::result::Result<(), Box<dyn Error>> {
Ok(())
}
fn ui<B: Backend, State>(f: &mut Frame<B>, app: &App<State>) {
fn ui<B: Backend>(f: &mut Frame<B>, app: &App) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(2)

View File

@ -3,6 +3,7 @@
pub mod contenttopic;
pub mod messagehash;
pub mod pubsubtopic;
pub mod store;
pub mod time;
pub mod waku_decode;

View File

@ -0,0 +1,136 @@
//! Waku store protocol types.
//!
//! The query itself goes through `LogosDeliveryCtx::waku_store_query`, which
//! takes and returns JSON; these are the typed shapes on either side of it.
// crates
use serde::{Deserialize, Serialize};
use uuid::Uuid;
// internal
use crate::general::time::get_now_in_nanosecs;
use crate::general::{
contenttopic::WakuContentTopic, messagehash::MessageHash, pubsubtopic::PubsubTopic,
WakuStoreRespMessage,
};
// #[derive(Clone, Serialize, Deserialize, Debug)]
// #[serde(rename_all = "camelCase")]
// pub struct PagingOptions {
// pub page_size: usize,
// pub cursor: Option<MessageHash>,
// pub forward: bool,
// }
/// Criteria used to retrieve historical messages
#[derive(Clone, Serialize, Debug)]
pub struct StoreQueryRequest {
/// if true, the store-response will include the full message content. If false,
/// the store-response will only include a list of message hashes.
#[serde(rename = "request_id")]
request_id: String,
#[serde(rename = "include_data")]
include_data: bool,
#[serde(rename = "pubsub_topic", skip_serializing_if = "Option::is_none")]
pubsub_topic: Option<PubsubTopic>,
#[serde(rename = "content_topics")]
content_topics: Vec<WakuContentTopic>,
#[serde(rename = "time_start", skip_serializing_if = "Option::is_none")]
time_start: Option<u64>,
#[serde(rename = "time_end", skip_serializing_if = "Option::is_none")]
time_end: Option<u64>,
#[serde(rename = "message_hashes", skip_serializing_if = "Option::is_none")]
message_hashes: Option<Vec<MessageHash>>,
#[serde(rename = "pagination_cursor", skip_serializing_if = "Option::is_none")]
pagination_cursor: Option<MessageHash>, // Message hash (key) from where to start query (exclusive)
#[serde(rename = "pagination_forward")]
pagination_forward: bool,
#[serde(rename = "pagination_limit", skip_serializing_if = "Option::is_none")]
pagination_limit: Option<u64>,
}
impl StoreQueryRequest {
pub fn new() -> Self {
StoreQueryRequest {
request_id: Uuid::new_v4().to_string(),
include_data: true,
pubsub_topic: None,
content_topics: Vec::new(),
time_start: Some(get_now_in_nanosecs()),
time_end: Some(get_now_in_nanosecs()),
message_hashes: None,
pagination_cursor: None,
pagination_forward: true,
pagination_limit: Some(25),
}
}
pub fn with_include_data(mut self, include_data: bool) -> Self {
self.include_data = include_data;
self
}
pub fn with_pubsub_topic(mut self, pubsub_topic: Option<PubsubTopic>) -> Self {
self.pubsub_topic = pubsub_topic;
self
}
pub fn with_content_topics(mut self, content_topics: Vec<WakuContentTopic>) -> Self {
self.content_topics = content_topics;
self
}
pub fn with_time_start(mut self, time_start: Option<u64>) -> Self {
self.time_start = time_start;
self
}
pub fn with_time_end(mut self, time_end: Option<u64>) -> Self {
self.time_end = time_end;
self
}
#[allow(dead_code)]
pub fn with_message_hashes(mut self, message_hashes: Vec<MessageHash>) -> Self {
self.message_hashes = Some(message_hashes);
self
}
pub fn with_pagination_cursor(mut self, pagination_cursor: Option<MessageHash>) -> Self {
self.pagination_cursor = pagination_cursor;
self
}
pub fn with_pagination_forward(mut self, pagination_forward: bool) -> Self {
self.pagination_forward = pagination_forward;
self
}
}
#[derive(Clone, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct StoreWakuMessageResponse {
pub message_hash: MessageHash,
pub message: Option<WakuStoreRespMessage>, // None if include_data == false
pub pubsub_topic: String,
}
#[derive(Clone, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct StoreResponse {
#[allow(unused)]
pub request_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[allow(unused)]
pub status_code: u32,
#[allow(unused)]
pub status_desc: String,
/// Array of retrieved historical messages in [`WakuMessage`] format
// #[serde(default)]
pub messages: Vec<StoreWakuMessageResponse>,
/// Paging information in [`PagingOptions`] format from which to resume further historical queries
#[serde(skip_serializing_if = "Option::is_none")]
pub pagination_cursor: Option<MessageHash>,
}

View File

@ -20,6 +20,7 @@ pub use waku_sys::*;
use rln;
pub use general::contenttopic::{Encoding, WakuContentTopic};
pub use general::store::{StoreQueryRequest, StoreResponse, StoreWakuMessageResponse};
pub use general::{messagehash::MessageHash, Result, WakuMessage, WakuMessageVersion};
pub use node::{
Key, Multiaddr, PublicKey, PubsubTopic, RLNConfig, SecretKey, WakuNodeConfig,