Skip to main content
The Client struct is the core of whatsapp-rust, managing connections, encryption, state, and all protocol-level operations.

Overview

The Client handles:
  • WebSocket connection lifecycle and automatic reconnection
  • Noise Protocol handshake and encryption
  • Signal Protocol E2E encryption for messages
  • App state synchronization
  • Device state persistence
  • Event dispatching
Most users should use the Bot builder instead of creating a Client directly. The Bot provides a simplified API with sensible defaults.

Creating a Client

use whatsapp_rust::Client;
use whatsapp_rust::TokioRuntime;
use std::sync::Arc;

let (client, sync_receiver) = Client::new(
    Arc::new(TokioRuntime),
    persistence_manager,
    transport_factory,
    http_client,
    None, // version override
).await;
runtime
Arc<dyn Runtime>
required
Async runtime for spawning tasks, sleeping, and blocking operations
persistence_manager
Arc<PersistenceManager>
required
State manager for device credentials, sessions, and app state
transport_factory
Arc<dyn TransportFactory>
required
Factory for creating WebSocket connections
http_client
Arc<dyn HttpClient>
required
HTTP client for media operations and version fetching
override_version
Option<(u32, u32, u32)>
Optional WhatsApp version override (primary, secondary, tertiary)
Returns
(Arc<Client>, Receiver<MajorSyncTask>)
Returns the client Arc and a receiver for history/app state sync tasks

Creating with custom cache configuration

use whatsapp_rust::{Client, CacheConfig, CacheEntryConfig};
use whatsapp_rust::TokioRuntime;
use std::time::Duration;

let cache_config = CacheConfig {
    group_cache: CacheEntryConfig::new(None, 500), // No TTL
    ..Default::default()
};

let (client, sync_receiver) = Client::new_with_cache_config(
    Arc::new(TokioRuntime),
    persistence_manager,
    transport_factory,
    http_client,
    None,
    cache_config,
).await;
See Bot - Cache Configuration Reference for available cache options.

Connection Management

run

pub async fn run(self: &Arc<Self>)
Main event loop that manages connection lifecycle with automatic reconnection. Runs indefinitely until:
  • disconnect() or logout() is called
  • Auto-reconnect is disabled and connection fails
  • Client receives a fatal stream error (401 unauthorized, 409 conflict, or 516 device removed)
Example:
let client_clone = client.clone();
tokio::spawn(async move {
    client_clone.run().await;
});

connect

pub async fn connect(self: &Arc<Self>) -> Result<(), anyhow::Error>
Establishes WebSocket connection and performs Noise Protocol handshake. Both the transport connection and the version fetch run in parallel under a 20-second timeout (TRANSPORT_CONNECT_TIMEOUT), matching WhatsApp Web’s MQTT and DGW connect timeout defaults. Without this, a dead network would block on the OS TCP SYN timeout (~60-75s). The Noise handshake response also has a separate 20-second timeout (NOISE_HANDSHAKE_RESPONSE_TIMEOUT). Errors:
  • ClientError::AlreadyConnected - Already connected
  • Transport connect timeout (20s exceeded)
  • Version fetch timeout (20s exceeded)
  • Noise handshake timeout or failure
  • Connection/handshake failures

logout

pub async fn logout(self: &Arc<Self>) -> Result<()>
Deregisters this companion device from WhatsApp and disconnects. This sends a device removal IQ to the server, disables auto-reconnect, disconnects the transport, and emits a LoggedOut event.
This does not wipe stored keys or credentials. To fully clear session data, delete the storage backend after calling logout().
Example:
// Logout and clean up
client.logout().await?;

// Optionally delete stored credentials
// std::fs::remove_dir_all("./whatsapp_data")?;
Behavior:
  1. Disables auto-reconnect
  2. Sends a RemoveCompanionDeviceSpec IQ to deregister the companion device (if connected)
  3. Disconnects the transport
  4. Emits Event::LoggedOut with reason: ConnectFailureReason::LoggedOut

disconnect

pub async fn disconnect(self: &Arc<Self>)
Disconnects gracefully and disables auto-reconnect. Internally calls cleanup_connection_state(), which resets all connection-scoped state — including invalidating per-chat message queues so stale workers exit, clearing the signal cache, draining pending IQ waiters, and resetting offline sync state. The cleanup runs exactly once in run() after the message loop exits, avoiding duplicate cleanup calls. See disconnect cleanup for the full list of resources cleaned up.

reconnect

pub async fn reconnect(self: &Arc<Self>)
Drops the current connection and triggers auto-reconnect with a deliberate ~5s offline window (Fibonacci backoff step 4). The run loop stays active. Use this for:
  • Handling network changes (e.g., Wi-Fi to cellular)
  • Forcing a fresh server session
  • Testing offline message delivery
Example:
// Force reconnection with backoff delay
client.reconnect().await;

// Wait for the new connection to be ready
client.wait_for_connected(Duration::from_secs(30)).await?;

reconnect_immediately

pub async fn reconnect_immediately(self: &Arc<Self>)
Drops the current connection and reconnects immediately with no delay. Unlike reconnect(), this sets the expected disconnect flag so the run loop skips the backoff delay. Example:
// Force immediate reconnection (no backoff)
client.reconnect_immediately().await;

wait_for_socket

pub async fn wait_for_socket(
    &self,
    timeout: std::time::Duration
) -> Result<(), anyhow::Error>
Waits for the Noise socket to be ready (before login). Useful for pair code flows.
timeout
Duration
required
Maximum time to wait
Returns
Result<(), anyhow::Error>
Ok if socket ready, Err on timeout

wait_for_connected

pub async fn wait_for_connected(
    &self,
    timeout: std::time::Duration
) -> Result<(), anyhow::Error>
Waits for full connection and authentication to complete, including offline sync. Post-login tasks (presence, background queries) are gated behind offline sync completion, which resolves either when the server sends the end marker, all expected items arrive, or the 60-second timeout fires.

pair_with_code

pub async fn pair_with_code(
    self: &Arc<Self>,
    options: PairCodeOptions,
) -> Result<String, PairCodeError>
Initiates pair code authentication as an alternative to QR code pairing. The returned 8-character code should be displayed to the user, who enters it on their phone under WhatsApp > Linked Devices > Link a Device > Link with phone number instead. This can run concurrently with QR code pairing — whichever completes first wins.
options
PairCodeOptions
required
Configuration for pair code authentication:
  • phone_number — Phone number in international format (e.g., "15551234567")
  • show_push_notification — Whether to show a push notification on the phone (default: true)
  • custom_code — Optional custom 8-character code using Crockford Base32 alphabet
  • platform_id — Platform identifier for the companion device (default: Chrome)
  • platform_display — Platform display name shown on the phone (default: "Chrome (Linux)")
