@svta/cml-cmcd changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased
Changed
- The
validateCmcdEventReportdoc now renders the example code block. The docs build no longer warns about the@exampletag. - The README now has a When to use section. The section names the tasks that the package covers and the entry point of each task.
- Every code example in the guides now typechecks. The
recordResponseReceivedexamples now includeencodedBodySize(#565).
2.8.3 - 2026-10-18
Changed
- Both overloads of
decodeCmcd,fromCmcdHeaders,fromCmcdQuery, andfromCmcdUrlnow have a doc comment. - The
CmcdTransformRequestdoc now links toDeepReadonlyin@svta/cml-utils. - Peer dependency ranges are now caret ranges, for example
^1.6.2instead of1.6.2. A later patch or minor release of a peer no longer requires a new release of this package.
2.8.2 - 2026-10-08
Fixed
dist/index.jsnow parses as ES2021. A bundler no longer needs to transpile the package for an ES2021 target- A webpack bundle with a bare import of the package no longer contains code from the package
2.8.1 - 2026-10-01
Fixed
- The
CMCDquery parameter uses the encoding of the CTA-5004-B query examples. Only letters, digits,-,.,_, and~stay unencoded. A space becomes%20, and(and)become%28and%29. This is a wire output change. It coverstoCmcdUrl,toCmcdQuery,appendCmcdQuery, andCmcdReporter. Before,CmcdReporterwrote a space as+, and the other functions sent(and)unencoded. A decoder that usesURLSearchParamsreads the old and the new output as the same value. If a test compares exact URLs, update its expected strings. An unpaired surrogate in a string becomes U+FFFD. Before, it madetoCmcdUrl,toCmcdQuery, andappendCmcdQuerythrowURIError CmcdReporteradds theCMCDquery parameter and makes no other change to the request URL. Before, it parsed and serialized the whole URL. The serialization changed other query parameters, such as~to%7Eandflagtoflag=. It also lowercased the host, removed a default port, and resolved./and../in the path. A CDN that checks a token against the raw query could reject such a request. Theurlkey of anrrevent is the request URL without theCMCDparameter, also with no other change.createRequestReportandrecordResponseReceivedaccept a relative URL. Before, they threwInvalid URLappendCmcdQueryadds the parameter before the fragment. Before, it added the parameter after the fragment, so the request carried no CMCD data. It reads only theCMCDparameter of the query. Before, it replaced the value of a parameter such asXCMCD, or text that starts withCMCD=in the path. It removes everyCMCDparameter after the first. If the data has no keys to send, it returns the URL unchanged. Before, it wrote aCMCDparameter with no valuefromCmcdUrldecodes a+as a space, asfromCmcdQuerydoes. CTA-5004-B refers to theapplication/x-www-form-urlencodedformat for theCMCDquery argument. In this format, a+is a space.CmcdReporter2.8.0 and earlier write a space as+. Before,fromCmcdUrlthrewfailed to parse ... as Dictfor an inner list with two or more items, such asnor=("a.m4s" "b.m4s"). It decoded the string"a b"asa+b. A%2Bstill decodes as a+
2.8.0 - 2026-10-01
Changed
- The encoder reads its key rules from one internal table. It no longer reads
CMCD_FORMATTER_MAP. To change a value, useCmcdEncodeOptions.formatters. A formatter runs at most once in a call, and only for a key that the encoder can send. Theotandeformatters also run when a rule needs their value. The encoder no longer calls avformatter.CmcdReporterapplies the same rules and has no option to change them.CMCD_FORMATTER_MAPhas an entry for each integer key and forpr.prepareCmcdDatareturns a symbol token as anSfToken. It returns anorvalue that is oneSfItemas a list. Both shapes give the same wire output as before
Fixed
- The encoder applies the value rules of CTA-5004 and CTA-5004-B. This is a wire output change. It covers
prepareCmcdData,encodeCmcd,toCmcdHeaders,toCmcdQuery, andCmcdReporter:- It rounds every integer key, also inside an inner list with parameters.
tblrounds to 100 ms, likebl. A fractionaltsand abl,br,mtp, ortblist with parameters no longer make encoding throw - It drops a value of the wrong type and a number that is not finite. In a list, it drops only that element. It also drops a string over the maximum length of its key. In version 2, a custom string has at most 64 characters. A number that is not finite in the data of
CmcdReportermade each later request report throw - It drops a plain object, a
Date, or a byte sequence in a custom key. A plain object made encoding throw. It also drops an emptynorentry - It omits a default value with parameters, such as
pr=1;x, as it omits the bare value - It reads the version from the value inside
v, and it sendsvwithout parameters - It ignores a
baseUrlthat is not a valid URL or that has an opaque origin. Before, it threwInvalid URL - In version 1, an inner list becomes one value. The encoder selects the item by its object type flag, such as
br=(5000;v 320;a). An item without parameters matches anyot. If no item matches, the encoder omits the key - In version 1,
norsends its first item. Therparameter of that item becomesnrr - In version 1,
cidhas at most 64 characters, and a custom string has no length limit.st=llbecomesst=l, andsf=ebecomessf=o - In version 1, event mode sends only version 1 keys and custom keys
- In version 2, one value on a list key is sent as a list.
br: 3000becomesbr=(3000) - In version 2,
baseUrlapplies to anorinner list with parameters. It also applies to anorvalue that is oneSfItem - An
hevent carriesh, also when the key filter removes it. In event mode, a report withoutedrops the response keys, such asttfbandurl - The event rules and the object type rules read
eandotafter their formatters. An event report keeps atswith parameters
- It rounds every integer key, also inside an inner list with parameters.
- The validators apply the rules of CTA-5004 and CTA-5004-B more closely. The changes cover
validateCmcd,validateCmcdRequest,validateCmcdHeaders,validateCmcdEvents,validateCmcdEventReport,validateCmcdStructure, andvalidateCmcdValues:- They accept a
bevent withoutbg, andpr,c, andbcevents without their state keys. Apsevent still requiressta. Its message now begins with "Play state change event" - A rounding rule stated with MUST gives an error, and a rule stated with SHOULD gives a warning. A payload that passed with a warning can now fail
- They check the rounding of each element of a version 2 inner list. They also check that
brandtbare integers - They accept a value with parameters, and they check the value inside its
SfItem
- They accept a
decodeCmcdwithconvertToLatestkeeps an inner list with parameters, such asbr=(3000 6000);p=2. Before, it put the list inside a second list, and the encoder dropped the key. A payload with parameters onv, such asv=2;x, no longer converts as version 1 data. The fix also applies tofromCmcdHeaders,fromCmcdQuery, andfromCmcdUrlCmcdReporter:recordResponseReceived()again reports a response without a provenance record, under the current session, as in version 2.4.0. Since version 2.6.0, hls.js and dash.js lost everyrrevent- The exit from backgrounded mode is a
bevent withoutbg. The reporter sentbg=?0. A receiver that checks for the key readbg=?0as an entry. This is a wire output change. If a receiver reads the exit frombg=?0, change it to accept abevent withoutbg. A target transform can addbg: falseto sendbg=?0.encodeCmcdstill writesbg: falseon abevent as?0 - A first
bg: falsein a session fires nobevent. A transform that setsbg: falseon an entry report no longer turns it into an exit update()accepts abrinner list with parameters. It threwv.slice is not a function. ABITRATE_CHANGEevent fires only when a bitrate or an object type flag changes
Documentation
- The user guide describes the value that
decodeCmcdreturns for a member with parameters. It also lists the values that the encoder drops and the version 1 limits. The TSDoc ofCmcdReporter.recordEventandCmcdReportConfig.enabledKeysno longer says that CTA-5004-B requires a key on every state-change event
2.7.0 - 2026-09-15
Changed
- TSDoc: the comment prose is rewritten for readers who do not read English as a first language. Signatures, examples, tags, and links are unchanged. Some comments had wrong facts. The
appendCmcdHeaderssummary said query args. TheisCmcdResponseReceivedKeyreturn description said request key. TheCmcdEventTypeconstants called their values keys. TheCmcdV1mtptitle had a stray key name. - The
Cmcdtype documentation links to the CTA-5004-B specification. It linked only to the version 1 PDF
Fixed
- A bare import of the package no longer runs code at module scope.
CmcdReporterbuilt its required-event-key table with an array spread ofCMCD_STATE_EVENT_FIELDS.prepareCmcdDatabuilt its key filter table with computed keys. Bundlers kept one or both tables in every bundle that imports the package, even when the bundle uses neither function validateCmcdRequestreports an error when a request carries CMCD data in both the headers and theCMCDquery parameter. CTA-5004-B allows one transmission mode per request. The validator still checks the headers and returns their datavalidateCmcdRequestreads theCMCDparameter from the query string and ignores the URL fragment. A relative URL no longer throws. When the fragment is the only place with aCMCDparameter, the error message says so- The encoder applies the "MUST NOT" rules of CTA-5004-B to version 2 payloads. The change covers
encodeCmcd,toCmcdHeaders,toCmcdQuery, andCmcdReporter, which shareprepareCmcdData dis sent only whenotisa,v,av,tt,c, oro.tpbis sent only whenotisa,v,av, orc. The check reads the formattedotfrom the data, even whenotis filtered out of the report. An absent or emptyotdoes not remove either key. CTA-5004 (version 1) has no such rule, so version 1 payloads are unchangedab,lab, andtabare omitted whenbr,lb, ortbis sent in the same payload. The check runs after formatting, so a formatter that empties the exact key keeps the aggregate keycenis omitted unless the event type isce- An empty value is omitted instead of formatted.
nullonbr,d,bl,dl,mtp,rtp, ortbwas sent as0.''onnorwas sent asnor=(""). A custom formatter is no longer called for an empty value - An empty array is omitted.
br: []was sent asbr=() CmcdReporterevent report bodies no longer end with a line feed. CTA-5004-B separates records with a single line feed and forbids a trailing onevalidateCmcdStructurereports an error whendortpbis present with anotthat the key does not allow. It also reports an error whenab,lab, ortabis present withbr,lb, ortb. The message names the keys and the allowed object types. The checks apply to version 2 payloads and run in every validatorvalidateCmcdEventsandvalidateCmcdEventReportreport an error when the body ends with a line feed. Every earlierCmcdReportersent that line feed. A receiver that upgrades marks reports from those players as invalidnoris the shortest relative path when the request URL andbaseUrlshare two or more leading path segments. AbaseUrldirectory such as/v/1080p/producednor=("../1080p/seg-2.m4s")for a segment in the same directory. The value is nownor=("seg-2.m4s"). A value that is empty, starts with/, or has:in its first segment gets a./prefix. This is a wire output change
2.6.1 - 2026-09-07
Fixed
encodeCmcdnow writes a token field as a bare token when its value is anSfItemthat wraps a string. The token fields areot,sf,st,e, andsta.decodeCmcdreturns that shape for a member with parameters, such asot=m;com.example-p=1. The encoder wrote the field as a quoted string, which is not valid CMCD (#419)encodeCmcdcompares theevalue by token text.decodeCmcdwithuseSymbolreturnseas aSymbolor anSfToken. Such an event report now keeps its response-received keys one=rr,bg=?0one=b, andpr=1one=pr. The version 1 down-conversion also matches theotparameter of an inner-list item by token text (#419)validateCmcdValuesaccepts a token field whose value is anSfItem, aSymbol, or anSfToken. Since 2.6.0,validateCmcdEventsand the other string validators rejected a valid member such asot=m;com.example-p=1. The message wasinvalid token value "[object Object]".validateCmcdStructureapplies the event rules (cen,url, response keys, state-change fields,ec) wheneis aSymbolor anSfToken(#419)
2.6.0 - 2026-09-03
Added
CmcdReporterConfig.sessionRetention— the number of ended sessions the reporter retains state for, in addition to the current one (default2;0disables retention,Infinitynever evicts; onlynumbervalues are accepted and floored, anything else falls back to the default). The reporter now keeps per-session state (data snapshot, sequence numbers,msdgate, dedup baseline, unsent queues) for recently ended sessions, so a media request that completes after asidchange reports under the session that issued it: its ownsid, its next per-target sequence number, its still-unsentmsd, and its frozen data snapshot, which is detached at the transition so mutating an array previously passed toupdate()cannot rewrite an ended session's late reports. Asidchange also drains an ended session's unsent event reports (each keeps its ownsidand sequence number) before eviction can discard them. Implements the accepted RFC inrfc/cmcd-session-retention.md(#416)CMCD_REQUEST_PROVENANCE— the symbol key (backed bySymbol.for('@svta/cml-cmcd/request-provenance')) under whichcreateRequestReport()stamps a frozen session-provenance record (CmcdRequestProvenance) on every request it returns, including requests it does not decorate (request reporting disabled, or a transform cancels decoration). The record carriessid, the issuing session's ID and the attribution key;cid, the content ID in effect when the request was issued; anddata, the request's per-call data encoded as a CMCD string, captured before the key filter and transform run so it rides undecorated requests too.recordResponseReceived()attributes by the record'ssid, and only by it: a response whose record does not name a retained session is dropped rather than relabeled, and a per-calldata.sidis not an attribution key. The record is honored wherever itssidresolves, so a hand-built record attributes, as does a split topology where one reporter decorates and another configured with the same session records. Session identity is thesiditself, which CTA-5004-B expects to be unique per playback session: reusing one replaces the retained namesake and relabels its late responses onto the replacement. TheRESPONSE_RECEIVEDevent is rebuilt from the record: the decoded per-call snapshot supplies the caller's request-time report keys (never re-ingesting the player-facing, player-mutablecustomData.cmcdobject, so token-typed values likeot,sf,st, and custom-keySfTokens keep their RFC 8941 wire type across serialization boundaries), and the record'scidreports in place of the session's current one, so a response that completes after a mid-session content change keeps the meaning it had when its request was issued. Spread andObject.assigncarry the record through request clones;JSON.stringifyand structured clone drop symbol keys, so read the value before such a boundary and restore it afterward — the record itself survives JSON and is read by value rather than object identity, so a revived copy behaves exactly like the original. The member is typed optional onCmcdRequestReportso the type stays constructible by consumers; every request the reporter returns carries it (#416)CmcdDecodeOptions.useSymbol— controls how RFC 8941 token values are represented bydecodeCmcd(andfromCmcdQuery/fromCmcdHeaders/fromCmcdUrl). When omitted, tokens reduce to plain strings as before, which cannot be told apart from string values on re-encoding.truedecodes tokens as registrySymbols,falseasSfTokeninstances; either preserved representation re-encodes as a bare token, making the codec symmetric:encodeCmcd(decodeCmcd(s, { useSymbol: false }))returns the input bytes.CmcdReporterdecodes provenance snapshots this way, so request-time token values keep their wire type onRESPONSE_RECEIVEDreports
Changed
- README: the
CmcdReportRecorderparagraph is rewritten for readers who do not read English as a first language. The code examples are unchanged. - User Guide, Report Recorder Guide, and Validation Guide: the prose is rewritten for readers who do not read English as a first language. The code examples and tables are unchanged.
- User Guide and Validation Guide: one heading, one table cell, and one code comment no longer use a contraction or an em dash.
Fixed
decodeCmcdno longer discards RFC 8941 parameters carried on a dictionary member or an inner list (com.example-x=1;p=2,tab=(3000);p=2): a params-bearing member now decodes as anSfItemwith its value reduced as usual, matching how inner-list members with params were already preserved. The value inside a params-bearing item is also reduced consistently now (previously an inner token leaked as a rawSymbolinstead of a string). AffectsdecodeCmcd,fromCmcdQuery,fromCmcdHeaders, andfromCmcdUrlCmcdReporterevent reports are encoded when they are queued rather than when a batch is sent. A report whose values cannot be serialized (RFC 8941 rejects them) now throws synchronously from the recording call that produced it, attributable to its caller, instead of rejecting the asynchronous batch send, where the failure was swallowed by the retry path and the un-encodable report re-queued forever, silently blocking every later report on batched targets. Queued reports are also finished wire bytes, immune to later mutation of caller-held values, so the per-target deep copy of report data is now made only where atransformis configuredCmcdReporternow stamps the session-owned keyssidandmsdafter per-call data and transforms in both reporting modes, so neitherrecordEvent()/recordResponseReceived()/createRequestReport()data nor atransformreturn value can substitute the session ID or putmsdon the wire outside the once-per-session gate. Transforms still see the canonicalsidin their input. This also makesupdate({ sid: undefined })a no-op; previously the explicitundefinedwas merged into the data store and every later report went out with nosidat all. OnrecordResponseReceived()per-call session keys have no effect either: attribution is by the request's provenance record alone (see Added) (#408)msdlifecycle fixes (#409):update({ msd })now accepts0(a valid instant-start value), rounds fractional values to integer milliseconds per CTA-5004-B, and rejectsInfinity, negatives,NaN, and values whose rounded form exceeds the RFC 8941 structured-field integer maximum of999_999_999_999_999. Previously0was rejected while fractions and negatives reached the wire as-is; an acceptedInfinityconsumed the once-per-session gate without ever being encodable, somsdwas silently never sent; and an over-range value consumed the gate and then threw in the serializer, poisoning event batches on every retry- A
sidchange now clears the storedmsdand re-arms every send gate:msdis once per Session ID, so a value supplied for the new session is sent again, and a stored-but-unsent value from the old session no longer leaks forward - The send gate is consumed only when the prepared report actually retains
msd. Previously both modes flipped the flag before theenabledKeysfilter ran, so a target that filteredmsdout still consumed its gate
- HTTP 410 disposal of an event target is now scoped per CTA-5004-B: suppression covers every event target configured with the matching URL, lasts for the remainder of the session that received the 410, and no longer outlives it. A
sidchange restores disposed targets (re-arming their intervals when the reporter is started), a 410 response that resolves after a session change silences the session that sent the batch instead of the one that replaced it, and a batch re-queued after a 429/5xx failure retries only within its own session, so a batch invalidated by a disposal in an ended session is never replayed into the new one (#410) - Corrected the
CmcdEncodeOptions.versiondocumentation, which still claimed a default of1. Encoding has defaulted toCMCD_V2since 2.0.0: whenversionis not set, the version is inferred from the data'svkey, falling back toCMCD_V2, which is whyencodeCmcd({ br: 1000 })emitsbr=1000,v=2. Documentation-only change; runtime behavior is unchanged
2.5.0 - 2026-07-28
Added
transformon the request-report config and on each event-target config: a synchronous(data, request) => Cmcd | nullhook that modifies a single CMCD report before it goes to the wire, or cancels it by returningnull. Placement scopes the hook the same wayenabledKeysdoes — the top-leveltransformapplies tocreateRequestReport()reports, and each event target'stransformapplies to that target's event reports, so targets sharing a collector URL can filter independently. ForRESPONSE_RECEIVEDevents the transform also receives the request that triggered them, which is how a player filters reports by its own request taxonomy oncustomData. Implements the accepted RFC inrfc/cmcd-reporter-middleware.md(#390)The contract is bounded so a transform cannot produce an invalid or cross-contaminated report:
e,sn, andmsdare stamped after the transform runs, so a cancelled report leaves no sequence-number gap andmsdrides the next report that is sent.- Keys the event requires (
tsalways, the signalled field for state-change events,cenfor custom events,ecfor errors,urlfor response-received) are restored if a transform removes them, so a transform cannot emit a reportvalidateCmcdEvents()would reject. Keys already absent beforehand are not fabricated. - The report data is copied for each target, nested values included, so mutating an array or an
SfItem'sparamsin place cannot reach the reporter's persistent data or another target's report. - Transform exceptions propagate rather than being swallowed, but are isolated per target: the remaining targets still receive the report and the error surfaces after the reporter finishes the event. This covers
start()'s initial time-interval event as well asrecordEvent(), so a throwing transform cannot leave later targets without armed intervals. See the user guide. - The
requestargument is a read-only view (CmcdTransformRequest): it is context for the decision and must not be mutated, since it belongs to the caller. Members arereadonly, andcustomDatavalues areunknownunless the reporter is given the player'scustomDatatype (see below). A mutableFormData/URLSearchParamsbody and JavaScript callers are outside what the type can enforce
CmcdReporterConfig.customHeaderMap— routes custom keys into specific CMCD header shards (CMCD-Session,CMCD-Object,CMCD-Status) when the transmission mode isHEADERS. Custom keys not listed in any shard still default toCMCD-Request; standard keys keep their spec-defined shards and cannot be re-routed. The option previously existed onCmcdEncodeOptionsbut was not reachable throughCmcdReporter
Fixed
prepareCmcdDatanow force-includesecon error events andurlon response-received events after the per-targetenabledKeysfilter, completing the set started in 2.4.0 for state-change fields andcen. A target whoseenabledKeysomittedecpreviously emittede=e,sid="…",sn=0,ts=…,v=2, whichvalidateCmcdEvents()rejects for the missing required key;rrtargets losturlthe same way. This affects any such target, with or without a report transform configured
Changed
CmcdTransformRequest,CmcdRequestReportTransform,CmcdEventReportTransform,CmcdRequestReportConfig,CmcdEventReportConfig,CmcdReporterConfig, andCmcdReporternow take a type parameter describing the player'scustomData, so a transform reads player fields with typed dot access instead of bracket access onunknown. The parameter is inferred from the configuration: annotating a single transform asCmcdEventReportTransform<PlayerData>types therequestargument in every other transform on the same reporter, andnew CmcdReporter<PlayerData>({ … })does the same explicitly. Every declaration defaults toRecord<string, unknown>, so existing code compiles unchanged and un-parameterized spellings such asCmcdRequestReportTransformkeep their currentunknown-valued behavior. This closes the asymmetry left by the genericrecordResponseReceived()below, where the call site preserved the player'scustomDatatype but the transform reading that same request did notcustomDatais readonly at every depth rather than only at the top level, so describing a nested shape does not trade the request's no-mutation guarantee for typed reads.Readonly<C>would have leftrequest.customData.nested.field = …compiling, which the opaque-record default had always rejectedcreateRequestReport()andrecordResponseReceived()require the request'scustomDatato satisfy the reporter's type, so a request the configured transforms could not read is rejected at the call site instead of reaching them and readingundefined. A reporter left on the default constrains nothing and accepts anycustomData, exactly as before; the rule is expressed by the newCmcdReporterCustomDatatypeCmcdReporterCustomData<C>— thecustomDataaCmcdReportermethod accepts for a reporter typedC. Exported because it appears in the signatures ofcreateRequestReport()andrecordResponseReceived()CmcdReporter.recordResponseReceived()is now generic over the request'scustomData, so a player can pass a request carrying only its own keys (e.g.{ requestType: 'segment' }) without declaring acmcdkey it does not own and without a cast. The parameter was previously pinned toHttpResponse<HttpRequest<{ cmcd?: Cmcd }>>; because{ cmcd?: Cmcd }is a weak type (every property optional), acustomDatasharing no properties with it was rejected outright. This pairs with the per-targettransform, which reads the player's taxonomy off the triggering request. Type-only widening: the reporter still reads justcustomData.cmcd, and existing callers passingHttpRequest<{ cmcd?: Cmcd }>continue to compileisCmcdCustomKeyand theCmcdCustomKeytype now only accept custom keys that survive RFC 8941 key serialization: a lowercase first letter, then characters froma-z 0-9 . -, with a hyphen that is neither the first nor the last character. Uppercase and digit-leading names were never serializable as CMCD; they are now rejected by the type, the validators, and key filtering instead of being dropped at encode preparation
Documentation
- The user guide now documents custom reverse-DNS keys end to end: naming rules (including the runtime constraints the
CmcdCustomKeytype cannot express), the explicitenabledKeysopt-in required in both request mode and per event target (no wildcard exists), value types and wire-format behavior (trueas a bare key,falsedropped), and the fixedCMCD-Requestheader placement in headers mode - The custom-event (
e=ce) documentation now shows a complete working configuration:CmcdEventType.CUSTOM_EVENTmust be listed in the target'seventsfor delivery,cenis force-included without anenabledKeysentry, and any accompanying payload remains subject to the target'senabledKeys. Also documents that response-received keys are stripped from non-rrevents - The user guide and validation guide custom-key sections now cross-link each other
2.4.1 - 2026-07-21
Fixed
- Module-scope key-table
Set/Mapinitializers (and theCmcdReporterstate-field tables) are now marked side-effect free so consumer bundlers can drop them when unused; a bare import of the package previously retained ~4.9 KB of eagerly-built tables (follow-up to the module-scope side-effect audit in #382) isCmcdCustomKeyno longer backtracks quadratically on hostile inputs (CodeQL polynomial ReDoS): the ambiguous regex was replaced with an unambiguous character-class check plus an interior-hyphen test; accepted inputs are unchanged (#388)
Documentation
- Clarify recommended event-firing patterns in
CmcdReporter: state-change events are fired viaupdate(), with snapshot context attached by combining the state field and continuous metrics in a single call.recordEvent()'sdataargument is documented as intended for non-state-change events (custom, error, ad-lifecycle, mute/unmute, expand/collapse, skip). CallingrecordEvent()for a state-change event afterupdate()has auto-fired it silently drops the second call's data; see the user guide for the recommended pattern.
2.4.0 - 2026-05-22
Added
CmcdEventType.PLAYBACK_RATE(token'pr') for playback-rate-change events, per CTA-5004-B.CmcdReportRecorder— test helper that records CMCD-bearing reports across XHR and fetch transports for assertion in e2e tests. IncludeswaitForReports,waitForManifest,waitForSegments, andwaitForEventswait primitives that accept aCmcdReportRecorderWaitOptionsobject ({ count?, timeout? },countdefaults to1, reject on timeout) plus awaitTimeoutattach option that sets the per-recorder default timeout, and event-target POST stubbing.CmcdRecordedRequestTypeconst-enum and supporting types (CmcdRecordedReport,CmcdRecordedReportMode,CmcdTransportAdapter,CmcdReportRecorderOptions,CmcdReportRecorderWaitOptions).createXhrTransportandcreateFetchTransportdefault adapter factories.
Changed
CmcdReporter.update()now auto-fires the corresponding state-change event (PLAY_STATE,PLAYBACK_RATE,CONTENT_ID,BACKGROUNDED_MODE,BITRATE_CHANGE) when a tracked field's value changes. A subsequentrecordEvent()call for the same state-change event is deduplicated, which silently drops any enrichment data passed to it.CmcdReporter.recordEvent()with a state-change event now persists the dedup field from itsdataargument into the persistent data store (write-through), keepingthis.dataconsistent with the most recently reported value.- Consecutive state-change events with the same effective field value are now suppressed, matching CTA-5004-B's definition of these events as state transitions.
Fixed
prepareCmcdDatanow force-includes the required field (sta,pr,cid,bg,br) for state-change events (ps,pr,c,b,bc) after the per-targetenabledKeysfilter, preventing CTA-5004-B-non-compliant payloads likee=pswith nosta(#368).prepareCmcdDatapreservespr=1when encoding ane=prstate-change event in event mode. The v1 default-omission forpr=1still applies in request mode and in non-prevent-mode payloads.prepareCmcdDatanow emits an explicit?0token forbg: falseon a backgrounded-mode (e=b) state-change event in event mode. Previously the field was stripped by theisValidfilter, producing a CTA-5004-B-non-compliant payload on backgrounded-mode exit. The carve-out is scoped tightly to this case (bgis the only state-change required field typed as boolean);''/nullish/NaNandfalseon other state-change required fields remain stripped, and request-mode default-omission is unchanged (#372).validateCmcdStructurenow reports missing required fields for all five state-change events; previously onlye=pswas checked forsta.
2.3.2 - 2026-05-13
Fixed
- Fix
norformatter crash (TypeError: Invalid URL) when the value is already a relative path andbaseUrlis configured (#364)
Changed
CmcdReporternow uses the current request URL's directory (viagetBaseUrl) as the base fornorrelative-path conversion, producing sibling-relative paths per the CMCD specification (previously the origin was used, yielding root-relative paths)- Clarify TSDoc for
norandCmcdEncodeOptions.baseUrl/CmcdFormatterOptions.baseUrlto document the absolute-URL convenience conversion
2.3.1 - 2026-04-30
Fixed
- Fix memory leak in
CmcdReporterfrom orphanedsetIntervalafter HTTP 410 response (#360) - Fix
CmcdReporter.start()not being idempotent — repeated calls leaked the previous time-interval timer
2.3.0 - 2026-03-12
Added
- Add
CMCD_MIME_TYPEconstant for the CMCD event report MIME type - Add
validateCmcdEventReport(request, options?)to validate full HTTP requests as event-mode payloads - Add
@seelinks to JSDoc comments referencing CTA-5004-B spec sections
Changed
- Update event report MIME type from
text/cmcdtoapplication/cmcdper CTA-5004-B spec (#332)
2.2.0 - 2026-02-18
Added
- Add
validateCmcd(data, options?)orchestrator that runs key, value, and structure validation - Add
validateCmcdKeys(data, options?)to check keys against recognized v1/v2 spec keys - Add
validateCmcdValues(data, options?)to validate value types, constraints, and rounding rules - Add
validateCmcdStructure(data, options?)to validate structural rules (event mode, version key, response-received keys) - Add
validateCmcdHeaders(headers)to validate CMCD HTTP headers including shard placement - Add
validateCmcdEvents(cmcd)to validate multi-line event-mode payloads - Add
validateCmcdRequest(request)to validate CMCD data from aRequestorHttpRequest - Add
CmcdValidationResult,CmcdDataValidationResult,CmcdEventsValidationResult,CmcdValidationIssue,CmcdValidationOptions, andCmcdValidationSeveritytypes - Add
CmcdData,CmcdV1Data, andCmcdV2Datadiscriminated union types usingvas the discriminator property - Add
isCmcdV1Data(data)type guard to narrowCmcdDatatoCmcdV1Data - Add
isCmcdV2Data(data)type guard to narrowCmcdDatatoCmcdV2Data - Add
CmcdDecodeOptionstype withconvertToLatestoption to up-convert v1 data to v2 format during decoding - Add optional
optionsparameter todecodeCmcd,fromCmcdHeaders,fromCmcdQuery, andfromCmcdUrl
Changed
- Change
decodeCmcd,fromCmcdHeaders,fromCmcdQuery, andfromCmcdUrlreturn type fromCmcdtoCmcdData
Fixed
- Fix
CMCD_EVENT_BITRATE_CHANGEvalue from'br'to'bc'
2.1.2 - 2026-02-11
Changed
- Improve TSDoc for
CmcdReportermethods - Update
@svta/cml-utilsto 1.4.0
2.1.1 - 2026-02-09
Fixed
- Fix
CmcdReporter.start()dispatching time interval events to all targets instead of only the target with the event enabled
2.1.0 - 2026-02-06
Added
- Add
CmcdReporter.recordResponseReceived(response, data?)method for automatedrrevent reporting - Add
CmcdReporter.createRequestReport(request, data?)method with generic type support and per-request CMCD data - Add
CmcdReporter.isRequestReportingEnabled()method to check if request reporting is enabled - Add
CmcdRequestReporttype
Changed
CmcdReporter.recordEventdata parameter is now scoped to the event instead of being persisted viaupdate()CmcdReporter.recordEventnow respects caller-suppliedtsvalues instead of always overriding withDate.now()CmcdReporter.stopnow accepts an optionalflushparameter
Deprecated
CmcdReporter.applyRequestReportin favor ofCmcdReporter.createRequestReport
2.0.1 - 2026-02-04
Changed
- Update
@svta/cml-structured-field-valuesto 1.1.1 - Update
@svta/cml-utilsto 1.3.0 - Update
CmcdReporterto useHttpRequestfrom@svta/cml-utils(renamed fromRequest)
Fixed
- Missing
CmcdVersionandCMCD_HEADER_MAPexports
2.0.0 - 2026-02-03
Added
- CMCD Version 2 support (#143)
1.0.2 - 2025-12-26
Changed
- Update
@svta/cml-utilsto 1.1.0 (#279)
1.0.1 - 2025-12-22
Changed
- Removed
response modefrom CMCD encoding - Added
response receivedevent and filtering
Fixed
- Use fixed version numbers for all CML peer dependencies (#277)