@svta/cml-cmcd 2.7.0 → 2.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/index.d.ts +63 -36
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1322 -766
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,462 +1,6 @@
|
|
|
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
|
|
5
|
-
/**
|
|
6
|
-
* Maps each aggregate bitrate key to the exact bitrate key that supersedes it.
|
|
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`.
|
|
11
|
-
*
|
|
12
|
-
* @internal
|
|
13
|
-
*/
|
|
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
|
-
};
|
|
69
|
-
|
|
70
|
-
//#endregion
|
|
71
|
-
//#region src/CMCD_KEY_OBJECT_TYPES.ts
|
|
72
|
-
/**
|
|
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.
|
|
78
|
-
*
|
|
79
|
-
* @internal
|
|
80
|
-
*/
|
|
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
|
-
]
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
//#endregion
|
|
99
|
-
//#region src/CMCD_V2.ts
|
|
100
|
-
/**
|
|
101
|
-
* CMCD Version 2
|
|
102
|
-
*
|
|
103
|
-
* @public
|
|
104
|
-
*/
|
|
105
|
-
const CMCD_V2 = 2;
|
|
106
|
-
|
|
107
|
-
//#endregion
|
|
108
|
-
//#region src/CmcdEventType.ts
|
|
109
|
-
/**
|
|
110
|
-
* CMCD event type 'bc' (bitrate change).
|
|
111
|
-
*
|
|
112
|
-
* @public
|
|
113
|
-
*/
|
|
114
|
-
const CMCD_EVENT_BITRATE_CHANGE = "bc";
|
|
115
|
-
/**
|
|
116
|
-
* CMCD event type 'ps' (play state change).
|
|
117
|
-
*
|
|
118
|
-
* @public
|
|
119
|
-
*/
|
|
120
|
-
const CMCD_EVENT_PLAY_STATE = "ps";
|
|
121
|
-
/**
|
|
122
|
-
* CMCD event type 'pr' (playback rate change).
|
|
123
|
-
*
|
|
124
|
-
* @public
|
|
125
|
-
*/
|
|
126
|
-
const CMCD_EVENT_PLAYBACK_RATE = "pr";
|
|
127
|
-
/**
|
|
128
|
-
* CMCD event type 'e' (error).
|
|
129
|
-
*
|
|
130
|
-
* @public
|
|
131
|
-
*/
|
|
132
|
-
const CMCD_EVENT_ERROR = "e";
|
|
133
|
-
/**
|
|
134
|
-
* CMCD event type 't' (time interval).
|
|
135
|
-
*
|
|
136
|
-
* @public
|
|
137
|
-
*/
|
|
138
|
-
const CMCD_EVENT_TIME_INTERVAL = "t";
|
|
139
|
-
/**
|
|
140
|
-
* CMCD event type 'c' (content ID).
|
|
141
|
-
*
|
|
142
|
-
* @public
|
|
143
|
-
*/
|
|
144
|
-
const CMCD_EVENT_CONTENT_ID = "c";
|
|
145
|
-
/**
|
|
146
|
-
* CMCD event type 'b' (backgrounded mode).
|
|
147
|
-
*
|
|
148
|
-
* @public
|
|
149
|
-
*/
|
|
150
|
-
const CMCD_EVENT_BACKGROUNDED_MODE = "b";
|
|
151
|
-
/**
|
|
152
|
-
* CMCD event type 'm' (mute).
|
|
153
|
-
*
|
|
154
|
-
* @public
|
|
155
|
-
*/
|
|
156
|
-
const CMCD_EVENT_MUTE = "m";
|
|
157
|
-
/**
|
|
158
|
-
* CMCD event type 'um' (unmute).
|
|
159
|
-
*
|
|
160
|
-
* @public
|
|
161
|
-
*/
|
|
162
|
-
const CMCD_EVENT_UNMUTE = "um";
|
|
163
|
-
/**
|
|
164
|
-
* CMCD event type 'pe' (player expand).
|
|
165
|
-
*
|
|
166
|
-
* @public
|
|
167
|
-
*/
|
|
168
|
-
const CMCD_EVENT_PLAYER_EXPAND = "pe";
|
|
169
|
-
/**
|
|
170
|
-
* CMCD event type 'pc' (player collapse).
|
|
171
|
-
*
|
|
172
|
-
* @public
|
|
173
|
-
*/
|
|
174
|
-
const CMCD_EVENT_PLAYER_COLLAPSE = "pc";
|
|
175
|
-
/**
|
|
176
|
-
* CMCD event type 'rr' (response received).
|
|
177
|
-
*
|
|
178
|
-
* @public
|
|
179
|
-
*/
|
|
180
|
-
const CMCD_EVENT_RESPONSE_RECEIVED = "rr";
|
|
181
|
-
/**
|
|
182
|
-
* CMCD event type 'as' (ad start).
|
|
183
|
-
*
|
|
184
|
-
* @public
|
|
185
|
-
*/
|
|
186
|
-
const CMCD_EVENT_AD_START = "as";
|
|
187
|
-
/**
|
|
188
|
-
* CMCD event type 'ae' (ad end).
|
|
189
|
-
*
|
|
190
|
-
* @public
|
|
191
|
-
*/
|
|
192
|
-
const CMCD_EVENT_AD_END = "ae";
|
|
193
|
-
/**
|
|
194
|
-
* CMCD event type 'abs' (ad break start).
|
|
195
|
-
*
|
|
196
|
-
* @public
|
|
197
|
-
*/
|
|
198
|
-
const CMCD_EVENT_AD_BREAK_START = "abs";
|
|
199
|
-
/**
|
|
200
|
-
* CMCD event type 'abe' (ad break end).
|
|
201
|
-
*
|
|
202
|
-
* @public
|
|
203
|
-
*/
|
|
204
|
-
const CMCD_EVENT_AD_BREAK_END = "abe";
|
|
205
|
-
/**
|
|
206
|
-
* CMCD event type 'sk' (skip).
|
|
207
|
-
*
|
|
208
|
-
* @public
|
|
209
|
-
*/
|
|
210
|
-
const CMCD_EVENT_SKIP = "sk";
|
|
211
|
-
/**
|
|
212
|
-
* CMCD event type 'ce' (custom event).
|
|
213
|
-
*
|
|
214
|
-
* @public
|
|
215
|
-
*/
|
|
216
|
-
const CMCD_EVENT_CUSTOM_EVENT = "ce";
|
|
217
|
-
/**
|
|
218
|
-
* CMCD event types for the 'e' key (event mode).
|
|
219
|
-
*
|
|
220
|
-
* @enum
|
|
221
|
-
*
|
|
222
|
-
* @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#event | CTA-5004-B Event}
|
|
223
|
-
*
|
|
224
|
-
* @public
|
|
225
|
-
*/
|
|
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
|
-
};
|
|
246
|
-
|
|
247
|
-
//#endregion
|
|
248
|
-
//#region src/CMCD_STATE_EVENT_FIELDS.ts
|
|
249
|
-
/**
|
|
250
|
-
* Maps each state-change event type to the persistent field whose value
|
|
251
|
-
* the event signals.
|
|
252
|
-
*
|
|
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`).
|
|
258
|
-
*
|
|
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.
|
|
262
|
-
*
|
|
263
|
-
* @internal
|
|
264
|
-
*/
|
|
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
|
-
]);
|
|
272
|
-
|
|
273
|
-
//#endregion
|
|
274
|
-
//#region src/CmcdReportingMode.ts
|
|
275
|
-
/**
|
|
276
|
-
* CMCD event mode variable name.
|
|
277
|
-
*
|
|
278
|
-
* @public
|
|
279
|
-
*/
|
|
280
|
-
const CMCD_EVENT_MODE = "event";
|
|
281
|
-
/**
|
|
282
|
-
* CMCD request mode variable name.
|
|
283
|
-
*
|
|
284
|
-
* @public
|
|
285
|
-
*/
|
|
286
|
-
const CMCD_REQUEST_MODE = "request";
|
|
287
|
-
/**
|
|
288
|
-
* CMCD reporting mode types.
|
|
289
|
-
*
|
|
290
|
-
* @enum
|
|
291
|
-
*
|
|
292
|
-
* @public
|
|
293
|
-
*/
|
|
294
|
-
const CmcdReportingMode = {
|
|
295
|
-
REQUEST: CMCD_REQUEST_MODE,
|
|
296
|
-
EVENT: CMCD_EVENT_MODE
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
//#endregion
|
|
300
|
-
//#region src/CMCD_EVENT_KEYS.ts
|
|
301
|
-
/**
|
|
302
|
-
* Defines the event-specific keys for CMCD (Common Media Client Data) version 2.
|
|
303
|
-
*
|
|
304
|
-
* @public
|
|
305
|
-
*/
|
|
306
|
-
const CMCD_EVENT_KEYS = [
|
|
307
|
-
"cen",
|
|
308
|
-
"e",
|
|
309
|
-
"h",
|
|
310
|
-
"ts"
|
|
311
|
-
];
|
|
312
|
-
|
|
313
|
-
//#endregion
|
|
314
|
-
//#region src/CMCD_REQUEST_KEYS.ts
|
|
315
|
-
/**
|
|
316
|
-
* Defines the request-specific keys for CMCD (Common Media Client Data) version 2.
|
|
317
|
-
*
|
|
318
|
-
* @public
|
|
319
|
-
*/
|
|
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.-]*$/;
|
|
364
|
-
/**
|
|
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.
|
|
373
|
-
*
|
|
374
|
-
* @public
|
|
375
|
-
*
|
|
376
|
-
* @example
|
|
377
|
-
* {@includeCode ../test/isCmcdCustomKey.test.ts#example}
|
|
378
|
-
*/
|
|
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);
|
|
387
|
-
/**
|
|
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.
|
|
393
|
-
*
|
|
394
|
-
* @public
|
|
395
|
-
*
|
|
396
|
-
* @example
|
|
397
|
-
* {@includeCode ../test/isCmcdRequestKey.test.ts#example}
|
|
398
|
-
*/
|
|
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
|
|
405
|
-
/**
|
|
406
|
-
* Response-only and timing keys for version 2.
|
|
407
|
-
*
|
|
408
|
-
* @public
|
|
409
|
-
*/
|
|
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);
|
|
424
|
-
/**
|
|
425
|
-
* Check if a key is a valid CMCD response key.
|
|
426
|
-
*
|
|
427
|
-
* @param key - The key to check.
|
|
428
|
-
*
|
|
429
|
-
* @returns `true` if the key is a valid CMCD response key, `false` otherwise.
|
|
430
|
-
*
|
|
431
|
-
* @public
|
|
432
|
-
*
|
|
433
|
-
* @example
|
|
434
|
-
* {@includeCode ../test/isCmcdResponseReceivedKey.test.ts#example}
|
|
435
|
-
*/
|
|
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);
|
|
443
|
-
/**
|
|
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.
|
|
449
|
-
*
|
|
450
|
-
* @public
|
|
451
|
-
*
|
|
452
|
-
* @example
|
|
453
|
-
* {@includeCode ../test/isCmcdEventKey.test.ts#example}
|
|
454
|
-
*/
|
|
455
|
-
function isCmcdEventKey(key) {
|
|
456
|
-
return isCmcdRequestKey(key) || isCmcdResponseReceivedKey(key) || CMCD_EVENT_KEY_SET.has(key);
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
//#endregion
|
|
460
4
|
//#region src/CMCD_INNER_LIST_KEYS.ts
|
|
461
5
|
/**
|
|
462
6
|
* Inner-list keys in version 2 that are scalars in version 1.
|
|
@@ -483,72 +27,424 @@ const CMCD_INNER_LIST_KEYS = new Set([
|
|
|
483
27
|
]);
|
|
484
28
|
|
|
485
29
|
//#endregion
|
|
486
|
-
//#region src/
|
|
30
|
+
//#region src/CMCD_KEY_SPECS.ts
|
|
31
|
+
const MEDIA = [
|
|
32
|
+
"a",
|
|
33
|
+
"v",
|
|
34
|
+
"av",
|
|
35
|
+
"tt",
|
|
36
|
+
"c",
|
|
37
|
+
"o"
|
|
38
|
+
];
|
|
487
39
|
/**
|
|
488
|
-
*
|
|
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`.
|
|
41
|
+
*
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
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
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
//#endregion
|
|
316
|
+
//#region src/CMCD_V2.ts
|
|
317
|
+
/**
|
|
318
|
+
* CMCD Version 2
|
|
489
319
|
*
|
|
490
320
|
* @public
|
|
491
321
|
*/
|
|
492
|
-
const
|
|
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
|
-
];
|
|
322
|
+
const CMCD_V2 = 2;
|
|
512
323
|
|
|
513
324
|
//#endregion
|
|
514
|
-
//#region src/
|
|
515
|
-
const CMCD_V1_KEY_SET$1 = /* @__PURE__ */ new Set(CMCD_V1_KEYS);
|
|
325
|
+
//#region src/CmcdReportingMode.ts
|
|
516
326
|
/**
|
|
517
|
-
*
|
|
327
|
+
* CMCD event mode variable name.
|
|
518
328
|
*
|
|
519
|
-
* @
|
|
329
|
+
* @public
|
|
330
|
+
*/
|
|
331
|
+
const CMCD_EVENT_MODE = "event";
|
|
332
|
+
/**
|
|
333
|
+
* CMCD request mode variable name.
|
|
520
334
|
*
|
|
521
|
-
* @
|
|
335
|
+
* @public
|
|
336
|
+
*/
|
|
337
|
+
const CMCD_REQUEST_MODE = "request";
|
|
338
|
+
/**
|
|
339
|
+
* CMCD reporting mode types.
|
|
340
|
+
*
|
|
341
|
+
* @enum
|
|
522
342
|
*
|
|
523
343
|
* @public
|
|
344
|
+
*/
|
|
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
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Unwrap an inner list or SfItem value to a scalar.
|
|
524
358
|
*
|
|
525
|
-
*
|
|
526
|
-
*
|
|
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`.
|
|
527
362
|
*/
|
|
528
|
-
function
|
|
529
|
-
|
|
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
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Down-convert version 2 CMCD data to version 1.
|
|
379
|
+
*
|
|
380
|
+
* - Extracts `nrr` from the `nor` SfItem's `r` parameter.
|
|
381
|
+
* - Unwraps inner-list values to scalars for the object type `ot`.
|
|
382
|
+
*
|
|
383
|
+
* @internal
|
|
384
|
+
*/
|
|
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;
|
|
530
403
|
}
|
|
531
404
|
|
|
532
405
|
//#endregion
|
|
533
|
-
//#region src/
|
|
534
|
-
const
|
|
535
|
-
"ot",
|
|
536
|
-
"sf",
|
|
537
|
-
"st",
|
|
538
|
-
"e",
|
|
539
|
-
"sta"
|
|
540
|
-
]);
|
|
406
|
+
//#region src/isCmcdCustomKey.ts
|
|
407
|
+
const CUSTOM_KEY_REGEX = /^[a-z][a-z0-9.-]*$/;
|
|
541
408
|
/**
|
|
542
|
-
*
|
|
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.
|
|
543
413
|
*
|
|
544
414
|
* @param key - The key to check.
|
|
545
415
|
*
|
|
546
|
-
* @returns `true` if the key is a
|
|
416
|
+
* @returns `true` if the key is a custom key, `false` otherwise.
|
|
417
|
+
*
|
|
418
|
+
* @public
|
|
419
|
+
*
|
|
420
|
+
* @example
|
|
421
|
+
* {@includeCode ../test/isCmcdCustomKey.test.ts#example}
|
|
422
|
+
*/
|
|
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
|
+
};
|
|
436
|
+
/**
|
|
437
|
+
* Returns the row of a key in the key table.
|
|
438
|
+
*
|
|
439
|
+
* @param key - The key to look up.
|
|
440
|
+
*
|
|
441
|
+
* @returns The row of a reserved key, the custom row for a valid custom key, or `undefined`.
|
|
547
442
|
*
|
|
548
443
|
* @internal
|
|
549
444
|
*/
|
|
550
|
-
function
|
|
551
|
-
|
|
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;
|
|
552
448
|
}
|
|
553
449
|
|
|
554
450
|
//#endregion
|
|
@@ -572,6 +468,60 @@ function isValid(value) {
|
|
|
572
468
|
return value != null && value !== "" && value !== false;
|
|
573
469
|
}
|
|
574
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
|
+
}
|
|
501
|
+
/**
|
|
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.
|
|
505
|
+
*
|
|
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
|
|
512
|
+
*/
|
|
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
|
+
|
|
575
525
|
//#endregion
|
|
576
526
|
//#region src/toTokenString.ts
|
|
577
527
|
/**
|
|
@@ -593,52 +543,128 @@ function toTokenString(value) {
|
|
|
593
543
|
}
|
|
594
544
|
|
|
595
545
|
//#endregion
|
|
596
|
-
//#region src/
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
}
|
|
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
|
+
}
|
|
601
591
|
/**
|
|
602
|
-
*
|
|
592
|
+
* Applies the value rule of a key type from the key table.
|
|
593
|
+
*
|
|
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.
|
|
597
|
+
*
|
|
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.
|
|
603
|
+
*
|
|
604
|
+
* @internal
|
|
603
605
|
*/
|
|
604
|
-
function
|
|
605
|
-
if (
|
|
606
|
-
|
|
607
|
-
|
|
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;
|
|
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);
|
|
614
610
|
}
|
|
611
|
+
|
|
612
|
+
//#endregion
|
|
613
|
+
//#region src/toBareValue.ts
|
|
615
614
|
/**
|
|
616
|
-
*
|
|
615
|
+
* Returns the value inside an `SfItem`, or the value itself.
|
|
617
616
|
*
|
|
618
|
-
*
|
|
619
|
-
*
|
|
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.
|
|
619
|
+
*
|
|
620
|
+
* @param value - The value to read.
|
|
621
|
+
* @returns The value without its parameters.
|
|
622
|
+
*
|
|
623
|
+
* @internal
|
|
620
624
|
*/
|
|
621
|
-
function
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
return
|
|
625
|
+
function toBareValue(value) {
|
|
626
|
+
return value instanceof SfItem ? value.value : value;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
//#endregion
|
|
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]);
|
|
638
657
|
}
|
|
639
|
-
function
|
|
640
|
-
const formatter = options.formatters?.[key]
|
|
641
|
-
|
|
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;
|
|
642
668
|
}
|
|
643
669
|
/**
|
|
644
670
|
* Convert a generic object to CMCD data.
|
|
@@ -654,61 +680,66 @@ function formatValue(key, value, options, formatterOptions) {
|
|
|
654
680
|
function prepareCmcdData(obj, options = {}) {
|
|
655
681
|
const results = {};
|
|
656
682
|
if (obj == null || typeof obj !== "object") return results;
|
|
657
|
-
const version = options.version || obj["v"] || CMCD_V2;
|
|
683
|
+
const version = options.version || toBareValue(obj["v"]) || CMCD_V2;
|
|
658
684
|
const reportingMode = options.reportingMode || CMCD_REQUEST_MODE;
|
|
659
|
-
const
|
|
660
|
-
const
|
|
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");
|
|
685
|
+
const isV1 = version === 1;
|
|
686
|
+
const isEventReport = !isV1 && reportingMode === CMCD_EVENT_MODE;
|
|
678
687
|
const formatterOptions = {
|
|
679
688
|
version,
|
|
680
689
|
reportingMode,
|
|
681
690
|
baseUrl: options.baseUrl
|
|
682
691
|
};
|
|
683
|
-
|
|
692
|
+
const filter = options.filter;
|
|
693
|
+
let objectTypeValue;
|
|
684
694
|
let objectType;
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
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;
|
|
698
730
|
}
|
|
699
|
-
if (key === "
|
|
700
|
-
|
|
701
|
-
if (key === "
|
|
702
|
-
else
|
|
703
|
-
if (key === "
|
|
704
|
-
|
|
705
|
-
|
|
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);
|
|
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();
|
|
710
738
|
}
|
|
711
|
-
|
|
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;
|
|
712
743
|
}
|
|
713
744
|
return results;
|
|
714
745
|
}
|
|
@@ -916,15 +947,6 @@ function appendCmcdHeaders(headers, cmcd, options) {
|
|
|
916
947
|
return Object.assign(headers, toCmcdHeaders(cmcd, options));
|
|
917
948
|
}
|
|
918
949
|
|
|
919
|
-
//#endregion
|
|
920
|
-
//#region src/CMCD_PARAM.ts
|
|
921
|
-
/**
|
|
922
|
-
* CMCD parameter name.
|
|
923
|
-
*
|
|
924
|
-
* @public
|
|
925
|
-
*/
|
|
926
|
-
const CMCD_PARAM = "CMCD";
|
|
927
|
-
|
|
928
950
|
//#endregion
|
|
929
951
|
//#region src/encodePreparedCmcd.ts
|
|
930
952
|
/**
|
|
@@ -962,53 +984,99 @@ function encodeCmcd(cmcd, options = {}) {
|
|
|
962
984
|
}
|
|
963
985
|
|
|
964
986
|
//#endregion
|
|
965
|
-
//#region src/
|
|
987
|
+
//#region src/CMCD_PARAM.ts
|
|
966
988
|
/**
|
|
967
|
-
*
|
|
989
|
+
* CMCD parameter name.
|
|
968
990
|
*
|
|
969
|
-
* @
|
|
970
|
-
|
|
991
|
+
* @public
|
|
992
|
+
*/
|
|
993
|
+
const CMCD_PARAM = "CMCD";
|
|
994
|
+
|
|
995
|
+
//#endregion
|
|
996
|
+
//#region src/percentEncode.ts
|
|
997
|
+
const SUB_DELIMS_REGEX = /[!'()*]/g;
|
|
998
|
+
const SURROGATE_REGEX = /[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDFFF]/g;
|
|
999
|
+
function encodeSubDelim(char) {
|
|
1000
|
+
return `%${char.charCodeAt(0).toString(16).toUpperCase()}`;
|
|
1001
|
+
}
|
|
1002
|
+
function replaceUnpaired(match) {
|
|
1003
|
+
return match.length === 2 ? match : "�";
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Percent-encode a string for the `CMCD` query parameter.
|
|
971
1007
|
*
|
|
972
|
-
*
|
|
1008
|
+
* Only the unreserved characters of RFC 3986 stay unencoded: letters, digits,
|
|
1009
|
+
* `-`, `.`, `_`, and `~`. The query examples of CTA-5004-B use this encoding.
|
|
1010
|
+
* An unpaired surrogate becomes U+FFFD.
|
|
973
1011
|
*
|
|
974
|
-
* @
|
|
1012
|
+
* @param value - The string to encode.
|
|
1013
|
+
* @returns The encoded string.
|
|
975
1014
|
*
|
|
976
|
-
* @
|
|
1015
|
+
* @internal
|
|
977
1016
|
*/
|
|
978
|
-
function
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1017
|
+
function percentEncode(value) {
|
|
1018
|
+
let encoded;
|
|
1019
|
+
try {
|
|
1020
|
+
encoded = encodeURIComponent(value);
|
|
1021
|
+
} catch {
|
|
1022
|
+
encoded = encodeURIComponent(value.replace(SURROGATE_REGEX, replaceUnpaired));
|
|
1023
|
+
}
|
|
1024
|
+
return encoded.replace(SUB_DELIMS_REGEX, encodeSubDelim);
|
|
982
1025
|
}
|
|
983
1026
|
|
|
984
1027
|
//#endregion
|
|
985
|
-
//#region src/
|
|
1028
|
+
//#region src/replaceCmcdParam.ts
|
|
1029
|
+
function isCmcdParam(param) {
|
|
1030
|
+
return param === CMCD_PARAM || param.startsWith(`${CMCD_PARAM}=`);
|
|
1031
|
+
}
|
|
986
1032
|
/**
|
|
987
|
-
*
|
|
988
|
-
*
|
|
989
|
-
* @param cmcd - The CMCD object to convert.
|
|
990
|
-
* @param options - Options for encoding.
|
|
991
|
-
*
|
|
992
|
-
* @returns The CMCD query arg.
|
|
1033
|
+
* Replace the `CMCD` query parameter of a URL.
|
|
993
1034
|
*
|
|
994
|
-
*
|
|
1035
|
+
* The first `CMCD` parameter takes the new value in place. The function
|
|
1036
|
+
* removes the other `CMCD` parameters. If the URL has no `CMCD` parameter,
|
|
1037
|
+
* the new parameter goes at the end of the query. An empty `value` only
|
|
1038
|
+
* removes the parameters. The function does not change the rest of the URL.
|
|
1039
|
+
* The URL can be relative.
|
|
995
1040
|
*
|
|
996
|
-
* @
|
|
1041
|
+
* @param url - The URL.
|
|
1042
|
+
* @param value - The encoded CMCD data, before percent-encoding.
|
|
1043
|
+
* @returns The URL with at most one `CMCD` parameter.
|
|
997
1044
|
*
|
|
998
|
-
* @
|
|
999
|
-
* {@includeCode ../test/toCmcdQuery.test.ts#example}
|
|
1045
|
+
* @internal
|
|
1000
1046
|
*/
|
|
1001
|
-
function
|
|
1002
|
-
|
|
1003
|
-
|
|
1047
|
+
function replaceCmcdParam(url, value) {
|
|
1048
|
+
const hashIndex = url.indexOf("#");
|
|
1049
|
+
const end = hashIndex === -1 ? url.length : hashIndex;
|
|
1050
|
+
const queryIndex = url.indexOf("?");
|
|
1051
|
+
const start = queryIndex === -1 || queryIndex > end ? end : queryIndex;
|
|
1052
|
+
const params = [];
|
|
1053
|
+
let param = value ? `${CMCD_PARAM}=${percentEncode(value)}` : "";
|
|
1054
|
+
let found = false;
|
|
1055
|
+
for (const item of start < end ? url.slice(start + 1, end).split("&") : []) if (!isCmcdParam(item)) params.push(item);
|
|
1056
|
+
else {
|
|
1057
|
+
found = true;
|
|
1058
|
+
if (param) {
|
|
1059
|
+
params.push(param);
|
|
1060
|
+
param = "";
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
if (param) {
|
|
1064
|
+
if (params[params.length - 1] === "") params.pop();
|
|
1065
|
+
params.push(param);
|
|
1066
|
+
} else if (!found) return url;
|
|
1067
|
+
const query = params.join("&");
|
|
1068
|
+
return `${url.slice(0, start)}${query ? `?${query}` : ""}${url.slice(end)}`;
|
|
1004
1069
|
}
|
|
1005
1070
|
|
|
1006
1071
|
//#endregion
|
|
1007
1072
|
//#region src/appendCmcdQuery.ts
|
|
1008
|
-
const REGEX = /CMCD=[^&#]+/;
|
|
1009
1073
|
/**
|
|
1010
1074
|
* Append CMCD query args to a URL.
|
|
1011
1075
|
*
|
|
1076
|
+
* If the URL has a `CMCD` parameter, the new value replaces it in place. The
|
|
1077
|
+
* other parameters and the fragment stay as they are. If the data has no keys
|
|
1078
|
+
* to send, the function returns the URL unchanged.
|
|
1079
|
+
*
|
|
1012
1080
|
* @param url - The URL to append to.
|
|
1013
1081
|
* @param cmcd - The CMCD object to append.
|
|
1014
1082
|
* @param options - Options for encoding.
|
|
@@ -1023,10 +1091,8 @@ const REGEX = /CMCD=[^&#]+/;
|
|
|
1023
1091
|
* @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}
|
|
1024
1092
|
*/
|
|
1025
1093
|
function appendCmcdQuery(url, cmcd, options) {
|
|
1026
|
-
const
|
|
1027
|
-
|
|
1028
|
-
if (REGEX.test(url)) return url.replace(REGEX, query);
|
|
1029
|
-
return `${url}${url.includes("?") ? "&" : "?"}${query}`;
|
|
1094
|
+
const value = cmcd ? encodeCmcd(cmcd, options) : "";
|
|
1095
|
+
return value ? replaceCmcdParam(url, value) : url;
|
|
1030
1096
|
}
|
|
1031
1097
|
|
|
1032
1098
|
//#endregion
|
|
@@ -1038,6 +1104,176 @@ function appendCmcdQuery(url, cmcd, options) {
|
|
|
1038
1104
|
*/
|
|
1039
1105
|
const CMCD_DEFAULT_TIME_INTERVAL = 30;
|
|
1040
1106
|
|
|
1107
|
+
//#endregion
|
|
1108
|
+
//#region src/CMCD_EVENT_KEYS.ts
|
|
1109
|
+
/**
|
|
1110
|
+
* Defines the event-specific keys for CMCD (Common Media Client Data) version 2.
|
|
1111
|
+
*
|
|
1112
|
+
* @public
|
|
1113
|
+
*/
|
|
1114
|
+
const CMCD_EVENT_KEYS = [
|
|
1115
|
+
"cen",
|
|
1116
|
+
"e",
|
|
1117
|
+
"h",
|
|
1118
|
+
"ts"
|
|
1119
|
+
];
|
|
1120
|
+
|
|
1121
|
+
//#endregion
|
|
1122
|
+
//#region src/CMCD_FORMATTER_MAP.ts
|
|
1123
|
+
const formatNumbers = (value, format) => {
|
|
1124
|
+
if (Array.isArray(value)) {
|
|
1125
|
+
const list = [];
|
|
1126
|
+
for (const item of value) {
|
|
1127
|
+
const formatted = formatNumbers(item, format);
|
|
1128
|
+
if (Number.isFinite(formatted instanceof SfItem ? formatted.value : formatted)) list.push(formatted);
|
|
1129
|
+
}
|
|
1130
|
+
return list;
|
|
1131
|
+
}
|
|
1132
|
+
if (value instanceof SfItem) {
|
|
1133
|
+
const formatted = formatNumbers(value.value, format);
|
|
1134
|
+
return isValid(formatted) ? new SfItem(formatted, value.params) : NaN;
|
|
1135
|
+
}
|
|
1136
|
+
return typeof value === "number" ? format(value) : NaN;
|
|
1137
|
+
};
|
|
1138
|
+
const toRounded = (value) => formatNumbers(value, Math.round);
|
|
1139
|
+
const toNumber = (value) => formatNumbers(value, Number);
|
|
1140
|
+
const roundToHundred = (value) => Math.round(value / 100) * 100;
|
|
1141
|
+
const toHundred = (value) => formatNumbers(value, roundToHundred);
|
|
1142
|
+
/**
|
|
1143
|
+
* Formatters for CMCD values.
|
|
1144
|
+
*
|
|
1145
|
+
* The encoder applies the same rounding through its key table, and `nor` is the `nor` rule of the encoder.
|
|
1146
|
+
* Use an entry to build a custom formatter for `CmcdEncodeOptions.formatters`.
|
|
1147
|
+
* The formatter of a numeric key drops a value or a list element that is not a finite number.
|
|
1148
|
+
*
|
|
1149
|
+
* @public
|
|
1150
|
+
*/
|
|
1151
|
+
const CMCD_FORMATTER_MAP = {
|
|
1152
|
+
br: toRounded,
|
|
1153
|
+
d: toRounded,
|
|
1154
|
+
bl: toHundred,
|
|
1155
|
+
dl: toHundred,
|
|
1156
|
+
mtp: toHundred,
|
|
1157
|
+
nor: (value, options) => formatNor(value, options) ?? "",
|
|
1158
|
+
rtp: toHundred,
|
|
1159
|
+
tb: toRounded,
|
|
1160
|
+
tbl: toHundred,
|
|
1161
|
+
ab: toRounded,
|
|
1162
|
+
bsa: toRounded,
|
|
1163
|
+
bsd: toRounded,
|
|
1164
|
+
bsda: toRounded,
|
|
1165
|
+
dfa: toRounded,
|
|
1166
|
+
lab: toRounded,
|
|
1167
|
+
lb: toRounded,
|
|
1168
|
+
ltc: toRounded,
|
|
1169
|
+
msd: toRounded,
|
|
1170
|
+
pb: toRounded,
|
|
1171
|
+
pr: toNumber,
|
|
1172
|
+
pt: toRounded,
|
|
1173
|
+
rc: toRounded,
|
|
1174
|
+
sn: toRounded,
|
|
1175
|
+
tab: toRounded,
|
|
1176
|
+
tpb: toRounded,
|
|
1177
|
+
ts: toRounded,
|
|
1178
|
+
ttfb: toRounded,
|
|
1179
|
+
ttfbb: toRounded,
|
|
1180
|
+
ttlb: toRounded
|
|
1181
|
+
};
|
|
1182
|
+
|
|
1183
|
+
//#endregion
|
|
1184
|
+
//#region src/CMCD_REQUEST_KEYS.ts
|
|
1185
|
+
/**
|
|
1186
|
+
* Defines the request-specific keys for CMCD (Common Media Client Data) version 2.
|
|
1187
|
+
*
|
|
1188
|
+
* @public
|
|
1189
|
+
*/
|
|
1190
|
+
const CMCD_REQUEST_KEYS = [
|
|
1191
|
+
"ab",
|
|
1192
|
+
"bg",
|
|
1193
|
+
"bl",
|
|
1194
|
+
"br",
|
|
1195
|
+
"bs",
|
|
1196
|
+
"bsa",
|
|
1197
|
+
"bsd",
|
|
1198
|
+
"bsda",
|
|
1199
|
+
"cdn",
|
|
1200
|
+
"cid",
|
|
1201
|
+
"cs",
|
|
1202
|
+
"d",
|
|
1203
|
+
"dfa",
|
|
1204
|
+
"dl",
|
|
1205
|
+
"ec",
|
|
1206
|
+
"lab",
|
|
1207
|
+
"lb",
|
|
1208
|
+
"ltc",
|
|
1209
|
+
"msd",
|
|
1210
|
+
"mtp",
|
|
1211
|
+
"nor",
|
|
1212
|
+
"nr",
|
|
1213
|
+
"ot",
|
|
1214
|
+
"pb",
|
|
1215
|
+
"pr",
|
|
1216
|
+
"pt",
|
|
1217
|
+
"rtp",
|
|
1218
|
+
"sf",
|
|
1219
|
+
"sid",
|
|
1220
|
+
"sn",
|
|
1221
|
+
"st",
|
|
1222
|
+
"sta",
|
|
1223
|
+
"su",
|
|
1224
|
+
"tab",
|
|
1225
|
+
"tb",
|
|
1226
|
+
"tbl",
|
|
1227
|
+
"tpb",
|
|
1228
|
+
"v"
|
|
1229
|
+
];
|
|
1230
|
+
|
|
1231
|
+
//#endregion
|
|
1232
|
+
//#region src/CMCD_RESPONSE_KEYS.ts
|
|
1233
|
+
/**
|
|
1234
|
+
* Response-only and timing keys for version 2.
|
|
1235
|
+
*
|
|
1236
|
+
* @public
|
|
1237
|
+
*/
|
|
1238
|
+
const CMCD_RESPONSE_KEYS = [
|
|
1239
|
+
"cmsdd",
|
|
1240
|
+
"cmsds",
|
|
1241
|
+
"rc",
|
|
1242
|
+
"smrt",
|
|
1243
|
+
"ttfb",
|
|
1244
|
+
"ttfbb",
|
|
1245
|
+
"ttlb",
|
|
1246
|
+
"url"
|
|
1247
|
+
];
|
|
1248
|
+
|
|
1249
|
+
//#endregion
|
|
1250
|
+
//#region src/CMCD_V1_KEYS.ts
|
|
1251
|
+
/**
|
|
1252
|
+
* Defines the keys for CMCD (Common Media Client Data) version 1.
|
|
1253
|
+
*
|
|
1254
|
+
* @public
|
|
1255
|
+
*/
|
|
1256
|
+
const CMCD_V1_KEYS = [
|
|
1257
|
+
"bl",
|
|
1258
|
+
"br",
|
|
1259
|
+
"bs",
|
|
1260
|
+
"cid",
|
|
1261
|
+
"d",
|
|
1262
|
+
"dl",
|
|
1263
|
+
"mtp",
|
|
1264
|
+
"nor",
|
|
1265
|
+
"nrr",
|
|
1266
|
+
"ot",
|
|
1267
|
+
"pr",
|
|
1268
|
+
"rtp",
|
|
1269
|
+
"sf",
|
|
1270
|
+
"sid",
|
|
1271
|
+
"st",
|
|
1272
|
+
"su",
|
|
1273
|
+
"tb",
|
|
1274
|
+
"v"
|
|
1275
|
+
];
|
|
1276
|
+
|
|
1041
1277
|
//#endregion
|
|
1042
1278
|
//#region src/CMCD_KEYS.ts
|
|
1043
1279
|
/**
|
|
@@ -1087,16 +1323,156 @@ const CMCD_MIME_TYPE = "application/cmcd";
|
|
|
1087
1323
|
*
|
|
1088
1324
|
* @public
|
|
1089
1325
|
*/
|
|
1090
|
-
const CMCD_REQUEST_PROVENANCE = /* @__PURE__ */ Symbol.for("@svta/cml-cmcd/request-provenance");
|
|
1091
|
-
|
|
1092
|
-
//#endregion
|
|
1093
|
-
//#region src/CMCD_V1.ts
|
|
1326
|
+
const CMCD_REQUEST_PROVENANCE = /* @__PURE__ */ Symbol.for("@svta/cml-cmcd/request-provenance");
|
|
1327
|
+
|
|
1328
|
+
//#endregion
|
|
1329
|
+
//#region src/CMCD_V1.ts
|
|
1330
|
+
/**
|
|
1331
|
+
* CMCD Version 1
|
|
1332
|
+
*
|
|
1333
|
+
* @public
|
|
1334
|
+
*/
|
|
1335
|
+
const CMCD_V1 = 1;
|
|
1336
|
+
|
|
1337
|
+
//#endregion
|
|
1338
|
+
//#region src/CmcdEventType.ts
|
|
1339
|
+
/**
|
|
1340
|
+
* CMCD event type 'bc' (bitrate change).
|
|
1341
|
+
*
|
|
1342
|
+
* @public
|
|
1343
|
+
*/
|
|
1344
|
+
const CMCD_EVENT_BITRATE_CHANGE = "bc";
|
|
1345
|
+
/**
|
|
1346
|
+
* CMCD event type 'ps' (play state change).
|
|
1347
|
+
*
|
|
1348
|
+
* @public
|
|
1349
|
+
*/
|
|
1350
|
+
const CMCD_EVENT_PLAY_STATE = "ps";
|
|
1351
|
+
/**
|
|
1352
|
+
* CMCD event type 'pr' (playback rate change).
|
|
1353
|
+
*
|
|
1354
|
+
* @public
|
|
1355
|
+
*/
|
|
1356
|
+
const CMCD_EVENT_PLAYBACK_RATE = "pr";
|
|
1357
|
+
/**
|
|
1358
|
+
* CMCD event type 'e' (error).
|
|
1359
|
+
*
|
|
1360
|
+
* @public
|
|
1361
|
+
*/
|
|
1362
|
+
const CMCD_EVENT_ERROR = "e";
|
|
1363
|
+
/**
|
|
1364
|
+
* CMCD event type 't' (time interval).
|
|
1365
|
+
*
|
|
1366
|
+
* @public
|
|
1367
|
+
*/
|
|
1368
|
+
const CMCD_EVENT_TIME_INTERVAL = "t";
|
|
1369
|
+
/**
|
|
1370
|
+
* CMCD event type 'c' (content ID).
|
|
1371
|
+
*
|
|
1372
|
+
* @public
|
|
1373
|
+
*/
|
|
1374
|
+
const CMCD_EVENT_CONTENT_ID = "c";
|
|
1375
|
+
/**
|
|
1376
|
+
* CMCD event type 'b' (backgrounded mode).
|
|
1377
|
+
*
|
|
1378
|
+
* @public
|
|
1379
|
+
*/
|
|
1380
|
+
const CMCD_EVENT_BACKGROUNDED_MODE = "b";
|
|
1381
|
+
/**
|
|
1382
|
+
* CMCD event type 'm' (mute).
|
|
1383
|
+
*
|
|
1384
|
+
* @public
|
|
1385
|
+
*/
|
|
1386
|
+
const CMCD_EVENT_MUTE = "m";
|
|
1387
|
+
/**
|
|
1388
|
+
* CMCD event type 'um' (unmute).
|
|
1389
|
+
*
|
|
1390
|
+
* @public
|
|
1391
|
+
*/
|
|
1392
|
+
const CMCD_EVENT_UNMUTE = "um";
|
|
1393
|
+
/**
|
|
1394
|
+
* CMCD event type 'pe' (player expand).
|
|
1395
|
+
*
|
|
1396
|
+
* @public
|
|
1397
|
+
*/
|
|
1398
|
+
const CMCD_EVENT_PLAYER_EXPAND = "pe";
|
|
1399
|
+
/**
|
|
1400
|
+
* CMCD event type 'pc' (player collapse).
|
|
1401
|
+
*
|
|
1402
|
+
* @public
|
|
1403
|
+
*/
|
|
1404
|
+
const CMCD_EVENT_PLAYER_COLLAPSE = "pc";
|
|
1405
|
+
/**
|
|
1406
|
+
* CMCD event type 'rr' (response received).
|
|
1407
|
+
*
|
|
1408
|
+
* @public
|
|
1409
|
+
*/
|
|
1410
|
+
const CMCD_EVENT_RESPONSE_RECEIVED = "rr";
|
|
1411
|
+
/**
|
|
1412
|
+
* CMCD event type 'as' (ad start).
|
|
1413
|
+
*
|
|
1414
|
+
* @public
|
|
1415
|
+
*/
|
|
1416
|
+
const CMCD_EVENT_AD_START = "as";
|
|
1417
|
+
/**
|
|
1418
|
+
* CMCD event type 'ae' (ad end).
|
|
1419
|
+
*
|
|
1420
|
+
* @public
|
|
1421
|
+
*/
|
|
1422
|
+
const CMCD_EVENT_AD_END = "ae";
|
|
1423
|
+
/**
|
|
1424
|
+
* CMCD event type 'abs' (ad break start).
|
|
1425
|
+
*
|
|
1426
|
+
* @public
|
|
1427
|
+
*/
|
|
1428
|
+
const CMCD_EVENT_AD_BREAK_START = "abs";
|
|
1429
|
+
/**
|
|
1430
|
+
* CMCD event type 'abe' (ad break end).
|
|
1431
|
+
*
|
|
1432
|
+
* @public
|
|
1433
|
+
*/
|
|
1434
|
+
const CMCD_EVENT_AD_BREAK_END = "abe";
|
|
1435
|
+
/**
|
|
1436
|
+
* CMCD event type 'sk' (skip).
|
|
1437
|
+
*
|
|
1438
|
+
* @public
|
|
1439
|
+
*/
|
|
1440
|
+
const CMCD_EVENT_SKIP = "sk";
|
|
1441
|
+
/**
|
|
1442
|
+
* CMCD event type 'ce' (custom event).
|
|
1443
|
+
*
|
|
1444
|
+
* @public
|
|
1445
|
+
*/
|
|
1446
|
+
const CMCD_EVENT_CUSTOM_EVENT = "ce";
|
|
1094
1447
|
/**
|
|
1095
|
-
* CMCD
|
|
1448
|
+
* CMCD event types for the 'e' key (event mode).
|
|
1449
|
+
*
|
|
1450
|
+
* @enum
|
|
1451
|
+
*
|
|
1452
|
+
* @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#event | CTA-5004-B Event}
|
|
1096
1453
|
*
|
|
1097
1454
|
* @public
|
|
1098
1455
|
*/
|
|
1099
|
-
const
|
|
1456
|
+
const CmcdEventType = {
|
|
1457
|
+
BITRATE_CHANGE: CMCD_EVENT_BITRATE_CHANGE,
|
|
1458
|
+
PLAY_STATE: CMCD_EVENT_PLAY_STATE,
|
|
1459
|
+
PLAYBACK_RATE: CMCD_EVENT_PLAYBACK_RATE,
|
|
1460
|
+
ERROR: CMCD_EVENT_ERROR,
|
|
1461
|
+
TIME_INTERVAL: CMCD_EVENT_TIME_INTERVAL,
|
|
1462
|
+
CONTENT_ID: CMCD_EVENT_CONTENT_ID,
|
|
1463
|
+
BACKGROUNDED_MODE: CMCD_EVENT_BACKGROUNDED_MODE,
|
|
1464
|
+
MUTE: CMCD_EVENT_MUTE,
|
|
1465
|
+
UNMUTE: CMCD_EVENT_UNMUTE,
|
|
1466
|
+
PLAYER_EXPAND: CMCD_EVENT_PLAYER_EXPAND,
|
|
1467
|
+
PLAYER_COLLAPSE: CMCD_EVENT_PLAYER_COLLAPSE,
|
|
1468
|
+
RESPONSE_RECEIVED: CMCD_EVENT_RESPONSE_RECEIVED,
|
|
1469
|
+
AD_START: CMCD_EVENT_AD_START,
|
|
1470
|
+
AD_END: CMCD_EVENT_AD_END,
|
|
1471
|
+
AD_BREAK_START: CMCD_EVENT_AD_BREAK_START,
|
|
1472
|
+
AD_BREAK_END: CMCD_EVENT_AD_BREAK_END,
|
|
1473
|
+
SKIP: CMCD_EVENT_SKIP,
|
|
1474
|
+
CUSTOM_EVENT: CMCD_EVENT_CUSTOM_EVENT
|
|
1475
|
+
};
|
|
1100
1476
|
|
|
1101
1477
|
//#endregion
|
|
1102
1478
|
//#region src/CmcdObjectType.ts
|
|
@@ -1556,6 +1932,39 @@ var CmcdReportRecorder = class {
|
|
|
1556
1932
|
}
|
|
1557
1933
|
};
|
|
1558
1934
|
|
|
1935
|
+
//#endregion
|
|
1936
|
+
//#region src/CMCD_STATE_EVENT_FIELDS.ts
|
|
1937
|
+
/**
|
|
1938
|
+
* Maps each state-change event type to the persistent field whose value
|
|
1939
|
+
* the event signals.
|
|
1940
|
+
*
|
|
1941
|
+
* The key table (`CMCD_KEY_SPECS`) marks the field as always sent on its
|
|
1942
|
+
* event, so the key filter of `prepareCmcdData` does not remove it.
|
|
1943
|
+
* `CmcdReporter` deduplicates events against the field value. It drops an
|
|
1944
|
+
* event whose field has no value and restores the field if a transform
|
|
1945
|
+
* removes it.
|
|
1946
|
+
*
|
|
1947
|
+
* Sending the field with each state-change event is a library choice.
|
|
1948
|
+
* The exception is the exit from backgrounded mode, which CTA-5004-B
|
|
1949
|
+
* defines as a `b` event without `bg`. CTA-5004-B requires the field only
|
|
1950
|
+
* on the `ps` event, which MUST carry `sta`. CTA-5004-B states no such rule
|
|
1951
|
+
* for `pr`, `c`, or `bc`. `validateCmcdStructure` follows CTA-5004-B and
|
|
1952
|
+
* does not read this map.
|
|
1953
|
+
*
|
|
1954
|
+
* Iteration order matters: `CmcdReporter.update()` records state-change
|
|
1955
|
+
* events in map order when several tracked fields change in one call.
|
|
1956
|
+
* Do not reorder entries without auditing reporter behavior.
|
|
1957
|
+
*
|
|
1958
|
+
* @internal
|
|
1959
|
+
*/
|
|
1960
|
+
const CMCD_STATE_EVENT_FIELDS = /* @__PURE__ */ new Map([
|
|
1961
|
+
[CMCD_EVENT_PLAY_STATE, "sta"],
|
|
1962
|
+
[CMCD_EVENT_PLAYBACK_RATE, "pr"],
|
|
1963
|
+
[CMCD_EVENT_CONTENT_ID, "cid"],
|
|
1964
|
+
[CMCD_EVENT_BACKGROUNDED_MODE, "bg"],
|
|
1965
|
+
[CMCD_EVENT_BITRATE_CHANGE, "br"]
|
|
1966
|
+
]);
|
|
1967
|
+
|
|
1559
1968
|
//#endregion
|
|
1560
1969
|
//#region src/CmcdTransmissionMode.ts
|
|
1561
1970
|
/**
|
|
@@ -1599,19 +2008,21 @@ const CmcdTransmissionMode = {
|
|
|
1599
2008
|
* - Wraps scalar values in arrays for inner-list keys.
|
|
1600
2009
|
* - Wraps `nor` string in an array.
|
|
1601
2010
|
*
|
|
1602
|
-
* The function returns version 2 data (has `v: 2`) unchanged.
|
|
2011
|
+
* The function returns version 2 data (has `v: 2`) unchanged. An inner list
|
|
2012
|
+
* with parameters, which `decodeCmcd` returns as an `SfItem` that wraps the
|
|
2013
|
+
* list, also stays unchanged.
|
|
1603
2014
|
*
|
|
1604
2015
|
* @internal
|
|
1605
2016
|
*/
|
|
1606
2017
|
function upConvertToV2(obj) {
|
|
1607
|
-
if (obj["v"] === CMCD_V2) return obj;
|
|
2018
|
+
if (toBareValue(obj["v"]) === CMCD_V2) return obj;
|
|
1608
2019
|
const result = {};
|
|
1609
2020
|
for (const [key, value] of Object.entries(obj)) {
|
|
1610
2021
|
if (value == null) {
|
|
1611
2022
|
result[key] = value;
|
|
1612
2023
|
continue;
|
|
1613
2024
|
}
|
|
1614
|
-
if (CMCD_INNER_LIST_KEYS.has(key) && !Array.isArray(value)) result[key] = [value];
|
|
2025
|
+
if (CMCD_INNER_LIST_KEYS.has(key) && !Array.isArray(toBareValue(value))) result[key] = [value];
|
|
1615
2026
|
else if (key === "nor" && typeof value === "string") result[key] = [value];
|
|
1616
2027
|
else result[key] = value;
|
|
1617
2028
|
}
|
|
@@ -1661,10 +2072,9 @@ function cmcdObjectTypeListEqual(a, b) {
|
|
|
1661
2072
|
const ai = a[i];
|
|
1662
2073
|
const bi = b[i];
|
|
1663
2074
|
if (ai === bi) continue;
|
|
1664
|
-
if (typeof ai === "number"
|
|
1665
|
-
|
|
1666
|
-
const
|
|
1667
|
-
const bp = bi.params;
|
|
2075
|
+
if ((typeof ai === "number" ? ai : ai.value) !== (typeof bi === "number" ? bi : bi.value)) return false;
|
|
2076
|
+
const ap = typeof ai === "number" ? void 0 : ai.params;
|
|
2077
|
+
const bp = typeof bi === "number" ? void 0 : bi.params;
|
|
1668
2078
|
const ak = ap && Object.keys(ap)[0];
|
|
1669
2079
|
const bk = bp && Object.keys(bp)[0];
|
|
1670
2080
|
if (ak !== bk) return false;
|
|
@@ -1672,6 +2082,18 @@ function cmcdObjectTypeListEqual(a, b) {
|
|
|
1672
2082
|
}
|
|
1673
2083
|
return true;
|
|
1674
2084
|
}
|
|
2085
|
+
/**
|
|
2086
|
+
* Equality for `br` deduplication. A `br` value can be an `SfItem` that
|
|
2087
|
+
* wraps the list, as `decodeCmcd` returns for an inner list with
|
|
2088
|
+
* parameters. Two values are equal when their lists are equal. The
|
|
2089
|
+
* parameters of the list are not compared.
|
|
2090
|
+
*/
|
|
2091
|
+
function brEqual(a, b) {
|
|
2092
|
+
const listA = toBareValue(a);
|
|
2093
|
+
const listB = toBareValue(b);
|
|
2094
|
+
if (!Array.isArray(listA) || !Array.isArray(listB)) return Object.is(a, b);
|
|
2095
|
+
return cmcdObjectTypeListEqual(listA, listB);
|
|
2096
|
+
}
|
|
1675
2097
|
const equal = Object.is;
|
|
1676
2098
|
const identity = (v) => v;
|
|
1677
2099
|
/**
|
|
@@ -1682,8 +2104,8 @@ const STATE_FIELDS = /* @__PURE__ */ Array.from(CMCD_STATE_EVENT_FIELDS, ([event
|
|
|
1682
2104
|
if (field === "br") return {
|
|
1683
2105
|
event,
|
|
1684
2106
|
field,
|
|
1685
|
-
equal:
|
|
1686
|
-
snapshot: (v) => v.slice()
|
|
2107
|
+
equal: brEqual,
|
|
2108
|
+
snapshot: (v) => Array.isArray(v) ? v.slice() : copyItemValue(v)
|
|
1687
2109
|
};
|
|
1688
2110
|
return {
|
|
1689
2111
|
event,
|
|
@@ -1702,29 +2124,14 @@ function buildRequiredEventKeys() {
|
|
|
1702
2124
|
]);
|
|
1703
2125
|
}
|
|
1704
2126
|
/**
|
|
1705
|
-
* Maps each event type to the key
|
|
1706
|
-
* Built from the state-change table plus the three
|
|
1707
|
-
* required key is event data, not player state.
|
|
2127
|
+
* Maps each event type to the key that a report of that type must keep,
|
|
2128
|
+
* beyond `e` and `ts`. Built from the state-change table plus the three
|
|
2129
|
+
* event types whose required key is event data, not player state.
|
|
1708
2130
|
*/
|
|
1709
2131
|
const CMCD_REQUIRED_EVENT_KEYS = /* @__PURE__ */ buildRequiredEventKeys();
|
|
1710
2132
|
/**
|
|
1711
|
-
*
|
|
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.
|
|
2133
|
+
* Copies a value with the `SfItem` structure, including its `params` record
|
|
2134
|
+
* and the list of an `SfItem` that wraps a list.
|
|
1728
2135
|
*
|
|
1729
2136
|
* The copy keeps the prototype because `prepareCmcdData`, the formatter map,
|
|
1730
2137
|
* validation, and the structured-field encoder all branch on
|
|
@@ -1735,6 +2142,7 @@ function copyItemValue(value) {
|
|
|
1735
2142
|
if (value === null || typeof value !== "object") return value;
|
|
1736
2143
|
const copy = Object.assign(Object.create(Object.getPrototypeOf(value)), value);
|
|
1737
2144
|
if (copy.params !== null && typeof copy.params === "object") copy.params = { ...copy.params };
|
|
2145
|
+
if (Array.isArray(copy.value)) copy.value = copy.value.map(copyItemValue);
|
|
1738
2146
|
return copy;
|
|
1739
2147
|
}
|
|
1740
2148
|
/**
|
|
@@ -1744,7 +2152,8 @@ function copyItemValue(value) {
|
|
|
1744
2152
|
*
|
|
1745
2153
|
* The copy is complete for the CMCD value space. `CmcdValue` and
|
|
1746
2154
|
* `CmcdCustomValue` admit only primitives, `SfItem<primitive>`, and arrays of
|
|
1747
|
-
* those. `
|
|
2155
|
+
* those. `decodeCmcd` also returns an `SfItem` that wraps a list, and the
|
|
2156
|
+
* copy includes that list. `SfItem.params` is a flat record. The reporter calls this function
|
|
1748
2157
|
* where a transform is configured, at session end on the ended session's
|
|
1749
2158
|
* store, and on the request's stored player-facing view. The session-end
|
|
1750
2159
|
* copy detaches the frozen snapshot from caller-held references.
|
|
@@ -1873,7 +2282,7 @@ var CmcdReporter = class {
|
|
|
1873
2282
|
provenance: mintProvenance(sid, data.cid),
|
|
1874
2283
|
data,
|
|
1875
2284
|
msd: NaN,
|
|
1876
|
-
lastEmitted: {},
|
|
2285
|
+
lastEmitted: toBareValue(data.bg) === true ? {} : { bg: false },
|
|
1877
2286
|
eventTargets,
|
|
1878
2287
|
requestTarget: {
|
|
1879
2288
|
sn: 0,
|
|
@@ -1952,6 +2361,14 @@ var CmcdReporter = class {
|
|
|
1952
2361
|
* a new session therefore always fires, even when the persisted value did
|
|
1953
2362
|
* not change across the `sid` boundary.
|
|
1954
2363
|
*
|
|
2364
|
+
* The `bg` field is an exception. A session that does not start with
|
|
2365
|
+
* `bg: true` begins with `false` as its last reported `bg` value.
|
|
2366
|
+
* `bg: false` therefore fires `BACKGROUNDED_MODE` only if the session
|
|
2367
|
+
* started with `bg: true` or reported `bg: true`. CTA-5004-B defines a
|
|
2368
|
+
* `b` event without `bg` as the exit from backgrounded mode, so the exit
|
|
2369
|
+
* report does not carry `bg`. A target transform can add `bg: false` to
|
|
2370
|
+
* the exit report. The encoder writes that value as `?0`.
|
|
2371
|
+
*
|
|
1955
2372
|
* Multi-field updates fire the events in this order: `sta`, `pr`, `cid`,
|
|
1956
2373
|
* `bg`, `br`. The order of keys in the input object does not affect the
|
|
1957
2374
|
* firing order.
|
|
@@ -1971,8 +2388,7 @@ var CmcdReporter = class {
|
|
|
1971
2388
|
* writes them into every outgoing report, so this method is the only way
|
|
1972
2389
|
* to change them. A per-call value on {@link CmcdReporter.recordEvent},
|
|
1973
2390
|
* {@link CmcdReporter.createRequestReport}, or
|
|
1974
|
-
* {@link CmcdReporter.recordResponseReceived} has no effect.
|
|
1975
|
-
* attribution uses the provenance record alone.
|
|
2391
|
+
* {@link CmcdReporter.recordResponseReceived} has no effect.
|
|
1976
2392
|
* `msd` must be a finite number of milliseconds between `0` and
|
|
1977
2393
|
* `999_999_999_999_999` (the RFC 8941 integer maximum). The reporter
|
|
1978
2394
|
* rounds `msd` to the nearest integer and ignores invalid values.
|
|
@@ -2035,9 +2451,9 @@ var CmcdReporter = class {
|
|
|
2035
2451
|
* data store, as `update()` would.
|
|
2036
2452
|
* 2. Discards the event if the state field has no value after that write
|
|
2037
2453
|
* (never set, or cleared with `update({ field: undefined })`).
|
|
2038
|
-
* A state-change event without its required field would violate CTA-5004-B.
|
|
2039
2454
|
* 3. Suppresses the event if the field's current value equals the
|
|
2040
|
-
* last reported value (no state transition).
|
|
2455
|
+
* last reported value (no state transition). {@link CmcdReporter.update}
|
|
2456
|
+
* describes the `bg` value that a new session starts with.
|
|
2041
2457
|
*
|
|
2042
2458
|
* The reporter always records all other event types.
|
|
2043
2459
|
*
|
|
@@ -2123,6 +2539,7 @@ var CmcdReporter = class {
|
|
|
2123
2539
|
e: type,
|
|
2124
2540
|
ts: data.ts ?? Date.now()
|
|
2125
2541
|
};
|
|
2542
|
+
if (type === CMCD_EVENT_BACKGROUNDED_MODE && toBareValue(item.bg) === false) delete item.bg;
|
|
2126
2543
|
const { transform } = config;
|
|
2127
2544
|
if (!transform) {
|
|
2128
2545
|
this.queueTargetEvent(session, target, config, item, type);
|
|
@@ -2134,8 +2551,8 @@ var CmcdReporter = class {
|
|
|
2134
2551
|
const ts = item.ts;
|
|
2135
2552
|
const report = transform(item, request);
|
|
2136
2553
|
if (report == null) return;
|
|
2137
|
-
if (!
|
|
2138
|
-
if (requiredKey &&
|
|
2554
|
+
if (!isValid(report.ts)) report.ts = ts;
|
|
2555
|
+
if (requiredKey && isValid(requiredValue) && !isValid(report[requiredKey])) Object.assign(report, { [requiredKey]: requiredValue });
|
|
2139
2556
|
this.queueTargetEvent(session, target, config, report, type);
|
|
2140
2557
|
}
|
|
2141
2558
|
/**
|
|
@@ -2189,27 +2606,24 @@ var CmcdReporter = class {
|
|
|
2189
2606
|
* TypeScript therefore rejects a call whose request the configured
|
|
2190
2607
|
* transforms could not read. See {@link CmcdReporterCustomData}.
|
|
2191
2608
|
*
|
|
2192
|
-
* The
|
|
2193
|
-
*
|
|
2194
|
-
* {@link
|
|
2195
|
-
*
|
|
2196
|
-
*
|
|
2197
|
-
*
|
|
2198
|
-
*
|
|
2199
|
-
*
|
|
2200
|
-
*
|
|
2201
|
-
*
|
|
2202
|
-
*
|
|
2203
|
-
* 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.
|
|
2609
|
+
* The provenance record that {@link CmcdReporter.createRequestReport}
|
|
2610
|
+
* stored on the request's `customData` (under
|
|
2611
|
+
* {@link CMCD_REQUEST_PROVENANCE}) selects the session by `sid`. A record
|
|
2612
|
+
* that names no retained session drops the response (see
|
|
2613
|
+
* `CmcdReporterConfig.sessionRetention`). A request without a record reports
|
|
2614
|
+
* under the current session, as in version 2.4.0. The request-time data then
|
|
2615
|
+
* comes from `customData.cmcd`. A per-call `data.sid` cannot substitute. The
|
|
2616
|
+
* reporter accepts any record whose `sid` names a retained session. The
|
|
2617
|
+
* record may come from another reporter with the same session attributes,
|
|
2618
|
+
* or be hand-built. If the request has a record, a response that completes
|
|
2619
|
+
* after a `sid` change reports under its own retained session, with that
|
|
2620
|
+
* session's data snapshot and sequence numbers.
|
|
2207
2621
|
*
|
|
2208
|
-
*
|
|
2209
|
-
* snapshot, which the reporter decodes for each response.
|
|
2210
|
-
* replaces the session's current `cid` in the report. A
|
|
2211
|
-
* completes after a mid-session content change therefore
|
|
2212
|
-
* request-time values.
|
|
2622
|
+
* With a record, request-time report keys come from the record's encoded
|
|
2623
|
+
* per-call `data` snapshot, which the reporter decodes for each response.
|
|
2624
|
+
* The record's `cid` replaces the session's current `cid` in the report. A
|
|
2625
|
+
* response that completes after a mid-session content change therefore
|
|
2626
|
+
* reports its request-time values.
|
|
2213
2627
|
*
|
|
2214
2628
|
* @typeParam RD - The `customData` of this request. Defaults to the
|
|
2215
2629
|
* reporter's own `C`.
|
|
@@ -2218,21 +2632,19 @@ var CmcdReporter = class {
|
|
|
2218
2632
|
* @param data - Additional CMCD data to include with the event.
|
|
2219
2633
|
* Values provided here override the derived values.
|
|
2220
2634
|
* The reporter ignores session-owned keys (`sid`, `msd`)
|
|
2221
|
-
* supplied here.
|
|
2635
|
+
* supplied here.
|
|
2222
2636
|
*/
|
|
2223
2637
|
recordResponseReceived(response, data = {}) {
|
|
2224
2638
|
const { request } = response;
|
|
2225
2639
|
const url = data.url ?? request?.url;
|
|
2226
2640
|
if (!url) return;
|
|
2227
2641
|
const provenance = request.customData?.[CMCD_REQUEST_PROVENANCE];
|
|
2228
|
-
const session = this.resolveSession(provenance);
|
|
2642
|
+
const session = provenance === void 0 ? this.session : this.resolveSession(provenance);
|
|
2229
2643
|
if (!session) return;
|
|
2230
|
-
const cmcd = decodeSnapshot(provenance);
|
|
2231
|
-
const { cid } = provenance;
|
|
2232
|
-
const urlObj = new URL(url);
|
|
2233
|
-
urlObj.searchParams.delete(CMCD_PARAM);
|
|
2644
|
+
const cmcd = provenance === void 0 ? { ...request.customData?.cmcd } : decodeSnapshot(provenance);
|
|
2645
|
+
const { cid } = provenance ?? {};
|
|
2234
2646
|
const derived = {
|
|
2235
|
-
url:
|
|
2647
|
+
url: replaceCmcdParam(url),
|
|
2236
2648
|
rc: response.status
|
|
2237
2649
|
};
|
|
2238
2650
|
const timing = response.resourceTiming;
|
|
@@ -2251,11 +2663,11 @@ var CmcdReporter = class {
|
|
|
2251
2663
|
}, request);
|
|
2252
2664
|
}
|
|
2253
2665
|
/**
|
|
2254
|
-
* Resolves the session a
|
|
2666
|
+
* Resolves the session that a provenance record names. The record's `sid`
|
|
2255
2667
|
* must name one of this reporter's retained sessions, or the reporter
|
|
2256
|
-
* discards the response.
|
|
2257
|
-
*
|
|
2258
|
-
*
|
|
2668
|
+
* discards the response. A record that names a removed or unknown `sid`
|
|
2669
|
+
* resolves nothing. Any other attribution would be wrong. The reporter reads
|
|
2670
|
+
* the `sid` as a plain property. A record
|
|
2259
2671
|
* copied through JSON therefore resolves the same session, and a hand-built
|
|
2260
2672
|
* record that names a retained session is accepted.
|
|
2261
2673
|
*/
|
|
@@ -2359,17 +2771,13 @@ var CmcdReporter = class {
|
|
|
2359
2771
|
const sendMsd = !isNaN(session.msd) && !session.requestTarget.msdSent;
|
|
2360
2772
|
if (sendMsd) cmcdData.msd = session.msd;
|
|
2361
2773
|
else delete cmcdData.msd;
|
|
2362
|
-
const url = new URL(report.url);
|
|
2363
2774
|
const options = createEncodingOptions(CMCD_REQUEST_MODE, this.config, report.url);
|
|
2364
2775
|
const cmcd = report.customData.cmcd = copyReportValues(prepareCmcdData(cmcdData, options));
|
|
2365
2776
|
if (sendMsd && cmcd.msd !== void 0) session.requestTarget.msdSent = true;
|
|
2366
2777
|
const encoded = encodePreparedCmcd(cmcd);
|
|
2367
2778
|
switch (this.config.transmissionMode) {
|
|
2368
2779
|
case CMCD_QUERY:
|
|
2369
|
-
if (encoded)
|
|
2370
|
-
url.searchParams.set(CMCD_PARAM, encoded);
|
|
2371
|
-
report.url = url.toString();
|
|
2372
|
-
}
|
|
2780
|
+
if (encoded) report.url = replaceCmcdParam(report.url, encoded);
|
|
2373
2781
|
break;
|
|
2374
2782
|
case CMCD_HEADERS:
|
|
2375
2783
|
Object.assign(report.headers, toPreparedCmcdHeaders(cmcd, options.customHeaderMap));
|
|
@@ -2541,7 +2949,64 @@ function fromCmcdQuery(query, options) {
|
|
|
2541
2949
|
//#endregion
|
|
2542
2950
|
//#region src/fromCmcdUrl.ts
|
|
2543
2951
|
function fromCmcdUrl(url, options) {
|
|
2544
|
-
return decodeCmcd(decodeURIComponent(url.replace(/^CMCD=/, "")), options);
|
|
2952
|
+
return decodeCmcd(decodeURIComponent(url.replace(/^CMCD=/, "").replace(/\+/g, " ")), options);
|
|
2953
|
+
}
|
|
2954
|
+
|
|
2955
|
+
//#endregion
|
|
2956
|
+
//#region src/isCmcdRequestKey.ts
|
|
2957
|
+
const CMCD_REQUEST_KEY_SET = /* @__PURE__ */ new Set(CMCD_REQUEST_KEYS);
|
|
2958
|
+
/**
|
|
2959
|
+
* Check if a key is a valid CMCD request key.
|
|
2960
|
+
*
|
|
2961
|
+
* @param key - The key to check.
|
|
2962
|
+
*
|
|
2963
|
+
* @returns `true` if the key is a valid CMCD request key, `false` otherwise.
|
|
2964
|
+
*
|
|
2965
|
+
* @public
|
|
2966
|
+
*
|
|
2967
|
+
* @example
|
|
2968
|
+
* {@includeCode ../test/isCmcdRequestKey.test.ts#example}
|
|
2969
|
+
*/
|
|
2970
|
+
function isCmcdRequestKey(key) {
|
|
2971
|
+
return CMCD_REQUEST_KEY_SET.has(key) || isCmcdCustomKey(key);
|
|
2972
|
+
}
|
|
2973
|
+
|
|
2974
|
+
//#endregion
|
|
2975
|
+
//#region src/isCmcdResponseReceivedKey.ts
|
|
2976
|
+
const CMCD_RESPONSE_KEY_SET = /* @__PURE__ */ new Set(CMCD_RESPONSE_KEYS);
|
|
2977
|
+
/**
|
|
2978
|
+
* Check if a key is a valid CMCD response key.
|
|
2979
|
+
*
|
|
2980
|
+
* @param key - The key to check.
|
|
2981
|
+
*
|
|
2982
|
+
* @returns `true` if the key is a valid CMCD response key, `false` otherwise.
|
|
2983
|
+
*
|
|
2984
|
+
* @public
|
|
2985
|
+
*
|
|
2986
|
+
* @example
|
|
2987
|
+
* {@includeCode ../test/isCmcdResponseReceivedKey.test.ts#example}
|
|
2988
|
+
*/
|
|
2989
|
+
function isCmcdResponseReceivedKey(key) {
|
|
2990
|
+
return CMCD_RESPONSE_KEY_SET.has(key);
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2993
|
+
//#endregion
|
|
2994
|
+
//#region src/isCmcdEventKey.ts
|
|
2995
|
+
const CMCD_EVENT_KEY_SET = /* @__PURE__ */ new Set(CMCD_EVENT_KEYS);
|
|
2996
|
+
/**
|
|
2997
|
+
* Check if a key is a valid CMCD event key.
|
|
2998
|
+
*
|
|
2999
|
+
* @param key - The key to check.
|
|
3000
|
+
*
|
|
3001
|
+
* @returns `true` if the key is a valid CMCD event key, `false` otherwise.
|
|
3002
|
+
*
|
|
3003
|
+
* @public
|
|
3004
|
+
*
|
|
3005
|
+
* @example
|
|
3006
|
+
* {@includeCode ../test/isCmcdEventKey.test.ts#example}
|
|
3007
|
+
*/
|
|
3008
|
+
function isCmcdEventKey(key) {
|
|
3009
|
+
return isCmcdRequestKey(key) || isCmcdResponseReceivedKey(key) || CMCD_EVENT_KEY_SET.has(key);
|
|
2545
3010
|
}
|
|
2546
3011
|
|
|
2547
3012
|
//#endregion
|
|
@@ -2562,6 +3027,25 @@ function isCmcdV1Data(data) {
|
|
|
2562
3027
|
return data.v !== CMCD_V2;
|
|
2563
3028
|
}
|
|
2564
3029
|
|
|
3030
|
+
//#endregion
|
|
3031
|
+
//#region src/isCmcdV1Key.ts
|
|
3032
|
+
const CMCD_V1_KEY_SET$1 = /* @__PURE__ */ new Set(CMCD_V1_KEYS);
|
|
3033
|
+
/**
|
|
3034
|
+
* Filter for CMCD version 1 keys.
|
|
3035
|
+
*
|
|
3036
|
+
* @param key - The CMCD key to filter.
|
|
3037
|
+
*
|
|
3038
|
+
* @returns `true` if the key should be included, `false` otherwise.
|
|
3039
|
+
*
|
|
3040
|
+
* @public
|
|
3041
|
+
*
|
|
3042
|
+
* @example
|
|
3043
|
+
* {@includeCode ../test/isCmcdV1Key.test.ts#example}
|
|
3044
|
+
*/
|
|
3045
|
+
function isCmcdV1Key(key) {
|
|
3046
|
+
return CMCD_V1_KEY_SET$1.has(key) || isCmcdCustomKey(key);
|
|
3047
|
+
}
|
|
3048
|
+
|
|
2565
3049
|
//#endregion
|
|
2566
3050
|
//#region src/isCmcdV2Data.ts
|
|
2567
3051
|
/**
|
|
@@ -2580,6 +3064,50 @@ function isCmcdV2Data(data) {
|
|
|
2580
3064
|
return data.v === CMCD_V2;
|
|
2581
3065
|
}
|
|
2582
3066
|
|
|
3067
|
+
//#endregion
|
|
3068
|
+
//#region src/toCmcdUrl.ts
|
|
3069
|
+
/**
|
|
3070
|
+
* Convert a CMCD object to a URL encoded string.
|
|
3071
|
+
*
|
|
3072
|
+
* Only letters, digits, `-`, `.`, `_`, and `~` stay unencoded, as in the
|
|
3073
|
+
* query examples of CTA-5004-B.
|
|
3074
|
+
*
|
|
3075
|
+
* @param cmcd - The CMCD object to convert.
|
|
3076
|
+
* @param options - Options for encoding.
|
|
3077
|
+
*
|
|
3078
|
+
* @returns The URL encoded CMCD data.
|
|
3079
|
+
*
|
|
3080
|
+
* @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}
|
|
3081
|
+
*
|
|
3082
|
+
* @public
|
|
3083
|
+
*/
|
|
3084
|
+
function toCmcdUrl(cmcd, options = {}) {
|
|
3085
|
+
if (!cmcd) return "";
|
|
3086
|
+
return percentEncode(encodeCmcd(cmcd, options));
|
|
3087
|
+
}
|
|
3088
|
+
|
|
3089
|
+
//#endregion
|
|
3090
|
+
//#region src/toCmcdQuery.ts
|
|
3091
|
+
/**
|
|
3092
|
+
* Convert a CMCD object to a query arg.
|
|
3093
|
+
*
|
|
3094
|
+
* @param cmcd - The CMCD object to convert.
|
|
3095
|
+
* @param options - Options for encoding.
|
|
3096
|
+
*
|
|
3097
|
+
* @returns The CMCD query arg.
|
|
3098
|
+
*
|
|
3099
|
+
* @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}
|
|
3100
|
+
*
|
|
3101
|
+
* @public
|
|
3102
|
+
*
|
|
3103
|
+
* @example
|
|
3104
|
+
* {@includeCode ../test/toCmcdQuery.test.ts#example}
|
|
3105
|
+
*/
|
|
3106
|
+
function toCmcdQuery(cmcd, options = {}) {
|
|
3107
|
+
if (!cmcd) return "";
|
|
3108
|
+
return `${CMCD_PARAM}=${toCmcdUrl(cmcd, options)}`;
|
|
3109
|
+
}
|
|
3110
|
+
|
|
2583
3111
|
//#endregion
|
|
2584
3112
|
//#region src/toCmcdValue.ts
|
|
2585
3113
|
/**
|
|
@@ -2619,7 +3147,7 @@ function mergeValidationResults(...results) {
|
|
|
2619
3147
|
*/
|
|
2620
3148
|
function resolveVersion(data, options) {
|
|
2621
3149
|
if (options?.version === 1 || options?.version === 2) return options.version;
|
|
2622
|
-
const payloadVersion = data["v"];
|
|
3150
|
+
const payloadVersion = toBareValue(data["v"]);
|
|
2623
3151
|
if (payloadVersion === 1 || payloadVersion === 2) return payloadVersion;
|
|
2624
3152
|
return CMCD_V1;
|
|
2625
3153
|
}
|
|
@@ -2660,6 +3188,51 @@ function validateCmcdKeys(data, options) {
|
|
|
2660
3188
|
};
|
|
2661
3189
|
}
|
|
2662
3190
|
|
|
3191
|
+
//#endregion
|
|
3192
|
+
//#region src/CMCD_AGGREGATE_BITRATE_KEYS.ts
|
|
3193
|
+
/**
|
|
3194
|
+
* Maps each aggregate bitrate key to the exact bitrate key that supersedes it.
|
|
3195
|
+
*
|
|
3196
|
+
* Per CTA-5004-B, `ab` MUST NOT be sent when the encoded bitrate `br` is
|
|
3197
|
+
* known. `lab` and `tab` follow the same rule for the lowest bitrate `lb`
|
|
3198
|
+
* and the top bitrate `tb`.
|
|
3199
|
+
*
|
|
3200
|
+
* @internal
|
|
3201
|
+
*/
|
|
3202
|
+
const CMCD_AGGREGATE_BITRATE_KEYS = {
|
|
3203
|
+
ab: "br",
|
|
3204
|
+
lab: "lb",
|
|
3205
|
+
tab: "tb"
|
|
3206
|
+
};
|
|
3207
|
+
|
|
3208
|
+
//#endregion
|
|
3209
|
+
//#region src/CMCD_KEY_OBJECT_TYPES.ts
|
|
3210
|
+
/**
|
|
3211
|
+
* Maps a key to the object types (`ot`) for which CTA-5004-B allows the key.
|
|
3212
|
+
*
|
|
3213
|
+
* A key that is absent from this map is allowed for every object type.
|
|
3214
|
+
* `d` (Object duration) and `tpb` (Top playable bitrate) MUST NOT be sent for
|
|
3215
|
+
* other object types.
|
|
3216
|
+
*
|
|
3217
|
+
* @internal
|
|
3218
|
+
*/
|
|
3219
|
+
const CMCD_KEY_OBJECT_TYPES = {
|
|
3220
|
+
d: [
|
|
3221
|
+
"a",
|
|
3222
|
+
"v",
|
|
3223
|
+
"av",
|
|
3224
|
+
"tt",
|
|
3225
|
+
"c",
|
|
3226
|
+
"o"
|
|
3227
|
+
],
|
|
3228
|
+
tpb: [
|
|
3229
|
+
"a",
|
|
3230
|
+
"v",
|
|
3231
|
+
"av",
|
|
3232
|
+
"c"
|
|
3233
|
+
]
|
|
3234
|
+
};
|
|
3235
|
+
|
|
2663
3236
|
//#endregion
|
|
2664
3237
|
//#region src/validateCmcdStructure.ts
|
|
2665
3238
|
/**
|
|
@@ -2727,9 +3300,9 @@ function validateCmcdStructure(data, options) {
|
|
|
2727
3300
|
message: `Response key "${key}" must only be present when e="rr".`,
|
|
2728
3301
|
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2729
3302
|
});
|
|
2730
|
-
|
|
2731
|
-
key:
|
|
2732
|
-
message:
|
|
3303
|
+
if (eventType === CMCD_EVENT_PLAY_STATE && !("sta" in data)) issues.push({
|
|
3304
|
+
key: "sta",
|
|
3305
|
+
message: "Play state change event (e=\"ps\") requires the \"sta\" key to be present.",
|
|
2733
3306
|
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2734
3307
|
});
|
|
2735
3308
|
if (eventType === CMCD_EVENT_ERROR && !("ec" in data)) issues.push({
|
|
@@ -2755,9 +3328,10 @@ function validateCmcdStructure(data, options) {
|
|
|
2755
3328
|
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2756
3329
|
});
|
|
2757
3330
|
}
|
|
2758
|
-
|
|
3331
|
+
const v = toBareValue(data["v"]);
|
|
3332
|
+
if ("v" in data && v !== 1 && v !== 2) issues.push({
|
|
2759
3333
|
key: "v",
|
|
2760
|
-
message: `Unsupported CMCD version "${String(
|
|
3334
|
+
message: `Unsupported CMCD version "${String(v)}". Expected 1 or 2.`,
|
|
2761
3335
|
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2762
3336
|
});
|
|
2763
3337
|
else if (version > 1 && !("v" in data)) issues.push({
|
|
@@ -2779,7 +3353,7 @@ function validateCmcdStructure(data, options) {
|
|
|
2779
3353
|
//#endregion
|
|
2780
3354
|
//#region src/CMCD_KEY_TYPES.ts
|
|
2781
3355
|
/**
|
|
2782
|
-
* CMCD key value type: inner list of
|
|
3356
|
+
* CMCD key value type: inner list of integers with token identifiers.
|
|
2783
3357
|
*
|
|
2784
3358
|
* @internal
|
|
2785
3359
|
*/
|
|
@@ -2886,9 +3460,9 @@ const CMCD_KEY_TYPES = {
|
|
|
2886
3460
|
*/
|
|
2887
3461
|
const CMCD_V1_KEY_TYPE_OVERRIDES = {
|
|
2888
3462
|
bl: CMCD_KEY_TYPE_INTEGER,
|
|
2889
|
-
br:
|
|
3463
|
+
br: CMCD_KEY_TYPE_INTEGER,
|
|
2890
3464
|
mtp: CMCD_KEY_TYPE_INTEGER,
|
|
2891
|
-
tb:
|
|
3465
|
+
tb: CMCD_KEY_TYPE_INTEGER,
|
|
2892
3466
|
nor: CMCD_KEY_TYPE_STRING
|
|
2893
3467
|
};
|
|
2894
3468
|
|
|
@@ -2979,22 +3553,31 @@ const CMCD_TOKEN_VALUES = {
|
|
|
2979
3553
|
|
|
2980
3554
|
//#endregion
|
|
2981
3555
|
//#region src/validateCmcdValues.ts
|
|
2982
|
-
const
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
const INTEGER_ROUNDING_KEYS = /* @__PURE__ */ new Set([
|
|
2990
|
-
"br",
|
|
2991
|
-
"d",
|
|
2992
|
-
"tb"
|
|
2993
|
-
]);
|
|
3556
|
+
const HUNDRED_ROUNDING_UNITS = {
|
|
3557
|
+
bl: "ms",
|
|
3558
|
+
dl: "ms",
|
|
3559
|
+
mtp: "kbps",
|
|
3560
|
+
rtp: "kbps",
|
|
3561
|
+
tbl: "ms"
|
|
3562
|
+
};
|
|
2994
3563
|
function isFiniteNumber(value) {
|
|
2995
3564
|
return typeof value === "number" && Number.isFinite(value);
|
|
2996
3565
|
}
|
|
2997
|
-
function
|
|
3566
|
+
function isInteger(value) {
|
|
3567
|
+
return Number.isInteger(value);
|
|
3568
|
+
}
|
|
3569
|
+
function validateRounding(key, value, version, issues, index) {
|
|
3570
|
+
const unit = HUNDRED_ROUNDING_UNITS[key];
|
|
3571
|
+
if (!unit || value % 100 === 0) return;
|
|
3572
|
+
const should = key === "tbl" || key === "bl" && version !== CMCD_V1;
|
|
3573
|
+
const target = index === void 0 ? `Key "${key}"` : `Key "${key}" array element [${index}]`;
|
|
3574
|
+
issues.push({
|
|
3575
|
+
key,
|
|
3576
|
+
message: `${target} ${should ? "should" : "must"} be rounded to the nearest 100 ${unit}. Received ${value}.`,
|
|
3577
|
+
severity: should ? CMCD_VALIDATION_SEVERITY_WARNING : CMCD_VALIDATION_SEVERITY_ERROR
|
|
3578
|
+
});
|
|
3579
|
+
}
|
|
3580
|
+
function validateListValue(key, value, version, issues) {
|
|
2998
3581
|
if (!Array.isArray(value)) {
|
|
2999
3582
|
issues.push({
|
|
3000
3583
|
key,
|
|
@@ -3004,18 +3587,13 @@ function validateListValue(key, value, issues) {
|
|
|
3004
3587
|
return;
|
|
3005
3588
|
}
|
|
3006
3589
|
for (let i = 0; i < value.length; i++) {
|
|
3007
|
-
const element = value[i];
|
|
3008
|
-
if (element
|
|
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({
|
|
3590
|
+
const element = toBareValue(value[i]);
|
|
3591
|
+
if (!isInteger(element)) issues.push({
|
|
3015
3592
|
key,
|
|
3016
|
-
message: `Key "${key}" array element [${i}] must be a finite
|
|
3593
|
+
message: `Key "${key}" array element [${i}] must be a finite integer.`,
|
|
3017
3594
|
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
3018
3595
|
});
|
|
3596
|
+
else validateRounding(key, element, version, issues, i);
|
|
3019
3597
|
}
|
|
3020
3598
|
}
|
|
3021
3599
|
function validateStringArrayValue(key, value, issues) {
|
|
@@ -3027,20 +3605,11 @@ function validateStringArrayValue(key, value, issues) {
|
|
|
3027
3605
|
});
|
|
3028
3606
|
return;
|
|
3029
3607
|
}
|
|
3030
|
-
for (let i = 0; i < value.length; i++) {
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
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
|
-
}
|
|
3608
|
+
for (let i = 0; i < value.length; i++) if (typeof toBareValue(value[i]) !== "string") issues.push({
|
|
3609
|
+
key,
|
|
3610
|
+
message: `Key "${key}" array element [${i}] must be a string.`,
|
|
3611
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
3612
|
+
});
|
|
3044
3613
|
}
|
|
3045
3614
|
/**
|
|
3046
3615
|
* Validates that all values in a CMCD payload conform to the expected types and constraints.
|
|
@@ -3059,7 +3628,8 @@ function validateStringArrayValue(key, value, issues) {
|
|
|
3059
3628
|
function validateCmcdValues(data, options) {
|
|
3060
3629
|
const version = resolveVersion(data, options);
|
|
3061
3630
|
const issues = [];
|
|
3062
|
-
for (const [key,
|
|
3631
|
+
for (const [key, entry] of Object.entries(data)) {
|
|
3632
|
+
const value = toBareValue(entry);
|
|
3063
3633
|
if (isCmcdCustomKey(key)) {
|
|
3064
3634
|
if (typeof value !== "string") issues.push({
|
|
3065
3635
|
key,
|
|
@@ -3086,16 +3656,12 @@ function validateCmcdValues(data, options) {
|
|
|
3086
3656
|
if (version === CMCD_V1 && key in CMCD_V1_KEY_TYPE_OVERRIDES) expectedType = CMCD_V1_KEY_TYPE_OVERRIDES[key];
|
|
3087
3657
|
switch (expectedType) {
|
|
3088
3658
|
case CMCD_KEY_TYPE_INTEGER:
|
|
3089
|
-
if (!
|
|
3659
|
+
if (!isInteger(value)) issues.push({
|
|
3090
3660
|
key,
|
|
3091
3661
|
message: `Key "${key}" must be a finite integer.`,
|
|
3092
3662
|
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
3093
3663
|
});
|
|
3094
|
-
else
|
|
3095
|
-
key,
|
|
3096
|
-
message: `Key "${key}" should be rounded to the nearest 100.`,
|
|
3097
|
-
severity: CMCD_VALIDATION_SEVERITY_WARNING
|
|
3098
|
-
});
|
|
3664
|
+
else validateRounding(key, value, version, issues);
|
|
3099
3665
|
break;
|
|
3100
3666
|
case CMCD_KEY_TYPE_NUMBER:
|
|
3101
3667
|
if (!isFiniteNumber(value)) issues.push({
|
|
@@ -3103,16 +3669,6 @@ function validateCmcdValues(data, options) {
|
|
|
3103
3669
|
message: `Key "${key}" must be a finite number.`,
|
|
3104
3670
|
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
3105
3671
|
});
|
|
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
3672
|
break;
|
|
3117
3673
|
case CMCD_KEY_TYPE_BOOLEAN:
|
|
3118
3674
|
if (typeof value !== "boolean") issues.push({
|
|
@@ -3144,7 +3700,7 @@ function validateCmcdValues(data, options) {
|
|
|
3144
3700
|
break;
|
|
3145
3701
|
}
|
|
3146
3702
|
case CMCD_KEY_TYPE_NUMBER_LIST:
|
|
3147
|
-
validateListValue(key, value, issues);
|
|
3703
|
+
validateListValue(key, value, version, issues);
|
|
3148
3704
|
break;
|
|
3149
3705
|
case CMCD_KEY_TYPE_STRING_LIST:
|
|
3150
3706
|
validateStringArrayValue(key, value, issues);
|