code
String
The 8-character pairing code to display to the user
Errors (PairCodeError):
VariantCause
PhoneNumberRequiredEmpty phone number
PhoneNumberTooShortFewer than 7 digits
PhoneNumberNotInternationalStarts with 0 (not international format)
InvalidCustomCodeCustom code is not 8 valid Crockford Base32 characters
MissingPairingRefServer response missing pairing ref
RequestFailedServer request failed
Example:
use whatsapp_rust::pair_code::PairCodeOptions;

let options = PairCodeOptions {
    phone_number: "15551234567".to_string(),
    show_push_notification: true,
    custom_code: None, // Generate random code
    ..Default::default()
};

let code = client.pair_with_code(options).await?;
println!("Enter this code on your phone: {}", code);

Connection State

is_connected

pub fn is_connected(&self) -> bool
Returns true if the Noise socket is established. This method uses an internal AtomicBool flag (with Acquire ordering) instead of probing the noise socket mutex, making it lock-free and immune to false negatives under mutex contention.
Prior to this design, connection checks used try_lock() on the noise socket mutex. Under contention (e.g., during frame encryption), try_lock() would fail and incorrectly report the client as disconnected — silently dropping receipt acks. The AtomicBool approach eliminates this race condition entirely.

is_logged_in

pub fn is_logged_in(&self) -> bool
Returns true if authenticated with WhatsApp servers.

Auto-Reconnection

The client includes automatic reconnection handling with Fibonacci backoff.

How it works

  1. On disconnect: The client detects unexpected disconnections and automatically attempts to reconnect
  2. Fibonacci backoff: Each failed attempt increases the delay following the Fibonacci sequence (1s, 1s, 2s, 3s, 5s, 8s, 13s, 21s…) with a maximum of 900 seconds (15 minutes) and +/-10% jitter
  3. Expected disconnects: Protocol-expected disconnects (e.g., 515 stream error after pairing) trigger immediate reconnection without backoff
  4. Keepalive monitoring: A keepalive loop sends periodic pings (every 15-30s) and forces reconnection if the socket appears dead (no data received for 20s after a send)

Controlling auto-reconnect

// Disable auto-reconnect (default: enabled)
client.enable_auto_reconnect.store(false, Ordering::Relaxed);

// Check current state
let enabled = client.enable_auto_reconnect.load(Ordering::Relaxed);

Stream error handling

The client handles specific <stream:error> codes from the WhatsApp server:
Stream error codeMeaningEvent emittedAuto-reconnect
401Session invalidated (unauthorized)LoggedOutDisabled — must re-pair
409Another client connected (conflict)StreamReplacedDisabled — prevents displacement loop
429Rate limited (too many connections)None (emits Disconnected)Yes, with extended backoff (+5 steps)
503Service unavailableNoneYes, normal backoff
515Expected disconnect (e.g., post-pairing)NoneYes, immediate (no backoff)
516Device removedLoggedOutDisabled — must re-pair
UnknownUnrecognized codeStreamErrorDisabled
When you receive a LoggedOut or StreamReplaced event, auto-reconnect is permanently disabled for that session. You must create a new client and re-pair to continue.

Rate limiting (429)

When the server returns a 429 stream error, the client bumps the internal backoff counter by 5 Fibonacci steps before reconnecting. This means the reconnection delay jumps significantly (e.g., from ~1s to ~13s on the first rate limit) to respect the server’s throttling.

General reconnection behavior

ScenarioBehavior
Unexpected disconnectReconnect with Fibonacci backoff
515 stream error (after pairing)Immediate reconnect
Keepalive dead socket (20s)Force disconnect and reconnect
disconnect() calledNo reconnect attempt
logout() calledNo reconnect attempt, device deregistered
Auto-reconnect disabledNo reconnect attempt

Messaging

send_message

pub async fn send_message(
    &self,
    to: Jid,
    message: wa::Message
) -> Result<SendResult, anyhow::Error>
Sends an encrypted message to a chat.
to
Jid
required
Recipient JID (user@s.whatsapp.net or group@g.us)
message
wa::Message
required
Protobuf message content
Returns
Result<SendResult, anyhow::Error>
A SendResult containing the message_id and destination to JID
Example:
use waproto::whatsapp as wa;

let message = wa::Message {
    conversation: Some("Hello!".to_string()),
    ..Default::default()
};

let result = client.send_message(jid, message).await?;
println!("Sent message ID: {}", result.message_id);

send_message_with_options

pub async fn send_message_with_options(
    &self,
    to: Jid,
    message: wa::Message,
    options: SendOptions
) -> Result<SendResult, anyhow::Error>
Sends a message with advanced options like a custom message ID, extra stanza nodes, or ephemeral expiration.
options
SendOptions
required
Configuration for message sending behavior. Supports message_id (override the auto-generated ID), extra_stanza_nodes (custom XML nodes on the stanza), and ephemeral_expiration (disappearing message duration in seconds).
See Send API reference for available options.

edit_message

pub async fn edit_message(
    &self,
    to: Jid,
    original_id: impl Into<String>,
    new_content: wa::Message
) -> Result<String, anyhow::Error>
Edits a previously sent message.
original_id
String
required
ID of the message to edit
new_content
wa::Message
required
New message content

revoke_message

pub async fn revoke_message(
    &self,
    to: Jid,
    message_id: impl Into<String>,
    revoke_type: RevokeType
) -> Result<(), anyhow::Error>
Deletes a message. Use Sender to revoke your own message, or Admin to revoke another user’s message as group admin.
revoke_type
RevokeType
required
RevokeType::Sender (delete your own message) or RevokeType::Admin { original_sender: Jid } (admin revoke in groups)

Feature APIs

The Client provides namespaced access to feature-specific operations:

blocking

pub fn blocking(&self) -> Blocking<'_>
Access blocking operations. Methods:
  • block(jid: &Jid) - Block a contact
  • unblock(jid: &Jid) - Unblock a contact
  • get_blocklist() - Get all blocked contacts
  • is_blocked(jid: &Jid) - Check if contact is blocked
