@svta/cml-cmcd 2.6.0 → 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 +521 -374
- 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,32 +554,58 @@ 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
|
|
|
575
|
+
//#endregion
|
|
576
|
+
//#region src/toTokenString.ts
|
|
577
|
+
/**
|
|
578
|
+
* Resolve the text of a token value.
|
|
579
|
+
*
|
|
580
|
+
* A token may be a plain string, a registry `Symbol`, an `SfToken`, or an
|
|
581
|
+
* `SfItem` that wraps such a value.
|
|
582
|
+
*
|
|
583
|
+
* @param value - The value to resolve.
|
|
584
|
+
*
|
|
585
|
+
* @returns The token text, or `undefined` when the value is not a token.
|
|
586
|
+
*
|
|
587
|
+
* @internal
|
|
588
|
+
*/
|
|
589
|
+
function toTokenString(value) {
|
|
590
|
+
if (value instanceof SfItem) return toTokenString(value.value);
|
|
591
|
+
if (typeof value === "string") return value;
|
|
592
|
+
if (typeof value === "symbol" || value instanceof SfToken) return symbolToStr(value);
|
|
593
|
+
}
|
|
594
|
+
|
|
525
595
|
//#endregion
|
|
526
596
|
//#region src/prepareCmcdData.ts
|
|
527
597
|
const filterMap = {
|
|
528
|
-
|
|
529
|
-
|
|
598
|
+
event: isCmcdEventKey,
|
|
599
|
+
request: isCmcdRequestKey
|
|
530
600
|
};
|
|
531
601
|
/**
|
|
532
|
-
* Unwrap an inner list or SfItem value to a
|
|
602
|
+
* Unwrap an inner list or SfItem value to a scalar.
|
|
533
603
|
*/
|
|
534
604
|
function unwrapValue(value, ot) {
|
|
535
605
|
if (Array.isArray(value)) {
|
|
536
606
|
let item;
|
|
537
|
-
|
|
607
|
+
const otText = toTokenString(ot);
|
|
608
|
+
if (otText) item = value.find((item$1) => toTokenString(item$1.params?.ot) === otText);
|
|
538
609
|
if (!item) item = value[0];
|
|
539
610
|
return unwrapValue(item);
|
|
540
611
|
}
|
|
@@ -542,10 +613,10 @@ function unwrapValue(value, ot) {
|
|
|
542
613
|
return value;
|
|
543
614
|
}
|
|
544
615
|
/**
|
|
545
|
-
* Down-convert
|
|
616
|
+
* Down-convert version 2 CMCD data to version 1.
|
|
546
617
|
*
|
|
547
|
-
* - Extracts `nrr` from `nor` SfItem `r` parameter.
|
|
548
|
-
* - Unwraps inner-list values to
|
|
618
|
+
* - Extracts `nrr` from the `nor` SfItem's `r` parameter.
|
|
619
|
+
* - Unwraps inner-list values to scalars.
|
|
549
620
|
*/
|
|
550
621
|
function downConvertToV1(obj) {
|
|
551
622
|
const result = {};
|
|
@@ -565,6 +636,10 @@ function downConvertToV1(obj) {
|
|
|
565
636
|
}
|
|
566
637
|
return result;
|
|
567
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
|
+
}
|
|
568
643
|
/**
|
|
569
644
|
* Convert a generic object to CMCD data.
|
|
570
645
|
*
|
|
@@ -582,15 +657,15 @@ function prepareCmcdData(obj, options = {}) {
|
|
|
582
657
|
const version = options.version || obj["v"] || CMCD_V2;
|
|
583
658
|
const reportingMode = options.reportingMode || CMCD_REQUEST_MODE;
|
|
584
659
|
const data = version === 1 ? downConvertToV1(obj) : obj;
|
|
660
|
+
const eventType = toTokenString(data["e"]);
|
|
585
661
|
const keyFilter = version === 1 ? isCmcdV1Key : filterMap[reportingMode];
|
|
586
662
|
let keys = Object.keys(data).filter(keyFilter);
|
|
587
|
-
if (data["e"] &&
|
|
663
|
+
if (data["e"] && eventType !== CMCD_EVENT_RESPONSE_RECEIVED) keys = keys.filter((key) => !isCmcdResponseReceivedKey(key));
|
|
588
664
|
const filter = options.filter;
|
|
589
665
|
if (typeof filter === "function") keys = keys.filter(filter);
|
|
590
666
|
const isEventMode = reportingMode === CMCD_EVENT_MODE;
|
|
591
667
|
if (isEventMode) {
|
|
592
|
-
|
|
593
|
-
if (!keys.includes("e") && eventType != null) keys.push("e");
|
|
668
|
+
if (!keys.includes("e") && data["e"] != null) keys.push("e");
|
|
594
669
|
if (!keys.includes("ts")) keys.push("ts");
|
|
595
670
|
if (!keys.includes("cen") && data["cen"] != null && eventType === CMCD_EVENT_CUSTOM_EVENT) keys.push("cen");
|
|
596
671
|
if (!keys.includes("ec") && data["ec"] != null && eventType === CMCD_EVENT_ERROR) keys.push("ec");
|
|
@@ -606,17 +681,33 @@ function prepareCmcdData(obj, options = {}) {
|
|
|
606
681
|
baseUrl: options.baseUrl
|
|
607
682
|
};
|
|
608
683
|
keys.sort();
|
|
684
|
+
let objectType;
|
|
685
|
+
let objectTypeResolved = false;
|
|
609
686
|
for (const key of keys) {
|
|
610
687
|
let value = data[key];
|
|
611
|
-
const
|
|
612
|
-
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);
|
|
613
701
|
if (key === "v") if (version === 1) continue;
|
|
614
702
|
else value = version;
|
|
615
|
-
if (key === "pr" && value === 1 && !(isEventMode &&
|
|
703
|
+
if (key === "pr" && value === 1 && !(isEventMode && eventType === CMCD_EVENT_PLAYBACK_RATE)) continue;
|
|
616
704
|
if (isEventMode && key === "ts" && !Number.isFinite(value)) value = Date.now();
|
|
617
|
-
const isBgFalseTransition = isEventMode && value === false && key === "bg" &&
|
|
705
|
+
const isBgFalseTransition = isEventMode && value === false && key === "bg" && eventType === CMCD_EVENT_BACKGROUNDED_MODE;
|
|
618
706
|
if (!isValid(value) && !isBgFalseTransition) continue;
|
|
619
|
-
if (isTokenField(key)
|
|
707
|
+
if (isTokenField(key)) {
|
|
708
|
+
if (typeof value === "string") value = new SfToken(value);
|
|
709
|
+
else if (value instanceof SfItem && typeof value.value === "string") value = new SfItem(new SfToken(value.value), value.params);
|
|
710
|
+
}
|
|
620
711
|
results[key] = value;
|
|
621
712
|
}
|
|
622
713
|
return results;
|
|
@@ -679,9 +770,9 @@ const CMCD_HEADER_FIELDS = [
|
|
|
679
770
|
/**
|
|
680
771
|
* The map of CMCD keys to their appropriate header shard.
|
|
681
772
|
*
|
|
682
|
-
*
|
|
683
|
-
*
|
|
684
|
-
*
|
|
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.
|
|
685
776
|
*
|
|
686
777
|
* @public
|
|
687
778
|
*/
|
|
@@ -736,9 +827,9 @@ function createHeaderMap(headerMap) {
|
|
|
736
827
|
}, {});
|
|
737
828
|
}
|
|
738
829
|
/**
|
|
739
|
-
* Group a CMCD
|
|
830
|
+
* Group a CMCD object into header shards
|
|
740
831
|
*
|
|
741
|
-
* @param cmcd - The CMCD
|
|
832
|
+
* @param cmcd - The CMCD object to convert.
|
|
742
833
|
* @param customHeaderMap - A map of CMCD header fields to custom CMCD keys.
|
|
743
834
|
*
|
|
744
835
|
* @returns The CMCD header shards.
|
|
@@ -784,10 +875,10 @@ function toPreparedCmcdHeaders(data, customHeaderMap) {
|
|
|
784
875
|
//#endregion
|
|
785
876
|
//#region src/toCmcdHeaders.ts
|
|
786
877
|
/**
|
|
787
|
-
* Convert a CMCD
|
|
878
|
+
* Convert a CMCD object to request headers
|
|
788
879
|
*
|
|
789
|
-
* @param cmcd - The CMCD
|
|
790
|
-
* @param options - Options for encoding
|
|
880
|
+
* @param cmcd - The CMCD object to convert.
|
|
881
|
+
* @param options - Options for encoding.
|
|
791
882
|
*
|
|
792
883
|
* @returns The CMCD header shards.
|
|
793
884
|
*
|
|
@@ -806,13 +897,13 @@ function toCmcdHeaders(cmcd, options = {}) {
|
|
|
806
897
|
//#endregion
|
|
807
898
|
//#region src/appendCmcdHeaders.ts
|
|
808
899
|
/**
|
|
809
|
-
* Append CMCD
|
|
900
|
+
* Append CMCD header shards to a header object.
|
|
810
901
|
*
|
|
811
902
|
* @param headers - The headers to append to.
|
|
812
903
|
* @param cmcd - The CMCD object to append.
|
|
813
|
-
* @param options -
|
|
904
|
+
* @param options - Options for encoding.
|
|
814
905
|
*
|
|
815
|
-
* @returns The headers with
|
|
906
|
+
* @returns The headers with CMCD header shards appended.
|
|
816
907
|
*
|
|
817
908
|
* @public
|
|
818
909
|
*
|
|
@@ -873,10 +964,10 @@ function encodeCmcd(cmcd, options = {}) {
|
|
|
873
964
|
//#endregion
|
|
874
965
|
//#region src/toCmcdUrl.ts
|
|
875
966
|
/**
|
|
876
|
-
* Convert a CMCD
|
|
967
|
+
* Convert a CMCD object to a URL encoded string.
|
|
877
968
|
*
|
|
878
969
|
* @param cmcd - The CMCD object to convert.
|
|
879
|
-
* @param options - Options for encoding
|
|
970
|
+
* @param options - Options for encoding.
|
|
880
971
|
*
|
|
881
972
|
* @returns The URL encoded CMCD data.
|
|
882
973
|
*
|
|
@@ -893,10 +984,10 @@ function toCmcdUrl(cmcd, options = {}) {
|
|
|
893
984
|
//#endregion
|
|
894
985
|
//#region src/toCmcdQuery.ts
|
|
895
986
|
/**
|
|
896
|
-
* Convert a CMCD
|
|
987
|
+
* Convert a CMCD object to a query arg.
|
|
897
988
|
*
|
|
898
989
|
* @param cmcd - The CMCD object to convert.
|
|
899
|
-
* @param options - Options for encoding
|
|
990
|
+
* @param options - Options for encoding.
|
|
900
991
|
*
|
|
901
992
|
* @returns The CMCD query arg.
|
|
902
993
|
*
|
|
@@ -920,7 +1011,7 @@ const REGEX = /CMCD=[^&#]+/;
|
|
|
920
1011
|
*
|
|
921
1012
|
* @param url - The URL to append to.
|
|
922
1013
|
* @param cmcd - The CMCD object to append.
|
|
923
|
-
* @param options - Options for encoding
|
|
1014
|
+
* @param options - Options for encoding.
|
|
924
1015
|
*
|
|
925
1016
|
* @returns The URL with the CMCD query args appended.
|
|
926
1017
|
*
|
|
@@ -941,7 +1032,7 @@ function appendCmcdQuery(url, cmcd, options) {
|
|
|
941
1032
|
//#endregion
|
|
942
1033
|
//#region src/CMCD_DEFAULT_TIME_INTERVAL.ts
|
|
943
1034
|
/**
|
|
944
|
-
* The default time interval in seconds when using
|
|
1035
|
+
* The default time interval in seconds when using event mode
|
|
945
1036
|
*
|
|
946
1037
|
* @public
|
|
947
1038
|
*/
|
|
@@ -973,25 +1064,26 @@ const CMCD_MIME_TYPE = "application/cmcd";
|
|
|
973
1064
|
//#endregion
|
|
974
1065
|
//#region src/CMCD_REQUEST_PROVENANCE.ts
|
|
975
1066
|
/**
|
|
976
|
-
* The `customData`
|
|
977
|
-
*
|
|
1067
|
+
* The `customData` property where `CmcdReporter.createRequestReport()`
|
|
1068
|
+
* writes session provenance on every request it returns.
|
|
978
1069
|
*
|
|
979
|
-
* The value is a frozen `CmcdRequestProvenance` record
|
|
980
|
-
* session that issued the request
|
|
981
|
-
*
|
|
982
|
-
*
|
|
983
|
-
*
|
|
984
|
-
*
|
|
985
|
-
* Spread and `Object.assign`
|
|
986
|
-
*
|
|
987
|
-
*
|
|
988
|
-
*
|
|
989
|
-
*
|
|
990
|
-
*
|
|
991
|
-
*
|
|
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.
|
|
992
1083
|
*
|
|
993
|
-
*
|
|
994
|
-
* 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.
|
|
995
1087
|
*
|
|
996
1088
|
* @public
|
|
997
1089
|
*/
|
|
@@ -1032,7 +1124,7 @@ const CmcdObjectType = {
|
|
|
1032
1124
|
//#endregion
|
|
1033
1125
|
//#region src/CmcdPlayerState.ts
|
|
1034
1126
|
/**
|
|
1035
|
-
* CMCD
|
|
1127
|
+
* CMCD version 2 player states for the 'sta' key.
|
|
1036
1128
|
*
|
|
1037
1129
|
*
|
|
1038
1130
|
* @enum
|
|
@@ -1057,29 +1149,29 @@ const CmcdPlayerState = {
|
|
|
1057
1149
|
//#endregion
|
|
1058
1150
|
//#region src/CmcdRecordedReportMode.ts
|
|
1059
1151
|
/**
|
|
1060
|
-
* CMCD query reporting mode
|
|
1152
|
+
* CMCD query reporting mode: CMCD data transmitted as a `CMCD=` URL
|
|
1061
1153
|
* query parameter.
|
|
1062
1154
|
*
|
|
1063
1155
|
* @public
|
|
1064
1156
|
*/
|
|
1065
1157
|
const CMCD_RECORDED_REPORT_MODE_QUERY = "query";
|
|
1066
1158
|
/**
|
|
1067
|
-
* CMCD header reporting mode
|
|
1159
|
+
* CMCD header reporting mode: CMCD data transmitted as `CMCD-*` HTTP
|
|
1068
1160
|
* headers.
|
|
1069
1161
|
*
|
|
1070
1162
|
* @public
|
|
1071
1163
|
*/
|
|
1072
1164
|
const CMCD_RECORDED_REPORT_MODE_HEADER = "header";
|
|
1073
1165
|
/**
|
|
1074
|
-
* CMCD event reporting mode
|
|
1166
|
+
* CMCD event reporting mode: CMCD data transmitted as a POST body to an
|
|
1075
1167
|
* event-target URL.
|
|
1076
1168
|
*
|
|
1077
1169
|
* @public
|
|
1078
1170
|
*/
|
|
1079
1171
|
const CMCD_RECORDED_REPORT_MODE_EVENT = "event";
|
|
1080
1172
|
/**
|
|
1081
|
-
* Reporting mode
|
|
1082
|
-
*
|
|
1173
|
+
* Reporting mode in which `CmcdReportRecorder` observed a captured
|
|
1174
|
+
* report.
|
|
1083
1175
|
*
|
|
1084
1176
|
* @enum
|
|
1085
1177
|
*
|
|
@@ -1113,7 +1205,7 @@ const CMCD_RECORDED_REQUEST_TYPE_SEGMENT = "segment";
|
|
|
1113
1205
|
*/
|
|
1114
1206
|
const CMCD_RECORDED_REQUEST_TYPE_EVENT = "event";
|
|
1115
1207
|
/**
|
|
1116
|
-
*
|
|
1208
|
+
* Default classification for requests the recorder did not recognize
|
|
1117
1209
|
* as manifest, segment, or event.
|
|
1118
1210
|
*
|
|
1119
1211
|
* @public
|
|
@@ -1155,17 +1247,17 @@ async function toHttpRequest(request) {
|
|
|
1155
1247
|
};
|
|
1156
1248
|
}
|
|
1157
1249
|
/**
|
|
1158
|
-
* Create a transport adapter
|
|
1159
|
-
*
|
|
1160
|
-
*
|
|
1161
|
-
*
|
|
1162
|
-
*
|
|
1163
|
-
*
|
|
1164
|
-
* safe for the body types CMCD reports use
|
|
1165
|
-
* `Blob`, `ArrayBuffer`, `FormData`,
|
|
1166
|
-
*
|
|
1167
|
-
* a `ReadableStream` as `init.body
|
|
1168
|
-
*
|
|
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
|
|
1169
1261
|
* already-disturbed stream.
|
|
1170
1262
|
*
|
|
1171
1263
|
* @public
|
|
@@ -1231,9 +1323,8 @@ async function completeXhrWith(xhr, response) {
|
|
|
1231
1323
|
});
|
|
1232
1324
|
}
|
|
1233
1325
|
/**
|
|
1234
|
-
* Create a transport adapter
|
|
1235
|
-
*
|
|
1236
|
-
* `CmcdReportRecorder`.
|
|
1326
|
+
* Create a transport adapter for `CmcdReportRecorder` that patches
|
|
1327
|
+
* `XMLHttpRequest.prototype` to capture requests with CMCD data.
|
|
1237
1328
|
*
|
|
1238
1329
|
* @public
|
|
1239
1330
|
*/
|
|
@@ -1296,10 +1387,10 @@ function detectReportingMode(url, headers, isEventTarget) {
|
|
|
1296
1387
|
return url.includes(`${CMCD_PARAM}=`) ? "query" : null;
|
|
1297
1388
|
}
|
|
1298
1389
|
/**
|
|
1299
|
-
* Test helper that records CMCD
|
|
1300
|
-
* transports for
|
|
1301
|
-
*
|
|
1302
|
-
* 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.
|
|
1303
1394
|
*
|
|
1304
1395
|
* @example
|
|
1305
1396
|
* {@includeCode ../test/CmcdReportRecorder.test.ts#example}
|
|
@@ -1367,11 +1458,11 @@ var CmcdReportRecorder = class {
|
|
|
1367
1458
|
});
|
|
1368
1459
|
}
|
|
1369
1460
|
/**
|
|
1370
|
-
* Install transport patches and
|
|
1371
|
-
*
|
|
1372
|
-
*
|
|
1373
|
-
*
|
|
1374
|
-
*
|
|
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.
|
|
1375
1466
|
*
|
|
1376
1467
|
* @public
|
|
1377
1468
|
*/
|
|
@@ -1412,7 +1503,7 @@ var CmcdReportRecorder = class {
|
|
|
1412
1503
|
this.#reports = [];
|
|
1413
1504
|
}
|
|
1414
1505
|
/**
|
|
1415
|
-
* Return a
|
|
1506
|
+
* Return a copy of all recorded reports.
|
|
1416
1507
|
*
|
|
1417
1508
|
* @public
|
|
1418
1509
|
*/
|
|
@@ -1421,8 +1512,8 @@ var CmcdReportRecorder = class {
|
|
|
1421
1512
|
}
|
|
1422
1513
|
/**
|
|
1423
1514
|
* Wait until at least `count` reports of any type are recorded.
|
|
1424
|
-
* Resolves with all matching reports
|
|
1425
|
-
* 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
|
|
1426
1517
|
* recorder's `waitTimeout` option (default 15000 ms).
|
|
1427
1518
|
*
|
|
1428
1519
|
* @public
|
|
@@ -1432,8 +1523,8 @@ var CmcdReportRecorder = class {
|
|
|
1432
1523
|
}
|
|
1433
1524
|
/**
|
|
1434
1525
|
* Wait until at least `count` manifest reports are recorded.
|
|
1435
|
-
* Resolves with all matching reports
|
|
1436
|
-
* 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
|
|
1437
1528
|
* recorder's `waitTimeout` option (default 15000 ms).
|
|
1438
1529
|
*
|
|
1439
1530
|
* @public
|
|
@@ -1443,8 +1534,8 @@ var CmcdReportRecorder = class {
|
|
|
1443
1534
|
}
|
|
1444
1535
|
/**
|
|
1445
1536
|
* Wait until at least `count` segment reports are recorded.
|
|
1446
|
-
* Resolves with all matching reports
|
|
1447
|
-
* 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
|
|
1448
1539
|
* recorder's `waitTimeout` option (default 15000 ms).
|
|
1449
1540
|
*
|
|
1450
1541
|
* @public
|
|
@@ -1454,8 +1545,8 @@ var CmcdReportRecorder = class {
|
|
|
1454
1545
|
}
|
|
1455
1546
|
/**
|
|
1456
1547
|
* Wait until at least `count` event-mode reports are recorded.
|
|
1457
|
-
* Resolves with all matching reports
|
|
1458
|
-
* 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
|
|
1459
1550
|
* recorder's `waitTimeout` option (default 15000 ms).
|
|
1460
1551
|
*
|
|
1461
1552
|
* @public
|
|
@@ -1503,12 +1594,12 @@ const CmcdTransmissionMode = {
|
|
|
1503
1594
|
//#endregion
|
|
1504
1595
|
//#region src/upConvertToV2.ts
|
|
1505
1596
|
/**
|
|
1506
|
-
* Up-convert
|
|
1597
|
+
* Up-convert version 1 CMCD data to version 2.
|
|
1507
1598
|
*
|
|
1508
|
-
* - Wraps
|
|
1599
|
+
* - Wraps scalar values in arrays for inner-list keys.
|
|
1509
1600
|
* - Wraps `nor` string in an array.
|
|
1510
1601
|
*
|
|
1511
|
-
*
|
|
1602
|
+
* The function returns version 2 data (has `v: 2`) unchanged.
|
|
1512
1603
|
*
|
|
1513
1604
|
* @internal
|
|
1514
1605
|
*/
|
|
@@ -1557,12 +1648,11 @@ function createEncodingOptions(reportingMode, config, baseUrl) {
|
|
|
1557
1648
|
};
|
|
1558
1649
|
}
|
|
1559
1650
|
/**
|
|
1560
|
-
* Deep equality for CmcdObjectTypeList (used for `br`
|
|
1651
|
+
* Deep equality for CmcdObjectTypeList (used for `br` deduplication).
|
|
1561
1652
|
*
|
|
1562
|
-
*
|
|
1563
|
-
*
|
|
1564
|
-
*
|
|
1565
|
-
* 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.
|
|
1566
1656
|
*/
|
|
1567
1657
|
function cmcdObjectTypeListEqual(a, b) {
|
|
1568
1658
|
if (a === b) return true;
|
|
@@ -1603,26 +1693,30 @@ const STATE_FIELDS = /* @__PURE__ */ Array.from(CMCD_STATE_EVENT_FIELDS, ([event
|
|
|
1603
1693
|
};
|
|
1604
1694
|
});
|
|
1605
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
|
+
}
|
|
1606
1704
|
/**
|
|
1607
1705
|
* Maps each event type to the key CTA-5004-B requires beyond `e` and `ts`.
|
|
1608
1706
|
* Built from the state-change table plus the three event types whose
|
|
1609
|
-
* required key
|
|
1707
|
+
* required key is event data, not player state.
|
|
1610
1708
|
*/
|
|
1611
|
-
const CMCD_REQUIRED_EVENT_KEYS = /* @__PURE__ */
|
|
1612
|
-
...CMCD_STATE_EVENT_FIELDS,
|
|
1613
|
-
[CMCD_EVENT_CUSTOM_EVENT, "cen"],
|
|
1614
|
-
[CMCD_EVENT_ERROR, "ec"],
|
|
1615
|
-
[CMCD_EVENT_RESPONSE_RECEIVED, "url"]
|
|
1616
|
-
]);
|
|
1709
|
+
const CMCD_REQUIRED_EVENT_KEYS = /* @__PURE__ */ buildRequiredEventKeys();
|
|
1617
1710
|
/**
|
|
1618
1711
|
* Whether a required key's value will survive report preparation.
|
|
1619
1712
|
*
|
|
1620
|
-
* This is `isValid`
|
|
1621
|
-
*
|
|
1622
|
-
*
|
|
1623
|
-
*
|
|
1624
|
-
*
|
|
1625
|
-
*
|
|
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.
|
|
1626
1720
|
*/
|
|
1627
1721
|
function isUsableRequiredValue(value) {
|
|
1628
1722
|
if (value == null || value === "") return false;
|
|
@@ -1630,12 +1724,12 @@ function isUsableRequiredValue(value) {
|
|
|
1630
1724
|
return !Array.isArray(value) || value.length > 0;
|
|
1631
1725
|
}
|
|
1632
1726
|
/**
|
|
1633
|
-
* Copies
|
|
1727
|
+
* Copies a value with the `SfItem` structure, including its `params` record.
|
|
1634
1728
|
*
|
|
1635
|
-
* The
|
|
1729
|
+
* The copy keeps the prototype because `prepareCmcdData`, the formatter map,
|
|
1636
1730
|
* validation, and the structured-field encoder all branch on
|
|
1637
1731
|
* `instanceof SfItem`. A plain spread (and `structuredClone`) would return a
|
|
1638
|
-
* prototype-less object and silently change
|
|
1732
|
+
* prototype-less object and silently change the encoded output.
|
|
1639
1733
|
*/
|
|
1640
1734
|
function copyItemValue(value) {
|
|
1641
1735
|
if (value === null || typeof value !== "object") return value;
|
|
@@ -1644,16 +1738,16 @@ function copyItemValue(value) {
|
|
|
1644
1738
|
return copy;
|
|
1645
1739
|
}
|
|
1646
1740
|
/**
|
|
1647
|
-
* Copies the nested values of a report in place
|
|
1648
|
-
* the reporter's
|
|
1649
|
-
*
|
|
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.
|
|
1650
1744
|
*
|
|
1651
|
-
*
|
|
1745
|
+
* The copy is complete for the CMCD value space. `CmcdValue` and
|
|
1652
1746
|
* `CmcdCustomValue` admit only primitives, `SfItem<primitive>`, and arrays of
|
|
1653
|
-
* those
|
|
1654
|
-
* configured, on the ended session's
|
|
1655
|
-
*
|
|
1656
|
-
*
|
|
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.
|
|
1657
1751
|
*/
|
|
1658
1752
|
function copyReportValues(data) {
|
|
1659
1753
|
const record = data;
|
|
@@ -1668,14 +1762,13 @@ function copyReportValues(data) {
|
|
|
1668
1762
|
return data;
|
|
1669
1763
|
}
|
|
1670
1764
|
/**
|
|
1671
|
-
* Decodes the per-call data snapshot a provenance record
|
|
1672
|
-
*
|
|
1673
|
-
*
|
|
1674
|
-
*
|
|
1675
|
-
*
|
|
1676
|
-
*
|
|
1677
|
-
*
|
|
1678
|
-
* 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.
|
|
1679
1772
|
*/
|
|
1680
1773
|
function decodeSnapshot(provenance) {
|
|
1681
1774
|
const encoded = provenance !== null && typeof provenance === "object" ? provenance.data : void 0;
|
|
@@ -1687,10 +1780,10 @@ function decodeSnapshot(provenance) {
|
|
|
1687
1780
|
}
|
|
1688
1781
|
}
|
|
1689
1782
|
/**
|
|
1690
|
-
*
|
|
1691
|
-
* the `cid`
|
|
1692
|
-
* change
|
|
1693
|
-
*
|
|
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.
|
|
1694
1787
|
*/
|
|
1695
1788
|
function mintProvenance(sid, cid) {
|
|
1696
1789
|
return Object.freeze(typeof cid === "string" && cid ? {
|
|
@@ -1729,12 +1822,12 @@ function createCmcdReporterConfig(config) {
|
|
|
1729
1822
|
/**
|
|
1730
1823
|
* The CMCD reporter.
|
|
1731
1824
|
*
|
|
1732
|
-
* `C` describes the player's own `customData`, which the reporter passes
|
|
1733
|
-
*
|
|
1734
|
-
*
|
|
1735
|
-
*
|
|
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`.
|
|
1736
1829
|
*
|
|
1737
|
-
* @typeParam C - The
|
|
1830
|
+
* @typeParam C - The type of the player's `customData`. Defaults to
|
|
1738
1831
|
* `Record<string, unknown>`.
|
|
1739
1832
|
*
|
|
1740
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}
|
|
@@ -1746,7 +1839,7 @@ var CmcdReporter = class {
|
|
|
1746
1839
|
* Creates a new CMCD reporter.
|
|
1747
1840
|
*
|
|
1748
1841
|
* @param config - The configuration for the CMCD reporter.
|
|
1749
|
-
* @param requester - The function
|
|
1842
|
+
* @param requester - The function that sends the request.
|
|
1750
1843
|
* The default is a simple wrapper around the
|
|
1751
1844
|
* native `fetch` API.
|
|
1752
1845
|
*/
|
|
@@ -1764,8 +1857,8 @@ var CmcdReporter = class {
|
|
|
1764
1857
|
this.requester = requester;
|
|
1765
1858
|
}
|
|
1766
1859
|
/**
|
|
1767
|
-
* Creates the state for a new session:
|
|
1768
|
-
*
|
|
1860
|
+
* Creates the state for a new session: initial counters, flags, queues, and
|
|
1861
|
+
* deduplication baseline for every configured target.
|
|
1769
1862
|
*/
|
|
1770
1863
|
createSession(sid, data) {
|
|
1771
1864
|
const eventTargets = /* @__PURE__ */ new Map();
|
|
@@ -1789,11 +1882,12 @@ var CmcdReporter = class {
|
|
|
1789
1882
|
};
|
|
1790
1883
|
}
|
|
1791
1884
|
/**
|
|
1792
|
-
* Starts the CMCD reporter.
|
|
1885
|
+
* Starts the CMCD reporter. The player calls this method when it enables the
|
|
1886
|
+
* reporter.
|
|
1793
1887
|
*
|
|
1794
|
-
*
|
|
1795
|
-
* before the first interval elapses.
|
|
1796
|
-
*
|
|
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().
|
|
1797
1891
|
*/
|
|
1798
1892
|
start() {
|
|
1799
1893
|
this.started = true;
|
|
@@ -1812,9 +1906,9 @@ var CmcdReporter = class {
|
|
|
1812
1906
|
if (failure) throw failure.error;
|
|
1813
1907
|
}
|
|
1814
1908
|
/**
|
|
1815
|
-
*
|
|
1816
|
-
*
|
|
1817
|
-
* 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.
|
|
1818
1912
|
*/
|
|
1819
1913
|
armInterval(config) {
|
|
1820
1914
|
if (config.interval === 0 || !config.events.includes(CMCD_EVENT_TIME_INTERVAL)) return false;
|
|
@@ -1829,7 +1923,8 @@ var CmcdReporter = class {
|
|
|
1829
1923
|
return true;
|
|
1830
1924
|
}
|
|
1831
1925
|
/**
|
|
1832
|
-
* Stops the CMCD reporter.
|
|
1926
|
+
* Stops the CMCD reporter. The player calls this method when it disables the
|
|
1927
|
+
* reporter.
|
|
1833
1928
|
*
|
|
1834
1929
|
* @param flush - Whether to flush the event targets.
|
|
1835
1930
|
*/
|
|
@@ -1839,8 +1934,8 @@ var CmcdReporter = class {
|
|
|
1839
1934
|
for (const config of this.config.eventTargets) this.disarmInterval(config);
|
|
1840
1935
|
}
|
|
1841
1936
|
/**
|
|
1842
|
-
*
|
|
1843
|
-
*
|
|
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
|
|
1844
1939
|
* session ends.
|
|
1845
1940
|
*/
|
|
1846
1941
|
flush() {
|
|
@@ -1849,40 +1944,38 @@ var CmcdReporter = class {
|
|
|
1849
1944
|
/**
|
|
1850
1945
|
* Updates the CMCD data.
|
|
1851
1946
|
*
|
|
1852
|
-
*
|
|
1853
|
-
*
|
|
1854
|
-
*
|
|
1855
|
-
*
|
|
1856
|
-
*
|
|
1857
|
-
*
|
|
1858
|
-
*
|
|
1859
|
-
* 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.
|
|
1860
1954
|
*
|
|
1861
|
-
* Multi-field updates fire
|
|
1862
|
-
* `
|
|
1863
|
-
*
|
|
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.
|
|
1864
1958
|
*
|
|
1865
|
-
*
|
|
1866
|
-
*
|
|
1867
|
-
*
|
|
1868
|
-
*
|
|
1869
|
-
*
|
|
1870
|
-
* the
|
|
1871
|
-
*
|
|
1872
|
-
*
|
|
1873
|
-
* 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.
|
|
1874
1967
|
*
|
|
1875
|
-
* A `sid` change resets the
|
|
1968
|
+
* A `sid` change resets the deduplication baseline.
|
|
1876
1969
|
*
|
|
1877
|
-
* `sid` and `msd` are session-owned
|
|
1878
|
-
*
|
|
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
|
|
1879
1972
|
* to change them. A per-call value on {@link CmcdReporter.recordEvent},
|
|
1880
1973
|
* {@link CmcdReporter.createRequestReport}, or
|
|
1881
|
-
* {@link CmcdReporter.recordResponseReceived} has no effect
|
|
1882
|
-
* attribution
|
|
1974
|
+
* {@link CmcdReporter.recordResponseReceived} has no effect. Response
|
|
1975
|
+
* attribution uses the provenance record alone.
|
|
1883
1976
|
* `msd` must be a finite number of milliseconds between `0` and
|
|
1884
|
-
* `999_999_999_999_999` (the RFC 8941 integer maximum)
|
|
1885
|
-
* 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.
|
|
1886
1979
|
*
|
|
1887
1980
|
* @param data - The data to update.
|
|
1888
1981
|
*/
|
|
@@ -1904,19 +1997,20 @@ var CmcdReporter = class {
|
|
|
1904
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);
|
|
1905
1998
|
}
|
|
1906
1999
|
/**
|
|
1907
|
-
* Starts a new session
|
|
1908
|
-
*
|
|
1909
|
-
* 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.
|
|
1910
2004
|
*
|
|
1911
|
-
* The
|
|
1912
|
-
*
|
|
1913
|
-
*
|
|
1914
|
-
*
|
|
1915
|
-
*
|
|
1916
|
-
*
|
|
1917
|
-
*
|
|
1918
|
-
*
|
|
1919
|
-
*
|
|
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.
|
|
1920
2014
|
*/
|
|
1921
2015
|
startSession(sid) {
|
|
1922
2016
|
this.session.data = copyReportValues({ ...this.session.data });
|
|
@@ -1933,59 +2027,57 @@ var CmcdReporter = class {
|
|
|
1933
2027
|
}
|
|
1934
2028
|
}
|
|
1935
2029
|
/**
|
|
1936
|
-
* Records an event.
|
|
2030
|
+
* Records an event. The player calls this method when an event occurs.
|
|
1937
2031
|
*
|
|
1938
2032
|
* For state-change events (`PLAY_STATE`, `PLAYBACK_RATE`, `CONTENT_ID`,
|
|
1939
2033
|
* `BACKGROUNDED_MODE`, `BITRATE_CHANGE`), this method:
|
|
1940
|
-
* 1.
|
|
1941
|
-
*
|
|
1942
|
-
* 2.
|
|
1943
|
-
*
|
|
1944
|
-
*
|
|
1945
|
-
* 3. Suppresses the event if the field's current value
|
|
1946
|
-
* 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).
|
|
1947
2041
|
*
|
|
1948
|
-
*
|
|
2042
|
+
* The reporter always records all other event types.
|
|
1949
2043
|
*
|
|
1950
|
-
* For state-change events, prefer {@link CmcdReporter.update}
|
|
1951
|
-
*
|
|
1952
|
-
* `update({ sta: 'p', bl: [3000], mtp: [8500] })`.
|
|
1953
|
-
*
|
|
1954
|
-
*
|
|
1955
|
-
*
|
|
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.
|
|
1956
2050
|
*
|
|
1957
|
-
*
|
|
1958
|
-
*
|
|
1959
|
-
*
|
|
1960
|
-
* `
|
|
1961
|
-
*
|
|
1962
|
-
* 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.
|
|
1963
2056
|
*
|
|
1964
2057
|
* @param type - The type of event to record.
|
|
1965
|
-
* @param data - Additional data to record with the event. This data
|
|
1966
|
-
* only
|
|
1967
|
-
*
|
|
1968
|
-
*
|
|
1969
|
-
* (`sid`, `msd`) supplied here
|
|
1970
|
-
* 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.
|
|
1971
2063
|
*/
|
|
1972
2064
|
recordEvent(type, data = {}) {
|
|
1973
2065
|
this.emitEvent(this.session, type, data);
|
|
1974
2066
|
}
|
|
1975
2067
|
/**
|
|
1976
|
-
* Records an event
|
|
1977
|
-
*
|
|
2068
|
+
* Records an event for every configured target of a session. State-change
|
|
2069
|
+
* deduplication runs once, before the per-target loop.
|
|
1978
2070
|
*
|
|
1979
2071
|
* @param session - The session the event belongs to. Everything the event
|
|
1980
|
-
* touches (data store,
|
|
1981
|
-
* is that session's
|
|
1982
|
-
* passes an
|
|
2072
|
+
* touches (data store, deduplication baseline, counters,
|
|
2073
|
+
* queues) is that session's. Only `recordResponseReceived()`
|
|
2074
|
+
* ever passes an ended session.
|
|
1983
2075
|
* @param type - The type of event to record.
|
|
1984
2076
|
* @param data - Additional data to record with the event.
|
|
1985
2077
|
* @param request - The media request that triggered the event, when
|
|
1986
|
-
* one exists. Only `recordResponseReceived()`
|
|
1987
|
-
*
|
|
1988
|
-
*
|
|
2078
|
+
* one exists. Only `recordResponseReceived()` sets this
|
|
2079
|
+
* parameter, which the reporter passes to each target's
|
|
2080
|
+
* `transform`.
|
|
1989
2081
|
*/
|
|
1990
2082
|
emitEvent(session, type, data, request) {
|
|
1991
2083
|
const entry = STATE_FIELDS_BY_EVENT.get(type);
|
|
@@ -2010,16 +2102,17 @@ var CmcdReporter = class {
|
|
|
2010
2102
|
if (failure) throw failure.error;
|
|
2011
2103
|
}
|
|
2012
2104
|
/**
|
|
2013
|
-
* Records an event for a target.
|
|
2105
|
+
* Records an event for a target. The reporter calls this method when an
|
|
2106
|
+
* event occurs.
|
|
2014
2107
|
*
|
|
2015
2108
|
* @param target - The target to record the event for.
|
|
2016
2109
|
* @param config - The configuration for the target.
|
|
2017
2110
|
* @param type - The type of event to record.
|
|
2018
2111
|
* @param data - Additional data to record with the event. This data
|
|
2019
|
-
* only applies to this event report.
|
|
2020
|
-
*
|
|
2112
|
+
* only applies to this event report. Use `update()` for
|
|
2113
|
+
* persistent data.
|
|
2021
2114
|
* @param request - The media request that triggered the event, when
|
|
2022
|
-
* one exists.
|
|
2115
|
+
* one exists. The target's `transform` receives it.
|
|
2023
2116
|
*/
|
|
2024
2117
|
recordTargetEvent(session, target, config, type, data = {}, request) {
|
|
2025
2118
|
if (target.disposed || !config.events.includes(type)) return;
|
|
@@ -2046,15 +2139,14 @@ var CmcdReporter = class {
|
|
|
2046
2139
|
this.queueTargetEvent(session, target, config, report, type);
|
|
2047
2140
|
}
|
|
2048
2141
|
/**
|
|
2049
|
-
*
|
|
2050
|
-
* 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.
|
|
2051
2144
|
*
|
|
2052
|
-
*
|
|
2053
|
-
* target's `events` filter
|
|
2054
|
-
* the session identity
|
|
2055
|
-
*
|
|
2056
|
-
*
|
|
2057
|
-
* 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.
|
|
2058
2150
|
*
|
|
2059
2151
|
* @param target - The target to queue the report for.
|
|
2060
2152
|
* @param config - The configuration for the target.
|
|
@@ -2072,10 +2164,10 @@ var CmcdReporter = class {
|
|
|
2072
2164
|
target.queue.push(encodeCmcd(report, createEncodingOptions(CMCD_EVENT_MODE, config)));
|
|
2073
2165
|
}
|
|
2074
2166
|
/**
|
|
2075
|
-
* Records a response-received event.
|
|
2076
|
-
* request response has
|
|
2167
|
+
* Records a response-received event. The player calls this method when a
|
|
2168
|
+
* media request response has fully arrived.
|
|
2077
2169
|
*
|
|
2078
|
-
* This method
|
|
2170
|
+
* This method derives these `rr` event keys automatically:
|
|
2079
2171
|
*
|
|
2080
2172
|
* - `url` - the original requested URL (before any redirects)
|
|
2081
2173
|
* - `rc` - the HTTP response status code
|
|
@@ -2083,50 +2175,50 @@ var CmcdReporter = class {
|
|
|
2083
2175
|
* - `ttfb` - time to first byte (from `resourceTiming.responseStart`)
|
|
2084
2176
|
* - `ttlb` - time to last byte (from `resourceTiming.duration`)
|
|
2085
2177
|
*
|
|
2086
|
-
*
|
|
2087
|
-
*
|
|
2178
|
+
* If the player has additional keys like `ttfbb`, `cmsdd`, `cmsds`, and
|
|
2179
|
+
* `smrt`, pass them in the `data` parameter.
|
|
2088
2180
|
*
|
|
2089
|
-
* The request's `customData` is generic, so a player can pass a request
|
|
2090
|
-
*
|
|
2091
|
-
*
|
|
2092
|
-
* {@link CmcdReporter.createRequestReport} writes there
|
|
2093
|
-
*
|
|
2094
|
-
*
|
|
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.
|
|
2095
2187
|
*
|
|
2096
|
-
* A reporter
|
|
2097
|
-
*
|
|
2098
|
-
*
|
|
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}.
|
|
2099
2191
|
*
|
|
2100
|
-
* The event
|
|
2101
|
-
*
|
|
2192
|
+
* The reporter attributes the event only to the session that issued the
|
|
2193
|
+
* request. The provenance record that
|
|
2102
2194
|
* {@link CmcdReporter.createRequestReport} stored on the request's
|
|
2103
|
-
* `customData` (under {@link CMCD_REQUEST_PROVENANCE}) selects the
|
|
2104
|
-
*
|
|
2105
|
-
*
|
|
2106
|
-
*
|
|
2107
|
-
* {@link CMCD_REQUEST_PROVENANCE})
|
|
2108
|
-
*
|
|
2109
|
-
* `
|
|
2110
|
-
*
|
|
2111
|
-
*
|
|
2112
|
-
* A response that completes after a `sid`
|
|
2113
|
-
* retained session, with that session's data
|
|
2114
|
-
* 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.
|
|
2115
2207
|
*
|
|
2116
|
-
* Request-time report keys come from the record's encoded per-call
|
|
2117
|
-
*
|
|
2118
|
-
*
|
|
2119
|
-
* completes after a mid-session content change
|
|
2120
|
-
*
|
|
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.
|
|
2121
2213
|
*
|
|
2122
|
-
* @typeParam RD - The `customData` this request
|
|
2214
|
+
* @typeParam RD - The `customData` of this request. Defaults to the
|
|
2123
2215
|
* reporter's own `C`.
|
|
2124
2216
|
*
|
|
2125
2217
|
* @param response - The HTTP response received.
|
|
2126
2218
|
* @param data - Additional CMCD data to include with the event.
|
|
2127
|
-
* Values provided here override
|
|
2128
|
-
*
|
|
2129
|
-
*
|
|
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.
|
|
2130
2222
|
*/
|
|
2131
2223
|
recordResponseReceived(response, data = {}) {
|
|
2132
2224
|
const { request } = response;
|
|
@@ -2159,13 +2251,13 @@ var CmcdReporter = class {
|
|
|
2159
2251
|
}, request);
|
|
2160
2252
|
}
|
|
2161
2253
|
/**
|
|
2162
|
-
* Resolves the session a response belongs to
|
|
2163
|
-
*
|
|
2164
|
-
*
|
|
2165
|
-
* names
|
|
2166
|
-
*
|
|
2167
|
-
*
|
|
2168
|
-
*
|
|
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.
|
|
2169
2261
|
*/
|
|
2170
2262
|
resolveSession(provenance) {
|
|
2171
2263
|
if (provenance === null || typeof provenance !== "object") return;
|
|
@@ -2173,8 +2265,8 @@ var CmcdReporter = class {
|
|
|
2173
2265
|
return typeof sid === "string" ? this.sessions.get(sid) : void 0;
|
|
2174
2266
|
}
|
|
2175
2267
|
/**
|
|
2176
|
-
* Applies the CMCD request report data to the request.
|
|
2177
|
-
* before sending the request.
|
|
2268
|
+
* Applies the CMCD request report data to the request. The player calls this
|
|
2269
|
+
* method before sending the request.
|
|
2178
2270
|
*
|
|
2179
2271
|
* @param req - The request to apply the CMCD request report to.
|
|
2180
2272
|
* @returns The request with the CMCD request report applied.
|
|
@@ -2185,23 +2277,22 @@ var CmcdReporter = class {
|
|
|
2185
2277
|
return this.createRequestReport(req) ?? req;
|
|
2186
2278
|
}
|
|
2187
2279
|
/**
|
|
2188
|
-
* Checks
|
|
2280
|
+
* Checks whether request reporting is enabled.
|
|
2189
2281
|
*
|
|
2190
|
-
* @returns `true` if
|
|
2282
|
+
* @returns `true` if request reporting is enabled, `false` otherwise.
|
|
2191
2283
|
*/
|
|
2192
2284
|
isRequestReportingEnabled() {
|
|
2193
2285
|
return !!this.config.enabledKeys?.length;
|
|
2194
2286
|
}
|
|
2195
2287
|
/**
|
|
2196
|
-
* The provenance record to
|
|
2197
|
-
* frozen base record, extended with the caller's per-call data encoded
|
|
2198
|
-
*
|
|
2199
|
-
*
|
|
2200
|
-
*
|
|
2201
|
-
*
|
|
2202
|
-
*
|
|
2203
|
-
*
|
|
2204
|
-
* 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.
|
|
2205
2296
|
*/
|
|
2206
2297
|
createRequestProvenance(data, baseUrl) {
|
|
2207
2298
|
const base = this.session.provenance;
|
|
@@ -2221,21 +2312,21 @@ var CmcdReporter = class {
|
|
|
2221
2312
|
}
|
|
2222
2313
|
}
|
|
2223
2314
|
/**
|
|
2224
|
-
* Creates a new request with the CMCD request report data applied.
|
|
2225
|
-
* 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.
|
|
2226
2317
|
*
|
|
2227
|
-
* A reporter
|
|
2228
|
-
* satisfy
|
|
2229
|
-
*
|
|
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
|
|
2230
2321
|
* {@link CmcdReporterCustomData}.
|
|
2231
2322
|
*
|
|
2232
|
-
* @typeParam R - The
|
|
2323
|
+
* @typeParam R - The type of the request. Its `customData` must satisfy
|
|
2233
2324
|
* the reporter's `C`.
|
|
2234
2325
|
*
|
|
2235
2326
|
* @param request - The request to apply the CMCD request report to.
|
|
2236
2327
|
* @param data - The data to apply to the request. This data only
|
|
2237
|
-
* applies to this request report.
|
|
2238
|
-
*
|
|
2328
|
+
* applies to this request report. Use `update()` for
|
|
2329
|
+
* persistent data.
|
|
2239
2330
|
* @returns The request with the CMCD request report applied.
|
|
2240
2331
|
*/
|
|
2241
2332
|
createRequestReport(request, data) {
|
|
@@ -2287,7 +2378,8 @@ var CmcdReporter = class {
|
|
|
2287
2378
|
return report;
|
|
2288
2379
|
}
|
|
2289
2380
|
/**
|
|
2290
|
-
* Processes the event targets.
|
|
2381
|
+
* Processes the event targets. The reporter calls this method when an event
|
|
2382
|
+
* occurs.
|
|
2291
2383
|
*
|
|
2292
2384
|
* @param flush - Whether to flush the event targets.
|
|
2293
2385
|
*/
|
|
@@ -2310,7 +2402,8 @@ var CmcdReporter = class {
|
|
|
2310
2402
|
if (reprocess) this.processEventTargets();
|
|
2311
2403
|
}
|
|
2312
2404
|
/**
|
|
2313
|
-
* Sends an event report.
|
|
2405
|
+
* Sends an event report. The reporter calls this method when a batch is
|
|
2406
|
+
* ready.
|
|
2314
2407
|
*
|
|
2315
2408
|
* @param config - The target config to send the event report to.
|
|
2316
2409
|
* @param data - The encoded report lines to send in the event report.
|
|
@@ -2320,27 +2413,27 @@ var CmcdReporter = class {
|
|
|
2320
2413
|
url: config.url,
|
|
2321
2414
|
method: "POST",
|
|
2322
2415
|
headers: { "Content-Type": CMCD_MIME_TYPE },
|
|
2323
|
-
body: data.join("\n")
|
|
2416
|
+
body: data.join("\n")
|
|
2324
2417
|
});
|
|
2325
2418
|
if (status === 410) this.disposeEventTarget(session, config);
|
|
2326
2419
|
else if (status === 429 || status > 499 && status < 600) throw new Error(`Event report failed with status ${status}`);
|
|
2327
2420
|
}
|
|
2328
2421
|
/**
|
|
2329
2422
|
* Cancels the time-interval timer for an event target config and clears the
|
|
2330
|
-
* stored id. Safe to call when no timer is
|
|
2331
|
-
*
|
|
2423
|
+
* stored id. Safe to call when no timer is active (clearInterval(undefined)
|
|
2424
|
+
* does nothing).
|
|
2332
2425
|
*/
|
|
2333
2426
|
disarmInterval(config) {
|
|
2334
2427
|
clearInterval(this.intervals.get(config));
|
|
2335
2428
|
this.intervals.delete(config);
|
|
2336
2429
|
}
|
|
2337
2430
|
/**
|
|
2338
|
-
* Silences an event target URL for the remainder of its session
|
|
2339
|
-
*
|
|
2340
|
-
*
|
|
2341
|
-
*
|
|
2342
|
-
*
|
|
2343
|
-
* 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.
|
|
2344
2437
|
*/
|
|
2345
2438
|
disposeEventTarget(session, config) {
|
|
2346
2439
|
session.eventTargets.forEach((target, sibling) => {
|
|
@@ -2493,7 +2586,7 @@ function isCmcdV2Data(data) {
|
|
|
2493
2586
|
* Convert a value to a CMCD value.
|
|
2494
2587
|
*
|
|
2495
2588
|
* @param value - The value to convert to a CMCD value.
|
|
2496
|
-
* @param params - The parameters
|
|
2589
|
+
* @param params - The parameters of the CMCD value.
|
|
2497
2590
|
* @returns The CMCD value.
|
|
2498
2591
|
*
|
|
2499
2592
|
* @public
|
|
@@ -2520,7 +2613,7 @@ function mergeValidationResults(...results) {
|
|
|
2520
2613
|
//#endregion
|
|
2521
2614
|
//#region src/resolveVersion.ts
|
|
2522
2615
|
/**
|
|
2523
|
-
* Resolves the CMCD version from
|
|
2616
|
+
* Resolves the CMCD version from options, the payload's `v` key, or the default (version 1).
|
|
2524
2617
|
*
|
|
2525
2618
|
* @internal
|
|
2526
2619
|
*/
|
|
@@ -2611,7 +2704,8 @@ function validateCmcdStructure(data, options) {
|
|
|
2611
2704
|
});
|
|
2612
2705
|
}
|
|
2613
2706
|
if ("e" in data) {
|
|
2614
|
-
|
|
2707
|
+
const eventType = toTokenString(data["e"]);
|
|
2708
|
+
if (eventType === CMCD_EVENT_CUSTOM_EVENT) {
|
|
2615
2709
|
if (!("cen" in data)) issues.push({
|
|
2616
2710
|
key: "cen",
|
|
2617
2711
|
message: "Custom event (e=\"ce\") requires the \"cen\" key to be present.",
|
|
@@ -2622,7 +2716,7 @@ function validateCmcdStructure(data, options) {
|
|
|
2622
2716
|
message: "The \"cen\" key must only be present when e=\"ce\".",
|
|
2623
2717
|
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2624
2718
|
});
|
|
2625
|
-
if (
|
|
2719
|
+
if (eventType === CMCD_EVENT_RESPONSE_RECEIVED) {
|
|
2626
2720
|
if (!("url" in data)) issues.push({
|
|
2627
2721
|
key: "url",
|
|
2628
2722
|
message: "Response received event (e=\"rr\") requires the \"url\" key to be present.",
|
|
@@ -2633,17 +2727,34 @@ function validateCmcdStructure(data, options) {
|
|
|
2633
2727
|
message: `Response key "${key}" must only be present when e="rr".`,
|
|
2634
2728
|
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2635
2729
|
});
|
|
2636
|
-
for (const [
|
|
2730
|
+
for (const [stateEventType, requiredField] of CMCD_STATE_EVENT_FIELDS) if (eventType === stateEventType && !(requiredField in data)) issues.push({
|
|
2637
2731
|
key: requiredField,
|
|
2638
|
-
message: `State-change event (e="${
|
|
2732
|
+
message: `State-change event (e="${stateEventType}") requires the "${requiredField}" key to be present.`,
|
|
2639
2733
|
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2640
2734
|
});
|
|
2641
|
-
if (
|
|
2735
|
+
if (eventType === CMCD_EVENT_ERROR && !("ec" in data)) issues.push({
|
|
2642
2736
|
key: "ec",
|
|
2643
2737
|
message: "Error event (e=\"e\") requires the \"ec\" key to be present.",
|
|
2644
2738
|
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2645
2739
|
});
|
|
2646
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
|
+
}
|
|
2647
2758
|
if ("v" in data && data["v"] !== 1 && data["v"] !== 2) issues.push({
|
|
2648
2759
|
key: "v",
|
|
2649
2760
|
message: `Unsupported CMCD version "${String(data["v"])}". Expected 1 or 2.`,
|
|
@@ -2710,8 +2821,8 @@ const CMCD_KEY_TYPE_STRING = "string";
|
|
|
2710
2821
|
*/
|
|
2711
2822
|
const CMCD_KEY_TYPE_TOKEN = "token";
|
|
2712
2823
|
/**
|
|
2713
|
-
* Maps each CMCD
|
|
2714
|
-
*
|
|
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.
|
|
2715
2826
|
*
|
|
2716
2827
|
* @internal
|
|
2717
2828
|
*/
|
|
@@ -2769,7 +2880,7 @@ const CMCD_KEY_TYPES = {
|
|
|
2769
2880
|
sta: CMCD_KEY_TYPE_TOKEN
|
|
2770
2881
|
};
|
|
2771
2882
|
/**
|
|
2772
|
-
*
|
|
2883
|
+
* Version 1 types of the keys that differ from version 2.
|
|
2773
2884
|
*
|
|
2774
2885
|
* @internal
|
|
2775
2886
|
*/
|
|
@@ -3024,9 +3135,10 @@ function validateCmcdValues(data, options) {
|
|
|
3024
3135
|
break;
|
|
3025
3136
|
case CMCD_KEY_TYPE_TOKEN: {
|
|
3026
3137
|
const validValues = CMCD_TOKEN_VALUES[key];
|
|
3027
|
-
|
|
3138
|
+
const token = toTokenString(value);
|
|
3139
|
+
if (validValues && (token === void 0 || !validValues.includes(token))) issues.push({
|
|
3028
3140
|
key,
|
|
3029
|
-
message: `Key "${key}" has invalid token value "${String(value)}". Expected one of: ${validValues.join(", ")}.`,
|
|
3141
|
+
message: `Key "${key}" has invalid token value "${token ?? String(value)}". Expected one of: ${validValues.join(", ")}.`,
|
|
3030
3142
|
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
3031
3143
|
});
|
|
3032
3144
|
break;
|
|
@@ -3072,8 +3184,8 @@ function validateCmcd(data, options) {
|
|
|
3072
3184
|
*
|
|
3073
3185
|
* This function decodes the string internally and validates it with
|
|
3074
3186
|
* `reportingMode` set to `'event'`. The input may contain multiple
|
|
3075
|
-
* newline-separated events
|
|
3076
|
-
*
|
|
3187
|
+
* newline-separated events, for example an `application/cmcd` POST body.
|
|
3188
|
+
* The function validates each line independently and merges the results.
|
|
3077
3189
|
*
|
|
3078
3190
|
* @param cmcd - The raw CMCD-encoded string to validate. May contain
|
|
3079
3191
|
* multiple newline-separated event lines.
|
|
@@ -3101,6 +3213,11 @@ function validateCmcdEvents(cmcd, options) {
|
|
|
3101
3213
|
}],
|
|
3102
3214
|
data: []
|
|
3103
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
|
+
});
|
|
3104
3221
|
const decodedLines = [];
|
|
3105
3222
|
const lineResults = [];
|
|
3106
3223
|
for (let i = 0; i < lines.length; i++) try {
|
|
@@ -3118,7 +3235,10 @@ function validateCmcdEvents(cmcd, options) {
|
|
|
3118
3235
|
});
|
|
3119
3236
|
}
|
|
3120
3237
|
return {
|
|
3121
|
-
...mergeValidationResults(
|
|
3238
|
+
...mergeValidationResults({
|
|
3239
|
+
valid: true,
|
|
3240
|
+
issues: bodyIssues
|
|
3241
|
+
}, ...lineResults),
|
|
3122
3242
|
data: decodedLines
|
|
3123
3243
|
};
|
|
3124
3244
|
}
|
|
@@ -3131,8 +3251,8 @@ function validateCmcdEvents(cmcd, options) {
|
|
|
3131
3251
|
* Accepts an {@link @svta/cml-utils!HttpRequest | HttpRequest} object.
|
|
3132
3252
|
*
|
|
3133
3253
|
* This function validates that the request uses the POST method and has
|
|
3134
|
-
* the correct `Content-Type` header (`application/cmcd`)
|
|
3135
|
-
*
|
|
3254
|
+
* the correct `Content-Type` header (`application/cmcd`). It also
|
|
3255
|
+
* validates the body content with {@link validateCmcdEvents}.
|
|
3136
3256
|
*
|
|
3137
3257
|
* @param request - An `HttpRequest` to validate.
|
|
3138
3258
|
* @param options - Validation options (excluding `reportingMode`).
|
|
@@ -3198,15 +3318,15 @@ function validateCmcdEventReport(request, options) {
|
|
|
3198
3318
|
/**
|
|
3199
3319
|
* Validates CMCD HTTP headers by checking shard placement and payload validity.
|
|
3200
3320
|
*
|
|
3201
|
-
* This function accepts raw CMCD header strings
|
|
3202
|
-
* internally
|
|
3203
|
-
* then merges all shards and runs
|
|
3204
|
-
* 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.
|
|
3205
3325
|
*
|
|
3206
3326
|
* @example
|
|
3207
3327
|
* {@includeCode ../test/validateCmcdHeaders.test.ts#example}
|
|
3208
3328
|
*
|
|
3209
|
-
* @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.
|
|
3210
3330
|
* @param options - Validation options (excluding `reportingMode`).
|
|
3211
3331
|
* @returns The validation result including decoded data.
|
|
3212
3332
|
*
|
|
@@ -3266,10 +3386,12 @@ function validateCmcdHeaders(headers, options) {
|
|
|
3266
3386
|
* {@link https://developer.mozilla.org/en-US/docs/Web/API/Request | Request}
|
|
3267
3387
|
* object or an {@link @svta/cml-utils!HttpRequest | HttpRequest} object.
|
|
3268
3388
|
*
|
|
3269
|
-
* The function checks for CMCD data
|
|
3270
|
-
*
|
|
3271
|
-
*
|
|
3272
|
-
*
|
|
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.
|
|
3273
3395
|
*
|
|
3274
3396
|
* @param request - A `Request` or `HttpRequest` to validate.
|
|
3275
3397
|
* @param options - Validation options (excluding `reportingMode`).
|
|
@@ -3284,12 +3406,25 @@ function validateCmcdHeaders(headers, options) {
|
|
|
3284
3406
|
*/
|
|
3285
3407
|
function validateCmcdRequest(request, options) {
|
|
3286
3408
|
const headers = extractHeaderRecord(request.headers);
|
|
3287
|
-
|
|
3288
|
-
|
|
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
|
+
}
|
|
3289
3424
|
if (!param) return {
|
|
3290
3425
|
valid: false,
|
|
3291
3426
|
issues: [{
|
|
3292
|
-
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.",
|
|
3293
3428
|
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
3294
3429
|
}],
|
|
3295
3430
|
data: {}
|
|
@@ -3315,6 +3450,18 @@ function validateCmcdRequest(request, options) {
|
|
|
3315
3450
|
data
|
|
3316
3451
|
};
|
|
3317
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
|
+
}
|
|
3318
3465
|
function extractHeaderRecord(headers) {
|
|
3319
3466
|
if (!headers) return;
|
|
3320
3467
|
const h = ensureHeaders(headers);
|