@vinikjkkj/wa-xml 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +273 -0
  2. package/index.d.ts +11704 -0
  3. package/index.js +146 -0
  4. package/package.json +52 -0
package/README.md ADDED
@@ -0,0 +1,273 @@
1
+ # @vinikjkkj/wa-xml
2
+
3
+ WhatsApp Web XML/stanza schemas — per-IQ-operation request + response trees
4
+ (tag, attrs, children, content) recovered from the Smax RPC modules, plus
5
+ the server-initiated stanza dispatch table (receipt / notification /
6
+ chatstate / presence / call / stream:error / ib / success / failure /
7
+ error) reconstructed from the imperative `WAWebHandle*` handlers.
8
+
9
+ ```sh
10
+ npm i @vinikjkkj/wa-xml
11
+ ```
12
+
13
+ ```ts
14
+ import { WA_XML_OPERATIONS, WA_XML_STANZAS } from '@vinikjkkj/wa-xml'
15
+ import type {
16
+ WaXmlOperations,
17
+ WaXmlOperationKey,
18
+ WaXmlStanzaEntry
19
+ } from '@vinikjkkj/wa-xml'
20
+
21
+ WA_XML_OPERATIONS.CompanionHello
22
+ // → {
23
+ // module: 'WASmaxMdCompanionHelloRPC',
24
+ // opName: 'CompanionHello',
25
+ // rootTag: 'iq',
26
+ // xmlns: 'md',
27
+ // type: 'set',
28
+ // requestModule: 'WASmaxOutMdCompanionHelloRequest',
29
+ // responseModules: [
30
+ // 'WASmaxInMdCompanionHelloResponseNotifyCompanion',
31
+ // 'WASmaxInMdCompanionHelloResponseError'
32
+ // ]
33
+ // }
34
+
35
+ WA_XML_STANZAS.notification
36
+ // → {
37
+ // tag: 'notification',
38
+ // discriminator: 'type',
39
+ // variants: {
40
+ // picture: { module: 'WAWebHandleProfilePicNotification', method: 'handleProfilePicNotificationJob' },
41
+ // devices: { module: 'WAWebHandleDeviceNotification', method: 'handleDevicesNotification' },
42
+ // server_sync: { module: 'WAWebHandleServerSyncNotification', method: 'handleServerSyncNotification' },
43
+ // …23 more
44
+ // }
45
+ // }
46
+ ```
47
+
48
+ The full per-op tree (request shape + each response variant) plus per-stanza
49
+ trees are in
50
+ [`index.json`](https://github.com/vinikjkkj/wa-spec/blob/master/packages/xml/index.json)
51
+ and as TypeScript literal types in `WaXmlOperations` / `WaXmlStanzas`.
52
+
53
+ ## What's in here
54
+
55
+ WhatsApp Web sends and receives binary-encoded XMPP-like "stanzas"
56
+ (`WapNode` trees) over the encrypted noise tunnel. The package covers two
57
+ surfaces — declarative IQ RPC operations and the imperative dispatch table
58
+ for everything else the client receives.
59
+
60
+ ### IQ operations (declarative Smax pipeline)
61
+
62
+ For *IQ-style* RPC operations the client code is fully declarative in two
63
+ places:
64
+
65
+ | Module pattern | Role |
66
+ |---|---|
67
+ | `WASmaxOut*Request` | request builder — calls `smax(tag, attrs, ...children)` (`smax === WAWap.wap`) with typed coercer attrs (`WAWap.CUSTOM_STRING`, `INT`, `LONG_INT`, `USER_JID`, `DEVICE_JID`, `GROUP_JID`, `NEWSLETTER_JID`, …) optionally wrapped in `WASmaxAttrs.OPTIONAL(coercer, val)` / `OPTIONAL_LITERAL(lit, cond)`. The sentinel `DROP_ATTR` drops an attribute at send time. |
68
+ | `WASmaxIn*Response*` | response parser — uses the `WASmaxParseUtils.*` primitives (`assertTag`, `attrString`, `attrInt`, `attrIntRange`, `attrStringEnum`, `optionalChildWithTag`, `mapChildrenWithTag(...,min,max,...)`, `contentString`, `contentBytes`, …) to decode the server response. |
69
+ | `WASmax*RPC` | binding — imports one Out + N In parsers, sends via `WAComms.sendSmaxStanza`, returns the first parser variant whose `.success` is true (typically `Success` / `Ack`/`Nack` / `ClientError` / `ServerError`). |
70
+
71
+ This extractor walks every `WASmax*RPC` module across the daily WA Web
72
+ bundle and reconstructs the request schema + every response variant's schema
73
+ as a tree of nodes. Mixin wrappers (`*BaseIQ<Type>RequestMixin`, etc.) are
74
+ followed to recover the iq attrs they inject (`id: generateId()`, `type:
75
+ "get"|"set"`).
76
+
77
+ ### Non-IQ stanzas (server-initiated, imperative dispatch)
78
+
79
+ Everything else the client *receives* — `receipt`, `notification`,
80
+ `chatstate`, `presence`, `ib`, `stream:error`, `success`, `failure`,
81
+ `call`, `error`, `xmlstreamend` — is routed through the dispatch table at
82
+ `WAWebCommsHandleLoggedInStanza`. The outer switch keys on the stanza's
83
+ root tag; `receipt` + `notification` have a nested switch on the `type`
84
+ attribute that fans out into ~25 handler modules.
85
+
86
+ Each handler is a function exported from a `WAWebHandle*` module. Three
87
+ parsing styles in the wild:
88
+
89
+ | Style | Recognition |
90
+ |---|---|
91
+ | `WADeprecatedWapParser` factory at module top | scanned first — its callback is the canonical parse fn |
92
+ | Inline ParsableWapNode method chain in the exported handler | walked via `.attrString` / `.maybeChild` / `.forEachChildWithTag` recognition |
93
+ | Delegation to a Smax `receive*RPC` | recorded as `delegatesToRPC` — schema lives in `WA_XML_OPERATIONS` |
94
+
95
+ ## What's published
96
+
97
+ | File | Format | Use case |
98
+ |---|---|---|
99
+ | `index.js` | CommonJS | Runtime `WA_XML_OPERATIONS` + `WA_XML_STANZAS` — frozen per-op summaries + the non-IQ dispatch table |
100
+ | `index.d.ts` | TS declarations | Per-op literal-typed request + response trees, per-stanza tree types, and the umbrella maps |
101
+
102
+ A raw IR file (`index.json`) is also produced by the extractor with the
103
+ fully decoded trees — see
104
+ [`packages/xml/index.json`](https://github.com/vinikjkkj/wa-spec/blob/master/packages/xml/index.json)
105
+ for non-TS consumers (diff tools, codegen, other languages).
106
+
107
+ ### IR node shape
108
+
109
+ ```jsonc
110
+ {
111
+ "tag": "iq",
112
+ "attrs": {
113
+ "<name>": {
114
+ "type": "literal" | "string" | "int" | "longInt" | "bool" | "bytes"
115
+ | "jid" | "userJid" | "deviceJid" | "groupJid" | "newsletterJid"
116
+ | "broadcastJid" | "callJid" | "phoneUserJid" | "lidUserJid"
117
+ | "phoneDeviceJid" | "lidDeviceJid" | "stanzaId" | "smaxId"
118
+ | "callId" | "enum" | "unknown",
119
+ "optional"?: true, // attr may be absent (`OPTIONAL(...)` / parser `optional(...)`)
120
+ "value"?: "...", // literal value (type: "literal")
121
+ "enumRef"?: "...", // identifier of the enum map (type: "enum") — kept for traceability
122
+ "enumValues"?: ["a","b"], // resolved enum value set (type: "enum") — inlined by the post-pass resolver
123
+ "generated"?: true, // request-side: client-generated stanza id
124
+ "arg"?: "..." // request-side: parameter name the value came from
125
+ }
126
+ },
127
+ "children": [ /* recursive */ ],
128
+ "content"?: { // leaf-level binary/text content (no children)
129
+ "type": "string" | "bytes" | "int" | "enum",
130
+ "optional"?: true,
131
+ "min"?: 0, "max"?: 1024
132
+ },
133
+ "min"?: 0, "max"?: 19999 // child-level cardinality (set by mapChildrenWithTag / WASmaxChildren.*)
134
+ }
135
+ ```
136
+
137
+ `appliedMixin` and `__helper` annotations are present in `index.json` when a
138
+ node was assembled from a mixin merge or a `WASmaxChildren.REPEATED_CHILD` /
139
+ `OPTIONAL_CHILD` helper. Some complex builders are still emitted as
140
+ `{ "__unknown": "<source snippet>" }` — these are surfaced for visibility and
141
+ will narrow as more helpers get recognised.
142
+
143
+ ## Coverage
144
+
145
+ From the latest bundle — **100% of recognised stanzas decoded** (every
146
+ `WASmax*RPC` registration AND every entry in
147
+ `WAWebCommsHandleLoggedInStanza` + `WAWebCommsHandleMessagingStanza`'s
148
+ dispatch table accounted for; no `unknown` attrs / children remain).
149
+
150
+ **IQ operations:**
151
+
152
+ - **121 RPC operations** (every `WASmax*RPC` registration — both
153
+ client-initiated `send*RPC` AND server-initiated `receive*RPC`)
154
+ - **102 request trees** + **incoming-stanza parsers** (the receive-RPCs
155
+ carry their inbound parser as a `WASmaxIn*Request` dep — treated as a
156
+ sole response variant named `Request`)
157
+ - **285 response + incoming trees** decoded across `Success` / `Ack` /
158
+ `Nack` / `ClientError` / `ServerError` / `Error` / `Request` variants
159
+ - **3541 typed attributes** + **1281 nested child entries** + **325 content
160
+ leaves** with concrete types (`string`, `int`, `longInt`, `bytes`, `jid`,
161
+ `userJid`, `lidUserJid`, `deviceJid`, `groupJid`, `newsletterJid`,
162
+ `broadcastJid`, `callJid`, `stanzaId`, `callId`, `enum`, `literal`)
163
+ - **89/89 enum value sets resolved** in-place — every `attrStringEnum` /
164
+ `attrEnum` / `contentStringEnum` reference traces back to its module-level
165
+ table (Smax `WASmaxIn*Enums`, handler-side `Common`, or wrapped
166
+ `$InternalEnum` / `$Mirrored` factory) and surfaces as a concrete
167
+ `enumValues: ["a","b",…]` array next to `enumRef`
168
+ - **Disjunction unions** (`presence` has 5 variants, `chatstate` has
169
+ several mutually-exclusive state types) captured via the `errorMixinDisjunction`
170
+ pattern — sibling parsers' attrs are merged onto the same node and
171
+ marked optional, with `__variants: [...]` listing the discriminator names
172
+ - **Three parser helpers honoured**: `WASmaxParseUtils` (generic),
173
+ `WASmaxParseJid` (typed JID accessors), `WASmaxParseReference` (path
174
+ walking) — schemas from all three contribute to the same tree
175
+ - **34 unique xmlns** namespaces represented (`abt`, `bot`, `blocklist`,
176
+ `disappearing_mode`, `encrypt`, `fb:thrift_iq`, `md`, `newsletter`,
177
+ `optoutlist`, `passive`, `privacy`, `privatestats`, `spam`, `status`,
178
+ `tos`, `usync`, `urn:xmpp:whatsapp:account`, `urn:xmpp:whatsapp:push`,
179
+ `waffle`, `w:biz` (+ sub-namespaces), `w:comms`, `w:g2`, `w:m`, `w:mex`,
180
+ `w:p`, `w:pay`, `w:profile:picture`, `w:stats`, `w:sync:app:state`)
181
+
182
+ **Non-IQ stanzas:**
183
+
184
+ - **13 root tags** dispatched (`receipt`, `notification`, `chatstate`,
185
+ `presence`, `ib`, `stream:error`, `failure`, `success`, `call`, `error`,
186
+ `xmlstreamend`, `message`, `status`)
187
+ - **37 sub-variants** under `receipt.type` + `notification.type` +
188
+ `message.condition`
189
+ - **46 handler trees** extracted directly (full attrs + children +
190
+ content) — the OO walker handles ParsableWapNode methods, raw
191
+ `e.attrs.*` / `e.content` access, lexically-scoped iterator callbacks,
192
+ helper-function inlining with multi-arg call-site matching (so handlers
193
+ that pass the stanza as 2nd arg — `p(r, e)` — get correctly walked),
194
+ AND constant-string resolution (`forEachChildWithTag(o("X").INFO_TYPE.DIRTY, …)`
195
+ traces the `Object.freeze({DIRTY:"dirty",…})` back to its literal)
196
+ - **Cross-module parser delegation** auto-inlined: handlers that delegate
197
+ to `WAWebParse*` / `WAWeb*Parser` / `WAWebCommonParsers*` modules (named-
198
+ accessor `.parseXxx(arg)` OR default-callable `r("M")(arg)`) get the
199
+ delegated parser's attrs/children merged onto the right scope var, with
200
+ rebinding-aware target resolution (an `l` reassigned across if-branches
201
+ to `e.child("remove")` then `e.child("verified_name")` correctly attaches
202
+ each parser's tree to its own synthetic child)
203
+ - **Multi-delegate Smax handlers** auto-inlined from `WA_XML_OPERATIONS`:
204
+ `chatstate`, `presence`, `notification/newsletter`, `notification/psa/{surfaces,reset_smb_*}`,
205
+ `notification/w:gp2`, `status`, `message/from-is-newsletter` get the
206
+ Smax-RPC schema grafted; multi-RPC delegates (`link_code_companion_reg`,
207
+ `hosted`) get the union under `__unionOfDelegates`. The original
208
+ `delegatesToRPC` ref is preserved for traceability.
209
+ - **Dangling incoming RPC grafting**: `notification/business` children
210
+ (`ctwa_suggestion`, `mm_campaign`, `wa_ad_account_nonce`, `privacy`) +
211
+ `ib/client_expiration` — handler-side stubs grafted from their orphan
212
+ `WASmaxIn*RPC` schemas.
213
+ - **1 handler** flagged as `noSchema: true` (`xmlstreamend` — server
214
+ stream-close marker, intentionally just logs + acks; no payload to parse)
215
+ - **Per-variant `type` discriminators auto-pinned** as literal attrs —
216
+ every `notification`/`receipt` variant carries `type: { type: "literal",
217
+ value: "<variant>" }` even when the handler doesn't `assertAttr` it
218
+ explicitly (the dispatch case guarantees the wire value)
219
+ - **Raw-attr-access type inference** — when a handler walks the stanza
220
+ manually (`e.attrs.id` / `e.attrs.from` without ParsableWapNode), known
221
+ attr names (`id`, `from`, `to`, `participant`, `t`, `timestamp`,
222
+ `count`, …) get typed correctly instead of falling back to `string`
223
+ - **261 typed attributes** + **102 nested children** + **14/14 enums
224
+ resolved** across the non-IQ trees
225
+
226
+ **No remaining gaps.** Zero `unknown` attrs, zero `unknown` children, zero
227
+ empty trees, zero unresolved parser refs, zero tagless nodes, every enum
228
+ reference resolved (**134/134** across both phases), every dispatch handler
229
+ accounted for. The only structural "leftovers" are by design:
230
+ - **18 ops** with no request module — server-initiated `receive*RPC`s
231
+ that the client only ever parses (their schema is the sole
232
+ `responses[0]` with `variant: "Request"`)
233
+ - **1 stanza** flagged `noSchema: true` — `xmlstreamend`, a server
234
+ stream-close marker that only logs
235
+
236
+ ## Generate locally
237
+
238
+ ```sh
239
+ npx wa-fetcher --out dump/ # download bundles
240
+ npx wa-xml apply --bundles dump/raw/<version>/
241
+ ```
242
+
243
+ ## Caveats
244
+
245
+ - **Non-IQ schemas are best-effort field-observation, not strict specs.**
246
+ The `WAWebHandle*` family is imperative — control flow can gate fields
247
+ conditionally — so the per-handler tree captures *every field the parser
248
+ is observed to read*. Practical reading: required vs optional matches
249
+ the `attrX` vs `maybeAttrX` accessor used; cardinality on child lists is
250
+ inferred from `forEachChildWithTag`/`mapChildrenWithTag` patterns; helper
251
+ functions called with the stanza var get inlined so their attr reads
252
+ surface on the parent. Use IQ schemas (Phase 1) when you need strict.
253
+ - **No wire-sample cross-check.** Unlike `@vinikjkkj/wa-mex`, this package
254
+ does not validate schemas against real captured stanzas — `dump/
255
+ wire-samples/captures.json` only holds GraphQL captures today. Static
256
+ inference is exact for tag/attrs/cardinality but conservative for enum
257
+ value sets (only what the client compares against explicitly is observable).
258
+ - **`appliedMixin` is a list when multiple mixins compose on the same node.**
259
+ `WASmaxMixins.optionalMerge(<mergeFn>, <base>, <cond>)` adds the merged
260
+ mixin as a conditional entry (suffixed `?`) so consumers can tell which
261
+ contributions are gated by a runtime condition.
262
+ - **`delegatesToRPC` handlers don't carry an inline tree** — they point at
263
+ the corresponding `WASmax*RPC.receive*RPC` (single object) OR an array
264
+ of RPCs (when the handler dispatches by sub-attribute, e.g.
265
+ `link_code_companion_reg` branches on `stage` to two RPCs). The
266
+ structural schema lives in `WA_XML_OPERATIONS`.
267
+
268
+ ## Roadmap
269
+
270
+ - Sample-based wire validation once a stanza capture pipeline lands in
271
+ the fetcher.
272
+
273
+ Daily-extracted by [wa-spec](https://github.com/vinikjkkj/wa-spec).