whatsapp-rust

repository·main·Indexed 20 days ago

https://github.com/oxidezap/whatsapp-rust

A high-performance, asynchronous Rust library for interacting with the WhatsApp Web API. Version 0.6.0 supports E2E encrypted messaging, media handling, VoIP calls, and group management. It is designed to be modular and runtime-agnostic, featuring a Bot builder for QR code pairing and message handling, as well as support for custom storage backends like SqliteStore.

Tokens
74.3K
Snippets
222
Records
330
Agent score
71%

What's inside whatsapp-rust

  1. Implement validated newtypes for protocol limits

    main

    To ensure protocol constraints are respected, use validated newtypes rather than performing checks at the call site.

    For example, instead of checking string lengths manually, use a type like GroupSubject (found in wacore/src/iq/groups.rs) which encapsulates the validation logic.

    Note on Limits: Constants like GROUP_SUBJECT_MAX_LENGTH, GROUP_DESCRIPTION_MAX_LENGTH, and GROUP_SIZE_LIMIT are derived from WhatsApp Web's A/B properties registry. Verify these against the registry before making changes.

  2. How the Noise handshake pattern is selected

    main

    The pattern selection logic (implemented in src/handshake.rs::select_pattern) determines whether to attempt an efficient IK handshake or a more robust XX handshake based on the following criteria:

    1. XX is selected if:
      • The device is not registered.
      • ik_failures has reached the IK_FAILURE_THRESHOLD.
      • There is no cached server_cert_chain.
      • The current time is outside the [not_before, not_after) window of the leaf or intermediate certificates (detecting expiry or backwards clock skew).
    2. IK (with leaf.key) is selected in all other cases.
  3. How native plugins work

    main

    Native plugins are build-time, type-safe extensions with scoped capabilities and lifecycle ownership. To implement a plugin, you must enable the plugins feature in your application:

    features = ["plugins"]

    Published plugin crates can enable this feature in their own whatsapp-rust dependency, and Cargo feature unification will activate it for the consumer. For details on the host contract and type-safe API examples, see agent_docs/plugin_architecture.md.

  4. Understand plugin capabilities and handles

    main

    Native plugins are trusted in-process code. They request specific capabilities via their manifest, and the PluginContext provides corresponding handles. These handles are designed to prevent ownership cycles by keeping a Weak<Client> internally.

    Capability identifierNative handle
    events.core.observePluginCoreEvents
    tasks.spawnPluginTasks / PluginConnectionTasks
    messaging.sendPluginMessaging
    iq.executePluginIq
    events.plugin.publishPluginEvents
  5. Understand persisted server certificate state

    main

    The Device.server_cert_chain stores a CachedServerCertChain containing an intermediate and a leaf certificate. Each certificate is reduced to the following fields:

    • key: [u8; 32]
    • not_before: i64
    • not_after: i64

    verify_server_cert performs strict validation, including structural shape, issuer-serial pinning against WA_CERT_ISSUER_SERIAL, chain linking, and verifying both XEdDSA signatures (the intermediate's signature over WA_CERT_PUB_KEY and the leaf's signature over the intermediate's key).

    Warning: Signature verification is only bypassed if the danger-skip-cert-chain-verify feature is enabled. Production builds always verify signatures.

  6. Understand binary size metrics and regression gates

    main

    The project uses a CI process to track the size of the demo example (the default-feature binary entry point). This prevents heavy new dependencies or monomorphization regressions from accumulating.

    Measured Metrics

    • bin size (stripped): A proxy for shipping size, measured using strip --strip-all.
    • bin .text: The signal for monomorphization bloat; invariant to stripping.
    • bin allocated (text+data+bss): Catches static data tables.
    • .text per crate: Attribution via cargo bloat --crates (workspace crates + std are individual series; others are aggregated as "other deps").
    • llvm-lines: LLVM IR lines and monomorphization copies (pre-link).
    • deps crates (Cargo.lock): A canary for new dependencies.

    Note: Do not use rlib size for measurement, as it fails to capture cross-crate instantiation bloat.

    Regression Gates (PRs)

    The CI gate uses absolute thresholds rather than percentages to ensure precision:

    • Stripped Δ: $\le$ 64 KiB
    • .text Δ: $\le$ 32 KiB

    If a PR exceeds these, the gate fails. You can use the size-increase-ok label on a PR to downgrade a failure to a warning (e.g., for toolchain bumps or accepted feature costs).

  7. Understand the Noise handshake patterns

    main

    The library uses three Noise handshake patterns to establish secure connections, mirroring WhatsApp Web's WAWebOpenChatSocket. Choosing the right pattern affects connection speed (RTT) and state management:

    • XX: Used for the first connection, pairing, or forced fallback. It uses the XxHandshakeState and requires 1.5 RTT.
    • IK: Used for reconnecting with a valid cached serverStaticPub. It uses IkHandshakeState, requires 1 RTT, and allows for 0-RTT login payloads.
    • XXfallback: Triggered when the server rejects an in-flight IK (indicated by a reply where static != null). It uses XxFallbackHandshakeState, requires 1 RTT, and reuses the already-sent ephemeral key.
  8. Isolate test accounts using `unique_push_name()`

    main

    Each TestClient uses an isolated InMemoryBackend. To ensure server-side account isolation (so tests don't interfere with each other), use unique_push_name(). This appends a fresh UUID to the name, ensuring clients land on different accounts.

    If you explicitly want to test multiple devices on the same account, build one name and pass it to multiple devices using connect_as(prefix, &name). This pairs them to the same phone number but with different device IDs.

  9. Understand IR limitations and sharp edges

    main

    The whatspec IR is a static analysis of minified code and has known limitations that developers must account for:

    • Heuristic Response Types: Generated Response types are not safe for direct deserialization (e.g., via serde_json::from_value). They may lack enums or incorrectly type string fields as numbers. Always use the typed input (Variables) and parse the output in a domain layer manually.
    • Flattened One-of Objects: Objects that use a oneof pattern in protobuf often flatten to a String in the IR. You may need to implement custom parsing logic to correct this.
    • Temporal Sensitivity: Protocol limits and doc IDs in the IR can change with WhatsApp updates. Use whatspec diff to detect changes.
    • Missing Control Flow: The IR does not describe when or how a request is sent (e.g., retries, debouncing, or feature flag gating). For this, you must inspect the raw JS bundle in docs/captured-js/.
  10. Identify the correct crate for protocol or runtime logic

    main

    The project is split into specific crates based on their responsibility. Use this guide to determine where to implement or look for logic:

    • wacore: Contains protocol logic, state traits, crypto helpers, and data models. It is platform-agnostic and supports wasm32 and ESP32.
    • whatsapp-rust: Handles runtime orchestration, storage, and the primary user-facing API. If your logic requires Tokio, it belongs here, not in wacore.
    • waproto: Contains only protobuf structures.

    Rule of thumb: If a feature requires a runtime (like Tokio), it belongs in whatsapp-rust. If it is pure protocol or crypto logic, it belongs in wacore.

  11. Use whatspec IR to verify protocol logic

    main

    The whatspec Intermediate Representation (IR) is a structured, queryable model of the WhatsApp Web contract derived from static analysis of the minified JS bundle. Use it to verify if a specific stanza exists, what attributes it carries, what its wire enum values are, or what protocol limits are set.

    Note: The IR is a derived model, not the absolute specification. It describes the shape of data but not the sequencing (control flow, retries, or timing).

    The IR is available in the `generated/` directory of the `whatspec` repository as JSON files.
  12. Manage plugin task lifecycles and scopes

    main

    Plugins operate within two primary task scopes:

    1. Install-scoped tasks (PluginTasks): These run once during installation and persist across connection reconnects. They are only cancelled during a rollback or terminal shutdown.
    2. Connection-scoped tasks (PluginConnectionTasks): These are tied to a specific connection generation. They are cancelled when a reconnect occurs or when the connection is closed.

    When spawning tasks, use spawn_cooperative if you need the task to observe shutdown_signal() or cancellation_signal() to finish work gracefully after a shutdown signal is received. The host will wait for a configured drain deadline before marking the plugin as degraded.