@svta/cml-cmcd 2.4.1 → 2.6.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/README.md +4 -4
- package/dist/index.d.ts +479 -29
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +536 -146
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -315,9 +315,12 @@ const CMCD_REQUEST_KEYS = [
|
|
|
315
315
|
|
|
316
316
|
//#endregion
|
|
317
317
|
//#region src/isCmcdCustomKey.ts
|
|
318
|
-
const CUSTOM_KEY_REGEX = /^[a-
|
|
318
|
+
const CUSTOM_KEY_REGEX = /^[a-z][a-z0-9.-]*$/;
|
|
319
319
|
/**
|
|
320
|
-
* Check if a key is a custom key
|
|
320
|
+
* Check if a key is a valid custom key: a lowercase first letter, then
|
|
321
|
+
* characters from `a-z 0-9 . -`, with a hyphen that is neither the first
|
|
322
|
+
* nor the last character. These are the CTA-5004-B custom-key rules
|
|
323
|
+
* restricted to names that survive RFC 8941 key serialization.
|
|
321
324
|
*
|
|
322
325
|
* @param key - The key to check.
|
|
323
326
|
*
|
|
@@ -569,6 +572,9 @@ function downConvertToV1(obj) {
|
|
|
569
572
|
* @param options - Options for encoding.
|
|
570
573
|
*
|
|
571
574
|
* @public
|
|
575
|
+
*
|
|
576
|
+
* @example
|
|
577
|
+
* {@includeCode ../test/prepareCmcdData.test.ts#example}
|
|
572
578
|
*/
|
|
573
579
|
function prepareCmcdData(obj, options = {}) {
|
|
574
580
|
const results = {};
|
|
@@ -587,6 +593,8 @@ function prepareCmcdData(obj, options = {}) {
|
|
|
587
593
|
if (!keys.includes("e") && eventType != null) keys.push("e");
|
|
588
594
|
if (!keys.includes("ts")) keys.push("ts");
|
|
589
595
|
if (!keys.includes("cen") && data["cen"] != null && eventType === CMCD_EVENT_CUSTOM_EVENT) keys.push("cen");
|
|
596
|
+
if (!keys.includes("ec") && data["ec"] != null && eventType === CMCD_EVENT_ERROR) keys.push("ec");
|
|
597
|
+
if (!keys.includes("url") && data["url"] != null && eventType === CMCD_EVENT_RESPONSE_RECEIVED) keys.push("url");
|
|
590
598
|
const requiredField = eventType ? CMCD_STATE_EVENT_FIELDS.get(eventType) : void 0;
|
|
591
599
|
if (requiredField && data[requiredField] != null && !keys.includes(requiredField)) keys.push(requiredField);
|
|
592
600
|
}
|
|
@@ -962,6 +970,33 @@ const CMCD_KEYS = /* @__PURE__ */ [
|
|
|
962
970
|
*/
|
|
963
971
|
const CMCD_MIME_TYPE = "application/cmcd";
|
|
964
972
|
|
|
973
|
+
//#endregion
|
|
974
|
+
//#region src/CMCD_REQUEST_PROVENANCE.ts
|
|
975
|
+
/**
|
|
976
|
+
* The `customData` key under which `CmcdReporter.createRequestReport()`
|
|
977
|
+
* stamps session provenance on every request it returns.
|
|
978
|
+
*
|
|
979
|
+
* The value is a frozen `CmcdRequestProvenance` record: `sid` names the
|
|
980
|
+
* session that issued the request, `cid` is the content id in effect when
|
|
981
|
+
* the request was issued, and `data` carries the request's per-call data
|
|
982
|
+
* as an encoded CMCD string. `CmcdReporter.recordResponseReceived()` reads
|
|
983
|
+
* the record to attribute a late response to its session and to rebuild
|
|
984
|
+
* the request-time report keys.
|
|
985
|
+
* Spread and `Object.assign` carry the record through ordinary request
|
|
986
|
+
* clones, but `JSON.stringify` and structured clone drop symbol-keyed
|
|
987
|
+
* properties: read the value before such a boundary and restore it
|
|
988
|
+
* afterward. The record itself survives JSON, and attribution is by the
|
|
989
|
+
* `sid` value rather than object identity, so a revived plain-object copy
|
|
990
|
+
* attributes exactly. A record naming a `sid` the reporter does not retain
|
|
991
|
+
* drops the response rather than relabeling it.
|
|
992
|
+
*
|
|
993
|
+
* Backed by the symbol registry so duplicated copies of this library in
|
|
994
|
+
* one bundle interoperate. The registry key is stable across versions.
|
|
995
|
+
*
|
|
996
|
+
* @public
|
|
997
|
+
*/
|
|
998
|
+
const CMCD_REQUEST_PROVENANCE = /* @__PURE__ */ Symbol.for("@svta/cml-cmcd/request-provenance");
|
|
999
|
+
|
|
965
1000
|
//#endregion
|
|
966
1001
|
//#region src/CMCD_V1.ts
|
|
967
1002
|
/**
|
|
@@ -1465,6 +1500,50 @@ const CmcdTransmissionMode = {
|
|
|
1465
1500
|
HEADERS: CMCD_HEADERS
|
|
1466
1501
|
};
|
|
1467
1502
|
|
|
1503
|
+
//#endregion
|
|
1504
|
+
//#region src/upConvertToV2.ts
|
|
1505
|
+
/**
|
|
1506
|
+
* Up-convert V1 CMCD data to V2 format.
|
|
1507
|
+
*
|
|
1508
|
+
* - Wraps plain scalar values in arrays for inner-list keys.
|
|
1509
|
+
* - Wraps `nor` string in an array.
|
|
1510
|
+
*
|
|
1511
|
+
* If the data is already V2 (has `v: 2`), it is returned unchanged.
|
|
1512
|
+
*
|
|
1513
|
+
* @internal
|
|
1514
|
+
*/
|
|
1515
|
+
function upConvertToV2(obj) {
|
|
1516
|
+
if (obj["v"] === CMCD_V2) return obj;
|
|
1517
|
+
const result = {};
|
|
1518
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1519
|
+
if (value == null) {
|
|
1520
|
+
result[key] = value;
|
|
1521
|
+
continue;
|
|
1522
|
+
}
|
|
1523
|
+
if (CMCD_INNER_LIST_KEYS.has(key) && !Array.isArray(value)) result[key] = [value];
|
|
1524
|
+
else if (key === "nor" && typeof value === "string") result[key] = [value];
|
|
1525
|
+
else result[key] = value;
|
|
1526
|
+
}
|
|
1527
|
+
return result;
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
//#endregion
|
|
1531
|
+
//#region src/decodeCmcd.ts
|
|
1532
|
+
function reduceValue(value, useSymbol) {
|
|
1533
|
+
if (Array.isArray(value)) return value.map((item) => reduceValue(item, useSymbol));
|
|
1534
|
+
if (typeof value === "symbol") return useSymbol === void 0 ? symbolToStr(value) : value;
|
|
1535
|
+
if (value instanceof SfItem) return value.params ? new SfItem(reduceValue(value.value, useSymbol), value.params) : reduceValue(value.value, useSymbol);
|
|
1536
|
+
return value;
|
|
1537
|
+
}
|
|
1538
|
+
function decodeCmcd(cmcd, options) {
|
|
1539
|
+
if (!cmcd) return {};
|
|
1540
|
+
const sfDict = decodeSfDict(cmcd, options);
|
|
1541
|
+
const result = {};
|
|
1542
|
+
for (const [key, item] of Object.entries(sfDict)) result[key] = reduceValue(item, options?.useSymbol);
|
|
1543
|
+
if (options?.convertToLatest) return upConvertToV2(result);
|
|
1544
|
+
return result;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1468
1547
|
//#endregion
|
|
1469
1548
|
//#region src/CmcdReporter.ts
|
|
1470
1549
|
function createEncodingOptions(reportingMode, config, baseUrl) {
|
|
@@ -1473,7 +1552,8 @@ function createEncodingOptions(reportingMode, config, baseUrl) {
|
|
|
1473
1552
|
version: config.version || CMCD_V2,
|
|
1474
1553
|
reportingMode,
|
|
1475
1554
|
filter: (key) => enabledKeySet.has(key),
|
|
1476
|
-
baseUrl
|
|
1555
|
+
baseUrl,
|
|
1556
|
+
customHeaderMap: config.customHeaderMap
|
|
1477
1557
|
};
|
|
1478
1558
|
}
|
|
1479
1559
|
/**
|
|
@@ -1523,17 +1603,115 @@ const STATE_FIELDS = /* @__PURE__ */ Array.from(CMCD_STATE_EVENT_FIELDS, ([event
|
|
|
1523
1603
|
};
|
|
1524
1604
|
});
|
|
1525
1605
|
const STATE_FIELDS_BY_EVENT = /* @__PURE__ */ new Map(/* @__PURE__ */ STATE_FIELDS.map((e) => [e.event, e]));
|
|
1606
|
+
/**
|
|
1607
|
+
* Maps each event type to the key CTA-5004-B requires beyond `e` and `ts`.
|
|
1608
|
+
* Built from the state-change table plus the three event types whose
|
|
1609
|
+
* required key rides the caller's per-event data.
|
|
1610
|
+
*/
|
|
1611
|
+
const CMCD_REQUIRED_EVENT_KEYS = /* @__PURE__ */ new Map([
|
|
1612
|
+
...CMCD_STATE_EVENT_FIELDS,
|
|
1613
|
+
[CMCD_EVENT_CUSTOM_EVENT, "cen"],
|
|
1614
|
+
[CMCD_EVENT_ERROR, "ec"],
|
|
1615
|
+
[CMCD_EVENT_RESPONSE_RECEIVED, "url"]
|
|
1616
|
+
]);
|
|
1617
|
+
/**
|
|
1618
|
+
* Whether a required key's value will survive report preparation.
|
|
1619
|
+
*
|
|
1620
|
+
* This is `isValid` minus its `false` exclusion, plus an empty-array check.
|
|
1621
|
+
* `false` must count as usable because `bg: false` is a legitimate value on a
|
|
1622
|
+
* backgrounded-mode event, which the encoder emits as `?0`; treating it as
|
|
1623
|
+
* unusable would let restoration silently revert a transform that cleared it.
|
|
1624
|
+
* Empty strings, empty lists and non-finite numbers are dropped downstream, so
|
|
1625
|
+
* a transform substituting one leaves the report short a required key.
|
|
1626
|
+
*/
|
|
1627
|
+
function isUsableRequiredValue(value) {
|
|
1628
|
+
if (value == null || value === "") return false;
|
|
1629
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
1630
|
+
return !Array.isArray(value) || value.length > 0;
|
|
1631
|
+
}
|
|
1632
|
+
/**
|
|
1633
|
+
* Copies an `SfItem`-shaped value and its `params` record.
|
|
1634
|
+
*
|
|
1635
|
+
* The prototype is preserved because `prepareCmcdData`, the formatter map,
|
|
1636
|
+
* validation, and the structured-field encoder all branch on
|
|
1637
|
+
* `instanceof SfItem`. A plain spread (and `structuredClone`) would return a
|
|
1638
|
+
* prototype-less object and silently change what goes on the wire.
|
|
1639
|
+
*/
|
|
1640
|
+
function copyItemValue(value) {
|
|
1641
|
+
if (value === null || typeof value !== "object") return value;
|
|
1642
|
+
const copy = Object.assign(Object.create(Object.getPrototypeOf(value)), value);
|
|
1643
|
+
if (copy.params !== null && typeof copy.params === "object") copy.params = { ...copy.params };
|
|
1644
|
+
return copy;
|
|
1645
|
+
}
|
|
1646
|
+
/**
|
|
1647
|
+
* Copies the nested values of a report in place so a transform cannot mutate
|
|
1648
|
+
* the reporter's persistent data, or another target's report for the same
|
|
1649
|
+
* event, by mutating an array or an `SfItem` it was handed.
|
|
1650
|
+
*
|
|
1651
|
+
* Complete for the CMCD value space rather than best-effort: `CmcdValue` and
|
|
1652
|
+
* `CmcdCustomValue` admit only primitives, `SfItem<primitive>`, and arrays of
|
|
1653
|
+
* those, and `SfItem.params` is a flat record. Called where a transform is
|
|
1654
|
+
* configured, on the ended session's store at archival (detaching the frozen
|
|
1655
|
+
* snapshot from caller-held references), and on the request's stored
|
|
1656
|
+
* player-facing view.
|
|
1657
|
+
*/
|
|
1658
|
+
function copyReportValues(data) {
|
|
1659
|
+
const record = data;
|
|
1660
|
+
for (const key in record) {
|
|
1661
|
+
const value = record[key];
|
|
1662
|
+
if (Array.isArray(value)) {
|
|
1663
|
+
const copy = new Array(value.length);
|
|
1664
|
+
for (let i = 0; i < value.length; i++) copy[i] = copyItemValue(value[i]);
|
|
1665
|
+
record[key] = copy;
|
|
1666
|
+
} else if (value !== null && typeof value === "object") record[key] = copyItemValue(value);
|
|
1667
|
+
}
|
|
1668
|
+
return data;
|
|
1669
|
+
}
|
|
1670
|
+
/**
|
|
1671
|
+
* Decodes the per-call data snapshot a provenance record carries into
|
|
1672
|
+
* fresh, encodable report data: tokens revive as `SfToken` and
|
|
1673
|
+
* params-bearing items as `SfItem` (`useSymbol: false`), so every value
|
|
1674
|
+
* keeps its wire type through re-encoding. Only called in
|
|
1675
|
+
* `recordResponseReceived()`. Returns an empty object when the record is
|
|
1676
|
+
* absent, carries no snapshot, or the snapshot does not parse (a value
|
|
1677
|
+
* this reporter did not write); the response then reports its derived keys
|
|
1678
|
+
* over the session's data alone.
|
|
1679
|
+
*/
|
|
1680
|
+
function decodeSnapshot(provenance) {
|
|
1681
|
+
const encoded = provenance !== null && typeof provenance === "object" ? provenance.data : void 0;
|
|
1682
|
+
if (typeof encoded !== "string" || !encoded) return {};
|
|
1683
|
+
try {
|
|
1684
|
+
return decodeCmcd(encoded, { useSymbol: false });
|
|
1685
|
+
} catch {
|
|
1686
|
+
return {};
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
/**
|
|
1690
|
+
* Mints a session's frozen base provenance record: the issuing `sid`, and
|
|
1691
|
+
* the `cid` in effect at mint time. `update()` re-mints on every `cid`
|
|
1692
|
+
* change, so requests issued before a mid-session content change keep the
|
|
1693
|
+
* `cid` they were issued under while later requests carry the new one.
|
|
1694
|
+
*/
|
|
1695
|
+
function mintProvenance(sid, cid) {
|
|
1696
|
+
return Object.freeze(typeof cid === "string" && cid ? {
|
|
1697
|
+
sid,
|
|
1698
|
+
cid
|
|
1699
|
+
} : { sid });
|
|
1700
|
+
}
|
|
1526
1701
|
function defaultRequester(request) {
|
|
1527
1702
|
const { url,...init } = request;
|
|
1528
1703
|
return fetch(url, init);
|
|
1529
1704
|
}
|
|
1530
1705
|
function createCmcdReporterConfig(config) {
|
|
1531
1706
|
const { version = CMCD_V2, eventTargets = [], sid = uuid(), transmissionMode = CMCD_QUERY,...rest } = config;
|
|
1707
|
+
const retention = config.sessionRetention;
|
|
1708
|
+
const sessionRetention = typeof retention === "number" ? Math.floor(retention) : NaN;
|
|
1532
1709
|
return {
|
|
1533
1710
|
...rest,
|
|
1534
1711
|
version,
|
|
1535
1712
|
transmissionMode,
|
|
1536
1713
|
sid,
|
|
1714
|
+
sessionRetention: sessionRetention >= 0 ? sessionRetention : 2,
|
|
1537
1715
|
eventTargets: eventTargets.reduce((acc, target) => {
|
|
1538
1716
|
if (target?.url && target.events?.length) acc.push({
|
|
1539
1717
|
version: target.version || CMCD_V2,
|
|
@@ -1541,7 +1719,8 @@ function createCmcdReporterConfig(config) {
|
|
|
1541
1719
|
url: target.url,
|
|
1542
1720
|
events: target.events.slice(),
|
|
1543
1721
|
interval: target.interval ?? CMCD_DEFAULT_TIME_INTERVAL,
|
|
1544
|
-
batchSize: target.batchSize || 1
|
|
1722
|
+
batchSize: target.batchSize || 1,
|
|
1723
|
+
transform: target.transform
|
|
1545
1724
|
});
|
|
1546
1725
|
return acc;
|
|
1547
1726
|
}, [])
|
|
@@ -1550,6 +1729,14 @@ function createCmcdReporterConfig(config) {
|
|
|
1550
1729
|
/**
|
|
1551
1730
|
* The CMCD reporter.
|
|
1552
1731
|
*
|
|
1732
|
+
* `C` describes the player's own `customData`, which the reporter passes
|
|
1733
|
+
* through to each configured `transform`. It is inferred from the
|
|
1734
|
+
* configuration, so annotating a single `transform` types the request in every
|
|
1735
|
+
* other one; the default leaves `customData` values `unknown`.
|
|
1736
|
+
*
|
|
1737
|
+
* @typeParam C - The shape of the player's `customData`. Defaults to
|
|
1738
|
+
* `Record<string, unknown>`.
|
|
1739
|
+
*
|
|
1553
1740
|
* @see {@link https://cta-wave.github.io/Resources/common-media-client-data--cta-5004-b.html#reporting-modes-when-we-send-data | CTA-5004-B Reporting Modes}
|
|
1554
1741
|
*
|
|
1555
1742
|
* @public
|
|
@@ -1565,27 +1752,41 @@ var CmcdReporter = class {
|
|
|
1565
1752
|
*/
|
|
1566
1753
|
constructor(config, requester = defaultRequester) {
|
|
1567
1754
|
this.timeOrigin = performance.timeOrigin || performance.timing?.fetchStart || Date.now() - performance.now();
|
|
1568
|
-
this.
|
|
1569
|
-
this.
|
|
1570
|
-
this.
|
|
1571
|
-
this.lastEmitted = {};
|
|
1572
|
-
this.requestTarget = {
|
|
1573
|
-
sn: 0,
|
|
1574
|
-
msdSent: false
|
|
1575
|
-
};
|
|
1755
|
+
this.sessions = /* @__PURE__ */ new Map();
|
|
1756
|
+
this.intervals = /* @__PURE__ */ new Map();
|
|
1757
|
+
this.started = false;
|
|
1576
1758
|
this.config = createCmcdReporterConfig(config);
|
|
1577
|
-
this.
|
|
1759
|
+
this.session = this.createSession(this.config.sid, {
|
|
1578
1760
|
cid: this.config.cid,
|
|
1579
|
-
sid: this.config.sid,
|
|
1580
1761
|
v: this.config.version
|
|
1581
|
-
};
|
|
1762
|
+
});
|
|
1763
|
+
this.sessions.set(this.session.sid, this.session);
|
|
1582
1764
|
this.requester = requester;
|
|
1583
|
-
|
|
1584
|
-
|
|
1765
|
+
}
|
|
1766
|
+
/**
|
|
1767
|
+
* Creates the state for a new session: fresh counters, gates, queues and
|
|
1768
|
+
* dedup baseline for every configured target.
|
|
1769
|
+
*/
|
|
1770
|
+
createSession(sid, data) {
|
|
1771
|
+
const eventTargets = /* @__PURE__ */ new Map();
|
|
1772
|
+
for (const target of this.config.eventTargets) eventTargets.set(target, {
|
|
1585
1773
|
sn: 0,
|
|
1586
1774
|
msdSent: false,
|
|
1587
|
-
queue: []
|
|
1775
|
+
queue: [],
|
|
1776
|
+
disposed: false
|
|
1588
1777
|
});
|
|
1778
|
+
return {
|
|
1779
|
+
sid,
|
|
1780
|
+
provenance: mintProvenance(sid, data.cid),
|
|
1781
|
+
data,
|
|
1782
|
+
msd: NaN,
|
|
1783
|
+
lastEmitted: {},
|
|
1784
|
+
eventTargets,
|
|
1785
|
+
requestTarget: {
|
|
1786
|
+
sn: 0,
|
|
1787
|
+
msdSent: false
|
|
1788
|
+
}
|
|
1789
|
+
};
|
|
1589
1790
|
}
|
|
1590
1791
|
/**
|
|
1591
1792
|
* Starts the CMCD reporter. Called by the player when the reporter is enabled.
|
|
@@ -1595,16 +1796,37 @@ var CmcdReporter = class {
|
|
|
1595
1796
|
* populated before calling start().
|
|
1596
1797
|
*/
|
|
1597
1798
|
start() {
|
|
1598
|
-
this.
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1799
|
+
this.started = true;
|
|
1800
|
+
let failure;
|
|
1801
|
+
const session = this.session;
|
|
1802
|
+
session.eventTargets.forEach((target, config) => {
|
|
1803
|
+
this.disarmInterval(config);
|
|
1804
|
+
if (target.disposed || !this.armInterval(config)) return;
|
|
1805
|
+
try {
|
|
1806
|
+
this.recordTargetEvent(session, target, config, CMCD_EVENT_TIME_INTERVAL);
|
|
1603
1807
|
this.processEventTargets();
|
|
1604
|
-
}
|
|
1605
|
-
|
|
1606
|
-
|
|
1808
|
+
} catch (error) {
|
|
1809
|
+
failure ??= { error };
|
|
1810
|
+
}
|
|
1607
1811
|
});
|
|
1812
|
+
if (failure) throw failure.error;
|
|
1813
|
+
}
|
|
1814
|
+
/**
|
|
1815
|
+
* Arms the time-interval timer for an event target when its config calls
|
|
1816
|
+
* for one. Returns whether a timer was armed. The callback reports into
|
|
1817
|
+
* whichever session is current when it fires.
|
|
1818
|
+
*/
|
|
1819
|
+
armInterval(config) {
|
|
1820
|
+
if (config.interval === 0 || !config.events.includes(CMCD_EVENT_TIME_INTERVAL)) return false;
|
|
1821
|
+
this.intervals.set(config, setInterval(() => {
|
|
1822
|
+
const session = this.session;
|
|
1823
|
+
const target = session.eventTargets.get(config);
|
|
1824
|
+
if (target) {
|
|
1825
|
+
this.recordTargetEvent(session, target, config, CMCD_EVENT_TIME_INTERVAL);
|
|
1826
|
+
this.processEventTargets();
|
|
1827
|
+
}
|
|
1828
|
+
}, config.interval * 1e3));
|
|
1829
|
+
return true;
|
|
1608
1830
|
}
|
|
1609
1831
|
/**
|
|
1610
1832
|
* Stops the CMCD reporter. Called by the player when the reporter is disabled.
|
|
@@ -1612,10 +1834,9 @@ var CmcdReporter = class {
|
|
|
1612
1834
|
* @param flush - Whether to flush the event targets.
|
|
1613
1835
|
*/
|
|
1614
1836
|
stop(flush = false) {
|
|
1837
|
+
this.started = false;
|
|
1615
1838
|
if (flush) this.flush();
|
|
1616
|
-
this.eventTargets.
|
|
1617
|
-
this.disarmInterval(target);
|
|
1618
|
-
});
|
|
1839
|
+
for (const config of this.config.eventTargets) this.disarmInterval(config);
|
|
1619
1840
|
}
|
|
1620
1841
|
/**
|
|
1621
1842
|
* Forces the sending of all event reports, regardless of the batch size or interval.
|
|
@@ -1653,17 +1874,63 @@ var CmcdReporter = class {
|
|
|
1653
1874
|
*
|
|
1654
1875
|
* A `sid` change resets the dedup baseline.
|
|
1655
1876
|
*
|
|
1877
|
+
* `sid` and `msd` are session-owned: the reporter tracks them itself and
|
|
1878
|
+
* stamps them onto every outgoing report, so this method is the only way
|
|
1879
|
+
* to change them. A per-call value on {@link CmcdReporter.recordEvent},
|
|
1880
|
+
* {@link CmcdReporter.createRequestReport}, or
|
|
1881
|
+
* {@link CmcdReporter.recordResponseReceived} has no effect; response
|
|
1882
|
+
* attribution is by the provenance record alone.
|
|
1883
|
+
* `msd` must be a finite number of milliseconds between `0` and
|
|
1884
|
+
* `999_999_999_999_999` (the RFC 8941 integer maximum); it is rounded to
|
|
1885
|
+
* the nearest integer, and invalid values are ignored.
|
|
1886
|
+
*
|
|
1656
1887
|
* @param data - The data to update.
|
|
1657
1888
|
*/
|
|
1658
1889
|
update(data) {
|
|
1659
|
-
if (data.sid && data.sid !== this.
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1890
|
+
if (data.sid && data.sid !== this.session.sid) this.startSession(data.sid);
|
|
1891
|
+
const session = this.session;
|
|
1892
|
+
const { msd } = data;
|
|
1893
|
+
if (typeof msd === "number" && Number.isFinite(msd) && msd >= 0) {
|
|
1894
|
+
const rounded = Math.round(msd);
|
|
1895
|
+
if (rounded <= 999999999999999) session.msd = rounded;
|
|
1896
|
+
}
|
|
1897
|
+
session.data = {
|
|
1898
|
+
...session.data,
|
|
1663
1899
|
...data,
|
|
1900
|
+
sid: void 0,
|
|
1664
1901
|
msd: void 0
|
|
1665
1902
|
};
|
|
1666
|
-
|
|
1903
|
+
if (session.data.cid !== session.provenance.cid) session.provenance = mintProvenance(session.sid, session.data.cid);
|
|
1904
|
+
for (const entry of STATE_FIELDS) if (entry.field in data && !entry.equal(session.data[entry.field], session.lastEmitted[entry.field])) this.recordEvent(entry.event);
|
|
1905
|
+
}
|
|
1906
|
+
/**
|
|
1907
|
+
* Starts a new session: the current one is archived in place for stale
|
|
1908
|
+
* attribution, a fresh one takes over, ended sessions are drained, and
|
|
1909
|
+
* the oldest are evicted beyond the configured retention.
|
|
1910
|
+
*
|
|
1911
|
+
* The data store is carried over, and the archived snapshot is frozen:
|
|
1912
|
+
* the ended session's value graph is detached first, so nested values
|
|
1913
|
+
* the caller handed to `update()` can no longer mutate it, and the new
|
|
1914
|
+
* session starts from a shallow copy of the detached graph, so `cid`,
|
|
1915
|
+
* `br`, custom keys and the rest survive a `sid` change as they always
|
|
1916
|
+
* have. Sessions are keyed by `sid`, which CTA-5004-B expects to be
|
|
1917
|
+
* unique per playback session: reusing one replaces the retained
|
|
1918
|
+
* namesake, whose remaining state dies as if evicted and whose late
|
|
1919
|
+
* responses relabel onto the replacement.
|
|
1920
|
+
*/
|
|
1921
|
+
startSession(sid) {
|
|
1922
|
+
this.session.data = copyReportValues({ ...this.session.data });
|
|
1923
|
+
this.session = this.createSession(sid, { ...this.session.data });
|
|
1924
|
+
this.sessions.delete(sid);
|
|
1925
|
+
this.sessions.set(sid, this.session);
|
|
1926
|
+
this.processEventTargets();
|
|
1927
|
+
for (const key of this.sessions.keys()) {
|
|
1928
|
+
if (this.sessions.size <= this.config.sessionRetention + 1) break;
|
|
1929
|
+
this.sessions.delete(key);
|
|
1930
|
+
}
|
|
1931
|
+
if (this.started) {
|
|
1932
|
+
for (const config of this.config.eventTargets) if (!this.intervals.has(config)) this.armInterval(config);
|
|
1933
|
+
}
|
|
1667
1934
|
}
|
|
1668
1935
|
/**
|
|
1669
1936
|
* Records an event. Called by the player when an event occurs.
|
|
@@ -1698,23 +1965,49 @@ var CmcdReporter = class {
|
|
|
1698
1965
|
* @param data - Additional data to record with the event. This data
|
|
1699
1966
|
* only applies to this event report, except for the dedup
|
|
1700
1967
|
* field of a state-change event, which is also persisted
|
|
1701
|
-
* into the reporter's data store.
|
|
1968
|
+
* into the reporter's data store. Session-owned keys
|
|
1969
|
+
* (`sid`, `msd`) supplied here are ignored; the reporter
|
|
1970
|
+
* stamps its own values.
|
|
1702
1971
|
*/
|
|
1703
1972
|
recordEvent(type, data = {}) {
|
|
1973
|
+
this.emitEvent(this.session, type, data);
|
|
1974
|
+
}
|
|
1975
|
+
/**
|
|
1976
|
+
* Records an event across every configured target of a session, applying
|
|
1977
|
+
* state-change dedup once before per-target fan-out.
|
|
1978
|
+
*
|
|
1979
|
+
* @param session - The session the event belongs to. Everything the event
|
|
1980
|
+
* touches (data store, dedup baseline, counters, queues)
|
|
1981
|
+
* is that session's; only `recordResponseReceived()` ever
|
|
1982
|
+
* passes an archived one.
|
|
1983
|
+
* @param type - The type of event to record.
|
|
1984
|
+
* @param data - Additional data to record with the event.
|
|
1985
|
+
* @param request - The media request that triggered the event, when
|
|
1986
|
+
* one exists. Only `recordResponseReceived()`
|
|
1987
|
+
* populates this; it is threaded through to each
|
|
1988
|
+
* target's `transform`.
|
|
1989
|
+
*/
|
|
1990
|
+
emitEvent(session, type, data, request) {
|
|
1704
1991
|
const entry = STATE_FIELDS_BY_EVENT.get(type);
|
|
1705
1992
|
if (entry) {
|
|
1706
1993
|
const field = entry.field;
|
|
1707
1994
|
const incoming = data[field];
|
|
1708
|
-
if (incoming !== void 0) Object.assign(
|
|
1709
|
-
const current =
|
|
1995
|
+
if (incoming !== void 0) Object.assign(session.data, { [field]: incoming });
|
|
1996
|
+
const current = session.data[field];
|
|
1710
1997
|
if (current === void 0) return;
|
|
1711
|
-
if (entry.equal(current,
|
|
1712
|
-
Object.assign(
|
|
1998
|
+
if (entry.equal(current, session.lastEmitted[field])) return;
|
|
1999
|
+
Object.assign(session.lastEmitted, { [field]: entry.snapshot(current) });
|
|
1713
2000
|
}
|
|
1714
|
-
|
|
1715
|
-
|
|
2001
|
+
let failure;
|
|
2002
|
+
session.eventTargets.forEach((target, config) => {
|
|
2003
|
+
try {
|
|
2004
|
+
this.recordTargetEvent(session, target, config, type, data, request);
|
|
2005
|
+
} catch (error) {
|
|
2006
|
+
failure ??= { error };
|
|
2007
|
+
}
|
|
1716
2008
|
});
|
|
1717
2009
|
this.processEventTargets();
|
|
2010
|
+
if (failure) throw failure.error;
|
|
1718
2011
|
}
|
|
1719
2012
|
/**
|
|
1720
2013
|
* Records an event for a target. Called by the reporter when an event occurs.
|
|
@@ -1725,21 +2018,58 @@ var CmcdReporter = class {
|
|
|
1725
2018
|
* @param data - Additional data to record with the event. This data
|
|
1726
2019
|
* only applies to this event report. Persistent data should
|
|
1727
2020
|
* be updated using `update()`.
|
|
2021
|
+
* @param request - The media request that triggered the event, when
|
|
2022
|
+
* one exists. Passed to the target's `transform`.
|
|
1728
2023
|
*/
|
|
1729
|
-
recordTargetEvent(target, config, type, data = {}) {
|
|
1730
|
-
if (!config.events.includes(type)) return;
|
|
2024
|
+
recordTargetEvent(session, target, config, type, data = {}, request) {
|
|
2025
|
+
if (target.disposed || !config.events.includes(type)) return;
|
|
1731
2026
|
const item = {
|
|
1732
|
-
...
|
|
2027
|
+
...session.data,
|
|
1733
2028
|
...data,
|
|
2029
|
+
sid: session.sid,
|
|
1734
2030
|
e: type,
|
|
1735
|
-
ts: data.ts ?? Date.now()
|
|
1736
|
-
sn: target.sn++
|
|
2031
|
+
ts: data.ts ?? Date.now()
|
|
1737
2032
|
};
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
2033
|
+
const { transform } = config;
|
|
2034
|
+
if (!transform) {
|
|
2035
|
+
this.queueTargetEvent(session, target, config, item, type);
|
|
2036
|
+
return;
|
|
1741
2037
|
}
|
|
1742
|
-
|
|
2038
|
+
copyReportValues(item);
|
|
2039
|
+
const requiredKey = CMCD_REQUIRED_EVENT_KEYS.get(type);
|
|
2040
|
+
const requiredValue = requiredKey ? item[requiredKey] : void 0;
|
|
2041
|
+
const ts = item.ts;
|
|
2042
|
+
const report = transform(item, request);
|
|
2043
|
+
if (report == null) return;
|
|
2044
|
+
if (!isUsableRequiredValue(report.ts)) report.ts = ts;
|
|
2045
|
+
if (requiredKey && isUsableRequiredValue(requiredValue) && !isUsableRequiredValue(report[requiredKey])) Object.assign(report, { [requiredKey]: requiredValue });
|
|
2046
|
+
this.queueTargetEvent(session, target, config, report, type);
|
|
2047
|
+
}
|
|
2048
|
+
/**
|
|
2049
|
+
* Stamps the reporter-owned fields on a finished event report, encodes it,
|
|
2050
|
+
* and pushes the wire line to the target's queue.
|
|
2051
|
+
*
|
|
2052
|
+
* Called after any transform has run, so a transform cannot bypass the
|
|
2053
|
+
* target's `events` filter via `e`, break `sn` continuity, or substitute
|
|
2054
|
+
* the session identity carried by `sid` and `msd`. Encoding here means a
|
|
2055
|
+
* report that cannot serialize throws inside the recording call that
|
|
2056
|
+
* produced it, instead of rejecting the batch send and re-queueing
|
|
2057
|
+
* forever.
|
|
2058
|
+
*
|
|
2059
|
+
* @param target - The target to queue the report for.
|
|
2060
|
+
* @param config - The configuration for the target.
|
|
2061
|
+
* @param report - The finished report data.
|
|
2062
|
+
* @param type - The type of event being reported.
|
|
2063
|
+
*/
|
|
2064
|
+
queueTargetEvent(session, target, config, report, type) {
|
|
2065
|
+
report.e = type;
|
|
2066
|
+
report.sn = target.sn++;
|
|
2067
|
+
report.sid = session.sid;
|
|
2068
|
+
if (!isNaN(session.msd) && !target.msdSent && config.enabledKeys?.includes("msd")) {
|
|
2069
|
+
report.msd = session.msd;
|
|
2070
|
+
target.msdSent = true;
|
|
2071
|
+
} else delete report.msd;
|
|
2072
|
+
target.queue.push(encodeCmcd(report, createEncodingOptions(CMCD_EVENT_MODE, config)));
|
|
1743
2073
|
}
|
|
1744
2074
|
/**
|
|
1745
2075
|
* Records a response-received event. Called by the player when a media
|
|
@@ -1756,14 +2086,57 @@ var CmcdReporter = class {
|
|
|
1756
2086
|
* Additional keys like `ttfbb`, `cmsdd`, `cmsds`, and `smrt` can be
|
|
1757
2087
|
* supplied via the `data` parameter if the player has access to them.
|
|
1758
2088
|
*
|
|
2089
|
+
* The request's `customData` is generic, so a player can pass a request
|
|
2090
|
+
* carrying its own taxonomy (e.g. `{ requestType: 'segment' }`) without a
|
|
2091
|
+
* cast. The reporter reads only the provenance record that
|
|
2092
|
+
* {@link CmcdReporter.createRequestReport} writes there; every other key
|
|
2093
|
+
* is left untouched and stays visible to an event target's `transform`
|
|
2094
|
+
* via its `request` argument.
|
|
2095
|
+
*
|
|
2096
|
+
* A reporter given a concrete `C` requires the request to satisfy it, so a
|
|
2097
|
+
* request the configured transforms could not read is rejected here rather
|
|
2098
|
+
* than reaching them; see {@link CmcdReporterCustomData}.
|
|
2099
|
+
*
|
|
2100
|
+
* The event is attributed to the session that issued the request, and to
|
|
2101
|
+
* nothing else: the provenance record that
|
|
2102
|
+
* {@link CmcdReporter.createRequestReport} stored on the request's
|
|
2103
|
+
* `customData` (under {@link CMCD_REQUEST_PROVENANCE}) selects the
|
|
2104
|
+
* session by its `sid`, or the response is dropped rather than
|
|
2105
|
+
* relabeled. There is no other key: a record lost to a serialization
|
|
2106
|
+
* boundary (and not restored; see the bridge notes on
|
|
2107
|
+
* {@link CMCD_REQUEST_PROVENANCE}) and a session no longer retained
|
|
2108
|
+
* (see `CmcdReporterConfig.sessionRetention`) both drop, and a per-call
|
|
2109
|
+
* `data.sid` cannot substitute. The record is honored wherever its
|
|
2110
|
+
* `sid` resolves: a request decorated by another reporter configured
|
|
2111
|
+
* with the same session attributes here, as does a hand-built record.
|
|
2112
|
+
* A response that completes after a `sid` change reports under its own
|
|
2113
|
+
* retained session, with that session's data snapshot and sequence
|
|
2114
|
+
* numbers.
|
|
2115
|
+
*
|
|
2116
|
+
* Request-time report keys come from the record's encoded per-call
|
|
2117
|
+
* `data` snapshot, decoded fresh per response, and the record's `cid`
|
|
2118
|
+
* reports in place of the session's current one, so a response that
|
|
2119
|
+
* completes after a mid-session content change keeps the meaning it
|
|
2120
|
+
* had when its request was issued.
|
|
2121
|
+
*
|
|
2122
|
+
* @typeParam RD - The `customData` this request carries. Defaults to the
|
|
2123
|
+
* reporter's own `C`.
|
|
2124
|
+
*
|
|
1759
2125
|
* @param response - The HTTP response received.
|
|
1760
2126
|
* @param data - Additional CMCD data to include with the event.
|
|
1761
2127
|
* Values provided here override any auto-derived values.
|
|
2128
|
+
* Session-owned keys (`sid`, `msd`) supplied here are
|
|
2129
|
+
* ignored; attribution is by the provenance record alone.
|
|
1762
2130
|
*/
|
|
1763
2131
|
recordResponseReceived(response, data = {}) {
|
|
1764
2132
|
const { request } = response;
|
|
1765
2133
|
const url = data.url ?? request?.url;
|
|
1766
2134
|
if (!url) return;
|
|
2135
|
+
const provenance = request.customData?.[CMCD_REQUEST_PROVENANCE];
|
|
2136
|
+
const session = this.resolveSession(provenance);
|
|
2137
|
+
if (!session) return;
|
|
2138
|
+
const cmcd = decodeSnapshot(provenance);
|
|
2139
|
+
const { cid } = provenance;
|
|
1767
2140
|
const urlObj = new URL(url);
|
|
1768
2141
|
urlObj.searchParams.delete(CMCD_PARAM);
|
|
1769
2142
|
const derived = {
|
|
@@ -1778,12 +2151,26 @@ var CmcdReporter = class {
|
|
|
1778
2151
|
}
|
|
1779
2152
|
if (timing.duration != null) derived.ttlb = Math.round(timing.duration);
|
|
1780
2153
|
}
|
|
1781
|
-
|
|
1782
|
-
|
|
2154
|
+
this.emitEvent(session, CMCD_EVENT_RESPONSE_RECEIVED, {
|
|
2155
|
+
...typeof cid === "string" && cid ? { cid } : void 0,
|
|
1783
2156
|
...cmcd,
|
|
1784
2157
|
...derived,
|
|
1785
2158
|
...data
|
|
1786
|
-
});
|
|
2159
|
+
}, request);
|
|
2160
|
+
}
|
|
2161
|
+
/**
|
|
2162
|
+
* Resolves the session a response belongs to: the provenance record's
|
|
2163
|
+
* `sid` names one of this reporter's retained sessions, or the response
|
|
2164
|
+
* is dropped. There is no other key — a record that was lost, or that
|
|
2165
|
+
* names an evicted or never-seen `sid`, resolves nothing, and
|
|
2166
|
+
* attributing it anywhere else would relabel it. The `sid` is read
|
|
2167
|
+
* structurally, so a JSON-revived copy of a record attributes exactly,
|
|
2168
|
+
* and a hand-built record naming a retained session is honored.
|
|
2169
|
+
*/
|
|
2170
|
+
resolveSession(provenance) {
|
|
2171
|
+
if (provenance === null || typeof provenance !== "object") return;
|
|
2172
|
+
const { sid } = provenance;
|
|
2173
|
+
return typeof sid === "string" ? this.sessions.get(sid) : void 0;
|
|
1787
2174
|
}
|
|
1788
2175
|
/**
|
|
1789
2176
|
* Applies the CMCD request report data to the request. Called by the player
|
|
@@ -1806,9 +2193,45 @@ var CmcdReporter = class {
|
|
|
1806
2193
|
return !!this.config.enabledKeys?.length;
|
|
1807
2194
|
}
|
|
1808
2195
|
/**
|
|
2196
|
+
* The provenance record to stamp on a returned request: the session's
|
|
2197
|
+
* frozen base record, extended with the caller's per-call data encoded
|
|
2198
|
+
* as a CMCD string when there is any. Requests without per-call data
|
|
2199
|
+
* share the base record, so decoration stays a single property write.
|
|
2200
|
+
* The snapshot never contains reporter-stamped fields, and a value that
|
|
2201
|
+
* cannot encode contributes no snapshot rather than failing the
|
|
2202
|
+
* request; its response still attributes and reports derived keys over
|
|
2203
|
+
* session data, matching `decodeSnapshot()`'s tolerance on the read
|
|
2204
|
+
* side.
|
|
2205
|
+
*/
|
|
2206
|
+
createRequestProvenance(data, baseUrl) {
|
|
2207
|
+
const base = this.session.provenance;
|
|
2208
|
+
if (!data) return base;
|
|
2209
|
+
try {
|
|
2210
|
+
const encoded = encodeCmcd(data, {
|
|
2211
|
+
version: this.config.version,
|
|
2212
|
+
reportingMode: CMCD_REQUEST_MODE,
|
|
2213
|
+
baseUrl
|
|
2214
|
+
});
|
|
2215
|
+
return encoded ? Object.freeze({
|
|
2216
|
+
...base,
|
|
2217
|
+
data: encoded
|
|
2218
|
+
}) : base;
|
|
2219
|
+
} catch {
|
|
2220
|
+
return base;
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
/**
|
|
1809
2224
|
* Creates a new request with the CMCD request report data applied. Called by the player
|
|
1810
2225
|
* before sending the request.
|
|
1811
2226
|
*
|
|
2227
|
+
* A reporter given a concrete `C` requires the request's `customData` to
|
|
2228
|
+
* satisfy it, so a request the configured transform could not read is
|
|
2229
|
+
* rejected here rather than reaching it; see
|
|
2230
|
+
* {@link CmcdReporterCustomData}.
|
|
2231
|
+
*
|
|
2232
|
+
* @typeParam R - The request being decorated. Its `customData` must satisfy
|
|
2233
|
+
* the reporter's `C`.
|
|
2234
|
+
*
|
|
1812
2235
|
* @param request - The request to apply the CMCD request report to.
|
|
1813
2236
|
* @param data - The data to apply to the request. This data only
|
|
1814
2237
|
* applies to this request report. Persistent data
|
|
@@ -1822,27 +2245,38 @@ var CmcdReporter = class {
|
|
|
1822
2245
|
headers: { ...headers },
|
|
1823
2246
|
customData: {
|
|
1824
2247
|
...customData,
|
|
1825
|
-
cmcd: {}
|
|
2248
|
+
cmcd: {},
|
|
2249
|
+
[CMCD_REQUEST_PROVENANCE]: this.createRequestProvenance(data, request.url)
|
|
1826
2250
|
}
|
|
1827
2251
|
};
|
|
1828
2252
|
if (!this.config.enabledKeys?.length || !report.url) return report;
|
|
1829
|
-
const
|
|
1830
|
-
const
|
|
1831
|
-
...
|
|
2253
|
+
const session = this.session;
|
|
2254
|
+
const merged = {
|
|
2255
|
+
...session.data,
|
|
1832
2256
|
...data,
|
|
1833
|
-
|
|
2257
|
+
sid: session.sid
|
|
1834
2258
|
};
|
|
1835
|
-
const
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
2259
|
+
const { transform } = this.config;
|
|
2260
|
+
let cmcdData = merged;
|
|
2261
|
+
if (transform) {
|
|
2262
|
+
copyReportValues(merged);
|
|
2263
|
+
cmcdData = transform(merged, request);
|
|
2264
|
+
if (cmcdData == null) return report;
|
|
1839
2265
|
}
|
|
1840
|
-
|
|
2266
|
+
cmcdData.sn = session.requestTarget.sn++;
|
|
2267
|
+
cmcdData.sid = session.sid;
|
|
2268
|
+
const sendMsd = !isNaN(session.msd) && !session.requestTarget.msdSent;
|
|
2269
|
+
if (sendMsd) cmcdData.msd = session.msd;
|
|
2270
|
+
else delete cmcdData.msd;
|
|
2271
|
+
const url = new URL(report.url);
|
|
2272
|
+
const options = createEncodingOptions(CMCD_REQUEST_MODE, this.config, report.url);
|
|
2273
|
+
const cmcd = report.customData.cmcd = copyReportValues(prepareCmcdData(cmcdData, options));
|
|
2274
|
+
if (sendMsd && cmcd.msd !== void 0) session.requestTarget.msdSent = true;
|
|
2275
|
+
const encoded = encodePreparedCmcd(cmcd);
|
|
1841
2276
|
switch (this.config.transmissionMode) {
|
|
1842
2277
|
case CMCD_QUERY:
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
url.searchParams.set(CMCD_PARAM, param);
|
|
2278
|
+
if (encoded) {
|
|
2279
|
+
url.searchParams.set(CMCD_PARAM, encoded);
|
|
1846
2280
|
report.url = url.toString();
|
|
1847
2281
|
}
|
|
1848
2282
|
break;
|
|
@@ -1859,16 +2293,19 @@ var CmcdReporter = class {
|
|
|
1859
2293
|
*/
|
|
1860
2294
|
processEventTargets(flush = false) {
|
|
1861
2295
|
let reprocess = false;
|
|
1862
|
-
this.
|
|
1863
|
-
const
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
2296
|
+
this.sessions.forEach((session) => {
|
|
2297
|
+
const drain = flush || session !== this.session;
|
|
2298
|
+
session.eventTargets.forEach((target, config) => {
|
|
2299
|
+
const { queue } = target;
|
|
2300
|
+
if (target.disposed || !queue.length) return;
|
|
2301
|
+
if (queue.length < config.batchSize && !drain) return;
|
|
2302
|
+
const deleteCount = drain ? queue.length : config.batchSize;
|
|
2303
|
+
const events = queue.splice(0, deleteCount);
|
|
2304
|
+
this.sendEventReport(session, config, events).catch(() => {
|
|
2305
|
+
target.queue.unshift(...events);
|
|
2306
|
+
});
|
|
2307
|
+
reprocess ||= queue.length > 0;
|
|
1870
2308
|
});
|
|
1871
|
-
reprocess ||= queue.length > 0;
|
|
1872
2309
|
});
|
|
1873
2310
|
if (reprocess) this.processEventTargets();
|
|
1874
2311
|
}
|
|
@@ -1876,44 +2313,42 @@ var CmcdReporter = class {
|
|
|
1876
2313
|
* Sends an event report. Called by the reporter when a batch is ready to be sent.
|
|
1877
2314
|
*
|
|
1878
2315
|
* @param config - The target config to send the event report to.
|
|
1879
|
-
* @param data - The
|
|
2316
|
+
* @param data - The encoded report lines to send in the event report.
|
|
1880
2317
|
*/
|
|
1881
|
-
async sendEventReport(config, data) {
|
|
1882
|
-
const options = createEncodingOptions(CMCD_EVENT_MODE, config);
|
|
2318
|
+
async sendEventReport(session, config, data) {
|
|
1883
2319
|
const { status } = await this.requester({
|
|
1884
2320
|
url: config.url,
|
|
1885
2321
|
method: "POST",
|
|
1886
2322
|
headers: { "Content-Type": CMCD_MIME_TYPE },
|
|
1887
|
-
body: data.
|
|
2323
|
+
body: data.join("\n") + "\n"
|
|
1888
2324
|
});
|
|
1889
|
-
if (status === 410) this.disposeEventTarget(config);
|
|
2325
|
+
if (status === 410) this.disposeEventTarget(session, config);
|
|
1890
2326
|
else if (status === 429 || status > 499 && status < 600) throw new Error(`Event report failed with status ${status}`);
|
|
1891
2327
|
}
|
|
1892
2328
|
/**
|
|
1893
|
-
* Cancels the time-interval timer for an event target and clears the
|
|
1894
|
-
* Safe to call when no timer is armed (clearInterval(undefined)
|
|
1895
|
-
|
|
1896
|
-
disarmInterval(target) {
|
|
1897
|
-
clearInterval(target.intervalId);
|
|
1898
|
-
target.intervalId = void 0;
|
|
1899
|
-
}
|
|
1900
|
-
/**
|
|
1901
|
-
* Permanently removes an event target: cancels its timer and removes it from the
|
|
1902
|
-
* eventTargets map. Used when the collector signals the target is gone (HTTP 410).
|
|
2329
|
+
* Cancels the time-interval timer for an event target config and clears the
|
|
2330
|
+
* stored id. Safe to call when no timer is armed (clearInterval(undefined)
|
|
2331
|
+
* is a no-op).
|
|
1903
2332
|
*/
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
this.disarmInterval(target);
|
|
1908
|
-
this.eventTargets.delete(config);
|
|
2333
|
+
disarmInterval(config) {
|
|
2334
|
+
clearInterval(this.intervals.get(config));
|
|
2335
|
+
this.intervals.delete(config);
|
|
1909
2336
|
}
|
|
1910
2337
|
/**
|
|
1911
|
-
*
|
|
2338
|
+
* Silences an event target URL for the remainder of its session: drops the
|
|
2339
|
+
* queues and blocks further enqueues for every config in the session that
|
|
2340
|
+
* reports to the URL, per CTA-5004-B's per-target-URL suppression scope,
|
|
2341
|
+
* and cancels their timers when the session is the live one. Used when the
|
|
2342
|
+
* collector signals the target is gone (HTTP 410). A session started after
|
|
2343
|
+
* the disposal is unaffected, since it gets fresh target state.
|
|
1912
2344
|
*/
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
2345
|
+
disposeEventTarget(session, config) {
|
|
2346
|
+
session.eventTargets.forEach((target, sibling) => {
|
|
2347
|
+
if (sibling.url !== config.url || target.disposed) return;
|
|
2348
|
+
target.disposed = true;
|
|
2349
|
+
target.queue.length = 0;
|
|
2350
|
+
if (session === this.session) this.disarmInterval(sibling);
|
|
2351
|
+
});
|
|
1917
2352
|
}
|
|
1918
2353
|
};
|
|
1919
2354
|
|
|
@@ -1976,51 +2411,6 @@ const CmcdValidationSeverity = {
|
|
|
1976
2411
|
WARNING: CMCD_VALIDATION_SEVERITY_WARNING
|
|
1977
2412
|
};
|
|
1978
2413
|
|
|
1979
|
-
//#endregion
|
|
1980
|
-
//#region src/upConvertToV2.ts
|
|
1981
|
-
/**
|
|
1982
|
-
* Up-convert V1 CMCD data to V2 format.
|
|
1983
|
-
*
|
|
1984
|
-
* - Wraps plain scalar values in arrays for inner-list keys.
|
|
1985
|
-
* - Wraps `nor` string in an array.
|
|
1986
|
-
*
|
|
1987
|
-
* If the data is already V2 (has `v: 2`), it is returned unchanged.
|
|
1988
|
-
*
|
|
1989
|
-
* @internal
|
|
1990
|
-
*/
|
|
1991
|
-
function upConvertToV2(obj) {
|
|
1992
|
-
if (obj["v"] === CMCD_V2) return obj;
|
|
1993
|
-
const result = {};
|
|
1994
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
1995
|
-
if (value == null) {
|
|
1996
|
-
result[key] = value;
|
|
1997
|
-
continue;
|
|
1998
|
-
}
|
|
1999
|
-
if (CMCD_INNER_LIST_KEYS.has(key) && !Array.isArray(value)) result[key] = [value];
|
|
2000
|
-
else if (key === "nor" && typeof value === "string") result[key] = [value];
|
|
2001
|
-
else result[key] = value;
|
|
2002
|
-
}
|
|
2003
|
-
return result;
|
|
2004
|
-
}
|
|
2005
|
-
|
|
2006
|
-
//#endregion
|
|
2007
|
-
//#region src/decodeCmcd.ts
|
|
2008
|
-
function reduceValue(value) {
|
|
2009
|
-
if (Array.isArray(value)) return value.map(reduceValue);
|
|
2010
|
-
if (typeof value === "symbol") return symbolToStr(value);
|
|
2011
|
-
if (value instanceof SfItem && !value.params) return reduceValue(value.value);
|
|
2012
|
-
if (typeof value === "string") return value;
|
|
2013
|
-
return value;
|
|
2014
|
-
}
|
|
2015
|
-
function decodeCmcd(cmcd, options) {
|
|
2016
|
-
if (!cmcd) return {};
|
|
2017
|
-
const sfDict = decodeSfDict(cmcd);
|
|
2018
|
-
const result = {};
|
|
2019
|
-
for (const [key, item] of Object.entries(sfDict)) result[key] = reduceValue(item.value);
|
|
2020
|
-
if (options?.convertToLatest) return upConvertToV2(result);
|
|
2021
|
-
return result;
|
|
2022
|
-
}
|
|
2023
|
-
|
|
2024
2414
|
//#endregion
|
|
2025
2415
|
//#region src/ensureHeaders.ts
|
|
2026
2416
|
/**
|
|
@@ -2941,5 +3331,5 @@ function extractHeaderRecord(headers) {
|
|
|
2941
3331
|
}
|
|
2942
3332
|
|
|
2943
3333
|
//#endregion
|
|
2944
|
-
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 };
|
|
3334
|
+
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 };
|
|
2945
3335
|
//# sourceMappingURL=index.js.map
|