Example:
client.blocking().block(&jid).await?;
let blocked = client.blocking().get_blocklist().await?;

groups

pub fn groups(&self) -> Groups<'_>
Access group management operations. Methods:
  • query_info(jid: &Jid) - Get cached group info
  • get_metadata(jid: &Jid) - Fetch group metadata from server
  • get_participating() - List all groups you’re in
  • create_group(options: GroupCreateOptions) - Create a new group
  • set_subject(jid: &Jid, subject: GroupSubject) - Change group name
  • set_description(jid: &Jid, desc: Option<GroupDescription>, prev: Option<String>) - Change description
  • leave(jid: &Jid) - Leave a group
  • add_participants(jid: &Jid, participants: &[Jid]) - Add members
  • remove_participants(jid: &Jid, participants: &[Jid]) - Remove members
  • promote_participants(jid: &Jid, participants: &[Jid]) - Make members admins
  • demote_participants(jid: &Jid, participants: &[Jid]) - Remove admin status
  • get_invite_link(jid: &Jid, reset: bool) - Get/reset invite link
  • join_with_invite_code(code: &str) - Join a group via invite code or URL
  • join_with_invite_v4(group_jid, code, expiration, admin_jid) - Accept a V4 invite message
  • get_invite_info(code: &str) - Preview group metadata from invite code
  • set_locked(jid: &Jid, locked: bool) - Lock/unlock group info editing
  • set_announce(jid: &Jid, announce: bool) - Enable/disable announcement mode
  • set_ephemeral(jid: &Jid, expiration: u32) - Set disappearing messages timer
  • set_membership_approval(jid: &Jid, mode: MembershipApprovalMode) - Require admin approval
  • get_membership_requests(jid: &Jid) - Get pending membership requests
  • approve_membership_requests(jid: &Jid, participants: &[Jid]) - Approve pending requests
  • reject_membership_requests(jid: &Jid, participants: &[Jid]) - Reject pending requests
  • set_member_add_mode(jid: &Jid, mode: MemberAddMode) - Set who can add members
  • set_no_frequently_forwarded(jid: &Jid, restrict: bool) - Restrict forwarding of frequently forwarded messages
  • set_allow_admin_reports(jid: &Jid, allow: bool) - Allow or disallow admin reports
  • set_group_history(jid: &Jid, enabled: bool) - Enable or disable group history for new members
  • set_member_link_mode(jid: &Jid, mode: MemberLinkMode) - Set member link mode
  • set_member_share_history_mode(jid: &Jid, mode: MemberShareHistoryMode) - Set history sharing mode for new members
  • set_limit_sharing(jid: &Jid, enabled: bool) - Limit sharing within the group
  • cancel_membership_requests(jid: &Jid, participants: &[Jid]) - Cancel pending membership requests
  • revoke_request_code(jid: &Jid, participants: &[Jid]) - Revoke request codes for participants
  • acknowledge(jid: &Jid) - Acknowledge a group
  • batch_get_info(jids: Vec<Jid>) - Batch fetch group metadata for multiple groups
  • get_profile_pictures(group_jids: Vec<Jid>, picture_type: PictureType) - Batch fetch group profile pictures
Example:
use whatsapp_rust::features::groups::{GroupCreateOptions, GroupParticipantOptions};

let options = GroupCreateOptions::builder()
    .subject("My Group")
    .participants(vec![GroupParticipantOptions::new(participant_jid)])
    .build();
let result = client.groups().create_group(options).await?;

presence

pub fn presence(&self) -> Presence<'_>
Access presence operations. Methods:
  • set(status: PresenceStatus) - Set presence status
  • set_available() - Set status to available/online
  • set_unavailable() - Set status to unavailable/offline
  • subscribe(jid: &Jid) - Subscribe to contact’s presence updates
  • unsubscribe(jid: &Jid) - Unsubscribe from contact’s presence updates
Subscriptions are automatically tracked and re-subscribed on reconnect. Example:
client.presence().set_available().await?;
client.presence().subscribe(&jid).await?;

// Later, stop receiving updates
client.presence().unsubscribe(&jid).await?;

chatstate

pub fn chatstate(&self) -> Chatstate<'_>
Access chat state (typing indicator) operations. Methods:
  • send(to: &Jid, state: ChatStateType) - Send a chat state update
  • send_composing(to: &Jid) - Send typing indicator
  • send_recording(to: &Jid) - Send recording indicator
  • send_paused(to: &Jid) - Send paused/stopped typing indicator
Example:
// Send typing indicator
client.chatstate().send_composing(&jid).await?;

// Send recording indicator
client.chatstate().send_recording(&jid).await?;

// Stop typing indicator
client.chatstate().send_paused(&jid).await?;

contacts

pub fn contacts(&self) -> Contacts<'_>
Access contact operations. Methods:
  • is_on_whatsapp(jids: &[Jid]) - Check if JIDs are registered on WhatsApp (supports PN and LID JIDs)
  • get_user_info(jids: &[Jid]) - Get profile info for users by JID
  • get_profile_picture(jid: &Jid, preview: bool) - Get profile picture URL (preview or full size)

tc_token

pub fn tc_token(&self) -> TcToken<'_>
Access trust/privacy token operations. Methods:
  • issue_tokens(jids: &[Jid]) - Request tokens for contacts
  • prune_expired() - Remove expired tokens
  • get(jid: &str) - Get a stored token by JID
  • get_all_jids() - List all JIDs with stored tokens

chat_actions

pub fn chat_actions(&self) -> ChatActions<'_>
Access chat management actions. Operations sync across all linked devices via app state sync. Methods:
  • archive_chat(jid: &Jid, message_range: Option<SyncActionMessageRange>) - Archive a chat
  • unarchive_chat(jid: &Jid, message_range: Option<SyncActionMessageRange>) - Unarchive a chat
  • pin_chat(jid: &Jid) - Pin a chat
  • unpin_chat(jid: &Jid) - Unpin a chat
  • mute_chat(jid: &Jid) - Mute a chat indefinitely
  • mute_chat_until(jid: &Jid, mute_end_timestamp_ms: i64) - Mute until a specific time
  • unmute_chat(jid: &Jid) - Unmute a chat
  • star_message(chat_jid: &Jid, participant_jid: Option<&Jid>, message_id: &str, from_me: bool) - Star a message
  • unstar_message(chat_jid: &Jid, participant_jid: Option<&Jid>, message_id: &str, from_me: bool) - Unstar a message
  • mark_chat_as_read(jid: &Jid, read: bool, message_range: Option<SyncActionMessageRange>) - Mark a chat as read or unread across devices
  • delete_chat(jid: &Jid, delete_media: bool, message_range: Option<SyncActionMessageRange>) - Delete a chat from all linked devices
  • delete_message_for_me(chat_jid: &Jid, participant_jid: Option<&Jid>, message_id: &str, from_me: bool, delete_media: bool, message_timestamp: Option<i64>) - Delete a message locally (not for the other party)

