Skip to content

feat(events): create and respond (RSVP) API - #758

Merged
jlucaso1 merged 2 commits into
mainfrom
feat/events-create-respond
Jun 8, 2026
Merged

feat(events): create and respond (RSVP) API#758
jlucaso1 merged 2 commits into
mainfrom
feat/events-create-respond

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

Closes the features-28 gap.

The proto types (EventMessage, EncEventResponseMessage, EventResponseMessage) and the EventResponse addon use-case already existed, but there was no way to create an event or RSVP to one.

src/features/events.rs (via Client::events()):

  • create(to, EventCreationParams) builds event_message plus a 32-byte messageContextInfo.message_secret (events require one — Events/ValidationError has MISSING_MESSAGE_SECRET, same pattern as polls) and sends through the normal path (classify.rs already routes event_message). Returns the secret so the creator can decrypt later RSVPs.
  • respond(chat, event_msg_id, creator, message_secret, EventResponseType, extra_guest_count) encrypts an EventResponseMessage and sends enc_event_response_message.

wacore/src/event.rs: encrypt_event_response_with_secret / decrypt_event_response_with_secret, a thin wrapper over secret_enc_addon with ModificationType::EventResponse (mirrors poll.rs). The event-creator + responder JIDs key the HKDF/AAD, and the responder JID is resolved to the creator's namespace (own LID for a LID-addressed event, else PN), matching the poll-vote path.

Verified against docs/captured-js/WAWeb/Events/GenerateEventCreationMessageProto.js (the { name, description, location, startTime, endTime, joinLink, isScheduleCall, ... } field set) and GenerateEventResponseMessageProto.js ({ eventCreationMessageKey, encPayload, encIv }).

Tests: event-message field mapping, response encrypt/decrypt roundtrip, wrong-responder decryption fails, and PN responder resolution.

Scope note: inbound decrypt wiring for received RSVPs (ModificationType::EventResponse on the recv path) is intentionally left as a follow-up; this PR covers the outbound create + respond APIs.

The proto types (EventMessage, EncEventResponseMessage, EventResponseMessage)
and the EventResponse addon use-case already existed, but there was no way to
create an event or RSVP to one.

Add src/features/events.rs (Client::events()):
- create(to, EventCreationParams) builds event_message + a 32-byte
  messageContextInfo.message_secret (events require one, like polls) and sends
  via the normal path (classify.rs already routes event_message). Returns the
  secret so the creator can decrypt later RSVPs.
- respond(chat, event_msg_id, creator, message_secret, EventResponseType,
  extra_guest_count) encrypts an EventResponseMessage and sends
  enc_event_response_message.

Add wacore/src/event.rs: encrypt/decrypt_event_response_with_secret, a thin
wrapper over secret_enc_addon with ModificationType::EventResponse (mirrors
poll.rs). The creator+responder JIDs key the HKDF/AAD, and the responder JID is
resolved to the creator's namespace (own LID for a LID event, else PN), matching
the poll-vote path.

Verified against docs/captured-js/WAWeb/Events/GenerateEventCreationMessageProto.js
and GenerateEventResponseMessageProto.js. Tests: event-message field mapping,
response encrypt/decrypt roundtrip, and wrong-responder decryption fails.
@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Event creation: Create WhatsApp events with metadata (name, description, time, location, join link) and guest settings.
    • Event RSVP: Send encrypted RSVP responses with attendance status and optional extra guest count; includes per-message secrets for decrypting responses.

Walkthrough

Adds event creation and RSVP APIs with per-message secrets, encrypted RSVP payloads (wacore addon encryption), responder JID resolution, client wiring, public re-exports, and unit tests.

Changes

Event Creation and RSVP Feature

Layer / File(s) Summary
Event response encryption foundation
wacore/src/event.rs, wacore/src/lib.rs
encrypt_event_response_with_secret / decrypt_event_response_with_secret enforce 32-byte secrets, protobuf-encode/decode EventResponseMessage, and use addon-based encryption with AddonContext derived from stanza id, event creator JID, and responder JID. Tests cover roundtrip and AAD mismatch failure.
Event creation and RSVP API
src/features/events.rs
Adds EventCreationParams and Events helper. Events::create validates name, constructs EventMessage, generates per-message secret, attaches it to message context, sends, and returns (SendResult, secret). Events::respond resolves responder JID (LID vs PN fallback), encrypts RSVP via wacore, wraps in EncEventResponseMessage, and sends. Includes builder and resolver tests.
Feature module integration
src/features/mod.rs, src/lib.rs
Declares events submodule and re-exports EventCreationParams, EventResponseType, and Events through features and crate public API.

Sequence Diagram

