@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.
- package/README.md +273 -0
- package/index.d.ts +11704 -0
- package/index.js +146 -0
- 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).
|