status

pub fn status(&self) -> Status<'_>
Access status/story operations. Methods:
  • send_text(text, background_argb, font, recipients, options) - Post a text status
  • send_image(upload, thumbnail, caption, recipients, options) - Post an image status
  • send_video(upload, thumbnail, duration_seconds, caption, recipients, options) - Post a video status
  • send_raw(message, recipients, options) - Post any message type as a status
  • revoke(message_id, recipients, options) - Delete a posted status
  • send_reaction(status_owner, server_id, reaction) - React to a status update
See Status API for full documentation.

mex

pub fn mex(&self) -> Mex<'_>
Access Meta Exchange (GraphQL) operations. Methods:
  • query(request: MexRequest) - Execute a GraphQL query
  • mutate(request: MexRequest) - Execute a GraphQL mutation
See MEX API for full documentation.

profile

pub fn profile(&self) -> Profile<'_>
Access profile operations. Methods:
  • set_push_name(name: &str) - Set display name (syncs across devices)
  • set_status_text(text: &str) - Set profile “About” text
  • set_profile_picture(image_data: Vec<u8>) - Set profile picture (JPEG, 640x640 recommended)
  • remove_profile_picture() - Remove profile picture

newsletter

pub fn newsletter(&self) -> Newsletter<'_>
Access newsletter (channel) operations. Methods:
  • list_subscribed() - List all subscribed newsletters
  • get_metadata(jid: &Jid) - Get newsletter metadata
  • get_metadata_by_invite(invite: &str) - Get metadata via invite link
  • create(name, description) - Create a new newsletter
  • join(jid: &Jid) - Join a newsletter
  • leave(jid: &Jid) - Leave a newsletter
  • update(jid, options) - Update newsletter settings
  • send_reaction(jid, msg_server_id, reaction) - React to a newsletter message
  • get_messages(jid, count, before) - Fetch newsletter messages
  • subscribe_live_updates(jid: &Jid) - Subscribe to real-time updates
Newsletter message sending is handled by the unified client.send_message() method — pass a newsletter JID and the message is sent as plaintext automatically. See the Send API for details.
Example:
let newsletters = client.newsletter().list_subscribed().await?;
let metadata = client.newsletter().get_metadata(&newsletter_jid).await?;
See Newsletter API for full documentation.

community

pub fn community(&self) -> Community<'_>
Access community operations. Methods:
  • create(options: CreateCommunityOptions) - Create a community
  • deactivate(jid: &Jid) - Deactivate a community
  • link_subgroups(jid: &Jid, subgroups: &[Jid]) - Link groups to a community
  • unlink_subgroups(jid: &Jid, subgroups: &[Jid], remove_orphan_members: bool) - Unlink groups from a community
  • get_subgroups(jid: &Jid) - List community subgroups
  • get_subgroup_participant_counts(jid: &Jid) - Get participant counts per subgroup
  • query_linked_group(community_jid: &Jid, subgroup_jid: &Jid) - Query a linked group’s community metadata
  • join_subgroup(community_jid: &Jid, subgroup_jid: &Jid) - Join a community subgroup
  • get_linked_groups_participants(jid: &Jid) - Get participants across linked groups
Example:
let subgroups = client.community().get_subgroups(&community_jid).await?;
See Community API for full documentation.

polls

pub fn polls(&self) -> Polls<'_>
Access poll operations. Methods:
  • create(to: &Jid, name: &str, options: &[String], selectable_count: u32) - Create a poll (returns message ID and secret)
  • vote(chat_jid, poll_msg_id, poll_creator_jid, message_secret, option_names) - Cast a vote on a poll
  • decrypt_vote(enc_payload, enc_iv, message_secret, poll_msg_id, poll_creator_jid, voter_jid) - Decrypt a vote (static method)
  • aggregate_votes(poll_options, votes, message_secret, poll_msg_id, poll_creator_jid) - Tally all votes (static method)
Example:
let options = vec!["Yes".to_string(), "No".to_string()];
let (msg_id, secret) = client.polls().create(&chat_jid, "Agree?", &options, 1).await?;
See Polls API for full documentation.

media_reupload

pub fn media_reupload(&self) -> MediaReupload<'_>
Access media reupload operations. Use this when a media download fails because the URL has expired. Methods:
  • request(req: &MediaReuploadRequest) - Request the server to re-upload expired media
Example:
let result = client.media_reupload().request(&MediaReuploadRequest {
    msg_id: "ABCD1234",
    chat_jid: &chat_jid,
    media_key: &media_key_bytes,
    is_from_me: false,
    participant: Some(&sender_jid),
}).await?;
The request sends a server-error receipt and waits up to 30 seconds for a mediaretry notification with an updated download path.

signal

pub fn signal(&self) -> Signal<'_>
Access low-level Signal protocol operations for direct encryption, decryption, and session management. Methods:
  • encrypt_message(jid: &Jid, plaintext: &[u8]) - Encrypt plaintext for a single recipient
  • decrypt_message(jid: &Jid, enc_type: EncType, ciphertext: &[u8]) - Decrypt a Signal protocol message
  • encrypt_group_message(group_jid: &Jid, plaintext: &[u8]) - Encrypt plaintext for a group using sender keys
  • decrypt_group_message(group_jid: &Jid, sender_jid: &Jid, ciphertext: &[u8]) - Decrypt a group message
  • validate_session(jid: &Jid) - Check whether a Signal session exists
  • delete_sessions(jids: &[Jid]) - Delete Signal sessions and identity keys
  • create_participant_nodes(recipient_jids: &[Jid], message: &Message) - Create encrypted participant nodes
  • assert_sessions(jids: &[Jid]) - Ensure E2E sessions exist
  • get_user_devices(jids: &[Jid]) - Get all device JIDs for users
