@svta/cml-cmcd 2.6.1 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +594 -572
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +482 -361
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
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
|
|
4
21
|
//#region src/CMCD_FORMATTER_MAP.ts
|
|
5
22
|
const roundValue = (value) => {
|
|
6
23
|
if (value instanceof SfItem) return new SfItem(Math.round(value.value), value.params);
|
|
@@ -50,6 +67,34 @@ const CMCD_FORMATTER_MAP = {
|
|
|
50
67
|
tb: toRounded
|
|
51
68
|
};
|
|
52
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
|
+
|
|
53
98
|
//#endregion
|
|
54
99
|
//#region src/CMCD_V2.ts
|
|
55
100
|
/**
|
|
@@ -62,109 +107,109 @@ const CMCD_V2 = 2;
|
|
|
62
107
|
//#endregion
|
|
63
108
|
//#region src/CmcdEventType.ts
|
|
64
109
|
/**
|
|
65
|
-
* CMCD event type
|
|
110
|
+
* CMCD event type 'bc' (bitrate change).
|
|
66
111
|
*
|
|
67
112
|
* @public
|
|
68
113
|
*/
|
|
69
114
|
const CMCD_EVENT_BITRATE_CHANGE = "bc";
|
|
70
115
|
/**
|
|
71
|
-
* CMCD event type
|
|
116
|
+
* CMCD event type 'ps' (play state change).
|
|
72
117
|
*
|
|
73
118
|
* @public
|
|
74
119
|
*/
|
|
75
120
|
const CMCD_EVENT_PLAY_STATE = "ps";
|
|
76
121
|
/**
|
|
77
|
-
* CMCD event type
|
|
122
|
+
* CMCD event type 'pr' (playback rate change).
|
|
78
123
|
*
|
|
79
124
|
* @public
|
|
80
125
|
*/
|
|
81
126
|
const CMCD_EVENT_PLAYBACK_RATE = "pr";
|
|
82
127
|
/**
|
|
83
|
-
* CMCD event type
|
|
128
|
+
* CMCD event type 'e' (error).
|
|
84
129
|
*
|
|
85
130
|
* @public
|
|
86
131
|
*/
|
|
87
132
|
const CMCD_EVENT_ERROR = "e";
|
|
88
133
|
/**
|
|
89
|
-
* CMCD event type
|
|
134
|
+
* CMCD event type 't' (time interval).
|
|
90
135
|
*
|
|
91
136
|
* @public
|
|
92
137
|
*/
|
|
93
138
|
const CMCD_EVENT_TIME_INTERVAL = "t";
|
|
94
139
|
/**
|
|
95
|
-
* CMCD event type
|
|
140
|
+
* CMCD event type 'c' (content ID).
|
|
96
141
|
*
|
|
97
142
|
* @public
|
|
98
143
|
*/
|
|
99
144
|
const CMCD_EVENT_CONTENT_ID = "c";
|
|
100
145
|
/**
|
|
101
|
-
* CMCD event type
|
|
146
|
+
* CMCD event type 'b' (backgrounded mode).
|
|
102
147
|
*
|
|
103
148
|
* @public
|
|
104
149
|
*/
|
|
105
150
|
const CMCD_EVENT_BACKGROUNDED_MODE = "b";
|
|
106
151
|
/**
|
|
107
|
-
* CMCD event type
|
|
152
|
+
* CMCD event type 'm' (mute).
|
|
108
153
|
*
|
|
109
154
|
* @public
|
|
110
155
|
*/
|
|
111
156
|
const CMCD_EVENT_MUTE = "m";
|
|
112
157
|
/**
|
|
113
|
-
* CMCD event type
|
|
158
|
+
* CMCD event type 'um' (unmute).
|
|
114
159
|
*
|
|
115
160
|
* @public
|
|
116
161
|
*/
|
|
117
162
|
const CMCD_EVENT_UNMUTE = "um";
|
|
118
163
|
/**
|
|
119
|
-
* CMCD event type
|
|
164
|
+
* CMCD event type 'pe' (player expand).
|
|
120
165
|
*
|
|
121
166
|
* @public
|
|
122
167
|
*/
|
|
123
168
|
const CMCD_EVENT_PLAYER_EXPAND = "pe";
|
|
124
169
|
/**
|
|
125
|
-
* CMCD event type
|
|
170
|
+
* CMCD event type 'pc' (player collapse).
|
|
126
171
|
*
|
|
127
172
|
* @public
|
|
128
173
|
*/
|
|
129
174
|
const CMCD_EVENT_PLAYER_COLLAPSE = "pc";
|
|
130
175
|
/**
|
|
131
|
-
* CMCD event type
|
|
176
|
+
* CMCD event type 'rr' (response received).
|
|
132
177
|
*
|
|
133
178
|
* @public
|
|
134
179
|
*/
|
|
135
180
|
const CMCD_EVENT_RESPONSE_RECEIVED = "rr";
|
|
136
181
|
/**
|
|
137
|
-
* CMCD event type
|
|
182
|
+
* CMCD event type 'as' (ad start).
|
|
138
183
|
*
|
|
139
184
|
* @public
|
|
140
185
|
*/
|
|
141
186
|
const CMCD_EVENT_AD_START = "as";
|
|
142
187
|
/**
|
|
143
|
-
* CMCD event type
|
|
188
|
+
* CMCD event type 'ae' (ad end).
|
|
144
189
|
*
|
|
145
190
|
* @public
|
|
146
191
|
*/
|
|
147
192
|
const CMCD_EVENT_AD_END = "ae";
|
|
148
193
|
/**
|
|
149
|
-
* CMCD event type
|
|
194
|
+
* CMCD event type 'abs' (ad break start).
|
|
150
195
|
*
|
|
151
196
|
* @public
|
|
152
197
|
*/
|
|
153
198
|
const CMCD_EVENT_AD_BREAK_START = "abs";
|
|
154
199
|
/**
|
|
155
|
-
* CMCD event type
|
|
200
|
+
* CMCD event type 'abe' (ad break end).
|
|
156
201
|
*
|
|
157
202
|
* @public
|
|
158
203
|
*/
|
|
159
204
|
const CMCD_EVENT_AD_BREAK_END = "abe";
|
|
160
205
|
/**
|
|
161
|
-
* CMCD event type
|
|
206
|
+
* CMCD event type 'sk' (skip).
|
|
162
207
|
*
|
|
163
208
|
* @public
|
|
164
209
|
*/
|
|
165
210
|
const CMCD_EVENT_SKIP = "sk";
|
|
166
211
|
/**
|
|
167
|
-
* CMCD event type
|
|
212
|
+
* CMCD event type 'ce' (custom event).
|
|
168
213
|
*
|
|
169
214
|
* @public
|
|
170
215
|
*/
|
|
@@ -206,13 +251,13 @@ const CmcdEventType = {
|
|
|
206
251
|
* the event signals.
|
|
207
252
|
*
|
|
208
253
|
* Per CTA-5004-B, the state-change events `ps`, `pr`, `c`, `b`, `bc` are
|
|
209
|
-
* state-transition markers
|
|
210
|
-
*
|
|
211
|
-
* against its value (`CmcdReporter`), and
|
|
212
|
-
* (`validateCmcdStructure`).
|
|
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`).
|
|
213
258
|
*
|
|
214
|
-
* Iteration order
|
|
215
|
-
* events in map order when
|
|
259
|
+
* Iteration order matters: `CmcdReporter.update()` records state-change
|
|
260
|
+
* events in map order when several tracked fields change in one call.
|
|
216
261
|
* Do not reorder entries without auditing reporter behavior.
|
|
217
262
|
*
|
|
218
263
|
* @internal
|
|
@@ -318,9 +363,9 @@ const CMCD_REQUEST_KEYS = [
|
|
|
318
363
|
const CUSTOM_KEY_REGEX = /^[a-z][a-z0-9.-]*$/;
|
|
319
364
|
/**
|
|
320
365
|
* Check if a key is a valid custom key: a lowercase first letter, then
|
|
321
|
-
* characters from `a-z 0-9 .
|
|
322
|
-
*
|
|
323
|
-
*
|
|
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.
|
|
324
369
|
*
|
|
325
370
|
* @param key - The key to check.
|
|
326
371
|
*
|
|
@@ -358,7 +403,7 @@ function isCmcdRequestKey(key) {
|
|
|
358
403
|
//#endregion
|
|
359
404
|
//#region src/CMCD_RESPONSE_KEYS.ts
|
|
360
405
|
/**
|
|
361
|
-
*
|
|
406
|
+
* Response-only and timing keys for version 2.
|
|
362
407
|
*
|
|
363
408
|
* @public
|
|
364
409
|
*/
|
|
@@ -381,7 +426,7 @@ const CMCD_RESPONSE_KEY_SET = /* @__PURE__ */ new Set(CMCD_RESPONSE_KEYS);
|
|
|
381
426
|
*
|
|
382
427
|
* @param key - The key to check.
|
|
383
428
|
*
|
|
384
|
-
* @returns `true` if the key is a valid CMCD
|
|
429
|
+
* @returns `true` if the key is a valid CMCD response key, `false` otherwise.
|
|
385
430
|
*
|
|
386
431
|
* @public
|
|
387
432
|
*
|
|
@@ -414,7 +459,7 @@ function isCmcdEventKey(key) {
|
|
|
414
459
|
//#endregion
|
|
415
460
|
//#region src/CMCD_INNER_LIST_KEYS.ts
|
|
416
461
|
/**
|
|
417
|
-
*
|
|
462
|
+
* Inner-list keys in version 2 that are scalars in version 1.
|
|
418
463
|
*
|
|
419
464
|
* Used by both encoding (down-conversion) and decoding (up-conversion).
|
|
420
465
|
*
|
|
@@ -469,7 +514,7 @@ const CMCD_V1_KEYS = [
|
|
|
469
514
|
//#region src/isCmcdV1Key.ts
|
|
470
515
|
const CMCD_V1_KEY_SET$1 = /* @__PURE__ */ new Set(CMCD_V1_KEYS);
|
|
471
516
|
/**
|
|
472
|
-
* Filter
|
|
517
|
+
* Filter for CMCD version 1 keys.
|
|
473
518
|
*
|
|
474
519
|
* @param key - The CMCD key to filter.
|
|
475
520
|
*
|
|
@@ -509,16 +554,21 @@ function isTokenField(key) {
|
|
|
509
554
|
//#endregion
|
|
510
555
|
//#region src/isValid.ts
|
|
511
556
|
/**
|
|
512
|
-
* Checks if the given value is valid
|
|
557
|
+
* Checks if the given value is valid to send as a CMCD value.
|
|
558
|
+
*
|
|
559
|
+
* `undefined`, `null`, an empty string, `false`, an empty array, and a
|
|
560
|
+
* number that is not finite are not valid. CTA-5004-B requires omitting
|
|
561
|
+
* the key when its value is unknown.
|
|
513
562
|
*
|
|
514
563
|
* @param value - The value to check.
|
|
515
564
|
*
|
|
516
|
-
* @returns `true` if the
|
|
565
|
+
* @returns `true` if the value is valid.
|
|
517
566
|
*
|
|
518
567
|
* @internal
|
|
519
568
|
*/
|
|
520
569
|
function isValid(value) {
|
|
521
570
|
if (typeof value === "number") return Number.isFinite(value);
|
|
571
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
522
572
|
return value != null && value !== "" && value !== false;
|
|
523
573
|
}
|
|
524
574
|
|
|
@@ -528,7 +578,7 @@ function isValid(value) {
|
|
|
528
578
|
* Resolve the text of a token value.
|
|
529
579
|
*
|
|
530
580
|
* A token may be a plain string, a registry `Symbol`, an `SfToken`, or an
|
|
531
|
-
* `SfItem` that wraps
|
|
581
|
+
* `SfItem` that wraps such a value.
|
|
532
582
|
*
|
|
533
583
|
* @param value - The value to resolve.
|
|
534
584
|
*
|
|
@@ -545,11 +595,11 @@ function toTokenString(value) {
|
|
|
545
595
|
//#endregion
|
|
546
596
|
//#region src/prepareCmcdData.ts
|
|
547
597
|
const filterMap = {
|
|
548
|
-
|
|
549
|
-
|
|
598
|
+
event: isCmcdEventKey,
|
|
599
|
+
request: isCmcdRequestKey
|
|
550
600
|
};
|
|
551
601
|
/**
|
|
552
|
-
* Unwrap an inner list or SfItem value to a
|
|
602
|
+
* Unwrap an inner list or SfItem value to a scalar.
|
|
553
603
|
*/
|
|
554
604
|
function unwrapValue(value, ot) {
|
|
555
605
|
if (Array.isArray(value)) {
|
|
@@ -563,10 +613,10 @@ function unwrapValue(value, ot) {
|
|
|
563
613
|
return value;
|
|
564
614
|
}
|
|
565
615
|
/**
|
|
566
|
-
* Down-convert
|
|
616
|
+
* Down-convert version 2 CMCD data to version 1.
|
|
567
617
|
*
|
|
568
|
-
* - Extracts `nrr` from `nor` SfItem `r` parameter.
|
|
569
|
-
* - Unwraps inner-list values to
|
|
618
|
+
* - Extracts `nrr` from the `nor` SfItem's `r` parameter.
|
|
619
|
+
* - Unwraps inner-list values to scalars.
|
|
570
620
|
*/
|
|
571
621
|
function downConvertToV1(obj) {
|
|
572
622
|
const result = {};
|
|
@@ -586,6 +636,10 @@ function downConvertToV1(obj) {
|
|
|
586
636
|
}
|
|
587
637
|
return result;
|
|
588
638
|
}
|
|
639
|
+
function formatValue(key, value, options, formatterOptions) {
|
|
640
|
+
const formatter = options.formatters?.[key] ?? CMCD_FORMATTER_MAP[key];
|
|
641
|
+
return typeof formatter === "function" && isValid(value) ? formatter(value, formatterOptions) : value;
|
|
642
|
+
}
|
|
589
643
|
/**
|
|
590
644
|
* Convert a generic object to CMCD data.
|
|
591
645
|
*
|
|
@@ -627,10 +681,23 @@ function prepareCmcdData(obj, options = {}) {
|
|
|
627
681
|
baseUrl: options.baseUrl
|
|
628
682
|
};
|
|
629
683
|
keys.sort();
|
|
684
|
+
let objectType;
|
|
685
|
+
let objectTypeResolved = false;
|
|
630
686
|
for (const key of keys) {
|
|
631
687
|
let value = data[key];
|
|
632
|
-
const
|
|
633
|
-
if (
|
|
688
|
+
const exactKey = CMCD_AGGREGATE_BITRATE_KEYS[key];
|
|
689
|
+
if (exactKey && keys.includes(exactKey) && isValid(formatValue(exactKey, data[exactKey], options, formatterOptions))) continue;
|
|
690
|
+
const objectTypes = version > 1 ? CMCD_KEY_OBJECT_TYPES[key] : void 0;
|
|
691
|
+
if (objectTypes) {
|
|
692
|
+
if (!objectTypeResolved) {
|
|
693
|
+
const ot = formatValue("ot", data["ot"], options, formatterOptions);
|
|
694
|
+
objectType = isValid(ot) ? toTokenString(ot) : void 0;
|
|
695
|
+
objectTypeResolved = true;
|
|
696
|
+
}
|
|
697
|
+
if (objectType !== void 0 && !objectTypes.includes(objectType)) continue;
|
|
698
|
+
}
|
|
699
|
+
if (key === "cen" && eventType !== CMCD_EVENT_CUSTOM_EVENT) continue;
|
|
700
|
+
value = formatValue(key, value, options, formatterOptions);
|
|
634
701
|
if (key === "v") if (version === 1) continue;
|
|
635
702
|
else value = version;
|
|
636
703
|
if (key === "pr" && value === 1 && !(isEventMode && eventType === CMCD_EVENT_PLAYBACK_RATE)) continue;
|
|
@@ -703,9 +770,9 @@ const CMCD_HEADER_FIELDS = [
|
|
|
703
770
|
/**
|
|
704
771
|
* The map of CMCD keys to their appropriate header shard.
|
|
705
772
|
*
|
|
706
|
-
*
|
|
707
|
-
*
|
|
708
|
-
*
|
|
773
|
+
* Event-only keys (e, ts, cen, h) and response-received keys (rc, ttfb,
|
|
774
|
+
* ttlb, url, and others) are intentionally absent. They are transmitted
|
|
775
|
+
* in the event-mode POST body, not HTTP headers.
|
|
709
776
|
*
|
|
710
777
|
* @public
|
|
711
778
|
*/
|
|
@@ -760,9 +827,9 @@ function createHeaderMap(headerMap) {
|
|
|
760
827
|
}, {});
|
|
761
828
|
}
|
|
762
829
|
/**
|
|
763
|
-
* Group a CMCD
|
|
830
|
+
* Group a CMCD object into header shards
|
|
764
831
|
*
|
|
765
|
-
* @param cmcd - The CMCD
|
|
832
|
+
* @param cmcd - The CMCD object to convert.
|
|
766
833
|
* @param customHeaderMap - A map of CMCD header fields to custom CMCD keys.
|
|
767
834
|
*
|
|
768
835
|
* @returns The CMCD header shards.
|
|
@@ -808,10 +875,10 @@ function toPreparedCmcdHeaders(data, customHeaderMap) {
|
|
|
808
875
|
//#endregion
|
|
809
876
|
//#region src/toCmcdHeaders.ts
|
|
810
877
|
/**
|
|
811
|
-
* Convert a CMCD
|
|
878
|
+
* Convert a CMCD object to request headers
|
|
812
879
|
*
|
|
813
|
-
* @param cmcd - The CMCD
|
|
814
|
-
* @param options - Options for encoding
|
|
880
|
+
* @param cmcd - The CMCD object to convert.
|
|
881
|
+
* @param options - Options for encoding.
|
|
815
882
|
*
|
|
816
883
|
* @returns The CMCD header shards.
|
|
817
884
|
*
|
|
@@ -830,13 +897,13 @@ function toCmcdHeaders(cmcd, options = {}) {
|
|
|
830
897
|
//#endregion
|
|
831
898
|
//#region src/appendCmcdHeaders.ts
|
|
832
899
|
/**
|
|
833
|
-
* Append CMCD
|
|
900
|
+
* Append CMCD header shards to a header object.
|
|
834
901
|
*
|
|
835
902
|
* @param headers - The headers to append to.
|
|
836
903
|
* @param cmcd - The CMCD object to append.
|
|
837
|
-
* @param options -
|
|
904
|
+
* @param options - Options for encoding.
|
|
838
905
|
*
|
|
839
|
-
* @returns The headers with
|
|
906
|
+
* @returns The headers with CMCD header shards appended.
|
|
840
907
|
*
|
|
841
908
|
* @public
|
|
842
909
|
*
|
|
@@ -897,10 +964,10 @@ function encodeCmcd(cmcd, options = {}) {
|
|
|
897
964
|
//#endregion
|
|
898
965
|
//#region src/toCmcdUrl.ts
|
|
899
966
|
/**
|
|
900
|
-
* Convert a CMCD
|
|
967
|
+
* Convert a CMCD object to a URL encoded string.
|
|
901
968
|
*
|
|
902
969
|
* @param cmcd - The CMCD object to convert.
|
|
903
|
-
* @param options - Options for encoding
|
|
970
|
+
* @param options - Options for encoding.
|
|
904
971
|
*
|
|
905
972
|
* @returns The URL encoded CMCD data.
|
|
906
973
|
*
|
|
@@ -917,10 +984,10 @@ function toCmcdUrl(cmcd, options = {}) {
|
|
|
917
984
|
//#endregion
|
|
918
985
|
//#region src/toCmcdQuery.ts
|
|
919
986
|
/**
|
|
920
|
-
* Convert a CMCD
|
|
987
|
+
* Convert a CMCD object to a query arg.
|
|
921
988
|
*
|
|
922
989
|
* @param cmcd - The CMCD object to convert.
|
|
923
|
-
* @param options - Options for encoding
|
|
990
|
+
* @param options - Options for encoding.
|
|
924
991
|
*
|
|
925
992
|
* @returns The CMCD query arg.
|
|
926
993
|
*
|
|
@@ -944,7 +1011,7 @@ const REGEX = /CMCD=[^&#]+/;
|
|
|
944
1011
|
*
|
|
945
1012
|
* @param url - The URL to append to.
|
|
946
1013
|
* @param cmcd - The CMCD object to append.
|
|
947
|
-
* @param options - Options for encoding
|
|
1014
|
+
* @param options - Options for encoding.
|
|
948
1015
|
*
|
|
949
1016
|
* @returns The URL with the CMCD query args appended.
|
|
950
1017
|
*
|
|
@@ -965,7 +1032,7 @@ function appendCmcdQuery(url, cmcd, options) {
|
|
|
965
1032
|
//#endregion
|
|
966
1033
|
//#region src/CMCD_DEFAULT_TIME_INTERVAL.ts
|
|
967
1034
|
/**
|
|
968
|
-
* The default time interval in seconds when using
|
|
1035
|
+
* The default time interval in seconds when using event mode
|
|
969
1036
|
*
|
|
970
1037
|
* @public
|
|
971
1038
|
*/
|
|
@@ -997,25 +1064,26 @@ const CMCD_MIME_TYPE = "application/cmcd";
|
|
|
997
1064
|
//#endregion
|
|
998
1065
|
//#region src/CMCD_REQUEST_PROVENANCE.ts
|
|
999
1066
|
/**
|
|
1000
|
-
* The `customData`
|
|
1001
|
-
*
|
|
1067
|
+
* The `customData` property where `CmcdReporter.createRequestReport()`
|
|
1068
|
+
* writes session provenance on every request it returns.
|
|
1002
1069
|
*
|
|
1003
|
-
* The value is a frozen `CmcdRequestProvenance` record
|
|
1004
|
-
* session that issued the request
|
|
1005
|
-
*
|
|
1006
|
-
*
|
|
1007
|
-
*
|
|
1008
|
-
*
|
|
1009
|
-
* Spread and `Object.assign`
|
|
1010
|
-
*
|
|
1011
|
-
*
|
|
1012
|
-
*
|
|
1013
|
-
*
|
|
1014
|
-
*
|
|
1015
|
-
*
|
|
1070
|
+
* The value is a frozen `CmcdRequestProvenance` record. `sid` names the
|
|
1071
|
+
* session that issued the request. `cid` is the content id at the time the
|
|
1072
|
+
* request was issued. `data` is the request's per-call data as an encoded
|
|
1073
|
+
* CMCD string. `CmcdReporter.recordResponseReceived()` reads the record to
|
|
1074
|
+
* attribute a late response to its session and to rebuild the request-time
|
|
1075
|
+
* report keys.
|
|
1076
|
+
* Spread and `Object.assign` copy the record into ordinary request clones.
|
|
1077
|
+
* `JSON.stringify` and structured clone drop symbol-keyed properties. Before
|
|
1078
|
+
* either operation, read the value and restore it afterward. JSON preserves
|
|
1079
|
+
* the record's fields. Attribution uses the `sid` value rather than object
|
|
1080
|
+
* identity, so a parsed plain-object copy attributes correctly. For a record
|
|
1081
|
+
* naming a `sid` the reporter does not retain, the reporter drops the
|
|
1082
|
+
* response rather than reattributing it.
|
|
1016
1083
|
*
|
|
1017
|
-
*
|
|
1018
|
-
* one bundle interoperate. The registry key is stable across
|
|
1084
|
+
* The symbol registry provides the symbol, so duplicated copies of this
|
|
1085
|
+
* library in one bundle interoperate. The registry key is stable across
|
|
1086
|
+
* versions.
|
|
1019
1087
|
*
|
|
1020
1088
|
* @public
|
|
1021
1089
|
*/
|
|
@@ -1056,7 +1124,7 @@ const CmcdObjectType = {
|
|
|
1056
1124
|
//#endregion
|
|
1057
1125
|
//#region src/CmcdPlayerState.ts
|
|
1058
1126
|
/**
|
|
1059
|
-
* CMCD
|
|
1127
|
+
* CMCD version 2 player states for the 'sta' key.
|
|
1060
1128
|
*
|
|
1061
1129
|
*
|
|
1062
1130
|
* @enum
|
|
@@ -1081,29 +1149,29 @@ const CmcdPlayerState = {
|
|
|
1081
1149
|
//#endregion
|
|
1082
1150
|
//#region src/CmcdRecordedReportMode.ts
|
|
1083
1151
|
/**
|
|
1084
|
-
* CMCD query reporting mode
|
|
1152
|
+
* CMCD query reporting mode: CMCD data transmitted as a `CMCD=` URL
|
|
1085
1153
|
* query parameter.
|
|
1086
1154
|
*
|
|
1087
1155
|
* @public
|
|
1088
1156
|
*/
|
|
1089
1157
|
const CMCD_RECORDED_REPORT_MODE_QUERY = "query";
|
|
1090
1158
|
/**
|
|
1091
|
-
* CMCD header reporting mode
|
|
1159
|
+
* CMCD header reporting mode: CMCD data transmitted as `CMCD-*` HTTP
|
|
1092
1160
|
* headers.
|
|
1093
1161
|
*
|
|
1094
1162
|
* @public
|
|
1095
1163
|
*/
|
|
1096
1164
|
const CMCD_RECORDED_REPORT_MODE_HEADER = "header";
|
|
1097
1165
|
/**
|
|
1098
|
-
* CMCD event reporting mode
|
|
1166
|
+
* CMCD event reporting mode: CMCD data transmitted as a POST body to an
|
|
1099
1167
|
* event-target URL.
|
|
1100
1168
|
*
|
|
1101
1169
|
* @public
|
|
1102
1170
|
*/
|
|
1103
1171
|
const CMCD_RECORDED_REPORT_MODE_EVENT = "event";
|
|
1104
1172
|
/**
|
|
1105
|
-
* Reporting mode
|
|
1106
|
-
*
|
|
1173
|
+
* Reporting mode in which `CmcdReportRecorder` observed a captured
|
|
1174
|
+
* report.
|
|
1107
1175
|
*
|
|
1108
1176
|
* @enum
|
|
1109
1177
|
*
|
|
@@ -1137,7 +1205,7 @@ const CMCD_RECORDED_REQUEST_TYPE_SEGMENT = "segment";
|
|
|
1137
1205
|
*/
|
|
1138
1206
|
const CMCD_RECORDED_REQUEST_TYPE_EVENT = "event";
|
|
1139
1207
|
/**
|
|
1140
|
-
*
|
|
1208
|
+
* Default classification for requests the recorder did not recognize
|
|
1141
1209
|
* as manifest, segment, or event.
|
|
1142
1210
|
*
|
|
1143
1211
|
* @public
|
|
@@ -1179,17 +1247,17 @@ async function toHttpRequest(request) {
|
|
|
1179
1247
|
};
|
|
1180
1248
|
}
|
|
1181
1249
|
/**
|
|
1182
|
-
* Create a transport adapter
|
|
1183
|
-
*
|
|
1184
|
-
*
|
|
1185
|
-
*
|
|
1186
|
-
*
|
|
1187
|
-
*
|
|
1188
|
-
* safe for the body types CMCD reports use
|
|
1189
|
-
* `Blob`, `ArrayBuffer`, `FormData`,
|
|
1190
|
-
*
|
|
1191
|
-
* a `ReadableStream` as `init.body
|
|
1192
|
-
*
|
|
1250
|
+
* Create a transport adapter for `CmcdReportRecorder` that patches
|
|
1251
|
+
* `globalThis.fetch` to capture requests with CMCD data. The patched
|
|
1252
|
+
* `fetch` normalizes each request to `HttpRequest` (lowercase headers,
|
|
1253
|
+
* body read as a UTF-8 string).
|
|
1254
|
+
*
|
|
1255
|
+
* The patched `fetch` reads each body once with `Request.text()` for
|
|
1256
|
+
* inspection. This read is safe for the body types that CMCD reports use
|
|
1257
|
+
* in practice: `string`, `Blob`, `ArrayBuffer`, `FormData`, and
|
|
1258
|
+
* `URLSearchParams`. The underlying `fetch` can read those bodies again.
|
|
1259
|
+
* Do not pass a `ReadableStream` as `init.body`. The patched `fetch`
|
|
1260
|
+
* consumes the stream, and the underlying `fetch` receives an
|
|
1193
1261
|
* already-disturbed stream.
|
|
1194
1262
|
*
|
|
1195
1263
|
* @public
|
|
@@ -1255,9 +1323,8 @@ async function completeXhrWith(xhr, response) {
|
|
|
1255
1323
|
});
|
|
1256
1324
|
}
|
|
1257
1325
|
/**
|
|
1258
|
-
* Create a transport adapter
|
|
1259
|
-
*
|
|
1260
|
-
* `CmcdReportRecorder`.
|
|
1326
|
+
* Create a transport adapter for `CmcdReportRecorder` that patches
|
|
1327
|
+
* `XMLHttpRequest.prototype` to capture requests with CMCD data.
|
|
1261
1328
|
*
|
|
1262
1329
|
* @public
|
|
1263
1330
|
*/
|
|
@@ -1320,10 +1387,10 @@ function detectReportingMode(url, headers, isEventTarget) {
|
|
|
1320
1387
|
return url.includes(`${CMCD_PARAM}=`) ? "query" : null;
|
|
1321
1388
|
}
|
|
1322
1389
|
/**
|
|
1323
|
-
* Test helper that records CMCD
|
|
1324
|
-
* transports for
|
|
1325
|
-
*
|
|
1326
|
-
* tests are identical regardless of which transport the player uses.
|
|
1390
|
+
* Test helper that records CMCD reports from the XHR and fetch
|
|
1391
|
+
* transports for assertions in e2e tests. The recorder normalizes each
|
|
1392
|
+
* captured request to {@link @svta/cml-utils!HttpRequest | HttpRequest},
|
|
1393
|
+
* so tests are identical regardless of which transport the player uses.
|
|
1327
1394
|
*
|
|
1328
1395
|
* @example
|
|
1329
1396
|
* {@includeCode ../test/CmcdReportRecorder.test.ts#example}
|
|
@@ -1391,11 +1458,11 @@ var CmcdReportRecorder = class {
|
|
|
1391
1458
|
});
|
|
1392
1459
|
}
|
|
1393
1460
|
/**
|
|
1394
|
-
* Install transport patches and
|
|
1395
|
-
*
|
|
1396
|
-
*
|
|
1397
|
-
*
|
|
1398
|
-
*
|
|
1461
|
+
* Install transport patches and start recording CMCD reports.
|
|
1462
|
+
* If the recorder is already attached, this call does nothing. The
|
|
1463
|
+
* recorder keeps the previous options and silently ignores the new
|
|
1464
|
+
* `options` argument. To re-attach with different options, call
|
|
1465
|
+
* `detach()` first.
|
|
1399
1466
|
*
|
|
1400
1467
|
* @public
|
|
1401
1468
|
*/
|
|
@@ -1436,7 +1503,7 @@ var CmcdReportRecorder = class {
|
|
|
1436
1503
|
this.#reports = [];
|
|
1437
1504
|
}
|
|
1438
1505
|
/**
|
|
1439
|
-
* Return a
|
|
1506
|
+
* Return a copy of all recorded reports.
|
|
1440
1507
|
*
|
|
1441
1508
|
* @public
|
|
1442
1509
|
*/
|
|
@@ -1445,8 +1512,8 @@ var CmcdReportRecorder = class {
|
|
|
1445
1512
|
}
|
|
1446
1513
|
/**
|
|
1447
1514
|
* Wait until at least `count` reports of any type are recorded.
|
|
1448
|
-
* Resolves with all matching reports
|
|
1449
|
-
* on timeout. `count` defaults to 1
|
|
1515
|
+
* Resolves with all matching reports. Rejects with a diagnostic error
|
|
1516
|
+
* on timeout. `count` defaults to 1. `timeout` defaults to the
|
|
1450
1517
|
* recorder's `waitTimeout` option (default 15000 ms).
|
|
1451
1518
|
*
|
|
1452
1519
|
* @public
|
|
@@ -1456,8 +1523,8 @@ var CmcdReportRecorder = class {
|
|
|
1456
1523
|
}
|
|
1457
1524
|
/**
|
|
1458
1525
|
* Wait until at least `count` manifest reports are recorded.
|
|
1459
|
-
* Resolves with all matching reports
|
|
1460
|
-
* on timeout. `count` defaults to 1
|
|
1526
|
+
* Resolves with all matching reports. Rejects with a diagnostic error
|
|
1527
|
+
* on timeout. `count` defaults to 1. `timeout` defaults to the
|
|
1461
1528
|
* recorder's `waitTimeout` option (default 15000 ms).
|
|
1462
1529
|
*
|
|
1463
1530
|
* @public
|
|
@@ -1467,8 +1534,8 @@ var CmcdReportRecorder = class {
|
|
|
1467
1534
|
}
|
|
1468
1535
|
/**
|
|
1469
1536
|
* Wait until at least `count` segment reports are recorded.
|
|
1470
|
-
* Resolves with all matching reports
|
|
1471
|
-
* on timeout. `count` defaults to 1
|
|
1537
|
+
* Resolves with all matching reports. Rejects with a diagnostic error
|
|
1538
|
+
* on timeout. `count` defaults to 1. `timeout` defaults to the
|
|
1472
1539
|
* recorder's `waitTimeout` option (default 15000 ms).
|
|
1473
1540
|
*
|
|
1474
1541
|
* @public
|
|
@@ -1478,8 +1545,8 @@ var CmcdReportRecorder = class {
|
|
|
1478
1545
|
}
|
|
1479
1546
|
/**
|
|
1480
1547
|
* Wait until at least `count` event-mode reports are recorded.
|
|
1481
|
-
* Resolves with all matching reports
|
|
1482
|
-
* on timeout. `count` defaults to 1
|
|
1548
|
+
* Resolves with all matching reports. Rejects with a diagnostic error
|
|
1549
|
+
* on timeout. `count` defaults to 1. `timeout` defaults to the
|
|
1483
1550
|
* recorder's `waitTimeout` option (default 15000 ms).
|
|
1484
1551
|
*
|
|
1485
1552
|
* @public
|
|
@@ -1527,12 +1594,12 @@ const CmcdTransmissionMode = {
|
|
|
1527
1594
|
//#endregion
|
|
1528
1595
|
//#region src/upConvertToV2.ts
|
|
1529
1596
|
/**
|
|
1530
|
-
* Up-convert
|
|
1597
|
+
* Up-convert version 1 CMCD data to version 2.
|
|
1531
1598
|
*
|
|
1532
|
-
* - Wraps
|
|
1599
|
+
* - Wraps scalar values in arrays for inner-list keys.
|
|
1533
1600
|
* - Wraps `nor` string in an array.
|
|
1534
1601
|
*
|
|
1535
|
-
*
|
|
1602
|
+
* The function returns version 2 data (has `v: 2`) unchanged.
|
|
1536
1603
|
*
|
|
1537
1604
|
* @internal
|
|
1538
1605
|
*/
|
|
@@ -1581,12 +1648,11 @@ function createEncodingOptions(reportingMode, config, baseUrl) {
|
|
|
1581
1648
|
};
|
|
1582
1649
|
}
|
|
1583
1650
|
/**
|
|
1584
|
-
* Deep equality for CmcdObjectTypeList (used for `br`
|
|
1651
|
+
* Deep equality for CmcdObjectTypeList (used for `br` deduplication).
|
|
1585
1652
|
*
|
|
1586
|
-
*
|
|
1587
|
-
*
|
|
1588
|
-
*
|
|
1589
|
-
* safer failure mode.
|
|
1653
|
+
* Arrays with the same elements in different positions count as different.
|
|
1654
|
+
* Players that construct `br` consistently get correct deduplication.
|
|
1655
|
+
* Reordered arrays produce extra events, which is the safer failure.
|
|
1590
1656
|
*/
|
|
1591
1657
|
function cmcdObjectTypeListEqual(a, b) {
|
|
1592
1658
|
if (a === b) return true;
|
|
@@ -1627,26 +1693,30 @@ const STATE_FIELDS = /* @__PURE__ */ Array.from(CMCD_STATE_EVENT_FIELDS, ([event
|
|
|
1627
1693
|
};
|
|
1628
1694
|
});
|
|
1629
1695
|
const STATE_FIELDS_BY_EVENT = /* @__PURE__ */ new Map(/* @__PURE__ */ STATE_FIELDS.map((e) => [e.event, e]));
|
|
1696
|
+
function buildRequiredEventKeys() {
|
|
1697
|
+
return new Map([
|
|
1698
|
+
...CMCD_STATE_EVENT_FIELDS,
|
|
1699
|
+
[CMCD_EVENT_CUSTOM_EVENT, "cen"],
|
|
1700
|
+
[CMCD_EVENT_ERROR, "ec"],
|
|
1701
|
+
[CMCD_EVENT_RESPONSE_RECEIVED, "url"]
|
|
1702
|
+
]);
|
|
1703
|
+
}
|
|
1630
1704
|
/**
|
|
1631
1705
|
* Maps each event type to the key CTA-5004-B requires beyond `e` and `ts`.
|
|
1632
1706
|
* Built from the state-change table plus the three event types whose
|
|
1633
|
-
* required key
|
|
1707
|
+
* required key is event data, not player state.
|
|
1634
1708
|
*/
|
|
1635
|
-
const CMCD_REQUIRED_EVENT_KEYS = /* @__PURE__ */
|
|
1636
|
-
...CMCD_STATE_EVENT_FIELDS,
|
|
1637
|
-
[CMCD_EVENT_CUSTOM_EVENT, "cen"],
|
|
1638
|
-
[CMCD_EVENT_ERROR, "ec"],
|
|
1639
|
-
[CMCD_EVENT_RESPONSE_RECEIVED, "url"]
|
|
1640
|
-
]);
|
|
1709
|
+
const CMCD_REQUIRED_EVENT_KEYS = /* @__PURE__ */ buildRequiredEventKeys();
|
|
1641
1710
|
/**
|
|
1642
1711
|
* Whether a required key's value will survive report preparation.
|
|
1643
1712
|
*
|
|
1644
|
-
* This is `isValid`
|
|
1645
|
-
*
|
|
1646
|
-
*
|
|
1647
|
-
*
|
|
1648
|
-
*
|
|
1649
|
-
*
|
|
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.
|
|
1650
1720
|
*/
|
|
1651
1721
|
function isUsableRequiredValue(value) {
|
|
1652
1722
|
if (value == null || value === "") return false;
|
|
@@ -1654,12 +1724,12 @@ function isUsableRequiredValue(value) {
|
|
|
1654
1724
|
return !Array.isArray(value) || value.length > 0;
|
|
1655
1725
|
}
|
|
1656
1726
|
/**
|
|
1657
|
-
* Copies
|
|
1727
|
+
* Copies a value with the `SfItem` structure, including its `params` record.
|
|
1658
1728
|
*
|
|
1659
|
-
* The
|
|
1729
|
+
* The copy keeps the prototype because `prepareCmcdData`, the formatter map,
|
|
1660
1730
|
* validation, and the structured-field encoder all branch on
|
|
1661
1731
|
* `instanceof SfItem`. A plain spread (and `structuredClone`) would return a
|
|
1662
|
-
* prototype-less object and silently change
|
|
1732
|
+
* prototype-less object and silently change the encoded output.
|
|
1663
1733
|
*/
|
|
1664
1734
|
function copyItemValue(value) {
|
|
1665
1735
|
if (value === null || typeof value !== "object") return value;
|
|
@@ -1668,16 +1738,16 @@ function copyItemValue(value) {
|
|
|
1668
1738
|
return copy;
|
|
1669
1739
|
}
|
|
1670
1740
|
/**
|
|
1671
|
-
* Copies the nested values of a report in place
|
|
1672
|
-
* the reporter's
|
|
1673
|
-
*
|
|
1741
|
+
* Copies the nested values of a report in place. A transform cannot mutate
|
|
1742
|
+
* the reporter's data store, or another target's report for the same event,
|
|
1743
|
+
* through an array or an `SfItem` it received.
|
|
1674
1744
|
*
|
|
1675
|
-
*
|
|
1745
|
+
* The copy is complete for the CMCD value space. `CmcdValue` and
|
|
1676
1746
|
* `CmcdCustomValue` admit only primitives, `SfItem<primitive>`, and arrays of
|
|
1677
|
-
* those
|
|
1678
|
-
* configured, on the ended session's
|
|
1679
|
-
*
|
|
1680
|
-
*
|
|
1747
|
+
* those. `SfItem.params` is a flat record. The reporter calls this function
|
|
1748
|
+
* where a transform is configured, at session end on the ended session's
|
|
1749
|
+
* store, and on the request's stored player-facing view. The session-end
|
|
1750
|
+
* copy detaches the frozen snapshot from caller-held references.
|
|
1681
1751
|
*/
|
|
1682
1752
|
function copyReportValues(data) {
|
|
1683
1753
|
const record = data;
|
|
@@ -1692,14 +1762,13 @@ function copyReportValues(data) {
|
|
|
1692
1762
|
return data;
|
|
1693
1763
|
}
|
|
1694
1764
|
/**
|
|
1695
|
-
* Decodes the per-call data snapshot a provenance record
|
|
1696
|
-
*
|
|
1697
|
-
*
|
|
1698
|
-
*
|
|
1699
|
-
*
|
|
1700
|
-
*
|
|
1701
|
-
*
|
|
1702
|
-
* over the session's data alone.
|
|
1765
|
+
* Decodes the per-call data snapshot of a provenance record into new,
|
|
1766
|
+
* encodable report data. Tokens decode as `SfToken` and items with params
|
|
1767
|
+
* as `SfItem` (`useSymbol: false`), so every value re-encodes with its
|
|
1768
|
+
* original type. Only `recordResponseReceived()` calls this function.
|
|
1769
|
+
* Returns an empty object when the record is absent, has no snapshot, or
|
|
1770
|
+
* the snapshot does not parse (a value this reporter did not write). The
|
|
1771
|
+
* response then reports its derived keys over the session's data alone.
|
|
1703
1772
|
*/
|
|
1704
1773
|
function decodeSnapshot(provenance) {
|
|
1705
1774
|
const encoded = provenance !== null && typeof provenance === "object" ? provenance.data : void 0;
|
|
@@ -1711,10 +1780,10 @@ function decodeSnapshot(provenance) {
|
|
|
1711
1780
|
}
|
|
1712
1781
|
}
|
|
1713
1782
|
/**
|
|
1714
|
-
*
|
|
1715
|
-
* the `cid`
|
|
1716
|
-
* change
|
|
1717
|
-
*
|
|
1783
|
+
* Creates a session's frozen base provenance record: the issuing `sid`, and
|
|
1784
|
+
* the `cid` that is current at creation time. `update()` creates a new record
|
|
1785
|
+
* on every `cid` change. Requests issued before a mid-session content change
|
|
1786
|
+
* keep their original `cid`, while later requests get the new one.
|
|
1718
1787
|
*/
|
|
1719
1788
|
function mintProvenance(sid, cid) {
|
|
1720
1789
|
return Object.freeze(typeof cid === "string" && cid ? {
|
|
@@ -1753,12 +1822,12 @@ function createCmcdReporterConfig(config) {
|
|
|
1753
1822
|
/**
|
|
1754
1823
|
* The CMCD reporter.
|
|
1755
1824
|
*
|
|
1756
|
-
* `C` describes the player's own `customData`, which the reporter passes
|
|
1757
|
-
*
|
|
1758
|
-
*
|
|
1759
|
-
*
|
|
1825
|
+
* `C` describes the player's own `customData`, which the reporter passes to
|
|
1826
|
+
* each configured `transform`. TypeScript infers `C` from the configuration,
|
|
1827
|
+
* so an annotation on one `transform` types the request in every other
|
|
1828
|
+
* `transform`. The default leaves `customData` values `unknown`.
|
|
1760
1829
|
*
|
|
1761
|
-
* @typeParam C - The
|
|
1830
|
+
* @typeParam C - The type of the player's `customData`. Defaults to
|
|
1762
1831
|
* `Record<string, unknown>`.
|
|
1763
1832
|
*
|
|
1764
1833
|
* @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#reporting-modes-when-we-send-data | CTA-5004-B Reporting Modes}
|
|
@@ -1770,7 +1839,7 @@ var CmcdReporter = class {
|
|
|
1770
1839
|
* Creates a new CMCD reporter.
|
|
1771
1840
|
*
|
|
1772
1841
|
* @param config - The configuration for the CMCD reporter.
|
|
1773
|
-
* @param requester - The function
|
|
1842
|
+
* @param requester - The function that sends the request.
|
|
1774
1843
|
* The default is a simple wrapper around the
|
|
1775
1844
|
* native `fetch` API.
|
|
1776
1845
|
*/
|
|
@@ -1788,8 +1857,8 @@ var CmcdReporter = class {
|
|
|
1788
1857
|
this.requester = requester;
|
|
1789
1858
|
}
|
|
1790
1859
|
/**
|
|
1791
|
-
* Creates the state for a new session:
|
|
1792
|
-
*
|
|
1860
|
+
* Creates the state for a new session: initial counters, flags, queues, and
|
|
1861
|
+
* deduplication baseline for every configured target.
|
|
1793
1862
|
*/
|
|
1794
1863
|
createSession(sid, data) {
|
|
1795
1864
|
const eventTargets = /* @__PURE__ */ new Map();
|
|
@@ -1813,11 +1882,12 @@ var CmcdReporter = class {
|
|
|
1813
1882
|
};
|
|
1814
1883
|
}
|
|
1815
1884
|
/**
|
|
1816
|
-
* Starts the CMCD reporter.
|
|
1885
|
+
* Starts the CMCD reporter. The player calls this method when it enables the
|
|
1886
|
+
* reporter.
|
|
1817
1887
|
*
|
|
1818
|
-
*
|
|
1819
|
-
* before the first interval elapses.
|
|
1820
|
-
*
|
|
1888
|
+
* This method fires an initial time-interval event immediately (synchronously),
|
|
1889
|
+
* before the first interval elapses. Populate the CMCD data (sid, cid, and
|
|
1890
|
+
* others) before calling start().
|
|
1821
1891
|
*/
|
|
1822
1892
|
start() {
|
|
1823
1893
|
this.started = true;
|
|
@@ -1836,9 +1906,9 @@ var CmcdReporter = class {
|
|
|
1836
1906
|
if (failure) throw failure.error;
|
|
1837
1907
|
}
|
|
1838
1908
|
/**
|
|
1839
|
-
*
|
|
1840
|
-
*
|
|
1841
|
-
* whichever session is current when
|
|
1909
|
+
* Starts the time-interval timer for an event target when its config
|
|
1910
|
+
* requires one. Returns whether a timer started. The callback reports into
|
|
1911
|
+
* whichever session is current when the timer fires.
|
|
1842
1912
|
*/
|
|
1843
1913
|
armInterval(config) {
|
|
1844
1914
|
if (config.interval === 0 || !config.events.includes(CMCD_EVENT_TIME_INTERVAL)) return false;
|
|
@@ -1853,7 +1923,8 @@ var CmcdReporter = class {
|
|
|
1853
1923
|
return true;
|
|
1854
1924
|
}
|
|
1855
1925
|
/**
|
|
1856
|
-
* Stops the CMCD reporter.
|
|
1926
|
+
* Stops the CMCD reporter. The player calls this method when it disables the
|
|
1927
|
+
* reporter.
|
|
1857
1928
|
*
|
|
1858
1929
|
* @param flush - Whether to flush the event targets.
|
|
1859
1930
|
*/
|
|
@@ -1863,8 +1934,8 @@ var CmcdReporter = class {
|
|
|
1863
1934
|
for (const config of this.config.eventTargets) this.disarmInterval(config);
|
|
1864
1935
|
}
|
|
1865
1936
|
/**
|
|
1866
|
-
*
|
|
1867
|
-
*
|
|
1937
|
+
* Sends all outstanding event reports, regardless of the batch size or
|
|
1938
|
+
* interval. Use this method when the player is destroyed or a playback
|
|
1868
1939
|
* session ends.
|
|
1869
1940
|
*/
|
|
1870
1941
|
flush() {
|
|
@@ -1873,40 +1944,38 @@ var CmcdReporter = class {
|
|
|
1873
1944
|
/**
|
|
1874
1945
|
* Updates the CMCD data.
|
|
1875
1946
|
*
|
|
1876
|
-
*
|
|
1877
|
-
*
|
|
1878
|
-
*
|
|
1879
|
-
*
|
|
1880
|
-
*
|
|
1881
|
-
*
|
|
1882
|
-
*
|
|
1883
|
-
* across the `sid` boundary.
|
|
1947
|
+
* The player calls this method when data changes. The tracked state fields
|
|
1948
|
+
* are `sta`, `pr`, `cid`, `bg`, and `br`. If a new value for one of them
|
|
1949
|
+
* differs from the last reported value for that field, the reporter fires
|
|
1950
|
+
* the matching state-change event. The comparison uses the last reported
|
|
1951
|
+
* value, not the previous persisted value. The first state-change event in
|
|
1952
|
+
* a new session therefore always fires, even when the persisted value did
|
|
1953
|
+
* not change across the `sid` boundary.
|
|
1884
1954
|
*
|
|
1885
|
-
* Multi-field updates fire
|
|
1886
|
-
* `
|
|
1887
|
-
*
|
|
1955
|
+
* Multi-field updates fire the events in this order: `sta`, `pr`, `cid`,
|
|
1956
|
+
* `bg`, `br`. The order of keys in the input object does not affect the
|
|
1957
|
+
* firing order.
|
|
1888
1958
|
*
|
|
1889
|
-
*
|
|
1890
|
-
*
|
|
1891
|
-
*
|
|
1892
|
-
*
|
|
1893
|
-
*
|
|
1894
|
-
* the
|
|
1895
|
-
*
|
|
1896
|
-
*
|
|
1897
|
-
* in periodic reports must be kept fresh here.
|
|
1959
|
+
* A state-change event can include context fields, for example `bl`, `mtp`,
|
|
1960
|
+
* `pt`, `ltc`. Those fields must be in the data store before the state field
|
|
1961
|
+
* changes. Either pass them with the state field in the same `update()`
|
|
1962
|
+
* call, or persist them with earlier `update()` calls. An auto-fired event
|
|
1963
|
+
* reports the current content of the data store. `TIME_INTERVAL` events also
|
|
1964
|
+
* read the data store, and the caller cannot add per-call data to them. The
|
|
1965
|
+
* player must therefore update the fields it wants in periodic reports with
|
|
1966
|
+
* this method.
|
|
1898
1967
|
*
|
|
1899
|
-
* A `sid` change resets the
|
|
1968
|
+
* A `sid` change resets the deduplication baseline.
|
|
1900
1969
|
*
|
|
1901
|
-
* `sid` and `msd` are session-owned
|
|
1902
|
-
*
|
|
1970
|
+
* `sid` and `msd` are session-owned. The reporter tracks them itself and
|
|
1971
|
+
* writes them into every outgoing report, so this method is the only way
|
|
1903
1972
|
* to change them. A per-call value on {@link CmcdReporter.recordEvent},
|
|
1904
1973
|
* {@link CmcdReporter.createRequestReport}, or
|
|
1905
|
-
* {@link CmcdReporter.recordResponseReceived} has no effect
|
|
1906
|
-
* attribution
|
|
1974
|
+
* {@link CmcdReporter.recordResponseReceived} has no effect. Response
|
|
1975
|
+
* attribution uses the provenance record alone.
|
|
1907
1976
|
* `msd` must be a finite number of milliseconds between `0` and
|
|
1908
|
-
* `999_999_999_999_999` (the RFC 8941 integer maximum)
|
|
1909
|
-
* the nearest integer
|
|
1977
|
+
* `999_999_999_999_999` (the RFC 8941 integer maximum). The reporter
|
|
1978
|
+
* rounds `msd` to the nearest integer and ignores invalid values.
|
|
1910
1979
|
*
|
|
1911
1980
|
* @param data - The data to update.
|
|
1912
1981
|
*/
|
|
@@ -1928,19 +1997,20 @@ var CmcdReporter = class {
|
|
|
1928
1997
|
for (const entry of STATE_FIELDS) if (entry.field in data && !entry.equal(session.data[entry.field], session.lastEmitted[entry.field])) this.recordEvent(entry.event);
|
|
1929
1998
|
}
|
|
1930
1999
|
/**
|
|
1931
|
-
* Starts a new session
|
|
1932
|
-
*
|
|
1933
|
-
* the oldest
|
|
2000
|
+
* Starts a new session. The reporter retains the current session for late
|
|
2001
|
+
* responses and creates a new current session. It then sends the ended
|
|
2002
|
+
* sessions' queued reports and removes the oldest sessions beyond the
|
|
2003
|
+
* configured retention.
|
|
1934
2004
|
*
|
|
1935
|
-
* The
|
|
1936
|
-
*
|
|
1937
|
-
*
|
|
1938
|
-
*
|
|
1939
|
-
*
|
|
1940
|
-
*
|
|
1941
|
-
*
|
|
1942
|
-
*
|
|
1943
|
-
*
|
|
2005
|
+
* The ended session's snapshot is frozen: the reporter detaches its value
|
|
2006
|
+
* graph, so nested values from earlier `update()` calls cannot mutate the
|
|
2007
|
+
* snapshot. The new session starts from a shallow copy of that graph, which
|
|
2008
|
+
* preserves the data store. All fields, including `cid`, `br`, and custom
|
|
2009
|
+
* keys, survive a `sid` change as before. CTA-5004-B expects a `sid` to be
|
|
2010
|
+
* unique per playback session, so a reused `sid` replaces the retained
|
|
2011
|
+
* session with that `sid`. The replaced session's remaining state is lost,
|
|
2012
|
+
* as if removed, and the reporter attributes its late responses to the
|
|
2013
|
+
* replacement.
|
|
1944
2014
|
*/
|
|
1945
2015
|
startSession(sid) {
|
|
1946
2016
|
this.session.data = copyReportValues({ ...this.session.data });
|
|
@@ -1957,59 +2027,57 @@ var CmcdReporter = class {
|
|
|
1957
2027
|
}
|
|
1958
2028
|
}
|
|
1959
2029
|
/**
|
|
1960
|
-
* Records an event.
|
|
2030
|
+
* Records an event. The player calls this method when an event occurs.
|
|
1961
2031
|
*
|
|
1962
2032
|
* For state-change events (`PLAY_STATE`, `PLAYBACK_RATE`, `CONTENT_ID`,
|
|
1963
2033
|
* `BACKGROUNDED_MODE`, `BITRATE_CHANGE`), this method:
|
|
1964
|
-
* 1.
|
|
1965
|
-
*
|
|
1966
|
-
* 2.
|
|
1967
|
-
*
|
|
1968
|
-
*
|
|
1969
|
-
* 3. Suppresses the event if the field's current value
|
|
1970
|
-
* last
|
|
2034
|
+
* 1. Writes the state field from `data` (if present) into the reporter's
|
|
2035
|
+
* data store, as `update()` would.
|
|
2036
|
+
* 2. Discards the event if the state field has no value after that write
|
|
2037
|
+
* (never set, or cleared with `update({ field: undefined })`).
|
|
2038
|
+
* A state-change event without its required field would violate CTA-5004-B.
|
|
2039
|
+
* 3. Suppresses the event if the field's current value equals the
|
|
2040
|
+
* last reported value (no state transition).
|
|
1971
2041
|
*
|
|
1972
|
-
*
|
|
2042
|
+
* The reporter always records all other event types.
|
|
1973
2043
|
*
|
|
1974
|
-
* For state-change events, prefer {@link CmcdReporter.update}
|
|
1975
|
-
*
|
|
1976
|
-
* `update({ sta: 'p', bl: [3000], mtp: [8500] })`.
|
|
1977
|
-
*
|
|
1978
|
-
*
|
|
1979
|
-
*
|
|
2044
|
+
* For state-change events, prefer {@link CmcdReporter.update}, also for
|
|
2045
|
+
* context fields in a combined call like
|
|
2046
|
+
* `update({ sta: 'p', bl: [3000], mtp: [8500] })`. After `update()`
|
|
2047
|
+
* auto-fires a state-change event, deduplication suppresses a
|
|
2048
|
+
* `recordEvent()` call for the same event, and that call's `data` is
|
|
2049
|
+
* silently lost.
|
|
1980
2050
|
*
|
|
1981
|
-
*
|
|
1982
|
-
*
|
|
1983
|
-
*
|
|
1984
|
-
* `
|
|
1985
|
-
*
|
|
1986
|
-
* per-response fields automatically.
|
|
2051
|
+
* Call `recordEvent()` directly for events whose data comes with the event
|
|
2052
|
+
* call. Examples: `CUSTOM_EVENT` with `cen`, `ERROR` with `ec`, ad lifecycle
|
|
2053
|
+
* events, `MUTE`/`UNMUTE`, `PLAYER_EXPAND`/`PLAYER_COLLAPSE`, `SKIP`. For
|
|
2054
|
+
* `RESPONSE_RECEIVED`, prefer {@link CmcdReporter.recordResponseReceived},
|
|
2055
|
+
* which derives the per-response fields automatically.
|
|
1987
2056
|
*
|
|
1988
2057
|
* @param type - The type of event to record.
|
|
1989
|
-
* @param data - Additional data to record with the event. This data
|
|
1990
|
-
* only
|
|
1991
|
-
*
|
|
1992
|
-
*
|
|
1993
|
-
* (`sid`, `msd`) supplied here
|
|
1994
|
-
* stamps its own values.
|
|
2058
|
+
* @param data - Additional data to record with the event. This data applies
|
|
2059
|
+
* only to this event report. The exception is the state field
|
|
2060
|
+
* of a state-change event, which the reporter also writes into
|
|
2061
|
+
* its data store. The reporter ignores session-owned keys
|
|
2062
|
+
* (`sid`, `msd`) supplied here and writes its own values.
|
|
1995
2063
|
*/
|
|
1996
2064
|
recordEvent(type, data = {}) {
|
|
1997
2065
|
this.emitEvent(this.session, type, data);
|
|
1998
2066
|
}
|
|
1999
2067
|
/**
|
|
2000
|
-
* Records an event
|
|
2001
|
-
*
|
|
2068
|
+
* Records an event for every configured target of a session. State-change
|
|
2069
|
+
* deduplication runs once, before the per-target loop.
|
|
2002
2070
|
*
|
|
2003
2071
|
* @param session - The session the event belongs to. Everything the event
|
|
2004
|
-
* touches (data store,
|
|
2005
|
-
* is that session's
|
|
2006
|
-
* passes an
|
|
2072
|
+
* touches (data store, deduplication baseline, counters,
|
|
2073
|
+
* queues) is that session's. Only `recordResponseReceived()`
|
|
2074
|
+
* ever passes an ended session.
|
|
2007
2075
|
* @param type - The type of event to record.
|
|
2008
2076
|
* @param data - Additional data to record with the event.
|
|
2009
2077
|
* @param request - The media request that triggered the event, when
|
|
2010
|
-
* one exists. Only `recordResponseReceived()`
|
|
2011
|
-
*
|
|
2012
|
-
*
|
|
2078
|
+
* one exists. Only `recordResponseReceived()` sets this
|
|
2079
|
+
* parameter, which the reporter passes to each target's
|
|
2080
|
+
* `transform`.
|
|
2013
2081
|
*/
|
|
2014
2082
|
emitEvent(session, type, data, request) {
|
|
2015
2083
|
const entry = STATE_FIELDS_BY_EVENT.get(type);
|
|
@@ -2034,16 +2102,17 @@ var CmcdReporter = class {
|
|
|
2034
2102
|
if (failure) throw failure.error;
|
|
2035
2103
|
}
|
|
2036
2104
|
/**
|
|
2037
|
-
* Records an event for a target.
|
|
2105
|
+
* Records an event for a target. The reporter calls this method when an
|
|
2106
|
+
* event occurs.
|
|
2038
2107
|
*
|
|
2039
2108
|
* @param target - The target to record the event for.
|
|
2040
2109
|
* @param config - The configuration for the target.
|
|
2041
2110
|
* @param type - The type of event to record.
|
|
2042
2111
|
* @param data - Additional data to record with the event. This data
|
|
2043
|
-
* only applies to this event report.
|
|
2044
|
-
*
|
|
2112
|
+
* only applies to this event report. Use `update()` for
|
|
2113
|
+
* persistent data.
|
|
2045
2114
|
* @param request - The media request that triggered the event, when
|
|
2046
|
-
* one exists.
|
|
2115
|
+
* one exists. The target's `transform` receives it.
|
|
2047
2116
|
*/
|
|
2048
2117
|
recordTargetEvent(session, target, config, type, data = {}, request) {
|
|
2049
2118
|
if (target.disposed || !config.events.includes(type)) return;
|
|
@@ -2070,15 +2139,14 @@ var CmcdReporter = class {
|
|
|
2070
2139
|
this.queueTargetEvent(session, target, config, report, type);
|
|
2071
2140
|
}
|
|
2072
2141
|
/**
|
|
2073
|
-
*
|
|
2074
|
-
* and pushes the
|
|
2142
|
+
* Writes the reporter-owned fields into a finished event report, encodes
|
|
2143
|
+
* the report, and pushes the encoded line to the target's queue.
|
|
2075
2144
|
*
|
|
2076
|
-
*
|
|
2077
|
-
* target's `events` filter
|
|
2078
|
-
* the session identity
|
|
2079
|
-
*
|
|
2080
|
-
*
|
|
2081
|
-
* forever.
|
|
2145
|
+
* This method runs after any transform. A transform cannot bypass the
|
|
2146
|
+
* target's `events` filter through `e`, break `sn` continuity, or replace
|
|
2147
|
+
* the session identity in `sid` and `msd`. Encoding here makes a report
|
|
2148
|
+
* that cannot serialize throw inside the recording call, instead of failing
|
|
2149
|
+
* the batch send and re-queueing forever.
|
|
2082
2150
|
*
|
|
2083
2151
|
* @param target - The target to queue the report for.
|
|
2084
2152
|
* @param config - The configuration for the target.
|
|
@@ -2096,10 +2164,10 @@ var CmcdReporter = class {
|
|
|
2096
2164
|
target.queue.push(encodeCmcd(report, createEncodingOptions(CMCD_EVENT_MODE, config)));
|
|
2097
2165
|
}
|
|
2098
2166
|
/**
|
|
2099
|
-
* Records a response-received event.
|
|
2100
|
-
* request response has
|
|
2167
|
+
* Records a response-received event. The player calls this method when a
|
|
2168
|
+
* media request response has fully arrived.
|
|
2101
2169
|
*
|
|
2102
|
-
* This method
|
|
2170
|
+
* This method derives these `rr` event keys automatically:
|
|
2103
2171
|
*
|
|
2104
2172
|
* - `url` - the original requested URL (before any redirects)
|
|
2105
2173
|
* - `rc` - the HTTP response status code
|
|
@@ -2107,50 +2175,50 @@ var CmcdReporter = class {
|
|
|
2107
2175
|
* - `ttfb` - time to first byte (from `resourceTiming.responseStart`)
|
|
2108
2176
|
* - `ttlb` - time to last byte (from `resourceTiming.duration`)
|
|
2109
2177
|
*
|
|
2110
|
-
*
|
|
2111
|
-
*
|
|
2178
|
+
* If the player has additional keys like `ttfbb`, `cmsdd`, `cmsds`, and
|
|
2179
|
+
* `smrt`, pass them in the `data` parameter.
|
|
2112
2180
|
*
|
|
2113
|
-
* The request's `customData` is generic, so a player can pass a request
|
|
2114
|
-
*
|
|
2115
|
-
*
|
|
2116
|
-
* {@link CmcdReporter.createRequestReport} writes there
|
|
2117
|
-
*
|
|
2118
|
-
*
|
|
2181
|
+
* The request's `customData` is generic, so a player can pass a request with
|
|
2182
|
+
* its own keys (for example `{ requestType: 'segment' }`) without a cast. The
|
|
2183
|
+
* reporter reads only the provenance record that
|
|
2184
|
+
* {@link CmcdReporter.createRequestReport} writes there. Every other key
|
|
2185
|
+
* stays unchanged and visible to an event target's `transform` through its
|
|
2186
|
+
* `request` argument.
|
|
2119
2187
|
*
|
|
2120
|
-
* A reporter
|
|
2121
|
-
*
|
|
2122
|
-
*
|
|
2188
|
+
* A reporter with a concrete `C` requires the request to satisfy that type.
|
|
2189
|
+
* TypeScript therefore rejects a call whose request the configured
|
|
2190
|
+
* transforms could not read. See {@link CmcdReporterCustomData}.
|
|
2123
2191
|
*
|
|
2124
|
-
* The event
|
|
2125
|
-
*
|
|
2192
|
+
* The reporter attributes the event only to the session that issued the
|
|
2193
|
+
* request. The provenance record that
|
|
2126
2194
|
* {@link CmcdReporter.createRequestReport} stored on the request's
|
|
2127
|
-
* `customData` (under {@link CMCD_REQUEST_PROVENANCE}) selects the
|
|
2128
|
-
*
|
|
2129
|
-
*
|
|
2130
|
-
*
|
|
2131
|
-
* {@link CMCD_REQUEST_PROVENANCE})
|
|
2132
|
-
*
|
|
2133
|
-
* `
|
|
2134
|
-
*
|
|
2135
|
-
*
|
|
2136
|
-
* A response that completes after a `sid`
|
|
2137
|
-
* retained session, with that session's data
|
|
2138
|
-
* numbers.
|
|
2195
|
+
* `customData` (under {@link CMCD_REQUEST_PROVENANCE}) selects the session
|
|
2196
|
+
* by `sid`. Without a match, the reporter discards the response rather than
|
|
2197
|
+
* attributing it elsewhere. There is no other key. The reporter discards the
|
|
2198
|
+
* response when a serialization boundary lost the record and nothing
|
|
2199
|
+
* restored it (see {@link CMCD_REQUEST_PROVENANCE}). It also discards the
|
|
2200
|
+
* response when the session is no longer retained (see
|
|
2201
|
+
* `CmcdReporterConfig.sessionRetention`). A per-call `data.sid` cannot
|
|
2202
|
+
* substitute. The reporter accepts any record whose `sid` names a retained
|
|
2203
|
+
* session. The record may come from another reporter with the same session
|
|
2204
|
+
* attributes, or be hand-built. A response that completes after a `sid`
|
|
2205
|
+
* change reports under its own retained session, with that session's data
|
|
2206
|
+
* snapshot and sequence numbers.
|
|
2139
2207
|
*
|
|
2140
|
-
* Request-time report keys come from the record's encoded per-call
|
|
2141
|
-
*
|
|
2142
|
-
*
|
|
2143
|
-
* completes after a mid-session content change
|
|
2144
|
-
*
|
|
2208
|
+
* Request-time report keys come from the record's encoded per-call `data`
|
|
2209
|
+
* snapshot, which the reporter decodes for each response. The record's `cid`
|
|
2210
|
+
* replaces the session's current `cid` in the report. A response that
|
|
2211
|
+
* completes after a mid-session content change therefore reports its
|
|
2212
|
+
* request-time values.
|
|
2145
2213
|
*
|
|
2146
|
-
* @typeParam RD - The `customData` this request
|
|
2214
|
+
* @typeParam RD - The `customData` of this request. Defaults to the
|
|
2147
2215
|
* reporter's own `C`.
|
|
2148
2216
|
*
|
|
2149
2217
|
* @param response - The HTTP response received.
|
|
2150
2218
|
* @param data - Additional CMCD data to include with the event.
|
|
2151
|
-
* Values provided here override
|
|
2152
|
-
*
|
|
2153
|
-
*
|
|
2219
|
+
* Values provided here override the derived values.
|
|
2220
|
+
* The reporter ignores session-owned keys (`sid`, `msd`)
|
|
2221
|
+
* supplied here. Attribution uses the provenance record alone.
|
|
2154
2222
|
*/
|
|
2155
2223
|
recordResponseReceived(response, data = {}) {
|
|
2156
2224
|
const { request } = response;
|
|
@@ -2183,13 +2251,13 @@ var CmcdReporter = class {
|
|
|
2183
2251
|
}, request);
|
|
2184
2252
|
}
|
|
2185
2253
|
/**
|
|
2186
|
-
* Resolves the session a response belongs to
|
|
2187
|
-
*
|
|
2188
|
-
*
|
|
2189
|
-
* names
|
|
2190
|
-
*
|
|
2191
|
-
*
|
|
2192
|
-
*
|
|
2254
|
+
* Resolves the session a response belongs to. The provenance record's `sid`
|
|
2255
|
+
* must name one of this reporter's retained sessions, or the reporter
|
|
2256
|
+
* discards the response. There is no other key. A lost record, or one that
|
|
2257
|
+
* names a removed or unknown `sid`, resolves nothing. Any other attribution
|
|
2258
|
+
* would be wrong. The reporter reads the `sid` as a plain property. A record
|
|
2259
|
+
* copied through JSON therefore resolves the same session, and a hand-built
|
|
2260
|
+
* record that names a retained session is accepted.
|
|
2193
2261
|
*/
|
|
2194
2262
|
resolveSession(provenance) {
|
|
2195
2263
|
if (provenance === null || typeof provenance !== "object") return;
|
|
@@ -2197,8 +2265,8 @@ var CmcdReporter = class {
|
|
|
2197
2265
|
return typeof sid === "string" ? this.sessions.get(sid) : void 0;
|
|
2198
2266
|
}
|
|
2199
2267
|
/**
|
|
2200
|
-
* Applies the CMCD request report data to the request.
|
|
2201
|
-
* before sending the request.
|
|
2268
|
+
* Applies the CMCD request report data to the request. The player calls this
|
|
2269
|
+
* method before sending the request.
|
|
2202
2270
|
*
|
|
2203
2271
|
* @param req - The request to apply the CMCD request report to.
|
|
2204
2272
|
* @returns The request with the CMCD request report applied.
|
|
@@ -2209,23 +2277,22 @@ var CmcdReporter = class {
|
|
|
2209
2277
|
return this.createRequestReport(req) ?? req;
|
|
2210
2278
|
}
|
|
2211
2279
|
/**
|
|
2212
|
-
* Checks
|
|
2280
|
+
* Checks whether request reporting is enabled.
|
|
2213
2281
|
*
|
|
2214
|
-
* @returns `true` if
|
|
2282
|
+
* @returns `true` if request reporting is enabled, `false` otherwise.
|
|
2215
2283
|
*/
|
|
2216
2284
|
isRequestReportingEnabled() {
|
|
2217
2285
|
return !!this.config.enabledKeys?.length;
|
|
2218
2286
|
}
|
|
2219
2287
|
/**
|
|
2220
|
-
* The provenance record to
|
|
2221
|
-
* frozen base record, extended with the caller's per-call data encoded
|
|
2222
|
-
*
|
|
2223
|
-
*
|
|
2224
|
-
*
|
|
2225
|
-
*
|
|
2226
|
-
*
|
|
2227
|
-
*
|
|
2228
|
-
* side.
|
|
2288
|
+
* The provenance record to write on a returned request. It is the session's
|
|
2289
|
+
* frozen base record, extended with the caller's per-call data encoded as a
|
|
2290
|
+
* CMCD string when there is any. Requests without per-call data share the
|
|
2291
|
+
* base record, so the reporter writes only one property. The snapshot never
|
|
2292
|
+
* contains reporter-written fields. A value that cannot encode contributes
|
|
2293
|
+
* no snapshot and does not fail the request. The response still attributes
|
|
2294
|
+
* and reports derived keys over session data, matching the tolerance of
|
|
2295
|
+
* `decodeSnapshot()` when reading.
|
|
2229
2296
|
*/
|
|
2230
2297
|
createRequestProvenance(data, baseUrl) {
|
|
2231
2298
|
const base = this.session.provenance;
|
|
@@ -2245,21 +2312,21 @@ var CmcdReporter = class {
|
|
|
2245
2312
|
}
|
|
2246
2313
|
}
|
|
2247
2314
|
/**
|
|
2248
|
-
* Creates a new request with the CMCD request report data applied.
|
|
2249
|
-
* before sending the request.
|
|
2315
|
+
* Creates a new request with the CMCD request report data applied. The
|
|
2316
|
+
* player calls this method before sending the request.
|
|
2250
2317
|
*
|
|
2251
|
-
* A reporter
|
|
2252
|
-
* satisfy
|
|
2253
|
-
*
|
|
2318
|
+
* A reporter with a concrete `C` requires the request's `customData` to
|
|
2319
|
+
* satisfy that type. TypeScript therefore rejects a call whose request the
|
|
2320
|
+
* configured transform could not read. See
|
|
2254
2321
|
* {@link CmcdReporterCustomData}.
|
|
2255
2322
|
*
|
|
2256
|
-
* @typeParam R - The
|
|
2323
|
+
* @typeParam R - The type of the request. Its `customData` must satisfy
|
|
2257
2324
|
* the reporter's `C`.
|
|
2258
2325
|
*
|
|
2259
2326
|
* @param request - The request to apply the CMCD request report to.
|
|
2260
2327
|
* @param data - The data to apply to the request. This data only
|
|
2261
|
-
* applies to this request report.
|
|
2262
|
-
*
|
|
2328
|
+
* applies to this request report. Use `update()` for
|
|
2329
|
+
* persistent data.
|
|
2263
2330
|
* @returns The request with the CMCD request report applied.
|
|
2264
2331
|
*/
|
|
2265
2332
|
createRequestReport(request, data) {
|
|
@@ -2311,7 +2378,8 @@ var CmcdReporter = class {
|
|
|
2311
2378
|
return report;
|
|
2312
2379
|
}
|
|
2313
2380
|
/**
|
|
2314
|
-
* Processes the event targets.
|
|
2381
|
+
* Processes the event targets. The reporter calls this method when an event
|
|
2382
|
+
* occurs.
|
|
2315
2383
|
*
|
|
2316
2384
|
* @param flush - Whether to flush the event targets.
|
|
2317
2385
|
*/
|
|
@@ -2334,7 +2402,8 @@ var CmcdReporter = class {
|
|
|
2334
2402
|
if (reprocess) this.processEventTargets();
|
|
2335
2403
|
}
|
|
2336
2404
|
/**
|
|
2337
|
-
* Sends an event report.
|
|
2405
|
+
* Sends an event report. The reporter calls this method when a batch is
|
|
2406
|
+
* ready.
|
|
2338
2407
|
*
|
|
2339
2408
|
* @param config - The target config to send the event report to.
|
|
2340
2409
|
* @param data - The encoded report lines to send in the event report.
|
|
@@ -2344,27 +2413,27 @@ var CmcdReporter = class {
|
|
|
2344
2413
|
url: config.url,
|
|
2345
2414
|
method: "POST",
|
|
2346
2415
|
headers: { "Content-Type": CMCD_MIME_TYPE },
|
|
2347
|
-
body: data.join("\n")
|
|
2416
|
+
body: data.join("\n")
|
|
2348
2417
|
});
|
|
2349
2418
|
if (status === 410) this.disposeEventTarget(session, config);
|
|
2350
2419
|
else if (status === 429 || status > 499 && status < 600) throw new Error(`Event report failed with status ${status}`);
|
|
2351
2420
|
}
|
|
2352
2421
|
/**
|
|
2353
2422
|
* Cancels the time-interval timer for an event target config and clears the
|
|
2354
|
-
* stored id. Safe to call when no timer is
|
|
2355
|
-
*
|
|
2423
|
+
* stored id. Safe to call when no timer is active (clearInterval(undefined)
|
|
2424
|
+
* does nothing).
|
|
2356
2425
|
*/
|
|
2357
2426
|
disarmInterval(config) {
|
|
2358
2427
|
clearInterval(this.intervals.get(config));
|
|
2359
2428
|
this.intervals.delete(config);
|
|
2360
2429
|
}
|
|
2361
2430
|
/**
|
|
2362
|
-
* Silences an event target URL for the remainder of its session
|
|
2363
|
-
*
|
|
2364
|
-
*
|
|
2365
|
-
*
|
|
2366
|
-
*
|
|
2367
|
-
* the disposal
|
|
2431
|
+
* Silences an event target URL for the remainder of its session after the
|
|
2432
|
+
* collector signals that the target is gone (HTTP 410). The method clears
|
|
2433
|
+
* the queues and blocks further enqueues for every config in the session
|
|
2434
|
+
* that reports to the URL (the CTA-5004-B suppression scope). For the
|
|
2435
|
+
* current session, the method also cancels those configs' timers. A session
|
|
2436
|
+
* started after the disposal gets new target state and is unaffected.
|
|
2368
2437
|
*/
|
|
2369
2438
|
disposeEventTarget(session, config) {
|
|
2370
2439
|
session.eventTargets.forEach((target, sibling) => {
|
|
@@ -2517,7 +2586,7 @@ function isCmcdV2Data(data) {
|
|
|
2517
2586
|
* Convert a value to a CMCD value.
|
|
2518
2587
|
*
|
|
2519
2588
|
* @param value - The value to convert to a CMCD value.
|
|
2520
|
-
* @param params - The parameters
|
|
2589
|
+
* @param params - The parameters of the CMCD value.
|
|
2521
2590
|
* @returns The CMCD value.
|
|
2522
2591
|
*
|
|
2523
2592
|
* @public
|
|
@@ -2544,7 +2613,7 @@ function mergeValidationResults(...results) {
|
|
|
2544
2613
|
//#endregion
|
|
2545
2614
|
//#region src/resolveVersion.ts
|
|
2546
2615
|
/**
|
|
2547
|
-
* Resolves the CMCD version from
|
|
2616
|
+
* Resolves the CMCD version from options, the payload's `v` key, or the default (version 1).
|
|
2548
2617
|
*
|
|
2549
2618
|
* @internal
|
|
2550
2619
|
*/
|
|
@@ -2669,6 +2738,23 @@ function validateCmcdStructure(data, options) {
|
|
|
2669
2738
|
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2670
2739
|
});
|
|
2671
2740
|
}
|
|
2741
|
+
const ot = version > 1 ? toTokenString(data["ot"]) : void 0;
|
|
2742
|
+
if (ot !== void 0) for (const key in CMCD_KEY_OBJECT_TYPES) {
|
|
2743
|
+
const objectTypes = CMCD_KEY_OBJECT_TYPES[key];
|
|
2744
|
+
if (key in data && !objectTypes.includes(ot)) issues.push({
|
|
2745
|
+
key,
|
|
2746
|
+
message: `Key "${key}" must not be present when "ot" is "${ot}". Expected "ot" to be one of: ${objectTypes.join(", ")}.`,
|
|
2747
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2748
|
+
});
|
|
2749
|
+
}
|
|
2750
|
+
if (version > 1) for (const key in CMCD_AGGREGATE_BITRATE_KEYS) {
|
|
2751
|
+
const exactKey = CMCD_AGGREGATE_BITRATE_KEYS[key];
|
|
2752
|
+
if (key in data && exactKey in data) issues.push({
|
|
2753
|
+
key,
|
|
2754
|
+
message: `Key "${key}" must not be present when "${exactKey}" is present.`,
|
|
2755
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2756
|
+
});
|
|
2757
|
+
}
|
|
2672
2758
|
if ("v" in data && data["v"] !== 1 && data["v"] !== 2) issues.push({
|
|
2673
2759
|
key: "v",
|
|
2674
2760
|
message: `Unsupported CMCD version "${String(data["v"])}". Expected 1 or 2.`,
|
|
@@ -2735,8 +2821,8 @@ const CMCD_KEY_TYPE_STRING = "string";
|
|
|
2735
2821
|
*/
|
|
2736
2822
|
const CMCD_KEY_TYPE_TOKEN = "token";
|
|
2737
2823
|
/**
|
|
2738
|
-
* Maps each CMCD
|
|
2739
|
-
*
|
|
2824
|
+
* Maps each CMCD key to its value type for version 2.
|
|
2825
|
+
* CMCD_V1_KEY_TYPE_OVERRIDES handles the keys that differ between version 1 and version 2.
|
|
2740
2826
|
*
|
|
2741
2827
|
* @internal
|
|
2742
2828
|
*/
|
|
@@ -2794,7 +2880,7 @@ const CMCD_KEY_TYPES = {
|
|
|
2794
2880
|
sta: CMCD_KEY_TYPE_TOKEN
|
|
2795
2881
|
};
|
|
2796
2882
|
/**
|
|
2797
|
-
*
|
|
2883
|
+
* Version 1 types of the keys that differ from version 2.
|
|
2798
2884
|
*
|
|
2799
2885
|
* @internal
|
|
2800
2886
|
*/
|
|
@@ -3098,8 +3184,8 @@ function validateCmcd(data, options) {
|
|
|
3098
3184
|
*
|
|
3099
3185
|
* This function decodes the string internally and validates it with
|
|
3100
3186
|
* `reportingMode` set to `'event'`. The input may contain multiple
|
|
3101
|
-
* newline-separated events
|
|
3102
|
-
*
|
|
3187
|
+
* newline-separated events, for example an `application/cmcd` POST body.
|
|
3188
|
+
* The function validates each line independently and merges the results.
|
|
3103
3189
|
*
|
|
3104
3190
|
* @param cmcd - The raw CMCD-encoded string to validate. May contain
|
|
3105
3191
|
* multiple newline-separated event lines.
|
|
@@ -3127,6 +3213,11 @@ function validateCmcdEvents(cmcd, options) {
|
|
|
3127
3213
|
}],
|
|
3128
3214
|
data: []
|
|
3129
3215
|
};
|
|
3216
|
+
const bodyIssues = [];
|
|
3217
|
+
if (cmcd.endsWith("\n")) bodyIssues.push({
|
|
3218
|
+
message: "Event report body must not end with a line feed.",
|
|
3219
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
3220
|
+
});
|
|
3130
3221
|
const decodedLines = [];
|
|
3131
3222
|
const lineResults = [];
|
|
3132
3223
|
for (let i = 0; i < lines.length; i++) try {
|
|
@@ -3144,7 +3235,10 @@ function validateCmcdEvents(cmcd, options) {
|
|
|
3144
3235
|
});
|
|
3145
3236
|
}
|
|
3146
3237
|
return {
|
|
3147
|
-
...mergeValidationResults(
|
|
3238
|
+
...mergeValidationResults({
|
|
3239
|
+
valid: true,
|
|
3240
|
+
issues: bodyIssues
|
|
3241
|
+
}, ...lineResults),
|
|
3148
3242
|
data: decodedLines
|
|
3149
3243
|
};
|
|
3150
3244
|
}
|
|
@@ -3157,8 +3251,8 @@ function validateCmcdEvents(cmcd, options) {
|
|
|
3157
3251
|
* Accepts an {@link @svta/cml-utils!HttpRequest | HttpRequest} object.
|
|
3158
3252
|
*
|
|
3159
3253
|
* This function validates that the request uses the POST method and has
|
|
3160
|
-
* the correct `Content-Type` header (`application/cmcd`)
|
|
3161
|
-
*
|
|
3254
|
+
* the correct `Content-Type` header (`application/cmcd`). It also
|
|
3255
|
+
* validates the body content with {@link validateCmcdEvents}.
|
|
3162
3256
|
*
|
|
3163
3257
|
* @param request - An `HttpRequest` to validate.
|
|
3164
3258
|
* @param options - Validation options (excluding `reportingMode`).
|
|
@@ -3224,15 +3318,15 @@ function validateCmcdEventReport(request, options) {
|
|
|
3224
3318
|
/**
|
|
3225
3319
|
* Validates CMCD HTTP headers by checking shard placement and payload validity.
|
|
3226
3320
|
*
|
|
3227
|
-
* This function accepts raw CMCD header strings
|
|
3228
|
-
* internally
|
|
3229
|
-
* then merges all shards and runs
|
|
3230
|
-
* structure) on the merged data.
|
|
3321
|
+
* This function accepts raw CMCD header strings and decodes each shard
|
|
3322
|
+
* internally. It verifies that each key is in its correct header shard.
|
|
3323
|
+
* The function then merges all shards and runs payload validation (keys,
|
|
3324
|
+
* values, and structure) on the merged data.
|
|
3231
3325
|
*
|
|
3232
3326
|
* @example
|
|
3233
3327
|
* {@includeCode ../test/validateCmcdHeaders.test.ts#example}
|
|
3234
3328
|
*
|
|
3235
|
-
* @param headers - A `Headers` instance or a record of CMCD header fields to
|
|
3329
|
+
* @param headers - A `Headers` instance or a record of CMCD header fields to raw encoded strings.
|
|
3236
3330
|
* @param options - Validation options (excluding `reportingMode`).
|
|
3237
3331
|
* @returns The validation result including decoded data.
|
|
3238
3332
|
*
|
|
@@ -3292,10 +3386,12 @@ function validateCmcdHeaders(headers, options) {
|
|
|
3292
3386
|
* {@link https://developer.mozilla.org/en-US/docs/Web/API/Request | Request}
|
|
3293
3387
|
* object or an {@link @svta/cml-utils!HttpRequest | HttpRequest} object.
|
|
3294
3388
|
*
|
|
3295
|
-
* The function checks for CMCD data
|
|
3296
|
-
*
|
|
3297
|
-
*
|
|
3298
|
-
*
|
|
3389
|
+
* The function checks the HTTP headers for CMCD data first. If the
|
|
3390
|
+
* request has CMCD headers, {@link validateCmcdHeaders} validates them,
|
|
3391
|
+
* including shard placement. Otherwise, the function validates the CMCD
|
|
3392
|
+
* query parameter of the URL. A request with CMCD data in both the
|
|
3393
|
+
* headers and the `CMCD` query parameter is an error. The function still
|
|
3394
|
+
* validates the headers and returns their data.
|
|
3299
3395
|
*
|
|
3300
3396
|
* @param request - A `Request` or `HttpRequest` to validate.
|
|
3301
3397
|
* @param options - Validation options (excluding `reportingMode`).
|
|
@@ -3310,12 +3406,25 @@ function validateCmcdHeaders(headers, options) {
|
|
|
3310
3406
|
*/
|
|
3311
3407
|
function validateCmcdRequest(request, options) {
|
|
3312
3408
|
const headers = extractHeaderRecord(request.headers);
|
|
3313
|
-
|
|
3314
|
-
|
|
3409
|
+
const param = getCmcdQueryParam(request.url);
|
|
3410
|
+
if (headers) {
|
|
3411
|
+
const result = validateCmcdHeaders(headers, options);
|
|
3412
|
+
if (!param) return result;
|
|
3413
|
+
return {
|
|
3414
|
+
...mergeValidationResults({
|
|
3415
|
+
valid: false,
|
|
3416
|
+
issues: [{
|
|
3417
|
+
message: "CMCD data found in both request headers and the \"CMCD\" query parameter. A request must use only one transmission mode.",
|
|
3418
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
3419
|
+
}]
|
|
3420
|
+
}, result),
|
|
3421
|
+
data: result.data
|
|
3422
|
+
};
|
|
3423
|
+
}
|
|
3315
3424
|
if (!param) return {
|
|
3316
3425
|
valid: false,
|
|
3317
3426
|
issues: [{
|
|
3318
|
-
message: "No CMCD data found in request headers or query parameters.",
|
|
3427
|
+
message: hasCmcdInFragment(request.url) ? "No CMCD data found in request headers or query parameters. The URL fragment contains a \"CMCD\" parameter. A server never receives the fragment." : "No CMCD data found in request headers or query parameters.",
|
|
3319
3428
|
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
3320
3429
|
}],
|
|
3321
3430
|
data: {}
|
|
@@ -3341,6 +3450,18 @@ function validateCmcdRequest(request, options) {
|
|
|
3341
3450
|
data
|
|
3342
3451
|
};
|
|
3343
3452
|
}
|
|
3453
|
+
const CMCD_IN_FRAGMENT = /(?:^|[?&])CMCD=/;
|
|
3454
|
+
function getCmcdQueryParam(url) {
|
|
3455
|
+
const hash = url.indexOf("#");
|
|
3456
|
+
const path = hash < 0 ? url : url.slice(0, hash);
|
|
3457
|
+
const start = path.indexOf("?");
|
|
3458
|
+
if (start < 0) return null;
|
|
3459
|
+
return new URLSearchParams(path.slice(start + 1)).get(CMCD_PARAM);
|
|
3460
|
+
}
|
|
3461
|
+
function hasCmcdInFragment(url) {
|
|
3462
|
+
const hash = url.indexOf("#");
|
|
3463
|
+
return hash >= 0 && CMCD_IN_FRAGMENT.test(url.slice(hash + 1));
|
|
3464
|
+
}
|
|
3344
3465
|
function extractHeaderRecord(headers) {
|
|
3345
3466
|
if (!headers) return;
|
|
3346
3467
|
const h = ensureHeaders(headers);
|