@svta/cml-cmcd 2.5.0 → 2.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/index.d.ts +229 -30
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +419 -168
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -522,6 +522,26 @@ function isValid(value) {
|
|
|
522
522
|
return value != null && value !== "" && value !== false;
|
|
523
523
|
}
|
|
524
524
|
|
|
525
|
+
//#endregion
|
|
526
|
+
//#region src/toTokenString.ts
|
|
527
|
+
/**
|
|
528
|
+
* Resolve the text of a token value.
|
|
529
|
+
*
|
|
530
|
+
* A token may be a plain string, a registry `Symbol`, an `SfToken`, or an
|
|
531
|
+
* `SfItem` that wraps one of those.
|
|
532
|
+
*
|
|
533
|
+
* @param value - The value to resolve.
|
|
534
|
+
*
|
|
535
|
+
* @returns The token text, or `undefined` when the value is not a token.
|
|
536
|
+
*
|
|
537
|
+
* @internal
|
|
538
|
+
*/
|
|
539
|
+
function toTokenString(value) {
|
|
540
|
+
if (value instanceof SfItem) return toTokenString(value.value);
|
|
541
|
+
if (typeof value === "string") return value;
|
|
542
|
+
if (typeof value === "symbol" || value instanceof SfToken) return symbolToStr(value);
|
|
543
|
+
}
|
|
544
|
+
|
|
525
545
|
//#endregion
|
|
526
546
|
//#region src/prepareCmcdData.ts
|
|
527
547
|
const filterMap = {
|
|
@@ -534,7 +554,8 @@ const filterMap = {
|
|
|
534
554
|
function unwrapValue(value, ot) {
|
|
535
555
|
if (Array.isArray(value)) {
|
|
536
556
|
let item;
|
|
537
|
-
|
|
557
|
+
const otText = toTokenString(ot);
|
|
558
|
+
if (otText) item = value.find((item$1) => toTokenString(item$1.params?.ot) === otText);
|
|
538
559
|
if (!item) item = value[0];
|
|
539
560
|
return unwrapValue(item);
|
|
540
561
|
}
|
|
@@ -582,15 +603,15 @@ function prepareCmcdData(obj, options = {}) {
|
|
|
582
603
|
const version = options.version || obj["v"] || CMCD_V2;
|
|
583
604
|
const reportingMode = options.reportingMode || CMCD_REQUEST_MODE;
|
|
584
605
|
const data = version === 1 ? downConvertToV1(obj) : obj;
|
|
606
|
+
const eventType = toTokenString(data["e"]);
|
|
585
607
|
const keyFilter = version === 1 ? isCmcdV1Key : filterMap[reportingMode];
|
|
586
608
|
let keys = Object.keys(data).filter(keyFilter);
|
|
587
|
-
if (data["e"] &&
|
|
609
|
+
if (data["e"] && eventType !== CMCD_EVENT_RESPONSE_RECEIVED) keys = keys.filter((key) => !isCmcdResponseReceivedKey(key));
|
|
588
610
|
const filter = options.filter;
|
|
589
611
|
if (typeof filter === "function") keys = keys.filter(filter);
|
|
590
612
|
const isEventMode = reportingMode === CMCD_EVENT_MODE;
|
|
591
613
|
if (isEventMode) {
|
|
592
|
-
|
|
593
|
-
if (!keys.includes("e") && eventType != null) keys.push("e");
|
|
614
|
+
if (!keys.includes("e") && data["e"] != null) keys.push("e");
|
|
594
615
|
if (!keys.includes("ts")) keys.push("ts");
|
|
595
616
|
if (!keys.includes("cen") && data["cen"] != null && eventType === CMCD_EVENT_CUSTOM_EVENT) keys.push("cen");
|
|
596
617
|
if (!keys.includes("ec") && data["ec"] != null && eventType === CMCD_EVENT_ERROR) keys.push("ec");
|
|
@@ -612,11 +633,14 @@ function prepareCmcdData(obj, options = {}) {
|
|
|
612
633
|
if (typeof formatter === "function") value = formatter(value, formatterOptions);
|
|
613
634
|
if (key === "v") if (version === 1) continue;
|
|
614
635
|
else value = version;
|
|
615
|
-
if (key === "pr" && value === 1 && !(isEventMode &&
|
|
636
|
+
if (key === "pr" && value === 1 && !(isEventMode && eventType === CMCD_EVENT_PLAYBACK_RATE)) continue;
|
|
616
637
|
if (isEventMode && key === "ts" && !Number.isFinite(value)) value = Date.now();
|
|
617
|
-
const isBgFalseTransition = isEventMode && value === false && key === "bg" &&
|
|
638
|
+
const isBgFalseTransition = isEventMode && value === false && key === "bg" && eventType === CMCD_EVENT_BACKGROUNDED_MODE;
|
|
618
639
|
if (!isValid(value) && !isBgFalseTransition) continue;
|
|
619
|
-
if (isTokenField(key)
|
|
640
|
+
if (isTokenField(key)) {
|
|
641
|
+
if (typeof value === "string") value = new SfToken(value);
|
|
642
|
+
else if (value instanceof SfItem && typeof value.value === "string") value = new SfItem(new SfToken(value.value), value.params);
|
|
643
|
+
}
|
|
620
644
|
results[key] = value;
|
|
621
645
|
}
|
|
622
646
|
return results;
|
|
@@ -970,6 +994,33 @@ const CMCD_KEYS = /* @__PURE__ */ [
|
|
|
970
994
|
*/
|
|
971
995
|
const CMCD_MIME_TYPE = "application/cmcd";
|
|
972
996
|
|
|
997
|
+
//#endregion
|
|
998
|
+
//#region src/CMCD_REQUEST_PROVENANCE.ts
|
|
999
|
+
/**
|
|
1000
|
+
* The `customData` key under which `CmcdReporter.createRequestReport()`
|
|
1001
|
+
* stamps session provenance on every request it returns.
|
|
1002
|
+
*
|
|
1003
|
+
* The value is a frozen `CmcdRequestProvenance` record: `sid` names the
|
|
1004
|
+
* session that issued the request, `cid` is the content id in effect when
|
|
1005
|
+
* the request was issued, and `data` carries the request's per-call data
|
|
1006
|
+
* as an encoded CMCD string. `CmcdReporter.recordResponseReceived()` reads
|
|
1007
|
+
* the record to attribute a late response to its session and to rebuild
|
|
1008
|
+
* the request-time report keys.
|
|
1009
|
+
* Spread and `Object.assign` carry the record through ordinary request
|
|
1010
|
+
* clones, but `JSON.stringify` and structured clone drop symbol-keyed
|
|
1011
|
+
* properties: read the value before such a boundary and restore it
|
|
1012
|
+
* afterward. The record itself survives JSON, and attribution is by the
|
|
1013
|
+
* `sid` value rather than object identity, so a revived plain-object copy
|
|
1014
|
+
* attributes exactly. A record naming a `sid` the reporter does not retain
|
|
1015
|
+
* drops the response rather than relabeling it.
|
|
1016
|
+
*
|
|
1017
|
+
* Backed by the symbol registry so duplicated copies of this library in
|
|
1018
|
+
* one bundle interoperate. The registry key is stable across versions.
|
|
1019
|
+
*
|
|
1020
|
+
* @public
|
|
1021
|
+
*/
|
|
1022
|
+
const CMCD_REQUEST_PROVENANCE = /* @__PURE__ */ Symbol.for("@svta/cml-cmcd/request-provenance");
|
|
1023
|
+
|
|
973
1024
|
//#endregion
|
|
974
1025
|
//#region src/CMCD_V1.ts
|
|
975
1026
|
/**
|
|
@@ -1473,6 +1524,50 @@ const CmcdTransmissionMode = {
|
|
|
1473
1524
|
HEADERS: CMCD_HEADERS
|
|
1474
1525
|
};
|
|
1475
1526
|
|
|
1527
|
+
//#endregion
|
|
1528
|
+
//#region src/upConvertToV2.ts
|
|
1529
|
+
/**
|
|
1530
|
+
* Up-convert V1 CMCD data to V2 format.
|
|
1531
|
+
*
|
|
1532
|
+
* - Wraps plain scalar values in arrays for inner-list keys.
|
|
1533
|
+
* - Wraps `nor` string in an array.
|
|
1534
|
+
*
|
|
1535
|
+
* If the data is already V2 (has `v: 2`), it is returned unchanged.
|
|
1536
|
+
*
|
|
1537
|
+
* @internal
|
|
1538
|
+
*/
|
|
1539
|
+
function upConvertToV2(obj) {
|
|
1540
|
+
if (obj["v"] === CMCD_V2) return obj;
|
|
1541
|
+
const result = {};
|
|
1542
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1543
|
+
if (value == null) {
|
|
1544
|
+
result[key] = value;
|
|
1545
|
+
continue;
|
|
1546
|
+
}
|
|
1547
|
+
if (CMCD_INNER_LIST_KEYS.has(key) && !Array.isArray(value)) result[key] = [value];
|
|
1548
|
+
else if (key === "nor" && typeof value === "string") result[key] = [value];
|
|
1549
|
+
else result[key] = value;
|
|
1550
|
+
}
|
|
1551
|
+
return result;
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
//#endregion
|
|
1555
|
+
//#region src/decodeCmcd.ts
|
|
1556
|
+
function reduceValue(value, useSymbol) {
|
|
1557
|
+
if (Array.isArray(value)) return value.map((item) => reduceValue(item, useSymbol));
|
|
1558
|
+
if (typeof value === "symbol") return useSymbol === void 0 ? symbolToStr(value) : value;
|
|
1559
|
+
if (value instanceof SfItem) return value.params ? new SfItem(reduceValue(value.value, useSymbol), value.params) : reduceValue(value.value, useSymbol);
|
|
1560
|
+
return value;
|
|
1561
|
+
}
|
|
1562
|
+
function decodeCmcd(cmcd, options) {
|
|
1563
|
+
if (!cmcd) return {};
|
|
1564
|
+
const sfDict = decodeSfDict(cmcd, options);
|
|
1565
|
+
const result = {};
|
|
1566
|
+
for (const [key, item] of Object.entries(sfDict)) result[key] = reduceValue(item, options?.useSymbol);
|
|
1567
|
+
if (options?.convertToLatest) return upConvertToV2(result);
|
|
1568
|
+
return result;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1476
1571
|
//#endregion
|
|
1477
1572
|
//#region src/CmcdReporter.ts
|
|
1478
1573
|
function createEncodingOptions(reportingMode, config, baseUrl) {
|
|
@@ -1579,8 +1674,10 @@ function copyItemValue(value) {
|
|
|
1579
1674
|
*
|
|
1580
1675
|
* Complete for the CMCD value space rather than best-effort: `CmcdValue` and
|
|
1581
1676
|
* `CmcdCustomValue` admit only primitives, `SfItem<primitive>`, and arrays of
|
|
1582
|
-
* those, and `SfItem.params` is a flat record.
|
|
1583
|
-
*
|
|
1677
|
+
* those, and `SfItem.params` is a flat record. Called where a transform is
|
|
1678
|
+
* configured, on the ended session's store at archival (detaching the frozen
|
|
1679
|
+
* snapshot from caller-held references), and on the request's stored
|
|
1680
|
+
* player-facing view.
|
|
1584
1681
|
*/
|
|
1585
1682
|
function copyReportValues(data) {
|
|
1586
1683
|
const record = data;
|
|
@@ -1594,17 +1691,51 @@ function copyReportValues(data) {
|
|
|
1594
1691
|
}
|
|
1595
1692
|
return data;
|
|
1596
1693
|
}
|
|
1694
|
+
/**
|
|
1695
|
+
* Decodes the per-call data snapshot a provenance record carries into
|
|
1696
|
+
* fresh, encodable report data: tokens revive as `SfToken` and
|
|
1697
|
+
* params-bearing items as `SfItem` (`useSymbol: false`), so every value
|
|
1698
|
+
* keeps its wire type through re-encoding. Only called in
|
|
1699
|
+
* `recordResponseReceived()`. Returns an empty object when the record is
|
|
1700
|
+
* absent, carries no snapshot, or the snapshot does not parse (a value
|
|
1701
|
+
* this reporter did not write); the response then reports its derived keys
|
|
1702
|
+
* over the session's data alone.
|
|
1703
|
+
*/
|
|
1704
|
+
function decodeSnapshot(provenance) {
|
|
1705
|
+
const encoded = provenance !== null && typeof provenance === "object" ? provenance.data : void 0;
|
|
1706
|
+
if (typeof encoded !== "string" || !encoded) return {};
|
|
1707
|
+
try {
|
|
1708
|
+
return decodeCmcd(encoded, { useSymbol: false });
|
|
1709
|
+
} catch {
|
|
1710
|
+
return {};
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
/**
|
|
1714
|
+
* Mints a session's frozen base provenance record: the issuing `sid`, and
|
|
1715
|
+
* the `cid` in effect at mint time. `update()` re-mints on every `cid`
|
|
1716
|
+
* change, so requests issued before a mid-session content change keep the
|
|
1717
|
+
* `cid` they were issued under while later requests carry the new one.
|
|
1718
|
+
*/
|
|
1719
|
+
function mintProvenance(sid, cid) {
|
|
1720
|
+
return Object.freeze(typeof cid === "string" && cid ? {
|
|
1721
|
+
sid,
|
|
1722
|
+
cid
|
|
1723
|
+
} : { sid });
|
|
1724
|
+
}
|
|
1597
1725
|
function defaultRequester(request) {
|
|
1598
1726
|
const { url,...init } = request;
|
|
1599
1727
|
return fetch(url, init);
|
|
1600
1728
|
}
|
|
1601
1729
|
function createCmcdReporterConfig(config) {
|
|
1602
1730
|
const { version = CMCD_V2, eventTargets = [], sid = uuid(), transmissionMode = CMCD_QUERY,...rest } = config;
|
|
1731
|
+
const retention = config.sessionRetention;
|
|
1732
|
+
const sessionRetention = typeof retention === "number" ? Math.floor(retention) : NaN;
|
|
1603
1733
|
return {
|
|
1604
1734
|
...rest,
|
|
1605
1735
|
version,
|
|
1606
1736
|
transmissionMode,
|
|
1607
1737
|
sid,
|
|
1738
|
+
sessionRetention: sessionRetention >= 0 ? sessionRetention : 2,
|
|
1608
1739
|
eventTargets: eventTargets.reduce((acc, target) => {
|
|
1609
1740
|
if (target?.url && target.events?.length) acc.push({
|
|
1610
1741
|
version: target.version || CMCD_V2,
|
|
@@ -1645,27 +1776,41 @@ var CmcdReporter = class {
|
|
|
1645
1776
|
*/
|
|
1646
1777
|
constructor(config, requester = defaultRequester) {
|
|
1647
1778
|
this.timeOrigin = performance.timeOrigin || performance.timing?.fetchStart || Date.now() - performance.now();
|
|
1648
|
-
this.
|
|
1649
|
-
this.
|
|
1650
|
-
this.
|
|
1651
|
-
this.lastEmitted = {};
|
|
1652
|
-
this.requestTarget = {
|
|
1653
|
-
sn: 0,
|
|
1654
|
-
msdSent: false
|
|
1655
|
-
};
|
|
1779
|
+
this.sessions = /* @__PURE__ */ new Map();
|
|
1780
|
+
this.intervals = /* @__PURE__ */ new Map();
|
|
1781
|
+
this.started = false;
|
|
1656
1782
|
this.config = createCmcdReporterConfig(config);
|
|
1657
|
-
this.
|
|
1783
|
+
this.session = this.createSession(this.config.sid, {
|
|
1658
1784
|
cid: this.config.cid,
|
|
1659
|
-
sid: this.config.sid,
|
|
1660
1785
|
v: this.config.version
|
|
1661
|
-
};
|
|
1786
|
+
});
|
|
1787
|
+
this.sessions.set(this.session.sid, this.session);
|
|
1662
1788
|
this.requester = requester;
|
|
1663
|
-
|
|
1664
|
-
|
|
1789
|
+
}
|
|
1790
|
+
/**
|
|
1791
|
+
* Creates the state for a new session: fresh counters, gates, queues and
|
|
1792
|
+
* dedup baseline for every configured target.
|
|
1793
|
+
*/
|
|
1794
|
+
createSession(sid, data) {
|
|
1795
|
+
const eventTargets = /* @__PURE__ */ new Map();
|
|
1796
|
+
for (const target of this.config.eventTargets) eventTargets.set(target, {
|
|
1665
1797
|
sn: 0,
|
|
1666
1798
|
msdSent: false,
|
|
1667
|
-
queue: []
|
|
1799
|
+
queue: [],
|
|
1800
|
+
disposed: false
|
|
1668
1801
|
});
|
|
1802
|
+
return {
|
|
1803
|
+
sid,
|
|
1804
|
+
provenance: mintProvenance(sid, data.cid),
|
|
1805
|
+
data,
|
|
1806
|
+
msd: NaN,
|
|
1807
|
+
lastEmitted: {},
|
|
1808
|
+
eventTargets,
|
|
1809
|
+
requestTarget: {
|
|
1810
|
+
sn: 0,
|
|
1811
|
+
msdSent: false
|
|
1812
|
+
}
|
|
1813
|
+
};
|
|
1669
1814
|
}
|
|
1670
1815
|
/**
|
|
1671
1816
|
* Starts the CMCD reporter. Called by the player when the reporter is enabled.
|
|
@@ -1675,17 +1820,15 @@ var CmcdReporter = class {
|
|
|
1675
1820
|
* populated before calling start().
|
|
1676
1821
|
*/
|
|
1677
1822
|
start() {
|
|
1823
|
+
this.started = true;
|
|
1678
1824
|
let failure;
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
this.recordTargetEvent(target, config, CMCD_EVENT_TIME_INTERVAL);
|
|
1684
|
-
this.processEventTargets();
|
|
1685
|
-
};
|
|
1686
|
-
target.intervalId = setInterval(timeIntervalEvent, config.interval * 1e3);
|
|
1825
|
+
const session = this.session;
|
|
1826
|
+
session.eventTargets.forEach((target, config) => {
|
|
1827
|
+
this.disarmInterval(config);
|
|
1828
|
+
if (target.disposed || !this.armInterval(config)) return;
|
|
1687
1829
|
try {
|
|
1688
|
-
|
|
1830
|
+
this.recordTargetEvent(session, target, config, CMCD_EVENT_TIME_INTERVAL);
|
|
1831
|
+
this.processEventTargets();
|
|
1689
1832
|
} catch (error) {
|
|
1690
1833
|
failure ??= { error };
|
|
1691
1834
|
}
|
|
@@ -1693,15 +1836,31 @@ var CmcdReporter = class {
|
|
|
1693
1836
|
if (failure) throw failure.error;
|
|
1694
1837
|
}
|
|
1695
1838
|
/**
|
|
1839
|
+
* Arms the time-interval timer for an event target when its config calls
|
|
1840
|
+
* for one. Returns whether a timer was armed. The callback reports into
|
|
1841
|
+
* whichever session is current when it fires.
|
|
1842
|
+
*/
|
|
1843
|
+
armInterval(config) {
|
|
1844
|
+
if (config.interval === 0 || !config.events.includes(CMCD_EVENT_TIME_INTERVAL)) return false;
|
|
1845
|
+
this.intervals.set(config, setInterval(() => {
|
|
1846
|
+
const session = this.session;
|
|
1847
|
+
const target = session.eventTargets.get(config);
|
|
1848
|
+
if (target) {
|
|
1849
|
+
this.recordTargetEvent(session, target, config, CMCD_EVENT_TIME_INTERVAL);
|
|
1850
|
+
this.processEventTargets();
|
|
1851
|
+
}
|
|
1852
|
+
}, config.interval * 1e3));
|
|
1853
|
+
return true;
|
|
1854
|
+
}
|
|
1855
|
+
/**
|
|
1696
1856
|
* Stops the CMCD reporter. Called by the player when the reporter is disabled.
|
|
1697
1857
|
*
|
|
1698
1858
|
* @param flush - Whether to flush the event targets.
|
|
1699
1859
|
*/
|
|
1700
1860
|
stop(flush = false) {
|
|
1861
|
+
this.started = false;
|
|
1701
1862
|
if (flush) this.flush();
|
|
1702
|
-
this.eventTargets.
|
|
1703
|
-
this.disarmInterval(target);
|
|
1704
|
-
});
|
|
1863
|
+
for (const config of this.config.eventTargets) this.disarmInterval(config);
|
|
1705
1864
|
}
|
|
1706
1865
|
/**
|
|
1707
1866
|
* Forces the sending of all event reports, regardless of the batch size or interval.
|
|
@@ -1739,17 +1898,63 @@ var CmcdReporter = class {
|
|
|
1739
1898
|
*
|
|
1740
1899
|
* A `sid` change resets the dedup baseline.
|
|
1741
1900
|
*
|
|
1901
|
+
* `sid` and `msd` are session-owned: the reporter tracks them itself and
|
|
1902
|
+
* stamps them onto every outgoing report, so this method is the only way
|
|
1903
|
+
* to change them. A per-call value on {@link CmcdReporter.recordEvent},
|
|
1904
|
+
* {@link CmcdReporter.createRequestReport}, or
|
|
1905
|
+
* {@link CmcdReporter.recordResponseReceived} has no effect; response
|
|
1906
|
+
* attribution is by the provenance record alone.
|
|
1907
|
+
* `msd` must be a finite number of milliseconds between `0` and
|
|
1908
|
+
* `999_999_999_999_999` (the RFC 8941 integer maximum); it is rounded to
|
|
1909
|
+
* the nearest integer, and invalid values are ignored.
|
|
1910
|
+
*
|
|
1742
1911
|
* @param data - The data to update.
|
|
1743
1912
|
*/
|
|
1744
1913
|
update(data) {
|
|
1745
|
-
if (data.sid && data.sid !== this.
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1914
|
+
if (data.sid && data.sid !== this.session.sid) this.startSession(data.sid);
|
|
1915
|
+
const session = this.session;
|
|
1916
|
+
const { msd } = data;
|
|
1917
|
+
if (typeof msd === "number" && Number.isFinite(msd) && msd >= 0) {
|
|
1918
|
+
const rounded = Math.round(msd);
|
|
1919
|
+
if (rounded <= 999999999999999) session.msd = rounded;
|
|
1920
|
+
}
|
|
1921
|
+
session.data = {
|
|
1922
|
+
...session.data,
|
|
1749
1923
|
...data,
|
|
1924
|
+
sid: void 0,
|
|
1750
1925
|
msd: void 0
|
|
1751
1926
|
};
|
|
1752
|
-
|
|
1927
|
+
if (session.data.cid !== session.provenance.cid) session.provenance = mintProvenance(session.sid, session.data.cid);
|
|
1928
|
+
for (const entry of STATE_FIELDS) if (entry.field in data && !entry.equal(session.data[entry.field], session.lastEmitted[entry.field])) this.recordEvent(entry.event);
|
|
1929
|
+
}
|
|
1930
|
+
/**
|
|
1931
|
+
* Starts a new session: the current one is archived in place for stale
|
|
1932
|
+
* attribution, a fresh one takes over, ended sessions are drained, and
|
|
1933
|
+
* the oldest are evicted beyond the configured retention.
|
|
1934
|
+
*
|
|
1935
|
+
* The data store is carried over, and the archived snapshot is frozen:
|
|
1936
|
+
* the ended session's value graph is detached first, so nested values
|
|
1937
|
+
* the caller handed to `update()` can no longer mutate it, and the new
|
|
1938
|
+
* session starts from a shallow copy of the detached graph, so `cid`,
|
|
1939
|
+
* `br`, custom keys and the rest survive a `sid` change as they always
|
|
1940
|
+
* have. Sessions are keyed by `sid`, which CTA-5004-B expects to be
|
|
1941
|
+
* unique per playback session: reusing one replaces the retained
|
|
1942
|
+
* namesake, whose remaining state dies as if evicted and whose late
|
|
1943
|
+
* responses relabel onto the replacement.
|
|
1944
|
+
*/
|
|
1945
|
+
startSession(sid) {
|
|
1946
|
+
this.session.data = copyReportValues({ ...this.session.data });
|
|
1947
|
+
this.session = this.createSession(sid, { ...this.session.data });
|
|
1948
|
+
this.sessions.delete(sid);
|
|
1949
|
+
this.sessions.set(sid, this.session);
|
|
1950
|
+
this.processEventTargets();
|
|
1951
|
+
for (const key of this.sessions.keys()) {
|
|
1952
|
+
if (this.sessions.size <= this.config.sessionRetention + 1) break;
|
|
1953
|
+
this.sessions.delete(key);
|
|
1954
|
+
}
|
|
1955
|
+
if (this.started) {
|
|
1956
|
+
for (const config of this.config.eventTargets) if (!this.intervals.has(config)) this.armInterval(config);
|
|
1957
|
+
}
|
|
1753
1958
|
}
|
|
1754
1959
|
/**
|
|
1755
1960
|
* Records an event. Called by the player when an event occurs.
|
|
@@ -1784,15 +1989,21 @@ var CmcdReporter = class {
|
|
|
1784
1989
|
* @param data - Additional data to record with the event. This data
|
|
1785
1990
|
* only applies to this event report, except for the dedup
|
|
1786
1991
|
* field of a state-change event, which is also persisted
|
|
1787
|
-
* into the reporter's data store.
|
|
1992
|
+
* into the reporter's data store. Session-owned keys
|
|
1993
|
+
* (`sid`, `msd`) supplied here are ignored; the reporter
|
|
1994
|
+
* stamps its own values.
|
|
1788
1995
|
*/
|
|
1789
1996
|
recordEvent(type, data = {}) {
|
|
1790
|
-
this.emitEvent(type, data);
|
|
1997
|
+
this.emitEvent(this.session, type, data);
|
|
1791
1998
|
}
|
|
1792
1999
|
/**
|
|
1793
|
-
* Records an event across every configured target, applying
|
|
2000
|
+
* Records an event across every configured target of a session, applying
|
|
1794
2001
|
* state-change dedup once before per-target fan-out.
|
|
1795
2002
|
*
|
|
2003
|
+
* @param session - The session the event belongs to. Everything the event
|
|
2004
|
+
* touches (data store, dedup baseline, counters, queues)
|
|
2005
|
+
* is that session's; only `recordResponseReceived()` ever
|
|
2006
|
+
* passes an archived one.
|
|
1796
2007
|
* @param type - The type of event to record.
|
|
1797
2008
|
* @param data - Additional data to record with the event.
|
|
1798
2009
|
* @param request - The media request that triggered the event, when
|
|
@@ -1800,21 +2011,21 @@ var CmcdReporter = class {
|
|
|
1800
2011
|
* populates this; it is threaded through to each
|
|
1801
2012
|
* target's `transform`.
|
|
1802
2013
|
*/
|
|
1803
|
-
emitEvent(type, data, request) {
|
|
2014
|
+
emitEvent(session, type, data, request) {
|
|
1804
2015
|
const entry = STATE_FIELDS_BY_EVENT.get(type);
|
|
1805
2016
|
if (entry) {
|
|
1806
2017
|
const field = entry.field;
|
|
1807
2018
|
const incoming = data[field];
|
|
1808
|
-
if (incoming !== void 0) Object.assign(
|
|
1809
|
-
const current =
|
|
2019
|
+
if (incoming !== void 0) Object.assign(session.data, { [field]: incoming });
|
|
2020
|
+
const current = session.data[field];
|
|
1810
2021
|
if (current === void 0) return;
|
|
1811
|
-
if (entry.equal(current,
|
|
1812
|
-
Object.assign(
|
|
2022
|
+
if (entry.equal(current, session.lastEmitted[field])) return;
|
|
2023
|
+
Object.assign(session.lastEmitted, { [field]: entry.snapshot(current) });
|
|
1813
2024
|
}
|
|
1814
2025
|
let failure;
|
|
1815
|
-
|
|
2026
|
+
session.eventTargets.forEach((target, config) => {
|
|
1816
2027
|
try {
|
|
1817
|
-
this.recordTargetEvent(target, config, type, data, request);
|
|
2028
|
+
this.recordTargetEvent(session, target, config, type, data, request);
|
|
1818
2029
|
} catch (error) {
|
|
1819
2030
|
failure ??= { error };
|
|
1820
2031
|
}
|
|
@@ -1834,17 +2045,18 @@ var CmcdReporter = class {
|
|
|
1834
2045
|
* @param request - The media request that triggered the event, when
|
|
1835
2046
|
* one exists. Passed to the target's `transform`.
|
|
1836
2047
|
*/
|
|
1837
|
-
recordTargetEvent(target, config, type, data = {}, request) {
|
|
1838
|
-
if (!config.events.includes(type)) return;
|
|
2048
|
+
recordTargetEvent(session, target, config, type, data = {}, request) {
|
|
2049
|
+
if (target.disposed || !config.events.includes(type)) return;
|
|
1839
2050
|
const item = {
|
|
1840
|
-
...
|
|
2051
|
+
...session.data,
|
|
1841
2052
|
...data,
|
|
2053
|
+
sid: session.sid,
|
|
1842
2054
|
e: type,
|
|
1843
2055
|
ts: data.ts ?? Date.now()
|
|
1844
2056
|
};
|
|
1845
2057
|
const { transform } = config;
|
|
1846
2058
|
if (!transform) {
|
|
1847
|
-
this.queueTargetEvent(target, item, type);
|
|
2059
|
+
this.queueTargetEvent(session, target, config, item, type);
|
|
1848
2060
|
return;
|
|
1849
2061
|
}
|
|
1850
2062
|
copyReportValues(item);
|
|
@@ -1855,27 +2067,33 @@ var CmcdReporter = class {
|
|
|
1855
2067
|
if (report == null) return;
|
|
1856
2068
|
if (!isUsableRequiredValue(report.ts)) report.ts = ts;
|
|
1857
2069
|
if (requiredKey && isUsableRequiredValue(requiredValue) && !isUsableRequiredValue(report[requiredKey])) Object.assign(report, { [requiredKey]: requiredValue });
|
|
1858
|
-
this.queueTargetEvent(target, report, type);
|
|
2070
|
+
this.queueTargetEvent(session, target, config, report, type);
|
|
1859
2071
|
}
|
|
1860
2072
|
/**
|
|
1861
|
-
* Stamps the reporter-owned fields on a finished event report
|
|
1862
|
-
* to the target's queue.
|
|
2073
|
+
* Stamps the reporter-owned fields on a finished event report, encodes it,
|
|
2074
|
+
* and pushes the wire line to the target's queue.
|
|
1863
2075
|
*
|
|
1864
2076
|
* Called after any transform has run, so a transform cannot bypass the
|
|
1865
|
-
* target's `events` filter via `e
|
|
2077
|
+
* target's `events` filter via `e`, break `sn` continuity, or substitute
|
|
2078
|
+
* the session identity carried by `sid` and `msd`. Encoding here means a
|
|
2079
|
+
* report that cannot serialize throws inside the recording call that
|
|
2080
|
+
* produced it, instead of rejecting the batch send and re-queueing
|
|
2081
|
+
* forever.
|
|
1866
2082
|
*
|
|
1867
2083
|
* @param target - The target to queue the report for.
|
|
2084
|
+
* @param config - The configuration for the target.
|
|
1868
2085
|
* @param report - The finished report data.
|
|
1869
2086
|
* @param type - The type of event being reported.
|
|
1870
2087
|
*/
|
|
1871
|
-
queueTargetEvent(target, report, type) {
|
|
2088
|
+
queueTargetEvent(session, target, config, report, type) {
|
|
1872
2089
|
report.e = type;
|
|
1873
2090
|
report.sn = target.sn++;
|
|
1874
|
-
|
|
1875
|
-
|
|
2091
|
+
report.sid = session.sid;
|
|
2092
|
+
if (!isNaN(session.msd) && !target.msdSent && config.enabledKeys?.includes("msd")) {
|
|
2093
|
+
report.msd = session.msd;
|
|
1876
2094
|
target.msdSent = true;
|
|
1877
|
-
}
|
|
1878
|
-
target.queue.push(report);
|
|
2095
|
+
} else delete report.msd;
|
|
2096
|
+
target.queue.push(encodeCmcd(report, createEncodingOptions(CMCD_EVENT_MODE, config)));
|
|
1879
2097
|
}
|
|
1880
2098
|
/**
|
|
1881
2099
|
* Records a response-received event. Called by the player when a media
|
|
@@ -1894,26 +2112,55 @@ var CmcdReporter = class {
|
|
|
1894
2112
|
*
|
|
1895
2113
|
* The request's `customData` is generic, so a player can pass a request
|
|
1896
2114
|
* carrying its own taxonomy (e.g. `{ requestType: 'segment' }`) without a
|
|
1897
|
-
* cast. The reporter only
|
|
1898
|
-
* {@link CmcdReporter.createRequestReport} writes there; every other key
|
|
1899
|
-
* left untouched and stays visible to an event target's `transform`
|
|
1900
|
-
* its `request` argument.
|
|
2115
|
+
* cast. The reporter reads only the provenance record that
|
|
2116
|
+
* {@link CmcdReporter.createRequestReport} writes there; every other key
|
|
2117
|
+
* is left untouched and stays visible to an event target's `transform`
|
|
2118
|
+
* via its `request` argument.
|
|
1901
2119
|
*
|
|
1902
2120
|
* A reporter given a concrete `C` requires the request to satisfy it, so a
|
|
1903
2121
|
* request the configured transforms could not read is rejected here rather
|
|
1904
2122
|
* than reaching them; see {@link CmcdReporterCustomData}.
|
|
1905
2123
|
*
|
|
2124
|
+
* The event is attributed to the session that issued the request, and to
|
|
2125
|
+
* nothing else: the provenance record that
|
|
2126
|
+
* {@link CmcdReporter.createRequestReport} stored on the request's
|
|
2127
|
+
* `customData` (under {@link CMCD_REQUEST_PROVENANCE}) selects the
|
|
2128
|
+
* session by its `sid`, or the response is dropped rather than
|
|
2129
|
+
* relabeled. There is no other key: a record lost to a serialization
|
|
2130
|
+
* boundary (and not restored; see the bridge notes on
|
|
2131
|
+
* {@link CMCD_REQUEST_PROVENANCE}) and a session no longer retained
|
|
2132
|
+
* (see `CmcdReporterConfig.sessionRetention`) both drop, and a per-call
|
|
2133
|
+
* `data.sid` cannot substitute. The record is honored wherever its
|
|
2134
|
+
* `sid` resolves: a request decorated by another reporter configured
|
|
2135
|
+
* with the same session attributes here, as does a hand-built record.
|
|
2136
|
+
* A response that completes after a `sid` change reports under its own
|
|
2137
|
+
* retained session, with that session's data snapshot and sequence
|
|
2138
|
+
* numbers.
|
|
2139
|
+
*
|
|
2140
|
+
* Request-time report keys come from the record's encoded per-call
|
|
2141
|
+
* `data` snapshot, decoded fresh per response, and the record's `cid`
|
|
2142
|
+
* reports in place of the session's current one, so a response that
|
|
2143
|
+
* completes after a mid-session content change keeps the meaning it
|
|
2144
|
+
* had when its request was issued.
|
|
2145
|
+
*
|
|
1906
2146
|
* @typeParam RD - The `customData` this request carries. Defaults to the
|
|
1907
2147
|
* reporter's own `C`.
|
|
1908
2148
|
*
|
|
1909
2149
|
* @param response - The HTTP response received.
|
|
1910
2150
|
* @param data - Additional CMCD data to include with the event.
|
|
1911
2151
|
* Values provided here override any auto-derived values.
|
|
2152
|
+
* Session-owned keys (`sid`, `msd`) supplied here are
|
|
2153
|
+
* ignored; attribution is by the provenance record alone.
|
|
1912
2154
|
*/
|
|
1913
2155
|
recordResponseReceived(response, data = {}) {
|
|
1914
2156
|
const { request } = response;
|
|
1915
2157
|
const url = data.url ?? request?.url;
|
|
1916
2158
|
if (!url) return;
|
|
2159
|
+
const provenance = request.customData?.[CMCD_REQUEST_PROVENANCE];
|
|
2160
|
+
const session = this.resolveSession(provenance);
|
|
2161
|
+
if (!session) return;
|
|
2162
|
+
const cmcd = decodeSnapshot(provenance);
|
|
2163
|
+
const { cid } = provenance;
|
|
1917
2164
|
const urlObj = new URL(url);
|
|
1918
2165
|
urlObj.searchParams.delete(CMCD_PARAM);
|
|
1919
2166
|
const derived = {
|
|
@@ -1928,14 +2175,28 @@ var CmcdReporter = class {
|
|
|
1928
2175
|
}
|
|
1929
2176
|
if (timing.duration != null) derived.ttlb = Math.round(timing.duration);
|
|
1930
2177
|
}
|
|
1931
|
-
|
|
1932
|
-
|
|
2178
|
+
this.emitEvent(session, CMCD_EVENT_RESPONSE_RECEIVED, {
|
|
2179
|
+
...typeof cid === "string" && cid ? { cid } : void 0,
|
|
1933
2180
|
...cmcd,
|
|
1934
2181
|
...derived,
|
|
1935
2182
|
...data
|
|
1936
2183
|
}, request);
|
|
1937
2184
|
}
|
|
1938
2185
|
/**
|
|
2186
|
+
* Resolves the session a response belongs to: the provenance record's
|
|
2187
|
+
* `sid` names one of this reporter's retained sessions, or the response
|
|
2188
|
+
* is dropped. There is no other key — a record that was lost, or that
|
|
2189
|
+
* names an evicted or never-seen `sid`, resolves nothing, and
|
|
2190
|
+
* attributing it anywhere else would relabel it. The `sid` is read
|
|
2191
|
+
* structurally, so a JSON-revived copy of a record attributes exactly,
|
|
2192
|
+
* and a hand-built record naming a retained session is honored.
|
|
2193
|
+
*/
|
|
2194
|
+
resolveSession(provenance) {
|
|
2195
|
+
if (provenance === null || typeof provenance !== "object") return;
|
|
2196
|
+
const { sid } = provenance;
|
|
2197
|
+
return typeof sid === "string" ? this.sessions.get(sid) : void 0;
|
|
2198
|
+
}
|
|
2199
|
+
/**
|
|
1939
2200
|
* Applies the CMCD request report data to the request. Called by the player
|
|
1940
2201
|
* before sending the request.
|
|
1941
2202
|
*
|
|
@@ -1956,6 +2217,34 @@ var CmcdReporter = class {
|
|
|
1956
2217
|
return !!this.config.enabledKeys?.length;
|
|
1957
2218
|
}
|
|
1958
2219
|
/**
|
|
2220
|
+
* The provenance record to stamp on a returned request: the session's
|
|
2221
|
+
* frozen base record, extended with the caller's per-call data encoded
|
|
2222
|
+
* as a CMCD string when there is any. Requests without per-call data
|
|
2223
|
+
* share the base record, so decoration stays a single property write.
|
|
2224
|
+
* The snapshot never contains reporter-stamped fields, and a value that
|
|
2225
|
+
* cannot encode contributes no snapshot rather than failing the
|
|
2226
|
+
* request; its response still attributes and reports derived keys over
|
|
2227
|
+
* session data, matching `decodeSnapshot()`'s tolerance on the read
|
|
2228
|
+
* side.
|
|
2229
|
+
*/
|
|
2230
|
+
createRequestProvenance(data, baseUrl) {
|
|
2231
|
+
const base = this.session.provenance;
|
|
2232
|
+
if (!data) return base;
|
|
2233
|
+
try {
|
|
2234
|
+
const encoded = encodeCmcd(data, {
|
|
2235
|
+
version: this.config.version,
|
|
2236
|
+
reportingMode: CMCD_REQUEST_MODE,
|
|
2237
|
+
baseUrl
|
|
2238
|
+
});
|
|
2239
|
+
return encoded ? Object.freeze({
|
|
2240
|
+
...base,
|
|
2241
|
+
data: encoded
|
|
2242
|
+
}) : base;
|
|
2243
|
+
} catch {
|
|
2244
|
+
return base;
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
/**
|
|
1959
2248
|
* Creates a new request with the CMCD request report data applied. Called by the player
|
|
1960
2249
|
* before sending the request.
|
|
1961
2250
|
*
|
|
@@ -1980,13 +2269,16 @@ var CmcdReporter = class {
|
|
|
1980
2269
|
headers: { ...headers },
|
|
1981
2270
|
customData: {
|
|
1982
2271
|
...customData,
|
|
1983
|
-
cmcd: {}
|
|
2272
|
+
cmcd: {},
|
|
2273
|
+
[CMCD_REQUEST_PROVENANCE]: this.createRequestProvenance(data, request.url)
|
|
1984
2274
|
}
|
|
1985
2275
|
};
|
|
1986
2276
|
if (!this.config.enabledKeys?.length || !report.url) return report;
|
|
2277
|
+
const session = this.session;
|
|
1987
2278
|
const merged = {
|
|
1988
|
-
...
|
|
1989
|
-
...data
|
|
2279
|
+
...session.data,
|
|
2280
|
+
...data,
|
|
2281
|
+
sid: session.sid
|
|
1990
2282
|
};
|
|
1991
2283
|
const { transform } = this.config;
|
|
1992
2284
|
let cmcdData = merged;
|
|
@@ -1995,19 +2287,20 @@ var CmcdReporter = class {
|
|
|
1995
2287
|
cmcdData = transform(merged, request);
|
|
1996
2288
|
if (cmcdData == null) return report;
|
|
1997
2289
|
}
|
|
1998
|
-
cmcdData.sn =
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2290
|
+
cmcdData.sn = session.requestTarget.sn++;
|
|
2291
|
+
cmcdData.sid = session.sid;
|
|
2292
|
+
const sendMsd = !isNaN(session.msd) && !session.requestTarget.msdSent;
|
|
2293
|
+
if (sendMsd) cmcdData.msd = session.msd;
|
|
2294
|
+
else delete cmcdData.msd;
|
|
2003
2295
|
const url = new URL(report.url);
|
|
2004
2296
|
const options = createEncodingOptions(CMCD_REQUEST_MODE, this.config, report.url);
|
|
2005
|
-
const cmcd = report.customData.cmcd = prepareCmcdData(cmcdData, options);
|
|
2297
|
+
const cmcd = report.customData.cmcd = copyReportValues(prepareCmcdData(cmcdData, options));
|
|
2298
|
+
if (sendMsd && cmcd.msd !== void 0) session.requestTarget.msdSent = true;
|
|
2299
|
+
const encoded = encodePreparedCmcd(cmcd);
|
|
2006
2300
|
switch (this.config.transmissionMode) {
|
|
2007
2301
|
case CMCD_QUERY:
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
url.searchParams.set(CMCD_PARAM, param);
|
|
2302
|
+
if (encoded) {
|
|
2303
|
+
url.searchParams.set(CMCD_PARAM, encoded);
|
|
2011
2304
|
report.url = url.toString();
|
|
2012
2305
|
}
|
|
2013
2306
|
break;
|
|
@@ -2024,16 +2317,19 @@ var CmcdReporter = class {
|
|
|
2024
2317
|
*/
|
|
2025
2318
|
processEventTargets(flush = false) {
|
|
2026
2319
|
let reprocess = false;
|
|
2027
|
-
this.
|
|
2028
|
-
const
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2320
|
+
this.sessions.forEach((session) => {
|
|
2321
|
+
const drain = flush || session !== this.session;
|
|
2322
|
+
session.eventTargets.forEach((target, config) => {
|
|
2323
|
+
const { queue } = target;
|
|
2324
|
+
if (target.disposed || !queue.length) return;
|
|
2325
|
+
if (queue.length < config.batchSize && !drain) return;
|
|
2326
|
+
const deleteCount = drain ? queue.length : config.batchSize;
|
|
2327
|
+
const events = queue.splice(0, deleteCount);
|
|
2328
|
+
this.sendEventReport(session, config, events).catch(() => {
|
|
2329
|
+
target.queue.unshift(...events);
|
|
2330
|
+
});
|
|
2331
|
+
reprocess ||= queue.length > 0;
|
|
2035
2332
|
});
|
|
2036
|
-
reprocess ||= queue.length > 0;
|
|
2037
2333
|
});
|
|
2038
2334
|
if (reprocess) this.processEventTargets();
|
|
2039
2335
|
}
|
|
@@ -2041,44 +2337,42 @@ var CmcdReporter = class {
|
|
|
2041
2337
|
* Sends an event report. Called by the reporter when a batch is ready to be sent.
|
|
2042
2338
|
*
|
|
2043
2339
|
* @param config - The target config to send the event report to.
|
|
2044
|
-
* @param data - The
|
|
2340
|
+
* @param data - The encoded report lines to send in the event report.
|
|
2045
2341
|
*/
|
|
2046
|
-
async sendEventReport(config, data) {
|
|
2047
|
-
const options = createEncodingOptions(CMCD_EVENT_MODE, config);
|
|
2342
|
+
async sendEventReport(session, config, data) {
|
|
2048
2343
|
const { status } = await this.requester({
|
|
2049
2344
|
url: config.url,
|
|
2050
2345
|
method: "POST",
|
|
2051
2346
|
headers: { "Content-Type": CMCD_MIME_TYPE },
|
|
2052
|
-
body: data.
|
|
2347
|
+
body: data.join("\n") + "\n"
|
|
2053
2348
|
});
|
|
2054
|
-
if (status === 410) this.disposeEventTarget(config);
|
|
2349
|
+
if (status === 410) this.disposeEventTarget(session, config);
|
|
2055
2350
|
else if (status === 429 || status > 499 && status < 600) throw new Error(`Event report failed with status ${status}`);
|
|
2056
2351
|
}
|
|
2057
2352
|
/**
|
|
2058
|
-
* Cancels the time-interval timer for an event target and clears the
|
|
2059
|
-
* Safe to call when no timer is armed (clearInterval(undefined)
|
|
2353
|
+
* Cancels the time-interval timer for an event target config and clears the
|
|
2354
|
+
* stored id. Safe to call when no timer is armed (clearInterval(undefined)
|
|
2355
|
+
* is a no-op).
|
|
2060
2356
|
*/
|
|
2061
|
-
disarmInterval(
|
|
2062
|
-
clearInterval(
|
|
2063
|
-
|
|
2357
|
+
disarmInterval(config) {
|
|
2358
|
+
clearInterval(this.intervals.get(config));
|
|
2359
|
+
this.intervals.delete(config);
|
|
2064
2360
|
}
|
|
2065
2361
|
/**
|
|
2066
|
-
*
|
|
2067
|
-
*
|
|
2362
|
+
* Silences an event target URL for the remainder of its session: drops the
|
|
2363
|
+
* queues and blocks further enqueues for every config in the session that
|
|
2364
|
+
* reports to the URL, per CTA-5004-B's per-target-URL suppression scope,
|
|
2365
|
+
* and cancels their timers when the session is the live one. Used when the
|
|
2366
|
+
* collector signals the target is gone (HTTP 410). A session started after
|
|
2367
|
+
* the disposal is unaffected, since it gets fresh target state.
|
|
2068
2368
|
*/
|
|
2069
|
-
disposeEventTarget(config) {
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
* Resets the session related data. Called when the session ID changes.
|
|
2077
|
-
*/
|
|
2078
|
-
resetSession() {
|
|
2079
|
-
this.eventTargets.forEach((target) => target.sn = 0);
|
|
2080
|
-
this.requestTarget.sn = 0;
|
|
2081
|
-
this.lastEmitted = {};
|
|
2369
|
+
disposeEventTarget(session, config) {
|
|
2370
|
+
session.eventTargets.forEach((target, sibling) => {
|
|
2371
|
+
if (sibling.url !== config.url || target.disposed) return;
|
|
2372
|
+
target.disposed = true;
|
|
2373
|
+
target.queue.length = 0;
|
|
2374
|
+
if (session === this.session) this.disarmInterval(sibling);
|
|
2375
|
+
});
|
|
2082
2376
|
}
|
|
2083
2377
|
};
|
|
2084
2378
|
|
|
@@ -2141,51 +2435,6 @@ const CmcdValidationSeverity = {
|
|
|
2141
2435
|
WARNING: CMCD_VALIDATION_SEVERITY_WARNING
|
|
2142
2436
|
};
|
|
2143
2437
|
|
|
2144
|
-
//#endregion
|
|
2145
|
-
//#region src/upConvertToV2.ts
|
|
2146
|
-
/**
|
|
2147
|
-
* Up-convert V1 CMCD data to V2 format.
|
|
2148
|
-
*
|
|
2149
|
-
* - Wraps plain scalar values in arrays for inner-list keys.
|
|
2150
|
-
* - Wraps `nor` string in an array.
|
|
2151
|
-
*
|
|
2152
|
-
* If the data is already V2 (has `v: 2`), it is returned unchanged.
|
|
2153
|
-
*
|
|
2154
|
-
* @internal
|
|
2155
|
-
*/
|
|
2156
|
-
function upConvertToV2(obj) {
|
|
2157
|
-
if (obj["v"] === CMCD_V2) return obj;
|
|
2158
|
-
const result = {};
|
|
2159
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
2160
|
-
if (value == null) {
|
|
2161
|
-
result[key] = value;
|
|
2162
|
-
continue;
|
|
2163
|
-
}
|
|
2164
|
-
if (CMCD_INNER_LIST_KEYS.has(key) && !Array.isArray(value)) result[key] = [value];
|
|
2165
|
-
else if (key === "nor" && typeof value === "string") result[key] = [value];
|
|
2166
|
-
else result[key] = value;
|
|
2167
|
-
}
|
|
2168
|
-
return result;
|
|
2169
|
-
}
|
|
2170
|
-
|
|
2171
|
-
//#endregion
|
|
2172
|
-
//#region src/decodeCmcd.ts
|
|
2173
|
-
function reduceValue(value) {
|
|
2174
|
-
if (Array.isArray(value)) return value.map(reduceValue);
|
|
2175
|
-
if (typeof value === "symbol") return symbolToStr(value);
|
|
2176
|
-
if (value instanceof SfItem && !value.params) return reduceValue(value.value);
|
|
2177
|
-
if (typeof value === "string") return value;
|
|
2178
|
-
return value;
|
|
2179
|
-
}
|
|
2180
|
-
function decodeCmcd(cmcd, options) {
|
|
2181
|
-
if (!cmcd) return {};
|
|
2182
|
-
const sfDict = decodeSfDict(cmcd);
|
|
2183
|
-
const result = {};
|
|
2184
|
-
for (const [key, item] of Object.entries(sfDict)) result[key] = reduceValue(item.value);
|
|
2185
|
-
if (options?.convertToLatest) return upConvertToV2(result);
|
|
2186
|
-
return result;
|
|
2187
|
-
}
|
|
2188
|
-
|
|
2189
2438
|
//#endregion
|
|
2190
2439
|
//#region src/ensureHeaders.ts
|
|
2191
2440
|
/**
|
|
@@ -2386,7 +2635,8 @@ function validateCmcdStructure(data, options) {
|
|
|
2386
2635
|
});
|
|
2387
2636
|
}
|
|
2388
2637
|
if ("e" in data) {
|
|
2389
|
-
|
|
2638
|
+
const eventType = toTokenString(data["e"]);
|
|
2639
|
+
if (eventType === CMCD_EVENT_CUSTOM_EVENT) {
|
|
2390
2640
|
if (!("cen" in data)) issues.push({
|
|
2391
2641
|
key: "cen",
|
|
2392
2642
|
message: "Custom event (e=\"ce\") requires the \"cen\" key to be present.",
|
|
@@ -2397,7 +2647,7 @@ function validateCmcdStructure(data, options) {
|
|
|
2397
2647
|
message: "The \"cen\" key must only be present when e=\"ce\".",
|
|
2398
2648
|
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2399
2649
|
});
|
|
2400
|
-
if (
|
|
2650
|
+
if (eventType === CMCD_EVENT_RESPONSE_RECEIVED) {
|
|
2401
2651
|
if (!("url" in data)) issues.push({
|
|
2402
2652
|
key: "url",
|
|
2403
2653
|
message: "Response received event (e=\"rr\") requires the \"url\" key to be present.",
|
|
@@ -2408,12 +2658,12 @@ function validateCmcdStructure(data, options) {
|
|
|
2408
2658
|
message: `Response key "${key}" must only be present when e="rr".`,
|
|
2409
2659
|
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2410
2660
|
});
|
|
2411
|
-
for (const [
|
|
2661
|
+
for (const [stateEventType, requiredField] of CMCD_STATE_EVENT_FIELDS) if (eventType === stateEventType && !(requiredField in data)) issues.push({
|
|
2412
2662
|
key: requiredField,
|
|
2413
|
-
message: `State-change event (e="${
|
|
2663
|
+
message: `State-change event (e="${stateEventType}") requires the "${requiredField}" key to be present.`,
|
|
2414
2664
|
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2415
2665
|
});
|
|
2416
|
-
if (
|
|
2666
|
+
if (eventType === CMCD_EVENT_ERROR && !("ec" in data)) issues.push({
|
|
2417
2667
|
key: "ec",
|
|
2418
2668
|
message: "Error event (e=\"e\") requires the \"ec\" key to be present.",
|
|
2419
2669
|
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
@@ -2799,9 +3049,10 @@ function validateCmcdValues(data, options) {
|
|
|
2799
3049
|
break;
|
|
2800
3050
|
case CMCD_KEY_TYPE_TOKEN: {
|
|
2801
3051
|
const validValues = CMCD_TOKEN_VALUES[key];
|
|
2802
|
-
|
|
3052
|
+
const token = toTokenString(value);
|
|
3053
|
+
if (validValues && (token === void 0 || !validValues.includes(token))) issues.push({
|
|
2803
3054
|
key,
|
|
2804
|
-
message: `Key "${key}" has invalid token value "${String(value)}". Expected one of: ${validValues.join(", ")}.`,
|
|
3055
|
+
message: `Key "${key}" has invalid token value "${token ?? String(value)}". Expected one of: ${validValues.join(", ")}.`,
|
|
2805
3056
|
severity: CMCD_VALIDATION_SEVERITY_ERROR
|
|
2806
3057
|
});
|
|
2807
3058
|
break;
|
|
@@ -3106,5 +3357,5 @@ function extractHeaderRecord(headers) {
|
|
|
3106
3357
|
}
|
|
3107
3358
|
|
|
3108
3359
|
//#endregion
|
|
3109
|
-
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_PLAYBACK_RATE, 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_RECORDED_REPORT_MODE_EVENT, CMCD_RECORDED_REPORT_MODE_HEADER, CMCD_RECORDED_REPORT_MODE_QUERY, CMCD_RECORDED_REQUEST_TYPE_EVENT, CMCD_RECORDED_REQUEST_TYPE_MANIFEST, CMCD_RECORDED_REQUEST_TYPE_SEGMENT, CMCD_RECORDED_REQUEST_TYPE_UNKNOWN, 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, CmcdRecordedReportMode, CmcdRecordedRequestType, CmcdReportRecorder, CmcdReporter, CmcdReportingMode, CmcdStreamType, CmcdStreamingFormat, CmcdTransmissionMode, CmcdValidationSeverity, appendCmcdHeaders, appendCmcdQuery, createFetchTransport, createXhrTransport, 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 };
|
|
3360
|
+
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_PLAYBACK_RATE, 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_RECORDED_REPORT_MODE_EVENT, CMCD_RECORDED_REPORT_MODE_HEADER, CMCD_RECORDED_REPORT_MODE_QUERY, CMCD_RECORDED_REQUEST_TYPE_EVENT, CMCD_RECORDED_REQUEST_TYPE_MANIFEST, CMCD_RECORDED_REQUEST_TYPE_SEGMENT, CMCD_RECORDED_REQUEST_TYPE_UNKNOWN, CMCD_REQUEST, CMCD_REQUEST_KEYS, CMCD_REQUEST_MODE, CMCD_REQUEST_PROVENANCE, 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, CmcdRecordedReportMode, CmcdRecordedRequestType, CmcdReportRecorder, CmcdReporter, CmcdReportingMode, CmcdStreamType, CmcdStreamingFormat, CmcdTransmissionMode, CmcdValidationSeverity, appendCmcdHeaders, appendCmcdQuery, createFetchTransport, createXhrTransport, 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 };
|
|
3110
3361
|
//# sourceMappingURL=index.js.map
|