Example:
// Check if a session exists, then encrypt
let has_session = client.signal().validate_session(&jid).await?;
if !has_session {
    client.signal().assert_sessions(&[jid.clone()]).await?;
}
let (enc_type, ciphertext) = client.signal().encrypt_message(&jid, plaintext).await?;
These are low-level APIs that bypass the high-level message sending pipeline. Most users should use send_message() which handles encryption automatically.
See Signal API for full documentation.

Public fields

http_client

pub http_client: Arc<dyn HttpClient>
The HTTP client used for media operations, version fetching, and other HTTP requests. This field is public and can be used directly for custom HTTP operations that share the same client configuration.

enable_auto_reconnect

pub enable_auto_reconnect: Arc<AtomicBool>
Controls whether the client automatically reconnects after an unexpected disconnection. Defaults to true. Set to false to disable auto-reconnect.

auto_reconnect_errors

pub auto_reconnect_errors: Arc<AtomicU32>
Tracks the number of consecutive reconnection failures. Used internally for Fibonacci backoff calculations. Resets to 0 on a successful connection.

custom_enc_handlers

pub custom_enc_handlers: Arc<async_lock::RwLock<HashMap<String, Arc<dyn EncHandler>>>>
Custom handlers for encrypted message types. You can register handlers for specific encryption types that the client doesn’t natively support. Access requires .read().await for lookups and .write().await for modifications. These are also registerable via BotBuilder::with_enc_handler().

RECONNECT_BACKOFF_STEP

pub const RECONNECT_BACKOFF_STEP: u32 = 4;
The number of Fibonacci steps added to the backoff counter when reconnect() is called, creating an approximately 5-second offline window before the next connection attempt. This prevents tight reconnect loops after intentional disconnects.

Device State

get_push_name

pub async fn get_push_name(&self) -> String
Returns the current push name (display name).

get_pn

pub async fn get_pn(&self) -> Option<Jid>
Returns the phone number JID.

get_lid

pub async fn get_lid(&self) -> Option<Jid>
Returns the LID (Linked Identity).

get_lid_pn_entry

pub async fn get_lid_pn_entry(&self, jid: &Jid) -> Option<LidPnEntry>
Unified LID-PN lookup that auto-routes based on the JID type. Pass a phone number JID (@s.whatsapp.net) to look up its LID, or a LID JID (@lid) to look up its phone number. Returns None for non-user JIDs (groups, newsletters, etc.) or if no mapping is cached.
jid
&Jid
required
The JID to look up — either a PN JID or a LID JID
LidPnEntry
Option<LidPnEntry>
Contains lid (String), phone_number (String), created_at (i64 Unix timestamp), and learning_source (LearningSource)
Example:
use wacore_binary::jid::Jid;

// Look up by phone number JID
let pn_jid: Jid = "15551234567@s.whatsapp.net".parse()?;
if let Some(entry) = client.get_lid_pn_entry(&pn_jid).await {
    println!("LID: {}", entry.lid);
    println!("Phone: {}", entry.phone_number);
}

// Look up by LID JID
let lid_jid: Jid = "100000012345678@lid".parse()?;
if let Some(entry) = client.get_lid_pn_entry(&lid_jid).await {
    println!("Phone: {}", entry.phone_number);
}
This replaces the previous get_phone_number_from_lid method. The new API accepts a full Jid instead of a raw string and supports bidirectional lookup — pass either a PN or LID JID to resolve the mapping in either direction.

LearningSource

The LearningSource enum indicates how a LID-PN mapping was discovered:
VariantDescription
UsyncFrom a device sync query response
PeerPnMessageFrom an incoming message with sender_lid attribute (sender is PN)
PeerLidMessageFrom an incoming message with sender_pn attribute (sender is LID)
RecipientLatestLidFrom looking up a recipient’s latest LID
MigrationSyncLatestFrom latest history sync migration
MigrationSyncOldFrom old history sync records
BlocklistActiveFrom an active blocklist entry
BlocklistInactiveFrom an inactive blocklist entry
PairingFrom device pairing (own JID to LID)
DeviceNotificationFrom a device notification with lid attribute
OtherFrom an unknown source

persistence_manager

pub fn persistence_manager(&self) -> Arc<PersistenceManager>
Access to the persistence manager for multi-account scenarios.

History Sync

History sync transfers chat history from the phone to the linked device. The client processes history sync notifications through a RAM-optimized pipeline that minimizes peak memory usage.

Processing pipeline

When a history sync notification arrives, the client:
  1. Sends a HistorySync receipt immediately (so the phone knows delivery succeeded)
  2. Retrieves the data — either from an inline payload (moved via .take(), not cloned) or by stream-decrypting an external blob in 8KB chunks
  3. Extracts a compressed_size_hint from the notification’s file_length field, which the decompressor uses with a 4x multiplier for better buffer pre-allocation (avoids repeated Vec reallocation)
  4. Runs decompression and protobuf parsing on a blocking thread (tokio::task::spawn_blocking) to avoid stalling the async runtime
  5. Wraps the decompressed blob in a LazyHistorySync with cheap metadata (sync type, chunk order, progress) and dispatches it as Event::HistorySync(Box<LazyHistorySync>). Full protobuf decoding is deferred until the event handler calls .get()
If no event handlers are registered, the blob is not retained — only internal data (pushname, NCT salt, TC tokens) is extracted.

process_sync_task

pub async fn process_sync_task(self: &Arc<Self>, task: MajorSyncTask)
Processes a MajorSyncTask received from the sync channel returned by Client::new. This is the public entry point for handling history sync and app state sync tasks. The method dispatches to the appropriate internal handler based on the task variant:
  • MajorSyncTask::HistorySync — downloads and processes history sync data
  • MajorSyncTask::AppStateSync — synchronizes app state (contacts, mutes, pins, etc.)
Example:
let (client, mut sync_receiver) = Client::new(/* ... */).await;

// Process sync tasks from the channel
tokio::spawn({
    let client = client.clone();
    async move {
        while let Ok(task) = sync_receiver.recv().await {
            client.process_sync_task(task).await;
        }
    }
});
If you use the Bot builder, sync task processing is handled automatically. You only need this method when building a custom client setup.

set_skip_history_sync

pub fn set_skip_history_sync(&self, enabled: bool)
Enable or disable skipping of history sync notifications at runtime. When skipping is enabled, the client sends a receipt (so the phone stops retrying uploads) but does not download or process any data.

