@svta/cml-cmcd 2.7.0 → 2.8.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/dist/index.js CHANGED
@@ -1,98 +1,315 @@
1
1
  import { SfItem, SfToken, decodeSfDict, encodeSfDict, symbolToStr } from "@svta/cml-structured-field-values";
2
2
  import { getBaseUrl, urlToRelativePath, uuid } from "@svta/cml-utils";
3
3
 
4
- //#region src/CMCD_AGGREGATE_BITRATE_KEYS.ts
4
+ //#region src/CMCD_INNER_LIST_KEYS.ts
5
5
  /**
6
- * Maps each aggregate bitrate key to the exact bitrate key that supersedes it.
6
+ * Inner-list keys in version 2 that are scalars in version 1.
7
7
  *
8
- * Per CTA-5004-B, `ab` MUST NOT be sent when the encoded bitrate `br` is
9
- * known. `lab` and `tab` follow the same rule for the lowest bitrate `lb`
10
- * and the top bitrate `tb`.
8
+ * Used by both encoding (down-conversion) and decoding (up-conversion).
11
9
  *
12
10
  * @internal
13
11
  */
14
- const CMCD_AGGREGATE_BITRATE_KEYS = {
15
- ab: "br",
16
- lab: "lb",
17
- tab: "tb"
18
- };
19
-
20
- //#endregion
21
- //#region src/CMCD_FORMATTER_MAP.ts
22
- const roundValue = (value) => {
23
- if (value instanceof SfItem) return new SfItem(Math.round(value.value), value.params);
24
- return Math.round(value);
25
- };
26
- const toRounded = (value) => {
27
- if (Array.isArray(value)) return value.map(roundValue);
28
- return roundValue(value);
29
- };
30
- const toUrlSafe = (value, options) => {
31
- if (Array.isArray(value)) return value.map((item) => toUrlSafe(item, options));
32
- if (value instanceof SfItem && typeof value.value === "string") return new SfItem(toUrlSafe(value.value, options), value.params);
33
- else {
34
- if (options.baseUrl) value = urlToRelativePath(value, getBaseUrl(options.baseUrl));
35
- return options.version === 1 ? encodeURIComponent(value) : value;
36
- }
37
- };
38
- const hundredValue = (value) => {
39
- if (value instanceof SfItem) return new SfItem(Math.round(value.value / 100) * 100, value.params);
40
- return Math.round(value / 100) * 100;
41
- };
42
- const toHundred = (value) => {
43
- if (Array.isArray(value)) return value.map(hundredValue);
44
- return hundredValue(value);
45
- };
46
- const nor = (value, options) => {
47
- let norValue = value;
48
- if (options.version >= 2) {
49
- if (value instanceof SfItem && typeof value.value === "string") norValue = new SfItem([value]);
50
- else if (typeof value === "string") norValue = [value];
51
- }
52
- return toUrlSafe(norValue, options);
53
- };
54
- /**
55
- * The default formatters for CMCD values.
56
- *
57
- * @public
58
- */
59
- const CMCD_FORMATTER_MAP = {
60
- br: toRounded,
61
- d: toRounded,
62
- bl: toHundred,
63
- dl: toHundred,
64
- mtp: toHundred,
65
- nor,
66
- rtp: toHundred,
67
- tb: toRounded
68
- };
12
+ const CMCD_INNER_LIST_KEYS = new Set([
13
+ "ab",
14
+ "bl",
15
+ "br",
16
+ "bsa",
17
+ "bsd",
18
+ "bsda",
19
+ "lab",
20
+ "lb",
21
+ "mtp",
22
+ "pb",
23
+ "tab",
24
+ "tb",
25
+ "tbl",
26
+ "tpb"
27
+ ]);
69
28
 
70
29
  //#endregion
71
- //#region src/CMCD_KEY_OBJECT_TYPES.ts
30
+ //#region src/CMCD_KEY_SPECS.ts
31
+ const MEDIA = [
32
+ "a",
33
+ "v",
34
+ "av",
35
+ "tt",
36
+ "c",
37
+ "o"
38
+ ];
72
39
  /**
73
- * Maps a key to the object types (`ot`) for which CTA-5004-B allows the key.
74
- *
75
- * A key that is absent from this map is allowed for every object type.
76
- * `d` (Object duration) and `tpb` (Top playable bitrate) MUST NOT be sent for
77
- * other object types.
40
+ * The encoder rules of each key. The table has one row for each key of CTA-5004-B Table 1, plus `nrr` of CTA-5004 and `cdn`.
78
41
  *
79
42
  * @internal
80
43
  */
