Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion advanced/signal-protocol.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,7 @@ This mechanism ensures that group messages from newly-paired companion devices a
// src/message.rs — simplified flow
async fn handle_unknown_device_sync(&self, info: &Arc<MessageInfo>) {
let user_jid = info.source.sender.to_non_ad();
if !self.pending_device_sync.add(user_jid.clone()).await {
if !self.pending_device_sync.add(&user_jid) {
return; // already queued, dedup
}
if info.is_offline {
Expand Down
9 changes: 4 additions & 5 deletions advanced/websocket-handling.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -777,7 +777,7 @@ impl Client {
).await?;

// Store socket and start receivers
self.noise_socket.store(Some(Arc::clone(&noise_socket)));
*self.noise_socket.lock().unwrap_or_else(|p| p.into_inner()) = Some(Arc::clone(&noise_socket));
self.start_frame_receiver(events, noise_socket).await;

Ok(())
Expand All @@ -795,7 +795,7 @@ async fn read_messages_loop(self: &Arc<Self>) -> Result<(), anyhow::Error> {
.ok_or_else(|| anyhow!("Cannot start message loop: not connected"))?;
// Noise socket is stable for the lifetime of this loop; resolve once
// instead of locking the mutex on every inbound frame.
let noise_socket = self.get_noise_socket().await
let noise_socket = self.get_noise_socket()
.map_err(|_| anyhow!("Cannot start message loop: no noise socket"))?;
let mut frame_decoder = FrameDecoder::new();

Expand Down Expand Up @@ -874,8 +874,7 @@ Frame decryption is always sequential (noise protocol counter ordering), but nod
```rust
impl Client {
pub async fn send_node(&self, node: &Node) -> Result<()> {
let noise_socket = self.noise_socket.load()
.ok_or_else(|| anyhow!("not connected"))?;
let noise_socket = self.get_noise_socket()?;

// Marshal node to binary with auto-sized buffer
let plaintext_buf = marshal_auto(node)?;
Expand All @@ -898,7 +897,7 @@ impl Client {
async fn cleanup_connection_state(&self) {
self.shutdown_notifier.notify(usize::MAX);
*self.transport.lock().await = None;
*self.noise_socket.lock().await = None;
*self.noise_socket.lock().unwrap_or_else(|p| p.into_inner()) = None;
self.is_connected.store(false, Ordering::Release);

// Drop per-chat lane senders so workers exit via channel close.
Expand Down
4 changes: 2 additions & 2 deletions api/chatstate.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ client.register_chatstate_handler(Arc::new(|event: ChatStateEvent| {
}
_ => {}
}
})).await;
}));
```

### ReceivedChatState
Expand Down Expand Up @@ -412,4 +412,4 @@ tokio::time::sleep(Duration::from_secs(3)).await;

client.chatstate().send_paused(&recipient).await?;
println!("Recording stopped (would send audio here)");
```
```
11 changes: 6 additions & 5 deletions api/client.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1808,13 +1808,14 @@ See [ChannelEventHandler](/concepts/events#channeleventhandler) for details.
### register_chatstate_handler

```rust
pub async fn register_chatstate_handler(
&self,
handler: Arc<dyn Fn(ChatStateEvent) + Send + Sync>,
)
pub fn register_chatstate_handler(&self, handler: Arc<dyn Fn(ChatStateEvent) + Send + Sync>)
```

Registers a handler for chat state events (typing indicators). The handler is wrapped in `Arc` for thread-safe sharing across the event dispatching system.
Register a handler for chat state events (typing indicators). Pass the handler in an `Arc` so the event dispatcher can share it across threads. Copy-on-write registration lets event dispatch continue while you register another handler. When no handler is registered, the client uses a lock-free fast path.

<Warning>
**Breaking change (as of PR #1227):** `register_chatstate_handler` is no longer `async`. Drop the `.await` at call sites: `client.register_chatstate_handler(handler)`.
</Warning>
Comment thread
coderabbitai[bot] marked this conversation as resolved.

### set_raw_node_forwarding

Expand Down
6 changes: 3 additions & 3 deletions concepts/architecture.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -148,14 +148,14 @@ See [custom backends](/guides/custom-backends) for implementing your own runtime

**Location:** `src/client.rs`

**Purpose:** Orchestrates connection lifecycle, event bus, and high-level operations. Uses `async-lock` (runtime-agnostic) for all internal synchronization instead of Tokio-specific primitives.
**Purpose:** Orchestrates connection lifecycle, event bus, and high-level operations. The synchronization primitive follows the shape of the state, not a single default. State whose critical section awaits uses `async-lock` (runtime-agnostic, not Tokio-specific). State whose critical section never awaits — a clone, a store, a set op — uses a `std::sync` lock instead. State that is built once and never replaced uses `std::sync::OnceLock`. On a path that must produce a `Send` future (e.g. a spawned task), a `std::sync::MutexGuard` isn't `Send`, so holding one across an `.await` there is a compile error rather than something a reviewer has to catch by hand ([#1227](https://github.com/oxidezap/whatsapp-rust/pull/1227)).

```rust
pub struct Client {
pub(crate) core: wacore::client::CoreClient,
pub(crate) persistence_manager: Arc<PersistenceManager>,
pub(crate) media_conn: Arc<RwLock<Option<MediaConn>>>,
pub(crate) noise_socket: Arc<Mutex<Option<Arc<NoiseSocket>>>>,
pub(crate) noise_socket: Arc<std::sync::Mutex<Option<Arc<NoiseSocket>>>>,
Comment thread
jlucaso1 marked this conversation as resolved.
Comment thread
jlucaso1 marked this conversation as resolved.
Comment thread
jlucaso1 marked this conversation as resolved.
// ... connection state, caches, locks
}
```
Expand Down Expand Up @@ -513,7 +513,7 @@ When online (not during offline sync), unknown devices trigger an immediate back
```rust
// src/pending_device_sync.rs
pub(crate) struct PendingDeviceSync {
pending: async_lock::Mutex<HashSet<Jid>>,
pending: std::sync::Mutex<HashSet<Jid>>,
}
```

Expand Down