skip_history_sync_enabled

pub fn skip_history_sync_enabled(&self) -> bool
Returns true if history sync is currently being skipped.

Offline sync

The client automatically manages offline message sync when reconnecting. During sync, message processing is restricted to sequential mode (1 concurrent task) to preserve ordering.

Semaphore transition safety

When offline sync completes, the concurrency semaphore is swapped from 1 permit to 64 permits. Tasks that were already waiting on the old semaphore use a generation-checked re-acquire loop to safely transition — they detect the swap via an atomic generation counter, drop the stale permit, and re-acquire from the new semaphore. This prevents pkmsg messages (which carry SKDM for group decryption) from being silently dropped during the transition. See Concurrency gating for details.

Timeout fallback

If the server advertises offline messages but never completes delivery, a 60-second timeout ensures startup is not blocked indefinitely. On timeout:
  1. A warning is logged with the number of processed vs. expected items
  2. Offline sync is marked complete
  3. OfflineSyncCompleted event is emitted
  4. Message processing switches from sequential to parallel (64 concurrent tasks)

State reset on reconnect

All offline sync state (counters, timing, concurrency semaphore) is fully reset on reconnect so stale state does not carry over to the next connection. Related events: OfflineSyncPreview, OfflineSyncCompleted

App State

fetch_props

pub async fn fetch_props(&self) -> Result<(), IqError>
Fetches A/B experiment properties from WhatsApp servers and updates the in-memory AbPropsCache. When a stored props hash exists and the cache has been seeded (at least one full fetch has occurred), the request includes the hash for a delta update — the server only returns changed props. Otherwise, a full fetch is performed and all cached props are replaced. After the response is applied to the cache, the new hash (if present) is persisted for future delta requests. Features like group privacy token attachment query the AbPropsCache to check whether specific experiment flags are enabled. See AB props cache for details.

AB props cache

The client maintains an in-memory AbPropsCache that stores server-side A/B experiment properties. The cache is populated each time fetch_props() runs (automatically on connect) and is not persisted — props are re-fetched on every connection. Features query the cache via client.ab_props().is_enabled(config_code) to check whether a specific experiment flag is active. A prop is considered enabled when its value is "1", "true", or "enabled" (case-insensitive).

Well-known config codes

The following config codes control privacy token behavior on group operations:
ConstantCodeDescription
PRIVACY_TOKEN_ON_ALL_1_ON_1_MESSAGES10518Attach privacy tokens to all 1:1 messages
PRIVACY_TOKEN_ON_GROUP_CREATE11261Attach privacy tokens when creating groups
PRIVACY_TOKEN_ON_GROUP_PARTICIPANT_ADD11262Attach privacy tokens when adding group participants
PRIVACY_TOKEN_ONLY_CHECK_LID15491Only resolve tokens via LID mappings (skip PN fallback)
These constants are defined in wacore::iq::props::config_codes.
The AB props cache is internal to the client. You don’t need to interact with it directly — the library automatically checks relevant flags when performing group operations like create_group and add_participants.

fetch_privacy_settings

pub async fn fetch_privacy_settings(&self) -> Result<PrivacySettingsResponse, IqError>
Fetches privacy settings (last seen, profile photo, about, etc.). See Privacy API for types and details.

set_privacy_setting

pub async fn set_privacy_setting(
    &self,
    category: PrivacyCategory,
    value: PrivacyValue,
) -> Result<SetPrivacySettingResponse, IqError>
Sets a privacy setting for a specific category using type-safe enums.
category
PrivacyCategory
required
Privacy category enum: Last, Online, Profile, Status, GroupAdd, ReadReceipts, CallAdd, Messages, or DefenseMode
value
PrivacyValue
required
Privacy value enum: All, Contacts, None, ContactBlacklist, MatchLastSeen, Known, Off, or OnStandard
Example:
use whatsapp_rust::privacy_settings::{PrivacyCategory, PrivacyValue};

// Hide last seen from everyone
client.set_privacy_setting(PrivacyCategory::Last, PrivacyValue::None).await?;

// Show profile photo only to contacts
client.set_privacy_setting(PrivacyCategory::Profile, PrivacyValue::Contacts).await?;
See Privacy API for all categories, values, and valid combinations.

set_privacy_disallowed_list

pub async fn set_privacy_disallowed_list(
    &self,
    category: PrivacyCategory,
    update: DisallowedListUpdate,
) -> Result<SetPrivacySettingResponse, IqError>
Updates a privacy category’s disallowed list (contacts-except-specific-users mode). Only available for Last, Profile, Status, and GroupAdd. See Privacy API for details and examples.

set_default_disappearing_mode

pub async fn set_default_disappearing_mode(
    &self,
    duration: u32,
) -> Result<(), IqError>
Sets the default disappearing messages duration for new chats.
duration
u32
required
Timer duration in seconds. Common values: 86400 (24 hours), 604800 (7 days), 7776000 (90 days). Pass 0 to disable.
Example:
// Enable 7-day default disappearing messages
client.set_default_disappearing_mode(604800).await?;

// Disable default disappearing messages
client.set_default_disappearing_mode(0).await?;

get_business_profile

pub async fn get_business_profile(
    &self,
    jid: &Jid,
) -> Result<Option<BusinessProfile>, IqError>
Fetches the business profile for a WhatsApp Business account. Returns None if the account is not a business account or has no business profile.
jid
&Jid
required
JID of the business account to query
Example:
let jid: Jid = "15551234567@s.whatsapp.net".parse()?;

if let Some(profile) = client.get_business_profile(&jid).await? {
    println!("Description: {}", profile.description);
    println!("Email: {:?}", profile.email);
    println!("Website: {:?}", profile.website);
    println!("Address: {:?}", profile.address);

    for category in &profile.categories {
        println!("Category: {} ({})", category.name, category.id);
    }
} else {
    println!("Not a business account");
}
See Business API for full type details.

clean_dirty_bits

pub async fn clean_dirty_bits(
    &self,
    bit: wacore::iq::dirty::DirtyBit
) -> Result<(), IqError>
Cleans app state dirty bits. The DirtyBit struct contains a dirty_type (e.g., AccountSync, Groups, SyncdAppState, NewsletterMetadata) and an optional timestamp.

Protocol Operations

send_node

pub async fn send_node(&self, node: Node) -> Result<(), ClientError>
Sends a raw protocol node (advanced usage).
node
Node
required
Binary protocol node to send
Errors:
  • ClientError::NotConnected - Not connected
  • ClientError::EncryptSend - Encryption/send failure

