fix(pair): WA Web compliant pairing QR + companion_platform_{id,display}#593
fix(pair): WA Web compliant pairing QR + companion_platform_{id,display}#593
Conversation
Primary devices started rejecting the legacy 4-field QR payload (tulir/whatsmeow#1110, WhiskeySockets/Baileys#2460); WA Web's WAWebLinkDeviceQrcode.react now emits a 5th field carrying WAWebCompanionRegClientUtils.DEVICE_PLATFORM. Mirror that: - New `CompanionWebClientType` enum with `#[repr(i32)]` discriminants pinned to the WA Web wire values. - `companion_web_client_type_for_props` derives the QR client type from DeviceProps.PlatformType, falling through to OtherWebClient for non-web companions like WA Web does for unknown browser names. - `make_qr_data` now appends the client type; the high-level wrapper auto-derives from `device_props` so callers don't have to wire it through. `make_qr_data_with_client_type` is available for overrides. - `parse_qr_code` accepts legacy 4-field, current 5-field, the native- camera `https://wa.me/settings/linked_devices#` prefix, and the jknob-770 FAQ URL suffix; still rejects empty fields, malformed base64, and wrong key lengths.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughQR/pair flows now include a typed CompanionWebClientType derived from DeviceProps. A new public Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Device as Device (DeviceProps)
participant Reg as companion_reg
participant App as App / PairUtils
participant QR as QR Image
participant Scanner as Mobile Scanner
Device->>Reg: companion_web_client_type_for_props(props)
Reg-->>Device: CompanionWebClientType
Device->>App: make_qr_data_with_client_type(device_state, ref, client_type)
App-->>QR: emit QR payload ("ref,noise,identity,adv,client_type")
Scanner->>QR: scan
Scanner->>App: parsed_string -> parse_qr_code(qr_body)
App->>App: accept 4-field or 5+ field, validate base64/keys
App-->>Scanner: (ref, noise, identity, adv, client_type?)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@wacore/src/companion_reg.rs`:
- Around line 13-25: The enum CompanionWebClientType is using manual
discriminants instead of the wire-format attributes; update the enum to derive
WireEnum and the standard set of derives (Debug, Clone, Copy, Default,
PartialEq, Eq, Hash, WireEnum), add #[wire(kind = "int")] on the enum, remove
all `= N` discriminants, annotate each variant with #[wire = N] corresponding to
its current numeric value and mark the default variant with #[wire_default]
(e.g., Unknown gets #[wire_default] and #[wire = 0]); keep the same variant
names and ordering so the wire values remain unchanged.
🪄 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: e42747c3-bdf6-4406-af7b-4c261986173c
📒 Files selected for processing (4)
src/pair.rswacore/src/companion_reg.rswacore/src/lib.rswacore/src/pair.rs
Benchmark Results1 regression(s) detected (>2% threshold):
66 unchanged benchmark(s)
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bd8060610f
ℹ️ 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".
| #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)] | ||
| #[repr(i32)] | ||
| pub enum CompanionWebClientType { |
There was a problem hiding this comment.
Derive this protocol enum with WireEnum
CompanionWebClientType is a wire-facing protocol enum, but it is implemented with #[repr(i32)] plus manual Display instead of #[derive(WireEnum)], which violates the repo convention in /workspace/whatsapp-rust/AGENTS.md (“Every protocol enum uses #[derive(WireEnum)] and #[wire = ...] is the single source of truth”). Keeping this as a manual special case makes future wire-tag updates easy to desynchronize from the rest of the protocol layer and can silently produce wrong QR wire values when enum definitions evolve.
Useful? React with 👍 / 👎.
Two pre-existing bugs in commit 63210f7's `derive_companion_platform`: 1. **id used the wrong enum**. The wire field `companion_platform_id` carries `WAWebCompanionRegClientUtils.DEVICE_PLATFORM` per `docs/captured-js/WAWeb/Alt/DeviceLinkingIq.js:35`, which is `CompanionWebClientType` (10 values). We were formatting `wa::device_props::PlatformType as i32`. The enums collide on indices 1 and 7 only — every other browser produced the wrong wire integer (Firefox sent "2" instead of "3", Edge "6" instead of "2", etc.) and mobile DeviceProps emitted values like "16" that aren't in the WA Web enum at all. 2. **display violated server validation**. whatsmeow's `PairPhone` doc states the server validates `companion_platform_display` strictly as `Browser (OS)` with one of {Chrome, Edge, Firefox, IE, Opera, Safari} and 400s otherwise. We were emitting "Android (Android)", "iPhone (iOS)", "iPad (iPadOS)" for mobile DeviceProps, breaking pair-code for any non-browser platform_type. Fixes: - `CompanionWebClientType` is now a `WireEnum` int with `Unrecognized(i32)` fallback (consistent with `TempBanReason` / `ConnectFailureReason`), unblocking the bridge's TS codegen and giving forward-compat `From<i32>`. - `derive_companion_platform` returns `(CompanionWebClientType, String)` and shares its derivation with the QR pairing flow — single source of truth. - New `companion_browser_name` returns one of the 6 server-valid browsers, falling back to "Chrome" for non-browser variants (what WA Web's `info().name` would emit even from Electron). - New `companion_platform_display(ct, os)` always emits `<Browser> (<OS>)`, substituting "Linux" for empty OS to avoid bare browser names which the server also rejects. - `PairCodeOptions::platform_id` is now `Option<CompanionWebClientType>` (correct type for the wire field). - `PairCodeOptions::platform_display` is dropped — WA Web has no equivalent override and any free-string value risks server rejection. Display is now a pure function of `(client_type, os)`; users customise via `DevicePropsOverride::with_os(...)` plus `platform_id`. - `platform_friendly_name` removed — only caller was the buggy derivation and its mixed browser/non-browser table was the bug 2 vector. Tests cover wire-integer correctness for every proto PlatformType, total display validity (browser ∈ valid set, always parenthesised OS), and parity between the pair-code id and the QR id for the same DeviceProps.
|
@claude review nitpicky |
|
Claude finished @jlucaso1's task in 2m 24s —— View job Nitpicky Review of PR #593
Overall this is a well-structured PR with excellent test coverage. The CodeRabbit WireEnum finding was already addressed in the follow-up commit. Here are the nitpicks:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7a7a84bd08
ℹ️ 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".
| /// Override for `companion_platform_id`. `None` auto-derives from | ||
| /// `Device.device_props.platform_type` via the same mapping used by the | ||
| /// pairing QR (`companion_web_client_type_for_props`). | ||
| pub platform_id: Option<CompanionWebClientType>, |
There was a problem hiding this comment.
Re-export CompanionWebClientType in public API
PairCodeOptions::platform_id now uses CompanionWebClientType, but whatsapp-rust does not re-export that enum (the public layer only re-exports PairCodeOptions in src/pair_code.rs), so downstream users depending only on whatsapp-rust can no longer set platform_id without adding a new direct dependency on wacore. This turns an intentional override path into an effectively inaccessible API for existing consumers and is a regression from the previous PlatformType-based field that was reachable via whatsapp_rust::waproto.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@wacore/src/companion_reg.rs`:
- Around line 32-36: The Unrecognized(i32) variant of CompanionWebClientType is
intended decode-only but is currently exposed via PairCodeOptions::platform_id
and serialized by platform_id.to_string(), allowing callers to emit arbitrary
integers; fix by preventing Unrecognized from being produced or serialized:
either split the wire-only fallback into a private/internal enum used for
From<i32> and keep the public CompanionWebClientType without Unrecognized, or
ensure any public accessor used for generation (e.g.,
PairCodeOptions::platform_id, CompanionWebClientType::to_string()/Display)
rejects or maps Unrecognized to a safe value/Error before generation (returning
a Result or panicking/asserting as appropriate) so Unrecognized cannot be
serialized as companion_platform_id.
In `@wacore/src/pair_code.rs`:
- Around line 121-124: PairCodeOptions exposes a platform_id:
Option<CompanionWebClientType> but CompanionWebClientType isn’t re-exported from
the top-level crate, making the field unusable for downstream consumers; fix
this by re-exporting CompanionWebClientType alongside PairCodeOptions (e.g., add
a public re-export like `pub use wacore::pair_code::CompanionWebClientType;` in
the same module where PairCodeOptions is re-exported) so callers can import the
type from the crate root and set PairCodeOptions::platform_id without reaching
into wacore.
🪄 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: b55dd7be-d272-47b7-8200-1c789f714f92
📒 Files selected for processing (3)
src/pair_code.rswacore/src/companion_reg.rswacore/src/pair_code.rs
- `companion_web_client_type_for_platform` is `const fn` again (lost it during the WireEnum migration). - `PairUtils::make_qr_data` takes `ref_str: &str` instead of `String` and formats directly via `format!`, dropping the intermediate `client_type.to_string()` allocation. Top-level wrappers updated to match. - Soften "server validates / 400s" claims in docstrings to "whatsmeow's PairPhone doc reports …" since the captured WA Web JS doesn't show the validation directly. - Document that WA Web emits the native-camera prefix and FAQ-URL suffix mutually exclusively; `parse_qr_code` is permissive on purpose for e2e replay. - Drop unused `adv_secret_b64` variable in `parse_qr_code`. - Add docstring to `make_qr_data_with_client_type`. - Add direct `Display` test for `CompanionWebClientType`, including the `Unrecognized(i32)` fallback paths (positive and negative integers).
…ection
WA Web's RegClientUtils.js short-circuits to UWP when isWindows is true,
but that flag is WAWebEnvironment's gkx("4112") — a GateKeeper
experiment that fires only when the JS is bundled inside the Microsoft
Store/UWP shell, not when the user's OS is Windows. Document why our
mapping doesn't replicate that branch (no analogous host-shell detection
in a Rust library; the user sets PlatformType::Uwp explicitly to claim
UWP). Same explanation applies to Electron — never emitted by WA Web's
runtime selector either, expressed via PlatformType::Desktop here.
There was a problem hiding this comment.
♻️ Duplicate comments (1)
wacore/src/pair_code.rs (1)
120-124:⚠️ Potential issue | 🟡 MinorCompanionWebClientType needs to be re-exported for downstream users.
This was flagged in a previous review and remains valid.
PairCodeOptionsis publicly exported, and itsplatform_idfield is typed asOption<CompanionWebClientType>. Downstream users who want to set this field can't importCompanionWebClientTypewithout reaching intowacoredirectly.Add a re-export in
src/lib.rsor alongsidePairCodeOptions:pub use wacore::companion_reg::CompanionWebClientType;#!/bin/bash # Verify if CompanionWebClientType is already re-exported from the main crate rg -n --type=rust 'pub use.*CompanionWebClientType' src/🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@wacore/src/pair_code.rs` around lines 120 - 124, PairCodeOptions exposes platform_id: Option<CompanionWebClientType> but CompanionWebClientType is not re-exported for downstream users; add a public re-export for CompanionWebClientType (e.g., pub use wacore::companion_reg::CompanionWebClientType) alongside the public API so callers can import CompanionWebClientType without depending on wacore, ensuring the symbol is available where PairCodeOptions is exposed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@wacore/src/pair_code.rs`:
- Around line 120-124: PairCodeOptions exposes platform_id:
Option<CompanionWebClientType> but CompanionWebClientType is not re-exported for
downstream users; add a public re-export for CompanionWebClientType (e.g., pub
use wacore::companion_reg::CompanionWebClientType) alongside the public API so
callers can import CompanionWebClientType without depending on wacore, ensuring
the symbol is available where PairCodeOptions is exposed.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 316b0841-9387-48a6-afd4-fc25594c0c00
📒 Files selected for processing (4)
src/pair.rswacore/src/companion_reg.rswacore/src/pair.rswacore/src/pair_code.rs
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
wacore/src/companion_reg.rs (1)
32-43:⚠️ Potential issue | 🟠 MajorWe can’t let
Unrecognized(i32)leak into outbound wire valuesLine 41 serializes every variant as raw code, so a public
CompanionWebClientType::Unrecognized(42)can become"42"in outbound pairing fields and trigger server 400s. Keep fallback decode-only on the wire-producing path.Proposed hardening
+impl CompanionWebClientType { + pub const fn outbound_code(self) -> i32 { + match self { + Self::Unrecognized(_) => Self::Unknown.code(), + _ => self.code(), + } + } +} + impl std::fmt::Display for CompanionWebClientType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.code().fmt(f) + self.outbound_code().fmt(f) } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@wacore/src/companion_reg.rs` around lines 32 - 43, The current Display impl for CompanionWebClientType calls self.code() for every variant and thus serializes Unrecognized(i32) into a raw numeric wire value; change fmt so it only formats known variants (by delegating to self.code() or matching each real variant) and explicitly refuse to format CompanionWebClientType::Unrecognized(_) (e.g., return Err(std::fmt::Error) or otherwise avoid emitting a numeric string) so the fallback remains decode-only and cannot be sent on the wire; update the impl std::fmt::Display for CompanionWebClientType to match on self and handle Unrecognized specially instead of calling self.code() unconditionally.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@wacore/src/companion_reg.rs`:
- Around line 71-79: Trim the lengthy comment block in companion_reg.rs (the
multi-line doc comment explaining WA Web runtime selector) to a concise "why"
summary and move the detailed GateKeeper/WAWeb wiring and rationale into a
separate ADR or docs file; keep a short note referencing PlatformType::Uwp and
Desktop/Electron behavior in the code comment and add a TODO pointing to the new
ADR so callers can find the full explanation later.
---
Duplicate comments:
In `@wacore/src/companion_reg.rs`:
- Around line 32-43: The current Display impl for CompanionWebClientType calls
self.code() for every variant and thus serializes Unrecognized(i32) into a raw
numeric wire value; change fmt so it only formats known variants (by delegating
to self.code() or matching each real variant) and explicitly refuse to format
CompanionWebClientType::Unrecognized(_) (e.g., return Err(std::fmt::Error) or
otherwise avoid emitting a numeric string) so the fallback remains decode-only
and cannot be sent on the wire; update the impl std::fmt::Display for
CompanionWebClientType to match on self and handle Unrecognized specially
instead of calling self.code() unconditionally.
🪄 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: 37546352-4203-4f97-bd47-8520268b2525
📒 Files selected for processing (1)
wacore/src/companion_reg.rs
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 506a0f93eb
ℹ️ 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".
| pub fn make_qr_data_with_client_type( | ||
| store: &crate::store::Device, | ||
| ref_str: &str, | ||
| client_type: CompanionWebClientType, |
There was a problem hiding this comment.
Re-export companion enum for QR override API
make_qr_data_with_client_type is public but its parameter type (CompanionWebClientType) is not exposed by whatsapp-rust, so downstream crates that depend only on whatsapp-rust cannot call this new override path without adding a direct wacore dependency. That makes the advertised override seam effectively unusable for current consumers of the high-level crate.
Useful? React with 👍 / 👎.
…se comments - Drop WireEnum on CompanionWebClientType, revert to #[repr(i32)]. The fallback Unrecognized(i32) was reachable via PairCodeOptions::platform_id and serialised by Display, letting callers emit arbitrary wire integers. Since this enum is encode-only (we never decode wire ints), the WireEnum pattern doesn't apply: all 10 valid values are nameable as variants and the user can't escape the spec. - Re-export CompanionWebClientType from src/pair.rs and src/pair_code.rs so downstream callers can set PairCodeOptions::platform_id without reaching into wacore directly. - Trim doc comments across companion_reg.rs / pair.rs / pair_code.rs: keep the why, drop the long-form rationale.
Forgotten in the previous commit. Downstream consumers can now write
`use whatsapp_rust::pair_code::{CompanionWebClientType, PairCodeOptions}`
or `use whatsapp_rust::pair::CompanionWebClientType` without reaching
into wacore directly.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
wacore/src/companion_reg.rs (1)
6-21:⚠️ Potential issue | 🟠 MajorPut the wire mapping back on the enum itself.
This enum sits on the protocol boundary.
#[repr(i32)]plusas i32makes the wire contract implicit again, and this crate already has a stronger pattern for that. We should keep the numeric mapping attached to each variant with#[derive(WireEnum)]and#[wire = N].Suggested fix
-#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)] -#[repr(i32)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, WireEnum)] +#[wire(kind = "int")] pub enum CompanionWebClientType { #[default] - Unknown = 0, - Chrome = 1, - Edge = 2, - Firefox = 3, - Ie = 4, - Opera = 5, - Safari = 6, - Electron = 7, - Uwp = 8, - OtherWebClient = 9, + #[wire_default] + #[wire = 0] + Unknown, + #[wire = 1] + Chrome, + #[wire = 2] + Edge, + #[wire = 3] + Firefox, + #[wire = 4] + Ie, + #[wire = 5] + Opera, + #[wire = 6] + Safari, + #[wire = 7] + Electron, + #[wire = 8] + Uwp, + #[wire = 9] + OtherWebClient, }As per coding guidelines "Every protocol enum must use
#[derive(WireEnum)]; the#[wire = "..."]or#[wire = NUM]attribute is the single source of truth for each variant's wire value; do not also deriveserde::Serialize/Deserializeor add#[serde(rename_all)]".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@wacore/src/companion_reg.rs` around lines 6 - 21, The enum CompanionWebClientType must declare its wire values explicitly: remove #[repr(i32)] and instead add #[derive(WireEnum)] to CompanionWebClientType and annotate each variant with its wire mapping (e.g., #[wire = 0] on Unknown, #[wire = 1] on Chrome, etc.), keeping the #[default] on Unknown; also update any call sites that relied on casting (as i32) to use the WireEnum-provided conversion helpers if available.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@wacore/src/pair.rs`:
- Around line 292-299: The QR parsing currently only checks pairing_ref,
dut_noise_pub_b64, dut_identity_pub_b64, and parts[3], so a 5-field WA Web QR
with an empty fifth field slips through; update the validation in the function
that contains pairing_ref / dut_noise_pub_b64 / dut_identity_pub_b64 /
parts[...] to also reject an empty fifth segment when parts.len() == 5 by
validating parts[4] (the client_type) is non-empty and returning the same
anyhow::anyhow! error on failure, and add a regression test that constructs a
5-field QR where the fifth field is empty and asserts the parser returns an
error.
---
Duplicate comments:
In `@wacore/src/companion_reg.rs`:
- Around line 6-21: The enum CompanionWebClientType must declare its wire values
explicitly: remove #[repr(i32)] and instead add #[derive(WireEnum)] to
CompanionWebClientType and annotate each variant with its wire mapping (e.g.,
#[wire = 0] on Unknown, #[wire = 1] on Chrome, etc.), keeping the #[default] on
Unknown; also update any call sites that relied on casting (as i32) to use the
WireEnum-provided conversion helpers if available.
🪄 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: c6eb10ed-41c8-4c3c-9d30-9b6fcb9b44ab
📒 Files selected for processing (4)
src/pair.rswacore/src/companion_reg.rswacore/src/pair.rswacore/src/pair_code.rs
…ths from comments - Promote the iOS native-camera QR prefix to a public constant (`wacore::companion_reg::NATIVE_CAMERA_DEEP_LINK_PREFIX`, re-exported from `whatsapp_rust::pair`) so callers can build a deep-link URL by prepending it to `make_qr_data` output. - `parse_qr_code` now uses the same constant (single source of truth). - Drop the `docs/captured-js/...` paths from comments per project rule .claude/rules/code-style.md — reference WA Web modules by name only.
5-field WA Web QR with an empty client_type slot would slip past the existing parts[3] non-empty check. Extend validation to all parts beyond the first four so any empty segment in the legacy or compliant form is rejected with the same error. Adds a regression test that builds a 5-field QR with an empty 5th segment and asserts the parser fails.
Summary
Two bugs in the pairing flows, fixed together because they share a derivation source.
QR pairing format (whatsmeow#1110, Baileys#2460 backport)
Primary devices started rejecting the legacy 4-field QR. WA Web's
WAWebLinkDeviceQrcode.reactnow appendsWAWebCompanionRegClientUtils.DEVICE_PLATFORMas a 5th field. Mirror that.CompanionWebClientTypeenum, 10 wire values matching WA Web exactly, plusUnrecognized(i32)fallback. Derived viaWireEnumint (same pattern asTempBanReason/ConnectFailureReason).make_qr_dataauto-derives the client type fromdevice_props.platform_type.make_qr_data_with_client_typeis the override seam.parse_qr_codeaccepts the legacy 4-field form, the new 5-field form, the native-camerahttps://wa.me/settings/linked_devices#prefix, and the jknob-770 FAQ URL suffix. Empty fields, malformed base64, and wrong key lengths still rejected.companion_platform_{id,display}correctness (regression from 63210f7)The pair-code IQ derivation introduced in 63210f7 had two wire-correctness bugs surfaced during the audit:
companion_platform_idwas the wrong enum's integer. WA Web sendsWAWebCompanionRegClientUtils.DEVICE_PLATFORM(CompanionWebClientType, 10 values) perdocs/captured-js/WAWeb/Alt/DeviceLinkingIq.js:35. We were formattingwa::device_props::PlatformType as i32(proto, 25+ values). The two enums collide on indices 1 and 7 only. Firefox sent "2" instead of "3", Edge "6" instead of "2", IE "3" instead of "4", and mobile DeviceProps sent values like "16" outside the WA Web range entirely.companion_platform_displayviolated server validation. whatsmeow'sPairPhonedoc states the server validates this field strictly asBrowser (OS)with browser ∈ {Chrome, Edge, Firefox, IE, Opera, Safari} and returns 400 otherwise. We were emittingAndroid (Android),iPhone (iOS),iPad (iPadOS)for mobile DeviceProps, breaking pair-code for any non-browser platform.Fixes:
derive_companion_platformreturns(CompanionWebClientType, String)and shares its derivation with the QR flow. Single source of truth.companion_browser_namereturns one of the 6 server-valid browsers, falling back toChromefor non-browser variants. That's what WA Web'sinfo().nameemits even from Electron contexts.companion_platform_displayalways emits<Browser> (<OS>), substitutingLinuxfor empty OS since WA Web never sends bare browser names.Breaking changes (pre-1.0, intentional)
wacore::pair_code::derive_companion_platform:(String, String)→(CompanionWebClientType, String)wacore::pair_code::resolve_companion_platform: same return-type changewacore::pair_code::PairCodeOptions::platform_id:Option<wa::device_props::PlatformType>→Option<CompanionWebClientType>wacore::pair_code::PairCodeOptions::platform_display: removed (WA Web has no equivalent override; any free string risks server 400)wacore::pair_code::platform_friendly_name: removed (only caller was the buggy derivation; the mixed browser/non-browser name table was the bug 2 vector)wacore::pair::PairUtils::make_qr_data: now requiresCompanionWebClientTypeparameterThe whatsapp-rust-bridge consumer only uses
PairCodeOptions { phone_number, custom_code, ..Default::default() }and reads the QR string viaEvent::PairingQrCode. Both work transparently with the new behaviour.Test plan
cargo test -p wacore --lib: 610 tests pass, includingcompanion_regtests: wire codes match WA Web,From<i32>round-trip,Unrecognizedfallback, mapping totality, browser-name validity, display invariantspair::tests: QR 5-field format, retro-compat 4-field parse, native-camera prefix, FAQ suffix, round-trip auto-derive for every client typepair_code::tests: per-browser correct wire, mobile → OtherWebClient + Chrome display fallback, total proto-PlatformType scan asserting display always uses one of 6 valid browsers, pair-code/QR id parity, override semanticscargo clippy --workspace --exclude e2e-tests --testscleancargo fmt --all -- --check