@svta/cml-cmcd 2.1.2 → 2.2.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 +403 -34
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +949 -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
|
*
|
|
@@ -297,6 +297,7 @@ function isCmcdCustomKey(key) {
|
|
|
297
297
|
|
|
298
298
|
//#endregion
|
|
299
299
|
//#region src/isCmcdRequestKey.ts
|
|
300
|
+
const CMCD_REQUEST_KEY_SET = new Set(CMCD_REQUEST_KEYS);
|
|
300
301
|
/**
|
|
301
302
|
* Check if a key is a valid CMCD request key.
|
|
302
303
|
*
|
|
@@ -310,7 +311,7 @@ function isCmcdCustomKey(key) {
|
|
|
310
311
|
* {@includeCode ../test/isCmcdRequestKey.test.ts#example}
|
|
311
312
|
*/
|
|
312
313
|
function isCmcdRequestKey(key) {
|
|
313
|
-
return
|
|
314
|
+
return CMCD_REQUEST_KEY_SET.has(key) || isCmcdCustomKey(key);
|
|
314
315
|
}
|
|
315
316
|
|
|
316
317
|
//#endregion
|
|
@@ -333,6 +334,7 @@ const CMCD_RESPONSE_KEYS = [
|
|
|
333
334
|
|
|
334
335
|
//#endregion
|
|
335
336
|
//#region src/isCmcdResponseReceivedKey.ts
|
|
337
|
+
const CMCD_RESPONSE_KEY_SET = new Set(CMCD_RESPONSE_KEYS);
|
|
336
338
|
/**
|
|
337
339
|
* Check if a key is a valid CMCD response key.
|
|
338
340
|
*
|
|
@@ -346,11 +348,12 @@ const CMCD_RESPONSE_KEYS = [
|
|
|
346
348
|
* {@includeCode ../test/isCmcdResponseReceivedKey.test.ts#example}
|
|
347
349
|
*/
|
|
348
350
|
function isCmcdResponseReceivedKey(key) {
|
|
349
|
-
return
|
|
351
|
+
return CMCD_RESPONSE_KEY_SET.has(key);
|
|
350
352
|
}
|
|
351
353
|
|
|
352
354
|
//#endregion
|
|
353
355
|
//#region src/isCmcdEventKey.ts
|
|
356
|
+
const CMCD_EVENT_KEY_SET = new Set(CMCD_EVENT_KEYS);
|
|
354
357
|
/**
|
|
355
358
|
* Check if a key is a valid CMCD event key.
|
|
356
359
|
*
|
|
@@ -364,9 +367,35 @@ function isCmcdResponseReceivedKey(key) {
|
|
|
364
367
|
* {@includeCode ../test/isCmcdEventKey.test.ts#example}
|
|
365
368
|
*/
|
|
366
369
|
function isCmcdEventKey(key) {
|
|
367
|
-
return isCmcdRequestKey(key) || isCmcdResponseReceivedKey(key) ||
|
|
370
|
+
return isCmcdRequestKey(key) || isCmcdResponseReceivedKey(key) || CMCD_EVENT_KEY_SET.has(key);
|
|
368
371
|
}
|
|
369
372
|
|
|
373
|
+
//#endregion
|
|
374
|
+
//#region src/CMCD_INNER_LIST_KEYS.ts
|
|
375
|
+
/**
|
|
376
|
+
* Keys that are inner lists in V2 but plain scalars in V1.
|
|
377
|
+
*
|
|
378
|
+
* Used by both encoding (down-conversion) and decoding (up-conversion).
|
|
379
|
+
*
|
|
380
|
+
* @internal
|
|
381
|
+
*/
|
|
382
|
+
const CMCD_INNER_LIST_KEYS = new Set([
|
|
383
|
+
"ab",
|
|
384
|
+
"bl",
|
|
385
|
+
"br",
|
|
386
|
+
"bsa",
|
|
387
|
+
"bsd",
|
|
388
|
+
"bsda",
|
|
389
|
+
"lab",
|
|
390
|
+
"lb",
|
|
391
|
+
"mtp",
|
|
392
|
+
"pb",
|
|
393
|
+
"tab",
|
|
394
|
+
"tb",
|
|
395
|
+
"tbl",
|
|
396
|
+
"tpb"
|
|
397
|
+
]);
|
|
398
|
+
|
|
370
399
|
//#endregion
|
|
371
400
|
//#region src/CMCD_V1_KEYS.ts
|
|
372
401
|
/**
|
|
@@ -397,6 +426,7 @@ const CMCD_V1_KEYS = [
|
|
|
397
426
|
|
|
398
427
|
//#endregion
|
|
399
428
|
//#region src/isCmcdV1Key.ts
|
|
429
|
+
const CMCD_V1_KEY_SET$1 = new Set(CMCD_V1_KEYS);
|
|
400
430
|
/**
|
|
401
431
|
* Filter function for CMCD v1 keys.
|
|
402
432
|
*
|
|
@@ -410,11 +440,18 @@ const CMCD_V1_KEYS = [
|
|
|
410
440
|
* {@includeCode ../test/isCmcdV1Key.test.ts#example}
|
|
411
441
|
*/
|
|
412
442
|
function isCmcdV1Key(key) {
|
|
413
|
-
return
|
|
443
|
+
return CMCD_V1_KEY_SET$1.has(key) || isCmcdCustomKey(key);
|
|
414
444
|
}
|
|
415
445
|
|
|
416
446
|
//#endregion
|
|
417
447
|
//#region src/isTokenField.ts
|
|
448
|
+
const TOKEN_FIELDS = new Set([
|
|
449
|
+
"ot",
|
|
450
|
+
"sf",
|
|
451
|
+
"st",
|
|
452
|
+
"e",
|
|
453
|
+
"sta"
|
|
454
|
+
]);
|
|
418
455
|
/**
|
|
419
456
|
* Checks if the given key is a token field.
|
|
420
457
|
*
|
|
@@ -425,13 +462,7 @@ function isCmcdV1Key(key) {
|
|
|
425
462
|
* @internal
|
|
426
463
|
*/
|
|
427
464
|
function isTokenField(key) {
|
|
428
|
-
return
|
|
429
|
-
"ot",
|
|
430
|
-
"sf",
|
|
431
|
-
"st",
|
|
432
|
-
"e",
|
|
433
|
-
"sta"
|
|
434
|
-
].includes(key);
|
|
465
|
+
return TOKEN_FIELDS.has(key);
|
|
435
466
|
}
|
|
436
467
|
|
|
437
468
|
//#endregion
|
|
@@ -457,25 +488,6 @@ const filterMap = {
|
|
|
457
488
|
[CMCD_REQUEST_MODE]: isCmcdRequestKey
|
|
458
489
|
};
|
|
459
490
|
/**
|
|
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
491
|
* Unwrap an inner list or SfItem value to a plain scalar.
|
|
480
492
|
*/
|
|
481
493
|
function unwrapValue(value, ot) {
|
|
@@ -507,7 +519,7 @@ function downConvertToV1(obj) {
|
|
|
507
519
|
result["nor"] = first.value;
|
|
508
520
|
if (first.params?.r) result["nrr"] = first.params.r;
|
|
509
521
|
} else result["nor"] = first;
|
|
510
|
-
} else if (
|
|
522
|
+
} else if (CMCD_INNER_LIST_KEYS.has(key)) result[key] = unwrapValue(value, obj["ot"]);
|
|
511
523
|
else result[key] = value;
|
|
512
524
|
}
|
|
513
525
|
return result;
|
|
@@ -527,37 +539,37 @@ function prepareCmcdData(obj, options = {}) {
|
|
|
527
539
|
const reportingMode = options.reportingMode || CMCD_REQUEST_MODE;
|
|
528
540
|
const data = version === 1 ? downConvertToV1(obj) : obj;
|
|
529
541
|
const keyFilter = version === 1 ? isCmcdV1Key : filterMap[reportingMode];
|
|
530
|
-
let keys
|
|
531
|
-
if (data["e"] && data["e"] !== CMCD_EVENT_RESPONSE_RECEIVED) keys
|
|
542
|
+
let keys = Object.keys(data).filter(keyFilter);
|
|
543
|
+
if (data["e"] && data["e"] !== CMCD_EVENT_RESPONSE_RECEIVED) keys = keys.filter((key) => !isCmcdResponseReceivedKey(key));
|
|
532
544
|
const filter = options.filter;
|
|
533
|
-
if (typeof filter === "function") keys
|
|
545
|
+
if (typeof filter === "function") keys = keys.filter(filter);
|
|
534
546
|
const isEventMode = reportingMode === CMCD_EVENT_MODE;
|
|
535
547
|
if (isEventMode) {
|
|
536
548
|
const eventType = data["e"];
|
|
537
|
-
if (!keys
|
|
538
|
-
if (!keys
|
|
539
|
-
if (!keys
|
|
549
|
+
if (!keys.includes("e") && eventType != null) keys.push("e");
|
|
550
|
+
if (!keys.includes("ts")) keys.push("ts");
|
|
551
|
+
if (!keys.includes("cen") && data["cen"] != null && eventType === CMCD_EVENT_CUSTOM_EVENT) keys.push("cen");
|
|
540
552
|
}
|
|
541
|
-
if (keys
|
|
542
|
-
if (version > 1 && !keys
|
|
543
|
-
const formatters = Object.assign({}, CMCD_FORMATTER_MAP, options.formatters);
|
|
553
|
+
if (keys.length === 0) return results;
|
|
554
|
+
if (version > 1 && !keys.includes("v")) keys.push("v");
|
|
544
555
|
const formatterOptions = {
|
|
545
556
|
version,
|
|
546
557
|
reportingMode,
|
|
547
558
|
baseUrl: options.baseUrl
|
|
548
559
|
};
|
|
549
|
-
keys
|
|
560
|
+
keys.sort();
|
|
561
|
+
for (const key of keys) {
|
|
550
562
|
let value = data[key];
|
|
551
|
-
const formatter = formatters[key];
|
|
563
|
+
const formatter = options.formatters?.[key] ?? CMCD_FORMATTER_MAP[key];
|
|
552
564
|
if (typeof formatter === "function") value = formatter(value, formatterOptions);
|
|
553
|
-
if (key === "v") if (version === 1)
|
|
565
|
+
if (key === "v") if (version === 1) continue;
|
|
554
566
|
else value = version;
|
|
555
|
-
if (key
|
|
567
|
+
if (key === "pr" && value === 1) continue;
|
|
556
568
|
if (isEventMode && key === "ts" && !Number.isFinite(value)) value = Date.now();
|
|
557
|
-
if (!isValid(value))
|
|
569
|
+
if (!isValid(value)) continue;
|
|
558
570
|
if (isTokenField(key) && typeof value === "string") value = new SfToken(value);
|
|
559
571
|
results[key] = value;
|
|
560
|
-
}
|
|
572
|
+
}
|
|
561
573
|
return results;
|
|
562
574
|
}
|
|
563
575
|
|
|
@@ -601,12 +613,27 @@ const CmcdHeaderField = {
|
|
|
601
613
|
SESSION: CMCD_SESSION,
|
|
602
614
|
STATUS: CMCD_STATUS
|
|
603
615
|
};
|
|
616
|
+
/**
|
|
617
|
+
* All CMCD header fields as an array.
|
|
618
|
+
*
|
|
619
|
+
* @public
|
|
620
|
+
*/
|
|
621
|
+
const CMCD_HEADER_FIELDS = [
|
|
622
|
+
CMCD_OBJECT,
|
|
623
|
+
CMCD_REQUEST,
|
|
624
|
+
CMCD_SESSION,
|
|
625
|
+
CMCD_STATUS
|
|
626
|
+
];
|
|
604
627
|
|
|
605
628
|
//#endregion
|
|
606
629
|
//#region src/CMCD_HEADER_MAP.ts
|
|
607
630
|
/**
|
|
608
631
|
* The map of CMCD keys to their appropriate header shard.
|
|
609
632
|
*
|
|
633
|
+
* Note: Event-only keys (e, ts, cen, h) and response-received keys
|
|
634
|
+
* (rc, ttfb, ttlb, url, etc.) are intentionally absent. They are
|
|
635
|
+
* transmitted via the event-mode POST body, not HTTP headers.
|
|
636
|
+
*
|
|
610
637
|
* @public
|
|
611
638
|
*/
|
|
612
639
|
const CMCD_HEADER_MAP = {
|
|
@@ -672,14 +699,14 @@ function createHeaderMap(headerMap) {
|
|
|
672
699
|
function groupCmcdHeaders(cmcd, customHeaderMap) {
|
|
673
700
|
const result = {};
|
|
674
701
|
if (!cmcd) return result;
|
|
675
|
-
const keys
|
|
702
|
+
const keys = Object.keys(cmcd);
|
|
676
703
|
const custom = customHeaderMap ? createHeaderMap(customHeaderMap) : {};
|
|
677
|
-
|
|
704
|
+
for (const key of keys) {
|
|
678
705
|
const field = CMCD_HEADER_MAP[key] || custom[key] || CmcdHeaderField.REQUEST;
|
|
679
|
-
const data =
|
|
706
|
+
const data = result[field] ??= {};
|
|
680
707
|
data[key] = cmcd[key];
|
|
681
|
-
|
|
682
|
-
|
|
708
|
+
}
|
|
709
|
+
return result;
|
|
683
710
|
}
|
|
684
711
|
|
|
685
712
|
//#endregion
|
|
@@ -696,11 +723,11 @@ function groupCmcdHeaders(cmcd, customHeaderMap) {
|
|
|
696
723
|
function toPreparedCmcdHeaders(data, customHeaderMap) {
|
|
697
724
|
const result = {};
|
|
698
725
|
const shards = groupCmcdHeaders(data, customHeaderMap);
|
|
699
|
-
|
|
726
|
+
for (const [field, value] of Object.entries(shards)) {
|
|
700
727
|
const shard = encodeSfDict(value, { whitespace: false });
|
|
701
|
-
if (shard)
|
|
702
|
-
|
|
703
|
-
|
|
728
|
+
if (shard) result[field] = shard;
|
|
729
|
+
}
|
|
730
|
+
return result;
|
|
704
731
|
}
|
|
705
732
|
|
|
706
733
|
//#endregion
|
|
@@ -859,18 +886,17 @@ const CMCD_DEFAULT_TIME_INTERVAL = 30;
|
|
|
859
886
|
|
|
860
887
|
//#endregion
|
|
861
888
|
//#region src/CMCD_KEYS.ts
|
|
862
|
-
const keySet = new Set([
|
|
863
|
-
...CMCD_V1_KEYS,
|
|
864
|
-
...CMCD_REQUEST_KEYS,
|
|
865
|
-
...CMCD_RESPONSE_KEYS,
|
|
866
|
-
...CMCD_EVENT_KEYS
|
|
867
|
-
]);
|
|
868
889
|
/**
|
|
869
890
|
* A list of all CMCD keys.
|
|
870
891
|
*
|
|
871
892
|
* @public
|
|
872
893
|
*/
|
|
873
|
-
const CMCD_KEYS =
|
|
894
|
+
const CMCD_KEYS = [
|
|
895
|
+
...CMCD_V1_KEYS,
|
|
896
|
+
...CMCD_REQUEST_KEYS,
|
|
897
|
+
...CMCD_RESPONSE_KEYS,
|
|
898
|
+
...CMCD_EVENT_KEYS
|
|
899
|
+
].filter((key, index, arr) => arr.indexOf(key) === index);
|
|
874
900
|
|
|
875
901
|
//#endregion
|
|
876
902
|
//#region src/CMCD_V1.ts
|
|
@@ -963,11 +989,11 @@ const CmcdTransmissionMode = {
|
|
|
963
989
|
//#endregion
|
|
964
990
|
//#region src/CmcdReporter.ts
|
|
965
991
|
function createEncodingOptions(reportingMode, config, baseUrl) {
|
|
966
|
-
const
|
|
992
|
+
const enabledKeySet = new Set(config.enabledKeys ?? []);
|
|
967
993
|
return {
|
|
968
994
|
version: config.version || CMCD_V2,
|
|
969
995
|
reportingMode,
|
|
970
|
-
filter: (key) =>
|
|
996
|
+
filter: (key) => enabledKeySet.has(key),
|
|
971
997
|
baseUrl
|
|
972
998
|
};
|
|
973
999
|
}
|
|
@@ -1034,6 +1060,10 @@ var CmcdReporter = class {
|
|
|
1034
1060
|
}
|
|
1035
1061
|
/**
|
|
1036
1062
|
* Starts the CMCD reporter. Called by the player when the reporter is enabled.
|
|
1063
|
+
*
|
|
1064
|
+
* Note: This fires an initial time-interval event immediately (synchronously)
|
|
1065
|
+
* before the first interval elapses. Ensure CMCD data (sid, cid, etc.) is
|
|
1066
|
+
* populated before calling start().
|
|
1037
1067
|
*/
|
|
1038
1068
|
start() {
|
|
1039
1069
|
this.eventTargets.forEach((target, config) => {
|
|
@@ -1071,10 +1101,7 @@ var CmcdReporter = class {
|
|
|
1071
1101
|
* @param data - The data to update.
|
|
1072
1102
|
*/
|
|
1073
1103
|
update(data) {
|
|
1074
|
-
if (data.sid && data.sid !== this.data.sid)
|
|
1075
|
-
this.resetSession();
|
|
1076
|
-
return;
|
|
1077
|
-
}
|
|
1104
|
+
if (data.sid && data.sid !== this.data.sid) this.resetSession();
|
|
1078
1105
|
if (data.msd && !isNaN(data.msd)) this.msd = data.msd;
|
|
1079
1106
|
this.data = {
|
|
1080
1107
|
...this.data,
|
|
@@ -1189,7 +1216,7 @@ var CmcdReporter = class {
|
|
|
1189
1216
|
* Creates a new request with the CMCD request report data applied. Called by the player
|
|
1190
1217
|
* before sending the request.
|
|
1191
1218
|
*
|
|
1192
|
-
* @param
|
|
1219
|
+
* @param request - The request to apply the CMCD request report to.
|
|
1193
1220
|
* @param data - The data to apply to the request. This data only
|
|
1194
1221
|
* applies to this request report. Persistent data
|
|
1195
1222
|
* should be updated using `update()`.
|
|
@@ -1245,7 +1272,9 @@ var CmcdReporter = class {
|
|
|
1245
1272
|
if (queue.length < config.batchSize && !flush) return;
|
|
1246
1273
|
const deleteCount = flush ? queue.length : config.batchSize;
|
|
1247
1274
|
const events = queue.splice(0, deleteCount);
|
|
1248
|
-
this.sendEventReport(config, events)
|
|
1275
|
+
this.sendEventReport(config, events).catch(() => {
|
|
1276
|
+
target.queue.unshift(...events);
|
|
1277
|
+
});
|
|
1249
1278
|
reprocess ||= queue.length > 0;
|
|
1250
1279
|
});
|
|
1251
1280
|
if (reprocess) this.processEventTargets();
|
|
@@ -1262,17 +1291,10 @@ var CmcdReporter = class {
|
|
|
1262
1291
|
url: target.url,
|
|
1263
1292
|
method: "POST",
|
|
1264
1293
|
headers: { "Content-Type": "text/cmcd" },
|
|
1265
|
-
body: data.
|
|
1294
|
+
body: data.map((item) => encodeCmcd(item, options)).join("\n") + "\n"
|
|
1266
1295
|
});
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
this.eventTargets.delete(target);
|
|
1270
|
-
break;
|
|
1271
|
-
case 429: break;
|
|
1272
|
-
default:
|
|
1273
|
-
if (status > 499 && status < 600) {}
|
|
1274
|
-
break;
|
|
1275
|
-
}
|
|
1296
|
+
if (status === 410) this.eventTargets.delete(target);
|
|
1297
|
+
else if (status === 429 || status > 499 && status < 600) throw new Error(`Event report failed with status ${status}`);
|
|
1276
1298
|
}
|
|
1277
1299
|
/**
|
|
1278
1300
|
* Resets the session related data. Called when the session ID changes.
|
|
@@ -1315,116 +1337,909 @@ const CmcdStreamType = {
|
|
|
1315
1337
|
LOW_LATENCY: "ll"
|
|
1316
1338
|
};
|
|
1317
1339
|
|
|
1340
|
+
//#endregion
|
|
1341
|
+
//#region src/upConvertToV2.ts
|
|
1342
|
+
/**
|
|
1343
|
+
* Up-convert V1 CMCD data to V2 format.
|
|
1344
|
+
*
|
|
1345
|
+
* - Wraps plain scalar values in arrays for inner-list keys.
|
|
1346
|
+
* - Wraps `nor` string in an array.
|
|
1347
|
+
*
|
|
1348
|
+
* If the data is already V2 (has `v: 2`), it is returned unchanged.
|
|
1349
|
+
*
|
|
1350
|
+
* @internal
|
|
1351
|
+
*/
|
|
1352
|
+
function upConvertToV2(obj) {
|
|
1353
|
+
if (obj["v"] === CMCD_V2) return obj;
|
|
1354
|
+
const result = {};
|
|
1355
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1356
|
+
if (value == null) {
|
|
1357
|
+
result[key] = value;
|
|
1358
|
+
continue;
|
|
1359
|
+
}
|
|
1360
|
+
if (CMCD_INNER_LIST_KEYS.has(key) && !Array.isArray(value)) result[key] = [value];
|
|
1361
|
+
else if (key === "nor" && typeof value === "string") result[key] = [value];
|
|
1362
|
+
else result[key] = value;
|
|
1363
|
+
}
|
|
1364
|
+
return result;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1318
1367
|
//#endregion
|
|
1319
1368
|
//#region src/decodeCmcd.ts
|
|
1320
1369
|
function reduceValue(value) {
|
|
1321
1370
|
if (Array.isArray(value)) return value.map(reduceValue);
|
|
1322
1371
|
if (typeof value === "symbol") return symbolToStr(value);
|
|
1323
1372
|
if (value instanceof SfItem && !value.params) return reduceValue(value.value);
|
|
1324
|
-
if (typeof value === "string") return
|
|
1373
|
+
if (typeof value === "string") return value;
|
|
1325
1374
|
return value;
|
|
1326
1375
|
}
|
|
1376
|
+
function decodeCmcd(cmcd, options) {
|
|
1377
|
+
if (!cmcd) return {};
|
|
1378
|
+
const sfDict = decodeSfDict(cmcd);
|
|
1379
|
+
const result = {};
|
|
1380
|
+
for (const [key, item] of Object.entries(sfDict)) result[key] = reduceValue(item.value);
|
|
1381
|
+
if (options?.convertToLatest) return upConvertToV2(result);
|
|
1382
|
+
return result;
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
//#endregion
|
|
1386
|
+
//#region src/ensureHeaders.ts
|
|
1387
|
+
/**
|
|
1388
|
+
* Converts a record of header fields to a `Headers` instance if necessary.
|
|
1389
|
+
*
|
|
1390
|
+
* @param headers - A `Headers` instance or a plain record of header fields.
|
|
1391
|
+
* @returns A `Headers` instance.
|
|
1392
|
+
*
|
|
1393
|
+
* @internal
|
|
1394
|
+
*/
|
|
1395
|
+
function ensureHeaders(headers) {
|
|
1396
|
+
return headers instanceof Headers ? headers : new Headers(headers);
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
//#endregion
|
|
1400
|
+
//#region src/fromCmcdHeaders.ts
|
|
1401
|
+
function fromCmcdHeaders(headers, options) {
|
|
1402
|
+
const h = ensureHeaders(headers);
|
|
1403
|
+
const result = {};
|
|
1404
|
+
for (const field of CMCD_HEADER_FIELDS) {
|
|
1405
|
+
const value = h.get(field);
|
|
1406
|
+
if (value) Object.assign(result, decodeCmcd(value));
|
|
1407
|
+
}
|
|
1408
|
+
if (options?.convertToLatest) return upConvertToV2(result);
|
|
1409
|
+
return result;
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
//#endregion
|
|
1413
|
+
//#region src/fromCmcdQuery.ts
|
|
1414
|
+
function fromCmcdQuery(query, options) {
|
|
1415
|
+
if (typeof query === "string") query = new URLSearchParams(query);
|
|
1416
|
+
return decodeCmcd(query.get(CMCD_PARAM) ?? "", options);
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
//#endregion
|
|
1420
|
+
//#region src/fromCmcdUrl.ts
|
|
1421
|
+
function fromCmcdUrl(url, options) {
|
|
1422
|
+
return decodeCmcd(decodeURIComponent(url.replace(/^CMCD=/, "")), options);
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
//#endregion
|
|
1426
|
+
//#region src/isCmcdV1Data.ts
|
|
1327
1427
|
/**
|
|
1328
|
-
*
|
|
1428
|
+
* Check if a CMCD data object is version 1.
|
|
1329
1429
|
*
|
|
1330
|
-
* @param
|
|
1430
|
+
* @param data - The CMCD data object to check.
|
|
1331
1431
|
*
|
|
1332
|
-
* @returns
|
|
1432
|
+
* @returns `true` if the data is version 1, `false` otherwise.
|
|
1333
1433
|
*
|
|
1334
1434
|
* @public
|
|
1335
1435
|
*
|
|
1336
1436
|
* @example
|
|
1337
|
-
* {@includeCode ../test/
|
|
1437
|
+
* {@includeCode ../test/isCmcdV1Data.test.ts#example}
|
|
1338
1438
|
*/
|
|
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
|
-
}, {});
|
|
1439
|
+
function isCmcdV1Data(data) {
|
|
1440
|
+
return data.v !== CMCD_V2;
|
|
1346
1441
|
}
|
|
1347
1442
|
|
|
1348
1443
|
//#endregion
|
|
1349
|
-
//#region src/
|
|
1350
|
-
const keys = [
|
|
1351
|
-
CMCD_OBJECT,
|
|
1352
|
-
CMCD_REQUEST,
|
|
1353
|
-
CMCD_SESSION,
|
|
1354
|
-
CMCD_STATUS
|
|
1355
|
-
];
|
|
1444
|
+
//#region src/isCmcdV2Data.ts
|
|
1356
1445
|
/**
|
|
1357
|
-
*
|
|
1446
|
+
* Check if a CMCD data object is version 2.
|
|
1358
1447
|
*
|
|
1359
|
-
* @param
|
|
1448
|
+
* @param data - The CMCD data object to check.
|
|
1360
1449
|
*
|
|
1361
|
-
* @returns
|
|
1450
|
+
* @returns `true` if the data is version 2, `false` otherwise.
|
|
1362
1451
|
*
|
|
1363
1452
|
* @public
|
|
1364
1453
|
*
|
|
1365
1454
|
* @example
|
|
1366
|
-
* {@includeCode ../test/
|
|
1455
|
+
* {@includeCode ../test/isCmcdV2Data.test.ts#example}
|
|
1367
1456
|
*/
|
|
1368
|
-
function
|
|
1369
|
-
|
|
1370
|
-
return keys.reduce((acc, key) => {
|
|
1371
|
-
const value = headers.get(key);
|
|
1372
|
-
return Object.assign(acc, decodeCmcd(value));
|
|
1373
|
-
}, {});
|
|
1457
|
+
function isCmcdV2Data(data) {
|
|
1458
|
+
return data.v === CMCD_V2;
|
|
1374
1459
|
}
|
|
1375
1460
|
|
|
1376
1461
|
//#endregion
|
|
1377
|
-
//#region src/
|
|
1462
|
+
//#region src/toCmcdValue.ts
|
|
1378
1463
|
/**
|
|
1379
|
-
*
|
|
1464
|
+
* Convert a value to a CMCD value.
|
|
1380
1465
|
*
|
|
1381
|
-
* @param
|
|
1466
|
+
* @param value - The value to convert to a CMCD value.
|
|
1467
|
+
* @param params - The parameters to convert to a CMCD value.
|
|
1468
|
+
* @returns The CMCD value.
|
|
1469
|
+
*
|
|
1470
|
+
* @public
|
|
1471
|
+
*/
|
|
1472
|
+
function toCmcdValue(value, params) {
|
|
1473
|
+
return new SfItem(value, params);
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
//#endregion
|
|
1477
|
+
//#region src/CmcdValidationSeverity.ts
|
|
1478
|
+
/**
|
|
1479
|
+
* CMCD validation severity level: error.
|
|
1480
|
+
*
|
|
1481
|
+
* @public
|
|
1482
|
+
*/
|
|
1483
|
+
const CMCD_VALIDATION_SEVERITY_ERROR = "error";
|
|
1484
|
+
/**
|
|
1485
|
+
* CMCD validation severity level: warning.
|
|
1382
1486
|
*
|
|
1383
|
-
* @
|
|
1487
|
+
* @public
|
|
1488
|
+
*/
|
|
1489
|
+
const CMCD_VALIDATION_SEVERITY_WARNING = "warning";
|
|
1490
|
+
/**
|
|
1491
|
+
* CMCD validation severity level.
|
|
1384
1492
|
*
|
|
1385
1493
|
* @public
|
|
1494
|
+
*/
|
|
1495
|
+
const CmcdValidationSeverity = {
|
|
1496
|
+
ERROR: CMCD_VALIDATION_SEVERITY_ERROR,
|
|
1497
|
+
WARNING: CMCD_VALIDATION_SEVERITY_WARNING
|
|
1498
|
+
};
|
|
1499
|
+
|
|
1500
|
+
//#endregion
|
|
1501
|
+
//#region src/mergeValidationResults.ts
|
|
1502
|
+
/**
|
|
1503
|
+
* Merges multiple validation results into a single result.
|
|
1504
|
+
*
|
|
1505
|
+
* @internal
|
|
1506
|
+
*/
|
|
1507
|
+
function mergeValidationResults(...results) {
|
|
1508
|
+
const issues = results.flatMap((r) => r.issues);
|
|
1509
|
+
return {
|
|
1510
|
+
valid: issues.every((i) => i.severity !== CMCD_VALIDATION_SEVERITY_ERROR),
|
|
1511
|
+
issues
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
//#endregion
|
|
1516
|
+
//#region src/resolveVersion.ts
|
|
1517
|
+
/**
|
|
1518
|
+
* Resolves the CMCD version from explicit options, the payload's `v` key, or the default (v1).
|
|
1519
|
+
*
|
|
1520
|
+
* @internal
|
|
1521
|
+
*/
|
|
1522
|
+
function resolveVersion(data, options) {
|
|
1523
|
+
if (options?.version === 1 || options?.version === 2) return options.version;
|
|
1524
|
+
const payloadVersion = data["v"];
|
|
1525
|
+
if (payloadVersion === 1 || payloadVersion === 2) return payloadVersion;
|
|
1526
|
+
return CMCD_V1;
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
//#endregion
|
|
1530
|
+
//#region src/validateCmcdKeys.ts
|
|
1531
|
+
const CMCD_V1_KEY_SET = new Set(CMCD_V1_KEYS);
|
|
1532
|
+
const CMCD_KEY_SET = new Set(CMCD_KEYS);
|
|
1533
|
+
/**
|
|
1534
|
+
* Validates that all keys in a CMCD payload are recognized spec keys or valid custom keys.
|
|
1386
1535
|
*
|
|
1387
1536
|
* @example
|
|
1388
|
-
* {@includeCode ../test/
|
|
1537
|
+
* {@includeCode ../test/validateCmcdKeys.test.ts#example}
|
|
1538
|
+
*
|
|
1539
|
+
* @param data - The CMCD payload to validate.
|
|
1540
|
+
* @param options - Validation options.
|
|
1541
|
+
* @returns The validation result.
|
|
1542
|
+
*
|
|
1543
|
+
* @public
|
|
1389
1544
|
*/
|
|
1390
|
-
function
|
|
1391
|
-
|
|
1392
|
-
|
|
1545
|
+
function validateCmcdKeys(data, options) {
|
|
1546
|
+
const version = resolveVersion(data, options);
|
|
1547
|
+
const validKeySet = version === CMCD_V1 ? CMCD_V1_KEY_SET : CMCD_KEY_SET;
|
|
1548
|
+
const issues = [];
|
|
1549
|
+
for (const key of Object.keys(data)) {
|
|
1550
|
+
if (isCmcdCustomKey(key)) continue;
|
|
1551
|
+
if (!validKeySet.has(key)) issues.push({
|
|
1552
|
+
key,
|
|
1553
|
+
message: `Unknown CMCD key "${key}" for version ${version}.`,
|
|
1554
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1555
|
+
});
|
|
1556
|
+
}
|
|
1557
|
+
return {
|
|
1558
|
+
valid: issues.length === 0,
|
|
1559
|
+
issues
|
|
1560
|
+
};
|
|
1393
1561
|
}
|
|
1394
1562
|
|
|
1395
1563
|
//#endregion
|
|
1396
|
-
//#region src/
|
|
1564
|
+
//#region src/validateCmcdStructure.ts
|
|
1397
1565
|
/**
|
|
1398
|
-
*
|
|
1566
|
+
* Validates the structural rules of a CMCD payload.
|
|
1399
1567
|
*
|
|
1400
|
-
* @
|
|
1568
|
+
* @example
|
|
1569
|
+
* {@includeCode ../test/validateCmcdStructure.test.ts#example}
|
|
1570
|
+
*
|
|
1571
|
+
* @param data - The CMCD payload to validate.
|
|
1572
|
+
* @param options - Validation options.
|
|
1573
|
+
* @returns The validation result.
|
|
1574
|
+
*
|
|
1575
|
+
* @public
|
|
1576
|
+
*/
|
|
1577
|
+
function validateCmcdStructure(data, options) {
|
|
1578
|
+
const version = resolveVersion(data, options);
|
|
1579
|
+
const issues = [];
|
|
1580
|
+
if (options?.reportingMode === CMCD_REQUEST_MODE) {
|
|
1581
|
+
for (const key of CMCD_EVENT_KEYS) if (key in data) issues.push({
|
|
1582
|
+
key,
|
|
1583
|
+
message: `Event key "${key}" must not be present in request mode.`,
|
|
1584
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1585
|
+
});
|
|
1586
|
+
for (const key of CMCD_RESPONSE_KEYS) if (key in data) issues.push({
|
|
1587
|
+
key,
|
|
1588
|
+
message: `Response key "${key}" must not be present in request mode.`,
|
|
1589
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1590
|
+
});
|
|
1591
|
+
}
|
|
1592
|
+
if (options?.reportingMode === CMCD_EVENT_MODE) {
|
|
1593
|
+
if (!("e" in data)) issues.push({
|
|
1594
|
+
key: "e",
|
|
1595
|
+
message: "Event mode requires the \"e\" key to be present.",
|
|
1596
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1597
|
+
});
|
|
1598
|
+
if (!("ts" in data)) issues.push({
|
|
1599
|
+
key: "ts",
|
|
1600
|
+
message: "Event mode requires the \"ts\" key to be present.",
|
|
1601
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1602
|
+
});
|
|
1603
|
+
}
|
|
1604
|
+
if ("e" in data) {
|
|
1605
|
+
if (data["e"] === CMCD_EVENT_CUSTOM_EVENT) {
|
|
1606
|
+
if (!("cen" in data)) issues.push({
|
|
1607
|
+
key: "cen",
|
|
1608
|
+
message: "Custom event (e=\"ce\") requires the \"cen\" key to be present.",
|
|
1609
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1610
|
+
});
|
|
1611
|
+
} else if ("cen" in data) issues.push({
|
|
1612
|
+
key: "cen",
|
|
1613
|
+
message: "The \"cen\" key must only be present when e=\"ce\".",
|
|
1614
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1615
|
+
});
|
|
1616
|
+
if (data["e"] === CMCD_EVENT_RESPONSE_RECEIVED) {
|
|
1617
|
+
if (!("url" in data)) issues.push({
|
|
1618
|
+
key: "url",
|
|
1619
|
+
message: "Response received event (e=\"rr\") requires the \"url\" key to be present.",
|
|
1620
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1621
|
+
});
|
|
1622
|
+
} else for (const key of CMCD_RESPONSE_KEYS) if (key in data) issues.push({
|
|
1623
|
+
key,
|
|
1624
|
+
message: `Response key "${key}" must only be present when e="rr".`,
|
|
1625
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1626
|
+
});
|
|
1627
|
+
if (data["e"] === CMCD_EVENT_PLAY_STATE && !("sta" in data)) issues.push({
|
|
1628
|
+
key: "sta",
|
|
1629
|
+
message: "Play state event (e=\"ps\") requires the \"sta\" key to be present.",
|
|
1630
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1631
|
+
});
|
|
1632
|
+
if (data["e"] === CMCD_EVENT_ERROR && !("ec" in data)) issues.push({
|
|
1633
|
+
key: "ec",
|
|
1634
|
+
message: "Error event (e=\"e\") requires the \"ec\" key to be present.",
|
|
1635
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1636
|
+
});
|
|
1637
|
+
}
|
|
1638
|
+
if ("v" in data && data["v"] !== 1 && data["v"] !== 2) issues.push({
|
|
1639
|
+
key: "v",
|
|
1640
|
+
message: `Unsupported CMCD version "${String(data["v"])}". Expected 1 or 2.`,
|
|
1641
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1642
|
+
});
|
|
1643
|
+
else if (version > 1 && !("v" in data)) issues.push({
|
|
1644
|
+
key: "v",
|
|
1645
|
+
message: "Version 2 payloads require the \"v\" key to be present.",
|
|
1646
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1647
|
+
});
|
|
1648
|
+
else if (version === CMCD_V1 && "v" in data) issues.push({
|
|
1649
|
+
key: "v",
|
|
1650
|
+
message: "Version 1 payloads should omit the \"v\" key (v1 is the default).",
|
|
1651
|
+
severity: CMCD_VALIDATION_SEVERITY_WARNING
|
|
1652
|
+
});
|
|
1653
|
+
return {
|
|
1654
|
+
valid: issues.every((i) => i.severity !== CMCD_VALIDATION_SEVERITY_ERROR),
|
|
1655
|
+
issues
|
|
1656
|
+
};
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
//#endregion
|
|
1660
|
+
//#region src/CMCD_KEY_TYPES.ts
|
|
1661
|
+
/**
|
|
1662
|
+
* CMCD key value type: inner list of numbers with token identifiers.
|
|
1663
|
+
*
|
|
1664
|
+
* @internal
|
|
1665
|
+
*/
|
|
1666
|
+
const CMCD_KEY_TYPE_NUMBER_LIST = "number[]";
|
|
1667
|
+
/**
|
|
1668
|
+
* CMCD key value type: inner list of strings.
|
|
1669
|
+
*
|
|
1670
|
+
* @internal
|
|
1671
|
+
*/
|
|
1672
|
+
const CMCD_KEY_TYPE_STRING_LIST = "string[]";
|
|
1673
|
+
/**
|
|
1674
|
+
* CMCD key value type: integer.
|
|
1675
|
+
*
|
|
1676
|
+
* @internal
|
|
1677
|
+
*/
|
|
1678
|
+
const CMCD_KEY_TYPE_INTEGER = "integer";
|
|
1679
|
+
/**
|
|
1680
|
+
* CMCD key value type: number (decimal).
|
|
1681
|
+
*
|
|
1682
|
+
* @internal
|
|
1683
|
+
*/
|
|
1684
|
+
const CMCD_KEY_TYPE_NUMBER = "number";
|
|
1685
|
+
/**
|
|
1686
|
+
* CMCD key value type: boolean.
|
|
1687
|
+
*
|
|
1688
|
+
* @internal
|
|
1689
|
+
*/
|
|
1690
|
+
const CMCD_KEY_TYPE_BOOLEAN = "boolean";
|
|
1691
|
+
/**
|
|
1692
|
+
* CMCD key value type: string.
|
|
1693
|
+
*
|
|
1694
|
+
* @internal
|
|
1695
|
+
*/
|
|
1696
|
+
const CMCD_KEY_TYPE_STRING = "string";
|
|
1697
|
+
/**
|
|
1698
|
+
* CMCD key value type: token.
|
|
1699
|
+
*
|
|
1700
|
+
* @internal
|
|
1701
|
+
*/
|
|
1702
|
+
const CMCD_KEY_TYPE_TOKEN = "token";
|
|
1703
|
+
/**
|
|
1704
|
+
* Maps each CMCD spec key to its expected value type for v2.
|
|
1705
|
+
* Keys that differ between v1 and v2 are handled by CMCD_V1_KEY_TYPE_OVERRIDES.
|
|
1706
|
+
*
|
|
1707
|
+
* @internal
|
|
1708
|
+
*/
|
|
1709
|
+
const CMCD_KEY_TYPES = {
|
|
1710
|
+
ab: CMCD_KEY_TYPE_NUMBER_LIST,
|
|
1711
|
+
bl: CMCD_KEY_TYPE_NUMBER_LIST,
|
|
1712
|
+
br: CMCD_KEY_TYPE_NUMBER_LIST,
|
|
1713
|
+
bsa: CMCD_KEY_TYPE_NUMBER_LIST,
|
|
1714
|
+
bsd: CMCD_KEY_TYPE_NUMBER_LIST,
|
|
1715
|
+
bsda: CMCD_KEY_TYPE_NUMBER_LIST,
|
|
1716
|
+
lab: CMCD_KEY_TYPE_NUMBER_LIST,
|
|
1717
|
+
lb: CMCD_KEY_TYPE_NUMBER_LIST,
|
|
1718
|
+
mtp: CMCD_KEY_TYPE_NUMBER_LIST,
|
|
1719
|
+
pb: CMCD_KEY_TYPE_NUMBER_LIST,
|
|
1720
|
+
tab: CMCD_KEY_TYPE_NUMBER_LIST,
|
|
1721
|
+
tb: CMCD_KEY_TYPE_NUMBER_LIST,
|
|
1722
|
+
tbl: CMCD_KEY_TYPE_NUMBER_LIST,
|
|
1723
|
+
tpb: CMCD_KEY_TYPE_NUMBER_LIST,
|
|
1724
|
+
ec: CMCD_KEY_TYPE_STRING_LIST,
|
|
1725
|
+
nor: CMCD_KEY_TYPE_STRING_LIST,
|
|
1726
|
+
d: CMCD_KEY_TYPE_INTEGER,
|
|
1727
|
+
dfa: CMCD_KEY_TYPE_INTEGER,
|
|
1728
|
+
dl: CMCD_KEY_TYPE_INTEGER,
|
|
1729
|
+
ltc: CMCD_KEY_TYPE_INTEGER,
|
|
1730
|
+
msd: CMCD_KEY_TYPE_INTEGER,
|
|
1731
|
+
pt: CMCD_KEY_TYPE_INTEGER,
|
|
1732
|
+
rc: CMCD_KEY_TYPE_INTEGER,
|
|
1733
|
+
rtp: CMCD_KEY_TYPE_INTEGER,
|
|
1734
|
+
sn: CMCD_KEY_TYPE_INTEGER,
|
|
1735
|
+
ts: CMCD_KEY_TYPE_INTEGER,
|
|
1736
|
+
ttfb: CMCD_KEY_TYPE_INTEGER,
|
|
1737
|
+
ttfbb: CMCD_KEY_TYPE_INTEGER,
|
|
1738
|
+
ttlb: CMCD_KEY_TYPE_INTEGER,
|
|
1739
|
+
v: CMCD_KEY_TYPE_INTEGER,
|
|
1740
|
+
pr: CMCD_KEY_TYPE_NUMBER,
|
|
1741
|
+
bg: CMCD_KEY_TYPE_BOOLEAN,
|
|
1742
|
+
bs: CMCD_KEY_TYPE_BOOLEAN,
|
|
1743
|
+
nr: CMCD_KEY_TYPE_BOOLEAN,
|
|
1744
|
+
su: CMCD_KEY_TYPE_BOOLEAN,
|
|
1745
|
+
cdn: CMCD_KEY_TYPE_STRING,
|
|
1746
|
+
cen: CMCD_KEY_TYPE_STRING,
|
|
1747
|
+
cid: CMCD_KEY_TYPE_STRING,
|
|
1748
|
+
cmsdd: CMCD_KEY_TYPE_STRING,
|
|
1749
|
+
cmsds: CMCD_KEY_TYPE_STRING,
|
|
1750
|
+
cs: CMCD_KEY_TYPE_STRING,
|
|
1751
|
+
h: CMCD_KEY_TYPE_STRING,
|
|
1752
|
+
nrr: CMCD_KEY_TYPE_STRING,
|
|
1753
|
+
sid: CMCD_KEY_TYPE_STRING,
|
|
1754
|
+
smrt: CMCD_KEY_TYPE_STRING,
|
|
1755
|
+
url: CMCD_KEY_TYPE_STRING,
|
|
1756
|
+
e: CMCD_KEY_TYPE_TOKEN,
|
|
1757
|
+
ot: CMCD_KEY_TYPE_TOKEN,
|
|
1758
|
+
sf: CMCD_KEY_TYPE_TOKEN,
|
|
1759
|
+
st: CMCD_KEY_TYPE_TOKEN,
|
|
1760
|
+
sta: CMCD_KEY_TYPE_TOKEN
|
|
1761
|
+
};
|
|
1762
|
+
/**
|
|
1763
|
+
* Maps keys to their v1-specific types when they differ from v2.
|
|
1764
|
+
*
|
|
1765
|
+
* @internal
|
|
1766
|
+
*/
|
|
1767
|
+
const CMCD_V1_KEY_TYPE_OVERRIDES = {
|
|
1768
|
+
bl: CMCD_KEY_TYPE_INTEGER,
|
|
1769
|
+
br: CMCD_KEY_TYPE_NUMBER,
|
|
1770
|
+
mtp: CMCD_KEY_TYPE_INTEGER,
|
|
1771
|
+
tb: CMCD_KEY_TYPE_NUMBER,
|
|
1772
|
+
nor: CMCD_KEY_TYPE_STRING
|
|
1773
|
+
};
|
|
1774
|
+
|
|
1775
|
+
//#endregion
|
|
1776
|
+
//#region src/CMCD_STRING_LENGTH_LIMITS.ts
|
|
1777
|
+
/**
|
|
1778
|
+
* Maps CMCD keys to their maximum string length.
|
|
1779
|
+
*
|
|
1780
|
+
* @internal
|
|
1781
|
+
*/
|
|
1782
|
+
const CMCD_STRING_LENGTH_LIMITS = {
|
|
1783
|
+
sid: 64,
|
|
1784
|
+
cid: 128,
|
|
1785
|
+
cdn: 128,
|
|
1786
|
+
h: 128,
|
|
1787
|
+
cen: 64
|
|
1788
|
+
};
|
|
1789
|
+
/**
|
|
1790
|
+
* Maximum length for custom key values.
|
|
1791
|
+
*
|
|
1792
|
+
* @internal
|
|
1793
|
+
*/
|
|
1794
|
+
const CMCD_CUSTOM_KEY_VALUE_MAX_LENGTH = 64;
|
|
1795
|
+
|
|
1796
|
+
//#endregion
|
|
1797
|
+
//#region src/CMCD_TOKEN_VALUES.ts
|
|
1798
|
+
/**
|
|
1799
|
+
* Maps token keys to their valid values.
|
|
1800
|
+
*
|
|
1801
|
+
* @internal
|
|
1802
|
+
*/
|
|
1803
|
+
const CMCD_TOKEN_VALUES = {
|
|
1804
|
+
e: [
|
|
1805
|
+
"bc",
|
|
1806
|
+
"ps",
|
|
1807
|
+
"e",
|
|
1808
|
+
"t",
|
|
1809
|
+
"c",
|
|
1810
|
+
"b",
|
|
1811
|
+
"m",
|
|
1812
|
+
"um",
|
|
1813
|
+
"pe",
|
|
1814
|
+
"pc",
|
|
1815
|
+
"rr",
|
|
1816
|
+
"as",
|
|
1817
|
+
"ae",
|
|
1818
|
+
"abs",
|
|
1819
|
+
"abe",
|
|
1820
|
+
"sk",
|
|
1821
|
+
"ce"
|
|
1822
|
+
],
|
|
1823
|
+
ot: [
|
|
1824
|
+
"m",
|
|
1825
|
+
"a",
|
|
1826
|
+
"v",
|
|
1827
|
+
"av",
|
|
1828
|
+
"i",
|
|
1829
|
+
"c",
|
|
1830
|
+
"tt",
|
|
1831
|
+
"k",
|
|
1832
|
+
"o"
|
|
1833
|
+
],
|
|
1834
|
+
sf: [
|
|
1835
|
+
"d",
|
|
1836
|
+
"h",
|
|
1837
|
+
"s",
|
|
1838
|
+
"o"
|
|
1839
|
+
],
|
|
1840
|
+
st: [
|
|
1841
|
+
"v",
|
|
1842
|
+
"l",
|
|
1843
|
+
"ll"
|
|
1844
|
+
],
|
|
1845
|
+
sta: [
|
|
1846
|
+
"s",
|
|
1847
|
+
"p",
|
|
1848
|
+
"k",
|
|
1849
|
+
"r",
|
|
1850
|
+
"a",
|
|
1851
|
+
"w",
|
|
1852
|
+
"e",
|
|
1853
|
+
"f",
|
|
1854
|
+
"q",
|
|
1855
|
+
"d"
|
|
1856
|
+
]
|
|
1857
|
+
};
|
|
1858
|
+
|
|
1859
|
+
//#endregion
|
|
1860
|
+
//#region src/validateCmcdValues.ts
|
|
1861
|
+
const HUNDRED_ROUNDING_KEYS = new Set([
|
|
1862
|
+
"bl",
|
|
1863
|
+
"dl",
|
|
1864
|
+
"mtp",
|
|
1865
|
+
"rtp",
|
|
1866
|
+
"tbl"
|
|
1867
|
+
]);
|
|
1868
|
+
const INTEGER_ROUNDING_KEYS = new Set([
|
|
1869
|
+
"br",
|
|
1870
|
+
"d",
|
|
1871
|
+
"tb"
|
|
1872
|
+
]);
|
|
1873
|
+
function isFiniteNumber(value) {
|
|
1874
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
1875
|
+
}
|
|
1876
|
+
function validateListValue(key, value, issues) {
|
|
1877
|
+
if (!Array.isArray(value)) {
|
|
1878
|
+
issues.push({
|
|
1879
|
+
key,
|
|
1880
|
+
message: `Key "${key}" must be an array.`,
|
|
1881
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1882
|
+
});
|
|
1883
|
+
return;
|
|
1884
|
+
}
|
|
1885
|
+
for (let i = 0; i < value.length; i++) {
|
|
1886
|
+
const element = value[i];
|
|
1887
|
+
if (element instanceof SfItem) {
|
|
1888
|
+
if (!isFiniteNumber(element.value)) issues.push({
|
|
1889
|
+
key,
|
|
1890
|
+
message: `Key "${key}" array element [${i}] must be a finite number.`,
|
|
1891
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1892
|
+
});
|
|
1893
|
+
} else if (!isFiniteNumber(element)) issues.push({
|
|
1894
|
+
key,
|
|
1895
|
+
message: `Key "${key}" array element [${i}] must be a finite number.`,
|
|
1896
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1897
|
+
});
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
function validateStringArrayValue(key, value, issues) {
|
|
1901
|
+
if (!Array.isArray(value)) {
|
|
1902
|
+
issues.push({
|
|
1903
|
+
key,
|
|
1904
|
+
message: `Key "${key}" must be an array.`,
|
|
1905
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1906
|
+
});
|
|
1907
|
+
return;
|
|
1908
|
+
}
|
|
1909
|
+
for (let i = 0; i < value.length; i++) {
|
|
1910
|
+
const element = value[i];
|
|
1911
|
+
if (element instanceof SfItem) {
|
|
1912
|
+
if (typeof element.value !== "string") issues.push({
|
|
1913
|
+
key,
|
|
1914
|
+
message: `Key "${key}" array element [${i}] must be a string.`,
|
|
1915
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1916
|
+
});
|
|
1917
|
+
} else if (typeof element !== "string") issues.push({
|
|
1918
|
+
key,
|
|
1919
|
+
message: `Key "${key}" array element [${i}] must be a string.`,
|
|
1920
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1921
|
+
});
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
/**
|
|
1925
|
+
* Validates that all values in a CMCD payload conform to the expected types and constraints.
|
|
1926
|
+
*
|
|
1927
|
+
* @example
|
|
1928
|
+
* {@includeCode ../test/validateCmcdValues.test.ts#example}
|
|
1401
1929
|
*
|
|
1402
|
-
* @
|
|
1930
|
+
* @param data - The CMCD payload to validate.
|
|
1931
|
+
* @param options - Validation options.
|
|
1932
|
+
* @returns The validation result.
|
|
1403
1933
|
*
|
|
1404
1934
|
* @public
|
|
1935
|
+
*/
|
|
1936
|
+
function validateCmcdValues(data, options) {
|
|
1937
|
+
const version = resolveVersion(data, options);
|
|
1938
|
+
const issues = [];
|
|
1939
|
+
for (const [key, value] of Object.entries(data)) {
|
|
1940
|
+
if (isCmcdCustomKey(key)) {
|
|
1941
|
+
if (typeof value !== "string") issues.push({
|
|
1942
|
+
key,
|
|
1943
|
+
message: `Custom key "${key}" value must be a string or token.`,
|
|
1944
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1945
|
+
});
|
|
1946
|
+
else if (value.length > CMCD_CUSTOM_KEY_VALUE_MAX_LENGTH) issues.push({
|
|
1947
|
+
key,
|
|
1948
|
+
message: `Custom key "${key}" value exceeds maximum length of ${CMCD_CUSTOM_KEY_VALUE_MAX_LENGTH}.`,
|
|
1949
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1950
|
+
});
|
|
1951
|
+
continue;
|
|
1952
|
+
}
|
|
1953
|
+
if (key === "v") {
|
|
1954
|
+
if (value !== 1 && value !== 2) issues.push({
|
|
1955
|
+
key,
|
|
1956
|
+
message: `Key "v" must be 1 or 2.`,
|
|
1957
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1958
|
+
});
|
|
1959
|
+
continue;
|
|
1960
|
+
}
|
|
1961
|
+
let expectedType = CMCD_KEY_TYPES[key];
|
|
1962
|
+
if (!expectedType) continue;
|
|
1963
|
+
if (version === CMCD_V1 && key in CMCD_V1_KEY_TYPE_OVERRIDES) expectedType = CMCD_V1_KEY_TYPE_OVERRIDES[key];
|
|
1964
|
+
switch (expectedType) {
|
|
1965
|
+
case CMCD_KEY_TYPE_INTEGER:
|
|
1966
|
+
if (!isFiniteNumber(value) || !Number.isInteger(value)) issues.push({
|
|
1967
|
+
key,
|
|
1968
|
+
message: `Key "${key}" must be a finite integer.`,
|
|
1969
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1970
|
+
});
|
|
1971
|
+
else if (HUNDRED_ROUNDING_KEYS.has(key) && value % 100 !== 0) issues.push({
|
|
1972
|
+
key,
|
|
1973
|
+
message: `Key "${key}" should be rounded to the nearest 100.`,
|
|
1974
|
+
severity: CMCD_VALIDATION_SEVERITY_WARNING
|
|
1975
|
+
});
|
|
1976
|
+
break;
|
|
1977
|
+
case CMCD_KEY_TYPE_NUMBER:
|
|
1978
|
+
if (!isFiniteNumber(value)) issues.push({
|
|
1979
|
+
key,
|
|
1980
|
+
message: `Key "${key}" must be a finite number.`,
|
|
1981
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1982
|
+
});
|
|
1983
|
+
else if (HUNDRED_ROUNDING_KEYS.has(key) && value % 100 !== 0) issues.push({
|
|
1984
|
+
key,
|
|
1985
|
+
message: `Key "${key}" should be rounded to the nearest 100.`,
|
|
1986
|
+
severity: CMCD_VALIDATION_SEVERITY_WARNING
|
|
1987
|
+
});
|
|
1988
|
+
else if (INTEGER_ROUNDING_KEYS.has(key) && !Number.isInteger(value)) issues.push({
|
|
1989
|
+
key,
|
|
1990
|
+
message: `Key "${key}" should be rounded to an integer.`,
|
|
1991
|
+
severity: CMCD_VALIDATION_SEVERITY_WARNING
|
|
1992
|
+
});
|
|
1993
|
+
break;
|
|
1994
|
+
case CMCD_KEY_TYPE_BOOLEAN:
|
|
1995
|
+
if (typeof value !== "boolean") issues.push({
|
|
1996
|
+
key,
|
|
1997
|
+
message: `Key "${key}" must be a boolean.`,
|
|
1998
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
1999
|
+
});
|
|
2000
|
+
break;
|
|
2001
|
+
case CMCD_KEY_TYPE_STRING:
|
|
2002
|
+
if (typeof value !== "string") issues.push({
|
|
2003
|
+
key,
|
|
2004
|
+
message: `Key "${key}" must be a string.`,
|
|
2005
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2006
|
+
});
|
|
2007
|
+
else if (key in CMCD_STRING_LENGTH_LIMITS && value.length > CMCD_STRING_LENGTH_LIMITS[key]) issues.push({
|
|
2008
|
+
key,
|
|
2009
|
+
message: `Key "${key}" exceeds maximum length of ${CMCD_STRING_LENGTH_LIMITS[key]}.`,
|
|
2010
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2011
|
+
});
|
|
2012
|
+
break;
|
|
2013
|
+
case CMCD_KEY_TYPE_TOKEN: {
|
|
2014
|
+
const validValues = CMCD_TOKEN_VALUES[key];
|
|
2015
|
+
if (validValues && !validValues.includes(value)) issues.push({
|
|
2016
|
+
key,
|
|
2017
|
+
message: `Key "${key}" has invalid token value "${String(value)}". Expected one of: ${validValues.join(", ")}.`,
|
|
2018
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2019
|
+
});
|
|
2020
|
+
break;
|
|
2021
|
+
}
|
|
2022
|
+
case CMCD_KEY_TYPE_NUMBER_LIST:
|
|
2023
|
+
validateListValue(key, value, issues);
|
|
2024
|
+
break;
|
|
2025
|
+
case CMCD_KEY_TYPE_STRING_LIST:
|
|
2026
|
+
validateStringArrayValue(key, value, issues);
|
|
2027
|
+
break;
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
return {
|
|
2031
|
+
valid: issues.every((i) => i.severity !== CMCD_VALIDATION_SEVERITY_ERROR),
|
|
2032
|
+
issues
|
|
2033
|
+
};
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
//#endregion
|
|
2037
|
+
//#region src/validateCmcd.ts
|
|
2038
|
+
/**
|
|
2039
|
+
* Validates a CMCD payload by checking keys, values, and structure.
|
|
1405
2040
|
*
|
|
1406
2041
|
* @example
|
|
1407
|
-
* {@includeCode ../test/
|
|
2042
|
+
* {@includeCode ../test/validateCmcd.test.ts#example}
|
|
2043
|
+
*
|
|
2044
|
+
* @param data - The CMCD payload to validate.
|
|
2045
|
+
* @param options - Validation options.
|
|
2046
|
+
* @returns The validation result.
|
|
2047
|
+
*
|
|
2048
|
+
* @public
|
|
1408
2049
|
*/
|
|
1409
|
-
function
|
|
1410
|
-
return
|
|
2050
|
+
function validateCmcd(data, options) {
|
|
2051
|
+
return mergeValidationResults(validateCmcdKeys(data, options), validateCmcdValues(data, options), validateCmcdStructure(data, options));
|
|
1411
2052
|
}
|
|
1412
2053
|
|
|
1413
2054
|
//#endregion
|
|
1414
|
-
//#region src/
|
|
2055
|
+
//#region src/validateCmcdEvents.ts
|
|
1415
2056
|
/**
|
|
1416
|
-
*
|
|
2057
|
+
* Validates a raw CMCD string as an event-mode payload.
|
|
1417
2058
|
*
|
|
1418
|
-
*
|
|
1419
|
-
*
|
|
1420
|
-
*
|
|
2059
|
+
* This function decodes the string internally and validates it with
|
|
2060
|
+
* `reportingMode` set to `'event'`. The input may contain multiple
|
|
2061
|
+
* newline-separated events (e.g. a `text/cmcd` POST body), in which
|
|
2062
|
+
* case each line is validated independently and the results are merged.
|
|
2063
|
+
*
|
|
2064
|
+
* @example
|
|
2065
|
+
* {@includeCode ../test/validateCmcdEvents.test.ts#example}
|
|
2066
|
+
*
|
|
2067
|
+
* @param cmcd - The raw CMCD-encoded string to validate. May contain
|
|
2068
|
+
* multiple newline-separated event lines.
|
|
2069
|
+
* @param options - Validation options (excluding `reportingMode`).
|
|
2070
|
+
* @returns The validation result including decoded data per event line.
|
|
1421
2071
|
*
|
|
1422
2072
|
* @public
|
|
1423
2073
|
*/
|
|
1424
|
-
function
|
|
1425
|
-
|
|
2074
|
+
function validateCmcdEvents(cmcd, options) {
|
|
2075
|
+
const opts = {
|
|
2076
|
+
...options,
|
|
2077
|
+
reportingMode: CMCD_EVENT_MODE
|
|
2078
|
+
};
|
|
2079
|
+
const lines = cmcd.split(/\r?\n/).filter((line) => line.length > 0);
|
|
2080
|
+
if (lines.length === 0) return {
|
|
2081
|
+
valid: false,
|
|
2082
|
+
issues: [{
|
|
2083
|
+
message: "Empty event mode payload. At least one event line is required.",
|
|
2084
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2085
|
+
}],
|
|
2086
|
+
data: []
|
|
2087
|
+
};
|
|
2088
|
+
const decodedLines = [];
|
|
2089
|
+
const lineResults = [];
|
|
2090
|
+
for (let i = 0; i < lines.length; i++) try {
|
|
2091
|
+
const data = decodeCmcd(lines[i]);
|
|
2092
|
+
decodedLines.push(data);
|
|
2093
|
+
lineResults.push(validateCmcd(data, opts));
|
|
2094
|
+
} catch {
|
|
2095
|
+
decodedLines.push({});
|
|
2096
|
+
lineResults.push({
|
|
2097
|
+
valid: false,
|
|
2098
|
+
issues: [{
|
|
2099
|
+
message: `Failed to decode event line ${i + 1}: invalid structured field syntax.`,
|
|
2100
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2101
|
+
}]
|
|
2102
|
+
});
|
|
2103
|
+
}
|
|
2104
|
+
return {
|
|
2105
|
+
...mergeValidationResults(...lineResults),
|
|
2106
|
+
data: decodedLines
|
|
2107
|
+
};
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
//#endregion
|
|
2111
|
+
//#region src/validateCmcdHeaders.ts
|
|
2112
|
+
/**
|
|
2113
|
+
* Validates CMCD HTTP headers by checking shard placement and payload validity.
|
|
2114
|
+
*
|
|
2115
|
+
* This function accepts raw CMCD header strings, decodes each shard
|
|
2116
|
+
* internally, verifies that each key is placed in its correct header shard,
|
|
2117
|
+
* then merges all shards and runs full payload validation (keys, values, and
|
|
2118
|
+
* structure) on the merged data.
|
|
2119
|
+
*
|
|
2120
|
+
* @example
|
|
2121
|
+
* {@includeCode ../test/validateCmcdHeaders.test.ts#example}
|
|
2122
|
+
*
|
|
2123
|
+
* @param headers - A `Headers` instance or a record of CMCD header fields to their raw encoded string values.
|
|
2124
|
+
* @param options - Validation options (excluding `reportingMode`).
|
|
2125
|
+
* @returns The validation result including decoded data.
|
|
2126
|
+
*
|
|
2127
|
+
* @public
|
|
2128
|
+
*/
|
|
2129
|
+
function validateCmcdHeaders(headers, options) {
|
|
2130
|
+
const h = ensureHeaders(headers);
|
|
2131
|
+
const issues = [];
|
|
2132
|
+
const decoded = {};
|
|
2133
|
+
for (const headerField of CMCD_HEADER_FIELDS) {
|
|
2134
|
+
const raw = h.get(headerField);
|
|
2135
|
+
if (!raw) continue;
|
|
2136
|
+
let shard;
|
|
2137
|
+
try {
|
|
2138
|
+
shard = decodeCmcd(raw);
|
|
2139
|
+
} catch {
|
|
2140
|
+
issues.push({
|
|
2141
|
+
key: headerField,
|
|
2142
|
+
message: `Failed to decode "${headerField}" header: invalid structured field syntax.`,
|
|
2143
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2144
|
+
});
|
|
2145
|
+
continue;
|
|
2146
|
+
}
|
|
2147
|
+
decoded[headerField] = shard;
|
|
2148
|
+
for (const key of Object.keys(shard)) {
|
|
2149
|
+
if (isCmcdCustomKey(key)) continue;
|
|
2150
|
+
const expectedHeader = CMCD_HEADER_MAP[key];
|
|
2151
|
+
if (expectedHeader && expectedHeader !== headerField) issues.push({
|
|
2152
|
+
key,
|
|
2153
|
+
message: `Key "${key}" is in "${headerField}" but should be in "${expectedHeader}".`,
|
|
2154
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2155
|
+
});
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
const shardResult = {
|
|
2159
|
+
valid: issues.length === 0,
|
|
2160
|
+
issues
|
|
2161
|
+
};
|
|
2162
|
+
const merged = Object.assign({}, ...CMCD_HEADER_FIELDS.map((f) => decoded[f]).filter(Boolean));
|
|
2163
|
+
return {
|
|
2164
|
+
...mergeValidationResults(shardResult, validateCmcd(merged, {
|
|
2165
|
+
...options,
|
|
2166
|
+
reportingMode: CMCD_REQUEST_MODE
|
|
2167
|
+
})),
|
|
2168
|
+
data: merged
|
|
2169
|
+
};
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
//#endregion
|
|
2173
|
+
//#region src/validateCmcdRequest.ts
|
|
2174
|
+
/**
|
|
2175
|
+
* Validates CMCD data from a request as a request-mode payload.
|
|
2176
|
+
*
|
|
2177
|
+
* Accepts a
|
|
2178
|
+
* {@link https://developer.mozilla.org/en-US/docs/Web/API/Request | Request}
|
|
2179
|
+
* object or an {@link @svta/cml-utils#HttpRequest | HttpRequest} object.
|
|
2180
|
+
*
|
|
2181
|
+
* The function checks for CMCD data in the HTTP headers first. If CMCD
|
|
2182
|
+
* headers are found, validation includes shard-placement checks via
|
|
2183
|
+
* {@link validateCmcdHeaders}. Otherwise, the CMCD query parameter is
|
|
2184
|
+
* extracted from the URL and validated.
|
|
2185
|
+
*
|
|
2186
|
+
* @example
|
|
2187
|
+
* {@includeCode ../test/validateCmcdRequest.test.ts#example}
|
|
2188
|
+
*
|
|
2189
|
+
* @param request - A `Request` or `HttpRequest` to validate.
|
|
2190
|
+
* @param options - Validation options (excluding `reportingMode`).
|
|
2191
|
+
* @returns The validation result including decoded data.
|
|
2192
|
+
*
|
|
2193
|
+
* @public
|
|
2194
|
+
*/
|
|
2195
|
+
function validateCmcdRequest(request, options) {
|
|
2196
|
+
const headers = extractHeaderRecord(request.headers);
|
|
2197
|
+
if (headers) return validateCmcdHeaders(headers, options);
|
|
2198
|
+
const param = new URL(request.url).searchParams.get(CMCD_PARAM);
|
|
2199
|
+
if (!param) return {
|
|
2200
|
+
valid: false,
|
|
2201
|
+
issues: [{
|
|
2202
|
+
message: "No CMCD data found in request headers or query parameters.",
|
|
2203
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2204
|
+
}],
|
|
2205
|
+
data: {}
|
|
2206
|
+
};
|
|
2207
|
+
let data;
|
|
2208
|
+
try {
|
|
2209
|
+
data = decodeCmcd(param);
|
|
2210
|
+
} catch {
|
|
2211
|
+
return {
|
|
2212
|
+
valid: false,
|
|
2213
|
+
issues: [{
|
|
2214
|
+
message: "Failed to decode CMCD query parameter: invalid structured field syntax.",
|
|
2215
|
+
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2216
|
+
}],
|
|
2217
|
+
data: {}
|
|
2218
|
+
};
|
|
2219
|
+
}
|
|
2220
|
+
return {
|
|
2221
|
+
...validateCmcd(data, {
|
|
2222
|
+
...options,
|
|
2223
|
+
reportingMode: CMCD_REQUEST_MODE
|
|
2224
|
+
}),
|
|
2225
|
+
data
|
|
2226
|
+
};
|
|
2227
|
+
}
|
|
2228
|
+
function extractHeaderRecord(headers) {
|
|
2229
|
+
if (!headers) return;
|
|
2230
|
+
const h = ensureHeaders(headers);
|
|
2231
|
+
const result = {};
|
|
2232
|
+
let found = false;
|
|
2233
|
+
for (const field of CMCD_HEADER_FIELDS) {
|
|
2234
|
+
const value = h.get(field);
|
|
2235
|
+
if (value) {
|
|
2236
|
+
result[field] = value;
|
|
2237
|
+
found = true;
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
return found ? result : void 0;
|
|
1426
2241
|
}
|
|
1427
2242
|
|
|
1428
2243
|
//#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 };
|
|
2244
|
+
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_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, validateCmcdEvents, validateCmcdHeaders, validateCmcdKeys, validateCmcdRequest, validateCmcdStructure, validateCmcdValues };
|
|
1430
2245
|
//# sourceMappingURL=index.js.map
|