send_raw_bytes

pub async fn send_raw_bytes(&self, plaintext: Vec<u8>) -> Result<(), ClientError>
Send pre-marshaled plaintext bytes through the noise socket. The bytes must be a valid WABinary-marshaled stanza (as produced by wacore_binary::marshal::marshal_to). Sending malformed data will cause the server to close the connection.
plaintext
Vec<u8>
required
WABinary-marshaled stanza bytes
Errors:
  • ClientError::NotConnected - Not connected
  • ClientError::EncryptSend - Encryption/send failure
This bypasses node logging and wait_for_sent_node waiter resolution. Use send_node for normal stanza sending. This method is intended for performance-critical paths where you already have marshaled bytes.

generate_message_id

pub async fn generate_message_id(&self) -> String
Generates a unique WhatsApp-protocol-conformant message ID. Combines timestamp, user JID, and random components for uniqueness.
This is intended for advanced users who need to build custom protocol interactions or manage message IDs manually. Most users should use send_message which handles ID generation automatically.

send_iq

pub async fn send_iq(&self, query: InfoQuery<'_>) -> Result<Arc<OwnedNodeRef>, IqError>
Sends a custom IQ (Info/Query) stanza to the WhatsApp server.
query
InfoQuery
required
IQ query containing stanza type, namespace, content, and optional timeout
Example:
use wacore::request::{InfoQuery, InfoQueryType};
use wacore_binary::builder::NodeBuilder;
use wacore_binary::node::NodeContent;
use wacore_binary::jid::{Jid, SERVER_JID};

let query_node = NodeBuilder::new("presence")
    .attr("type", "available")
    .build();

let server_jid = Jid::new("", SERVER_JID);

let query = InfoQuery {
    query_type: InfoQueryType::Set,
    namespace: "presence",
    to: server_jid,
    target: None,
    content: Some(NodeContent::Nodes(vec![query_node])),
    id: None,
    timeout: None,
};

let response = client.send_iq(query).await?;
This bypasses higher-level abstractions and safety checks. You should be familiar with the WhatsApp protocol and IQ stanza format before using this.

execute

pub async fn execute<S: IqSpec>(&self, spec: S) -> Result<S::Response, IqError>
Executes a typed IQ specification. This is the preferred way to send IQ stanzas — each spec type handles building the request and parsing the response.
spec
S: IqSpec
required
A typed IQ specification that defines the request structure and response parsing
Example:
// Fetch group metadata using a typed spec
let metadata = client.execute(GroupQueryIq::new(&group_jid)).await?;

wait_for_node

pub fn wait_for_node(&self, filter: NodeFilter) -> oneshot::Receiver<Arc<OwnedNodeRef>>
Waits for a specific incoming protocol node matching the given filter. Returns a receiver that resolves when a matching node arrives.
filter
NodeFilter
required
Filter specifying which node to wait for (by tag and attributes)
Example:
// Wait for a group notification
let waiter = client.wait_for_node(
    NodeFilter::tag("notification").attr("type", "w:gp2"),
);

// Perform the action that triggers the node
client.groups().add_participants(&group_jid, &[jid]).await?;

// Receive the notification
let node = waiter.await.expect("notification arrived");
Register the waiter before performing the action that triggers the expected node. When no waiters are active, this has zero cost (single atomic load per incoming node).

NodeFilter

Builder for matching incoming protocol nodes:
// Match by tag
let filter = NodeFilter::tag("notification");

// Match by tag and attributes
let filter = NodeFilter::tag("notification")
    .attr("type", "w:gp2");

// Match by tag and source JID
let filter = NodeFilter::tag("notification")
    .from_jid(&group_jid);

wait_for_sent_node

pub fn wait_for_sent_node(&self, filter: NodeFilter) -> oneshot::Receiver<Arc<Node>>
Waits for a specific outgoing protocol node matching the given filter. Returns a receiver that resolves when a matching node is sent by the client. This is the outbound counterpart to wait_for_node.
filter
NodeFilter
required
Filter specifying which outgoing node to intercept (by tag and attributes)
Example:
// Assert that a privacy token was attached to an outgoing message stanza
let waiter = client.wait_for_sent_node(
    NodeFilter::tag("message").attr("to", "15551234567@s.whatsapp.net"),
);

client.send_message(jid, message).await?;

let sent_node = waiter.await.expect("message stanza sent");
// Inspect the stanza to verify tctoken or cstoken was attached
Register the waiter before performing the action that produces the outgoing node. When no sent-node waiters are active, this has zero cost (single atomic load per outgoing node). Useful for testing whether <tctoken> or <cstoken> was attached to a sent stanza.

register_handler

pub fn register_handler(&self, handler: Arc<dyn EventHandler>)
Registers an event handler for protocol events.
handler
Arc<dyn EventHandler>
required
Handler implementing the EventHandler trait
Example:
use wacore::types::events::{Event, EventHandler};
use std::sync::Arc;

struct MyHandler;

impl EventHandler for MyHandler {
    fn handle_event(&self, event: Arc<Event>) {
        match &*event {
            Event::Message(msg, info) => println!("New message from {}: {:?}", info.source.sender, msg),
            Event::Connected(_) => println!("Connected!"),
            _ => {}
        }
    }
}

client.register_handler(Arc::new(MyHandler));
Using ChannelEventHandler: For channel-based event processing (useful for testing and custom event loops), use the built-in ChannelEventHandler:
use wacore::types::events::{ChannelEventHandler, Event};

let (handler, event_rx) = ChannelEventHandler::new();
client.register_handler(handler);

// Process events asynchronously via async-channel (runtime-agnostic)
// event_rx yields Arc<Event>
while let Ok(event) = event_rx.recv().await {
    match &*event {
        Event::Connected(_) => break,
        _ => {}
    }
}
See ChannelEventHandler for details.

register_chatstate_handler

pub async 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.

set_raw_node_forwarding

pub fn set_raw_node_forwarding(&self, enabled: bool)
Enable or disable raw node forwarding. When enabled, Event::RawNode is emitted for every decoded stanza before the stanza router dispatches it. Disabled by default to avoid overhead.
enabled
bool
required
Whether to emit Event::RawNode for every incoming stanza
Example:
// Enable raw protocol access (e.g. for voice call stanzas)
client.set_raw_node_forwarding(true);

// Handle raw nodes in your event handler
bot.on_event(|event, _client| async move {
    if let Event::RawNode(node) = &*event {
        println!("Raw stanza: {} {:?}", node.tag, node.attrs);
    }
});
Only enable this when you need raw protocol access. Every decoded stanza triggers the event, which adds overhead to the message processing pipeline.

Call management

reject_call

pub async fn reject_call(
    &self,
    call_id: &str,
    call_from: &Jid,
) -> Result<(), anyhow::Error>
Reject an incoming call. This is fire-and-forget — no server response is expected. Sends a <call><reject> stanza to the WhatsApp server.
call_id
&str
required
The ID of the incoming call to reject. Must not be empty.
call_from
&Jid
required
The JID of the caller.
Example:
bot.on_event(|event, client| async move {
    if let Event::Notification(node) = &*event {
        // Handle incoming call notification and reject it
        if let Some(call_id) = extract_call_id(&node) {
            let caller = extract_caller(&node);
            client.reject_call(&call_id, &caller).await.ok();
        }
    }
});

Spam Reporting

send_spam_report

pub async fn send_spam_report(
    &self,
    request: SpamReportRequest
) -> Result<SpamReportResult, IqError>
Send a spam report to WhatsApp for messages or groups.
request
SpamReportRequest
required
The spam report request containing:
  • message_id - ID of the message being reported
  • message_timestamp - Timestamp of the message
  • spam_flow - Context where report was initiated (MessageMenu, GroupInfoReport, etc.)
  • from_jid - Optional sender JID
  • group_jid - Optional group JID for group spam
  • group_subject - Optional group name/subject for group reports
  • participant_jid - Optional participant JID in group context
  • raw_message - Optional raw message bytes
  • media_type - Optional media type if reporting media
  • local_message_type - Optional local message type
Returns: SpamReportResult indicating success or failure Example:
use whatsapp_rust::{SpamReportRequest, SpamFlow};

// Report a spam message
let result = client.send_spam_report(SpamReportRequest {
    message_id: "MESSAGE_ID".to_string(),
    message_timestamp: 1234567890,
    from_jid: Some(sender_jid),
    spam_flow: SpamFlow::MessageMenu,
    ..Default::default()
}).await?;
SpamFlow variants:
  • MessageMenu - Reported from message context menu
  • GroupInfoReport - Reported from group info screen
  • GroupSpamBannerReport - Reported from group spam banner
  • ContactInfo - Reported from contact info screen
  • StatusReport - Reported from status view

Passive Mode

set_passive

pub async fn set_passive(&self, passive: bool) -> Result<(), IqError>
Sets passive mode. When false (active), the server starts sending offline messages.

Prekeys

refresh_pre_keys

pub async fn refresh_pre_keys(&self) -> Result<(), anyhow::Error>
Force-refreshes the server’s one-time pre-key pool with a fresh batch. This is intended for device migration scenarios where you restore a device from an external source (e.g., migrating a Baileys session into an InMemoryBackend) and the server may still hold pre-key IDs whose private key material you cannot reconstruct. Any pkmsg referencing those old IDs will fail permanently with InvalidPreKeyId. Calling refresh_pre_keys() uploads a fresh batch that the caller does have locally, and old unmatched IDs drain as peers consume them. Behavior:
  • Acquires the internal prekey_upload_lock so this force-upload cannot race with the count-based and digest-repair upload paths
  • Uploads a full batch of 812 pre-keys with Fibonacci retry backoff (1s, 2s, 3s, 5s, 8s, … capped at 610s)
  • Retries until success or the connection is lost
Only call this after restoring a device from an external session store. Under normal operation, the client manages pre-key uploads automatically.
Example:
// After migrating a session from another library (e.g., Baileys)
client.refresh_pre_keys().await?;

send_digest_key_bundle

pub async fn send_digest_key_bundle(&self) -> Result<(), IqError>
Validates that the server’s copy of the Signal Protocol key bundle matches local keys by querying a digest endpoint and comparing SHA-1 hashes. This matches WhatsApp Web’s WAWebDigestKeyJob.digestKey() flow. Behavior:
  • Queries the server for the current key bundle digest (identity key, signed pre-key, pre-key IDs, and a SHA-1 hash)
  • If the server returns 404 (no record), triggers a full pre-key re-upload
  • On success, loads local keys, computes the same SHA-1 digest, and compares
  • Hash mismatches or missing keys are logged but do not trigger re-upload — only 404 does
See Signal Protocol - Digest key validation for the wire format and detailed validation process.

Memory diagnostics

Requires the debug-diagnostics feature flag.

memory_diagnostics

#[cfg(feature = "debug-diagnostics")]
pub async fn memory_diagnostics(&self) -> MemoryDiagnostics
Returns a snapshot of all internal collection sizes for memory leak detection. Moka caches report approximate counts (pending evictions may not be reflected). MemoryDiagnostics fields:
FieldTypeDescription
group_cacheu64Moka cache — group metadata
device_registry_cacheu64Moka cache — device registry
lid_pn_lid_entriesu64LID-to-PN mapping entries
lid_pn_pn_entriesu64PN-to-LID mapping entries
retried_group_messagesu64Pending group message retries
recent_messagesu64Recent message dedup cache
message_retry_countsu64Message retry counter cache
pdo_pending_requestsu64PDO pending request cache
sender_key_device_cacheu64Per-group sender key device tracking cache
session_locksu64Per-device session locks
chat_lanesu64Per-chat lanes (combined enqueue lock + message queue)
response_waitersusizeActive IQ response waiters
node_waitersusizeActive node waiters
pending_retriesusizePending message retries
presence_subscriptionsusizeActive presence subscriptions
app_state_key_requestsusizePending app state key requests
app_state_syncingusizeActive app state sync operations
signal_cache_sessionsusizeCached Signal sessions
signal_cache_identitiesusizeCached Signal identities
signal_cache_sender_keysusizeCached sender keys
chatstate_handlersusizeRegistered chat state handlers
custom_enc_handlersusizeRegistered custom encryption handlers
Example:
let diag = client.memory_diagnostics().await;
println!("{}", diag); // Pretty-prints all collection sizes

Error Types

pub enum ClientError {
    NotConnected,
    Socket(SocketError),
    EncryptSend(EncryptSendError),
    AlreadyConnected,
    NotLoggedIn,
}

See Also