81
- const CMCD_KEY_OBJECT_TYPES = {
82
- d: [
83
- "a",
84
- "v",
85
- "av",
86
- "tt",
87
- "c",
88
- "o"
89
- ],
90
- tpb: [
91
- "a",
92
- "v",
93
- "av",
94
- "c"
95
- ]
44
+ const CMCD_KEY_SPECS = {
45
+ ab: {
46
+ type: "ot-list",
47
+ round: 1,
48
+ supersededBy: "br",
49
+ version: 2
50
+ },
51
+ bg: {
52
+ type: "boolean",
53
+ omitDefault: false,
54
+ requiredOn: "b",
55
+ version: 2
56
+ },
57
+ bl: {
58
+ type: "ot-list",
59
+ round: 100
60
+ },
61
+ br: {
62
+ type: "ot-list",
63
+ round: 1,
64
+ requiredOn: "bc"
65
+ },
66
+ bs: {
67
+ type: "boolean",
68
+ omitDefault: false
69
+ },
70
+ bsa: {
71
+ type: "ot-list",
72
+ round: 1,
73
+ version: 2
74
+ },
75
+ bsd: {
76
+ type: "ot-list",
77
+ round: 1,
78
+ version: 2
79
+ },
80
+ bsda: {
81
+ type: "ot-list",
82
+ round: 1,
83
+ version: 2
84
+ },
85
+ cdn: {
86
+ type: "string",
87
+ max: 128,
88
+ version: 2
89
+ },
90
+ cen: {
91
+ type: "string",
92
+ max: 64,
93
+ mode: "event",
94
+ onlyOn: "ce",
95
+ requiredOn: "ce",
96
+ version: 2
97
+ },
98
+ cid: {
99
+ type: "string",
100
+ max: 128,
101
+ v1Max: 64,
102
+ requiredOn: "c"
103
+ },
104
+ cmsdd: {
105
+ type: "string",
106
+ mode: "event",
107
+ onlyOn: "rr",
108
+ version: 2
109
+ },
110
+ cmsds: {
111
+ type: "string",
112
+ mode: "event",
113
+ onlyOn: "rr",
114
+ version: 2
115
+ },
116
+ cs: {
117
+ type: "string",
118
+ version: 2
119
+ },
120
+ d: {
121
+ type: "integer",
122
+ round: 1,
123
+ ot: MEDIA
124
+ },
125
+ dfa: {
126
+ type: "integer",
127
+ round: 1,
128
+ version: 2
129
+ },
130
+ dl: {
131
+ type: "integer",
132
+ round: 100
133
+ },
134
+ e: {
135
+ type: "token",
136
+ mode: "event",
137
+ requiredOn: "always",
138
+ version: 2
139
+ },
140
+ ec: {
141
+ type: "string-list",
142
+ requiredOn: "e",
143
+ version: 2
144
+ },
145
+ h: {
146
+ type: "string",
147
+ max: 128,
148
+ mode: "event",
149
+ requiredOn: "h",
150
+ version: 2
151
+ },
152
+ lab: {
153
+ type: "ot-list",
154
+ round: 1,
155
+ supersededBy: "lb",
156
+ version: 2
157
+ },
158
+ lb: {
159
+ type: "ot-list",
160
+ round: 1,
161
+ version: 2
162
+ },
163
+ ltc: {
164
+ type: "integer",
165
+ round: 1,
166
+ version: 2
167
+ },
168
+ msd: {
169
+ type: "integer",
170
+ round: 1,
171
+ version: 2
172
+ },
173
+ mtp: {
174
+ type: "ot-list",
175
+ round: 100
176
+ },
177
+ nor: { type: "nor" },
178
+ nr: {
179
+ type: "boolean",
180
+ omitDefault: false,
181
+ version: 2
182
+ },
183
+ nrr: {
184
+ type: "string",
185
+ version: 1
186
+ },
187
+ ot: { type: "token" },
188
+ pb: {
189
+ type: "ot-list",
190
+ round: 1,
191
+ version: 2
192
+ },
193
+ pr: {
194
+ type: "decimal",
195
+ omitDefault: 1,
196
+ requiredOn: "pr"
197
+ },
198
+ pt: {
199
+ type: "integer",
200
+ round: 1,
201
+ version: 2
202
+ },
203
+ rc: {
204
+ type: "integer",
205
+ round: 1,
206
+ mode: "event",
207
+ onlyOn: "rr",
208
+ version: 2
209
+ },
210
+ rtp: {
211
+ type: "integer",
212
+ round: 100
213
+ },
214
+ sf: {
215
+ type: "token",
216
+ v1Map: { e: "o" }
217
+ },
218
+ sid: {
219
+ type: "string",
220
+ max: 64
221
+ },
222
+ smrt: {
223
+ type: "string",
224
+ mode: "event",
225
+ onlyOn: "rr",
226
+ version: 2
227
+ },
228
+ sn: {
229
+ type: "integer",
230
+ round: 1,
231
+ version: 2
232
+ },
233
+ st: {
234
+ type: "token",
235
+ v1Map: { ll: "l" }
236
+ },
237
+ sta: {
238
+ type: "token",
239
+ requiredOn: "ps",
240
+ version: 2
241
+ },
242
+ su: {
243
+ type: "boolean",
244
+ omitDefault: false
245
+ },
246
+ tab: {
247
+ type: "ot-list",
248
+ round: 1,
249
+ supersededBy: "tb",
250
+ version: 2
251
+ },
252
+ tb: {
253
+ type: "ot-list",
254
+ round: 1
255
+ },
256
+ tbl: {
257
+ type: "ot-list",
258
+ round: 100,
259
+ version: 2
260
+ },
261
+ tpb: {
262
+ type: "ot-list",
263
+ round: 1,
264
+ ot: [
265
+ "a",
266
+ "v",
267
+ "av",
268
+ "c"
269
+ ],
270
+ version: 2
271
+ },
272
+ ts: {
273
+ type: "integer",
274
+ round: 1,
275
+ mode: "event",
276
+ requiredOn: "always",
277
+ version: 2
278
+ },
279
+ ttfb: {
280
+ type: "integer",
281
+ round: 1,
282
+ mode: "event",
283
+ onlyOn: "rr",
284
+ version: 2
285
+ },
286
+ ttfbb: {
287
+ type: "integer",
288
+ round: 1,
289
+ mode: "event",
290
+ onlyOn: "rr",
291
+ version: 2
292
+ },
293
+ ttlb: {
294
+ type: "integer",
295
+ round: 1,
296
+ mode: "event",
297
+ onlyOn: "rr",
298
+ version: 2
299
+ },
300
+ url: {
301
+ type: "string",
302
+ mode: "event",
303
+ onlyOn: "rr",
304
+ requiredOn: "rr",
305
+ version: 2
306
+ },
307
+ v: {
308
+ type: "integer",
309
+ round: 1,
310
+ requiredOn: "always",
311
+ version: 2
312
+ }
96
313
  };
97
314
 
98
315
  //#endregion
@@ -105,998 +322,1122 @@ const CMCD_KEY_OBJECT_TYPES = {
105
322
  const CMCD_V2 = 2;
106
323
 
107
324
  //#endregion
108
- //#region src/CmcdEventType.ts
325
+ //#region src/CmcdReportingMode.ts
109
326
  /**
110
- * CMCD event type 'bc' (bitrate change).
327
+ * CMCD event mode variable name.
111
328
  *
112
329
  * @public
113
330
  */
114
- const CMCD_EVENT_BITRATE_CHANGE = "bc";
331
+ const CMCD_EVENT_MODE = "event";
115
332
  /**
116
- * CMCD event type 'ps' (play state change).
333
+ * CMCD request mode variable name.
117
334
  *
118
335
  * @public
119
336
  */
120
- const CMCD_EVENT_PLAY_STATE = "ps";
337
+ const CMCD_REQUEST_MODE = "request";
121
338
  /**
122
- * CMCD event type 'pr' (playback rate change).
339
+ * CMCD reporting mode types.
123
340
  *
124
- * @public
125
- */
126
- const CMCD_EVENT_PLAYBACK_RATE = "pr";
127
- /**
128
- * CMCD event type 'e' (error).
341
+ * @enum
129
342
  *
130
343
  * @public
131
344
  */
132
- const CMCD_EVENT_ERROR = "e";
345
+ const CmcdReportingMode = {
346
+ REQUEST: CMCD_REQUEST_MODE,
347
+ EVENT: CMCD_EVENT_MODE
348
+ };
349
+
350
+ //#endregion
351
+ //#region src/downConvertToV1.ts
352
+ function hasParams(params) {
353
+ for (const _ in params) return true;
354
+ return false;
355
+ }
133
356
  /**
134
- * CMCD event type 't' (time interval).
357
+ * Unwrap an inner list or SfItem value to a scalar.
135
358
  *
136
- * @public
359
+ * The scalar is the value of the item with the object type flag of `ot`
360
+ * (CTA-5004-B section 4.1, item 14). Without that item, it is the value
361
+ * of the first item without parameters. Otherwise it is `undefined`.
137
362
  */
138
- const CMCD_EVENT_TIME_INTERVAL = "t";
363
+ function unwrapValue(value, ot) {
364
+ if (value instanceof SfItem) value = value.value;
365
+ if (!Array.isArray(value)) return value;
366
+ let fallback;
367
+ for (const item of value) {
368
+ if (!(item instanceof SfItem)) {
369
+ if (fallback === void 0) fallback = item;
370
+ continue;
371
+ }
372
+ if (ot && item.params?.[ot] === true) return item.value;
373
+ if (fallback === void 0 && !hasParams(item.params)) fallback = item.value;
374
+ }
375
+ return fallback;
376
+ }
139
377
  /**
140
- * CMCD event type 'c' (content ID).
378
+ * Down-convert version 2 CMCD data to version 1.
141
379
  *
142
- * @public
143
- */
144
- const CMCD_EVENT_CONTENT_ID = "c";
145
- /**
146
- * CMCD event type 'b' (backgrounded mode).
380
+ * - Extracts `nrr` from the `nor` SfItem's `r` parameter.
381
+ * - Unwraps inner-list values to scalars for the object type `ot`.
147
382
  *
148
- * @public
383
+ * @internal
149
384
  */
150
- const CMCD_EVENT_BACKGROUNDED_MODE = "b";
385
+ function downConvertToV1(obj, ot) {
386
+ const result = {};
387
+ for (const [key, value] of Object.entries(obj)) {
388
+ if (value == null) {
389
+ result[key] = value;
390
+ continue;
391
+ }
392
+ if (key === "nor") {
393
+ const list = value instanceof SfItem && Array.isArray(value.value) ? value.value : value;
394
+ const first = Array.isArray(list) ? list[0] : list;
395
+ if (first instanceof SfItem) {
396
+ result["nor"] = first.value;
397
+ if (first.params?.r) result["nrr"] = first.params.r;
398
+ } else result["nor"] = first;
399
+ } else if (CMCD_INNER_LIST_KEYS.has(key)) result[key] = unwrapValue(value, ot);
400
+ else result[key] = value;
401
+ }
402
+ return result;
403
+ }
404
+
405
+ //#endregion
406
+ //#region src/isCmcdCustomKey.ts
407
+ const CUSTOM_KEY_REGEX = /^[a-z][a-z0-9.-]*$/;
151
408
  /**
152
- * CMCD event type 'm' (mute).
409
+ * Check if a key is a valid custom key: a lowercase first letter, then
410
+ * characters from `a-z 0-9 . -`. The key needs a hyphen, not at the start
411
+ * or end. This check applies the CTA-5004-B custom-key rules, restricted
412
+ * to names that RFC 8941 key serialization accepts.
153
413
  *
154
- * @public
155
- */
156
- const CMCD_EVENT_MUTE = "m";
157
- /**
158
- * CMCD event type 'um' (unmute).
414
+ * @param key - The key to check.
159
415
  *
160
- * @public
161
- */
162
- const CMCD_EVENT_UNMUTE = "um";
163
- /**
164
- * CMCD event type 'pe' (player expand).
416
+ * @returns `true` if the key is a custom key, `false` otherwise.
165
417
  *
166
418
  * @public
167
- */
168
- const CMCD_EVENT_PLAYER_EXPAND = "pe";
169
- /**
170
- * CMCD event type 'pc' (player collapse).
171
419
  *
172
- * @public
420
+ * @example
421
+ * {@includeCode ../test/isCmcdCustomKey.test.ts#example}
173
422
  */
174
- const CMCD_EVENT_PLAYER_COLLAPSE = "pc";
423
+ function isCmcdCustomKey(key) {
424
+ const separator = key.indexOf("-", 1);
425
+ return separator > 0 && separator < key.length - 1 && CUSTOM_KEY_REGEX.test(key);
426
+ }
427
+
428
+ //#endregion
429
+ //#region src/getKeySpec.ts
430
+ const CUSTOM_KEY_SPEC = {
431
+ type: "custom",
432
+ omitDefault: false,
433
+ max: 64,
434
+ v1Max: Infinity
435
+ };
175
436
  /**
176
- * CMCD event type 'rr' (response received).
437
+ * Returns the row of a key in the key table.
177
438
  *
178
- * @public
179
- */
180
- const CMCD_EVENT_RESPONSE_RECEIVED = "rr";
181
- /**
182
- * CMCD event type 'as' (ad start).
439
+ * @param key - The key to look up.
183
440
  *
184
- * @public
185
- */
186
- const CMCD_EVENT_AD_START = "as";
187
- /**
188
- * CMCD event type 'ae' (ad end).
441
+ * @returns The row of a reserved key, the custom row for a valid custom key, or `undefined`.
189
442
  *
190
- * @public
443
+ * @internal
191
444
  */
192
- const CMCD_EVENT_AD_END = "ae";
445
+ function getKeySpec(key) {
446
+ if (Object.prototype.hasOwnProperty.call(CMCD_KEY_SPECS, key)) return CMCD_KEY_SPECS[key];
447
+ return isCmcdCustomKey(key) ? CUSTOM_KEY_SPEC : void 0;
448
+ }
449
+
450
+ //#endregion
451
+ //#region src/isValid.ts
193
452
  /**
194
- * CMCD event type 'abs' (ad break start).
453
+ * Checks if the given value is valid to send as a CMCD value.
195
454
  *
196
- * @public
197
- */
198
- const CMCD_EVENT_AD_BREAK_START = "abs";
199
- /**
200
- * CMCD event type 'abe' (ad break end).
455
+ * `undefined`, `null`, an empty string, `false`, an empty array, and a
456
+ * number that is not finite are not valid. CTA-5004-B requires omitting
457
+ * the key when its value is unknown.
201
458
  *
202
- * @public
203
- */
204
- const CMCD_EVENT_AD_BREAK_END = "abe";
205
- /**
206
- * CMCD event type 'sk' (skip).
459
+ * @param value - The value to check.
207
460
  *
208
- * @public
461
+ * @returns `true` if the value is valid.
462
+ *
463
+ * @internal
209
464
  */
210
- const CMCD_EVENT_SKIP = "sk";
465
+ function isValid(value) {
466
+ if (typeof value === "number") return Number.isFinite(value);
467
+ if (Array.isArray(value)) return value.length > 0;
468
+ return value != null && value !== "" && value !== false;
469
+ }
470
+
471
+ //#endregion
472
+ //#region src/formatNor.ts
473
+ function toBase(baseUrl) {
474
+ if (!baseUrl) return;
475
+ try {
476
+ return new URL(getBaseUrl(baseUrl)).href;
477
+ } catch {
478
+ return;
479
+ }
480
+ }
481
+ function formatPath(path, base, version) {
482
+ if (typeof path !== "string" || path === "") return;
483
+ const relative = base ? urlToRelativePath(path, base) : path;
484
+ return version === 1 ? encodeURIComponent(relative) : relative;
485
+ }
486
+ function formatEntry(entry, base, version) {
487
+ if (entry instanceof SfItem) {
488
+ const path = formatPath(entry.value, base, version);
489
+ return path === void 0 ? void 0 : new SfItem(path, entry.params);
490
+ }
491
+ return formatPath(entry, base, version);
492
+ }
493
+ function formatEntries(entries, base, version) {
494
+ const list = [];
495
+ for (const entry of entries) {
496
+ const formatted = formatEntry(entry, base, version);
497
+ if (formatted !== void 0) list.push(formatted);
498
+ }
499
+ return list.length > 0 ? list : void 0;
500
+ }
211
501
  /**
212
- * CMCD event type 'ce' (custom event).
502
+ * Formats a `nor` value. Each path becomes relative to `baseUrl` when `baseUrl` is a valid URL.
503
+ * Version 1 encodes the path with `encodeURIComponent`. Version 2 wraps one path in a list.
504
+ * The parameters of an entry and of an inner list stay on the value.
213
505
  *
214
- * @public
506
+ * @param value - A path, an `SfItem` of a path, or a list of them. An `SfItem` can also wrap the list.
507
+ * @param options - The version and `baseUrl` of the report.
508
+ *
509
+ * @returns The formatted value, or `undefined` when no entry is a non-empty string.
510
+ *
511
+ * @internal
215
512
  */
216
- const CMCD_EVENT_CUSTOM_EVENT = "ce";
513
+ function formatNor(value, options) {
514
+ const base = toBase(options.baseUrl);
515
+ const version = options.version;
516
+ if (Array.isArray(value)) return formatEntries(value, base, version);
517
+ if (value instanceof SfItem && Array.isArray(value.value)) {
518
+ const list = formatEntries(value.value, base, version);
519
+ return list && new SfItem(list, value.params);
520
+ }
521
+ const entry = formatEntry(value, base, version);
522
+ return version === 1 || entry === void 0 ? entry : [entry];
523
+ }
524
+
525
+ //#endregion
526
+ //#region src/toTokenString.ts
217
527
  /**
218
- * CMCD event types for the 'e' key (event mode).
528
+ * Resolve the text of a token value.
219
529
  *
220
- * @enum
530
+ * A token may be a plain string, a registry `Symbol`, an `SfToken`, or an
531
+ * `SfItem` that wraps such a value.
221
532
  *
222
- * @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#event | CTA-5004-B Event}
533
+ * @param value - The value to resolve.
223
534
  *
224
- * @public
535
+ * @returns The token text, or `undefined` when the value is not a token.
536
+ *
537
+ * @internal
225
538
  */
226
- const CmcdEventType = {
227
- BITRATE_CHANGE: CMCD_EVENT_BITRATE_CHANGE,
228
- PLAY_STATE: CMCD_EVENT_PLAY_STATE,
229
- PLAYBACK_RATE: CMCD_EVENT_PLAYBACK_RATE,
230
- ERROR: CMCD_EVENT_ERROR,
231
- TIME_INTERVAL: CMCD_EVENT_TIME_INTERVAL,
232
- CONTENT_ID: CMCD_EVENT_CONTENT_ID,
233
- BACKGROUNDED_MODE: CMCD_EVENT_BACKGROUNDED_MODE,
234
- MUTE: CMCD_EVENT_MUTE,
235
- UNMUTE: CMCD_EVENT_UNMUTE,
236
- PLAYER_EXPAND: CMCD_EVENT_PLAYER_EXPAND,
237
- PLAYER_COLLAPSE: CMCD_EVENT_PLAYER_COLLAPSE,
238
- RESPONSE_RECEIVED: CMCD_EVENT_RESPONSE_RECEIVED,
239
- AD_START: CMCD_EVENT_AD_START,
240
- AD_END: CMCD_EVENT_AD_END,
241
- AD_BREAK_START: CMCD_EVENT_AD_BREAK_START,
242
- AD_BREAK_END: CMCD_EVENT_AD_BREAK_END,
243
- SKIP: CMCD_EVENT_SKIP,
244
- CUSTOM_EVENT: CMCD_EVENT_CUSTOM_EVENT
245
- };
539
+ function toTokenString(value) {
540
+ if (value instanceof SfItem) return toTokenString(value.value);
541
+ if (typeof value === "string") return value;
542
+ if (typeof value === "symbol" || value instanceof SfToken) return symbolToStr(value);
543
+ }
246
544
 
247
545
  //#endregion
248
- //#region src/CMCD_STATE_EVENT_FIELDS.ts
546
+ //#region src/normalizeValue.ts
547
+ function toText(value, spec, version) {
548
+ const max = (version === 1 ? spec.v1Max : void 0) ?? spec.max ?? Infinity;
549
+ return typeof value === "string" && value !== "" && value.length <= max ? value : void 0;
550
+ }
551
+ function normalizeBare(value, spec, version) {
552
+ switch (spec.type) {
553
+ case "boolean": return typeof value === "boolean" ? value : void 0;
554
+ case "integer":
555
+ case "ot-list": return typeof value === "number" && Number.isFinite(value) ? Math.round(value / spec.round) * spec.round : void 0;
556
+ case "decimal": return typeof value === "number" && Number.isFinite(value) ? value : void 0;
557
+ case "string": return toText(value, spec, version);
558
+ case "string-list": return typeof value === "string" && value !== "" ? value : void 0;
559
+ case "token": {
560
+ const text = toTokenString(value);
561
+ const v1Map = version === 1 ? spec.v1Map : void 0;
562
+ const token = text !== void 0 && v1Map !== void 0 && Object.prototype.hasOwnProperty.call(v1Map, text) ? v1Map[text] : text;
563
+ return token ? new SfToken(token) : void 0;
564
+ }
565
+ case "custom":
566
+ if (typeof value === "string") return toText(value, spec, version);
567
+ if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
568
+ if (Array.isArray(value)) return value.length > 0 ? value : void 0;
569
+ return typeof value === "boolean" || typeof value === "symbol" || value instanceof SfToken ? value : void 0;
570
+ default: return;
571
+ }
572
+ }
573
+ function normalizeItem(value, spec, version) {
574
+ if (value instanceof SfItem) {
575
+ const bare = normalizeBare(value.value, spec, version);
576
+ return bare === void 0 ? void 0 : new SfItem(bare, value.params);
577
+ }
578
+ return normalizeBare(value, spec, version);
579
+ }
580
+ function normalizeList(value, spec, version) {
581
+ const wrapper = value instanceof SfItem && Array.isArray(value.value) ? value : void 0;
582
+ const items = wrapper ? wrapper.value : Array.isArray(value) ? value : [value];
583
+ const list = [];
584
+ for (const item of items) {
585
+ const normalized = normalizeItem(item, spec, version);
586
+ if (normalized !== void 0) list.push(normalized);
587
+ }
588
+ if (list.length === 0) return;
589
+ return wrapper ? new SfItem(list, wrapper.params) : list;
590
+ }
249
591
  /**
250
- * Maps each state-change event type to the persistent field whose value
251
- * the event signals.
592
+ * Applies the value rule of a key type from the key table.
252
593
  *
253
- * Per CTA-5004-B, the state-change events `ps`, `pr`, `c`, `b`, `bc` are
254
- * state-transition markers. Each event must include the field whose value
255
- * it signals. Callers force-include the field after filtering
256
- * (`prepareCmcdData`), deduplicate against its value (`CmcdReporter`), and
257
- * check its presence in payloads (`validateCmcdStructure`).
594
+ * An `SfItem` keeps its parameters, and the rule applies to the value inside it.
595
+ * In version 2, a list key wraps one value in a list.
596
+ * The rules do not check the limits of RFC 8941, such as the integer range.
258
597
  *
259
- * Iteration order matters: `CmcdReporter.update()` records state-change
260
- * events in map order when several tracked fields change in one call.
261
- * Do not reorder entries without auditing reporter behavior.
598
+ * @param value - The value to prepare.
599
+ * @param spec - The row of the key in the key table.
600
+ * @param options - The version and `baseUrl` of the report.
601
+ *
602
+ * @returns The value to send, or `undefined` when the value fails the rule.
262
603
  *
263
604
  * @internal
264
605
  */
265
- const CMCD_STATE_EVENT_FIELDS = /* @__PURE__ */ new Map([
266
- [CMCD_EVENT_PLAY_STATE, "sta"],
267
- [CMCD_EVENT_PLAYBACK_RATE, "pr"],
268
- [CMCD_EVENT_CONTENT_ID, "cid"],
269
- [CMCD_EVENT_BACKGROUNDED_MODE, "bg"],
270
- [CMCD_EVENT_BITRATE_CHANGE, "br"]
271
- ]);
606
+ function normalizeValue(value, spec, options) {
607
+ if (spec.type === "nor") return formatNor(value, options);
608
+ if (spec.type === "string-list" || spec.type === "ot-list" && options.version !== 1) return normalizeList(value, spec, options.version);
609
+ return normalizeItem(value, spec, options.version);
610
+ }
272
611
 
273
612
  //#endregion
274
- //#region src/CmcdReportingMode.ts
275
- /**
276
- * CMCD event mode variable name.
277
- *
278
- * @public
279
- */
280
- const CMCD_EVENT_MODE = "event";
613
+ //#region src/toBareValue.ts
281
614
  /**
282
- * CMCD request mode variable name.
615
+ * Returns the value inside an `SfItem`, or the value itself.
283
616
  *
284
- * @public
285
- */
286
- const CMCD_REQUEST_MODE = "request";
287
- /**
288
- * CMCD reporting mode types.
617
+ * `decodeCmcd` returns a member with parameters as an `SfItem`. For an
618
+ * inner list with parameters, the value of the `SfItem` is the list.
289
619
  *
290
- * @enum
620
+ * @param value - The value to read.
621
+ * @returns The value without its parameters.
291
622
  *
292
- * @public
623
+ * @internal
293
624
  */
294
- const CmcdReportingMode = {
295
- REQUEST: CMCD_REQUEST_MODE,
296
- EVENT: CMCD_EVENT_MODE
297
- };
625
+ function toBareValue(value) {
626
+ return value instanceof SfItem ? value.value : value;
627
+ }
298
628
 
299
629
  //#endregion
300
- //#region src/CMCD_EVENT_KEYS.ts
630
+ //#region src/prepareCmcdData.ts
631
+ function hasKey(spec, reportingMode, isV1) {
632
+ return (spec.mode === void 0 || spec.mode === reportingMode) && (spec.version === void 0 || spec.version === 1 === isV1);
633
+ }
634
+ function isRequired(spec, event) {
635
+ return spec.requiredOn === "always" || event !== void 0 && spec.requiredOn === event;
636
+ }
637
+ function hasObjectTypes(spec) {
638
+ return (spec.type === "integer" || spec.type === "ot-list") && spec.ot !== void 0;
639
+ }
640
+ function needsV1ObjectType(obj, filter) {
641
+ if (typeof filter !== "function" || filter("ot")) return true;
642
+ for (const key of CMCD_INNER_LIST_KEYS) if (Array.isArray(toBareValue(obj[key])) && filter(key)) return true;
643
+ return false;
644
+ }
645
+ function allowsObjectType(spec, objectType) {
646
+ if (objectType === void 0 || spec.type !== "integer" && spec.type !== "ot-list" || spec.ot === void 0) return true;
647
+ return spec.ot.includes(objectType);
648
+ }
649
+ function isDefaultValue(spec, value) {
650
+ return "omitDefault" in spec && toBareValue(value) === spec.omitDefault;
651
+ }
652
+ function insertKey(keys, specs, key) {
653
+ let index = 0;
654
+ while (index < keys.length && keys[index] < key) index++;
655
+ keys.splice(index, 0, key);
656
+ specs.splice(index, 0, CMCD_KEY_SPECS[key]);
657
+ }
658
+ function prepareValue(key, value, spec, options, formatterOptions) {
659
+ const formatter = options.formatters?.[key];
660
+ if (typeof formatter !== "function") return value == null ? void 0 : normalizeValue(value, spec, formatterOptions);
661
+ const formatted = isValid(value) ? formatter(value, formatterOptions) : value;
662
+ if (!isValid(formatted) && !(formatted === false && spec.type === "boolean")) return;
663
+ if (spec.type === "token") {
664
+ if (typeof formatted === "string") return new SfToken(formatted);
665
+ if (formatted instanceof SfItem && typeof formatted.value === "string") return new SfItem(new SfToken(formatted.value), formatted.params);
666
+ }
667
+ return formatted;
668
+ }
301
669
  /**
302
- * Defines the event-specific keys for CMCD (Common Media Client Data) version 2.
670
+ * Convert a generic object to CMCD data.
671
+ *
672
+ * @param obj - The CMCD object to process.
673
+ * @param options - Options for encoding.
303
674
  *
304
675
  * @public
676
+ *
677
+ * @example
678
+ * {@includeCode ../test/prepareCmcdData.test.ts#example}
305
679
  */
306
- const CMCD_EVENT_KEYS = [
307
- "cen",
308
- "e",
309
- "h",
310
- "ts"
311
- ];
680
+ function prepareCmcdData(obj, options = {}) {
681
+ const results = {};
682
+ if (obj == null || typeof obj !== "object") return results;
683
+ const version = options.version || toBareValue(obj["v"]) || CMCD_V2;
684
+ const reportingMode = options.reportingMode || CMCD_REQUEST_MODE;
685
+ const isV1 = version === 1;
686
+ const isEventReport = !isV1 && reportingMode === CMCD_EVENT_MODE;
687
+ const formatterOptions = {
688
+ version,
689
+ reportingMode,
690
+ baseUrl: options.baseUrl
691
+ };
692
+ const filter = options.filter;
693
+ let objectTypeValue;
694
+ let objectType;
695
+ if (isV1 && needsV1ObjectType(obj, filter)) {
696
+ objectTypeValue = prepareValue("ot", obj["ot"], CMCD_KEY_SPECS["ot"], options, formatterOptions);
697
+ objectType = toTokenString(objectTypeValue);
698
+ }
699
+ const data = isV1 ? downConvertToV1(obj, objectType) : obj;
700
+ const eventValue = isEventReport ? prepareValue("e", data["e"], CMCD_KEY_SPECS["e"], options, formatterOptions) : void 0;
701
+ const event = toTokenString(eventValue);
702
+ const keys = [];
703
+ const specs = [];
704
+ let passed = false;
705
+ let needsObjectType = false;
706
+ for (const key of Object.keys(data).sort()) {
707
+ const spec = getKeySpec(key);
708
+ if (spec === void 0 || !hasKey(spec, reportingMode, isV1)) continue;
709
+ if (typeof filter !== "function" || filter(key)) passed = true;
710
+ else if (!isRequired(spec, event)) continue;
711
+ if (spec.onlyOn !== void 0 && spec.onlyOn !== event) continue;
712
+ if (key === "ot" || hasObjectTypes(spec)) needsObjectType = true;
713
+ keys.push(key);
714
+ specs.push(spec);
715
+ }
716
+ if (!passed && !isEventReport) return results;
717
+ if (needsObjectType && !isV1) {
718
+ objectTypeValue = prepareValue("ot", data["ot"], CMCD_KEY_SPECS["ot"], options, formatterOptions);
719
+ objectType = toTokenString(objectTypeValue);
720
+ }
721
+ if (isEventReport && !keys.includes("ts")) insertKey(keys, specs, "ts");
722
+ if (!isV1 && !keys.includes("v")) insertKey(keys, specs, "v");
723
+ const values = new Array(keys.length);
724
+ for (let i = keys.length - 1; i >= 0; i--) {
725
+ const key = keys[i];
726
+ const spec = specs[i];
727
+ if (spec.type === "ot-list" && spec.supersededBy !== void 0) {
728
+ const exact = keys.indexOf(spec.supersededBy, i + 1);
729
+ if (exact !== -1 && values[exact] !== void 0) continue;
730
+ }
731
+ if (key === "v") values[i] = version;
732
+ else if (key === "ot") values[i] = objectTypeValue;
733
+ else if (key === "e") values[i] = eventValue;
734
+ else if (isV1 || allowsObjectType(spec, objectType)) values[i] = prepareValue(key, data[key], spec, options, formatterOptions);
735
+ if (key === "ts") {
736
+ const timestamp = toBareValue(values[i]);
737
+ if (typeof timestamp !== "number" || !Number.isFinite(timestamp)) values[i] = Date.now();
738
+ }
739
+ }
740
+ for (let i = 0; i < keys.length; i++) {
741
+ const value = values[i];
742
+ if (value !== void 0 && (!isDefaultValue(specs[i], value) || isRequired(specs[i], event))) results[keys[i]] = value;
743
+ }
744
+ return results;
745
+ }
312
746
 
313
747
  //#endregion
314
- //#region src/CMCD_REQUEST_KEYS.ts
748
+ //#region src/CmcdHeaderField.ts
315
749
  /**
316
- * Defines the request-specific keys for CMCD (Common Media Client Data) version 2.
750
+ * CMCD object header name.
317
751
  *
318
752
  * @public
319
753
  */
320
- const CMCD_REQUEST_KEYS = [
321
- "ab",
322
- "bg",
323
- "bl",
324
- "br",
325
- "bs",
326
- "bsa",
327
- "bsd",
328
- "bsda",
329
- "cdn",
330
- "cid",
331
- "cs",
332
- "d",
333
- "dfa",
334
- "dl",
335
- "ec",
336
- "lab",
337
- "lb",
338
- "ltc",
339
- "msd",
340
- "mtp",
341
- "nor",
342
- "nr",
343
- "ot",
344
- "pb",
345
- "pr",
346
- "pt",
347
- "rtp",
348
- "sf",
349
- "sid",
350
- "sn",
351
- "st",
352
- "sta",
353
- "su",
354
- "tab",
355
- "tb",
356
- "tbl",
357
- "tpb",
358
- "v"
359
- ];
360
-
361
- //#endregion
362
- //#region src/isCmcdCustomKey.ts
363
- const CUSTOM_KEY_REGEX = /^[a-z][a-z0-9.-]*$/;
754
+ const CMCD_OBJECT = "CMCD-Object";
364
755
  /**
365
- * Check if a key is a valid custom key: a lowercase first letter, then
366
- * characters from `a-z 0-9 . -`. The key needs a hyphen, not at the start
367
- * or end. This check applies the CTA-5004-B custom-key rules, restricted
368
- * to names that RFC 8941 key serialization accepts.
369
- *
370
- * @param key - The key to check.
371
- *
372
- * @returns `true` if the key is a custom key, `false` otherwise.
756
+ * CMCD request header name.
373
757
  *
374
758
  * @public
375
- *
376
- * @example
377
- * {@includeCode ../test/isCmcdCustomKey.test.ts#example}
378
759
  */
379
- function isCmcdCustomKey(key) {
380
- const separator = key.indexOf("-", 1);
381
- return separator > 0 && separator < key.length - 1 && CUSTOM_KEY_REGEX.test(key);
382
- }
383
-
384
- //#endregion
385
- //#region src/isCmcdRequestKey.ts
386
- const CMCD_REQUEST_KEY_SET = /* @__PURE__ */ new Set(CMCD_REQUEST_KEYS);
760
+ const CMCD_REQUEST = "CMCD-Request";
387
761
  /**
388
- * Check if a key is a valid CMCD request key.
389
- *
390
- * @param key - The key to check.
391
- *
392
- * @returns `true` if the key is a valid CMCD request key, `false` otherwise.
762
+ * CMCD session header name.
393
763
  *
394
764
  * @public
395
- *
396
- * @example
397
- * {@includeCode ../test/isCmcdRequestKey.test.ts#example}
398
765
  */
399
- function isCmcdRequestKey(key) {
400
- return CMCD_REQUEST_KEY_SET.has(key) || isCmcdCustomKey(key);
401
- }
402
-
403
- //#endregion
404
- //#region src/CMCD_RESPONSE_KEYS.ts
766
+ const CMCD_SESSION = "CMCD-Session";
405
767
  /**
406
- * Response-only and timing keys for version 2.
768
+ * CMCD status header name.
407
769
  *
408
770
  * @public
409
771
  */
410
- const CMCD_RESPONSE_KEYS = [
411
- "cmsdd",
412
- "cmsds",
413
- "rc",
414
- "smrt",
415
- "ttfb",
416
- "ttfbb",
417
- "ttlb",
418
- "url"
419
- ];
420
-
421
- //#endregion
422
- //#region src/isCmcdResponseReceivedKey.ts
423
- const CMCD_RESPONSE_KEY_SET = /* @__PURE__ */ new Set(CMCD_RESPONSE_KEYS);
772
+ const CMCD_STATUS = "CMCD-Status";
424
773
  /**
425
- * Check if a key is a valid CMCD response key.
774
+ * CMCD header fields.
426
775
  *
427
- * @param key - The key to check.
428
776
  *
429
- * @returns `true` if the key is a valid CMCD response key, `false` otherwise.
777
+ * @enum
430
778
  *
431
779
  * @public
432
- *
433
- * @example
434
- * {@includeCode ../test/isCmcdResponseReceivedKey.test.ts#example}
435
780
  */
436
- function isCmcdResponseReceivedKey(key) {
437
- return CMCD_RESPONSE_KEY_SET.has(key);
438
- }
439
-
440
- //#endregion
441
- //#region src/isCmcdEventKey.ts
442
- const CMCD_EVENT_KEY_SET = /* @__PURE__ */ new Set(CMCD_EVENT_KEYS);
781
+ const CmcdHeaderField = {
782
+ OBJECT: CMCD_OBJECT,
783
+ REQUEST: CMCD_REQUEST,
784
+ SESSION: CMCD_SESSION,
785
+ STATUS: CMCD_STATUS
786
+ };
443
787
  /**
444
- * Check if a key is a valid CMCD event key.
445
- *
446
- * @param key - The key to check.
447
- *
448
- * @returns `true` if the key is a valid CMCD event key, `false` otherwise.
788
+ * All CMCD header fields as an array.
449
789
  *
450
790
  * @public
451
- *
452
- * @example
453
- * {@includeCode ../test/isCmcdEventKey.test.ts#example}
454
791
  */
455
- function isCmcdEventKey(key) {
456
- return isCmcdRequestKey(key) || isCmcdResponseReceivedKey(key) || CMCD_EVENT_KEY_SET.has(key);
457
- }
792
+ const CMCD_HEADER_FIELDS = [
793
+ CMCD_OBJECT,
794
+ CMCD_REQUEST,
795
+ CMCD_SESSION,
796
+ CMCD_STATUS
797
+ ];
458
798
 
459
799
  //#endregion
460
- //#region src/CMCD_INNER_LIST_KEYS.ts
800
+ //#region src/CMCD_HEADER_MAP.ts
461
801
  /**
462
- * Inner-list keys in version 2 that are scalars in version 1.
463
- *
464
- * Used by both encoding (down-conversion) and decoding (up-conversion).
802
+ * The map of CMCD keys to their appropriate header shard.
465
803
  *
466
- * @internal
467
- */
468
- const CMCD_INNER_LIST_KEYS = new Set([
469
- "ab",
470
- "bl",
471
- "br",
472
- "bsa",
473
- "bsd",
474
- "bsda",
475
- "lab",
476
- "lb",
477
- "mtp",
478
- "pb",
479
- "tab",
480
- "tb",
481
- "tbl",
482
- "tpb"
483
- ]);
484
-
485
- //#endregion
486
- //#region src/CMCD_V1_KEYS.ts
487
- /**
488
- * Defines the keys for CMCD (Common Media Client Data) version 1.
804
+ * Event-only keys (e, ts, cen, h) and response-received keys (rc, ttfb,
805
+ * ttlb, url, and others) are intentionally absent. They are transmitted
806
+ * in the event-mode POST body, not HTTP headers.
489
807
  *
490
808
  * @public
491
809
  */
492
- const CMCD_V1_KEYS = [
493
- "bl",
494
- "br",
495
- "bs",
496
- "cid",
497
- "d",
498
- "dl",
499
- "mtp",
500
- "nor",
501
- "nrr",
502
- "ot",
503
- "pr",
504
- "rtp",
505
- "sf",
506
- "sid",
507
- "st",
508
- "su",
509
- "tb",
510
- "v"
511
- ];
810
+ const CMCD_HEADER_MAP = {
811
+ ab: CMCD_OBJECT,
812
+ br: CMCD_OBJECT,
813
+ d: CMCD_OBJECT,
814
+ lab: CMCD_OBJECT,
815
+ lb: CMCD_OBJECT,
816
+ ot: CMCD_OBJECT,
817
+ tab: CMCD_OBJECT,
818
+ tb: CMCD_OBJECT,
819
+ tpb: CMCD_OBJECT,
820
+ bl: CMCD_REQUEST,
821
+ cs: CMCD_REQUEST,
822
+ dfa: CMCD_REQUEST,
823
+ dl: CMCD_REQUEST,
824
+ ltc: CMCD_REQUEST,
825
+ mtp: CMCD_REQUEST,
826
+ nor: CMCD_REQUEST,
827
+ nrr: CMCD_REQUEST,
828
+ pb: CMCD_REQUEST,
829
+ sn: CMCD_REQUEST,
830
+ sta: CMCD_REQUEST,
831
+ su: CMCD_REQUEST,
832
+ tbl: CMCD_REQUEST,
833
+ cid: CMCD_SESSION,
834
+ msd: CMCD_SESSION,
835
+ sf: CMCD_SESSION,
836
+ sid: CMCD_SESSION,
837
+ st: CMCD_SESSION,
838
+ v: CMCD_SESSION,
839
+ bg: CMCD_STATUS,
840
+ bs: CMCD_STATUS,
841
+ bsa: CMCD_STATUS,
842
+ bsd: CMCD_STATUS,
843
+ bsda: CMCD_STATUS,
844
+ cdn: CMCD_STATUS,
845
+ ec: CMCD_STATUS,
846
+ nr: CMCD_STATUS,
847
+ pr: CMCD_STATUS,
848
+ pt: CMCD_STATUS,
849
+ rtp: CMCD_STATUS
850
+ };
512
851
 
513
852
  //#endregion
514
- //#region src/isCmcdV1Key.ts
515
- const CMCD_V1_KEY_SET$1 = /* @__PURE__ */ new Set(CMCD_V1_KEYS);
853
+ //#region src/groupCmcdHeaders.ts
854
+ function createHeaderMap(headerMap) {
855
+ return Object.keys(headerMap).reduce((acc, field) => {
856
+ headerMap[field]?.forEach((key) => acc[key] = field);
857
+ return acc;
858
+ }, {});
859
+ }
516
860
  /**
517
- * Filter for CMCD version 1 keys.
861
+ * Group a CMCD object into header shards
518
862
  *
519
- * @param key - The CMCD key to filter.
863
+ * @param cmcd - The CMCD object to convert.
864
+ * @param customHeaderMap - A map of CMCD header fields to custom CMCD keys.
520
865
  *
521
- * @returns `true` if the key should be included, `false` otherwise.
866
+ * @returns The CMCD header shards.
522
867
  *
523
- * @public
868
+ * @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#header-field-definition | CTA-5004-B Header Field Definition}
524
869
  *
525
- * @example
526
- * {@includeCode ../test/isCmcdV1Key.test.ts#example}
870
+ * @public
527
871
  */
528
- function isCmcdV1Key(key) {
529
- return CMCD_V1_KEY_SET$1.has(key) || isCmcdCustomKey(key);
872
+ function groupCmcdHeaders(cmcd, customHeaderMap) {
873
+ const result = {};
874
+ if (!cmcd) return result;
875
+ const keys = Object.keys(cmcd);
876
+ const custom = customHeaderMap ? createHeaderMap(customHeaderMap) : {};
877
+ for (const key of keys) {
878
+ const field = CMCD_HEADER_MAP[key] || custom[key] || CmcdHeaderField.REQUEST;
879
+ const data = result[field] ??= {};
880
+ data[key] = cmcd[key];
881
+ }
882
+ return result;
530
883
  }
531
884
 
532
885
  //#endregion
533
- //#region src/isTokenField.ts
534
- const TOKEN_FIELDS = /* @__PURE__ */ new Set([
535
- "ot",
536
- "sf",
537
- "st",
538
- "e",
539
- "sta"
540
- ]);
886
+ //#region src/toPreparedCmcdHeaders.ts
541
887
  /**
542
- * Checks if the given key is a token field.
543
- *
544
- * @param key - The key to check.
888
+ * Encode already-prepared CMCD data to CMCD header shards.
545
889
  *
546
- * @returns `true` if the key is a token field.
890
+ * @param data - The prepared CMCD data to encode.
891
+ * @param customHeaderMap - A map of CMCD header fields to custom CMCD keys.
892
+ * @returns The CMCD header shards.
547
893
  *
548
894
  * @internal
549
895
  */
550
- function isTokenField(key) {
551
- return TOKEN_FIELDS.has(key);
896
+ function toPreparedCmcdHeaders(data, customHeaderMap) {
897
+ const result = {};
898
+ const shards = groupCmcdHeaders(data, customHeaderMap);
899
+ for (const [field, value] of Object.entries(shards)) {
900
+ const shard = encodeSfDict(value, { whitespace: false });
901
+ if (shard) result[field] = shard;
902
+ }
903
+ return result;
552
904
  }
553
905
 
554
906
  //#endregion
555
- //#region src/isValid.ts
907
+ //#region src/toCmcdHeaders.ts
556
908
  /**
557
- * Checks if the given value is valid to send as a CMCD value.
909
+ * Convert a CMCD object to request headers
558
910
  *
559
- * `undefined`, `null`, an empty string, `false`, an empty array, and a
560
- * number that is not finite are not valid. CTA-5004-B requires omitting
561
- * the key when its value is unknown.
911
+ * @param cmcd - The CMCD object to convert.
912
+ * @param options - Options for encoding.
562
913
  *
563
- * @param value - The value to check.
914
+ * @returns The CMCD header shards.
564
915
  *
565
- * @returns `true` if the value is valid.
916
+ * @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#header-field-definition | CTA-5004-B Header Field Definition}
566
917
  *
567
- * @internal
918
+ * @public
919
+ *
920
+ * @example
921
+ * {@includeCode ../test/toCmcdHeaders.test.ts#example}
568
922
  */
569
- function isValid(value) {
570
- if (typeof value === "number") return Number.isFinite(value);
571
- if (Array.isArray(value)) return value.length > 0;
572
- return value != null && value !== "" && value !== false;
923
+ function toCmcdHeaders(cmcd, options = {}) {
924
+ if (!cmcd) return {};
925
+ return toPreparedCmcdHeaders(prepareCmcdData(cmcd, options), options?.customHeaderMap);
573
926
  }
574
927
 
575
928
  //#endregion
576
- //#region src/toTokenString.ts
929
+ //#region src/appendCmcdHeaders.ts
577
930
  /**
578
- * Resolve the text of a token value.
931
+ * Append CMCD header shards to a header object.
579
932
  *
580
- * A token may be a plain string, a registry `Symbol`, an `SfToken`, or an
581
- * `SfItem` that wraps such a value.
933
+ * @param headers - The headers to append to.
934
+ * @param cmcd - The CMCD object to append.
935
+ * @param options - Options for encoding.
582
936
  *
583
- * @param value - The value to resolve.
937
+ * @returns The headers with CMCD header shards appended.
584
938
  *
585
- * @returns The token text, or `undefined` when the value is not a token.
939
+ * @public
586
940
  *
587
- * @internal
941
+ * @example
942
+ * {@includeCode ../test/appendCmcdHeaders.test.ts#example}
943
+ *
944
+ * @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#header-field-definition | CTA-5004-B Header Field Definition}
588
945
  */
589
- function toTokenString(value) {
590
- if (value instanceof SfItem) return toTokenString(value.value);
591
- if (typeof value === "string") return value;
592
- if (typeof value === "symbol" || value instanceof SfToken) return symbolToStr(value);
946
+ function appendCmcdHeaders(headers, cmcd, options) {
947
+ return Object.assign(headers, toCmcdHeaders(cmcd, options));
593
948
  }
594
949
 
595
950
  //#endregion
596
- //#region src/prepareCmcdData.ts
597
- const filterMap = {
598
- event: isCmcdEventKey,
599
- request: isCmcdRequestKey
600
- };
951
+ //#region src/CMCD_PARAM.ts
601
952
  /**
602
- * Unwrap an inner list or SfItem value to a scalar.
953
+ * CMCD parameter name.
954
+ *
955
+ * @public
603
956
  */
604
- function unwrapValue(value, ot) {
605
- if (Array.isArray(value)) {
606
- let item;
607
- const otText = toTokenString(ot);
608
- if (otText) item = value.find((item$1) => toTokenString(item$1.params?.ot) === otText);
609
- if (!item) item = value[0];
610
- return unwrapValue(item);
611
- }
612
- if (value instanceof SfItem) return value.value;
613
- return value;
614
- }
957
+ const CMCD_PARAM = "CMCD";
958
+
959
+ //#endregion
960
+ //#region src/encodePreparedCmcd.ts
615
961
  /**
616
- * Down-convert version 2 CMCD data to version 1.
962
+ * Encode already-prepared CMCD data to a structured field dictionary string.
617
963
  *
618
- * - Extracts `nrr` from the `nor` SfItem's `r` parameter.
619
- * - Unwraps inner-list values to scalars.
964
+ * @param data - The prepared CMCD data to encode.
965
+ * @returns The encoded CMCD string.
966
+ *
967
+ * @internal
620
968
  */
621
- function downConvertToV1(obj) {
622
- const result = {};
623
- for (const [key, value] of Object.entries(obj)) {
624
- if (value == null) {
625
- result[key] = value;
626
- continue;
627
- }
628
- if (key === "nor") {
629
- const first = (Array.isArray(value) ? value : [value])[0];
630
- if (first instanceof SfItem) {
631
- result["nor"] = first.value;
632
- if (first.params?.r) result["nrr"] = first.params.r;
633
- } else result["nor"] = first;
634
- } else if (CMCD_INNER_LIST_KEYS.has(key)) result[key] = unwrapValue(value, obj["ot"]);
635
- else result[key] = value;
636
- }
637
- return result;
638
- }
639
- function formatValue(key, value, options, formatterOptions) {
640
- const formatter = options.formatters?.[key] ?? CMCD_FORMATTER_MAP[key];
641
- return typeof formatter === "function" && isValid(value) ? formatter(value, formatterOptions) : value;
969
+ function encodePreparedCmcd(data) {
970
+ return encodeSfDict(data, { whitespace: false });
642
971
  }
972
+
973
+ //#endregion
974
+ //#region src/encodeCmcd.ts
643
975
  /**
644
- * Convert a generic object to CMCD data.
976
+ * Encode a CMCD object to a string.
645
977
  *
646
- * @param obj - The CMCD object to process.
978
+ * @param cmcd - The CMCD object to encode.
647
979
  * @param options - Options for encoding.
648
980
  *
981
+ * @returns The encoded CMCD string.
982
+ *
983
+ * @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#payload-definition-for-headers-and-query-argument-transmission | CTA-5004-B Payload Definition}
984
+ *
649
985
  * @public
650
986
  *
651
987
  * @example
652
- * {@includeCode ../test/prepareCmcdData.test.ts#example}
988
+ * {@includeCode ../test/encodeCmcd.test.ts#example}
653
989
  */
654
- function prepareCmcdData(obj, options = {}) {
655
- const results = {};
656
- if (obj == null || typeof obj !== "object") return results;
657
- const version = options.version || obj["v"] || CMCD_V2;
658
- const reportingMode = options.reportingMode || CMCD_REQUEST_MODE;
659
- const data = version === 1 ? downConvertToV1(obj) : obj;
660
- const eventType = toTokenString(data["e"]);
661
- const keyFilter = version === 1 ? isCmcdV1Key : filterMap[reportingMode];
662
- let keys = Object.keys(data).filter(keyFilter);
663
- if (data["e"] && eventType !== CMCD_EVENT_RESPONSE_RECEIVED) keys = keys.filter((key) => !isCmcdResponseReceivedKey(key));
664
- const filter = options.filter;
665
- if (typeof filter === "function") keys = keys.filter(filter);
666
- const isEventMode = reportingMode === CMCD_EVENT_MODE;
667
- if (isEventMode) {
668
- if (!keys.includes("e") && data["e"] != null) keys.push("e");
669
- if (!keys.includes("ts")) keys.push("ts");
670
- if (!keys.includes("cen") && data["cen"] != null && eventType === CMCD_EVENT_CUSTOM_EVENT) keys.push("cen");
671
- if (!keys.includes("ec") && data["ec"] != null && eventType === CMCD_EVENT_ERROR) keys.push("ec");
672
- if (!keys.includes("url") && data["url"] != null && eventType === CMCD_EVENT_RESPONSE_RECEIVED) keys.push("url");
673
- const requiredField = eventType ? CMCD_STATE_EVENT_FIELDS.get(eventType) : void 0;
674
- if (requiredField && data[requiredField] != null && !keys.includes(requiredField)) keys.push(requiredField);
675
- }
676
- if (keys.length === 0) return results;
677
- if (version > 1 && !keys.includes("v")) keys.push("v");
678
- const formatterOptions = {
679
- version,
680
- reportingMode,
681
- baseUrl: options.baseUrl
682
- };
683
- keys.sort();
684
- let objectType;
685
- let objectTypeResolved = false;
686
- for (const key of keys) {
687
- let value = data[key];
688
- const exactKey = CMCD_AGGREGATE_BITRATE_KEYS[key];
689
- if (exactKey && keys.includes(exactKey) && isValid(formatValue(exactKey, data[exactKey], options, formatterOptions))) continue;
690
- const objectTypes = version > 1 ? CMCD_KEY_OBJECT_TYPES[key] : void 0;
691
- if (objectTypes) {
692
- if (!objectTypeResolved) {
693
- const ot = formatValue("ot", data["ot"], options, formatterOptions);
694
- objectType = isValid(ot) ? toTokenString(ot) : void 0;
695
- objectTypeResolved = true;
696
- }
697
- if (objectType !== void 0 && !objectTypes.includes(objectType)) continue;
698
- }
699
- if (key === "cen" && eventType !== CMCD_EVENT_CUSTOM_EVENT) continue;
700
- value = formatValue(key, value, options, formatterOptions);
701
- if (key === "v") if (version === 1) continue;
702
- else value = version;
703
- if (key === "pr" && value === 1 && !(isEventMode && eventType === CMCD_EVENT_PLAYBACK_RATE)) continue;
704
- if (isEventMode && key === "ts" && !Number.isFinite(value)) value = Date.now();
705
- const isBgFalseTransition = isEventMode && value === false && key === "bg" && eventType === CMCD_EVENT_BACKGROUNDED_MODE;
706
- if (!isValid(value) && !isBgFalseTransition) continue;
707
- if (isTokenField(key)) {
708
- if (typeof value === "string") value = new SfToken(value);
709
- else if (value instanceof SfItem && typeof value.value === "string") value = new SfItem(new SfToken(value.value), value.params);
710
- }
711
- results[key] = value;
712
- }
713
- return results;
990
+ function encodeCmcd(cmcd, options = {}) {
991
+ if (!cmcd) return "";
992
+ return encodePreparedCmcd(prepareCmcdData(cmcd, options));
714
993
  }
715
994
 
716
995
  //#endregion
717
- //#region src/CmcdHeaderField.ts
996
+ //#region src/toCmcdUrl.ts
718
997
  /**
719
- * CMCD object header name.
998
+ * Convert a CMCD object to a URL encoded string.
999
+ *
1000
+ * @param cmcd - The CMCD object to convert.
1001
+ * @param options - Options for encoding.
1002
+ *
1003
+ * @returns The URL encoded CMCD data.
1004
+ *
1005
+ * @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#query-argument-definition | CTA-5004-B Query Argument Definition}
720
1006
  *
721
1007
  * @public
722
1008
  */
723
- const CMCD_OBJECT = "CMCD-Object";
1009
+ function toCmcdUrl(cmcd, options = {}) {
1010
+ if (!cmcd) return "";
1011
+ const params = encodeCmcd(cmcd, options);
1012
+ return encodeURIComponent(params);
1013
+ }
1014
+
1015
+ //#endregion
1016
+ //#region src/toCmcdQuery.ts
724
1017
  /**
725
- * CMCD request header name.
1018
+ * Convert a CMCD object to a query arg.
1019
+ *
1020
+ * @param cmcd - The CMCD object to convert.
1021
+ * @param options - Options for encoding.
1022
+ *
1023
+ * @returns The CMCD query arg.
1024
+ *
1025
+ * @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#query-argument-definition | CTA-5004-B Query Argument Definition}
726
1026
  *
727
1027
  * @public
1028
+ *
1029
+ * @example
1030
+ * {@includeCode ../test/toCmcdQuery.test.ts#example}
728
1031
  */
729
- const CMCD_REQUEST = "CMCD-Request";
1032
+ function toCmcdQuery(cmcd, options = {}) {
1033
+ if (!cmcd) return "";
1034
+ return `${CMCD_PARAM}=${toCmcdUrl(cmcd, options)}`;
1035
+ }
1036
+
1037
+ //#endregion
1038
+ //#region src/appendCmcdQuery.ts
1039
+ const REGEX = /CMCD=[^&#]+/;
730
1040
  /**
731
- * CMCD session header name.
1041
+ * Append CMCD query args to a URL.
1042
+ *
1043
+ * @param url - The URL to append to.
1044
+ * @param cmcd - The CMCD object to append.
1045
+ * @param options - Options for encoding.
1046
+ *
1047
+ * @returns The URL with the CMCD query args appended.
732
1048
  *
733
1049
  * @public
1050
+ *
1051
+ * @example
1052
+ * {@includeCode ../test/appendCmcdQuery.test.ts#example}
1053
+ *
1054
+ * @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#query-argument-definition | CTA-5004-B Query Argument Definition}
734
1055
  */
735
- const CMCD_SESSION = "CMCD-Session";
1056
+ function appendCmcdQuery(url, cmcd, options) {
1057
+ const query = toCmcdQuery(cmcd, options);
1058
+ if (!query) return url;
1059
+ if (REGEX.test(url)) return url.replace(REGEX, query);
1060
+ return `${url}${url.includes("?") ? "&" : "?"}${query}`;
1061
+ }
1062
+
1063
+ //#endregion
1064
+ //#region src/CMCD_DEFAULT_TIME_INTERVAL.ts
736
1065
  /**
737
- * CMCD status header name.
1066
+ * The default time interval in seconds when using event mode
738
1067
  *
739
1068
  * @public
740
1069
  */
741
- const CMCD_STATUS = "CMCD-Status";
1070
+ const CMCD_DEFAULT_TIME_INTERVAL = 30;
1071
+
1072
+ //#endregion
1073
+ //#region src/CMCD_EVENT_KEYS.ts
742
1074
  /**
743
- * CMCD header fields.
1075
+ * Defines the event-specific keys for CMCD (Common Media Client Data) version 2.
744
1076
  *
1077
+ * @public
1078
+ */
1079
+ const CMCD_EVENT_KEYS = [
1080
+ "cen",
1081
+ "e",
1082
+ "h",
1083
+ "ts"
1084
+ ];
1085
+
1086
+ //#endregion
1087
+ //#region src/CMCD_FORMATTER_MAP.ts
1088
+ const formatNumbers = (value, format) => {
1089
+ if (Array.isArray(value)) {
1090
+ const list = [];
1091
+ for (const item of value) {
1092
+ const formatted = formatNumbers(item, format);
1093
+ if (Number.isFinite(formatted instanceof SfItem ? formatted.value : formatted)) list.push(formatted);
1094
+ }
1095
+ return list;
1096
+ }
1097
+ if (value instanceof SfItem) {
1098
+ const formatted = formatNumbers(value.value, format);
1099
+ return isValid(formatted) ? new SfItem(formatted, value.params) : NaN;
1100
+ }
1101
+ return typeof value === "number" ? format(value) : NaN;
1102
+ };
1103
+ const toRounded = (value) => formatNumbers(value, Math.round);
1104
+ const toNumber = (value) => formatNumbers(value, Number);
1105
+ const roundToHundred = (value) => Math.round(value / 100) * 100;
1106
+ const toHundred = (value) => formatNumbers(value, roundToHundred);
1107
+ /**
1108
+ * Formatters for CMCD values.
745
1109
  *
746
- * @enum
1110
+ * The encoder applies the same rounding through its key table, and `nor` is the `nor` rule of the encoder.
1111
+ * Use an entry to build a custom formatter for `CmcdEncodeOptions.formatters`.
1112
+ * The formatter of a numeric key drops a value or a list element that is not a finite number.
747
1113
  *
748
1114
  * @public
749
1115
  */
750
- const CmcdHeaderField = {
751
- OBJECT: CMCD_OBJECT,
752
- REQUEST: CMCD_REQUEST,
753
- SESSION: CMCD_SESSION,
754
- STATUS: CMCD_STATUS
1116
+ const CMCD_FORMATTER_MAP = {
1117
+ br: toRounded,
1118
+ d: toRounded,
1119
+ bl: toHundred,
1120
+ dl: toHundred,
1121
+ mtp: toHundred,
1122
+ nor: (value, options) => formatNor(value, options) ?? "",
1123
+ rtp: toHundred,
1124
+ tb: toRounded,
1125
+ tbl: toHundred,
1126
+ ab: toRounded,
1127
+ bsa: toRounded,
1128
+ bsd: toRounded,
1129
+ bsda: toRounded,
1130
+ dfa: toRounded,
1131
+ lab: toRounded,
1132
+ lb: toRounded,
1133
+ ltc: toRounded,
1134
+ msd: toRounded,
1135
+ pb: toRounded,
1136
+ pr: toNumber,
1137
+ pt: toRounded,
1138
+ rc: toRounded,
1139
+ sn: toRounded,
1140
+ tab: toRounded,
1141
+ tpb: toRounded,
1142
+ ts: toRounded,
1143
+ ttfb: toRounded,
1144
+ ttfbb: toRounded,
1145
+ ttlb: toRounded
755
1146
  };
1147
+
1148
+ //#endregion
1149
+ //#region src/CMCD_REQUEST_KEYS.ts
756
1150
  /**
757
- * All CMCD header fields as an array.
1151
+ * Defines the request-specific keys for CMCD (Common Media Client Data) version 2.
758
1152
  *
759
1153
  * @public
760
1154
  */
761
- const CMCD_HEADER_FIELDS = [
762
- CMCD_OBJECT,
763
- CMCD_REQUEST,
764
- CMCD_SESSION,
765
- CMCD_STATUS
1155
+ const CMCD_REQUEST_KEYS = [
1156
+ "ab",
1157
+ "bg",
1158
+ "bl",
1159
+ "br",
1160
+ "bs",
1161
+ "bsa",
1162
+ "bsd",
1163
+ "bsda",
1164
+ "cdn",
1165
+ "cid",
1166
+ "cs",
1167
+ "d",
1168
+ "dfa",
1169
+ "dl",
1170
+ "ec",
1171
+ "lab",
1172
+ "lb",
1173
+ "ltc",
1174
+ "msd",
1175
+ "mtp",
1176
+ "nor",
1177
+ "nr",
1178
+ "ot",
1179
+ "pb",
1180
+ "pr",
1181
+ "pt",
1182
+ "rtp",
1183
+ "sf",
1184
+ "sid",
1185
+ "sn",
1186
+ "st",
1187
+ "sta",
1188
+ "su",
1189
+ "tab",
1190
+ "tb",
1191
+ "tbl",
1192
+ "tpb",
1193
+ "v"
766
1194
  ];
767
1195
 
768
1196
  //#endregion
769
- //#region src/CMCD_HEADER_MAP.ts
1197
+ //#region src/CMCD_RESPONSE_KEYS.ts
770
1198
  /**
771
- * The map of CMCD keys to their appropriate header shard.
772
- *
773
- * Event-only keys (e, ts, cen, h) and response-received keys (rc, ttfb,
774
- * ttlb, url, and others) are intentionally absent. They are transmitted
775
- * in the event-mode POST body, not HTTP headers.
1199
+ * Response-only and timing keys for version 2.
776
1200
  *
777
1201
  * @public
778
1202
  */
779
- const CMCD_HEADER_MAP = {
780
- ab: CMCD_OBJECT,
781
- br: CMCD_OBJECT,
782
- d: CMCD_OBJECT,
783
- lab: CMCD_OBJECT,
784
- lb: CMCD_OBJECT,
785
- ot: CMCD_OBJECT,
786
- tab: CMCD_OBJECT,
787
- tb: CMCD_OBJECT,
788
- tpb: CMCD_OBJECT,
789
- bl: CMCD_REQUEST,
790
- cs: CMCD_REQUEST,
791
- dfa: CMCD_REQUEST,
792
- dl: CMCD_REQUEST,
793
- ltc: CMCD_REQUEST,
794
- mtp: CMCD_REQUEST,
795
- nor: CMCD_REQUEST,
796
- nrr: CMCD_REQUEST,
797
- pb: CMCD_REQUEST,
798
- sn: CMCD_REQUEST,
799
- sta: CMCD_REQUEST,
800
- su: CMCD_REQUEST,
801
- tbl: CMCD_REQUEST,
802
- cid: CMCD_SESSION,
803
- msd: CMCD_SESSION,
804
- sf: CMCD_SESSION,
805
- sid: CMCD_SESSION,
806
- st: CMCD_SESSION,
807
- v: CMCD_SESSION,
808
- bg: CMCD_STATUS,
809
- bs: CMCD_STATUS,
810
- bsa: CMCD_STATUS,
811
- bsd: CMCD_STATUS,
812
- bsda: CMCD_STATUS,
813
- cdn: CMCD_STATUS,
814
- ec: CMCD_STATUS,
815
- nr: CMCD_STATUS,
816
- pr: CMCD_STATUS,
817
- pt: CMCD_STATUS,
818
- rtp: CMCD_STATUS
819
- };
1203
+ const CMCD_RESPONSE_KEYS = [
1204
+ "cmsdd",
1205
+ "cmsds",
1206
+ "rc",
1207
+ "smrt",
1208
+ "ttfb",
1209
+ "ttfbb",
1210
+ "ttlb",
1211
+ "url"
1212
+ ];
820
1213
 
821
1214
  //#endregion
822
- //#region src/groupCmcdHeaders.ts
823
- function createHeaderMap(headerMap) {
824
- return Object.keys(headerMap).reduce((acc, field) => {
825
- headerMap[field]?.forEach((key) => acc[key] = field);
826
- return acc;
827
- }, {});
828
- }
1215
+ //#region src/CMCD_V1_KEYS.ts
829
1216
  /**
830
- * Group a CMCD object into header shards
831
- *
832
- * @param cmcd - The CMCD object to convert.
833
- * @param customHeaderMap - A map of CMCD header fields to custom CMCD keys.
834
- *
835
- * @returns The CMCD header shards.
836
- *
837
- * @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#header-field-definition | CTA-5004-B Header Field Definition}
1217
+ * Defines the keys for CMCD (Common Media Client Data) version 1.
838
1218
  *
839
1219
  * @public
840
1220
  */
841
- function groupCmcdHeaders(cmcd, customHeaderMap) {
842
- const result = {};
843
- if (!cmcd) return result;
844
- const keys = Object.keys(cmcd);
845
- const custom = customHeaderMap ? createHeaderMap(customHeaderMap) : {};
846
- for (const key of keys) {
847
- const field = CMCD_HEADER_MAP[key] || custom[key] || CmcdHeaderField.REQUEST;
848
- const data = result[field] ??= {};
849
- data[key] = cmcd[key];
850
- }
851
- return result;
852
- }
1221
+ const CMCD_V1_KEYS = [
1222
+ "bl",
1223
+ "br",
1224
+ "bs",
1225
+ "cid",
1226
+ "d",
1227
+ "dl",
1228
+ "mtp",
1229
+ "nor",
1230
+ "nrr",
1231
+ "ot",
1232
+ "pr",
1233
+ "rtp",
1234
+ "sf",
1235
+ "sid",
1236
+ "st",
1237
+ "su",
1238
+ "tb",
1239
+ "v"
1240
+ ];
853
1241
 
854
1242
  //#endregion
855
- //#region src/toPreparedCmcdHeaders.ts
1243
+ //#region src/CMCD_KEYS.ts
856
1244
  /**
857
- * Encode already-prepared CMCD data to CMCD header shards.
858
- *
859
- * @param data - The prepared CMCD data to encode.
860
- * @param customHeaderMap - A map of CMCD header fields to custom CMCD keys.
861
- * @returns The CMCD header shards.
1245
+ * A list of all CMCD keys.
862
1246
  *
863
- * @internal
1247
+ * @public
864
1248
  */
865
- function toPreparedCmcdHeaders(data, customHeaderMap) {
866
- const result = {};
867
- const shards = groupCmcdHeaders(data, customHeaderMap);
868
- for (const [field, value] of Object.entries(shards)) {
869
- const shard = encodeSfDict(value, { whitespace: false });
870
- if (shard) result[field] = shard;
871
- }
872
- return result;
873
- }
1249
+ const CMCD_KEYS = /* @__PURE__ */ [
1250
+ ...CMCD_V1_KEYS,
1251
+ ...CMCD_REQUEST_KEYS,
1252
+ ...CMCD_RESPONSE_KEYS,
1253
+ ...CMCD_EVENT_KEYS
1254
+ ].filter((key, index, arr) => arr.indexOf(key) === index);
874
1255
 
875
1256
  //#endregion
876
- //#region src/toCmcdHeaders.ts
1257
+ //#region src/CMCD_MIME_TYPE.ts
877
1258
  /**
878
- * Convert a CMCD object to request headers
879
- *
880
- * @param cmcd - The CMCD object to convert.
881
- * @param options - Options for encoding.
882
- *
883
- * @returns The CMCD header shards.
884
- *
885
- * @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#header-field-definition | CTA-5004-B Header Field Definition}
1259
+ * CMCD MIME type for event report payloads.
886
1260
  *
887
1261
  * @public
888
- *
889
- * @example
890
- * {@includeCode ../test/toCmcdHeaders.test.ts#example}
891
1262
  */
892
- function toCmcdHeaders(cmcd, options = {}) {
893
- if (!cmcd) return {};
894
- return toPreparedCmcdHeaders(prepareCmcdData(cmcd, options), options?.customHeaderMap);
895
- }
1263
+ const CMCD_MIME_TYPE = "application/cmcd";
896
1264
 
897
1265
  //#endregion
898
- //#region src/appendCmcdHeaders.ts
1266
+ //#region src/CMCD_REQUEST_PROVENANCE.ts
899
1267
  /**
900
- * Append CMCD header shards to a header object.
1268
+ * The `customData` property where `CmcdReporter.createRequestReport()`
1269
+ * writes session provenance on every request it returns.
901
1270
  *
902
- * @param headers - The headers to append to.
903
- * @param cmcd - The CMCD object to append.
904
- * @param options - Options for encoding.
1271
+ * The value is a frozen `CmcdRequestProvenance` record. `sid` names the
1272
+ * session that issued the request. `cid` is the content id at the time the
1273
+ * request was issued. `data` is the request's per-call data as an encoded
1274
+ * CMCD string. `CmcdReporter.recordResponseReceived()` reads the record to
1275
+ * attribute a late response to its session and to rebuild the request-time
1276
+ * report keys.
1277
+ * Spread and `Object.assign` copy the record into ordinary request clones.
1278
+ * `JSON.stringify` and structured clone drop symbol-keyed properties. Before
1279
+ * either operation, read the value and restore it afterward. JSON preserves
1280
+ * the record's fields. Attribution uses the `sid` value rather than object
1281
+ * identity, so a parsed plain-object copy attributes correctly. For a record
1282
+ * naming a `sid` the reporter does not retain, the reporter drops the
1283
+ * response rather than reattributing it.
905
1284
  *
906
- * @returns The headers with CMCD header shards appended.
1285
+ * The symbol registry provides the symbol, so duplicated copies of this
1286
+ * library in one bundle interoperate. The registry key is stable across
1287
+ * versions.
907
1288
  *
908
1289
  * @public
909
- *
910
- * @example
911
- * {@includeCode ../test/appendCmcdHeaders.test.ts#example}
912
- *
913
- * @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#header-field-definition | CTA-5004-B Header Field Definition}
914
1290
  */
915
- function appendCmcdHeaders(headers, cmcd, options) {
916
- return Object.assign(headers, toCmcdHeaders(cmcd, options));
917
- }
1291
+ const CMCD_REQUEST_PROVENANCE = /* @__PURE__ */ Symbol.for("@svta/cml-cmcd/request-provenance");
918
1292
 
919
1293
  //#endregion
920
- //#region src/CMCD_PARAM.ts
1294
+ //#region src/CMCD_V1.ts
921
1295
  /**
922
- * CMCD parameter name.
1296
+ * CMCD Version 1
923
1297
  *
924
1298
  * @public
925
1299
  */
926
- const CMCD_PARAM = "CMCD";
1300
+ const CMCD_V1 = 1;
927
1301
 
928
1302
  //#endregion
929
- //#region src/encodePreparedCmcd.ts
1303
+ //#region src/CmcdEventType.ts
930
1304
  /**
931
- * Encode already-prepared CMCD data to a structured field dictionary string.
932
- *
933
- * @param data - The prepared CMCD data to encode.
934
- * @returns The encoded CMCD string.
1305
+ * CMCD event type 'bc' (bitrate change).
935
1306
  *
936
- * @internal
1307
+ * @public
937
1308
  */
938
- function encodePreparedCmcd(data) {
939
- return encodeSfDict(data, { whitespace: false });
940
- }
941
-
942
- //#endregion
943
- //#region src/encodeCmcd.ts
1309
+ const CMCD_EVENT_BITRATE_CHANGE = "bc";
944
1310
  /**
945
- * Encode a CMCD object to a string.
946
- *
947
- * @param cmcd - The CMCD object to encode.
948
- * @param options - Options for encoding.
949
- *
950
- * @returns The encoded CMCD string.
951
- *
952
- * @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#payload-definition-for-headers-and-query-argument-transmission | CTA-5004-B Payload Definition}
1311
+ * CMCD event type 'ps' (play state change).
953
1312
  *
954
1313
  * @public
955
- *
956
- * @example
957
- * {@includeCode ../test/encodeCmcd.test.ts#example}
958
1314
  */
959
- function encodeCmcd(cmcd, options = {}) {
960
- if (!cmcd) return "";
961
- return encodePreparedCmcd(prepareCmcdData(cmcd, options));
962
- }
963
-
964
- //#endregion
965
- //#region src/toCmcdUrl.ts
1315
+ const CMCD_EVENT_PLAY_STATE = "ps";
966
1316
  /**
967
- * Convert a CMCD object to a URL encoded string.
968
- *
969
- * @param cmcd - The CMCD object to convert.
970
- * @param options - Options for encoding.
971
- *
972
- * @returns The URL encoded CMCD data.
973
- *
974
- * @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#query-argument-definition | CTA-5004-B Query Argument Definition}
1317
+ * CMCD event type 'pr' (playback rate change).
975
1318
  *
976
1319
  * @public
977
1320
  */
978
- function toCmcdUrl(cmcd, options = {}) {
979
- if (!cmcd) return "";
980
- const params = encodeCmcd(cmcd, options);
981
- return encodeURIComponent(params);
982
- }
983
-
984
- //#endregion
985
- //#region src/toCmcdQuery.ts
1321
+ const CMCD_EVENT_PLAYBACK_RATE = "pr";
986
1322
  /**
987
- * Convert a CMCD object to a query arg.
1323
+ * CMCD event type 'e' (error).
988
1324
  *
989
- * @param cmcd - The CMCD object to convert.
990
- * @param options - Options for encoding.
1325
+ * @public
1326
+ */
1327
+ const CMCD_EVENT_ERROR = "e";
1328
+ /**
1329
+ * CMCD event type 't' (time interval).
991
1330
  *
992
- * @returns The CMCD query arg.
1331
+ * @public
1332
+ */
1333
+ const CMCD_EVENT_TIME_INTERVAL = "t";
1334
+ /**
1335
+ * CMCD event type 'c' (content ID).
993
1336
  *
994
- * @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#query-argument-definition | CTA-5004-B Query Argument Definition}
1337
+ * @public
1338
+ */
1339
+ const CMCD_EVENT_CONTENT_ID = "c";
1340
+ /**
1341
+ * CMCD event type 'b' (backgrounded mode).
995
1342
  *
996
1343
  * @public
1344
+ */
1345
+ const CMCD_EVENT_BACKGROUNDED_MODE = "b";
1346
+ /**
1347
+ * CMCD event type 'm' (mute).
997
1348
  *
998
- * @example
999
- * {@includeCode ../test/toCmcdQuery.test.ts#example}
1349
+ * @public
1000
1350
  */
1001
- function toCmcdQuery(cmcd, options = {}) {
1002
- if (!cmcd) return "";
1003
- return `${CMCD_PARAM}=${toCmcdUrl(cmcd, options)}`;
1004
- }
1005
-
1006
- //#endregion
1007
- //#region src/appendCmcdQuery.ts
1008
- const REGEX = /CMCD=[^&#]+/;
1351
+ const CMCD_EVENT_MUTE = "m";
1009
1352
  /**
1010
- * Append CMCD query args to a URL.
1353
+ * CMCD event type 'um' (unmute).
1011
1354
  *
1012
- * @param url - The URL to append to.
1013
- * @param cmcd - The CMCD object to append.
1014
- * @param options - Options for encoding.
1355
+ * @public
1356
+ */
1357
+ const CMCD_EVENT_UNMUTE = "um";
1358
+ /**
1359
+ * CMCD event type 'pe' (player expand).
1015
1360
  *
1016
- * @returns The URL with the CMCD query args appended.
1361
+ * @public
1362
+ */
1363
+ const CMCD_EVENT_PLAYER_EXPAND = "pe";
1364
+ /**
1365
+ * CMCD event type 'pc' (player collapse).
1017
1366
  *
1018
1367
  * @public
1368
+ */
1369
+ const CMCD_EVENT_PLAYER_COLLAPSE = "pc";
1370
+ /**
1371
+ * CMCD event type 'rr' (response received).
1019
1372
  *
1020
- * @example
1021
- * {@includeCode ../test/appendCmcdQuery.test.ts#example}
1373
+ * @public
1374
+ */
1375
+ const CMCD_EVENT_RESPONSE_RECEIVED = "rr";
1376
+ /**
1377
+ * CMCD event type 'as' (ad start).
1022
1378
  *
1023
- * @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#query-argument-definition | CTA-5004-B Query Argument Definition}
1379
+ * @public
1024
1380
  */
1025
- function appendCmcdQuery(url, cmcd, options) {
1026
- const query = toCmcdQuery(cmcd, options);
1027
- if (!query) return url;
1028
- if (REGEX.test(url)) return url.replace(REGEX, query);
1029
- return `${url}${url.includes("?") ? "&" : "?"}${query}`;
1030
- }
1031
-
1032
- //#endregion
1033
- //#region src/CMCD_DEFAULT_TIME_INTERVAL.ts
1381
+ const CMCD_EVENT_AD_START = "as";
1034
1382
  /**
1035
- * The default time interval in seconds when using event mode
1383
+ * CMCD event type 'ae' (ad end).
1036
1384
  *
1037
1385
  * @public
1038
1386
  */
1039
- const CMCD_DEFAULT_TIME_INTERVAL = 30;
1040
-
1041
- //#endregion
1042
- //#region src/CMCD_KEYS.ts
1387
+ const CMCD_EVENT_AD_END = "ae";
1043
1388
  /**
1044
- * A list of all CMCD keys.
1389
+ * CMCD event type 'abs' (ad break start).
1045
1390
  *
1046
1391
  * @public
1047
1392
  */
1048
- const CMCD_KEYS = /* @__PURE__ */ [
1049
- ...CMCD_V1_KEYS,
1050
- ...CMCD_REQUEST_KEYS,
1051
- ...CMCD_RESPONSE_KEYS,
1052
- ...CMCD_EVENT_KEYS
1053
- ].filter((key, index, arr) => arr.indexOf(key) === index);
1054
-
1055
- //#endregion
1056
- //#region src/CMCD_MIME_TYPE.ts
1393
+ const CMCD_EVENT_AD_BREAK_START = "abs";
1057
1394
  /**
1058
- * CMCD MIME type for event report payloads.
1395
+ * CMCD event type 'abe' (ad break end).
1059
1396
  *
1060
1397
  * @public
1061
1398
  */
1062
- const CMCD_MIME_TYPE = "application/cmcd";
1063
-
1064
- //#endregion
1065
- //#region src/CMCD_REQUEST_PROVENANCE.ts
1399
+ const CMCD_EVENT_AD_BREAK_END = "abe";
1066
1400
  /**
1067
- * The `customData` property where `CmcdReporter.createRequestReport()`
1068
- * writes session provenance on every request it returns.
1069
- *
1070
- * The value is a frozen `CmcdRequestProvenance` record. `sid` names the
1071
- * session that issued the request. `cid` is the content id at the time the
1072
- * request was issued. `data` is the request's per-call data as an encoded
1073
- * CMCD string. `CmcdReporter.recordResponseReceived()` reads the record to
1074
- * attribute a late response to its session and to rebuild the request-time
1075
- * report keys.
1076
- * Spread and `Object.assign` copy the record into ordinary request clones.
1077
- * `JSON.stringify` and structured clone drop symbol-keyed properties. Before
1078
- * either operation, read the value and restore it afterward. JSON preserves
1079
- * the record's fields. Attribution uses the `sid` value rather than object
1080
- * identity, so a parsed plain-object copy attributes correctly. For a record
1081
- * naming a `sid` the reporter does not retain, the reporter drops the
1082
- * response rather than reattributing it.
1401
+ * CMCD event type 'sk' (skip).
1083
1402
  *
1084
- * The symbol registry provides the symbol, so duplicated copies of this
1085
- * library in one bundle interoperate. The registry key is stable across
1086
- * versions.
1403
+ * @public
1404
+ */
1405
+ const CMCD_EVENT_SKIP = "sk";
1406
+ /**
1407
+ * CMCD event type 'ce' (custom event).
1087
1408
  *
1088
1409
  * @public
1089
1410
  */
1090
- const CMCD_REQUEST_PROVENANCE = /* @__PURE__ */ Symbol.for("@svta/cml-cmcd/request-provenance");
1091
-
1092
- //#endregion
1093
- //#region src/CMCD_V1.ts
1411
+ const CMCD_EVENT_CUSTOM_EVENT = "ce";
1094
1412
  /**
1095
- * CMCD Version 1
1413
+ * CMCD event types for the 'e' key (event mode).
1414
+ *
1415
+ * @enum
1416
+ *
1417
+ * @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#event | CTA-5004-B Event}
1096
1418
  *
1097
1419
  * @public
1098
1420
  */
1099
- const CMCD_V1 = 1;
1421
+ const CmcdEventType = {
1422
+ BITRATE_CHANGE: CMCD_EVENT_BITRATE_CHANGE,
1423
+ PLAY_STATE: CMCD_EVENT_PLAY_STATE,
1424
+ PLAYBACK_RATE: CMCD_EVENT_PLAYBACK_RATE,
1425
+ ERROR: CMCD_EVENT_ERROR,
1426
+ TIME_INTERVAL: CMCD_EVENT_TIME_INTERVAL,
1427
+ CONTENT_ID: CMCD_EVENT_CONTENT_ID,
1428
+ BACKGROUNDED_MODE: CMCD_EVENT_BACKGROUNDED_MODE,
1429
+ MUTE: CMCD_EVENT_MUTE,
1430
+ UNMUTE: CMCD_EVENT_UNMUTE,
1431
+ PLAYER_EXPAND: CMCD_EVENT_PLAYER_EXPAND,
1432
+ PLAYER_COLLAPSE: CMCD_EVENT_PLAYER_COLLAPSE,
1433
+ RESPONSE_RECEIVED: CMCD_EVENT_RESPONSE_RECEIVED,
1434
+ AD_START: CMCD_EVENT_AD_START,
1435
+ AD_END: CMCD_EVENT_AD_END,
1436
+ AD_BREAK_START: CMCD_EVENT_AD_BREAK_START,
1437
+ AD_BREAK_END: CMCD_EVENT_AD_BREAK_END,
1438
+ SKIP: CMCD_EVENT_SKIP,
1439
+ CUSTOM_EVENT: CMCD_EVENT_CUSTOM_EVENT
1440
+ };
1100
1441
 
1101
1442
  //#endregion
1102
1443
  //#region src/CmcdObjectType.ts
@@ -1556,6 +1897,39 @@ var CmcdReportRecorder = class {
1556
1897
  }
1557
1898
  };
1558
1899
 
1900
+ //#endregion
1901
+ //#region src/CMCD_STATE_EVENT_FIELDS.ts
1902
+ /**
1903
+ * Maps each state-change event type to the persistent field whose value
1904
+ * the event signals.
1905
+ *
1906
+ * The key table (`CMCD_KEY_SPECS`) marks the field as always sent on its
1907
+ * event, so the key filter of `prepareCmcdData` does not remove it.
1908
+ * `CmcdReporter` deduplicates events against the field value. It drops an
1909
+ * event whose field has no value and restores the field if a transform
1910
+ * removes it.
1911
+ *
1912
+ * Sending the field with each state-change event is a library choice.
1913
+ * The exception is the exit from backgrounded mode, which CTA-5004-B
1914
+ * defines as a `b` event without `bg`. CTA-5004-B requires the field only
1915
+ * on the `ps` event, which MUST carry `sta`. CTA-5004-B states no such rule
1916
+ * for `pr`, `c`, or `bc`. `validateCmcdStructure` follows CTA-5004-B and
1917
+ * does not read this map.
1918
+ *
1919
+ * Iteration order matters: `CmcdReporter.update()` records state-change
1920
+ * events in map order when several tracked fields change in one call.
1921
+ * Do not reorder entries without auditing reporter behavior.
1922
+ *
1923
+ * @internal
1924
+ */
1925
+ const CMCD_STATE_EVENT_FIELDS = /* @__PURE__ */ new Map([
1926
+ [CMCD_EVENT_PLAY_STATE, "sta"],
1927
+ [CMCD_EVENT_PLAYBACK_RATE, "pr"],
1928
+ [CMCD_EVENT_CONTENT_ID, "cid"],
1929
+ [CMCD_EVENT_BACKGROUNDED_MODE, "bg"],
1930
+ [CMCD_EVENT_BITRATE_CHANGE, "br"]
1931
+ ]);
1932
+
1559
1933
  //#endregion
1560
1934
  //#region src/CmcdTransmissionMode.ts
1561
1935
  /**
@@ -1599,19 +1973,21 @@ const CmcdTransmissionMode = {
1599
1973
  * - Wraps scalar values in arrays for inner-list keys.
1600
1974
  * - Wraps `nor` string in an array.
1601
1975
  *
1602
- * The function returns version 2 data (has `v: 2`) unchanged.
1976
+ * The function returns version 2 data (has `v: 2`) unchanged. An inner list
1977
+ * with parameters, which `decodeCmcd` returns as an `SfItem` that wraps the
1978
+ * list, also stays unchanged.
1603
1979
  *
1604
1980
  * @internal
1605
1981
  */
1606
1982
  function upConvertToV2(obj) {
1607
- if (obj["v"] === CMCD_V2) return obj;
1983
+ if (toBareValue(obj["v"]) === CMCD_V2) return obj;
1608
1984
  const result = {};
1609
1985
  for (const [key, value] of Object.entries(obj)) {
1610
1986
  if (value == null) {
1611
1987
  result[key] = value;
1612
1988
  continue;
1613
1989
  }
1614
- if (CMCD_INNER_LIST_KEYS.has(key) && !Array.isArray(value)) result[key] = [value];
1990
+ if (CMCD_INNER_LIST_KEYS.has(key) && !Array.isArray(toBareValue(value))) result[key] = [value];
1615
1991
  else if (key === "nor" && typeof value === "string") result[key] = [value];
1616
1992
  else result[key] = value;
1617
1993
  }
@@ -1661,10 +2037,9 @@ function cmcdObjectTypeListEqual(a, b) {
1661
2037
  const ai = a[i];
1662
2038
  const bi = b[i];
1663
2039
  if (ai === bi) continue;
1664
- if (typeof ai === "number" || typeof bi === "number") return false;
1665
- if (ai.value !== bi.value) return false;
1666
- const ap = ai.params;
1667
- const bp = bi.params;
2040
+ if ((typeof ai === "number" ? ai : ai.value) !== (typeof bi === "number" ? bi : bi.value)) return false;
2041
+ const ap = typeof ai === "number" ? void 0 : ai.params;
2042
+ const bp = typeof bi === "number" ? void 0 : bi.params;
1668
2043
  const ak = ap && Object.keys(ap)[0];
1669
2044
  const bk = bp && Object.keys(bp)[0];
1670
2045
  if (ak !== bk) return false;
@@ -1672,6 +2047,18 @@ function cmcdObjectTypeListEqual(a, b) {
1672
2047
  }
1673
2048
  return true;
1674
2049
  }
2050
+ /**
2051
+ * Equality for `br` deduplication. A `br` value can be an `SfItem` that
2052
+ * wraps the list, as `decodeCmcd` returns for an inner list with
2053
+ * parameters. Two values are equal when their lists are equal. The
2054
+ * parameters of the list are not compared.
2055
+ */
2056
+ function brEqual(a, b) {
2057
+ const listA = toBareValue(a);
2058
+ const listB = toBareValue(b);
2059
+ if (!Array.isArray(listA) || !Array.isArray(listB)) return Object.is(a, b);
2060
+ return cmcdObjectTypeListEqual(listA, listB);
2061
+ }
1675
2062
  const equal = Object.is;
1676
2063
  const identity = (v) => v;
1677
2064
  /**
@@ -1682,8 +2069,8 @@ const STATE_FIELDS = /* @__PURE__ */ Array.from(CMCD_STATE_EVENT_FIELDS, ([event
1682
2069
  if (field === "br") return {
1683
2070
  event,
1684
2071
  field,
1685
- equal: (a, b) => a === void 0 || b === void 0 ? a === b : cmcdObjectTypeListEqual(a, b),
1686
- snapshot: (v) => v.slice()
2072
+ equal: brEqual,
2073
+ snapshot: (v) => Array.isArray(v) ? v.slice() : copyItemValue(v)
1687
2074
  };
1688
2075
  return {
1689
2076
  event,
@@ -1702,29 +2089,14 @@ function buildRequiredEventKeys() {
1702
2089
  ]);
1703
2090
  }
1704
2091
  /**
1705
- * Maps each event type to the key CTA-5004-B requires beyond `e` and `ts`.
1706
- * Built from the state-change table plus the three event types whose
1707
- * required key is event data, not player state.
2092
+ * Maps each event type to the key that a report of that type must keep,
2093
+ * beyond `e` and `ts`. Built from the state-change table plus the three
2094
+ * event types whose required key is event data, not player state.
1708
2095
  */
1709
2096
  const CMCD_REQUIRED_EVENT_KEYS = /* @__PURE__ */ buildRequiredEventKeys();
1710
2097
  /**
1711
- * Whether a required key's value will survive report preparation.
1712
- *
1713
- * This is `isValid` without its `false` exclusion. `false` must count as
1714
- * usable because `bg: false` is a valid value on a backgrounded-mode event,
1715
- * which the encoder writes as `?0`. If `false` counted as unusable,
1716
- * restoration would silently revert a transform that cleared the key.
1717
- * Later processing drops empty strings, empty lists, and non-finite numbers.
1718
- * A transform that substitutes one of them leaves the report without a
1719
- * required key.
1720
- */
1721
- function isUsableRequiredValue(value) {
1722
- if (value == null || value === "") return false;
1723
- if (typeof value === "number") return Number.isFinite(value);
1724
- return !Array.isArray(value) || value.length > 0;
1725
- }
1726
- /**
1727
- * Copies a value with the `SfItem` structure, including its `params` record.
2098
+ * Copies a value with the `SfItem` structure, including its `params` record
2099
+ * and the list of an `SfItem` that wraps a list.
1728
2100
  *
1729
2101
  * The copy keeps the prototype because `prepareCmcdData`, the formatter map,
1730
2102
  * validation, and the structured-field encoder all branch on
@@ -1735,6 +2107,7 @@ function copyItemValue(value) {
1735
2107
  if (value === null || typeof value !== "object") return value;
1736
2108
  const copy = Object.assign(Object.create(Object.getPrototypeOf(value)), value);
1737
2109
  if (copy.params !== null && typeof copy.params === "object") copy.params = { ...copy.params };
2110
+ if (Array.isArray(copy.value)) copy.value = copy.value.map(copyItemValue);
1738
2111
  return copy;
1739
2112
  }
1740
2113
  /**
@@ -1744,7 +2117,8 @@ function copyItemValue(value) {
1744
2117
  *
1745
2118
  * The copy is complete for the CMCD value space. `CmcdValue` and
1746
2119
  * `CmcdCustomValue` admit only primitives, `SfItem<primitive>`, and arrays of
1747
- * those. `SfItem.params` is a flat record. The reporter calls this function
2120
+ * those. `decodeCmcd` also returns an `SfItem` that wraps a list, and the
2121
+ * copy includes that list. `SfItem.params` is a flat record. The reporter calls this function
1748
2122
  * where a transform is configured, at session end on the ended session's
1749
2123
  * store, and on the request's stored player-facing view. The session-end
1750
2124
  * copy detaches the frozen snapshot from caller-held references.
@@ -1873,7 +2247,7 @@ var CmcdReporter = class {
1873
2247
  provenance: mintProvenance(sid, data.cid),
1874
2248
  data,
1875
2249
  msd: NaN,
1876
- lastEmitted: {},
2250
+ lastEmitted: toBareValue(data.bg) === true ? {} : { bg: false },
1877
2251
  eventTargets,
1878
2252
  requestTarget: {
1879
2253
  sn: 0,
@@ -1952,6 +2326,14 @@ var CmcdReporter = class {
1952
2326
  * a new session therefore always fires, even when the persisted value did
1953
2327
  * not change across the `sid` boundary.
1954
2328
  *
2329
+ * The `bg` field is an exception. A session that does not start with
2330
+ * `bg: true` begins with `false` as its last reported `bg` value.
2331
+ * `bg: false` therefore fires `BACKGROUNDED_MODE` only if the session
2332
+ * started with `bg: true` or reported `bg: true`. CTA-5004-B defines a
2333
+ * `b` event without `bg` as the exit from backgrounded mode, so the exit
2334
+ * report does not carry `bg`. A target transform can add `bg: false` to
2335
+ * the exit report. The encoder writes that value as `?0`.
2336
+ *
1955
2337
  * Multi-field updates fire the events in this order: `sta`, `pr`, `cid`,
1956
2338
  * `bg`, `br`. The order of keys in the input object does not affect the
1957
2339
  * firing order.
@@ -1971,8 +2353,7 @@ var CmcdReporter = class {
1971
2353
  * writes them into every outgoing report, so this method is the only way
1972
2354
  * to change them. A per-call value on {@link CmcdReporter.recordEvent},
1973
2355
  * {@link CmcdReporter.createRequestReport}, or
1974
- * {@link CmcdReporter.recordResponseReceived} has no effect. Response
1975
- * attribution uses the provenance record alone.
2356
+ * {@link CmcdReporter.recordResponseReceived} has no effect.
1976
2357
  * `msd` must be a finite number of milliseconds between `0` and
1977
2358
  * `999_999_999_999_999` (the RFC 8941 integer maximum). The reporter
1978
2359
  * rounds `msd` to the nearest integer and ignores invalid values.
@@ -2035,9 +2416,9 @@ var CmcdReporter = class {
2035
2416
  * data store, as `update()` would.
2036
2417
  * 2. Discards the event if the state field has no value after that write
2037
2418
  * (never set, or cleared with `update({ field: undefined })`).
2038
- * A state-change event without its required field would violate CTA-5004-B.
2039
2419
  * 3. Suppresses the event if the field's current value equals the
2040
- * last reported value (no state transition).
2420
+ * last reported value (no state transition). {@link CmcdReporter.update}
2421
+ * describes the `bg` value that a new session starts with.
2041
2422
  *
2042
2423
  * The reporter always records all other event types.
2043
2424
  *
@@ -2123,6 +2504,7 @@ var CmcdReporter = class {
2123
2504
  e: type,
2124
2505
  ts: data.ts ?? Date.now()
2125
2506
  };
2507
+ if (type === CMCD_EVENT_BACKGROUNDED_MODE && toBareValue(item.bg) === false) delete item.bg;
2126
2508
  const { transform } = config;
2127
2509
  if (!transform) {
2128
2510
  this.queueTargetEvent(session, target, config, item, type);
@@ -2134,8 +2516,8 @@ var CmcdReporter = class {
2134
2516
  const ts = item.ts;
2135
2517
  const report = transform(item, request);
2136
2518
  if (report == null) return;
2137
- if (!isUsableRequiredValue(report.ts)) report.ts = ts;
2138
- if (requiredKey && isUsableRequiredValue(requiredValue) && !isUsableRequiredValue(report[requiredKey])) Object.assign(report, { [requiredKey]: requiredValue });
2519
+ if (!isValid(report.ts)) report.ts = ts;
2520
+ if (requiredKey && isValid(requiredValue) && !isValid(report[requiredKey])) Object.assign(report, { [requiredKey]: requiredValue });
2139
2521
  this.queueTargetEvent(session, target, config, report, type);
2140
2522
  }
2141
2523
  /**
@@ -2189,27 +2571,24 @@ var CmcdReporter = class {
2189
2571
  * TypeScript therefore rejects a call whose request the configured
2190
2572
  * transforms could not read. See {@link CmcdReporterCustomData}.
2191
2573
  *
2192
- * The reporter attributes the event only to the session that issued the
2193
- * request. The provenance record that
2194
- * {@link CmcdReporter.createRequestReport} stored on the request's
2195
- * `customData` (under {@link CMCD_REQUEST_PROVENANCE}) selects the session
2196
- * by `sid`. Without a match, the reporter discards the response rather than
2197
- * attributing it elsewhere. There is no other key. The reporter discards the
2198
- * response when a serialization boundary lost the record and nothing
2199
- * restored it (see {@link CMCD_REQUEST_PROVENANCE}). It also discards the
2200
- * response when the session is no longer retained (see
2201
- * `CmcdReporterConfig.sessionRetention`). A per-call `data.sid` cannot
2202
- * substitute. The reporter accepts any record whose `sid` names a retained
2203
- * session. The record may come from another reporter with the same session
2204
- * attributes, or be hand-built. A response that completes after a `sid`
2205
- * change reports under its own retained session, with that session's data
2206
- * snapshot and sequence numbers.
2574
+ * The provenance record that {@link CmcdReporter.createRequestReport}
2575
+ * stored on the request's `customData` (under
2576
+ * {@link CMCD_REQUEST_PROVENANCE}) selects the session by `sid`. A record
2577
+ * that names no retained session drops the response (see
2578
+ * `CmcdReporterConfig.sessionRetention`). A request without a record reports
2579
+ * under the current session, as in version 2.4.0. The request-time data then
2580
+ * comes from `customData.cmcd`. A per-call `data.sid` cannot substitute. The
2581
+ * reporter accepts any record whose `sid` names a retained session. The
2582
+ * record may come from another reporter with the same session attributes,
2583
+ * or be hand-built. If the request has a record, a response that completes
2584
+ * after a `sid` change reports under its own retained session, with that
2585
+ * session's data snapshot and sequence numbers.
2207
2586
  *
2208
- * Request-time report keys come from the record's encoded per-call `data`
2209
- * snapshot, which the reporter decodes for each response. The record's `cid`
2210
- * replaces the session's current `cid` in the report. A response that
2211
- * completes after a mid-session content change therefore reports its
2212
- * request-time values.
2587
+ * With a record, request-time report keys come from the record's encoded
2588
+ * per-call `data` snapshot, which the reporter decodes for each response.
2589
+ * The record's `cid` replaces the session's current `cid` in the report. A
2590
+ * response that completes after a mid-session content change therefore
2591
+ * reports its request-time values.
2213
2592
  *
2214
2593
  * @typeParam RD - The `customData` of this request. Defaults to the
2215
2594
  * reporter's own `C`.
@@ -2218,17 +2597,17 @@ var CmcdReporter = class {
2218
2597
  * @param data - Additional CMCD data to include with the event.
2219
2598
  * Values provided here override the derived values.
2220
2599
  * The reporter ignores session-owned keys (`sid`, `msd`)
2221
- * supplied here. Attribution uses the provenance record alone.
2600
+ * supplied here.
2222
2601
  */
2223
2602
  recordResponseReceived(response, data = {}) {
2224
2603
  const { request } = response;
2225
2604
  const url = data.url ?? request?.url;
2226
2605
  if (!url) return;
2227
2606
  const provenance = request.customData?.[CMCD_REQUEST_PROVENANCE];
2228
- const session = this.resolveSession(provenance);
2607
+ const session = provenance === void 0 ? this.session : this.resolveSession(provenance);
2229
2608
  if (!session) return;
2230
- const cmcd = decodeSnapshot(provenance);
2231
- const { cid } = provenance;
2609
+ const cmcd = provenance === void 0 ? { ...request.customData?.cmcd } : decodeSnapshot(provenance);
2610
+ const { cid } = provenance ?? {};
2232
2611
  const urlObj = new URL(url);
2233
2612
  urlObj.searchParams.delete(CMCD_PARAM);
2234
2613
  const derived = {
@@ -2251,11 +2630,11 @@ var CmcdReporter = class {
2251
2630
  }, request);
2252
2631
  }
2253
2632
  /**
2254
- * Resolves the session a response belongs to. The provenance record's `sid`
2633
+ * Resolves the session that a provenance record names. The record's `sid`
2255
2634
  * must name one of this reporter's retained sessions, or the reporter
2256
- * discards the response. There is no other key. A lost record, or one that
2257
- * names a removed or unknown `sid`, resolves nothing. Any other attribution
2258
- * would be wrong. The reporter reads the `sid` as a plain property. A record
2635
+ * discards the response. A record that names a removed or unknown `sid`
2636
+ * resolves nothing. Any other attribution would be wrong. The reporter reads
2637
+ * the `sid` as a plain property. A record
2259
2638
  * copied through JSON therefore resolves the same session, and a hand-built
2260
2639
  * record that names a retained session is accepted.
2261
2640
  */
@@ -2544,6 +2923,63 @@ function fromCmcdUrl(url, options) {
2544
2923
  return decodeCmcd(decodeURIComponent(url.replace(/^CMCD=/, "")), options);
2545
2924
  }
2546
2925
 
2926
+ //#endregion
2927
+ //#region src/isCmcdRequestKey.ts
2928
+ const CMCD_REQUEST_KEY_SET = /* @__PURE__ */ new Set(CMCD_REQUEST_KEYS);
2929
+ /**
2930
+ * Check if a key is a valid CMCD request key.
2931
+ *
2932
+ * @param key - The key to check.
2933
+ *
2934
+ * @returns `true` if the key is a valid CMCD request key, `false` otherwise.
2935
+ *
2936
+ * @public
2937
+ *
2938
+ * @example
2939
+ * {@includeCode ../test/isCmcdRequestKey.test.ts#example}
2940
+ */
2941
+ function isCmcdRequestKey(key) {
2942
+ return CMCD_REQUEST_KEY_SET.has(key) || isCmcdCustomKey(key);
2943
+ }
2944
+
2945
+ //#endregion
2946
+ //#region src/isCmcdResponseReceivedKey.ts
2947
+ const CMCD_RESPONSE_KEY_SET = /* @__PURE__ */ new Set(CMCD_RESPONSE_KEYS);
2948
+ /**
2949
+ * Check if a key is a valid CMCD response key.
2950
+ *
2951
+ * @param key - The key to check.
2952
+ *
2953
+ * @returns `true` if the key is a valid CMCD response key, `false` otherwise.
2954
+ *
2955
+ * @public
2956
+ *
2957
+ * @example
2958
+ * {@includeCode ../test/isCmcdResponseReceivedKey.test.ts#example}
2959
+ */
2960
+ function isCmcdResponseReceivedKey(key) {
2961
+ return CMCD_RESPONSE_KEY_SET.has(key);
2962
+ }
2963
+
2964
+ //#endregion
2965
+ //#region src/isCmcdEventKey.ts
2966
+ const CMCD_EVENT_KEY_SET = /* @__PURE__ */ new Set(CMCD_EVENT_KEYS);
2967
+ /**
2968
+ * Check if a key is a valid CMCD event key.
2969
+ *
2970
+ * @param key - The key to check.
2971
+ *
2972
+ * @returns `true` if the key is a valid CMCD event key, `false` otherwise.
2973
+ *
2974
+ * @public
2975
+ *
2976
+ * @example
2977
+ * {@includeCode ../test/isCmcdEventKey.test.ts#example}
2978
+ */
2979
+ function isCmcdEventKey(key) {
2980
+ return isCmcdRequestKey(key) || isCmcdResponseReceivedKey(key) || CMCD_EVENT_KEY_SET.has(key);
2981
+ }
2982
+
2547
2983
  //#endregion
2548
2984
  //#region src/isCmcdV1Data.ts
2549
2985
  /**
@@ -2562,6 +2998,25 @@ function isCmcdV1Data(data) {
2562
2998
  return data.v !== CMCD_V2;
2563
2999
  }
2564
3000
 
3001
+ //#endregion
3002
+ //#region src/isCmcdV1Key.ts
3003
+ const CMCD_V1_KEY_SET$1 = /* @__PURE__ */ new Set(CMCD_V1_KEYS);
3004
+ /**
3005
+ * Filter for CMCD version 1 keys.
3006
+ *
3007
+ * @param key - The CMCD key to filter.
3008
+ *
3009
+ * @returns `true` if the key should be included, `false` otherwise.
3010
+ *
3011
+ * @public
3012
+ *
3013
+ * @example
3014
+ * {@includeCode ../test/isCmcdV1Key.test.ts#example}
3015
+ */
3016
+ function isCmcdV1Key(key) {
3017
+ return CMCD_V1_KEY_SET$1.has(key) || isCmcdCustomKey(key);
3018
+ }
3019
+
2565
3020
  //#endregion
2566
3021
  //#region src/isCmcdV2Data.ts
2567
3022
  /**
@@ -2619,7 +3074,7 @@ function mergeValidationResults(...results) {
2619
3074
  */
2620
3075
  function resolveVersion(data, options) {
2621
3076
  if (options?.version === 1 || options?.version === 2) return options.version;
2622
- const payloadVersion = data["v"];
3077
+ const payloadVersion = toBareValue(data["v"]);
2623
3078
  if (payloadVersion === 1 || payloadVersion === 2) return payloadVersion;
2624
3079
  return CMCD_V1;
2625
3080
  }
@@ -2660,6 +3115,51 @@ function validateCmcdKeys(data, options) {
2660
3115
  };
2661
3116
  }
2662
3117
 
3118
+ //#endregion
3119
+ //#region src/CMCD_AGGREGATE_BITRATE_KEYS.ts
3120
+ /**
3121
+ * Maps each aggregate bitrate key to the exact bitrate key that supersedes it.
3122
+ *
3123
+ * Per CTA-5004-B, `ab` MUST NOT be sent when the encoded bitrate `br` is
3124
+ * known. `lab` and `tab` follow the same rule for the lowest bitrate `lb`
3125
+ * and the top bitrate `tb`.
3126
+ *
3127
+ * @internal
3128
+ */
3129
+ const CMCD_AGGREGATE_BITRATE_KEYS = {
3130
+ ab: "br",
3131
+ lab: "lb",
3132
+ tab: "tb"
3133
+ };
3134
+
3135
+ //#endregion
3136
+ //#region src/CMCD_KEY_OBJECT_TYPES.ts
3137
+ /**
3138
+ * Maps a key to the object types (`ot`) for which CTA-5004-B allows the key.
3139
+ *
3140
+ * A key that is absent from this map is allowed for every object type.
3141
+ * `d` (Object duration) and `tpb` (Top playable bitrate) MUST NOT be sent for
3142
+ * other object types.
3143
+ *
3144
+ * @internal
3145
+ */
3146
+ const CMCD_KEY_OBJECT_TYPES = {
3147
+ d: [
3148
+ "a",
3149
+ "v",
3150
+ "av",
3151
+ "tt",
3152
+ "c",
3153
+ "o"
3154
+ ],
3155
+ tpb: [
3156
+ "a",
3157
+ "v",
3158
+ "av",
3159
+ "c"
3160
+ ]
3161
+ };
3162
+
2663
3163
  //#endregion
2664
3164
  //#region src/validateCmcdStructure.ts
2665
3165
  /**
@@ -2727,9 +3227,9 @@ function validateCmcdStructure(data, options) {
2727
3227
  message: `Response key "${key}" must only be present when e="rr".`,
2728
3228
  severity: CMCD_VALIDATION_SEVERITY_ERROR
2729
3229
  });
2730
- for (const [stateEventType, requiredField] of CMCD_STATE_EVENT_FIELDS) if (eventType === stateEventType && !(requiredField in data)) issues.push({
2731
- key: requiredField,
2732
- message: `State-change event (e="${stateEventType}") requires the "${requiredField}" key to be present.`,
3230
+ if (eventType === CMCD_EVENT_PLAY_STATE && !("sta" in data)) issues.push({
3231
+ key: "sta",
3232
+ message: "Play state change event (e=\"ps\") requires the \"sta\" key to be present.",
2733
3233
  severity: CMCD_VALIDATION_SEVERITY_ERROR
2734
3234
  });
2735
3235
  if (eventType === CMCD_EVENT_ERROR && !("ec" in data)) issues.push({
@@ -2755,9 +3255,10 @@ function validateCmcdStructure(data, options) {
2755
3255
  severity: CMCD_VALIDATION_SEVERITY_ERROR
2756
3256
  });
2757
3257
  }
2758
- if ("v" in data && data["v"] !== 1 && data["v"] !== 2) issues.push({
3258
+ const v = toBareValue(data["v"]);
3259
+ if ("v" in data && v !== 1 && v !== 2) issues.push({
2759
3260
  key: "v",
2760
- message: `Unsupported CMCD version "${String(data["v"])}". Expected 1 or 2.`,
3261
+ message: `Unsupported CMCD version "${String(v)}". Expected 1 or 2.`,
2761
3262
  severity: CMCD_VALIDATION_SEVERITY_ERROR
2762
3263
  });
2763
3264
  else if (version > 1 && !("v" in data)) issues.push({
@@ -2779,7 +3280,7 @@ function validateCmcdStructure(data, options) {
2779
3280
  //#endregion
2780
3281
  //#region src/CMCD_KEY_TYPES.ts
2781
3282
  /**
2782
- * CMCD key value type: inner list of numbers with token identifiers.
3283
+ * CMCD key value type: inner list of integers with token identifiers.
2783
3284
  *
2784
3285
  * @internal
2785
3286
  */
@@ -2886,9 +3387,9 @@ const CMCD_KEY_TYPES = {
2886
3387
  */
2887
3388
  const CMCD_V1_KEY_TYPE_OVERRIDES = {
2888
3389
  bl: CMCD_KEY_TYPE_INTEGER,
2889
- br: CMCD_KEY_TYPE_NUMBER,
3390
+ br: CMCD_KEY_TYPE_INTEGER,
2890
3391
  mtp: CMCD_KEY_TYPE_INTEGER,
2891
- tb: CMCD_KEY_TYPE_NUMBER,
3392
+ tb: CMCD_KEY_TYPE_INTEGER,
2892
3393
  nor: CMCD_KEY_TYPE_STRING
2893
3394
  };
2894
3395
 
@@ -2979,22 +3480,31 @@ const CMCD_TOKEN_VALUES = {
2979
3480
 
2980
3481
  //#endregion
2981
3482
  //#region src/validateCmcdValues.ts
2982
- const HUNDRED_ROUNDING_KEYS = /* @__PURE__ */ new Set([
2983
- "bl",
2984
- "dl",
2985
- "mtp",
2986
- "rtp",
2987
- "tbl"
2988
- ]);
2989
- const INTEGER_ROUNDING_KEYS = /* @__PURE__ */ new Set([
2990
- "br",
2991
- "d",
2992
- "tb"
2993
- ]);
3483
+ const HUNDRED_ROUNDING_UNITS = {
3484
+ bl: "ms",
3485
+ dl: "ms",
3486
+ mtp: "kbps",
3487
+ rtp: "kbps",
3488
+ tbl: "ms"
3489
+ };
2994
3490
  function isFiniteNumber(value) {
2995
3491
  return typeof value === "number" && Number.isFinite(value);
2996
3492
  }
2997
- function validateListValue(key, value, issues) {
3493
+ function isInteger(value) {
3494
+ return Number.isInteger(value);
3495
+ }
3496
+ function validateRounding(key, value, version, issues, index) {
3497
+ const unit = HUNDRED_ROUNDING_UNITS[key];
3498
+ if (!unit || value % 100 === 0) return;
3499
+ const should = key === "tbl" || key === "bl" && version !== CMCD_V1;
3500
+ const target = index === void 0 ? `Key "${key}"` : `Key "${key}" array element [${index}]`;
3501
+ issues.push({
3502
+ key,
3503
+ message: `${target} ${should ? "should" : "must"} be rounded to the nearest 100 ${unit}. Received ${value}.`,
3504
+ severity: should ? CMCD_VALIDATION_SEVERITY_WARNING : CMCD_VALIDATION_SEVERITY_ERROR
3505
+ });
3506
+ }
3507
+ function validateListValue(key, value, version, issues) {
2998
3508
  if (!Array.isArray(value)) {
2999
3509
  issues.push({
3000
3510
  key,
@@ -3004,18 +3514,13 @@ function validateListValue(key, value, issues) {
3004
3514
  return;
3005
3515
  }
3006
3516
  for (let i = 0; i < value.length; i++) {
3007
- const element = value[i];
3008
- if (element instanceof SfItem) {
3009
- if (!isFiniteNumber(element.value)) issues.push({
3010
- key,
3011
- message: `Key "${key}" array element [${i}] must be a finite number.`,
3012
- severity: CMCD_VALIDATION_SEVERITY_ERROR
3013
- });
3014
- } else if (!isFiniteNumber(element)) issues.push({
3517
+ const element = toBareValue(value[i]);
3518
+ if (!isInteger(element)) issues.push({
3015
3519
  key,
3016
- message: `Key "${key}" array element [${i}] must be a finite number.`,
3520
+ message: `Key "${key}" array element [${i}] must be a finite integer.`,
3017
3521
  severity: CMCD_VALIDATION_SEVERITY_ERROR
3018
3522
  });
3523
+ else validateRounding(key, element, version, issues, i);
3019
3524
  }
3020
3525
  }
3021
3526
  function validateStringArrayValue(key, value, issues) {
@@ -3027,20 +3532,11 @@ function validateStringArrayValue(key, value, issues) {
3027
3532
  });
3028
3533
  return;
3029
3534
  }
3030
- for (let i = 0; i < value.length; i++) {
3031
- const element = value[i];
3032
- if (element instanceof SfItem) {
3033
- if (typeof element.value !== "string") issues.push({
3034
- key,
3035
- message: `Key "${key}" array element [${i}] must be a string.`,
3036
- severity: CMCD_VALIDATION_SEVERITY_ERROR
3037
- });
3038
- } else if (typeof element !== "string") issues.push({
3039
- key,
3040
- message: `Key "${key}" array element [${i}] must be a string.`,
3041
- severity: CMCD_VALIDATION_SEVERITY_ERROR
3042
- });
3043
- }
3535
+ for (let i = 0; i < value.length; i++) if (typeof toBareValue(value[i]) !== "string") issues.push({
3536
+ key,
3537
+ message: `Key "${key}" array element [${i}] must be a string.`,
3538
+ severity: CMCD_VALIDATION_SEVERITY_ERROR
3539
+ });
3044
3540
  }
3045
3541
  /**
3046
3542
  * Validates that all values in a CMCD payload conform to the expected types and constraints.
@@ -3059,7 +3555,8 @@ function validateStringArrayValue(key, value, issues) {
3059
3555
  function validateCmcdValues(data, options) {
3060
3556
  const version = resolveVersion(data, options);
3061
3557
  const issues = [];
3062
- for (const [key, value] of Object.entries(data)) {
3558
+ for (const [key, entry] of Object.entries(data)) {
3559
+ const value = toBareValue(entry);
3063
3560
  if (isCmcdCustomKey(key)) {
3064
3561
  if (typeof value !== "string") issues.push({
3065
3562
  key,
@@ -3086,16 +3583,12 @@ function validateCmcdValues(data, options) {
3086
3583
  if (version === CMCD_V1 && key in CMCD_V1_KEY_TYPE_OVERRIDES) expectedType = CMCD_V1_KEY_TYPE_OVERRIDES[key];
3087
3584
  switch (expectedType) {
3088
3585
  case CMCD_KEY_TYPE_INTEGER:
3089
- if (!isFiniteNumber(value) || !Number.isInteger(value)) issues.push({
3586
+ if (!isInteger(value)) issues.push({
3090
3587
  key,
3091
3588
  message: `Key "${key}" must be a finite integer.`,
3092
3589
  severity: CMCD_VALIDATION_SEVERITY_ERROR
3093
3590
  });
3094
- else if (HUNDRED_ROUNDING_KEYS.has(key) && value % 100 !== 0) issues.push({
3095
- key,
3096
- message: `Key "${key}" should be rounded to the nearest 100.`,
3097
- severity: CMCD_VALIDATION_SEVERITY_WARNING
3098
- });
3591
+ else validateRounding(key, value, version, issues);
3099
3592
  break;
3100
3593
  case CMCD_KEY_TYPE_NUMBER:
3101
3594
  if (!isFiniteNumber(value)) issues.push({
@@ -3103,16 +3596,6 @@ function validateCmcdValues(data, options) {
3103
3596
  message: `Key "${key}" must be a finite number.`,
3104
3597
  severity: CMCD_VALIDATION_SEVERITY_ERROR
3105
3598
  });
3106
- else if (HUNDRED_ROUNDING_KEYS.has(key) && value % 100 !== 0) issues.push({
3107
- key,
3108
- message: `Key "${key}" should be rounded to the nearest 100.`,
3109
- severity: CMCD_VALIDATION_SEVERITY_WARNING
3110
- });
3111
- else if (INTEGER_ROUNDING_KEYS.has(key) && !Number.isInteger(value)) issues.push({
3112
- key,
3113
- message: `Key "${key}" should be rounded to an integer.`,
3114
- severity: CMCD_VALIDATION_SEVERITY_WARNING
3115
- });
3116
3599
  break;
3117
3600
  case CMCD_KEY_TYPE_BOOLEAN:
3118
3601
  if (typeof value !== "boolean") issues.push({
@@ -3144,7 +3627,7 @@ function validateCmcdValues(data, options) {
3144
3627
  break;
3145
3628
  }
3146
3629
  case CMCD_KEY_TYPE_NUMBER_LIST:
3147
- validateListValue(key, value, issues);
3630
+ validateListValue(key, value, version, issues);
3148
3631
  break;
3149
3632
  case CMCD_KEY_TYPE_STRING_LIST:
3150
3633
  validateStringArrayValue(key, value, issues);