mirror of
https://github.com/logos-messaging/logos-delivery-rust-bindings.git
synced 2026-07-30 06:53:29 +00:00
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:
parent
6a107e66cc
commit
dd4d093945
8
.github/workflows/main.yml
vendored
8
.github/workflows/main.yml
vendored
@ -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
172
examples/Cargo.lock
generated
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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],
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
pub mod contenttopic;
|
||||
pub mod messagehash;
|
||||
pub mod pubsubtopic;
|
||||
pub mod store;
|
||||
pub mod time;
|
||||
pub mod waku_decode;
|
||||
|
||||
|
||||
136
waku-bindings/src/general/store.rs
Normal file
136
waku-bindings/src/general/store.rs
Normal 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>,
|
||||
}
|
||||
@ -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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user