@svta/cml-cmcd 2.1.2 → 2.3.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 +574 -25
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1066 -134
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -62,11 +62,11 @@ const CMCD_V2 = 2;
|
|
|
62
62
|
//#endregion
|
|
63
63
|
//#region src/CmcdEventType.ts
|
|
64
64
|
/**
|
|
65
|
-
* CMCD event type for the '
|
|
65
|
+
* CMCD event type for the 'bc' key (bitrate change).
|
|
66
66
|
*
|
|
67
67
|
* @public
|
|
68
68
|
*/
|
|
69
|
-
const CMCD_EVENT_BITRATE_CHANGE = "
|
|
69
|
+
const CMCD_EVENT_BITRATE_CHANGE = "bc";
|
|
70
70
|
/**
|
|
71
71
|
* CMCD event type for the 'ps' key (play state change).
|
|
72
72
|
*
|
|
@@ -166,9 +166,10 @@ const CMCD_EVENT_CUSTOM_EVENT = "ce";
|
|
|
166
166
|
/**
|
|
167
167
|
* CMCD event types for the 'e' key (event mode).
|
|
168
168
|
*
|
|
169
|
-
*
|
|
170
169
|
* @enum
|
|
171
170
|
*
|
|
171
|
+
* @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#event | CTA-5004-A Event}
|
|
172
|
+
*
|
|
172
173
|
* @public
|
|
173
174
|
*/
|
|
174
175
|
const CmcdEventType = {
|
|
@@ -297,6 +298,7 @@ function isCmcdCustomKey(key) {
|
|
|
297
298
|
|
|
298
299
|
//#endregion
|
|
299
300
|
//#region src/isCmcdRequestKey.ts
|
|
301
|
+
const CMCD_REQUEST_KEY_SET = new Set(CMCD_REQUEST_KEYS);
|
|
300
302
|
/**
|
|
301
303
|
* Check if a key is a valid CMCD request key.
|
|
302
304
|
*
|
|
@@ -310,7 +312,7 @@ function isCmcdCustomKey(key) {
|
|
|
310
312
|
* {@includeCode ../test/isCmcdRequestKey.test.ts#example}
|
|
311
313
|
*/
|
|
312
314
|
function isCmcdRequestKey(key) {
|
|
313
|
-
return
|
|
315
|
+
return CMCD_REQUEST_KEY_SET.has(key) || isCmcdCustomKey(key);
|
|
314
316
|
}
|
|
315
317
|
|
|
316
318
|
//#endregion
|
|
@@ -333,6 +335,7 @@ const CMCD_RESPONSE_KEYS = [
|
|
|
333
335
|
|
|
334
336
|
//#endregion
|
|
335
337
|
//#region src/isCmcdResponseReceivedKey.ts
|
|
338
|
+
const CMCD_RESPONSE_KEY_SET = new Set(CMCD_RESPONSE_KEYS);
|
|
336
339
|
/**
|
|
337
340
|
* Check if a key is a valid CMCD response key.
|
|
338
341
|
*
|
|
@@ -346,11 +349,12 @@ const CMCD_RESPONSE_KEYS = [
|
|
|
346
349
|
* {@includeCode ../test/isCmcdResponseReceivedKey.test.ts#example}
|
|
347
350
|
*/
|
|
348
351
|
function isCmcdResponseReceivedKey(key) {
|
|
349
|
-
return
|
|
352
|
+
return CMCD_RESPONSE_KEY_SET.has(key);
|
|
350
353
|
}
|
|
351
354
|
|
|
352
355
|
//#endregion
|
|
353
356
|
//#region src/isCmcdEventKey.ts
|
|
357
|
+
const CMCD_EVENT_KEY_SET = new Set(CMCD_EVENT_KEYS);
|
|
354
358
|
/**
|
|
355
359
|
* Check if a key is a valid CMCD event key.
|
|
356
360
|
*
|
|
@@ -364,9 +368,35 @@ function isCmcdResponseReceivedKey(key) {
|
|
|
364
368
|
* {@includeCode ../test/isCmcdEventKey.test.ts#example}
|
|
365
369
|
*/
|
|
366
370
|
function isCmcdEventKey(key) {
|
|
367
|
-
return isCmcdRequestKey(key) || isCmcdResponseReceivedKey(key) ||
|
|
371
|
+
return isCmcdRequestKey(key) || isCmcdResponseReceivedKey(key) || CMCD_EVENT_KEY_SET.has(key);
|
|
368
372
|
}
|
|
369
373
|
|
|
374
|
+
//#endregion
|
|
375
|
+
//#region src/CMCD_INNER_LIST_KEYS.ts
|
|
376
|
+
/**
|
|
377
|
+
* Keys that are inner lists in V2 but plain scalars in V1.
|
|
378
|
+
*
|
|
379
|
+
* Used by both encoding (down-conversion) and decoding (up-conversion).
|
|
380
|
+
*
|
|
381
|
+
* @internal
|
|
382
|
+
*/
|
|
383
|
+
const CMCD_INNER_LIST_KEYS = new Set([
|
|
384
|
+
"ab",
|
|
385
|
+
"bl",
|
|
386
|
+
"br",
|
|
387
|
+
"bsa",
|
|
388
|
+
"bsd",
|
|
389
|
+
"bsda",
|
|
390
|
+
"lab",
|
|
391
|
+
"lb",
|
|
392
|
+
"mtp",
|
|
393
|
+
"pb",
|
|
394
|
+
"tab",
|
|
395
|
+
"tb",
|
|
396
|
+
"tbl",
|
|
397
|
+
"tpb"
|
|
398
|
+
]);
|
|
399
|
+
|
|
370
400
|
//#endregion
|
|
371
401
|
//#region src/CMCD_V1_KEYS.ts
|
|
372
402
|
/**
|
|
@@ -397,6 +427,7 @@ const CMCD_V1_KEYS = [
|
|
|
397
427
|
|
|
398
428
|
//#endregion
|
|
399
429
|
//#region src/isCmcdV1Key.ts
|
|
430
|
+
const CMCD_V1_KEY_SET$1 = new Set(CMCD_V1_KEYS);
|
|
400
431
|
/**
|
|
401
432
|
* Filter function for CMCD v1 keys.
|
|
402
433
|
*
|
|
@@ -410,11 +441,18 @@ const CMCD_V1_KEYS = [
|
|
|
410
441
|
* {@includeCode ../test/isCmcdV1Key.test.ts#example}
|
|
411
442
|
*/
|
|
412
443
|
function isCmcdV1Key(key) {
|
|
413
|
-
return
|
|
444
|
+
return CMCD_V1_KEY_SET$1.has(key) || isCmcdCustomKey(key);
|
|
414
445
|
}
|
|
415
446
|
|
|
416
447
|
//#endregion
|
|
417
448
|
//#region src/isTokenField.ts
|
|
449
|
+
const TOKEN_FIELDS = new Set([
|
|
450
|
+
"ot",
|
|
451
|
+
"sf",
|
|
452
|
+
"st",
|
|
453
|
+
"e",
|
|
454
|
+
"sta"
|
|
455
|
+
]);
|
|
418
456
|
/**
|
|
419
457
|
* Checks if the given key is a token field.
|
|
420
458
|
*
|
|
@@ -425,13 +463,7 @@ function isCmcdV1Key(key) {
|
|
|
425
463
|
* @internal
|
|
426
464
|
*/
|
|
427
465
|
function isTokenField(key) {
|
|
428
|
-
return
|
|
429
|
-
"ot",
|
|
430
|
-
"sf",
|
|
431
|
-
"st",
|
|
432
|
-
"e",
|
|
433
|
-
"sta"
|
|
434
|
-
].includes(key);
|
|
466
|
+
return TOKEN_FIELDS.has(key);
|
|
435
467
|
}
|
|
436
468
|
|
|
437
469
|
//#endregion
|
|
@@ -457,25 +489,6 @@ const filterMap = {
|
|
|
457
489
|
[CMCD_REQUEST_MODE]: isCmcdRequestKey
|
|
458
490
|
};
|
|
459
491
|
/**
|
|
460
|
-
* V1 keys that use inner lists in V2 but plain numbers in V1.
|
|
461
|
-
*/
|
|
462
|
-
const INNER_LIST_V1_KEYS = new Set([
|
|
463
|
-
"ab",
|
|
464
|
-
"bl",
|
|
465
|
-
"br",
|
|
466
|
-
"bsa",
|
|
467
|
-
"bsd",
|
|
468
|
-
"bsda",
|
|
469
|
-
"lab",
|
|
470
|
-
"lb",
|
|
471
|
-
"mtp",
|
|
472
|
-
"pb",
|
|
473
|
-
"tab",
|
|
474
|
-
"tb",
|
|
475
|
-
"tbl",
|
|
476
|
-
"tpb"
|
|
477
|
-
]);
|
|
478
|
-
/**
|
|
479
492
|
* Unwrap an inner list or SfItem value to a plain scalar.
|
|
480
493
|
*/
|
|
481
494
|
function unwrapValue(value, ot) {
|
|
@@ -507,7 +520,7 @@ function downConvertToV1(obj) {
|
|
|
507
520
|
result["nor"] = first.value;
|
|
508
521
|
if (first.params?.r) result["nrr"] = first.params.r;
|
|
509
522
|
} else result["nor"] = first;
|
|
510
|
-
} else if (
|
|
523
|
+
} else if (CMCD_INNER_LIST_KEYS.has(key)) result[key] = unwrapValue(value, obj["ot"]);
|
|
511
524
|
else result[key] = value;
|
|
512
525
|
}
|
|
513
526
|
return result;
|
|
@@ -527,37 +540,37 @@ function prepareCmcdData(obj, options = {}) {
|
|
|
527
540
|
const reportingMode = options.reportingMode || CMCD_REQUEST_MODE;
|
|
528
541
|
const data = version === 1 ? downConvertToV1(obj) : obj;
|
|
529
542
|
const keyFilter = version === 1 ? isCmcdV1Key : filterMap[reportingMode];
|
|
530
|
-
let keys
|
|
531
|
-
if (data["e"] && data["e"] !== CMCD_EVENT_RESPONSE_RECEIVED) keys
|
|
543
|
+
let keys = Object.keys(data).filter(keyFilter);
|
|
544
|
+
if (data["e"] && data["e"] !== CMCD_EVENT_RESPONSE_RECEIVED) keys = keys.filter((key) => !isCmcdResponseReceivedKey(key));
|
|
532
545
|
const filter = options.filter;
|
|
533
|
-
if (typeof filter === "function") keys
|
|
546
|
+
if (typeof filter === "function") keys = keys.filter(filter);
|
|
534
547
|
const isEventMode = reportingMode === CMCD_EVENT_MODE;
|
|
535
548
|
if (isEventMode) {
|
|
536
549
|
const eventType = data["e"];
|
|
537
|
-
if (!keys
|
|
538
|
-
if (!keys
|
|
539
|
-
if (!keys
|
|
550
|
+
if (!keys.includes("e") && eventType != null) keys.push("e");
|
|
551
|
+
if (!keys.includes("ts")) keys.push("ts");
|
|
552
|
+
if (!keys.includes("cen") && data["cen"] != null && eventType === CMCD_EVENT_CUSTOM_EVENT) keys.push("cen");
|
|
540
553
|
}
|
|
541
|
-
if (keys
|
|
542
|
-
if (version > 1 && !keys
|
|
543
|
-
const formatters = Object.assign({}, CMCD_FORMATTER_MAP, options.formatters);
|
|
554
|
+
if (keys.length === 0) return results;
|
|
555
|
+
if (version > 1 && !keys.includes("v")) keys.push("v");
|
|
544
556
|
const formatterOptions = {
|
|
545
557
|
version,
|
|
546
558
|
reportingMode,
|
|
547
559
|
baseUrl: options.baseUrl
|
|
548
560
|
};
|
|
549
|
-
keys
|
|
561
|
+
keys.sort();
|
|
562
|
+
for (const key of keys) {
|
|
550
563
|
let value = data[key];
|
|
551
|
-
const formatter = formatters[key];
|
|
564
|
+
const formatter = options.formatters?.[key] ?? CMCD_FORMATTER_MAP[key];
|
|
552
565
|
if (typeof formatter === "function") value = formatter(value, formatterOptions);
|
|
553
|
-
if (key === "v") if (version === 1)
|
|
566
|
+
if (key === "v") if (version === 1) continue;
|
|
554
567
|
else value = version;
|
|
555
|
-
if (key
|
|
568
|
+
if (key === "pr" && value === 1) continue;
|
|
556
569
|
if (isEventMode && key === "ts" && !Number.isFinite(value)) value = Date.now();
|
|
557
|
-
if (!isValid(value))
|
|
570
|
+
if (!isValid(value)) continue;
|
|
558
571
|
if (isTokenField(key) && typeof value === "string") value = new SfToken(value);
|
|
559
572
|
results[key] = value;
|
|
560
|
-
}
|
|
573
|
+
}
|
|
561
574
|
return results;
|
|
562
575
|
}
|
|
563
576
|
|
|
@@ -601,12 +614,27 @@ const CmcdHeaderField = {
|
|
|
601
614
|
SESSION: CMCD_SESSION,
|
|
602
615
|
STATUS: CMCD_STATUS
|
|
603
616
|
};
|
|
617
|
+
/**
|
|
618
|
+
* All CMCD header fields as an array.
|
|
619
|
+
*
|
|
620
|
+
* @public
|
|
621
|
+
*/
|
|
622
|
+
const CMCD_HEADER_FIELDS = [
|
|
623
|
+
CMCD_OBJECT,
|
|
624
|
+
CMCD_REQUEST,
|
|
625
|
+
CMCD_SESSION,
|
|
626
|
+
CMCD_STATUS
|
|
627
|
+
];
|
|
604
628
|
|
|
605
629
|
//#endregion
|
|
606
630
|
//#region src/CMCD_HEADER_MAP.ts
|
|
607
631
|
/**
|
|
608
632
|
* The map of CMCD keys to their appropriate header shard.
|
|
609
633
|
*
|
|
634
|
+
* Note: Event-only keys (e, ts, cen, h) and response-received keys
|
|
635
|
+
* (rc, ttfb, ttlb, url, etc.) are intentionally absent. They are
|
|
636
|
+
* transmitted via the event-mode POST body, not HTTP headers.
|
|
637
|
+
*
|
|
610
638
|
* @public
|
|
611
639
|
*/
|
|
612
640
|
const CMCD_HEADER_MAP = {
|
|
@@ -667,19 +695,21 @@ function createHeaderMap(headerMap) {
|
|
|
667
695
|
*
|
|
668
696
|
* @returns The CMCD header shards.
|
|
669
697
|
*
|
|
698
|
+
* @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#header-field-definition | CTA-5004-A Header Field Definition}
|
|
699
|
+
*
|
|
670
700
|
* @public
|
|
671
701
|
*/
|
|
672
702
|
function groupCmcdHeaders(cmcd, customHeaderMap) {
|
|
673
703
|
const result = {};
|
|
674
704
|
if (!cmcd) return result;
|
|
675
|
-
const keys
|
|
705
|
+
const keys = Object.keys(cmcd);
|
|
676
706
|
const custom = customHeaderMap ? createHeaderMap(customHeaderMap) : {};
|
|
677
|
-
|
|
707
|
+
for (const key of keys) {
|
|
678
708
|
const field = CMCD_HEADER_MAP[key] || custom[key] || CmcdHeaderField.REQUEST;
|
|
679
|
-
const data =
|
|
709
|
+
const data = result[field] ??= {};
|
|
680
710
|
data[key] = cmcd[key];
|
|
681
|
-
|
|
682
|
-
|
|
711
|
+
}
|
|
712
|
+
return result;
|
|
683
713
|
}
|
|
684
714
|
|
|
685
715
|
//#endregion
|
|
@@ -696,11 +726,11 @@ function groupCmcdHeaders(cmcd, customHeaderMap) {
|
|
|
696
726
|
function toPreparedCmcdHeaders(data, customHeaderMap) {
|
|
697
727
|
const result = {};
|
|
698
728
|
const shards = groupCmcdHeaders(data, customHeaderMap);
|
|
699
|
-
|
|
729
|
+
for (const [field, value] of Object.entries(shards)) {
|
|
700
730
|
const shard = encodeSfDict(value, { whitespace: false });
|
|
701
|
-
if (shard)
|
|
702
|
-
|
|
703
|
-
|
|
731
|
+
if (shard) result[field] = shard;
|
|
732
|
+
}
|
|
733
|
+
return result;
|
|
704
734
|
}
|
|
705
735
|
|
|
706
736
|
//#endregion
|
|
@@ -713,6 +743,8 @@ function toPreparedCmcdHeaders(data, customHeaderMap) {
|
|
|
713
743
|
*
|
|
714
744
|
* @returns The CMCD header shards.
|
|
715
745
|
*
|
|
746
|
+
* @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#header-field-definition | CTA-5004-A Header Field Definition}
|
|
747
|
+
*
|
|
716
748
|
* @public
|
|
717
749
|
*
|
|
718
750
|
* @example
|
|
@@ -738,6 +770,8 @@ function toCmcdHeaders(cmcd, options = {}) {
|
|
|
738
770
|
*
|
|
739
771
|
* @example
|
|
740
772
|
* {@includeCode ../test/appendCmcdHeaders.test.ts#example}
|
|
773
|
+
*
|
|
774
|
+
* @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#header-field-definition | CTA-5004-A Header Field Definition}
|
|
741
775
|
*/
|
|
742
776
|
function appendCmcdHeaders(headers, cmcd, options) {
|
|
743
777
|
return Object.assign(headers, toCmcdHeaders(cmcd, options));
|
|
@@ -776,6 +810,8 @@ function encodePreparedCmcd(data) {
|
|
|
776
810
|
*
|
|
777
811
|
* @returns The encoded CMCD string.
|
|
778
812
|
*
|
|
813
|
+
* @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#payload-definition-for-headers-and-query-argument-transmission | CTA-5004-A Payload Definition}
|
|
814
|
+
*
|
|
779
815
|
* @public
|
|
780
816
|
*
|
|
781
817
|
* @example
|
|
@@ -796,6 +832,8 @@ function encodeCmcd(cmcd, options = {}) {
|
|
|
796
832
|
*
|
|
797
833
|
* @returns The URL encoded CMCD data.
|
|
798
834
|
*
|
|
835
|
+
* @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#query-argument-definition | CTA-5004-A Query Argument Definition}
|
|
836
|
+
*
|
|
799
837
|
* @public
|
|
800
838
|
*/
|
|
801
839
|
function toCmcdUrl(cmcd, options = {}) {
|
|
@@ -814,6 +852,8 @@ function toCmcdUrl(cmcd, options = {}) {
|
|
|
814
852
|
*
|
|
815
853
|
* @returns The CMCD query arg.
|
|
816
854
|
*
|
|
855
|
+
* @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#query-argument-definition | CTA-5004-A Query Argument Definition}
|
|
856
|
+
*
|
|
817
857
|
* @public
|
|
818
858
|
*
|
|
819
859
|
* @example
|
|
@@ -840,6 +880,8 @@ const REGEX = /CMCD=[^&#]+/;
|
|
|
840
880
|
*
|
|
841
881
|
* @example
|
|
842
882
|
* {@includeCode ../test/appendCmcdQuery.test.ts#example}
|
|
883
|
+
*
|
|
884
|
+
* @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#query-argument-definition | CTA-5004-A Query Argument Definition}
|
|
843
885
|
*/
|
|
844
886
|
function appendCmcdQuery(url, cmcd, options) {
|
|
845
887
|
const query = toCmcdQuery(cmcd, options);
|
|
@@ -859,18 +901,26 @@ const CMCD_DEFAULT_TIME_INTERVAL = 30;
|
|
|
859
901
|
|
|
860
902
|
//#endregion
|
|
861
903
|
//#region src/CMCD_KEYS.ts
|
|
862
|
-
|
|
904
|
+
/**
|
|
905
|
+
* A list of all CMCD keys.
|
|
906
|
+
*
|
|
907
|
+
* @public
|
|
908
|
+
*/
|
|
909
|
+
const CMCD_KEYS = [
|
|
863
910
|
...CMCD_V1_KEYS,
|
|
864
911
|
...CMCD_REQUEST_KEYS,
|
|
865
912
|
...CMCD_RESPONSE_KEYS,
|
|
866
913
|
...CMCD_EVENT_KEYS
|
|
867
|
-
]);
|
|
914
|
+
].filter((key, index, arr) => arr.indexOf(key) === index);
|
|
915
|
+
|
|
916
|
+
//#endregion
|
|
917
|
+
//#region src/CMCD_MIME_TYPE.ts
|
|
868
918
|
/**
|
|
869
|
-
*
|
|
919
|
+
* CMCD MIME type for event report payloads.
|
|
870
920
|
*
|
|
871
921
|
* @public
|
|
872
922
|
*/
|
|
873
|
-
const
|
|
923
|
+
const CMCD_MIME_TYPE = "application/cmcd";
|
|
874
924
|
|
|
875
925
|
//#endregion
|
|
876
926
|
//#region src/CMCD_V1.ts
|
|
@@ -889,6 +939,8 @@ const CMCD_V1 = 1;
|
|
|
889
939
|
* @public
|
|
890
940
|
*
|
|
891
941
|
* @enum
|
|
942
|
+
*
|
|
943
|
+
* @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#object-type | CTA-5004-A Object Type}
|
|
892
944
|
*/
|
|
893
945
|
const CmcdObjectType = {
|
|
894
946
|
MANIFEST: "m",
|
|
@@ -911,6 +963,8 @@ const CmcdObjectType = {
|
|
|
911
963
|
* @enum
|
|
912
964
|
*
|
|
913
965
|
* @public
|
|
966
|
+
*
|
|
967
|
+
* @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#state | CTA-5004-A State}
|
|
914
968
|
*/
|
|
915
969
|
const CmcdPlayerState = {
|
|
916
970
|
STARTING: "s",
|
|
@@ -963,11 +1017,11 @@ const CmcdTransmissionMode = {
|
|
|
963
1017
|
//#endregion
|
|
964
1018
|
//#region src/CmcdReporter.ts
|
|
965
1019
|
function createEncodingOptions(reportingMode, config, baseUrl) {
|
|
966
|
-
const
|
|
1020
|
+
const enabledKeySet = new Set(config.enabledKeys ?? []);
|
|
967
1021
|
return {
|
|
968
1022
|
version: config.version || CMCD_V2,
|
|
969
1023
|
reportingMode,
|
|
970
|
-
filter: (key) =>
|
|
1024
|
+
filter: (key) => enabledKeySet.has(key),
|
|
971
1025
|
baseUrl
|
|
972
1026
|
};
|
|
973
1027
|
}
|
|
@@ -998,6 +1052,8 @@ function createCmcdReporterConfig(config) {
|
|
|
998
1052
|
/**
|
|
999
1053
|
* The CMCD reporter.
|
|
1000
1054
|
*
|
|
1055
|
+
* @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#reporting-modes-when-we-send-data | CTA-5004-A Reporting Modes}
|
|
1056
|
+
*
|
|
1001
1057
|
* @public
|
|
1002
1058
|
*/
|
|
1003
1059
|
var CmcdReporter = class {
|
|
@@ -1034,6 +1090,10 @@ var CmcdReporter = class {
|
|
|
1034
1090
|
}
|
|
1035
1091
|
/**
|
|
1036
1092
|
* Starts the CMCD reporter. Called by the player when the reporter is enabled.
|
|
1093
|
+
*
|
|
1094
|
+
* Note: This fires an initial time-interval event immediately (synchronously)
|
|
1095
|
+
* before the first interval elapses. Ensure CMCD data (sid, cid, etc.) is
|
|
1096
|
+
* populated before calling start().
|
|
1037
1097
|
*/
|
|
1038
1098
|
start() {
|
|
1039
1099
|
this.eventTargets.forEach((target, config) => {
|
|
@@ -1071,10 +1131,7 @@ var CmcdReporter = class {
|
|
|
1071
1131
|
* @param data - The data to update.
|
|
1072
1132
|
*/
|
|
1073
1133
|
update(data) {
|
|
1074
|
-
if (data.sid && data.sid !== this.data.sid)
|
|
1075
|
-
this.resetSession();
|
|
1076
|
-
return;
|
|
1077
|
-
}
|
|
1134
|
+
if (data.sid && data.sid !== this.data.sid) this.resetSession();
|
|
1078
1135
|
if (data.msd && !isNaN(data.msd)) this.msd = data.msd;
|
|
1079
1136
|
this.data = {
|
|
1080
1137
|
...this.data,
|
|
@@ -1189,7 +1246,7 @@ var CmcdReporter = class {
|
|
|
1189
1246
|
* Creates a new request with the CMCD request report data applied. Called by the player
|
|
1190
1247
|
* before sending the request.
|
|
1191
1248
|
*
|
|
1192
|
-
* @param
|
|
1249
|
+
* @param request - The request to apply the CMCD request report to.
|
|
1193
1250
|
* @param data - The data to apply to the request. This data only
|
|
1194
1251
|
* applies to this request report. Persistent data
|
|
1195
1252
|
* should be updated using `update()`.
|
|
@@ -1245,7 +1302,9 @@ var CmcdReporter = class {
|
|
|
1245
1302
|
if (queue.length < config.batchSize && !flush) return;
|
|
1246
1303
|
const deleteCount = flush ? queue.length : config.batchSize;
|
|
1247
1304
|
const events = queue.splice(0, deleteCount);
|
|
1248
|
-
this.sendEventReport(config, events)
|
|
1305
|
+
this.sendEventReport(config, events).catch(() => {
|
|
1306
|
+
target.queue.unshift(...events);
|
|
1307
|
+
});
|
|
1249
1308
|
reprocess ||= queue.length > 0;
|
|
1250
1309
|
});
|
|
1251
1310
|
if (reprocess) this.processEventTargets();
|
|
@@ -1261,18 +1320,11 @@ var CmcdReporter = class {
|
|
|
1261
1320
|
const { status } = await this.requester({
|
|
1262
1321
|
url: target.url,
|
|
1263
1322
|
method: "POST",
|
|
1264
|
-
headers: { "Content-Type":
|
|
1265
|
-
body: data.
|
|
1323
|
+
headers: { "Content-Type": CMCD_MIME_TYPE },
|
|
1324
|
+
body: data.map((item) => encodeCmcd(item, options)).join("\n") + "\n"
|
|
1266
1325
|
});
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
this.eventTargets.delete(target);
|
|
1270
|
-
break;
|
|
1271
|
-
case 429: break;
|
|
1272
|
-
default:
|
|
1273
|
-
if (status > 499 && status < 600) {}
|
|
1274
|
-
break;
|
|
1275
|
-
}
|
|
1326
|
+
if (status === 410) this.eventTargets.delete(target);
|
|
1327
|
+
else if (status === 429 || status > 499 && status < 600) throw new Error(`Event report failed with status ${status}`);
|
|
1276
1328
|
}
|
|
1277
1329
|
/**
|
|
1278
1330
|
* Resets the session related data. Called when the session ID changes.
|
|
@@ -1288,9 +1340,10 @@ var CmcdReporter = class {
|
|
|
1288
1340
|
/**
|
|
1289
1341
|
* Common Media Client Data Streaming Format
|
|
1290
1342
|
*
|
|
1291
|
-
*
|
|
1292
1343
|
* @enum
|
|
1293
1344
|
*
|
|
1345
|
+
* @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#streaming-format | CTA-5004-A Streaming Format}
|
|
1346
|
+
*
|
|
1294
1347
|
* @public
|
|
1295
1348
|
*/
|
|
1296
1349
|
const CmcdStreamingFormat = {
|
|
@@ -1307,6 +1360,8 @@ const CmcdStreamingFormat = {
|
|
|
1307
1360
|
*
|
|
1308
1361
|
* @enum
|
|
1309
1362
|
*
|
|
1363
|
+
* @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#stream-type | CTA-5004-A Stream Type}
|
|
1364
|
+
*
|
|
1310
1365
|
* @public
|
|
1311
1366
|
*/
|
|
1312
1367
|
const CmcdStreamType = {
|
|
@@ -1315,116 +1370,993 @@ const CmcdStreamType = {
|
|
|
1315
1370
|
LOW_LATENCY: "ll"
|
|
1316
1371
|
};
|
|
1317
1372
|
|
|
1373
|
+
//#endregion
|
|
1374
|
+
//#region src/CmcdValidationSeverity.ts
|
|
1375
|
+
/**
|
|
1376
|
+
* CMCD validation severity level: error.
|
|
1377
|
+
*
|
|
1378
|
+
* @public
|
|
1379
|
+
*/
|
|
1380
|
+
const CMCD_VALIDATION_SEVERITY_ERROR = "error";
|
|
1381
|
+
/**
|
|
1382
|
+
* CMCD validation severity level: warning.
|
|
1383
|
+
*
|
|
1384
|
+
* @public
|
|
1385
|
+
*/
|
|
1386
|
+
const CMCD_VALIDATION_SEVERITY_WARNING = "warning";
|
|
1387
|
+
/**
|
|
1388
|
+
* CMCD validation severity level.
|
|
1389
|
+
*
|
|
1390
|
+
* @public
|
|
1391
|
+
*/
|
|
1392
|
+
const CmcdValidationSeverity = {
|
|
1393
|
+
ERROR: CMCD_VALIDATION_SEVERITY_ERROR,
|
|
1394
|
+
WARNING: CMCD_VALIDATION_SEVERITY_WARNING
|
|
1395
|
+
};
|
|
1396
|
+
|
|
1397
|
+
//#endregion
|
|
1398
|
+
//#region src/upConvertToV2.ts
|
|
1399
|
+
/**
|
|
1400
|
+
* Up-convert V1 CMCD data to V2 format.
|
|
1401
|
+
*
|
|
1402
|
+
* - Wraps plain scalar values in arrays for inner-list keys.
|
|
1403
|
+
* - Wraps `nor` string in an array.
|
|
1404
|
+
*
|
|
1405
|
+
* If the data is already V2 (has `v: 2`), it is returned unchanged.
|
|
1406
|
+
*
|
|
1407
|
+
* @internal
|
|
1408
|
+
*/
|
|
1409
|
+
function upConvertToV2(obj) {
|
|
1410
|
+
if (obj["v"] === CMCD_V2) return obj;
|
|
1411
|
+
const result = {};
|
|
1412
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1413
|
+
if (value == null) {
|
|
1414
|
+
result[key] = value;
|
|
1415
|
+
continue;
|
|
1416
|
+
}
|
|
1417
|
+
if (CMCD_INNER_LIST_KEYS.has(key) && !Array.isArray(value)) result[key] = [value];
|
|
1418
|
+
else if (key === "nor" && typeof value === "string") result[key] = [value];
|
|
1419
|
+
else result[key] = value;
|
|
1420
|
+
}
|
|
1421
|
+
return result;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1318
1424
|
//#endregion
|
|
1319
1425
|
//#region src/decodeCmcd.ts
|
|
1320
1426
|
function reduceValue(value) {
|
|
1321
1427
|
if (Array.isArray(value)) return value.map(reduceValue);
|
|
1322
1428
|
if (typeof value === "symbol") return symbolToStr(value);
|
|
1323
1429
|
if (value instanceof SfItem && !value.params) return reduceValue(value.value);
|
|
1324
|
-
if (typeof value === "string") return
|
|
1430
|
+
if (typeof value === "string") return value;
|
|
1325
1431
|
return value;
|
|
1326
1432
|
}
|
|
1433
|
+
function decodeCmcd(cmcd, options) {
|
|
1434
|
+
if (!cmcd) return {};
|
|
1435
|
+
const sfDict = decodeSfDict(cmcd);
|
|
1436
|
+
const result = {};
|
|
1437
|
+
for (const [key, item] of Object.entries(sfDict)) result[key] = reduceValue(item.value);
|
|
1438
|
+
if (options?.convertToLatest) return upConvertToV2(result);
|
|
1439
|
+
return result;
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
//#endregion
|
|
1443
|
+
//#region src/ensureHeaders.ts
|
|
1444
|
+
/**
|
|
1445
|
+
* Converts a record of header fields to a `Headers` instance if necessary.
|
|
1446
|
+
*
|
|
1447
|
+
* @param headers - A `Headers` instance or a plain record of header fields.
|
|
1448
|
+
* @returns A `Headers` instance.
|
|
1449
|
+
*
|
|
1450
|
+
* @internal
|
|
1451
|
+
*/
|
|
1452
|
+
function ensureHeaders(headers) {
|
|
1453
|
+
return headers instanceof Headers ? headers : new Headers(headers);
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
//#endregion
|
|
1457
|
+
//#region src/fromCmcdHeaders.ts
|
|
1458
|
+
function fromCmcdHeaders(headers, options) {
|
|
1459
|
+
const h = ensureHeaders(headers);
|
|
1460
|
+
const result = {};
|
|
1461
|
+
for (const field of CMCD_HEADER_FIELDS) {
|
|
1462
|
+
const value = h.get(field);
|
|
1463
|
+
if (value) Object.assign(result, decodeCmcd(value));
|
|
1464
|
+
}
|
|
1465
|
+
if (options?.convertToLatest) return upConvertToV2(result);
|
|
1466
|
+
return result;
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
//#endregion
|
|
1470
|
+
//#region src/fromCmcdQuery.ts
|
|
1471
|
+
function fromCmcdQuery(query, options) {
|
|
1472
|
+
if (typeof query === "string") query = new URLSearchParams(query);
|
|
1473
|
+
return decodeCmcd(query.get(CMCD_PARAM) ?? "", options);
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
//#endregion
|
|
1477
|
+
//#region src/fromCmcdUrl.ts
|
|
1478
|
+
function fromCmcdUrl(url, options) {
|
|
1479
|
+
return decodeCmcd(decodeURIComponent(url.replace(/^CMCD=/, "")), options);
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
//#endregion
|
|
1483
|
+
//#region src/isCmcdV1Data.ts
|
|
1327
1484
|
/**
|
|
1328
|
-
*
|
|
1485
|
+
* Check if a CMCD data object is version 1.
|
|
1329
1486
|
*
|
|
1330
|
-
* @param
|
|
1487
|
+
* @param data - The CMCD data object to check.
|
|
1331
1488
|
*
|
|
1332
|
-
* @returns
|
|
1489
|
+
* @returns `true` if the data is version 1, `false` otherwise.
|
|
1333
1490
|
*
|
|
1334
1491
|
* @public
|
|
1335
1492
|
*
|
|
1336
1493
|
* @example
|
|
1337
|
-
* {@includeCode ../test/
|
|
1494
|
+
* {@includeCode ../test/isCmcdV1Data.test.ts#example}
|
|
1338
1495
|
*/
|
|
1339
|
-
function
|
|
1340
|
-
|
|
1341
|
-
const sfDict = decodeSfDict(cmcd);
|
|
1342
|
-
return Object.entries(sfDict).reduce((acc, [key, item]) => {
|
|
1343
|
-
acc[key] = reduceValue(item.value);
|
|
1344
|
-
return acc;
|
|
1345
|
-
}, {});
|
|
1496
|
+
function isCmcdV1Data(data) {
|
|
1497
|
+
return data.v !== CMCD_V2;
|
|
1346
1498
|
}
|
|
1347
1499
|
|
|
1348
1500
|
//#endregion
|
|
1349
|
-
//#region src/
|
|
1350
|
-
const keys = [
|
|
1351
|
-
CMCD_OBJECT,
|
|
1352
|
-
CMCD_REQUEST,
|
|
1353
|
-
CMCD_SESSION,
|
|
1354
|
-
CMCD_STATUS
|
|
1355
|
-
];
|
|
1501
|
+
//#region src/isCmcdV2Data.ts
|
|
1356
1502
|
/**
|
|
1357
|
-
*
|
|
1503
|
+
* Check if a CMCD data object is version 2.
|
|
1358
1504
|
*
|
|
1359
|
-
* @param
|
|
1505
|
+
* @param data - The CMCD data object to check.
|
|
1360
1506
|
*
|
|
1361
|
-
* @returns
|
|
1507
|
+
* @returns `true` if the data is version 2, `false` otherwise.
|
|
1362
1508
|
*
|
|
1363
1509
|
* @public
|
|
1364
1510
|
*
|
|
1365
1511
|
* @example
|
|
1366
|
-
* {@includeCode ../test/
|
|
1512
|
+
* {@includeCode ../test/isCmcdV2Data.test.ts#example}
|
|
1367
1513
|
*/
|
|
1368
|
-
function
|
|
1369
|
-
|
|
1370
|
-
return keys.reduce((acc, key) => {
|
|
1371
|
-
const value = headers.get(key);
|
|
1372
|
-
return Object.assign(acc, decodeCmcd(value));
|
|
1373
|
-
}, {});
|
|
1514
|
+
function isCmcdV2Data(data) {
|
|
1515
|
+
return data.v === CMCD_V2;
|
|
1374
1516
|
}
|
|
1375
1517
|
|
|
1376
1518
|
//#endregion
|
|
1377
|
-
//#region src/
|
|
1519
|
+
//#region src/toCmcdValue.ts
|
|
1378
1520
|
/**
|
|
1379
|
-
*
|
|
1521
|
+
* Convert a value to a CMCD value.
|
|
1380
1522
|
*
|
|
1381
|
-
* @param
|
|
1523
|
+
* @param value - The value to convert to a CMCD value.
|
|
1524
|
+
* @param params - The parameters to convert to a CMCD value.
|
|
1525
|
+
* @returns The CMCD value.
|
|
1382
1526
|
*
|
|
1383
|
-
* @
|
|
1527
|
+
* @public
|
|
1528
|
+
*/
|
|
1529
|
+
function toCmcdValue(value, params) {
|
|
1530
|
+
return new SfItem(value, params);
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
//#endregion
|
|
1534
|
+
//#region src/mergeValidationResults.ts
|
|
1535
|
+
/**
|
|
1536
|
+
* Merges multiple validation results into a single result.
|
|
1537
|
+
*
|
|
1538
|
+
* @internal
|
|
1539
|
+
*/
|
|
1540
|
+
function mergeValidationResults(...results) {
|
|
1541
|
+
const issues = results.flatMap((r) => r.issues);
|
|
1542
|
+
return {
|
|
1543
|
+
valid: issues.every((i) => i.severity !== CMCD_VALIDATION_SEVERITY_ERROR),
|
|
1544
|
+
issues
|
|
1545
|
+
};
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
//#endregion
|
|
1549
|
+
//#region src/resolveVersion.ts
|
|
1550
|
+
/**
|
|
1551
|
+
* Resolves the CMCD version from explicit options, the payload's `v` key, or the default (v1).
|
|
1552
|
+
*
|
|
1553
|
+
* @internal
|
|
1554
|
+
*/
|
|
1555
|
+
function resolveVersion(data, options) {
|
|
1556
|
+
if (options?.version === 1 || options?.version === 2) return options.version;
|
|
1557
|
+
const payloadVersion = data["v"];
|
|
1558
|
+
if (payloadVersion === 1 || payloadVersion === 2) return payloadVersion;
|
|
1559
|
+
return CMCD_V1;
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
//#endregion
|
|
1563
|
+
//#region src/validateCmcdKeys.ts
|
|
1564
|
+
const CMCD_V1_KEY_SET = new Set(CMCD_V1_KEYS);
|
|
1565
|
+
const CMCD_KEY_SET = new Set(CMCD_KEYS);
|
|
1566
|
+
/**
|
|
1567
|
+
* Validates that all keys in a CMCD payload are recognized spec keys or valid custom keys.
|
|
1568
|
+
*
|
|
1569
|
+
* @example
|
|
1570
|
+
* {@includeCode ../test/validateCmcdKeys.test.ts#example}
|
|
1571
|
+
*
|
|
1572
|
+
* @param data - The CMCD payload to validate.
|
|
1573
|
+
* @param options - Validation options.
|
|
1574
|
+
* @returns The validation result.
|
|
1575
|
+
*
|
|
1576
|
+
* @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#reserved-keys | CTA-5004-A Reserved Keys}
|
|
1384
1577
|
*
|
|
1385
1578
|
* @public
|
|
1579
|
+
*/
|
|
1580
|
+
function validateCmcdKeys(data, options) {
|
|
1581
|
+
const version = resolveVersion(data, options);
|
|
1582
|
+
const validKeySet = version === CMCD_V1 ? CMCD_V1_KEY_SET : CMCD_KEY_SET;
|
|
1583
|
+
const issues = [];
|
|
1584
|
+
for (const key of Object.keys(data)) {
|
|
1585
|
+
if (isCmcdCustomKey(key)) continue;
|
|
1586
|
+
if (!validKeySet.has(key)) issues.push({
|
|
1587
|
+
key,
|
|
1588
|
+
message: `Unknown CMCD key "${key}" for version ${version}.`,
|
|
1589
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1590
|
+
});
|
|
1591
|
+
}
|
|
1592
|
+
return {
|
|
1593
|
+
valid: issues.length === 0,
|
|
1594
|
+
issues
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
//#endregion
|
|
1599
|
+
//#region src/validateCmcdStructure.ts
|
|
1600
|
+
/**
|
|
1601
|
+
* Validates the structural rules of a CMCD payload.
|
|
1386
1602
|
*
|
|
1387
1603
|
* @example
|
|
1388
|
-
* {@includeCode ../test/
|
|
1604
|
+
* {@includeCode ../test/validateCmcdStructure.test.ts#example}
|
|
1605
|
+
*
|
|
1606
|
+
* @param data - The CMCD payload to validate.
|
|
1607
|
+
* @param options - Validation options.
|
|
1608
|
+
* @returns The validation result.
|
|
1609
|
+
*
|
|
1610
|
+
* @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#data-payload-definition-what-data-to-send | CTA-5004-A Data Payload Definition}
|
|
1611
|
+
*
|
|
1612
|
+
* @public
|
|
1389
1613
|
*/
|
|
1390
|
-
function
|
|
1391
|
-
|
|
1392
|
-
|
|
1614
|
+
function validateCmcdStructure(data, options) {
|
|
1615
|
+
const version = resolveVersion(data, options);
|
|
1616
|
+
const issues = [];
|
|
1617
|
+
if (options?.reportingMode === CMCD_REQUEST_MODE) {
|
|
1618
|
+
for (const key of CMCD_EVENT_KEYS) if (key in data) issues.push({
|
|
1619
|
+
key,
|
|
1620
|
+
message: `Event key "${key}" must not be present in request mode.`,
|
|
1621
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1622
|
+
});
|
|
1623
|
+
for (const key of CMCD_RESPONSE_KEYS) if (key in data) issues.push({
|
|
1624
|
+
key,
|
|
1625
|
+
message: `Response key "${key}" must not be present in request mode.`,
|
|
1626
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1627
|
+
});
|
|
1628
|
+
}
|
|
1629
|
+
if (options?.reportingMode === CMCD_EVENT_MODE) {
|
|
1630
|
+
if (!("e" in data)) issues.push({
|
|
1631
|
+
key: "e",
|
|
1632
|
+
message: "Event mode requires the \"e\" key to be present.",
|
|
1633
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1634
|
+
});
|
|
1635
|
+
if (!("ts" in data)) issues.push({
|
|
1636
|
+
key: "ts",
|
|
1637
|
+
message: "Event mode requires the \"ts\" key to be present.",
|
|
1638
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1639
|
+
});
|
|
1640
|
+
}
|
|
1641
|
+
if ("e" in data) {
|
|
1642
|
+
if (data["e"] === CMCD_EVENT_CUSTOM_EVENT) {
|
|
1643
|
+
if (!("cen" in data)) issues.push({
|
|
1644
|
+
key: "cen",
|
|
1645
|
+
message: "Custom event (e=\"ce\") requires the \"cen\" key to be present.",
|
|
1646
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1647
|
+
});
|
|
1648
|
+
} else if ("cen" in data) issues.push({
|
|
1649
|
+
key: "cen",
|
|
1650
|
+
message: "The \"cen\" key must only be present when e=\"ce\".",
|
|
1651
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1652
|
+
});
|
|
1653
|
+
if (data["e"] === CMCD_EVENT_RESPONSE_RECEIVED) {
|
|
1654
|
+
if (!("url" in data)) issues.push({
|
|
1655
|
+
key: "url",
|
|
1656
|
+
message: "Response received event (e=\"rr\") requires the \"url\" key to be present.",
|
|
1657
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1658
|
+
});
|
|
1659
|
+
} else for (const key of CMCD_RESPONSE_KEYS) if (key in data) issues.push({
|
|
1660
|
+
key,
|
|
1661
|
+
message: `Response key "${key}" must only be present when e="rr".`,
|
|
1662
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1663
|
+
});
|
|
1664
|
+
if (data["e"] === CMCD_EVENT_PLAY_STATE && !("sta" in data)) issues.push({
|
|
1665
|
+
key: "sta",
|
|
1666
|
+
message: "Play state event (e=\"ps\") requires the \"sta\" key to be present.",
|
|
1667
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1668
|
+
});
|
|
1669
|
+
if (data["e"] === CMCD_EVENT_ERROR && !("ec" in data)) issues.push({
|
|
1670
|
+
key: "ec",
|
|
1671
|
+
message: "Error event (e=\"e\") requires the \"ec\" key to be present.",
|
|
1672
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1673
|
+
});
|
|
1674
|
+
}
|
|
1675
|
+
if ("v" in data && data["v"] !== 1 && data["v"] !== 2) issues.push({
|
|
1676
|
+
key: "v",
|
|
1677
|
+
message: `Unsupported CMCD version "${String(data["v"])}". Expected 1 or 2.`,
|
|
1678
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1679
|
+
});
|
|
1680
|
+
else if (version > 1 && !("v" in data)) issues.push({
|
|
1681
|
+
key: "v",
|
|
1682
|
+
message: "Version 2 payloads require the \"v\" key to be present.",
|
|
1683
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1684
|
+
});
|
|
1685
|
+
else if (version === CMCD_V1 && "v" in data) issues.push({
|
|
1686
|
+
key: "v",
|
|
1687
|
+
message: "Version 1 payloads should omit the \"v\" key (v1 is the default).",
|
|
1688
|
+
severity: CMCD_VALIDATION_SEVERITY_WARNING
|
|
1689
|
+
});
|
|
1690
|
+
return {
|
|
1691
|
+
valid: issues.every((i) => i.severity !== CMCD_VALIDATION_SEVERITY_ERROR),
|
|
1692
|
+
issues
|
|
1693
|
+
};
|
|
1393
1694
|
}
|
|
1394
1695
|
|
|
1395
1696
|
//#endregion
|
|
1396
|
-
//#region src/
|
|
1697
|
+
//#region src/CMCD_KEY_TYPES.ts
|
|
1698
|
+
/**
|
|
1699
|
+
* CMCD key value type: inner list of numbers with token identifiers.
|
|
1700
|
+
*
|
|
1701
|
+
* @internal
|
|
1702
|
+
*/
|
|
1703
|
+
const CMCD_KEY_TYPE_NUMBER_LIST = "number[]";
|
|
1704
|
+
/**
|
|
1705
|
+
* CMCD key value type: inner list of strings.
|
|
1706
|
+
*
|
|
1707
|
+
* @internal
|
|
1708
|
+
*/
|
|
1709
|
+
const CMCD_KEY_TYPE_STRING_LIST = "string[]";
|
|
1710
|
+
/**
|
|
1711
|
+
* CMCD key value type: integer.
|
|
1712
|
+
*
|
|
1713
|
+
* @internal
|
|
1714
|
+
*/
|
|
1715
|
+
const CMCD_KEY_TYPE_INTEGER = "integer";
|
|
1397
1716
|
/**
|
|
1398
|
-
*
|
|
1717
|
+
* CMCD key value type: number (decimal).
|
|
1399
1718
|
*
|
|
1400
|
-
* @
|
|
1719
|
+
* @internal
|
|
1720
|
+
*/
|
|
1721
|
+
const CMCD_KEY_TYPE_NUMBER = "number";
|
|
1722
|
+
/**
|
|
1723
|
+
* CMCD key value type: boolean.
|
|
1401
1724
|
*
|
|
1402
|
-
* @
|
|
1725
|
+
* @internal
|
|
1726
|
+
*/
|
|
1727
|
+
const CMCD_KEY_TYPE_BOOLEAN = "boolean";
|
|
1728
|
+
/**
|
|
1729
|
+
* CMCD key value type: string.
|
|
1730
|
+
*
|
|
1731
|
+
* @internal
|
|
1732
|
+
*/
|
|
1733
|
+
const CMCD_KEY_TYPE_STRING = "string";
|
|
1734
|
+
/**
|
|
1735
|
+
* CMCD key value type: token.
|
|
1736
|
+
*
|
|
1737
|
+
* @internal
|
|
1738
|
+
*/
|
|
1739
|
+
const CMCD_KEY_TYPE_TOKEN = "token";
|
|
1740
|
+
/**
|
|
1741
|
+
* Maps each CMCD spec key to its expected value type for v2.
|
|
1742
|
+
* Keys that differ between v1 and v2 are handled by CMCD_V1_KEY_TYPE_OVERRIDES.
|
|
1743
|
+
*
|
|
1744
|
+
* @internal
|
|
1745
|
+
*/
|
|
1746
|
+
const CMCD_KEY_TYPES = {
|
|
1747
|
+
ab: CMCD_KEY_TYPE_NUMBER_LIST,
|
|
1748
|
+
bl: CMCD_KEY_TYPE_NUMBER_LIST,
|
|
1749
|
+
br: CMCD_KEY_TYPE_NUMBER_LIST,
|
|
1750
|
+
bsa: CMCD_KEY_TYPE_NUMBER_LIST,
|
|
1751
|
+
bsd: CMCD_KEY_TYPE_NUMBER_LIST,
|
|
1752
|
+
bsda: CMCD_KEY_TYPE_NUMBER_LIST,
|
|
1753
|
+
lab: CMCD_KEY_TYPE_NUMBER_LIST,
|
|
1754
|
+
lb: CMCD_KEY_TYPE_NUMBER_LIST,
|
|
1755
|
+
mtp: CMCD_KEY_TYPE_NUMBER_LIST,
|
|
1756
|
+
pb: CMCD_KEY_TYPE_NUMBER_LIST,
|
|
1757
|
+
tab: CMCD_KEY_TYPE_NUMBER_LIST,
|
|
1758
|
+
tb: CMCD_KEY_TYPE_NUMBER_LIST,
|
|
1759
|
+
tbl: CMCD_KEY_TYPE_NUMBER_LIST,
|
|
1760
|
+
tpb: CMCD_KEY_TYPE_NUMBER_LIST,
|
|
1761
|
+
ec: CMCD_KEY_TYPE_STRING_LIST,
|
|
1762
|
+
nor: CMCD_KEY_TYPE_STRING_LIST,
|
|
1763
|
+
d: CMCD_KEY_TYPE_INTEGER,
|
|
1764
|
+
dfa: CMCD_KEY_TYPE_INTEGER,
|
|
1765
|
+
dl: CMCD_KEY_TYPE_INTEGER,
|
|
1766
|
+
ltc: CMCD_KEY_TYPE_INTEGER,
|
|
1767
|
+
msd: CMCD_KEY_TYPE_INTEGER,
|
|
1768
|
+
pt: CMCD_KEY_TYPE_INTEGER,
|
|
1769
|
+
rc: CMCD_KEY_TYPE_INTEGER,
|
|
1770
|
+
rtp: CMCD_KEY_TYPE_INTEGER,
|
|
1771
|
+
sn: CMCD_KEY_TYPE_INTEGER,
|
|
1772
|
+
ts: CMCD_KEY_TYPE_INTEGER,
|
|
1773
|
+
ttfb: CMCD_KEY_TYPE_INTEGER,
|
|
1774
|
+
ttfbb: CMCD_KEY_TYPE_INTEGER,
|
|
1775
|
+
ttlb: CMCD_KEY_TYPE_INTEGER,
|
|
1776
|
+
v: CMCD_KEY_TYPE_INTEGER,
|
|
1777
|
+
pr: CMCD_KEY_TYPE_NUMBER,
|
|
1778
|
+
bg: CMCD_KEY_TYPE_BOOLEAN,
|
|
1779
|
+
bs: CMCD_KEY_TYPE_BOOLEAN,
|
|
1780
|
+
nr: CMCD_KEY_TYPE_BOOLEAN,
|
|
1781
|
+
su: CMCD_KEY_TYPE_BOOLEAN,
|
|
1782
|
+
cdn: CMCD_KEY_TYPE_STRING,
|
|
1783
|
+
cen: CMCD_KEY_TYPE_STRING,
|
|
1784
|
+
cid: CMCD_KEY_TYPE_STRING,
|
|
1785
|
+
cmsdd: CMCD_KEY_TYPE_STRING,
|
|
1786
|
+
cmsds: CMCD_KEY_TYPE_STRING,
|
|
1787
|
+
cs: CMCD_KEY_TYPE_STRING,
|
|
1788
|
+
h: CMCD_KEY_TYPE_STRING,
|
|
1789
|
+
nrr: CMCD_KEY_TYPE_STRING,
|
|
1790
|
+
sid: CMCD_KEY_TYPE_STRING,
|
|
1791
|
+
smrt: CMCD_KEY_TYPE_STRING,
|
|
1792
|
+
url: CMCD_KEY_TYPE_STRING,
|
|
1793
|
+
e: CMCD_KEY_TYPE_TOKEN,
|
|
1794
|
+
ot: CMCD_KEY_TYPE_TOKEN,
|
|
1795
|
+
sf: CMCD_KEY_TYPE_TOKEN,
|
|
1796
|
+
st: CMCD_KEY_TYPE_TOKEN,
|
|
1797
|
+
sta: CMCD_KEY_TYPE_TOKEN
|
|
1798
|
+
};
|
|
1799
|
+
/**
|
|
1800
|
+
* Maps keys to their v1-specific types when they differ from v2.
|
|
1801
|
+
*
|
|
1802
|
+
* @internal
|
|
1803
|
+
*/
|
|
1804
|
+
const CMCD_V1_KEY_TYPE_OVERRIDES = {
|
|
1805
|
+
bl: CMCD_KEY_TYPE_INTEGER,
|
|
1806
|
+
br: CMCD_KEY_TYPE_NUMBER,
|
|
1807
|
+
mtp: CMCD_KEY_TYPE_INTEGER,
|
|
1808
|
+
tb: CMCD_KEY_TYPE_NUMBER,
|
|
1809
|
+
nor: CMCD_KEY_TYPE_STRING
|
|
1810
|
+
};
|
|
1811
|
+
|
|
1812
|
+
//#endregion
|
|
1813
|
+
//#region src/CMCD_STRING_LENGTH_LIMITS.ts
|
|
1814
|
+
/**
|
|
1815
|
+
* Maps CMCD keys to their maximum string length.
|
|
1816
|
+
*
|
|
1817
|
+
* @internal
|
|
1818
|
+
*/
|
|
1819
|
+
const CMCD_STRING_LENGTH_LIMITS = {
|
|
1820
|
+
sid: 64,
|
|
1821
|
+
cid: 128,
|
|
1822
|
+
cdn: 128,
|
|
1823
|
+
h: 128,
|
|
1824
|
+
cen: 64
|
|
1825
|
+
};
|
|
1826
|
+
/**
|
|
1827
|
+
* Maximum length for custom key values.
|
|
1828
|
+
*
|
|
1829
|
+
* @internal
|
|
1830
|
+
*/
|
|
1831
|
+
const CMCD_CUSTOM_KEY_VALUE_MAX_LENGTH = 64;
|
|
1832
|
+
|
|
1833
|
+
//#endregion
|
|
1834
|
+
//#region src/CMCD_TOKEN_VALUES.ts
|
|
1835
|
+
/**
|
|
1836
|
+
* Maps token keys to their valid values.
|
|
1837
|
+
*
|
|
1838
|
+
* @internal
|
|
1839
|
+
*/
|
|
1840
|
+
const CMCD_TOKEN_VALUES = {
|
|
1841
|
+
e: [
|
|
1842
|
+
"bc",
|
|
1843
|
+
"ps",
|
|
1844
|
+
"e",
|
|
1845
|
+
"t",
|
|
1846
|
+
"c",
|
|
1847
|
+
"b",
|
|
1848
|
+
"m",
|
|
1849
|
+
"um",
|
|
1850
|
+
"pe",
|
|
1851
|
+
"pc",
|
|
1852
|
+
"rr",
|
|
1853
|
+
"as",
|
|
1854
|
+
"ae",
|
|
1855
|
+
"abs",
|
|
1856
|
+
"abe",
|
|
1857
|
+
"sk",
|
|
1858
|
+
"ce"
|
|
1859
|
+
],
|
|
1860
|
+
ot: [
|
|
1861
|
+
"m",
|
|
1862
|
+
"a",
|
|
1863
|
+
"v",
|
|
1864
|
+
"av",
|
|
1865
|
+
"i",
|
|
1866
|
+
"c",
|
|
1867
|
+
"tt",
|
|
1868
|
+
"k",
|
|
1869
|
+
"o"
|
|
1870
|
+
],
|
|
1871
|
+
sf: [
|
|
1872
|
+
"d",
|
|
1873
|
+
"h",
|
|
1874
|
+
"s",
|
|
1875
|
+
"o"
|
|
1876
|
+
],
|
|
1877
|
+
st: [
|
|
1878
|
+
"v",
|
|
1879
|
+
"l",
|
|
1880
|
+
"ll"
|
|
1881
|
+
],
|
|
1882
|
+
sta: [
|
|
1883
|
+
"s",
|
|
1884
|
+
"p",
|
|
1885
|
+
"k",
|
|
1886
|
+
"r",
|
|
1887
|
+
"a",
|
|
1888
|
+
"w",
|
|
1889
|
+
"e",
|
|
1890
|
+
"f",
|
|
1891
|
+
"q",
|
|
1892
|
+
"d"
|
|
1893
|
+
]
|
|
1894
|
+
};
|
|
1895
|
+
|
|
1896
|
+
//#endregion
|
|
1897
|
+
//#region src/validateCmcdValues.ts
|
|
1898
|
+
const HUNDRED_ROUNDING_KEYS = new Set([
|
|
1899
|
+
"bl",
|
|
1900
|
+
"dl",
|
|
1901
|
+
"mtp",
|
|
1902
|
+
"rtp",
|
|
1903
|
+
"tbl"
|
|
1904
|
+
]);
|
|
1905
|
+
const INTEGER_ROUNDING_KEYS = new Set([
|
|
1906
|
+
"br",
|
|
1907
|
+
"d",
|
|
1908
|
+
"tb"
|
|
1909
|
+
]);
|
|
1910
|
+
function isFiniteNumber(value) {
|
|
1911
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
1912
|
+
}
|
|
1913
|
+
function validateListValue(key, value, issues) {
|
|
1914
|
+
if (!Array.isArray(value)) {
|
|
1915
|
+
issues.push({
|
|
1916
|
+
key,
|
|
1917
|
+
message: `Key "${key}" must be an array.`,
|
|
1918
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1919
|
+
});
|
|
1920
|
+
return;
|
|
1921
|
+
}
|
|
1922
|
+
for (let i = 0; i < value.length; i++) {
|
|
1923
|
+
const element = value[i];
|
|
1924
|
+
if (element instanceof SfItem) {
|
|
1925
|
+
if (!isFiniteNumber(element.value)) issues.push({
|
|
1926
|
+
key,
|
|
1927
|
+
message: `Key "${key}" array element [${i}] must be a finite number.`,
|
|
1928
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1929
|
+
});
|
|
1930
|
+
} else if (!isFiniteNumber(element)) issues.push({
|
|
1931
|
+
key,
|
|
1932
|
+
message: `Key "${key}" array element [${i}] must be a finite number.`,
|
|
1933
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1934
|
+
});
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
function validateStringArrayValue(key, value, issues) {
|
|
1938
|
+
if (!Array.isArray(value)) {
|
|
1939
|
+
issues.push({
|
|
1940
|
+
key,
|
|
1941
|
+
message: `Key "${key}" must be an array.`,
|
|
1942
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1943
|
+
});
|
|
1944
|
+
return;
|
|
1945
|
+
}
|
|
1946
|
+
for (let i = 0; i < value.length; i++) {
|
|
1947
|
+
const element = value[i];
|
|
1948
|
+
if (element instanceof SfItem) {
|
|
1949
|
+
if (typeof element.value !== "string") issues.push({
|
|
1950
|
+
key,
|
|
1951
|
+
message: `Key "${key}" array element [${i}] must be a string.`,
|
|
1952
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1953
|
+
});
|
|
1954
|
+
} else if (typeof element !== "string") issues.push({
|
|
1955
|
+
key,
|
|
1956
|
+
message: `Key "${key}" array element [${i}] must be a string.`,
|
|
1957
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1958
|
+
});
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
/**
|
|
1962
|
+
* Validates that all values in a CMCD payload conform to the expected types and constraints.
|
|
1963
|
+
*
|
|
1964
|
+
* @example
|
|
1965
|
+
* {@includeCode ../test/validateCmcdValues.test.ts#example}
|
|
1966
|
+
*
|
|
1967
|
+
* @param data - The CMCD payload to validate.
|
|
1968
|
+
* @param options - Validation options.
|
|
1969
|
+
* @returns The validation result.
|
|
1970
|
+
*
|
|
1971
|
+
* @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#reserved-keys | CTA-5004-A Reserved Keys}
|
|
1403
1972
|
*
|
|
1404
1973
|
* @public
|
|
1974
|
+
*/
|
|
1975
|
+
function validateCmcdValues(data, options) {
|
|
1976
|
+
const version = resolveVersion(data, options);
|
|
1977
|
+
const issues = [];
|
|
1978
|
+
for (const [key, value] of Object.entries(data)) {
|
|
1979
|
+
if (isCmcdCustomKey(key)) {
|
|
1980
|
+
if (typeof value !== "string") issues.push({
|
|
1981
|
+
key,
|
|
1982
|
+
message: `Custom key "${key}" value must be a string or token.`,
|
|
1983
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1984
|
+
});
|
|
1985
|
+
else if (value.length > CMCD_CUSTOM_KEY_VALUE_MAX_LENGTH) issues.push({
|
|
1986
|
+
key,
|
|
1987
|
+
message: `Custom key "${key}" value exceeds maximum length of ${CMCD_CUSTOM_KEY_VALUE_MAX_LENGTH}.`,
|
|
1988
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1989
|
+
});
|
|
1990
|
+
continue;
|
|
1991
|
+
}
|
|
1992
|
+
if (key === "v") {
|
|
1993
|
+
if (value !== 1 && value !== 2) issues.push({
|
|
1994
|
+
key,
|
|
1995
|
+
message: `Key "v" must be 1 or 2.`,
|
|
1996
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1997
|
+
});
|
|
1998
|
+
continue;
|
|
1999
|
+
}
|
|
2000
|
+
let expectedType = CMCD_KEY_TYPES[key];
|
|
2001
|
+
if (!expectedType) continue;
|
|
2002
|
+
if (version === CMCD_V1 && key in CMCD_V1_KEY_TYPE_OVERRIDES) expectedType = CMCD_V1_KEY_TYPE_OVERRIDES[key];
|
|
2003
|
+
switch (expectedType) {
|
|
2004
|
+
case CMCD_KEY_TYPE_INTEGER:
|
|
2005
|
+
if (!isFiniteNumber(value) || !Number.isInteger(value)) issues.push({
|
|
2006
|
+
key,
|
|
2007
|
+
message: `Key "${key}" must be a finite integer.`,
|
|
2008
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2009
|
+
});
|
|
2010
|
+
else if (HUNDRED_ROUNDING_KEYS.has(key) && value % 100 !== 0) issues.push({
|
|
2011
|
+
key,
|
|
2012
|
+
message: `Key "${key}" should be rounded to the nearest 100.`,
|
|
2013
|
+
severity: CMCD_VALIDATION_SEVERITY_WARNING
|
|
2014
|
+
});
|
|
2015
|
+
break;
|
|
2016
|
+
case CMCD_KEY_TYPE_NUMBER:
|
|
2017
|
+
if (!isFiniteNumber(value)) issues.push({
|
|
2018
|
+
key,
|
|
2019
|
+
message: `Key "${key}" must be a finite number.`,
|
|
2020
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2021
|
+
});
|
|
2022
|
+
else if (HUNDRED_ROUNDING_KEYS.has(key) && value % 100 !== 0) issues.push({
|
|
2023
|
+
key,
|
|
2024
|
+
message: `Key "${key}" should be rounded to the nearest 100.`,
|
|
2025
|
+
severity: CMCD_VALIDATION_SEVERITY_WARNING
|
|
2026
|
+
});
|
|
2027
|
+
else if (INTEGER_ROUNDING_KEYS.has(key) && !Number.isInteger(value)) issues.push({
|
|
2028
|
+
key,
|
|
2029
|
+
message: `Key "${key}" should be rounded to an integer.`,
|
|
2030
|
+
severity: CMCD_VALIDATION_SEVERITY_WARNING
|
|
2031
|
+
});
|
|
2032
|
+
break;
|
|
2033
|
+
case CMCD_KEY_TYPE_BOOLEAN:
|
|
2034
|
+
if (typeof value !== "boolean") issues.push({
|
|
2035
|
+
key,
|
|
2036
|
+
message: `Key "${key}" must be a boolean.`,
|
|
2037
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2038
|
+
});
|
|
2039
|
+
break;
|
|
2040
|
+
case CMCD_KEY_TYPE_STRING:
|
|
2041
|
+
if (typeof value !== "string") issues.push({
|
|
2042
|
+
key,
|
|
2043
|
+
message: `Key "${key}" must be a string.`,
|
|
2044
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2045
|
+
});
|
|
2046
|
+
else if (key in CMCD_STRING_LENGTH_LIMITS && value.length > CMCD_STRING_LENGTH_LIMITS[key]) issues.push({
|
|
2047
|
+
key,
|
|
2048
|
+
message: `Key "${key}" exceeds maximum length of ${CMCD_STRING_LENGTH_LIMITS[key]}.`,
|
|
2049
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2050
|
+
});
|
|
2051
|
+
break;
|
|
2052
|
+
case CMCD_KEY_TYPE_TOKEN: {
|
|
2053
|
+
const validValues = CMCD_TOKEN_VALUES[key];
|
|
2054
|
+
if (validValues && !validValues.includes(value)) issues.push({
|
|
2055
|
+
key,
|
|
2056
|
+
message: `Key "${key}" has invalid token value "${String(value)}". Expected one of: ${validValues.join(", ")}.`,
|
|
2057
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2058
|
+
});
|
|
2059
|
+
break;
|
|
2060
|
+
}
|
|
2061
|
+
case CMCD_KEY_TYPE_NUMBER_LIST:
|
|
2062
|
+
validateListValue(key, value, issues);
|
|
2063
|
+
break;
|
|
2064
|
+
case CMCD_KEY_TYPE_STRING_LIST:
|
|
2065
|
+
validateStringArrayValue(key, value, issues);
|
|
2066
|
+
break;
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
return {
|
|
2070
|
+
valid: issues.every((i) => i.severity !== CMCD_VALIDATION_SEVERITY_ERROR),
|
|
2071
|
+
issues
|
|
2072
|
+
};
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
//#endregion
|
|
2076
|
+
//#region src/validateCmcd.ts
|
|
2077
|
+
/**
|
|
2078
|
+
* Validates a CMCD payload by checking keys, values, and structure.
|
|
1405
2079
|
*
|
|
1406
2080
|
* @example
|
|
1407
|
-
* {@includeCode ../test/
|
|
2081
|
+
* {@includeCode ../test/validateCmcd.test.ts#example}
|
|
2082
|
+
*
|
|
2083
|
+
* @param data - The CMCD payload to validate.
|
|
2084
|
+
* @param options - Validation options.
|
|
2085
|
+
* @returns The validation result.
|
|
2086
|
+
*
|
|
2087
|
+
* @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#data-payload-definition-what-data-to-send | CTA-5004-A Data Payload Definition}
|
|
2088
|
+
*
|
|
2089
|
+
* @public
|
|
1408
2090
|
*/
|
|
1409
|
-
function
|
|
1410
|
-
return
|
|
2091
|
+
function validateCmcd(data, options) {
|
|
2092
|
+
return mergeValidationResults(validateCmcdKeys(data, options), validateCmcdValues(data, options), validateCmcdStructure(data, options));
|
|
1411
2093
|
}
|
|
1412
2094
|
|
|
1413
2095
|
//#endregion
|
|
1414
|
-
//#region src/
|
|
2096
|
+
//#region src/validateCmcdEvents.ts
|
|
1415
2097
|
/**
|
|
1416
|
-
*
|
|
2098
|
+
* Validates a raw CMCD string as an event-mode payload.
|
|
1417
2099
|
*
|
|
1418
|
-
*
|
|
1419
|
-
*
|
|
1420
|
-
*
|
|
2100
|
+
* This function decodes the string internally and validates it with
|
|
2101
|
+
* `reportingMode` set to `'event'`. The input may contain multiple
|
|
2102
|
+
* newline-separated events (e.g. an `application/cmcd` POST body), in which
|
|
2103
|
+
* case each line is validated independently and the results are merged.
|
|
2104
|
+
*
|
|
2105
|
+
* @param cmcd - The raw CMCD-encoded string to validate. May contain
|
|
2106
|
+
* multiple newline-separated event lines.
|
|
2107
|
+
* @param options - Validation options (excluding `reportingMode`).
|
|
2108
|
+
* @returns The validation result including decoded data per event line.
|
|
2109
|
+
*
|
|
2110
|
+
* @example
|
|
2111
|
+
* {@includeCode ../test/validateCmcdEvents.test.ts#example}
|
|
2112
|
+
*
|
|
2113
|
+
* @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#body-definition | CTA-5004-A Body Definition}
|
|
1421
2114
|
*
|
|
1422
2115
|
* @public
|
|
1423
2116
|
*/
|
|
1424
|
-
function
|
|
1425
|
-
|
|
2117
|
+
function validateCmcdEvents(cmcd, options) {
|
|
2118
|
+
const opts = {
|
|
2119
|
+
...options,
|
|
2120
|
+
reportingMode: CMCD_EVENT_MODE
|
|
2121
|
+
};
|
|
2122
|
+
const lines = cmcd.split(/\r?\n/).filter((line) => line.length > 0);
|
|
2123
|
+
if (lines.length === 0) return {
|
|
2124
|
+
valid: false,
|
|
2125
|
+
issues: [{
|
|
2126
|
+
message: "Empty event mode payload. At least one event line is required.",
|
|
2127
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2128
|
+
}],
|
|
2129
|
+
data: []
|
|
2130
|
+
};
|
|
2131
|
+
const decodedLines = [];
|
|
2132
|
+
const lineResults = [];
|
|
2133
|
+
for (let i = 0; i < lines.length; i++) try {
|
|
2134
|
+
const data = decodeCmcd(lines[i]);
|
|
2135
|
+
decodedLines.push(data);
|
|
2136
|
+
lineResults.push(validateCmcd(data, opts));
|
|
2137
|
+
} catch {
|
|
2138
|
+
decodedLines.push({});
|
|
2139
|
+
lineResults.push({
|
|
2140
|
+
valid: false,
|
|
2141
|
+
issues: [{
|
|
2142
|
+
message: `Failed to decode event line ${i + 1}: invalid structured field syntax.`,
|
|
2143
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2144
|
+
}]
|
|
2145
|
+
});
|
|
2146
|
+
}
|
|
2147
|
+
return {
|
|
2148
|
+
...mergeValidationResults(...lineResults),
|
|
2149
|
+
data: decodedLines
|
|
2150
|
+
};
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
//#endregion
|
|
2154
|
+
//#region src/validateCmcdEventReport.ts
|
|
2155
|
+
/**
|
|
2156
|
+
* Validates a full HTTP request as an event-mode payload.
|
|
2157
|
+
*
|
|
2158
|
+
* Accepts an {@link @svta/cml-utils!HttpRequest | HttpRequest} object.
|
|
2159
|
+
*
|
|
2160
|
+
* This function validates that the request uses the POST method and has
|
|
2161
|
+
* the correct `Content-Type` header (`application/cmcd`) in addition to
|
|
2162
|
+
* validating the body content via {@link validateCmcdEvents}.
|
|
2163
|
+
*
|
|
2164
|
+
* @param request - An `HttpRequest` to validate.
|
|
2165
|
+
* @param options - Validation options (excluding `reportingMode`).
|
|
2166
|
+
* @returns The validation result including decoded data per event line.
|
|
2167
|
+
*
|
|
2168
|
+
* @example {@includeCode ../test/validateCmcdEventReport.test.ts#example}
|
|
2169
|
+
*
|
|
2170
|
+
* @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#event-mode | CTA-5004-A Event Mode}
|
|
2171
|
+
*
|
|
2172
|
+
* @public
|
|
2173
|
+
*/
|
|
2174
|
+
function validateCmcdEventReport(request, options) {
|
|
2175
|
+
const issues = [];
|
|
2176
|
+
if (request.method !== "POST") issues.push({
|
|
2177
|
+
message: `Invalid HTTP method '${request.method ?? "GET"}'. Event reports must use POST.`,
|
|
2178
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2179
|
+
});
|
|
2180
|
+
const contentType = request.headers ? ensureHeaders(request.headers).get("Content-Type") : void 0;
|
|
2181
|
+
if (!contentType) issues.push({
|
|
2182
|
+
message: `Missing Content-Type header. Event reports must use '${CMCD_MIME_TYPE}'.`,
|
|
2183
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2184
|
+
});
|
|
2185
|
+
else {
|
|
2186
|
+
const mediaType = contentType.split(";")[0].trim().toLowerCase();
|
|
2187
|
+
if (mediaType !== CMCD_MIME_TYPE) issues.push({
|
|
2188
|
+
message: `Invalid Content-Type '${mediaType}'. Event reports must use '${CMCD_MIME_TYPE}'.`,
|
|
2189
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2190
|
+
});
|
|
2191
|
+
}
|
|
2192
|
+
const body = request.body;
|
|
2193
|
+
if (body === void 0 || body === null) {
|
|
2194
|
+
issues.push({
|
|
2195
|
+
message: "Missing request body. Event reports must include a body.",
|
|
2196
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2197
|
+
});
|
|
2198
|
+
return {
|
|
2199
|
+
valid: false,
|
|
2200
|
+
issues,
|
|
2201
|
+
data: []
|
|
2202
|
+
};
|
|
2203
|
+
}
|
|
2204
|
+
if (typeof body !== "string") {
|
|
2205
|
+
issues.push({
|
|
2206
|
+
message: "Request body must be a string.",
|
|
2207
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2208
|
+
});
|
|
2209
|
+
return {
|
|
2210
|
+
valid: false,
|
|
2211
|
+
issues,
|
|
2212
|
+
data: []
|
|
2213
|
+
};
|
|
2214
|
+
}
|
|
2215
|
+
const bodyResult = validateCmcdEvents(body, options);
|
|
2216
|
+
return {
|
|
2217
|
+
valid: issues.length === 0 && bodyResult.valid,
|
|
2218
|
+
issues: [...issues, ...bodyResult.issues],
|
|
2219
|
+
data: bodyResult.data
|
|
2220
|
+
};
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
//#endregion
|
|
2224
|
+
//#region src/validateCmcdHeaders.ts
|
|
2225
|
+
/**
|
|
2226
|
+
* Validates CMCD HTTP headers by checking shard placement and payload validity.
|
|
2227
|
+
*
|
|
2228
|
+
* This function accepts raw CMCD header strings, decodes each shard
|
|
2229
|
+
* internally, verifies that each key is placed in its correct header shard,
|
|
2230
|
+
* then merges all shards and runs full payload validation (keys, values, and
|
|
2231
|
+
* structure) on the merged data.
|
|
2232
|
+
*
|
|
2233
|
+
* @example
|
|
2234
|
+
* {@includeCode ../test/validateCmcdHeaders.test.ts#example}
|
|
2235
|
+
*
|
|
2236
|
+
* @param headers - A `Headers` instance or a record of CMCD header fields to their raw encoded string values.
|
|
2237
|
+
* @param options - Validation options (excluding `reportingMode`).
|
|
2238
|
+
* @returns The validation result including decoded data.
|
|
2239
|
+
*
|
|
2240
|
+
* @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#header-field-definition | CTA-5004-A Header Field Definition}
|
|
2241
|
+
*
|
|
2242
|
+
* @public
|
|
2243
|
+
*/
|
|
2244
|
+
function validateCmcdHeaders(headers, options) {
|
|
2245
|
+
const h = ensureHeaders(headers);
|
|
2246
|
+
const issues = [];
|
|
2247
|
+
const decoded = {};
|
|
2248
|
+
for (const headerField of CMCD_HEADER_FIELDS) {
|
|
2249
|
+
const raw = h.get(headerField);
|
|
2250
|
+
if (!raw) continue;
|
|
2251
|
+
let shard;
|
|
2252
|
+
try {
|
|
2253
|
+
shard = decodeCmcd(raw);
|
|
2254
|
+
} catch {
|
|
2255
|
+
issues.push({
|
|
2256
|
+
key: headerField,
|
|
2257
|
+
message: `Failed to decode "${headerField}" header: invalid structured field syntax.`,
|
|
2258
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2259
|
+
});
|
|
2260
|
+
continue;
|
|
2261
|
+
}
|
|
2262
|
+
decoded[headerField] = shard;
|
|
2263
|
+
for (const key of Object.keys(shard)) {
|
|
2264
|
+
if (isCmcdCustomKey(key)) continue;
|
|
2265
|
+
const expectedHeader = CMCD_HEADER_MAP[key];
|
|
2266
|
+
if (expectedHeader && expectedHeader !== headerField) issues.push({
|
|
2267
|
+
key,
|
|
2268
|
+
message: `Key "${key}" is in "${headerField}" but should be in "${expectedHeader}".`,
|
|
2269
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2270
|
+
});
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
const shardResult = {
|
|
2274
|
+
valid: issues.length === 0,
|
|
2275
|
+
issues
|
|
2276
|
+
};
|
|
2277
|
+
const merged = Object.assign({}, ...CMCD_HEADER_FIELDS.map((f) => decoded[f]).filter(Boolean));
|
|
2278
|
+
return {
|
|
2279
|
+
...mergeValidationResults(shardResult, validateCmcd(merged, {
|
|
2280
|
+
...options,
|
|
2281
|
+
reportingMode: CMCD_REQUEST_MODE
|
|
2282
|
+
})),
|
|
2283
|
+
data: merged
|
|
2284
|
+
};
|
|
2285
|
+
}
|
|
2286
|
+
|
|
2287
|
+
//#endregion
|
|
2288
|
+
//#region src/validateCmcdRequest.ts
|
|
2289
|
+
/**
|
|
2290
|
+
* Validates CMCD data from a request as a request-mode payload.
|
|
2291
|
+
*
|
|
2292
|
+
* Accepts a
|
|
2293
|
+
* {@link https://developer.mozilla.org/en-US/docs/Web/API/Request | Request}
|
|
2294
|
+
* object or an {@link @svta/cml-utils!HttpRequest | HttpRequest} object.
|
|
2295
|
+
*
|
|
2296
|
+
* The function checks for CMCD data in the HTTP headers first. If CMCD
|
|
2297
|
+
* headers are found, validation includes shard-placement checks via
|
|
2298
|
+
* {@link validateCmcdHeaders}. Otherwise, the CMCD query parameter is
|
|
2299
|
+
* extracted from the URL and validated.
|
|
2300
|
+
*
|
|
2301
|
+
* @param request - A `Request` or `HttpRequest` to validate.
|
|
2302
|
+
* @param options - Validation options (excluding `reportingMode`).
|
|
2303
|
+
* @returns The validation result including decoded data.
|
|
2304
|
+
*
|
|
2305
|
+
* @example
|
|
2306
|
+
* {@includeCode ../test/validateCmcdRequest.test.ts#example}
|
|
2307
|
+
*
|
|
2308
|
+
* @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#request-mode | CTA-5004-A Request Mode}
|
|
2309
|
+
*
|
|
2310
|
+
* @public
|
|
2311
|
+
*/
|
|
2312
|
+
function validateCmcdRequest(request, options) {
|
|
2313
|
+
const headers = extractHeaderRecord(request.headers);
|
|
2314
|
+
if (headers) return validateCmcdHeaders(headers, options);
|
|
2315
|
+
const param = new URL(request.url).searchParams.get(CMCD_PARAM);
|
|
2316
|
+
if (!param) return {
|
|
2317
|
+
valid: false,
|
|
2318
|
+
issues: [{
|
|
2319
|
+
message: "No CMCD data found in request headers or query parameters.",
|
|
2320
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2321
|
+
}],
|
|
2322
|
+
data: {}
|
|
2323
|
+
};
|
|
2324
|
+
let data;
|
|
2325
|
+
try {
|
|
2326
|
+
data = decodeCmcd(param);
|
|
2327
|
+
} catch {
|
|
2328
|
+
return {
|
|
2329
|
+
valid: false,
|
|
2330
|
+
issues: [{
|
|
2331
|
+
message: "Failed to decode CMCD query parameter: invalid structured field syntax.",
|
|
2332
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2333
|
+
}],
|
|
2334
|
+
data: {}
|
|
2335
|
+
};
|
|
2336
|
+
}
|
|
2337
|
+
return {
|
|
2338
|
+
...validateCmcd(data, {
|
|
2339
|
+
...options,
|
|
2340
|
+
reportingMode: CMCD_REQUEST_MODE
|
|
2341
|
+
}),
|
|
2342
|
+
data
|
|
2343
|
+
};
|
|
2344
|
+
}
|
|
2345
|
+
function extractHeaderRecord(headers) {
|
|
2346
|
+
if (!headers) return;
|
|
2347
|
+
const h = ensureHeaders(headers);
|
|
2348
|
+
const result = {};
|
|
2349
|
+
let found = false;
|
|
2350
|
+
for (const field of CMCD_HEADER_FIELDS) {
|
|
2351
|
+
const value = h.get(field);
|
|
2352
|
+
if (value) {
|
|
2353
|
+
result[field] = value;
|
|
2354
|
+
found = true;
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
return found ? result : void 0;
|
|
1426
2358
|
}
|
|
1427
2359
|
|
|
1428
2360
|
//#endregion
|
|
1429
|
-
export { CMCD_DEFAULT_TIME_INTERVAL, CMCD_EVENT_AD_BREAK_END, CMCD_EVENT_AD_BREAK_START, CMCD_EVENT_AD_END, CMCD_EVENT_AD_START, CMCD_EVENT_BACKGROUNDED_MODE, CMCD_EVENT_BITRATE_CHANGE, CMCD_EVENT_CONTENT_ID, CMCD_EVENT_CUSTOM_EVENT, CMCD_EVENT_ERROR, CMCD_EVENT_KEYS, CMCD_EVENT_MODE, CMCD_EVENT_MUTE, CMCD_EVENT_PLAYER_COLLAPSE, CMCD_EVENT_PLAYER_EXPAND, CMCD_EVENT_PLAY_STATE, CMCD_EVENT_RESPONSE_RECEIVED, CMCD_EVENT_SKIP, CMCD_EVENT_TIME_INTERVAL, CMCD_EVENT_UNMUTE, CMCD_FORMATTER_MAP, CMCD_HEADERS, CMCD_HEADER_MAP, CMCD_JSON, CMCD_KEYS, CMCD_OBJECT, CMCD_PARAM, CMCD_QUERY, CMCD_REQUEST, CMCD_REQUEST_KEYS, CMCD_REQUEST_MODE, CMCD_RESPONSE_KEYS, CMCD_SESSION, CMCD_STATUS, CMCD_V1, CMCD_V1_KEYS, CMCD_V2, CmcdEventType, CmcdHeaderField, CmcdObjectType, CmcdPlayerState, CmcdReporter, CmcdReportingMode, CmcdStreamType, CmcdStreamingFormat, CmcdTransmissionMode, appendCmcdHeaders, appendCmcdQuery, decodeCmcd, encodeCmcd, fromCmcdHeaders, fromCmcdQuery, fromCmcdUrl, groupCmcdHeaders, isCmcdCustomKey, isCmcdEventKey, isCmcdRequestKey, isCmcdResponseReceivedKey, isCmcdV1Key, prepareCmcdData, toCmcdHeaders, toCmcdQuery, toCmcdUrl, toCmcdValue };
|
|
2361
|
+
export { CMCD_DEFAULT_TIME_INTERVAL, CMCD_EVENT_AD_BREAK_END, CMCD_EVENT_AD_BREAK_START, CMCD_EVENT_AD_END, CMCD_EVENT_AD_START, CMCD_EVENT_BACKGROUNDED_MODE, CMCD_EVENT_BITRATE_CHANGE, CMCD_EVENT_CONTENT_ID, CMCD_EVENT_CUSTOM_EVENT, CMCD_EVENT_ERROR, CMCD_EVENT_KEYS, CMCD_EVENT_MODE, CMCD_EVENT_MUTE, CMCD_EVENT_PLAYER_COLLAPSE, CMCD_EVENT_PLAYER_EXPAND, CMCD_EVENT_PLAY_STATE, CMCD_EVENT_RESPONSE_RECEIVED, CMCD_EVENT_SKIP, CMCD_EVENT_TIME_INTERVAL, CMCD_EVENT_UNMUTE, CMCD_FORMATTER_MAP, CMCD_HEADERS, CMCD_HEADER_FIELDS, CMCD_HEADER_MAP, CMCD_JSON, CMCD_KEYS, CMCD_MIME_TYPE, CMCD_OBJECT, CMCD_PARAM, CMCD_QUERY, CMCD_REQUEST, CMCD_REQUEST_KEYS, CMCD_REQUEST_MODE, CMCD_RESPONSE_KEYS, CMCD_SESSION, CMCD_STATUS, CMCD_V1, CMCD_V1_KEYS, CMCD_V2, CMCD_VALIDATION_SEVERITY_ERROR, CMCD_VALIDATION_SEVERITY_WARNING, CmcdEventType, CmcdHeaderField, CmcdObjectType, CmcdPlayerState, CmcdReporter, CmcdReportingMode, CmcdStreamType, CmcdStreamingFormat, CmcdTransmissionMode, CmcdValidationSeverity, appendCmcdHeaders, appendCmcdQuery, decodeCmcd, encodeCmcd, fromCmcdHeaders, fromCmcdQuery, fromCmcdUrl, groupCmcdHeaders, isCmcdCustomKey, isCmcdEventKey, isCmcdRequestKey, isCmcdResponseReceivedKey, isCmcdV1Data, isCmcdV1Key, isCmcdV2Data, prepareCmcdData, toCmcdHeaders, toCmcdQuery, toCmcdUrl, toCmcdValue, validateCmcd, validateCmcdEventReport, validateCmcdEvents, validateCmcdHeaders, validateCmcdKeys, validateCmcdRequest, validateCmcdStructure, validateCmcdValues };
|
|
1430
2362
|
//# sourceMappingURL=index.js.map
|