Commit 5ed014b
fix(profile-picture): WA Web parity — port PR WhiskeySockets#2614 + add id/invite/persona_id/common_gid (#510)
* fix(profile-picture): WA Web parity — nest tctoken + add id/invite/persona_id/common_gid
Port of upstream WhiskeySockets#2614 + adicional gaps identified by
comparing our `profilePictureUrl` against WA Web (live source via CDP) and
whatsmeow source code. Brings our `<iq xmlns="w:profile:picture">` stanza
to byte-for-byte parity with the official client behavior.
What changes:
1. New `LIDMappingStore.getKnownLIDForPN(pn)` (port of PR WhiskeySockets#2614, literal).
Returns the LID for a PN **only if locally known** (cache or store) — never
triggers USync. Opportunistic call sites (profile-picture, etc.) use this
so the failure-to-resolve path does NOT fire a USync round-trip. USync-
on-profile-picture is not a behavior WA Web or whatsmeow emit; doing it
makes our traffic profile stand out and may serve as a ban signal.
2. New `buildTcTokenNode({ authState, jid, getLIDForPN })` helper in
tc-token-utils.ts. Same expiry / opportunistic-cleanup semantics as the
pre-existing `buildTcTokenFromJid`, but returns the single <tctoken>
BinaryNode (or undefined) instead of pushing into a content array. Callers
that need the token as a nested CHILD of another node use this; the older
`buildTcTokenFromJid` is kept intact for `presenceSubscribe` which uses
the sibling-array shape.
3. `profilePictureUrl` refactored to match WA Web's
`WASmaxOutProfilePictureGetRequest` mixin composition:
- tctoken is now nested as a CHILD of <picture> (was a sibling — verified
wrong against WA Web JS source: mergeStanzas(<picture>, <tctoken>)
places tctoken inside picture, NOT alongside it).
- tctoken resolver swapped to getKnownLIDForPN (no USync, PR WhiskeySockets#2614).
- New optional params accepted in `opts`:
* existingId → <picture id="..."> for 304-NotModified caching
(matches WA Web pictureId + whatsmeow ExistingID).
Saves a CDN re-fetch when the picture hasn't changed.
* invite → <picture invite="..."> for group-invite-link preview
(fetch a group's picture without joining). When set,
we skip the tctoken (the invite IS the authorization).
* personaId → <picture persona_id="..."> for Meta AI bot personas.
* commonGid → <picture common_gid="..."> required when the target's
privacy is "My contacts" and we share a group but are
not in their contacts — without it the server returns
401/403. Matches WA Web.
- Same callers using the old signature work unchanged (all new params are
in an optional `opts` object).
4. `presenceSubscribe` is intentionally NOT changed — it doesn't wrap the
tctoken inside a `<picture>` (no nesting question), and a USync miss
during presence-subscribe is far less common (we usually know the LID by
the time we subscribe). Kept on `buildTcTokenFromJid` + `getLIDForPN`.
Empirical validation — triple-confirmed before applying:
a) PR WhiskeySockets#2614 description (`fix: nest profile picture tctoken and avoid
usync on lookup`) plus cubic + coderabbit summaries.
b) whatsmeow's `Client.GetProfilePictureInfo` in user.go — pictureContent
is the `Content` of the picture node (Go semantics: child).
c) WA Web's live JS bundle, extracted via Chrome DevTools Protocol
(`Debugger.getScriptSource`). The mixin chain in
`WASmaxOutProfilePictureGetRequest` is:
smax("picture", attrs)
→ optionalMerge(mergeAddRequestMixin, ...)
→ optionalMerge(mergeTCTokenMixin, ...)
→ optionalMerge(mergeAvatarMixin, ...)
where `mergeStanzas(target, mixin)` places the mixin INSIDE target.
The tctoken is undeniably a child of <picture>.
Tests:
- Existing 72 tests in `tc-token.test.ts` continue to pass (no signature
changes to `buildTcTokenFromJid`).
- 6 new tests for `buildTcTokenNode` (valid token, missing, expired +
cleanup, no-wipe-when-missing, error swallow, return shape).
- Full TC token suite: 78/78 passing.
Out of scope (intentionally NOT touched):
- Carousel, lists, buttons, polls, view-once, biz quality_control,
useLegacyLock, the broader TC token issuance flow, LID↔PN batched, Phase
9 multi-DB, lidDbMigrated:false, the cacheMetricsInterval memory-leak
fix, schema migrations + statement cache + busy retry, presence-
subscribe path (covered separately above).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(lid-mapping): wrap getKnownLIDForPN in checkDestroyed + trackOperation (PR #510 review)
PR #510 review caught a P2 contract violation: every other public method on
`LIDMappingStore` calls `this.checkDestroyed()` at entry and runs its body
inside `this.trackOperation(...)`. The new `getKnownLIDForPN` (added in this
PR as part of the WA Web profile-picture parity port) was missing both
guards.
Why this matters:
- Without `checkDestroyed()`: the method runs after `destroy()` has been
called, when `this.keys` may already have been torn down. The first
`await this.keys.get(...)` then races UAF on the underlying signal key
store.
- Without `trackOperation()`: `destroy()` does not wait for this method's
in-flight I/O before clearing the mapping cache. A concurrent call can
observe a half-cleared cache (a `mappingCache.set(...)` against a Map
that `destroy()` is about to `clear()`) and silently return stale data
rather than failing fast.
Both threads (copilot + cubic) flagged the same issue, cubic with
confidence 9. The fix is identical to the suggestion both reviewers
provided: wrap the body in `this.trackOperation(async () => { … })`
preceded by `this.checkDestroyed()`.
Caller-side impact: none. `buildTcTokenNode` (the sole call site) has a
try/catch that swallows any throw — so the only effect a post-destroy
call has is "tctoken not attached" (same as the existing miss path), not
a crash.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* refactor(tc-token): extract resolveTcTokenForJid helper (PR #510 review)
PR #510 cubic review (Thread 3, P3) flagged that `buildTcTokenFromJid` and
`buildTcTokenNode` carried byte-for-byte identical retrieval / expiry /
opportunistic-cleanup pipelines — only the return shape differed (sibling
array vs single node). Extracted that pipeline into a private
`resolveTcTokenForJid(...)` helper so the two public builders become thin
adapters around a single source of truth.
Behavioral contract preserved (verified against the existing 78 tests):
- Both public builders keep their exact public signatures.
- `buildTcTokenFromJid` still mutates `baseContent` in place when a
token exists, and still returns `baseContent.length > 0 ? baseContent
: undefined` on every "no token" branch — same legacy behavior the
presenceSubscribe call site relies on.
- `buildTcTokenNode` still returns a single `BinaryNode` or `undefined` —
same shape profilePictureUrl uses for nesting under `<picture>`.
- The opportunistic expired-token wipe (with senderTimestamp
preservation) runs exactly once, in the helper, with the same write
shape as before. Missing-token entries are still NOT wiped.
- Key-store errors are still swallowed inside the helper; both public
builders fall back to their no-token branch on throw.
Why the two builders stay as separate public surfaces (instead of one):
- `presenceSubscribe` builds a `<presence>` whose content is the legacy
sibling-array shape; it cares about preserving `baseContent` when no
token exists. Forcing it to the single-node shape would require every
call site to special-case `undefined`.
- `profilePictureUrl` (port of PR WhiskeySockets#2614) needs the token nested as a
CHILD of `<picture>`. It MUST get a single node, not an array.
So the API split is meaningful; only the internal implementation was
duplicated. The helper kills the duplication without changing the surface.
Test impact: 0 changes to the test suite, 78/78 still pass. Same numbers
for the LID-mapping suite (15) and identity-change suite (23) that
indirectly exercise the same signal-store paths — 116/116 in the
combined run.
Out of scope (intentionally NOT touched):
- Carousel, lists, buttons, polls, view-once, biz quality_control,
useLegacyLock, the broader TC token issuance flow (only retrieval
helpers refactored), LID↔PN batched, Phase 9 multi-DB,
lidDbMigrated:false, the cacheMetricsInterval memory-leak fix, schema
migrations + statement cache + busy retry.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>1 parent 74052ce commit 5ed014b
4 files changed
Lines changed: 288 additions & 33 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
505 | 505 | | |
506 | 506 | | |
507 | 507 | | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
508 | 555 | | |
509 | 556 | | |
510 | 557 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
55 | 55 | | |
56 | 56 | | |
57 | 57 | | |
58 | | - | |
| 58 | + | |
59 | 59 | | |
60 | 60 | | |
61 | 61 | | |
| |||
96 | 96 | | |
97 | 97 | | |
98 | 98 | | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
99 | 107 | | |
100 | 108 | | |
101 | 109 | | |
| |||
784 | 792 | | |
785 | 793 | | |
786 | 794 | | |
787 | | - | |
788 | | - | |
789 | | - | |
| 795 | + | |
| 796 | + | |
| 797 | + | |
| 798 | + | |
| 799 | + | |
| 800 | + | |
| 801 | + | |
| 802 | + | |
| 803 | + | |
| 804 | + | |
| 805 | + | |
| 806 | + | |
| 807 | + | |
| 808 | + | |
| 809 | + | |
| 810 | + | |
| 811 | + | |
| 812 | + | |
| 813 | + | |
| 814 | + | |
| 815 | + | |
| 816 | + | |
| 817 | + | |
790 | 818 | | |
791 | | - | |
792 | | - | |
793 | | - | |
794 | | - | |
795 | | - | |
796 | | - | |
| 819 | + | |
| 820 | + | |
| 821 | + | |
| 822 | + | |
| 823 | + | |
| 824 | + | |
| 825 | + | |
| 826 | + | |
| 827 | + | |
| 828 | + | |
| 829 | + | |
797 | 830 | | |
798 | 831 | | |
799 | 832 | | |
800 | 833 | | |
801 | 834 | | |
802 | | - | |
803 | 835 | | |
804 | | - | |
805 | | - | |
| 836 | + | |
| 837 | + | |
| 838 | + | |
| 839 | + | |
| 840 | + | |
| 841 | + | |
| 842 | + | |
| 843 | + | |
| 844 | + | |
| 845 | + | |
| 846 | + | |
| 847 | + | |
| 848 | + | |
| 849 | + | |
| 850 | + | |
| 851 | + | |
| 852 | + | |
| 853 | + | |
806 | 854 | | |
807 | 855 | | |
808 | | - | |
809 | | - | |
| 856 | + | |
| 857 | + | |
| 858 | + | |
| 859 | + | |
| 860 | + | |
810 | 861 | | |
| 862 | + | |
| 863 | + | |
| 864 | + | |
811 | 865 | | |
812 | 866 | | |
813 | | - | |
814 | 867 | | |
815 | 868 | | |
816 | 869 | | |
817 | 870 | | |
818 | | - | |
| 871 | + | |
819 | 872 | | |
820 | 873 | | |
821 | 874 | | |
822 | 875 | | |
823 | | - | |
| 876 | + | |
824 | 877 | | |
825 | 878 | | |
826 | 879 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
101 | 101 | | |
102 | 102 | | |
103 | 103 | | |
104 | | - | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
105 | 142 | | |
106 | 143 | | |
107 | | - | |
108 | 144 | | |
109 | | - | |
| 145 | + | |
110 | 146 | | |
111 | 147 | | |
112 | 148 | | |
113 | 149 | | |
114 | 150 | | |
115 | 151 | | |
116 | 152 | | |
117 | | - | |
118 | | - | |
119 | | - | |
120 | 153 | | |
121 | 154 | | |
122 | 155 | | |
| |||
125 | 158 | | |
126 | 159 | | |
127 | 160 | | |
128 | | - | |
| 161 | + | |
129 | 162 | | |
130 | 163 | | |
131 | | - | |
132 | | - | |
133 | | - | |
134 | | - | |
135 | | - | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
136 | 169 | | |
137 | | - | |
138 | | - | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
139 | 190 | | |
140 | 191 | | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
141 | 216 | | |
142 | 217 | | |
143 | 218 | | |
| |||
0 commit comments