sequenceDiagram
  participant Client
  participant Events
  participant Wacore
  participant Chat
  Client->>Events: Client::events()->Events::create(params)
  Events->>Wacore: (for respond) encrypt_event_response_with_secret(response, secret, stanza_id, creator_jid, responder_jid)
  Wacore-->>Events: encrypted payload + iv
  Events->>Chat: send EncEventResponseMessage(enc_payload, iv)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~35 minutes

Possibly related PRs

Suggested labels

api-design

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: adding create and respond (RSVP) APIs for events, which is the core focus of this PR.
Description check ✅ Passed The description is directly related to the changeset, providing detailed context about event creation and RSVP handling APIs, encryption utilities, and verification against captured JavaScript prototypes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/events-create-respond

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown

Benchmark Results

67 unchanged benchmark(s)
Benchmark Current Baseline Change
reporting_token_benchmark::content_extraction_group::bench_content_extraction simple:setup_simple_message() 2,925 2,925 +0.0%
reporting_token_benchmark::content_extraction_group::bench_content_extraction extended:setup_extended_message() 8,446 8,446 +0.0%
reporting_token_benchmark::key_derivation_group::bench_key_derivation 31,317 31,317 +0.0%
reporting_token_benchmark::token_calculation_group::bench_token_calculation 13,827 13,827 +0.0%
reporting_token_benchmark::full_generation_group::bench_full_token_generation simple:setup_full_gen_simple() 49,485 49,485 +0.0%
reporting_token_benchmark::full_generation_group::bench_full_token_generation extended:setup_full_gen_extended() 55,001 55,001 +0.0%
reporting_token_benchmark::message_encoding_group::bench_message_encoding simple:setup_simple_message() 1,679 1,679 +0.0%
reporting_token_benchmark::message_encoding_group::bench_message_encoding extended:setup_extended_message() 4,393 4,393 +0.0%
send_receive_benchmark::dm_send::bench_dm_send text:setup_dm_send() 112,962 112,965 -0.0%
send_receive_benchmark::dm_recv::bench_dm_recv text:setup_dm_recv() 1,656,619 1,656,624 -0.0%
send_receive_benchmark::group_send::bench_group_send group_10:setup_group_send_10() 651,784 651,810 -0.0%
send_receive_benchmark::group_send::bench_group_send group_50:setup_group_send_50() 875,885 875,771 +0.0%
send_receive_benchmark::group_send::bench_group_send group_256:setup_group_send_256() 2,083,558 2,083,358 +0.0%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_10:setup_group_skdm_10() 749,046 749,048 -0.0%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_50:setup_group_skdm_50() 1,329,955 1,329,705 +0.0%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_256:setup_group_skdm_256() 4,373,445 4,395,382 -0.5%
send_receive_benchmark::group_recv::bench_group_recv text:setup_group_recv() 515,801 518,511 -0.5%
binary_benchmark::marshal_group::bench_marshal_allocating 45,381 45,381 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_allocating 45,431 45,431 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_allocating 66,334 66,334 +0.0%
binary_benchmark::marshal_group::bench_marshal_reusing_buffer 43,492 43,492 +0.0%
binary_benchmark::marshal_group::bench_marshal_reusing_buffer_vec_writer 45,487 45,487 +0.0%
binary_benchmark::marshal_group::bench_marshal_long_string 4,945 4,945 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_long_string 4,976 4,976 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_long_string 6,747 6,747 +0.0%
binary_benchmark::marshal_group::bench_marshal_huge_bytes_allocating 528,544 528,544 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_huge_bytes_allocating 528,165 528,165 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_huge_bytes_allocating 529,411 529,411 +0.0%
binary_benchmark::marshal_group::bench_marshal_many_children_allocating 5,417,732 5,417,732 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_many_children_allocating 5,362,047 5,362,047 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_many_children_allocating 13,276,365 13,276,365 +0.0%
binary_benchmark::unmarshal_group::bench_unmarshal small:setup_small_marshaled() 1,850 1,850 +0.0%
binary_benchmark::unmarshal_group::bench_unmarshal large:setup_large_marshaled() 29,217 29,217 +0.0%
binary_benchmark::unpack_group::bench_unpack_uncompressed 618 618 +0.0%
binary_benchmark::unpack_group::bench_unpack_compressed 672,890 672,890 +0.0%
binary_benchmark::attr_parser_group::bench_attr_parser attr_lookup:setup_attr_marshaled() 3,736 3,736 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip small:setup_small_marshaled() 3,840 3,840 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip large:setup_large_marshaled() 48,274 48,274 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_auto small:setup_small_marshaled() 3,866 3,866 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_auto large:setup_large_marshaled() 48,335 48,335 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_exact small:setup_small_marshaled() 5,206 5,206 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_exact large:setup_large_marshaled() 66,659 66,659 +0.0%
binary_benchmark::child_iteration_group::bench_get_children_by_tag 310,312 310,312 +0.0%
binary_benchmark::jid_optimization_group::bench_jid_to_owned_access jid_access:setup_jid_heavy_marshaled() 8,291 8,291 +0.0%
numeric_attr_benchmark::bench_group::bench_baseline_u32 254 254 +0.0%
numeric_attr_benchmark::bench_group::bench_proposed_u32 91 91 +0.0%
numeric_attr_benchmark::bench_group::bench_baseline_u64 292 292 +0.0%
numeric_attr_benchmark::bench_group::bench_proposed_u64 137 137 +0.0%
numeric_attr_benchmark::bench_group::bench_baseline_i64 317 317 +0.0%
numeric_attr_benchmark::bench_group::bench_proposed_i64 145 145 +0.0%
numeric_attr_benchmark::bench_group::bench_baseline_loop_100_u64 27,425 27,425 +0.0%
numeric_attr_benchmark::bench_group::bench_proposed_loop_100_u64 10,725 10,725 +0.0%
libsignal_benchmark::dm_group::bench_dm_session_establishment setup:setup_dm_users() 4,145,272 4,139,297 +0.1%
libsignal_benchmark::dm_group::bench_dm_encrypt_first_message first_msg:setup_dm_session() 100,131 100,133 -0.0%
libsignal_benchmark::dm_group::bench_dm_decrypt_first_message decrypt_prekey:setup_dm_with_first_message() 4,264,189 4,264,189 +0.0%
libsignal_benchmark::dm_group::bench_dm_encrypt_subsequent_message subsequent:setup_established_dm_session() 100,399 100,399 +0.0%
libsignal_benchmark::group_messaging_group::bench_group_create_distribution_message create:setup_group_sender() 210,262 210,262 +0.0%
libsignal_benchmark::group_messaging_group::bench_group_encrypt_message encrypt:setup_group_with_distribution() 496,921 496,921 +0.0%
libsignal_benchmark::group_messaging_group::bench_group_decrypt_message decrypt:setup_group_with_encrypted_message() 510,940 509,402 +0.3%
libsignal_benchmark::conversation_group::bench_full_dm_conversation full:setup_conversation_data() 11,978,865 11,973,694 +0.0%
libsignal_benchmark::signature_group::bench_signature_creation sign:setup_keypair_with_message() 2,466,138 2,466,138 +0.0%
libsignal_benchmark::signature_group::bench_signature_verification verify:setup_keypair_with_message() 4,895,402 4,901,832 -0.1%
libsignal_benchmark::signature_group::bench_key_generation keygen 2,043,397 2,043,397 +0.0%
libsignal_benchmark::session_optimization_group::bench_decrypt_with_previous_session previous_session:setup_with_archived_sessions() 37,404 37,414 -0.0%
libsignal_benchmark::session_optimization_group::bench_out_of_order_decryption out_of_order:setup_out_of_order_messages() 3,617,967 3,617,967 +0.0%
libsignal_benchmark::session_optimization_group::bench_promote_matching_session promote:setup_promote_matching_session() 230,648 230,648 +0.0%
libsignal_benchmark::session_optimization_group::bench_message_key_eviction eviction:setup_message_key_eviction() 9,980,959 9,980,959 +0.0%
No significant changes detected.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3dffe6a265

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/features/mod.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@wacore/src/event.rs`:
- Around line 29-35: Validate fixed-size crypto inputs at the API boundary: in
encrypt_event_response_with_secret (and the other function that accepts an iv
slice) check that message_secret.len() == 32 and iv.len() == GCM_IV_SIZE (12)
immediately and return an Err with a clear error if sizes mismatch; do this
before any cryptographic operations so callers get deterministic errors rather
than downstream panics/crypto failures, and reference the functions
encrypt_event_response_with_secret and the corresponding decrypt/iv-handling
function to locate and apply the same checks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d1bf638c-329a-491c-914f-2abe14f9d590

📥 Commits

Reviewing files that changed from the base of the PR and between 0cb4ca5 and 3dffe6a.

📒 Files selected for processing (4)
  • src/features/events.rs
  • src/features/mod.rs
  • wacore/src/event.rs
  • wacore/src/lib.rs

Comment thread wacore/src/event.rs
Comment on lines +29 to +35
pub fn encrypt_event_response_with_secret(
response: &EventResponseMessage,
message_secret: &[u8],
stanza_id: &str,
event_creator_jid: &str,
responder_jid: &str,
) -> Result<(Vec<u8>, [u8; GCM_IV_SIZE])> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Lock down fixed-size crypto inputs at the API boundary.

Line 29 and Line 48 accept unconstrained slices for message_secret/iv, but this protocol path requires fixed sizes (32-byte secret, 12-byte IV). Reject invalid sizes early so callers get deterministic errors instead of opaque downstream crypto failures.

Proposed patch
-use anyhow::Result;
+use anyhow::{ensure, Result};
@@
 pub fn encrypt_event_response_with_secret(
     response: &EventResponseMessage,
     message_secret: &[u8],
@@
 ) -> Result<(Vec<u8>, [u8; GCM_IV_SIZE])> {
+    ensure!(
+        message_secret.len() == 32,
+        "message_secret must be exactly 32 bytes"
+    );
     let plaintext = response.encode_to_vec();
@@
 pub fn decrypt_event_response_with_secret(
     enc_payload: &[u8],
     iv: &[u8],
     message_secret: &[u8],
@@
 ) -> Result<EventResponseMessage> {
+    ensure!(
+        message_secret.len() == 32,
+        "message_secret must be exactly 32 bytes"
+    );
+    ensure!(
+        iv.len() == GCM_IV_SIZE,
+        "iv must be exactly {} bytes",
+        GCM_IV_SIZE
+    );
     let plaintext = decrypt_addon(

Also applies to: 48-55

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wacore/src/event.rs` around lines 29 - 35, Validate fixed-size crypto inputs
at the API boundary: in encrypt_event_response_with_secret (and the other
function that accepts an iv slice) check that message_secret.len() == 32 and
iv.len() == GCM_IV_SIZE (12) immediately and return an Err with a clear error if
sizes mismatch; do this before any cryptographic operations so callers get
deterministic errors rather than downstream panics/crypto failures, and
reference the functions encrypt_event_response_with_secret and the corresponding
decrypt/iv-handling function to locate and apply the same checks.

