@vinikjkkj/wa-wam 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 +359 -0
  2. package/index.d.ts +30177 -0
  3. package/index.js +28358 -0
  4. package/package.json +51 -0
package/README.md ADDED
@@ -0,0 +1,359 @@
1
+ # @vinikjkkj/wa-wam
2
+
3
+ WhatsApp Web WAM (analytics/metrics) event schemas — event IDs, field IDs +
4
+ types + enum refs, global session attributes, private-stats bucket IDs, and
5
+ resolved enum value sets. Everything is daily-extracted directly from the
6
+ minified `WAWebWamCodegenUtils.defineEvents(...)` / `defineGlobal(...)` calls
7
+ in WA Web bundles.
8
+
9
+ ```sh
10
+ npm i @vinikjkkj/wa-wam
11
+ ```
12
+
13
+ ```ts
14
+ import {
15
+ WA_WAM_EVENTS,
16
+ WA_WAM_GLOBALS,
17
+ WA_WAM_ENUMS,
18
+ WA_WAM_PRIVATE_STATS_IDS,
19
+ WA_WAM_RESERVED_GLOBALS, // synthetic batch-level IDs (commitTime, eventSequenceNumber, psIdValue)
20
+ WA_WAM_CHANNELS,
21
+ WA_WAM_CHANNEL_WIRE_CODES, // { regular: 0, realtime: 1, private: 2 } — channel byte in batch header
22
+ WA_WAM_PROTOCOL_VERSION, // 2nd byte of every batch header (currently 5)
23
+ WA_WAM_WIRE_FORMAT, // marker bytes + value-encoding bits for TLV
24
+ WA_WAM_BUFFER_CONSTANTS // flush/upload limits (maxBufferSize, etc.)
25
+ } from '@vinikjkkj/wa-wam'
26
+ import type {
27
+ WaWamEventName,
28
+ WaWamEventArgs,
29
+ WaWamGlobalName,
30
+ WaWamEnumName,
31
+ WaWamField,
32
+ WaWamChannel,
33
+ WaWamReservedGlobal
34
+ } from '@vinikjkkj/wa-wam'
35
+
36
+ WA_WAM_EVENTS.UiAction
37
+ // → {
38
+ // id: 472,
39
+ // falcoName: 'wam_ui_action',
40
+ // channel: 'regular',
41
+ // privateStatsIdInt: null,
42
+ // weight: { default: 1, gkx26259: 100, gkx26258: 5000 },
43
+ // requiredFields: [],
44
+ // fields: {
45
+ // uiActionType: { id: 1, type: 'enum', enum: 'UI_ACTION_TYPE', falcoName: 'ui_action_type' },
46
+ // uiActionPreloaded: { id: 2, type: 'boolean', falcoName: 'ui_action_preloaded' },
47
+ // uiActionT: { id: 3, type: 'timer', falcoName: 'ui_action_t' },
48
+ // sizeBucket: { id: 4, type: 'enum', enum: 'SIZE_BUCKET', falcoName: 'size_bucket' },
49
+ // …23 more
50
+ // }
51
+ // }
52
+
53
+ WA_WAM_ENUMS.UI_ACTION_TYPE
54
+ // → {
55
+ // module: 'WAWebWamEnumUiActionType',
56
+ // export: 'UI_ACTION_TYPE',
57
+ // values: { OTHER: 1, APP_OPEN: 2, CHAT_OPEN: 3, IMAGE_OPEN: 4, … }
58
+ // }
59
+
60
+ WA_WAM_GLOBALS.platform
61
+ // → {
62
+ // id: 11,
63
+ // type: 'enum',
64
+ // enum: 'PLATFORM_TYPE',
65
+ // channels: ['regular', 'private'],
66
+ // falcoName: 'platform'
67
+ // }
68
+
69
+ WA_WAM_PRIVATE_STATS_IDS
70
+ // → [
71
+ // { key: 'DefaultPsId', keyHashInt: 113760892, rotationPeriodDays: -1 },
72
+ // { key: 'GroupExitExperienceId', keyHashInt: 152546501, rotationPeriodDays: 30 },
73
+ // { key: 'IdTtlDaily', keyHashInt: 248614979, rotationPeriodDays: 1 },
74
+ // …5 more
75
+ // ]
76
+ ```
77
+
78
+ ## Typed payload builder
79
+
80
+ Each event's `fields` tuple is a discriminated union, so `type` narrows the
81
+ shape and lets `WaWamEventArgs<K>` synthesise the typed payload — enum fields
82
+ become the string-literal union of their enum's value keys (so consumers pass
83
+ `'CHAT_OPEN'` instead of the magic integer `3`), primitives map to their
84
+ natural JS types:
85
+
86
+ ```ts
87
+ import type { WaWamEventArgs, WaWamEventName } from '@vinikjkkj/wa-wam'
88
+
89
+ type UiActionPayload = WaWamEventArgs<'UiAction'>
90
+ // → {
91
+ // readonly uiActionType?: 'OTHER' | 'APP_OPEN' | 'CHAT_OPEN' | …
92
+ // readonly uiActionPreloaded?: boolean
93
+ // readonly uiActionT?: number // ms since startMarker
94
+ // readonly sizeBucket?: 'SMALL' | 'MEDIUM' | 'LARGE' | …
95
+ // readonly uiActionType?: 'OTHER' | 'APP_OPEN' | 'CHAT_OPEN' | …
96
+ // …
97
+ // }
98
+
99
+ function commitWam<K extends WaWamEventName>(name: K, payload: WaWamEventArgs<K>) {
100
+ // …your encoder; convert enum keys to numeric values via
101
+ // WA_WAM_ENUMS[event.fields[fieldName].enum].values[stringKey].
102
+ }
103
+
104
+ commitWam('UiAction', { uiActionType: 'CHAT_OPEN', uiActionT: 142 }) // ✓
105
+ commitWam('UiAction', { uiActionType: 'NONEXISTENT' }) // ✗ caught at compile time
106
+ commitWam('UiAction', { uiActionTypo: 'CHAT_OPEN' }) // ✗ caught at compile time
107
+ ```
108
+
109
+ ## What's in here
110
+
111
+ WAM (sometimes "WA Logger" / "Falco") is WA Web's client-side analytics
112
+ pipeline. Each mutation, action, latency, error, or daily snapshot the client
113
+ wants to report is registered through one call:
114
+
115
+ ```js
116
+ // from WAWebUiActionWamEvent
117
+ WAWebWamCodegenUtils.defineEvents({
118
+ UiAction: [472, {
119
+ uiActionType: [1, WAWebWamEnumUiActionType.UI_ACTION_TYPE],
120
+ uiActionPreloaded: [2, TYPES.BOOLEAN],
121
+ uiActionT: [3, TYPES.TIMER],
122
+ // …
123
+ }, [1, 100, 5000], 'regular']
124
+ }, { UiAction: [] })
125
+ ```
126
+
127
+ Each entry boils down to:
128
+
129
+ - an **event id** (`472`) — what the server keys batched payloads by
130
+ - an **event name** (`UiAction`) — the camelCase JS handle, also reduces to
131
+ the snake-case **falco name** (`wam_ui_action`) used in shadow-logging
132
+ - a **field map** — each field has its own **field id** (`1`), JS type
133
+ (`boolean`/`integer`/`number`/`string`/`timer`) or a **protobuf-style enum
134
+ reference**, and a **falco field name** (snake-case)
135
+ - a **weight tuple** — three sampling weights selected at commit time based
136
+ on gkx 26259/26258 (the runtime does `Math.random() * weight > 1`); higher
137
+ = more aggressive sampling
138
+ - a **channel** — `regular` | `private` | `realtime`. `private` events are
139
+ routed through a rotating pseudo-anonymous bucket (see
140
+ `WA_WAM_PRIVATE_STATS_IDS`) and a separate upload backend
141
+ - a **privateStatsIdInt** (only on `private` events) — the `keyHashInt` of
142
+ the bucket this event's payload belongs to
143
+
144
+ `WAWebWamGlobals.defineGlobal({...})` registers attributes attached to every
145
+ batch — platform, app version, network state, etc. — under their own ids
146
+ (`WA_WAM_GLOBALS`).
147
+
148
+ This package gives you the static metadata for all **423 events** + **46
149
+ globals** + **851 enums** + **8 privateStatsIds** + **1 synthetic `none`
150
+ bucket** + **3 reserved batch-level IDs** (commitTime / eventSequenceNumber /
151
+ psIdValue), so you can build wire-level encoders/decoders or replay metrics
152
+ without manually transcribing the client's registry.
153
+
154
+ ## What's published
155
+
156
+ | File | Format | Use case |
157
+ |---|---|---|
158
+ | `index.js` | CommonJS | Runtime `WA_WAM_EVENTS` / `WA_WAM_GLOBALS` / `WA_WAM_ENUMS` / `WA_WAM_PRIVATE_STATS_IDS` / `WA_WAM_RESERVED_GLOBALS` / `WA_WAM_CHANNELS` / `WA_WAM_CHANNEL_WIRE_CODES` / `WA_WAM_PROTOCOL_VERSION` / `WA_WAM_WIRE_FORMAT` / `WA_WAM_BUFFER_CONSTANTS` frozen tables |
159
+ | `index.d.ts` | TS declarations | Per-event/global/enum literal-typed schemas + the umbrella maps + the `WaWamEventArgs<K>` payload helper |
160
+
161
+ A raw IR file (`index.json`) is also produced — see
162
+ [`packages/wam/index.json`](https://github.com/vinikjkkj/wa-spec/blob/master/packages/wam/index.json)
163
+ for non-TS consumers (diff tools, codegen, other languages).
164
+
165
+ `index.json` shape:
166
+
167
+ ```jsonc
168
+ {
169
+ "waVersion": "2.3000.xxxxx",
170
+ "protocolVersion": 5,
171
+ "channels": ["private", "realtime", "regular"],
172
+ "channelWireCodes": { "regular": 0, "realtime": 1, "private": 2 },
173
+ "wireFormat": {
174
+ "markers": { "globalAttribute": 0, "event": 1, "field": 2, "lastFlag": 4, "extendedIdFlag": 8 },
175
+ "valueEncodingBits": {
176
+ "null": 0, "intZero": 16, "intOne": 32, "int8": 48, "int16": 64,
177
+ "int32": 80, "int64": 96, "float64": 112,
178
+ "stringShort": 128, "stringMedium": 144, "stringLong": 160
179
+ }
180
+ },
181
+ "bufferConstants": {
182
+ "maxBufferSize": 50000,
183
+ "maxBufferSizeForUpload": 64000,
184
+ "inMemoryBufferingDurationSecs": 5,
185
+ "bufferRotateIntervalSecs": 120,
186
+ "workerDataBatchSize": 100,
187
+ "guestInMemoryBufferingDurationSecs": 1,
188
+ "guestBufferRotateIntervalSecs": 2
189
+ },
190
+ "privateStatsIds": [
191
+ { "key": "DefaultPsId", "keyHashInt": 113760892, "rotationPeriodDays": -1 },
192
+ { "key": "none", "keyHashInt": 0, "rotationPeriodDays": -1 },
193
+
194
+ ],
195
+ "reservedGlobals": [
196
+ { "id": 47, "label": "commitTime" },
197
+ { "id": 3433, "label": "eventSequenceNumber" },
198
+ { "id": 6005, "label": "psIdValue" }
199
+ ],
200
+ "enums": {
201
+ "UI_ACTION_TYPE": {
202
+ "module": "WAWebWamEnumUiActionType",
203
+ "export": "UI_ACTION_TYPE",
204
+ "values": { "OTHER": 1, "APP_OPEN": 2, "CHAT_OPEN": 3, … }
205
+ }
206
+ },
207
+ "globals": {
208
+ "platform": { "id": 11, "type": "enum", "enum": "PLATFORM_TYPE",
209
+ "channels": ["regular","private"], "falcoName": "platform" }
210
+ },
211
+ "events": {
212
+ "UiAction": {
213
+ "id": 472,
214
+ "module": "WAWebUiActionWamEvent",
215
+ "falcoName": "wam_ui_action",
216
+ "channel": "regular",
217
+ "privateStatsIdInt": null,
218
+ "emittedByWorker": false,
219
+ "weight": { "default": 1, "gkx26259": 100, "gkx26258": 5000 },
220
+ "requiredFields": [],
221
+ "conditions": [],
222
+ "fields": {
223
+ "uiActionType": { "id": 1, "type": "enum", "enum": "UI_ACTION_TYPE",
224
+ "falcoName": "ui_action_type" }
225
+ }
226
+ }
227
+ }
228
+ }
229
+ ```
230
+
231
+ ## Wire format
232
+
233
+ Events are buffered in-memory and flushed in batches to
234
+ `WAWebUploadStatsBackend` (regular) or `WAWebUploadPrivateStatsBackend`
235
+ (private). The on-wire encoding is a compact TLV serialisation keyed by the
236
+ numeric field ids — see `WAWebWamLibContext` / `WAWebWamLibProtocol` in WA
237
+ Web's source for the canonical writer.
238
+
239
+ For most consumers the IR alone is sufficient: pair `event.id` + per-field
240
+ `{id, type, enum?}` with whatever framing your transport uses. If you need
241
+ to speak the binary protocol directly, the schema also surfaces:
242
+
243
+ - **`WA_WAM_PROTOCOL_VERSION`** — the byte stamped right after the literal
244
+ `"WAM"` magic on every batch (currently `5`).
245
+ - **`WA_WAM_CHANNEL_WIRE_CODES`** — the channel byte at offset 5 of the
246
+ header (`regular: 0, realtime: 1, private: 2`).
247
+ - **`WA_WAM_RESERVED_GLOBALS`** — three IDs `WAWebWamLibContext` injects
248
+ into every batch as **global attributes** (wire marker byte `0`, disjoint
249
+ from the event-field marker byte `2`):
250
+ - `47` → `commitTime` (unix seconds, set right before each event)
251
+ - `3433` → `eventSequenceNumber` (Beaconing sequence, when non-null)
252
+ - `6005` → `psIdValue` (psId bucket value, only for `private` channel —
253
+ same id as the declared `psId` global, listed here for completeness)
254
+ - **`WA_WAM_WIRE_FORMAT`** — the magic numbers a TLV encoder/decoder
255
+ needs. Marker byte = (bottom 4 bits: `markers.{globalAttribute|event|
256
+ field}` plus `lastFlag` + `extendedIdFlag`) | (top 4 bits:
257
+ `valueEncodingBits.{null|intZero|intOne|int8|int16|int32|int64|
258
+ float64|stringShort|stringMedium|stringLong}` — selects payload type
259
+ and size class).
260
+ - **`WA_WAM_BUFFER_CONSTANTS`** — the runtime's flush/upload limits:
261
+ `maxBufferSize` (50KB triggers flush), `maxBufferSizeForUpload`
262
+ (64KB server-side cap), `bufferRotateIntervalSecs` (120s rotation),
263
+ `inMemoryBufferingDurationSecs` (5s), `workerDataBatchSize` (100).
264
+ Guest sessions (unauthenticated companion links) override the two
265
+ time-based thresholds with much shorter values:
266
+ `guestInMemoryBufferingDurationSecs` (1s) and
267
+ `guestBufferRotateIntervalSecs` (2s).
268
+
269
+ These IDs live in the global namespace on the wire, so an event field with
270
+ `id: 47` does NOT collide with `commitTime` — they're tagged with different
271
+ marker bytes.
272
+
273
+ ## Generate locally
274
+
275
+ ```sh
276
+ npx wa-fetcher --out dump/ # download bundles
277
+ npx wa-wam apply --bundles dump/raw/<version>/
278
+ ```
279
+
280
+ ## Caveats
281
+
282
+ - **Sampling weight = `1` means always emit.** Higher values reduce sampling
283
+ rate (the runtime gate is `Math.random() * weight > 1`). The tuple's three
284
+ slots map to `default` (no gkx), `gkx26259`, `gkx26258` — the latter
285
+ takes precedence when on.
286
+ - **`requiredFields` covers static nullability checks only.** Events list
287
+ the camelCase field names that must be non-null at commit time, taken
288
+ from slot 1 of every validator triple `[[predicates], [requiredFields],
289
+ [conditions]]` (multiple triples can chain; the union is captured).
290
+ - **`conditions` captures the human-readable validation messages** from
291
+ slot 2's `[fn, "msg"]` pairs — e.g. `"about_chat_bubble_tap_count >= 0"`,
292
+ `"was_sheet_seen_for_first_time != False"`. The predicate function itself
293
+ is a JS function literal we can't represent statically, but the
294
+ developer-authored message string IS the canonical rule.
295
+ - **Predicates (slot 0)** are conditional guards that, when truthy, suppress
296
+ the validator's required-fields/conditions checks. They're JS functions
297
+ too — not captured.
298
+ - **`emittedByWorker: true`** on an event means its module is listed in
299
+ `WAWebWamProcessWorkerData`'s dep array — the event is committed from the
300
+ Web Worker thread and replayed on the main thread for serialisation. The
301
+ worker emits 63 of the 423 events (mostly E2E/Md/AppState/Receipt/Daily
302
+ metrics that run in background); the other 360 are committed only from
303
+ the main UI thread.
304
+
305
+ ## Auto-generated instance methods (derivable from the schema)
306
+
307
+ Each `WamEvent` subclass that `defineEvents` builds at module-load time has a
308
+ predictable instance API. The schema doesn't list these methods explicitly
309
+ (they're all derivable from `event.fields`) but they're worth knowing if
310
+ you're calling WA Web's own runtime or wrapping it:
311
+
312
+ ```ts
313
+ class UiActionWamEvent {
314
+ // Set on construction; one per event class
315
+ readonly id: 472
316
+ readonly $className: 'UiAction'
317
+ readonly weight: number
318
+ readonly wamChannel: 'regular'
319
+ eventTime: number // Date.now() at construction, override via setTime(t)
320
+
321
+ // One typed setter per field (typeof-validated against the schema type)
322
+ uiActionType: ...
323
+ uiActionPreloaded: boolean
324
+ uiActionT: number // TIMER field — milliseconds
325
+ // …
326
+
327
+ // For every TIMER field `<x>`, two helpers auto-attached:
328
+ startUiActionT(): void // records ts = Date.now()
329
+ markUiActionT(): void // sets this.uiActionT = Date.now() - ts (or eventTime)
330
+
331
+ // Generic accessors
332
+ getValue(fieldName: string): unknown
333
+ resolveEnumValue(fieldName: string, numericValue: number): string | number
334
+ getEventNameForFalco(): string // → 'wam_ui_action'
335
+ getFieldsMapForFalco(): Record<string, unknown> | null
336
+
337
+ // Commit lifecycle
338
+ runPreCommitValidation(): void // throws if requiredFields/conditions fail
339
+ commit(): void // buffered (channel + buffer constants)
340
+ commitAndWaitForFlush(force?: boolean): Promise<void> // force-flush all channels if true
341
+ setTime(t?: number): void // override eventTime
342
+ }
343
+ ```
344
+
345
+ So consumers writing typed wrappers around WA Web's runtime can derive every
346
+ method name (`mark<Field>` / `start<Field>`) from `event.fields[name].type
347
+ === 'timer'`, and every field setter's accepted type from
348
+ `WaWamFieldValueOf<event.fields[name]>`.
349
+ - **`channel: 'realtime'` events bypass the regular buffer.** They flush
350
+ immediately via `setTimeout(forceRunNow, 1)` when gkx 3237 is on.
351
+ - **`privateStatsIdInt` references a `keyHashInt` in `WA_WAM_PRIVATE_STATS_IDS`.**
352
+ The matching `key` is the rotation bucket (e.g. `IdTtlDaily` rotates every
353
+ 1 day, `IdTtl90Days` every 90 days). `keyHashInt` is the hash the server
354
+ joins on; `key` is the human label.
355
+ - **The codegen module is `WAWebWamCodegenUtils`.** Any new event added to WA
356
+ Web shows up here on the next daily extract; any event id/field id change
357
+ surfaces in the diff.
358
+
359
+ Daily-extracted by [wa-spec](https://github.com/vinikjkkj/wa-spec).