Review follow-ups on the events PR:
- Re-export EventCreationParams, EventResponseType, Events from src/lib.rs
  (pub use features::{...}), matching how the other high-level feature APIs are
  reachable as whatsapp_rust::* per AGENTS.md (Codex).
- Validate message_secret is 32 bytes at the public crypto boundary in
  encrypt/decrypt_event_response_with_secret so a wrong-size secret fails with a
  clear error instead of an opaque GCM failure (HKDF accepts any ikm length). The
  IV length is already validated by decrypt_addon (CodeRabbit).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
wacore/src/event.rs (1)

54-75: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Consider early IV length validation for clearer error messages.

Look, I get it—you're delegating IV validation to decrypt_addon's try_into [u8; 12]. That works. But when something goes wrong, your users are going to see some cryptic slice conversion error instead of a clear "iv must be 12 bytes" message. We're building something people actually use here.

The message_secret check you added is exactly right. The IV is the same deal—fixed-size crypto input that should fail fast with an obvious error at the API boundary rather than deep in the stack.

Proposed patch
 pub fn decrypt_event_response_with_secret(
     enc_payload: &[u8],
     iv: &[u8],
     message_secret: &[u8],
     stanza_id: &str,
     event_creator_jid: &str,
     responder_jid: &str,
 ) -> Result<EventResponseMessage> {
-    // The IV length is validated downstream by decrypt_addon (try_into [u8; 12]).
+    ensure!(
+        iv.len() == GCM_IV_SIZE,
+        "iv must be {GCM_IV_SIZE} bytes, got {}",
+        iv.len()
+    );
     ensure!(
         message_secret.len() == MESSAGE_SECRET_SIZE,
         "message_secret must be {MESSAGE_SECRET_SIZE} bytes, got {}",
         message_secret.len()
     );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wacore/src/event.rs` around lines 54 - 75, Add an early IV-length check in
decrypt_event_response_with_secret so callers get a clear error instead of a
downstream slice conversion panic: validate iv.len() == 12 (the expected IV size
used by decrypt_addon/try_into) using ensure! similar to the existing
message_secret check (referencing MESSAGE_SECRET_SIZE) before calling
decrypt_addon; keep the error text descriptive like "iv must be 12 bytes, got
{}" and otherwise leave decrypt_addon and event_response_addon_ctx usage
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@wacore/src/event.rs`:
- Around line 54-75: Add an early IV-length check in
decrypt_event_response_with_secret so callers get a clear error instead of a
downstream slice conversion panic: validate iv.len() == 12 (the expected IV size
used by decrypt_addon/try_into) using ensure! similar to the existing
message_secret check (referencing MESSAGE_SECRET_SIZE) before calling
decrypt_addon; keep the error text descriptive like "iv must be 12 bytes, got
{}" and otherwise leave decrypt_addon and event_response_addon_ctx usage
unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b13a22af-00f8-4484-9f36-76045aee3748

📥 Commits

Reviewing files that changed from the base of the PR and between 3dffe6a and 1d23f53.

📒 Files selected for processing (2)
  • src/lib.rs
  • wacore/src/event.rs

@jlucaso1
jlucaso1 merged commit db62f7a into main Jun 8, 2026
11 checks passed
@jlucaso1
jlucaso1 deleted the feat/events-create-respond branch June 8, 2026 11:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant