From 59ede999a2c94c46136abbd6197b3fbc9fd0a5d0 Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Wed, 13 Aug 2025 23:21:48 -0700 Subject: [PATCH 01/64] Disable part loading for subtitle playlists (not supported in subtitle-stream-controller, timeline-contoller, fragment-tracker...) Stop gap measure for #7460 --- src/controller/base-stream-controller.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/controller/base-stream-controller.ts b/src/controller/base-stream-controller.ts index fd902ada72c..ee6bb650ded 100644 --- a/src/controller/base-stream-controller.ts +++ b/src/controller/base-stream-controller.ts @@ -876,7 +876,7 @@ export default class BaseStreamController if (this.loadingParts && isMediaFragment(frag)) { const partList = details.partList; if (partList && progressCallback) { - if (targetBufferTime > frag.end && details.fragmentHint) { + if (targetBufferTime > details.fragmentEnd && details.fragmentHint) { frag = details.fragmentHint; } const partIndex = this.getNextPart(partList, frag, targetBufferTime); @@ -1106,6 +1106,10 @@ export default class BaseStreamController // Buffer must be ahead of first part + duration of parts after last segment // and playback must be at or past segment adjacent to part list const firstPart = details.partList[0]; + // Loading of VTT subtitle parts is not implemented in subtitle-stream-controller (#7460) + if (firstPart.fragment.type === PlaylistLevelType.SUBTITLE) { + return false; + } const safePartStart = firstPart.end + (details.fragmentHint?.duration || 0); if (bufferEnd >= safePartStart) { From e3c4f1ddc020bf8934224f6464ca25c0003d3aad Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Wed, 13 Aug 2025 09:59:48 -0700 Subject: [PATCH 02/64] Merge new Date Ranges in Delta Update when last details have none Fixes #7461 --- src/loader/m3u8-parser.ts | 11 +- src/utils/level-helper.ts | 38 +++-- tests/unit/controller/level-helper.ts | 219 ++++++++++++++++++++++++++ 3 files changed, 246 insertions(+), 22 deletions(-) diff --git a/src/loader/m3u8-parser.ts b/src/loader/m3u8-parser.ts index 942e4b6ef28..a7765fdfc76 100644 --- a/src/loader/m3u8-parser.ts +++ b/src/loader/m3u8-parser.ts @@ -758,9 +758,16 @@ export function mapDateRanges( details: LevelDetails, ) { // Make sure DateRanges are mapped to a ProgramDateTime tag that applies a date to a segment that overlaps with its start date - const programDateTimeCount = programDateTimes.length; + let programDateTimeCount = programDateTimes.length; if (!programDateTimeCount) { - return; + if (details.hasProgramDateTime) { + const lastFragment = details.fragments[details.fragments.length - 1]; + programDateTimes.push(lastFragment); + programDateTimeCount++; + } else { + // no segments with EXT-X-PROGRAM-DATE-TIME references in playlist history + return; + } } const lastProgramDateTime = programDateTimes[programDateTimeCount - 1]; const playlistEnd = details.live ? Infinity : details.totalduration; diff --git a/src/utils/level-helper.ts b/src/utils/level-helper.ts index 7ceff215f30..0d68170d868 100644 --- a/src/utils/level-helper.ts +++ b/src/utils/level-helper.ts @@ -352,27 +352,25 @@ function mergeDateRanges( } const mergeIds = Object.keys(dateRanges); const mergeCount = mergeIds.length; - if (mergeCount) { - Object.keys(deltaDateRanges).forEach((id) => { - const mergedDateRange = dateRanges[id]; - const dateRange = new DateRange( - deltaDateRanges[id]!.attr, - mergedDateRange, - ); - if (dateRange.isValid) { - dateRanges[id] = dateRange; - if (!mergedDateRange) { - dateRange.tagOrder += mergeCount; - } - } else { - logger.warn( - `Ignoring invalid Playlist Delta Update DATERANGE tag: "${stringify( - deltaDateRanges[id]!.attr, - )}"`, - ); - } - }); + if (!mergeCount) { + return deltaDateRanges; } + Object.keys(deltaDateRanges).forEach((id) => { + const mergedDateRange = dateRanges[id]; + const dateRange = new DateRange(deltaDateRanges[id]!.attr, mergedDateRange); + if (dateRange.isValid) { + dateRanges[id] = dateRange; + if (!mergedDateRange) { + dateRange.tagOrder += mergeCount; + } + } else { + logger.warn( + `Ignoring invalid Playlist Delta Update DATERANGE tag: "${stringify( + deltaDateRanges[id]!.attr, + )}"`, + ); + } + }); return dateRanges; } diff --git a/tests/unit/controller/level-helper.ts b/tests/unit/controller/level-helper.ts index 166d9a65006..5889314b97c 100644 --- a/tests/unit/controller/level-helper.ts +++ b/tests/unit/controller/level-helper.ts @@ -1789,6 +1789,225 @@ video_5432.m4s`; sn: 6, }); }); + + it('merges new dateranges in delta updates with previous details containing no dateranges', function () { + const playlist1 = `#EXTM3U +#EXT-X-VERSION:6 +#EXT-X-MEDIA-SEQUENCE:1 +#EXT-X-TARGETDURATION:6 +#EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=9,CAN-SKIP-DATERANGES=YES +#EXT-X-MAP:URI="hls/20821722-video=2499968.m4s" +#EXT-X-PROGRAM-DATE-TIME:2025-08-06T16:00:00Z +#EXTINF:4.0, no desc +1.m4s +#EXTINF:4.0, no desc +2.m4s +#EXT-X-PROGRAM-DATE-TIME:2025-08-06T16:08:00Z +#EXTINF:4.0, no desc +3.m4s +#EXTINF:4.0, no desc +4.m4s +#EXTINF:4.0, no desc +5.m4s +#EXT-X-PROGRAM-DATE-TIME:2025-08-06T16:00:20Z +#EXTINF:4.0, no desc +6.m4s +#EXTINF:4.0, no desc +7.m4s +#EXTINF:4.0, no desc +8.m4s +#EXTINF:4.0, no desc +9.m4s`; + // Media sequence increased by one but two segments removed. + const playlist2 = `#EXTM3U +#EXT-X-VERSION:10 +#EXT-X-MEDIA-SEQUENCE:5 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-TARGETDURATION:6 +#EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=9,CAN-SKIP-DATERANGES=YES +#EXT-X-SKIP:SKIPPED-SEGMENTS=4,RECENTLY-REMOVED-DATERANGES="D0" +#EXTINF:4, no desc +9.m4s +#EXTINF:4, no desc +10.m4s +#EXT-X-DATERANGE:ID="D3",START-DATE="2025-08-06T16:00:59.100Z",DURATION=12,SCTE35-CMD=0x00000000`; + + const details1 = parseLevelPlaylist(playlist1); + const details2 = parseLevelPlaylist(playlist2); + + expect(details1.playlistParsingError).to.be.null; + expect(details2.playlistParsingError).to.be.null; + + // First playilst details + expect(details1).to.include({ + startSN: 1, + endSN: 9, + totalduration: 36, + dateRangeTagCount: 0, + }); + expect(details1.dateRanges).to.be.empty; + + // Merged delta playlist + mergeDetails(details1, details2, logger); + + expect(details2).to.include({ + startSN: 5, + endSN: 10, + totalduration: 24, + dateRangeTagCount: 1, + }); + expect(details2.dateRanges).to.have.keys(['D3']); + expect(details2.dateRanges.D3).to.include({ + startTime: 59.1, + tagOrder: 0, + }); + expect(details2.dateRanges.D3?.tagAnchor, 'D3?.tagAnchor').to.include({ + sn: 6, + }); + }); + + it('adds and removed dateranges in delta updates with previous details when all previous dateranges are removed', function () { + const playlist1 = `#EXTM3U +#EXT-X-VERSION:6 +#EXT-X-MEDIA-SEQUENCE:1 +#EXT-X-TARGETDURATION:6 +#EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=9,CAN-SKIP-DATERANGES=YES +#EXT-X-MAP:URI="hls/20821722-video=2499968.m4s" +#EXT-X-PROGRAM-DATE-TIME:2025-08-06T16:00:00Z +#EXTINF:4.0, no desc +1.m4s +#EXTINF:4.0, no desc +2.m4s +#EXTINF:4.0, no desc +3.m4s +#EXTINF:4.0, no desc +4.m4s +#EXTINF:4.0, no desc +5.m4s +#EXTINF:4.0, no desc +6.m4s +#EXTINF:4.0, no desc +7.m4s +#EXTINF:4.0, no desc +8.m4s +#EXTINF:4.0, no desc +9.m4s +#EXT-X-DATERANGE:ID="D2",START-DATE="2025-08-06T16:00:15Z",PLANNED-DURATION=12,SCTE35-OUT=0x00000000`; + // Media sequence increased by one but two segments removed. + const playlist2 = `#EXTM3U +#EXT-X-VERSION:10 +#EXT-X-MEDIA-SEQUENCE:5 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-TARGETDURATION:6 +#EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=9,CAN-SKIP-DATERANGES=YES +#EXT-X-SKIP:SKIPPED-SEGMENTS=4,RECENTLY-REMOVED-DATERANGES="D2" +#EXTINF:4, no desc +9.m4s +#EXTINF:4, no desc +10.m4s +#EXT-X-DATERANGE:ID="D3",START-DATE="2025-08-06T16:00:59.100Z",DURATION=12,SCTE35-CMD=0x00000000`; + + const playlist3 = `#EXTM3U +#EXT-X-VERSION:10 +#EXT-X-MEDIA-SEQUENCE:6 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-TARGETDURATION:6 +#EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=9,CAN-SKIP-DATERANGES=YES +#EXT-X-SKIP:SKIPPED-SEGMENTS=4,RECENTLY-REMOVED-DATERANGES="D2" +#EXTINF:4, no desc +10.m4s +#EXTINF:4, no desc +11.m4s +#EXT-X-DATERANGE:ID="D3",START-DATE="2025-08-06T16:00:59.100Z",DURATION=12,SCTE35-CMD=0x00000000`; + + const playlist4 = `#EXTM3U +#EXT-X-VERSION:10 +#EXT-X-MEDIA-SEQUENCE:7 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-TARGETDURATION:6 +#EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=9,CAN-SKIP-DATERANGES=YES +#EXT-X-SKIP:SKIPPED-SEGMENTS=4,RECENTLY-REMOVED-DATERANGES="D2 D3" +#EXTINF:4, no desc +11.m4s +#EXTINF:4, no desc +12.m4s +#EXT-X-DATERANGE:ID="D4",START-DATE="2025-08-06T16:01:09.100Z",DURATION=12,SCTE35-CMD=0x00000000`; + + const details1 = parseLevelPlaylist(playlist1); + const details2 = parseLevelPlaylist(playlist2); + const details3 = parseLevelPlaylist(playlist3); + const details4 = parseLevelPlaylist(playlist4); + + expect(details1.playlistParsingError).to.be.null; + expect(details2.playlistParsingError).to.be.null; + expect(details3.playlistParsingError).to.be.null; + expect(details4.playlistParsingError).to.be.null; + + // First playilst details + expect(details1).to.include({ + startSN: 1, + endSN: 9, + totalduration: 36, + dateRangeTagCount: 1, + }); + expect(details1.dateRanges).to.have.keys(['D2']); + + // Merge delta playlist + mergeDetails(details1, details2, logger); + + expect(details2).to.include({ + startSN: 5, + endSN: 10, + totalduration: 24, + dateRangeTagCount: 1, + }); + expect(details2.dateRanges).to.have.keys(['D3']); + expect(details2.dateRanges.D3).to.include({ + startTime: 59.1, + tagOrder: 0, + }); + expect(details2.dateRanges.D3?.tagAnchor, 'D3?.tagAnchor').to.include({ + sn: 1, + }); + + // Merge next delta playlist + mergeDetails(details2, details3, logger); + + expect(details3).to.include({ + startSN: 6, + endSN: 11, + totalduration: 24, + dateRangeTagCount: 1, + }); + expect(details3.dateRanges).to.have.keys(['D3']); + expect(details3.dateRanges.D3).to.include({ + startTime: 59.1, + tagOrder: 0, + }); + // `tagAnchor` moved to last segment when no segments with `rawProgramDateTime` (#EXT-X-PROGRAM-DATE-TIME) remain + expect(details3.dateRanges.D3?.tagAnchor, 'D3?.tagAnchor').to.include({ + sn: 11, + }); + + // Merge next delta playlist + mergeDetails(details3, details4, logger); + + expect(details4).to.include({ + startSN: 7, + endSN: 12, + totalduration: 24, + dateRangeTagCount: 1, + }); + expect(details4.dateRanges).to.have.keys(['D4']); + // `tagAnchor` not inherited in new daterange when no segments with `rawProgramDateTime` (#EXT-X-PROGRAM-DATE-TIME) remain + expect(details4.dateRanges.D4?.tagAnchor, 'D4?.tagAnchor').to.include({ + sn: 12, + }); + expect(details4.dateRanges.D4).to.include({ + startTime: 69.1, + tagOrder: 0, + }); + }); }); function parseLevelPlaylist( From b829dd3b004841b827ecdb5973c73770d5bba854 Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Thu, 14 Aug 2025 08:59:32 -0700 Subject: [PATCH 03/64] Do not treat HTTP status 0 frag load errors as gaps Fixes #7410 --- src/controller/base-stream-controller.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/controller/base-stream-controller.ts b/src/controller/base-stream-controller.ts index ee6bb650ded..04e1c2973cf 100644 --- a/src/controller/base-stream-controller.ts +++ b/src/controller/base-stream-controller.ts @@ -1886,7 +1886,14 @@ export default class BaseStreamController couldRetry && !errorAction.resolved && flags === ErrorActionFlags.MoveAllAlternatesMatchingHost; - if (!retry && noAlternate && isMediaFragment(frag) && !frag.endList) { + const httpStatus = data.response?.code || 0; + if ( + !retry && + noAlternate && + isMediaFragment(frag) && + !frag.endList && + httpStatus !== 0 + ) { this.resetFragmentErrors(filterType); this.treatAsGap(frag); errorAction.resolved = true; From 5ea8f781bef578a697a3548a1ab953f38b06cc9d Mon Sep 17 00:00:00 2001 From: Matt Joiner Date: Fri, 15 Aug 2025 03:00:37 +1000 Subject: [PATCH 04/64] Minor improvements to docs (#7459) --- docs/API.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/API.md b/docs/API.md index 9bb2807e443..0f6e47a0963 100644 --- a/docs/API.md +++ b/docs/API.md @@ -756,10 +756,10 @@ Decreasing this value will mean that each stall will have less affect on `hls.ta (default: `Infinity`) -maximum delay allowed from edge of live, expressed in multiple of `EXT-X-TARGETDURATION`. -if set to 10, the player will seek back to `liveSyncDurationCount` whenever the next fragment to be loaded is older than N-10, N being the last fragment of the live playlist. -If set, this value must be stricly superior to `liveSyncDurationCount` -a value too close from `liveSyncDurationCount` is likely to cause playback stalls. +Maximum delay allowed from edge of live, expressed in multiple of `EXT-X-TARGETDURATION`. +If set to 10, the player will seek back to `liveSyncDurationCount` whenever the next fragment to be loaded is older than N-10, N being the last fragment of the live playlist. +If set, this value must be strictly superior to `liveSyncDurationCount`. +A value too close from `liveSyncDurationCount` is likely to cause playback stalls. ### `liveSyncDuration` @@ -776,7 +776,7 @@ A value too low (inferior to ~3 segment durations) is likely to cause playback s Alternative parameter to `liveMaxLatencyDurationCount`, expressed in seconds vs number of segments. If defined in the configuration object, `liveMaxLatencyDuration` will take precedence over the default `liveMaxLatencyDurationCount`. -If set, this value must be stricly superior to `liveSyncDuration` which must be defined as well. +If set, this value must be strictly superior to `liveSyncDuration` which must be defined as well. You can't define this parameter and either `liveSyncDurationCount` or `liveMaxLatencyDurationCount` in your configuration object at the same time. A value too close from `liveSyncDuration` is likely to cause playback stalls. From b21b6de9f67b02ded0e6dcb6330785ad168bc8e0 Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Thu, 14 Aug 2025 11:39:01 -0700 Subject: [PATCH 05/64] Allow base and query URI differences in segment mismatch error check (#7465) --- src/utils/level-helper.ts | 18 ++++++++++++- tests/unit/controller/level-helper.ts | 37 +++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/utils/level-helper.ts b/src/utils/level-helper.ts index 0d68170d868..467dac78a7a 100644 --- a/src/utils/level-helper.ts +++ b/src/utils/level-helper.ts @@ -429,7 +429,9 @@ export function mapFragmentIntersection( } if ((oldFrag as any) && (newFrag as any)) { intersectionFn(oldFrag, newFrag, i, newFrags); - if (oldFrag.url && oldFrag.url !== newFrag.url) { + const uriBefore = oldFrag.relurl; + const uriAfter = newFrag.relurl; + if (uriBefore && notEqualAfterStrippingQueries(uriBefore, uriAfter)) { newDetails.playlistParsingError = getSequenceError( `media sequence mismatch ${newFrag.sn}:`, oldDetails, @@ -600,3 +602,17 @@ export function reassignFragmentLevelIndexes(levels: Level[]) { }); }); } + +function notEqualAfterStrippingQueries( + uriBefore: string, + uriAfter: string | undefined, +): boolean { + if (uriBefore !== uriAfter && uriAfter) { + return stripQuery(uriBefore) !== stripQuery(uriAfter); + } + return false; +} + +function stripQuery(uri: string): string { + return uri.replace(/\?[^?]*$/, ''); +} diff --git a/tests/unit/controller/level-helper.ts b/tests/unit/controller/level-helper.ts index 5889314b97c..d8f932f8d89 100644 --- a/tests/unit/controller/level-helper.ts +++ b/tests/unit/controller/level-helper.ts @@ -1677,6 +1677,43 @@ video_5432.m4s`; }); }); + it('does not error between updates when only the query part of the URI changes', function () { + const playlist1 = `#EXTM3U +#EXT-X-VERSION:6 +#EXT-X-TARGETDURATION:3 +#EXT-X-MEDIA-SEQUENCE:5428 +#EXT-X-DISCONTINUITY-SEQUENCE:31 +#EXT-X-MAP:URI="video_init.mp4" +#EXTINF:2.000, +video_5428.m4s?t=1 +#EXTINF:2.000, +video_5429.m4s?t=1 +#EXTINF:2.000, +video_5430.m4s?t=1 +#EXTINF:2.000, +video_5431.m4s?t=1`; + // Media sequence increased by one but two segments removed. + const playlist2 = `#EXTM3U +#EXT-X-VERSION:6 +#EXT-X-TARGETDURATION:3 +#EXT-X-MEDIA-SEQUENCE:5429 +#EXT-X-DISCONTINUITY-SEQUENCE:31 +#EXT-X-MAP:URI="video_init.mp4" +#EXTINF:2.000, +video_5429.m4s?t=2 +#EXTINF:2.000, +video_5430.m4s?t=2 +#EXTINF:2.000, +video_5431.m4s?t=2 +#EXTINF:2.000, +video_5432.m4s?t=2`; + const details1 = parseLevelPlaylist(playlist1); + const details2 = parseLevelPlaylist(playlist2); + details2.fragments[0].base.url += '?base=changed'; + mergeDetails(details1, details2, logger); + expect(details2.playlistParsingError).to.be.null; + }); + it('maps dateranges based on latest EXT-X-PROGRAM-DATE-TIME', function () { const playlist1 = `#EXTM3U #EXT-X-VERSION:6 From c64313bd2da3a1b3dd6aecea1e231ed00f4900b6 Mon Sep 17 00:00:00 2001 From: Tristan Matthews Date: Tue, 19 Aug 2025 10:00:08 -0400 Subject: [PATCH 06/64] Fix syntax in `recoverMediaError` example in API.md This also addresses some formatting issues found by prettier. --- docs/API.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/API.md b/docs/API.md index 0f6e47a0963..f1f8067f986 100644 --- a/docs/API.md +++ b/docs/API.md @@ -362,7 +362,7 @@ It should not be used in response to non-fatal hls.js error events. ```js let attemptedErrorRecovery = null; -video.addEventListener('error', (event) { +video.addEventListener('error', (event) => { const mediaError = event.currentTarget.error; if (mediaError.code === mediaError.MEDIA_ERR_DECODE) { const now = Date.now(); @@ -380,11 +380,19 @@ hls.on(Hls.Events.ERROR, function (name, data) { case Hls.ErrorTypes.MEDIA_ERROR: { const now = Date.now(); if (!attemptedErrorRecovery || now - attemptedErrorRecovery > 5000) { - console.log('Fatal media error encountered (' + video.error + + '), attempting to recover'); + console.log( + 'Fatal media error encountered (' + + video.error + + +'), attempting to recover', + ); attemptedErrorRecovery = now; hls.recoverMediaError(); } else { - console.log('Skipping media error recovery (only ' + (now - attemptedErrorRecovery) + 'ms since last error)'); + console.log( + 'Skipping media error recovery (only ' + + (now - attemptedErrorRecovery) + + 'ms since last error)', + ); } break; } From 1e3453e1b1c482392cad2094e83dedafda9bacc8 Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Wed, 20 Aug 2025 16:08:06 -0700 Subject: [PATCH 07/64] Fix interstitial asset events not firing when attaching primary early for playout-limit change (#7467) * Fix interstitial asset events not firing when attaching primary early for playout-limit change * Add log message helper for timeline position updates --- docs/API.md | 4 +++- src/controller/interstitials-controller.ts | 26 +++++++++++++++++----- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/docs/API.md b/docs/API.md index f1f8067f986..359c5ddbd7d 100644 --- a/docs/API.md +++ b/docs/API.md @@ -569,7 +569,9 @@ This configuration will be applied by default to all instances. (default: `false`) -Setting `config.debug = true;` will turn on debug logs on JS console. +Setting `config.debug = true` enables JavaScript debug console logs. Debug mode also disables catching exceptions in even handler callbacks. +In debug mode, when an event listener throws, the exception is not caught. This allows uncaught exeptions to trigger the JavaScript debugger. +In production mode (`config.debug = false`), exceptions that are caught in event handlers are redispatched as errors with `type: OTHER_ERROR, details: INTERNAL_EXCEPTION, error: `. A logger object could also be provided for custom logging: `config.debug = customLogger;`. diff --git a/src/controller/interstitials-controller.ts b/src/controller/interstitials-controller.ts index e81be3072fd..f790422bfd4 100644 --- a/src/controller/interstitials-controller.ts +++ b/src/controller/interstitials-controller.ts @@ -86,6 +86,10 @@ function playWithCatch(media: HTMLMediaElement | null) { }); } +function timelineMessage(label: string, time: number) { + return `[${label}] Advancing timeline position to ${time}`; +} + export default class InterstitialsController extends Logger implements NetworkComponentAPI @@ -281,6 +285,7 @@ export default class InterstitialsController } private clearScheduleState() { + this.log(`clear schedule state`); this.playingItem = this.bufferingItem = this.waitingItem = @@ -308,6 +313,7 @@ export default class InterstitialsController if (this.detachedData) { const player = this.getBufferingPlayer(); if (player) { + this.log(`Removing schedule state for detachedData and ${player}`); this.playingAsset = this.endedAsset = this.bufferingAsset = @@ -1027,6 +1033,7 @@ export default class InterstitialsController const effectivePlayingItem = this.effectivePlayingItem; if (timelinePos === -1) { const startPosition = this.hls.startPosition; + this.log(timelineMessage('checkStart', startPosition)); this.timelinePos = startPosition; if (interstitialEvents.length && interstitialEvents[0].cue.pre) { const index = schedule.findEventIndex(interstitialEvents[0].identifier); @@ -1090,6 +1097,7 @@ export default class InterstitialsController } const resumptionTime = interstitial.resumeTime; if (this.timelinePos < resumptionTime) { + this.log(timelineMessage('advanceAfterAssetEnded', resumptionTime)); this.timelinePos = resumptionTime; if (interstitial.appendInPlace) { this.advanceInPlace(resumptionTime); @@ -1125,7 +1133,7 @@ export default class InterstitialsController } const scheduledItem = index >= 0 ? scheduleItems[index] : null; this.log( - `setSchedulePosition ${index}, ${assetListIndex} (${scheduledItem ? segmentToString(scheduledItem) : scheduledItem})`, + `setSchedulePosition ${index}, ${assetListIndex} (${scheduledItem ? segmentToString(scheduledItem) : scheduledItem}) pos: ${this.timelinePos}`, ); // Cleanup current item / asset const currentItem = this.waitingItem || this.playingItem; @@ -1395,6 +1403,7 @@ export default class InterstitialsController timelinePos >= scheduledItem.end ) { timelinePos = this.getPrimaryResumption(scheduledItem, index); + this.log(timelineMessage('resumePrimary', timelinePos)); this.timelinePos = timelinePos; } this.attachPrimary(timelinePos, scheduledItem); @@ -1475,6 +1484,7 @@ export default class InterstitialsController } if (!skipSeekToStartPosition) { // Set primary position to resume time + this.log(timelineMessage('attachPrimary', timelinePos)); this.timelinePos = timelinePos; this.startLoadingPrimaryAt(timelinePos, skipSeekToStartPosition); } @@ -1762,11 +1772,11 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli if (playingItem) { this.trimInPlace(updatedPlayingItem, playingItem); } - if (bufferingItem) { + if (bufferingItem && updatedBufferingItem !== updatedPlayingItem) { this.trimInPlace(updatedBufferingItem, bufferingItem); } - // Check is buffered to new Interstitial event boundary + // Check if buffered to new Interstitial event boundary // (Live update publishes Interstitial with new segment) this.checkBuffer(); } @@ -1809,7 +1819,11 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli bufferInfo.end > flushStart || (bufferInfo.nextStart || 0) > flushStart ) { - this.attachPrimary(flushStart, null); + this.log( + `trim buffered interstitial ${segmentToString(updatedItem)} (was ${segmentToString(itemBeforeUpdate)})`, + ); + const skipSeekToStartPosition = true; + this.attachPrimary(flushStart, null, skipSeekToStartPosition); this.flushFrontBuffer(flushStart); } } @@ -2486,10 +2500,10 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli ) { const playerIndex = this.getAssetPlayerQueueIndex(assetId); if (playerIndex !== -1) { + const player = this.playerQueue[playerIndex]; this.log( - `clear asset player "${assetId}" toSegment: ${toSegment ? segmentToString(toSegment) : toSegment}`, + `clear ${player} toSegment: ${toSegment ? segmentToString(toSegment) : toSegment}`, ); - const player = this.playerQueue[playerIndex]; this.transferMediaFromPlayer(player, toSegment); this.playerQueue.splice(playerIndex, 1); player.destroy(); From d42eccee09f9383f911c6418caec8930b148a5c1 Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Fri, 22 Aug 2025 12:19:21 -0700 Subject: [PATCH 08/64] Fix exception when seeking to program end --- src/controller/interstitials-controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controller/interstitials-controller.ts b/src/controller/interstitials-controller.ts index f790422bfd4..abe29884b49 100644 --- a/src/controller/interstitials-controller.ts +++ b/src/controller/interstitials-controller.ts @@ -1235,7 +1235,7 @@ export default class InterstitialsController if (!schedule) { return; } - const scheduledItem = index >= 0 ? scheduleItems[index] : null; + const scheduledItem = scheduleItems[index] || null; const media = this.primaryMedia; // Cleanup out of range Interstitials const playerQueue = this.playerQueue; @@ -1354,7 +1354,7 @@ export default class InterstitialsController if (this.shouldPlay) { playWithCatch(player.media); } - } else if (scheduledItem !== null) { + } else if (scheduledItem) { this.resumePrimary(scheduledItem, index, currentItem); if (this.shouldPlay) { playWithCatch(this.hls.media); From 4ebd6d2431db42e57bbfd6ee586d5c70d8ae33c7 Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Fri, 22 Aug 2025 14:55:59 -0700 Subject: [PATCH 09/64] Handle EME key status errors such as "internal-error" and "output-restricted" before appending media (#7414) * Handle EME key status errors before appending segments Fixes #7413 (playback/switching fails on KEY_SYSTEM_STATUS_INTERNAL_ERROR 'internal-error' key status) * Fix handling of one-to-many KEY URI to Key IDs #7413 * Remove levels with "internal-error" key status errors and optimize key-bytes comparison Add "FIXME" comments for future MediaKeySessionContext multi-key handling improvements * Do not throw fatal keyLoadingPromise error when context changes on KEY_LOADING reproducible with `hls.once(Hls.Events.KEY_LOADING, () => hls.removeLevel(hls.loadLevel))` --- api-extractor/report/hls.js.api.md | 20 +- src/config.ts | 4 +- src/controller/base-stream-controller.ts | 22 +- src/controller/eme-controller.ts | 288 +++++++++++------- src/controller/error-controller.ts | 143 ++++++--- src/hls.ts | 2 +- src/loader/key-loader.ts | 85 ++++-- src/loader/level-details.ts | 13 + src/loader/level-key.ts | 15 +- src/types/events.ts | 1 + src/utils/arrays.ts | 22 ++ src/utils/hex.ts | 28 +- src/utils/mediakeys-helper.ts | 11 +- src/utils/mp4-tools.ts | 6 +- .../controller/audio-stream-controller.ts | 2 +- .../unit/controller/base-stream-controller.ts | 2 +- tests/unit/controller/eme-controller.ts | 66 ++-- .../controller/subtitle-stream-controller.ts | 2 +- 18 files changed, 454 insertions(+), 278 deletions(-) create mode 100644 src/utils/arrays.ts diff --git a/api-extractor/report/hls.js.api.md b/api-extractor/report/hls.js.api.md index 9532f9b276a..5511ee3ed90 100644 --- a/api-extractor/report/hls.js.api.md +++ b/api-extractor/report/hls.js.api.md @@ -1189,8 +1189,8 @@ export type EMEControllerConfig = { licenseResponseCallback?: (this: Hls, xhr: XMLHttpRequest, url: string, keyContext: MediaKeySessionContext) => ArrayBuffer; emeEnabled: boolean; widevineLicenseUrl?: string; - drmSystems: DRMSystemsConfiguration; - drmSystemOptions: DRMSystemOptions; + drmSystems: DRMSystemsConfiguration | undefined; + drmSystemOptions: DRMSystemOptions | undefined; requestMediaKeySystemAccessFunc: MediaKeyFunc | null; requireKeySystemAccessOnStart: boolean; }; @@ -1204,9 +1204,11 @@ export const enum ErrorActionFlags { // (undocumented) MoveAllAlternatesMatchingHost = 1, // (undocumented) + MoveAllAlternatesMatchingKey = 4, + // (undocumented) None = 0, // (undocumented) - SwitchToSDR = 4 + SwitchToSDR = 8 } // Warning: (ae-missing-release-tag) "ErrorController" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -1239,6 +1241,8 @@ export interface ErrorData { // (undocumented) context?: PlaylistLoaderContext; // (undocumented) + decryptdata?: LevelKey; + // (undocumented) details: ErrorDetails; // @deprecated (undocumented) err?: { @@ -2981,8 +2985,8 @@ export interface KeyLoadedData { // Warning: (ae-missing-release-tag) "KeyLoader" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export class KeyLoader implements ComponentAPI { - constructor(config: HlsConfig); +export class KeyLoader extends Logger implements ComponentAPI { + constructor(config: HlsConfig, logger: ILogger); // (undocumented) abort(type?: PlaylistLevelType): void; // (undocumented) @@ -2999,10 +3003,6 @@ export class KeyLoader implements ComponentAPI { // (undocumented) emeController: EMEController | null; // (undocumented) - keyUriToKeyInfo: { - [keyuri: string]: KeyLoaderInfo; - }; - // (undocumented) load(frag: Fragment): Promise; // (undocumented) loadClear(loadingFrag: Fragment, encryptedFragments: Fragment[], startFragRequested: boolean): null | Promise; @@ -3278,6 +3278,8 @@ export class LevelDetails { // (undocumented) get fragmentStart(): number; // (undocumented) + hasKey(levelKey: LevelKey): boolean; + // (undocumented) get hasProgramDateTime(): boolean; // (undocumented) hasVariableRefs: boolean; diff --git a/src/config.ts b/src/config.ts index 4627baa5797..7ba0901fb2f 100644 --- a/src/config.ts +++ b/src/config.ts @@ -119,8 +119,8 @@ export type EMEControllerConfig = { ) => ArrayBuffer; emeEnabled: boolean; widevineLicenseUrl?: string; - drmSystems: DRMSystemsConfiguration; - drmSystemOptions: DRMSystemOptions; + drmSystems: DRMSystemsConfiguration | undefined; + drmSystemOptions: DRMSystemOptions | undefined; requestMediaKeySystemAccessFunc: MediaKeyFunc | null; requireKeySystemAccessOnStart: boolean; }; diff --git a/src/controller/base-stream-controller.ts b/src/controller/base-stream-controller.ts index 04e1c2973cf..5ce214dd3b2 100644 --- a/src/controller/base-stream-controller.ts +++ b/src/controller/base-stream-controller.ts @@ -841,10 +841,9 @@ export default class BaseStreamController } }); this.hls.trigger(Events.KEY_LOADING, { frag }); - if (this.fragCurrent === null) { - keyLoadingPromise = Promise.reject( - new Error(`frag load aborted, context changed in KEY_LOADING`), - ); + if ((this.fragCurrent as Fragment | null) === null) { + this.log(`context changed in KEY_LOADING`); + return Promise.resolve(null); } } else if (!frag.encrypted) { keyLoadingPromise = this.keyLoader.loadClear( @@ -1040,11 +1039,16 @@ export default class BaseStreamController ); } - private handleFragLoadError(error: LoadError | Error) { + private handleFragLoadError( + error: LoadError | Error | (Error & { data: ErrorData }), + ) { if ('data' in error) { const data = error.data; - if ((data as any) && data.details === ErrorDetails.INTERNAL_ABORTED) { + if (data.frag && data.details === ErrorDetails.INTERNAL_ABORTED) { this.handleFragLoadAborted(data.frag, data.part); + } else if (data.frag && data.type === ErrorTypes.KEY_SYSTEM_ERROR) { + data.frag.abortRequests(); + this.resetFragmentLoading(data.frag); } else { this.hls.trigger(Events.ERROR, data as ErrorData); } @@ -1826,7 +1830,7 @@ export default class BaseStreamController return pos; } - private handleFragLoadAborted(frag: Fragment, part: Part | undefined) { + private handleFragLoadAborted(frag: Fragment, part: Part | null | undefined) { if ( this.transmuxer && frag.type === this.playlistType && @@ -2033,8 +2037,8 @@ export default class BaseStreamController } protected resetWhenMissingContext(chunkMeta: ChunkMetadata | Fragment) { - this.warn( - `The loading context changed while buffering fragment ${chunkMeta.sn} of ${this.playlistLabel()} ${chunkMeta.level}. This chunk will not be buffered.`, + this.log( + `Loading context changed while buffering sn ${chunkMeta.sn} of ${this.playlistLabel()} ${chunkMeta.level === -1 ? '' : chunkMeta.level}. This chunk will not be buffered.`, ); this.removeUnbufferedFrags(); this.resetStartWhenNotLoaded(this.levelLastLoaded); diff --git a/src/controller/eme-controller.ts b/src/controller/eme-controller.ts index 331e588942c..08a06fb166b 100644 --- a/src/controller/eme-controller.ts +++ b/src/controller/eme-controller.ts @@ -11,7 +11,7 @@ import { addEventListener, removeEventListener, } from '../utils/event-listener-helper'; -import Hex from '../utils/hex'; +import { arrayToHex } from '../utils/hex'; import { Logger } from '../utils/logger'; import { getKeySystemsForConfig, @@ -44,6 +44,7 @@ import type { LoaderContext, } from '../types/loader'; import type { KeySystemFormats } from '../utils/mediakeys-helper'; + interface KeySystemAccessPromises { keySystemAccess: Promise; mediaKeys?: Promise; @@ -54,9 +55,9 @@ interface KeySystemAccessPromises { export interface MediaKeySessionContext { keySystem: KeySystems; mediaKeys: MediaKeys; - decryptdata: LevelKey; + decryptdata: LevelKey; // FIXME: LevelKey has a URI which should be bound to the session, but is dependent one KeyId specifically. Session context should be allowed to adopt multiple level keys. mediaKeysSession: MediaKeySession; - keyStatus: MediaKeyStatus; + keyStatus: MediaKeyStatus; // FIXME: MediaKeySession can manage multiple keys with each with its own status licenseXhr?: XMLHttpRequest; _onmessage?: (this: MediaKeySession, ev: MediaKeyMessageEvent) => any; _onkeystatuseschange?: (this: MediaKeySession, ev: Event) => any; @@ -81,17 +82,18 @@ class EMEController extends Logger implements ComponentAPI { private media: HTMLMediaElement | null = null; private keyFormatPromise: Promise | null = null; private keySystemAccessPromises: { - [keysystem: string]: KeySystemAccessPromises; + [keysystem: string]: KeySystemAccessPromises | undefined; } = {}; private _requestLicenseFailureCount: number = 0; private mediaKeySessions: MediaKeySessionContext[] = []; private keyIdToKeySessionPromise: { - [keyId: string]: Promise; + [keyId: string]: Promise | undefined; } = {}; private mediaKeys: MediaKeys | null = null; private setMediaKeysQueue: Promise[] = EMEController.CDMCleanupPromise ? [EMEController.CDMCleanupPromise] : []; + private bannedKeyIds: { [keyId: string]: MediaKeyStatus | undefined } = {}; constructor(hls: Hls) { super('eme', hls.logger); @@ -111,7 +113,7 @@ class EMEController extends Logger implements ComponentAPI { // @ts-ignore this.hls = this.config = this.keyIdToKeySessionPromise = null; // @ts-ignore - this.onWaitingForKey = null; + this.onMediaEncrypted = this.onWaitingForKey = null; } private registerListeners() { @@ -132,7 +134,7 @@ class EMEController extends Logger implements ComponentAPI { private getLicenseServerUrl(keySystem: KeySystems): string | undefined { const { drmSystems, widevineLicenseUrl } = this.config; - const keySystemConfiguration = drmSystems[keySystem]; + const keySystemConfiguration = drmSystems?.[keySystem]; if (keySystemConfiguration) { return keySystemConfiguration.licenseUrl; @@ -156,7 +158,7 @@ class EMEController extends Logger implements ComponentAPI { private getServerCertificateUrl(keySystem: KeySystems): string | void { const { drmSystems } = this.config; - const keySystemConfiguration = drmSystems[keySystem]; + const keySystemConfiguration = drmSystems?.[keySystem]; if (keySystemConfiguration) { return keySystemConfiguration.serverCertificateUrl; @@ -247,10 +249,9 @@ class EMEController extends Logger implements ComponentAPI { keySystem, audioCodecs, videoCodecs, - this.config.drmSystemOptions, + this.config.drmSystemOptions || {}, ); - const keySystemAccessPromises: KeySystemAccessPromises = - this.keySystemAccessPromises[keySystem]; + let keySystemAccessPromises = this.keySystemAccessPromises[keySystem]; let keySystemAccess = keySystemAccessPromises?.keySystemAccess; if (!keySystemAccess) { this.log( @@ -262,10 +263,11 @@ class EMEController extends Logger implements ComponentAPI { keySystem, mediaKeySystemConfigs, ); - const keySystemAccessPromises: KeySystemAccessPromises = - (this.keySystemAccessPromises[keySystem] = { - keySystemAccess, - }); + const keySystemAccessPromisesNew = (keySystemAccessPromises = + this.keySystemAccessPromises[keySystem] = + { + keySystemAccess, + }) as KeySystemAccessPromises; keySystemAccess.catch((error) => { this.log( `Failed to obtain access to key-system "${keySystem}": ${error}`, @@ -279,11 +281,10 @@ class EMEController extends Logger implements ComponentAPI { const certificateRequest = this.fetchServerCertificate(keySystem); this.log(`Create media-keys for "${keySystem}"`); - keySystemAccessPromises.mediaKeys = mediaKeySystemAccess - .createMediaKeys() - .then((mediaKeys) => { + const mediaKeys = (keySystemAccessPromisesNew.mediaKeys = + mediaKeySystemAccess.createMediaKeys().then((mediaKeys) => { this.log(`Media-keys created for "${keySystem}"`); - keySystemAccessPromises.hasMediaKeys = true; + keySystemAccessPromisesNew.hasMediaKeys = true; return certificateRequest.then((certificate) => { if (certificate) { return this.setMediaKeysServerCertificate( @@ -294,18 +295,18 @@ class EMEController extends Logger implements ComponentAPI { } return mediaKeys; }); - }); + })); - keySystemAccessPromises.mediaKeys.catch((error) => { + mediaKeys.catch((error) => { this.error( `Failed to create media-keys for "${keySystem}"}: ${error}`, ); }); - return keySystemAccessPromises.mediaKeys; + return mediaKeys; }); } - return keySystemAccess.then(() => keySystemAccessPromises.mediaKeys!); + return keySystemAccess.then(() => keySystemAccessPromises!.mediaKeys!); } private createMediaKeySessionContext({ @@ -318,8 +319,8 @@ class EMEController extends Logger implements ComponentAPI { mediaKeys: MediaKeys; }): MediaKeySessionContext { this.log( - `Creating key-system session "${keySystem}" keyId: ${Hex.hexDump( - decryptdata.keyId! || [], + `Creating key-system session "${keySystem}" keyId: ${arrayToHex( + decryptdata.keyId || ([] as number[]), )}`, ); @@ -366,7 +367,7 @@ class EMEController extends Logger implements ComponentAPI { if (decryptdata.keyId === null) { throw new Error('keyId is null'); } - return Hex.hexDump(decryptdata.keyId); + return arrayToHex(decryptdata.keyId); } private updateKeySession( @@ -375,10 +376,10 @@ class EMEController extends Logger implements ComponentAPI { ): Promise { const keySession = mediaKeySessionContext.mediaKeysSession; this.log( - `Updating key-session "${keySession.sessionId}" for keyID ${Hex.hexDump( - mediaKeySessionContext.decryptdata?.keyId! || [], + `Updating key-session "${keySession.sessionId}" for keyId ${arrayToHex( + mediaKeySessionContext.decryptdata.keyId || [], )} - } (data length: ${data ? data.byteLength : data})`, + } (data length: ${data.byteLength})`, ); return keySession.update(data); } @@ -387,7 +388,7 @@ class EMEController extends Logger implements ComponentAPI { return (Object.keys(this.keySystemAccessPromises) as KeySystems[]) .map((keySystem) => ({ keySystem, - hasMediaKeys: this.keySystemAccessPromises[keySystem].hasMediaKeys, + hasMediaKeys: this.keySystemAccessPromises[keySystem]!.hasMediaKeys, })) .filter(({ hasMediaKeys }) => !!hasMediaKeys) .map(({ keySystem }) => keySystemDomainToKeySystemFormat(keySystem)) @@ -451,14 +452,22 @@ class EMEController extends Logger implements ComponentAPI { const decryptdata = data.keyInfo.decryptdata; const keyId = this.getKeyIdString(decryptdata); + const badStatus = this.bannedKeyIds[keyId]; + if (badStatus) { + const error = getKeyStatusError(badStatus, decryptdata); + this.handleError(error, data.frag); + return Promise.reject(error); + } const keyDetails = `(keyId: ${keyId} format: "${decryptdata.keyFormat}" method: ${decryptdata.method} uri: ${decryptdata.uri})`; this.log(`Starting session for key ${keyDetails}`); - let keyContextPromise = this.keyIdToKeySessionPromise[keyId]; + const keyContextPromise = this.keyIdToKeySessionPromise[keyId]; if (!keyContextPromise) { - keyContextPromise = this.getKeySystemForKeyPromise(decryptdata).then( - ({ keySystem, mediaKeys }) => { + const keySessionContextPromise = this.getKeySystemForKeyPromise( + decryptdata, + ) + .then(({ keySystem, mediaKeys }) => { this.throwIfDestroyed(); this.log( `Handle encrypted media sn: ${data.frag.sn} ${data.frag.type}: ${data.frag.level} using key ${keyDetails}`, @@ -472,11 +481,8 @@ class EMEController extends Logger implements ComponentAPI { decryptdata, }); }); - }, - ); - - const keySessionContextPromise = (this.keyIdToKeySessionPromise[keyId] = - keyContextPromise.then((keySessionContext) => { + }) + .then((keySessionContext) => { const scheme = 'cenc'; const initData = decryptdata.pssh ? decryptdata.pssh.buffer : null; return this.generateRequestWithPreferredKeySession( @@ -485,26 +491,34 @@ class EMEController extends Logger implements ComponentAPI { initData, 'playlist-key', ); - })); + }); + + keySessionContextPromise.catch((error) => + this.handleError(error, data.frag), + ); + this.keyIdToKeySessionPromise[keyId] = keySessionContextPromise; - keySessionContextPromise.catch((error) => this.handleError(error)); + return keySessionContextPromise; } return keyContextPromise; } private throwIfDestroyed(message = 'Invalid state'): void | never { - if (!this.hls) { + if (!this.hls as any) { throw new Error('invalid state'); } } - private handleError(error: EMEKeyError | Error) { - if (!this.hls) { + private handleError(error: EMEKeyError | Error, frag?: Fragment) { + if (!this.hls as any) { return; } this.error(error.message); if (error instanceof EMEKeyError) { + if (frag) { + error.data.frag = frag; + } this.hls.trigger(Events.ERROR, error.data); } else { this.hls.trigger(Events.ERROR, { @@ -604,7 +618,7 @@ class EMEController extends Logger implements ComponentAPI { return; } - const keyIdHex = Hex.hexDump(keyId); + const keyIdHex = arrayToHex(keyId); const { keyIdToKeySessionPromise, mediaKeySessions } = this; let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex]; @@ -615,7 +629,7 @@ class EMEController extends Logger implements ComponentAPI { if (!decryptdata.keyId) { continue; } - const oldKeyIdHex = Hex.hexDump(decryptdata.keyId); + const oldKeyIdHex = arrayToHex(decryptdata.keyId); if ( keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1 @@ -717,7 +731,7 @@ class EMEController extends Logger implements ComponentAPI { context.decryptdata.pssh = initData ? new Uint8Array(initData) : null; } catch (error) { this.warn(error.message); - if (this.hls?.config.debug) { + if ((this.hls as any) && this.hls.config.debug) { throw error; } } @@ -731,7 +745,7 @@ class EMEController extends Logger implements ComponentAPI { const keyId = this.getKeyIdString(context.decryptdata); this.log( `Generating key-session request for "${reason}": ${keyId} (init data type: ${initDataType} length: ${ - initData ? initData.byteLength : null + initData.byteLength })`, ); @@ -739,7 +753,7 @@ class EMEController extends Logger implements ComponentAPI { const onmessage = (context._onmessage = (event: MediaKeyMessageEvent) => { const keySession = context.mediaKeysSession; - if (!keySession) { + if (!keySession as any) { licenseStatus.emit('error', new Error('invalid state')); return; } @@ -772,16 +786,19 @@ class EMEController extends Logger implements ComponentAPI { event: Event, ) => { const keySession = context.mediaKeysSession; - if (!keySession) { + if (!keySession as any) { licenseStatus.emit('error', new Error('invalid state')); return; } + const initialStatus = context.keyStatus; this.onKeyStatusChange(context); - const keyStatus = context.keyStatus; - licenseStatus.emit('keyStatus', keyStatus); - if (keyStatus === 'expired') { - this.warn(`${context.keySystem} expired for key ${keyId}`); - this.renewKeySession(context); + const status = context.keyStatus; + if (status !== initialStatus) { + licenseStatus.emit('keyStatus', status, context); + if (status === 'expired') { + this.log(`${context.keySystem} expired for key ${keyId}`); + this.renewKeySession(context); + } } }); @@ -796,37 +813,32 @@ class EMEController extends Logger implements ComponentAPI { (resolve: (value?: void) => void, reject) => { licenseStatus.on('error', reject); - licenseStatus.on('keyStatus', (keyStatus) => { - if (keyStatus.startsWith('usable')) { - resolve(); - } else if (keyStatus === 'output-restricted') { - reject( - new EMEKeyError( - { - type: ErrorTypes.KEY_SYSTEM_ERROR, - details: ErrorDetails.KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED, - fatal: false, - }, - 'HDCP level output restricted', - ), - ); - } else if (keyStatus === 'internal-error') { - reject( - new EMEKeyError( - { - type: ErrorTypes.KEY_SYSTEM_ERROR, - details: ErrorDetails.KEY_SYSTEM_STATUS_INTERNAL_ERROR, - fatal: true, - }, - `key status changed to "${keyStatus}"`, - ), - ); - } else if (keyStatus === 'expired') { - reject(new Error('key expired while generating request')); - } else { - this.warn(`unhandled key status change "${keyStatus}"`); - } - }); + licenseStatus.on( + 'keyStatus', + ( + keyStatus: MediaKeyStatus, + { decryptdata }: MediaKeySessionContext, + ) => { + if (keyStatus.startsWith('usable')) { + resolve(); + } else if ( + keyStatus === 'internal-error' || + keyStatus === 'output-restricted' + ) { + reject(getKeyStatusError(keyStatus, decryptdata)); + } else if (keyStatus === 'expired') { + reject( + new Error( + `key expired while generating request (keyId: ${keyId})`, + ), + ); + } else { + this.warn( + `unhandled key status change "${keyStatus}" (keyId: ${keyId})`, + ); + } + }, + ); }, ); @@ -834,7 +846,7 @@ class EMEController extends Logger implements ComponentAPI { .generateRequest(initDataType, initData) .then(() => { this.log( - `Request generated for key-session "${context.mediaKeysSession?.sessionId}" keyId: ${keyId}`, + `Request generated for key-session "${context.mediaKeysSession.sessionId}" keyId: ${keyId}`, ); }) .catch((error) => { @@ -843,6 +855,7 @@ class EMEController extends Logger implements ComponentAPI { type: ErrorTypes.KEY_SYSTEM_ERROR, details: ErrorDetails.KEY_SYSTEM_NO_SESSION, error, + decryptdata: context.decryptdata, fatal: false, }, `Error generating key-session request: ${error}`, @@ -861,6 +874,9 @@ class EMEController extends Logger implements ComponentAPI { } private onKeyStatusChange(mediaKeySessionContext: MediaKeySessionContext) { + const sessionLevelKeyId = arrayToHex( + new Uint8Array(mediaKeySessionContext.decryptdata.keyId || []), + ); mediaKeySessionContext.mediaKeysSession.keyStatuses.forEach( (status: MediaKeyStatus, keyId: BufferSource) => { // keyStatuses.forEach is not standard API so the callback value looks weird on xboxone @@ -870,16 +886,25 @@ class EMEController extends Logger implements ComponentAPI { keyId = status; status = temp; } + const keyIdWithStatusChange = arrayToHex( + 'buffer' in keyId + ? new Uint8Array(keyId.buffer, keyId.byteOffset, keyId.byteLength) + : new Uint8Array(keyId), + ); + + // Error immediately when encountering a key ID with this status again + if (status === 'internal-error') { + this.bannedKeyIds[keyIdWithStatusChange] = status; + } + + // Only acknowledge status changes for level-key ID + const matched = keyIdWithStatusChange === sessionLevelKeyId; this.log( - `key status change "${status}" for keyStatuses keyId: ${Hex.hexDump( - 'buffer' in keyId - ? new Uint8Array(keyId.buffer, keyId.byteOffset, keyId.byteLength) - : new Uint8Array(keyId), - )} session keyId: ${Hex.hexDump( - new Uint8Array(mediaKeySessionContext.decryptdata.keyId || []), - )} uri: ${mediaKeySessionContext.decryptdata.uri}`, + `${matched ? '' : 'un'}matched key status change "${status}" for keyStatuses keyId: ${keyIdWithStatusChange} session keyId: ${sessionLevelKeyId} uri: ${mediaKeySessionContext.decryptdata.uri}`, ); - mediaKeySessionContext.keyStatus = status; + if (matched) { + mediaKeySessionContext.keyStatus = status; + } }, ); } @@ -969,7 +994,7 @@ class EMEController extends Logger implements ComponentAPI { this.log( `setServerCertificate ${ success ? 'success' : 'not supported by CDM' - } (${cert?.byteLength}) on "${keySystem}"`, + } (${cert.byteLength}) on "${keySystem}"`, ); resolve(mediaKeys); }) @@ -1002,8 +1027,9 @@ class EMEController extends Logger implements ComponentAPI { { type: ErrorTypes.KEY_SYSTEM_ERROR, details: ErrorDetails.KEY_SYSTEM_SESSION_UPDATE_FAILED, + decryptdata: context.decryptdata, error, - fatal: true, + fatal: false, }, error.message, ); @@ -1078,7 +1104,7 @@ class EMEController extends Logger implements ComponentAPI { return Promise.resolve() .then(() => { - if (!keysListItem.decryptdata) { + if (!keysListItem.decryptdata as any) { throw new Error('Key removed'); } return licenseXhrSetup.call( @@ -1090,7 +1116,7 @@ class EMEController extends Logger implements ComponentAPI { ); }) .catch((error: Error) => { - if (!keysListItem.decryptdata) { + if (!keysListItem.decryptdata as any) { // Key session removed. Cancel license request. throw error; } @@ -1128,7 +1154,10 @@ class EMEController extends Logger implements ComponentAPI { const xhr = new XMLHttpRequest(); xhr.responseType = 'arraybuffer'; xhr.onreadystatechange = () => { - if (!this.hls || !keySessionContext.mediaKeysSession) { + if ( + (!this.hls as any) || + (!keySessionContext.mediaKeysSession as any) + ) { return reject(new Error('invalid state')); } if (xhr.readyState === 4) { @@ -1167,6 +1196,7 @@ class EMEController extends Logger implements ComponentAPI { { type: ErrorTypes.KEY_SYSTEM_ERROR, details: ErrorDetails.KEY_SYSTEM_LICENSE_REQUEST_FAILED, + decryptdata: keySessionContext.decryptdata, fatal: true, networkDetails: xhr, response: { @@ -1251,6 +1281,7 @@ class EMEController extends Logger implements ComponentAPI { private _clear() { this._requestLicenseFailureCount = 0; this.keyIdToKeySessionPromise = {}; + this.bannedKeyIds = {}; if (!this.mediaKeys && !this.mediaKeySessions.length) { return; } @@ -1269,20 +1300,24 @@ class EMEController extends Logger implements ComponentAPI { this.removeSession(mediaKeySessionContext), ) .concat( - media?.setMediaKeys(null)?.catch((error) => { - this.log(`Could not clear media keys: ${error}`); - this.hls?.trigger(Events.ERROR, { - type: ErrorTypes.OTHER_ERROR, - details: ErrorDetails.KEY_SYSTEM_DESTROY_MEDIA_KEYS_ERROR, - fatal: false, - error: new Error(`Could not clear media keys: ${error}`), - }); - }), + (media?.setMediaKeys(null) as Promise | null)?.catch( + (error) => { + this.log(`Could not clear media keys: ${error}`); + if (!this.hls as any) return; + this.hls.trigger(Events.ERROR, { + type: ErrorTypes.OTHER_ERROR, + details: ErrorDetails.KEY_SYSTEM_DESTROY_MEDIA_KEYS_ERROR, + fatal: false, + error: new Error(`Could not clear media keys: ${error}`), + }); + }, + ), ), ) .catch((error) => { this.log(`Could not close sessions and clear media keys: ${error}`); - this.hls?.trigger(Events.ERROR, { + if (!this.hls as any) return; + this.hls.trigger(Events.ERROR, { type: ErrorTypes.OTHER_ERROR, details: ErrorDetails.KEY_SYSTEM_DESTROY_CLOSE_SESSION_ERROR, fatal: false, @@ -1301,6 +1336,7 @@ class EMEController extends Logger implements ComponentAPI { private onManifestLoading() { this.keyFormatPromise = null; + this.bannedKeyIds = {}; } private onManifestLoaded( @@ -1332,10 +1368,11 @@ class EMEController extends Logger implements ComponentAPI { private removeSession( mediaKeySessionContext: MediaKeySessionContext, ): Promise | void { - const { mediaKeysSession, licenseXhr } = mediaKeySessionContext; - if (mediaKeysSession) { + const { mediaKeysSession, licenseXhr, decryptdata } = + mediaKeySessionContext; + if (mediaKeysSession as MediaKeySession | undefined) { this.log( - `Remove licenses and keys and close session ${mediaKeysSession.sessionId}`, + `Remove licenses and keys and close session "${mediaKeysSession.sessionId}" keyId: ${arrayToHex((decryptdata as LevelKey | undefined)?.keyId || [])}`, ); if (mediaKeySessionContext._onmessage) { mediaKeysSession.removeEventListener( @@ -1376,7 +1413,8 @@ class EMEController extends Logger implements ComponentAPI { return removePromise .catch((error) => { this.log(`Could not remove session: ${error}`); - this.hls?.trigger(Events.ERROR, { + if (!this.hls as any) return; + this.hls.trigger(Events.ERROR, { type: ErrorTypes.OTHER_ERROR, details: ErrorDetails.KEY_SYSTEM_DESTROY_REMOVE_SESSION_ERROR, fatal: false, @@ -1388,7 +1426,8 @@ class EMEController extends Logger implements ComponentAPI { }) .catch((error) => { this.log(`Could not close session: ${error}`); - this.hls?.trigger(Events.ERROR, { + if (!this.hls as any) return; + this.hls.trigger(Events.ERROR, { type: ErrorTypes.OTHER_ERROR, details: ErrorDetails.KEY_SYSTEM_DESTROY_CLOSE_SESSION_ERROR, fatal: false, @@ -1412,4 +1451,25 @@ class EMEKeyError extends Error { } } +function getKeyStatusError( + keyStatus: MediaKeyStatus, + decryptdata: LevelKey, +): EMEKeyError { + const outputRestricted = keyStatus === 'output-restricted'; + const details = outputRestricted + ? ErrorDetails.KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED + : ErrorDetails.KEY_SYSTEM_STATUS_INTERNAL_ERROR; + return new EMEKeyError( + { + type: ErrorTypes.KEY_SYSTEM_ERROR, + details, + fatal: false, + decryptdata, + }, + outputRestricted + ? 'HDCP level output restricted' + : `key status changed to "${keyStatus}"`, + ); +} + export default EMEController; diff --git a/src/controller/error-controller.ts b/src/controller/error-controller.ts index e2bbc73d392..171a3fe1ad6 100644 --- a/src/controller/error-controller.ts +++ b/src/controller/error-controller.ts @@ -9,14 +9,15 @@ import { isTimeoutError, shouldRetry, } from '../utils/error-helper'; +import { arrayToHex } from '../utils/hex'; import { Logger } from '../utils/logger'; import type { RetryConfig } from '../config'; +import type { LevelKey } from '../hls'; import type Hls from '../hls'; import type { Fragment, MediaFragment } from '../loader/fragment'; -import type { LevelDetails } from '../loader/level-details'; import type { NetworkComponentAPI } from '../types/component-api'; import type { ErrorData } from '../types/events'; -import type { HdcpLevel } from '../types/level'; +import type { HdcpLevel, Level } from '../types/level'; export const enum NetworkErrorAction { DoNothing = 0, @@ -30,8 +31,9 @@ export const enum NetworkErrorAction { export const enum ErrorActionFlags { None = 0, MoveAllAlternatesMatchingHost = 1, - MoveAllAlternatesMatchingHDCP = 1 << 1, - SwitchToSDR = 1 << 2, // Reserved for future use + MoveAllAlternatesMatchingHDCP = 2, + MoveAllAlternatesMatchingKey = 4, + SwitchToSDR = 8, } export type IErrorAction = { @@ -43,22 +45,12 @@ export type IErrorAction = { nextAutoLevel?: number; resolved?: boolean; }; - -type PenalizedRendition = { - lastErrorPerfMs: number; - errors: ErrorData[]; - details?: LevelDetails; -}; - -type PenalizedRenditions = { [key: number]: PenalizedRendition }; - export default class ErrorController extends Logger implements NetworkComponentAPI { private readonly hls: Hls; private playlistError: number = 0; - private penalizedRenditions: PenalizedRenditions = {}; constructor(hls: Hls) { super('error-controller', hls.logger); @@ -88,7 +80,6 @@ export default class ErrorController this.unregisterListeners(); // @ts-ignore this.hls = null; - this.penalizedRenditions = {}; } startLoad(startPosition: number): void {} @@ -98,14 +89,42 @@ export default class ErrorController } private getVariantLevelIndex(frag: Fragment | undefined): number { - return frag?.type === PlaylistLevelType.MAIN - ? frag.level - : this.hls.loadLevel; + if (frag?.type === PlaylistLevelType.MAIN) { + return frag.level; + } + return this.getVariantIndex(); + } + + private getVariantIndex(): number { + const hls = this.hls; + const currentLevel = hls.currentLevel; + if (hls.loadLevelObj?.details || currentLevel === -1) { + return hls.loadLevel; + } + return currentLevel; + } + + private variantHasKey( + level: Level | undefined, + keyInError: LevelKey, + ): boolean { + if (level) { + if (level.details?.hasKey(keyInError)) { + return true; + } + const audioGroupsIds = level.audioGroups; + if (audioGroupsIds) { + const audioTracks = this.hls.allAudioTracks.filter( + (track) => audioGroupsIds.indexOf(track.groupId) >= 0, + ); + return audioTracks.some((track) => track.details?.hasKey(keyInError)); + } + } + return false; } private onManifestLoading() { this.playlistError = 0; - this.penalizedRenditions = {}; } private onLevelUpdated() { @@ -201,17 +220,20 @@ export default class ErrorController return; case ErrorDetails.KEY_SYSTEM_STATUS_OUTPUT_RESTRICTED: { - const level = hls.loadLevelObj; - const restrictedHdcpLevel = level?.attrs['HDCP-LEVEL']; - if (restrictedHdcpLevel) { - data.errorAction = { - action: NetworkErrorAction.SendAlternateToPenaltyBox, - flags: ErrorActionFlags.MoveAllAlternatesMatchingHDCP, - hdcpLevel: restrictedHdcpLevel, - }; - } else { - this.keySystemError(data); - } + data.errorAction = { + action: NetworkErrorAction.SendAlternateToPenaltyBox, + flags: ErrorActionFlags.MoveAllAlternatesMatchingHDCP, + }; + } + return; + case ErrorDetails.KEY_SYSTEM_SESSION_UPDATE_FAILED: + case ErrorDetails.KEY_SYSTEM_STATUS_INTERNAL_ERROR: + case ErrorDetails.KEY_SYSTEM_NO_SESSION: + { + data.errorAction = { + action: NetworkErrorAction.SendAlternateToPenaltyBox, + flags: ErrorActionFlags.MoveAllAlternatesMatchingKey, + }; } return; case ErrorDetails.BUFFER_ADD_CODEC_ERROR: @@ -237,17 +259,12 @@ export default class ErrorController } if (data.type === ErrorTypes.KEY_SYSTEM_ERROR) { - this.keySystemError(data); + // Do not retry level. Should be fatal if ErrorDetails.KEY_SYSTEM_ not handled with early return above. + data.levelRetry = false; + data.errorAction = createDoNothingErrorAction(); } } - private keySystemError(data: ErrorData) { - const levelIndex = this.getVariantLevelIndex(data.frag); - // Do not retry level. Escalate to fatal if switching levels fails. - data.levelRetry = false; - data.errorAction = this.getLevelSwitchAction(data, levelIndex); - } - private getPlaylistRetryOrSwitchAction( data: ErrorData, levelIndex: number | null | undefined, @@ -478,21 +495,54 @@ export default class ErrorController if (!errorAction) { return; } - const { flags, hdcpLevel, nextAutoLevel } = errorAction; + const { flags } = errorAction; + const nextAutoLevel = errorAction.nextAutoLevel; switch (flags) { case ErrorActionFlags.None: this.switchLevel(data, nextAutoLevel); break; - case ErrorActionFlags.MoveAllAlternatesMatchingHDCP: - if (hdcpLevel) { - hls.maxHdcpLevel = HdcpLevels[HdcpLevels.indexOf(hdcpLevel) - 1]; + case ErrorActionFlags.MoveAllAlternatesMatchingHDCP: { + const levelIndex = this.getVariantLevelIndex(data.frag); + const level = hls.levels[levelIndex]; + const restrictedHdcpLevel = (level as Level | undefined)?.attrs[ + 'HDCP-LEVEL' + ]; + errorAction.hdcpLevel = restrictedHdcpLevel; + if (restrictedHdcpLevel) { + hls.maxHdcpLevel = + HdcpLevels[HdcpLevels.indexOf(restrictedHdcpLevel) - 1]; errorAction.resolved = true; + this.warn( + `Restricting playback to HDCP-LEVEL of "${hls.maxHdcpLevel}" or lower`, + ); + break; + } + // Fallthrough when no HDCP-LEVEL attribute is found + } + // eslint-disable-next-line no-fallthrough + case ErrorActionFlags.MoveAllAlternatesMatchingKey: { + const levelKey = data.decryptdata; + if (levelKey) { + // Penalize all levels with key + const levels = this.hls.levels; + for (let i = levels.length; i--; ) { + if (this.variantHasKey(levels[i], levelKey)) { + this.log( + `Banned key found in level ${i} (${levels[i].bitrate}bps) or audio group "${levels[i].audioGroups?.join(',')}" (${data.frag?.type} fragment) ${arrayToHex(levelKey.keyId || [])}`, + ); + levels[i].fragmentError++; + levels[i].loadError++; + this.log(`Removing level ${i} with key error (${data.error})`); + this.hls.removeLevel(i); + } + } + if (levels.length) { + errorAction.resolved = true; + } } - this.warn( - `Restricting playback to HDCP-LEVEL of "${hls.maxHdcpLevel}" or lower`, - ); break; + } } // If not resolved by previous actions try to switch to next level if (!errorAction.resolved) { @@ -516,6 +566,9 @@ export default class ErrorController const levels = this.hls.levels; for (let i = levels.length; i--; ) { if (levels[i][`${data.sourceBufferName}Codec`] === codec) { + this.log( + `Removing level ${i} for ${data.details} ("${codec}" not supported)`, + ); this.hls.removeLevel(i); } } diff --git a/src/hls.ts b/src/hls.ts index 718005282f7..b0df771dac2 100644 --- a/src/hls.ts +++ b/src/hls.ts @@ -233,7 +233,7 @@ export default class Hls implements HlsEventEmitter { )); const id3TrackController = new ID3TrackController(this); - const keyLoader = new KeyLoader(this.config); + const keyLoader = new KeyLoader(this.config, this.logger); const streamController = (this.streamController = new StreamController( this, fragmentTracker, diff --git a/src/loader/key-loader.ts b/src/loader/key-loader.ts index 124e07bb20d..80f4e72b8c5 100644 --- a/src/loader/key-loader.ts +++ b/src/loader/key-loader.ts @@ -1,6 +1,8 @@ import { LoadError } from './fragment-loader'; import { ErrorDetails, ErrorTypes } from '../errors'; import { type Fragment, isMediaFragment } from '../loader/fragment'; +import { arrayToHex } from '../utils/hex'; +import { Logger } from '../utils/logger'; import { getKeySystemsForConfig, keySystemFormatToKeySystemDomain, @@ -20,6 +22,7 @@ import type { LoaderStats, PlaylistLevelType, } from '../types/loader'; +import type { ILogger } from '../utils/logger'; import type { KeySystemFormats } from '../utils/mediakeys-helper'; export interface KeyLoaderInfo { @@ -28,18 +31,19 @@ export interface KeyLoaderInfo { loader: Loader | null; mediaKeySessionContext: MediaKeySessionContext | null; } -export default class KeyLoader implements ComponentAPI { +export default class KeyLoader extends Logger implements ComponentAPI { private readonly config: HlsConfig; - public keyUriToKeyInfo: { [keyuri: string]: KeyLoaderInfo } = {}; + private keyIdToKeyInfo: { [keyId: string]: KeyLoaderInfo | undefined } = {}; public emeController: EMEController | null = null; - constructor(config: HlsConfig) { + constructor(config: HlsConfig, logger: ILogger) { + super('key-loader', logger); this.config = config; } abort(type?: PlaylistLevelType) { - for (const uri in this.keyUriToKeyInfo) { - const loader = this.keyUriToKeyInfo[uri].loader; + for (const id in this.keyIdToKeyInfo) { + const loader = this.keyIdToKeyInfo[id]!.loader; if (loader) { if (type && type !== loader.context?.frag.type) { return; @@ -50,27 +54,27 @@ export default class KeyLoader implements ComponentAPI { } detach() { - for (const uri in this.keyUriToKeyInfo) { - const keyInfo = this.keyUriToKeyInfo[uri]; + for (const id in this.keyIdToKeyInfo) { + const keyInfo = this.keyIdToKeyInfo[id]!; // Remove cached EME keys on detach if ( keyInfo.mediaKeySessionContext || keyInfo.decryptdata.isCommonEncryption ) { - delete this.keyUriToKeyInfo[uri]; + delete this.keyIdToKeyInfo[id]; } } } destroy() { this.detach(); - for (const uri in this.keyUriToKeyInfo) { - const loader = this.keyUriToKeyInfo[uri].loader; + for (const id in this.keyIdToKeyInfo) { + const loader = this.keyIdToKeyInfo[id]!.loader; if (loader) { loader.destroy(); } } - this.keyUriToKeyInfo = {}; + this.keyIdToKeyInfo = {}; } createKeyLoadError( @@ -185,22 +189,23 @@ export default class KeyLoader implements ComponentAPI { ), ); } - let keyInfo = this.keyUriToKeyInfo[uri]; + const id = getKeyId(decryptdata); + let keyInfo = this.keyIdToKeyInfo[id]; if (keyInfo?.decryptdata.key) { decryptdata.key = keyInfo.decryptdata.key; return Promise.resolve({ frag, keyInfo }); } - // Return key load promise as long as it does not have a mediakey session with an unusable key status + // Return key load promise once it has a mediakey session with an usable key status if (keyInfo?.keyLoadPromise) { - switch (keyInfo.mediaKeySessionContext?.keyStatus) { - case undefined: - case 'status-pending': + const keyStatus = keyInfo.mediaKeySessionContext?.keyStatus; + switch (keyStatus) { case 'usable': case 'usable-in-future': return keyInfo.keyLoadPromise.then((keyLoadedData) => { // Return the correct fragment with updated decryptdata key and loaded keyInfo - decryptdata.key = keyLoadedData.keyInfo.decryptdata.key; + const { keyInfo } = keyLoadedData; + decryptdata.key = keyInfo.decryptdata.key; return { frag, keyInfo }; }); } @@ -209,7 +214,11 @@ export default class KeyLoader implements ComponentAPI { } // Load the key or return the loading promise - keyInfo = this.keyUriToKeyInfo[uri] = { + this.log( + `Loading key ${arrayToHex(decryptdata.keyId || [])} from ${frag.type} ${frag.level}`, + ); + + keyInfo = this.keyIdToKeyInfo[id] = { decryptdata, keyLoadPromise: null, loader: null, @@ -217,7 +226,6 @@ export default class KeyLoader implements ComponentAPI { }; switch (decryptdata.method) { - case 'ISO-23001-7': case 'SAMPLE-AES': case 'SAMPLE-AES-CENC': case 'SAMPLE-AES-CTR': @@ -248,18 +256,19 @@ export default class KeyLoader implements ComponentAPI { if (this.emeController && this.config.emeEnabled) { const keySessionContextPromise = this.emeController.loadKey(keyLoadedData); - if (keySessionContextPromise) { - return (keyInfo.keyLoadPromise = keySessionContextPromise.then( - (keySessionContext) => { - keyInfo.mediaKeySessionContext = keySessionContext; - return keyLoadedData; - }, - )).catch((error) => { - // Remove promise for license renewal or retry - keyInfo.keyLoadPromise = null; - throw error; - }); - } + return (keyInfo.keyLoadPromise = keySessionContextPromise.then( + (keySessionContext) => { + keyInfo.mediaKeySessionContext = keySessionContext; + return keyLoadedData; + }, + )).catch((error) => { + // Remove promise for license renewal or retry + keyInfo.keyLoadPromise = null; + if (error.data) { + error.data.frag = frag; + } + throw error; + }); } return Promise.resolve(keyLoadedData); } @@ -298,7 +307,8 @@ export default class KeyLoader implements ComponentAPI { networkDetails: any, ) => { const { frag, keyInfo, url: uri } = context; - if (!frag.decryptdata || keyInfo !== this.keyUriToKeyInfo[uri]) { + const id = getKeyId(keyInfo.decryptdata) || uri; + if (!frag.decryptdata || keyInfo !== this.keyIdToKeyInfo[id]) { return reject( this.createKeyLoadError( frag, @@ -383,9 +393,18 @@ export default class KeyLoader implements ComponentAPI { frag.keyLoader = null; keyInfo.loader = null; } - delete this.keyUriToKeyInfo[uri]; + const id = getKeyId(keyInfo.decryptdata) || uri; + delete this.keyIdToKeyInfo[id]; if (loader) { loader.destroy(); } } } + +function getKeyId(decryptdata: LevelKey) { + const keyId = decryptdata.keyId; + if (keyId) { + return arrayToHex(keyId); + } + return decryptdata.uri; +} diff --git a/src/loader/level-details.ts b/src/loader/level-details.ts index ed2d7aba8a0..3c954fe00c2 100644 --- a/src/loader/level-details.ts +++ b/src/loader/level-details.ts @@ -1,7 +1,9 @@ import type { DateRange } from './date-range'; import type { Fragment, MediaFragment, Part } from './fragment'; +import type { LevelKey } from './level-key'; import type { VariableMap } from '../types/level'; import type { AttrList } from '../utils/attr-list'; +import type { KeySystemFormats } from '../utils/mediakeys-helper'; const DEFAULT_TARGET_DURATION = 10; @@ -88,6 +90,17 @@ export class LevelDetails { } } + hasKey(levelKey: LevelKey): boolean { + return this.encryptedFragments.some((frag) => { + let decryptdata = frag.decryptdata; + if (!decryptdata) { + frag.setKeyFormat(levelKey.keyFormat as KeySystemFormats); + decryptdata = frag.decryptdata; + } + return !!decryptdata && levelKey.matches(decryptdata); + }); + } + get hasProgramDateTime(): boolean { if (this.fragments.length) { return Number.isFinite( diff --git a/src/loader/level-key.ts b/src/loader/level-key.ts index 49d4dd925f7..0da3ae32ce0 100644 --- a/src/loader/level-key.ts +++ b/src/loader/level-key.ts @@ -1,3 +1,4 @@ +import { arrayValuesMatch, optionalArrayValuesMatch } from '../utils/arrays'; import { isFullSegmentEncryption } from '../utils/encryption-methods-util'; import { hexToArrayBuffer } from '../utils/hex'; import { convertDataUriToArrayBytes } from '../utils/keysystem-util'; @@ -63,8 +64,9 @@ export class LevelKey implements DecryptData { key.method === this.method && key.encrypted === this.encrypted && key.keyFormat === this.keyFormat && - key.keyFormatVersions.join(',') === this.keyFormatVersions.join(',') && - key.iv?.join(',') === this.iv?.join(',') + arrayValuesMatch(key.keyFormatVersions, this.keyFormatVersions) && + optionalArrayValuesMatch(key.iv, this.iv) && + optionalArrayValuesMatch(key.keyId, this.keyId) ); } @@ -84,12 +86,9 @@ export class LevelKey implements DecryptData { case KeySystemFormats.PLAYREADY: case KeySystemFormats.CLEARKEY: return ( - [ - 'ISO-23001-7', - 'SAMPLE-AES', - 'SAMPLE-AES-CENC', - 'SAMPLE-AES-CTR', - ].indexOf(this.method) !== -1 + ['SAMPLE-AES', 'SAMPLE-AES-CENC', 'SAMPLE-AES-CTR'].indexOf( + this.method, + ) !== -1 ); } } diff --git a/src/types/events.ts b/src/types/events.ts index 975cf49656f..d3f5943dd93 100644 --- a/src/types/events.ts +++ b/src/types/events.ts @@ -319,6 +319,7 @@ export interface ErrorData { bytes?: number; chunkMeta?: ChunkMetadata; context?: PlaylistLoaderContext; + decryptdata?: LevelKey; event?: keyof HlsListeners | 'demuxerWorker'; frag?: Fragment; part?: Part | null; diff --git a/src/utils/arrays.ts b/src/utils/arrays.ts new file mode 100644 index 00000000000..d2272794127 --- /dev/null +++ b/src/utils/arrays.ts @@ -0,0 +1,22 @@ +export function arrayValuesMatch( + a: (string | number)[] | Uint8Array, + b: (string | number)[] | Uint8Array, +): boolean { + if (a.length === b.length) { + return !a.some((value: string | number, i: number) => value !== b[i]); + } + return false; +} + +export function optionalArrayValuesMatch( + a: (string | number)[] | Uint8Array | null | undefined, + b: (string | number)[] | Uint8Array | null | undefined, +): boolean { + if (!a && !b) { + return true; + } + if (!a || !b) { + return false; + } + return arrayValuesMatch(a, b); +} diff --git a/src/utils/hex.ts b/src/utils/hex.ts index a7db9633fea..2bc66b55132 100644 --- a/src/utils/hex.ts +++ b/src/utils/hex.ts @@ -2,20 +2,18 @@ * hex dump helper class */ -const Hex = { - hexDump: function (array: Uint8Array) { - let str = ''; - for (let i = 0; i < array.length; i++) { - let h = array[i].toString(16); - if (h.length < 2) { - h = '0' + h; - } - - str += h; +export function arrayToHex(array: Uint8Array | number[]) { + let str = ''; + for (let i = 0; i < array.length; i++) { + let h = array[i].toString(16); + if (h.length < 2) { + h = '0' + h; } - return str; - }, -}; + + str += h; + } + return str; +} export function hexToArrayBuffer(str: string): ArrayBuffer { return Uint8Array.from( @@ -27,4 +25,8 @@ export function hexToArrayBuffer(str: string): ArrayBuffer { ).buffer; } +const Hex = { + hexDump: arrayToHex, +}; + export default Hex; diff --git a/src/utils/mediakeys-helper.ts b/src/utils/mediakeys-helper.ts index c32f2e9bce0..5d9f5e2f64e 100755 --- a/src/utils/mediakeys-helper.ts +++ b/src/utils/mediakeys-helper.ts @@ -167,13 +167,14 @@ function createMediaKeySystemConfigurations( } export function isPersistentSessionType( - drmSystemOptions: DRMSystemOptions, + drmSystemOptions: DRMSystemOptions | undefined, ): boolean { return ( - drmSystemOptions.sessionType === 'persistent-license' || - !!drmSystemOptions.sessionTypes?.some( - (type) => type === 'persistent-license', - ) + !!drmSystemOptions && + (drmSystemOptions.sessionType === 'persistent-license' || + !!drmSystemOptions.sessionTypes?.some( + (type) => type === 'persistent-license', + )) ); } diff --git a/src/utils/mp4-tools.ts b/src/utils/mp4-tools.ts index 5becb00d177..86d536f7047 100644 --- a/src/utils/mp4-tools.ts +++ b/src/utils/mp4-tools.ts @@ -1,5 +1,5 @@ import { utf8ArrayToStr } from '@svta/common-media-library/utils/utf8ArrayToStr'; -import Hex from './hex'; +import { arrayToHex } from './hex'; import { ElementaryStreamTypes } from '../loader/fragment'; import { logger } from '../utils/logger'; import type { KeySystemIds } from './mediakeys-helper'; @@ -596,7 +596,7 @@ export function patchEncyptionData( logger.log( `[eme] Patching keyId in 'enc${ isAudio ? 'a' : 'v' - }>sinf>>tenc' box: ${Hex.hexDump(tencKeyId)} -> ${Hex.hexDump( + }>sinf>>tenc' box: ${arrayToHex(tencKeyId)} -> ${arrayToHex( keyId, )}`, ); @@ -1401,7 +1401,7 @@ function parsePssh(view: DataView): PsshData | PsshInvalidResult { return { offset, size }; } const buffer = view.buffer; - const systemId = Hex.hexDump( + const systemId = arrayToHex( new Uint8Array(buffer, offset + 12, 16), ) as KeySystemIds; diff --git a/tests/unit/controller/audio-stream-controller.ts b/tests/unit/controller/audio-stream-controller.ts index 2076463f9a3..d1c24b81d68 100644 --- a/tests/unit/controller/audio-stream-controller.ts +++ b/tests/unit/controller/audio-stream-controller.ts @@ -139,7 +139,7 @@ describe('AudioStreamController', function () { sandbox = sinon.createSandbox(); hls = new Hls(); fragmentTracker = new FragmentTracker(hls); - keyLoader = new KeyLoader(hlsDefaultConfig); + keyLoader = new KeyLoader(hlsDefaultConfig, hls.logger); audioStreamController = new AudioStreamController( hls, fragmentTracker, diff --git a/tests/unit/controller/base-stream-controller.ts b/tests/unit/controller/base-stream-controller.ts index 40a3651795c..52aaef8e679 100644 --- a/tests/unit/controller/base-stream-controller.ts +++ b/tests/unit/controller/base-stream-controller.ts @@ -42,7 +42,7 @@ describe('BaseStreamController', function () { baseStreamController = new BaseStreamController( hls, fragmentTracker, - new KeyLoader(hlsDefaultConfig), + new KeyLoader(hlsDefaultConfig, hls.logger), ) as unknown as BaseStreamControllerTestable; bufferInfo = { len: 1, diff --git a/tests/unit/controller/eme-controller.ts b/tests/unit/controller/eme-controller.ts index b94509a3005..d84bb54d215 100644 --- a/tests/unit/controller/eme-controller.ts +++ b/tests/unit/controller/eme-controller.ts @@ -61,7 +61,7 @@ class MediaKeySessionMock extends EventEmitter { messageType: 'license-request', message: new Uint8Array(0), }); - this.keyStatuses.set(new Uint8Array(0), 'usable'); + this.keyStatuses.set(new Uint8Array(16), 'usable'); this.emit('keystatuseschange', {}); }); } @@ -373,51 +373,51 @@ describe('EMEController', function () { drmSystems: { 'com.apple.fps': { serverCertificateUrl: 'https://example.com/certificate.cer', + licenseUrl: 'https://example.com/license', }, }, }); - let xhrInstance; sinonFakeXMLHttpRequestStatic.onCreate = ( xhr: sinon.SinonFakeXMLHttpRequest, ) => { - xhrInstance = xhr; - Promise.resolve().then(() => { - (xhr as any).response = new Uint8Array(); - xhr.respond(200, {}, ''); - }); + self.setTimeout(() => { + xhr.respond(200, {}, 'abcdef'); + }, 0); }; emeController.onMediaAttached(Events.MEDIA_ATTACHED, { media: media as any as HTMLMediaElement, }); - emeController.loadKey({ - frag: {}, - keyInfo: { - decryptdata: { - encrypted: true, - method: 'SAMPLE-AES', - uri: 'data://key-uri', - keyFormatVersions: [1], - keyId: new Uint8Array(16), - pssh: new Uint8Array(16), + return emeController + .loadKey({ + frag: {}, + keyInfo: { + decryptdata: { + encrypted: true, + method: 'SAMPLE-AES', + uri: 'data://key-uri', + keyFormatVersions: [1], + keyId: new Uint8Array(16), + pssh: new Uint8Array(16), + }, }, - }, - } as any); - - expect( - emeController.keyIdToKeySessionPromise[ - '00000000000000000000000000000000' - ], - ).to.be.a('Promise'); - return emeController.keyIdToKeySessionPromise[ - '00000000000000000000000000000000' - ].finally(() => { - expect(mediaKeysSetServerCertificateSpy).to.have.been.calledOnce; - expect(mediaKeysSetServerCertificateSpy).to.have.been.calledWith( - xhrInstance.response, - ); - }); + } as any) + .then(() => { + expect( + emeController.keyIdToKeySessionPromise[ + '00000000000000000000000000000000' + ], + ).to.be.a('Promise'); + return emeController.keyIdToKeySessionPromise[ + '00000000000000000000000000000000' + ].finally(() => { + expect(mediaKeysSetServerCertificateSpy).to.have.been.calledOnce; + expect(mediaKeysSetServerCertificateSpy).to.have.been.calledWith( + sinon.match({ byteLength: 6 }), + ); + }); + }); }); it('should fetch the server certificate and trigger update failed error', function () { diff --git a/tests/unit/controller/subtitle-stream-controller.ts b/tests/unit/controller/subtitle-stream-controller.ts index d0489781814..b70ae6371c8 100644 --- a/tests/unit/controller/subtitle-stream-controller.ts +++ b/tests/unit/controller/subtitle-stream-controller.ts @@ -42,7 +42,7 @@ describe('SubtitleStreamController', function () { hls = new Hls({}); mediaMock.currentTime = 0; fragmentTracker = new FragmentTracker(hls); - keyLoader = new KeyLoader(hls.config); + keyLoader = new KeyLoader(hls.config, hls.logger); subtitleStreamController = new SubtitleStreamController( hls, From b2d73913c1cbb8dd9cf53370582bd00200b62100 Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Fri, 22 Aug 2025 16:13:19 -0700 Subject: [PATCH 10/64] Defer segment requests when network connection is lost (#7476) * Wait to retry fragment request when `navigator.onLine` is false (uses polling for Firefox where "online" event never fires) Fixes #7471 * Only treat no-alternate segment request failures as gaps in live playlists (#7464, #7410) * Retry once per seeking out of current fragment range (even when offline) * Do not exhaust retries in tick loop while seeking * Only schedule immediate tick on seeking when buffer is empty and state is idle (#7472) --- api-extractor/report/hls.js.api.md | 4 +- src/controller/audio-stream-controller.ts | 10 +-- src/controller/base-stream-controller.ts | 76 ++++++++++++++++------- src/controller/stream-controller.ts | 12 +--- src/utils/error-helper.ts | 8 ++- 5 files changed, 65 insertions(+), 45 deletions(-) diff --git a/api-extractor/report/hls.js.api.md b/api-extractor/report/hls.js.api.md index 5511ee3ed90..efcc3d551ee 100644 --- a/api-extractor/report/hls.js.api.md +++ b/api-extractor/report/hls.js.api.md @@ -392,6 +392,8 @@ export class BaseStreamController extends TaskLoop implements NetworkComponentAP // (undocumented) protected checkLiveUpdate(details: LevelDetails): void; // (undocumented) + protected checkRetryDate(): void; + // (undocumented) protected clearTrackerIfNeeded(frag: Fragment): void; // (undocumented) protected config: HlsConfig; @@ -528,8 +530,6 @@ export class BaseStreamController extends TaskLoop implements NetworkComponentAP // (undocumented) protected resetLoadingState(): void; // (undocumented) - protected resetStartWhenNotLoaded(level: Level | null): void; - // (undocumented) protected resetTransmuxer(): void; // (undocumented) protected resetWhenMissingContext(chunkMeta: ChunkMetadata | Fragment): void; diff --git a/src/controller/audio-stream-controller.ts b/src/controller/audio-stream-controller.ts index 53965814b94..d4cc58c5c70 100644 --- a/src/controller/audio-stream-controller.ts +++ b/src/controller/audio-stream-controller.ts @@ -255,15 +255,7 @@ class AudioStreamController break; } case State.FRAG_LOADING_WAITING_RETRY: { - const now = performance.now(); - const retryDate = this.retryDate; - // if current time is gt than retryDate, or if media seeking let's switch to IDLE state to retry loading - if (!retryDate || now >= retryDate || this.media?.seeking) { - const { levels, trackId } = this; - this.log('RetryDate reached, switch back to IDLE state'); - this.resetStartWhenNotLoaded(levels?.[trackId] || null); - this.state = State.IDLE; - } + this.checkRetryDate(); break; } case State.WAITING_INIT_PTS: { diff --git a/src/controller/base-stream-controller.ts b/src/controller/base-stream-controller.ts index 5ce214dd3b2..a26c14380be 100644 --- a/src/controller/base-stream-controller.ts +++ b/src/controller/base-stream-controller.ts @@ -24,7 +24,11 @@ import { getAesModeFromFullSegmentMethod, isFullSegmentEncryption, } from '../utils/encryption-methods-util'; -import { getRetryDelay } from '../utils/error-helper'; +import { getRetryDelay, offlineHttpStatus } from '../utils/error-helper'; +import { + addEventListener, + removeEventListener, +} from '../utils/event-listener-helper'; import { findPart, getFragmentWithSN, @@ -284,10 +288,8 @@ export default class BaseStreamController data: MediaAttachedData, ) { const media = (this.media = this.mediaBuffer = data.media); - media.removeEventListener('seeking', this.onMediaSeeking); - media.removeEventListener('ended', this.onMediaEnded); - media.addEventListener('seeking', this.onMediaSeeking); - media.addEventListener('ended', this.onMediaEnded); + addEventListener(media, 'seeking', this.onMediaSeeking); + addEventListener(media, 'ended', this.onMediaEnded); const config = this.config; if (this.levels && config.autoStartLoad && this.state === State.STOPPED) { this.startLoad(config.startPosition); @@ -309,8 +311,8 @@ export default class BaseStreamController } // remove video listeners - media.removeEventListener('seeking', this.onMediaSeeking); - media.removeEventListener('ended', this.onMediaEnded); + removeEventListener(media, 'seeking', this.onMediaSeeking); + removeEventListener(media, 'ended', this.onMediaEnded); if (this.keyLoader && !transferringMedia) { this.keyLoader.detach(); @@ -424,8 +426,10 @@ export default class BaseStreamController } } - // Async tick to speed up processing - this.tickImmediate(); + if (noFowardBuffer && this.state === State.IDLE) { + // Async tick to speed up processing + this.tickImmediate(); + } }; protected onMediaEnded = () => { @@ -1883,27 +1887,41 @@ export default class BaseStreamController } // keep retrying until the limit will be reached const errorAction = data.errorAction; - const { action, flags, retryCount = 0, retryConfig } = errorAction || {}; - const couldRetry = !!errorAction && !!retryConfig; + if (!errorAction) { + this.state = State.ERROR; + return; + } + const { action, flags, retryCount = 0, retryConfig } = errorAction; + const couldRetry = !!retryConfig; const retry = couldRetry && action === NetworkErrorAction.RetryRequest; const noAlternate = couldRetry && !errorAction.resolved && flags === ErrorActionFlags.MoveAllAlternatesMatchingHost; - const httpStatus = data.response?.code || 0; + const live = this.hls.latestLevelDetails?.live; if ( !retry && noAlternate && isMediaFragment(frag) && !frag.endList && - httpStatus !== 0 + live ) { this.resetFragmentErrors(filterType); this.treatAsGap(frag); errorAction.resolved = true; } else if ((retry || noAlternate) && retryCount < retryConfig.maxNumRetry) { - this.resetStartWhenNotLoaded(this.levelLastLoaded); + const offlineStatus = offlineHttpStatus(data.response?.code); const delay = getRetryDelay(retryConfig, retryCount); + this.resetStartWhenNotLoaded(); + this.retryDate = self.performance.now() + delay; + this.state = State.FRAG_LOADING_WAITING_RETRY; + errorAction.resolved = true; + if (offlineStatus) { + this.log(`Waiting for connection (offline)`); + this.retryDate = Infinity; + data.reason = 'offline'; + return; + } this.warn( `Fragment ${frag.sn} of ${filterType} ${frag.level} errored with ${ data.details @@ -1911,10 +1929,7 @@ export default class BaseStreamController retryConfig.maxNumRetry } in ${delay}ms`, ); - errorAction.resolved = true; - this.retryDate = self.performance.now() + delay; - this.state = State.FRAG_LOADING_WAITING_RETRY; - } else if (retryConfig && errorAction) { + } else if (retryConfig) { this.resetFragmentErrors(filterType); if (retryCount < retryConfig.maxNumRetry) { // Network retry is skipped when level switch is preferred @@ -1939,6 +1954,24 @@ export default class BaseStreamController this.tickImmediate(); } + protected checkRetryDate() { + const now = self.performance.now(); + const retryDate = this.retryDate; + // if current time is gt than retryDate, or if media seeking let's switch to IDLE state to retry loading + const waitingForConnection = retryDate === Infinity; + if ( + !retryDate || + now >= retryDate || + (waitingForConnection && !offlineHttpStatus(0)) + ) { + if (waitingForConnection) { + this.log(`Connection restored (online)`); + } + this.resetStartWhenNotLoaded(); + this.state = State.IDLE; + } + } + protected reduceLengthAndFlushBuffer(data: ErrorData): boolean { // if in appending state if (this.state === State.PARSING || this.state === State.PARSED) { @@ -2018,11 +2051,12 @@ export default class BaseStreamController } } - protected resetStartWhenNotLoaded(level: Level | null): void { + private resetStartWhenNotLoaded() { // if loadedmetadata is not set, it means that first frag request failed // in that case, reset startFragRequested flag if (!this.hls.hasEnoughToStart) { this.startFragRequested = false; + const level = this.levelLastLoaded; const details = level ? level.details : null; if (details?.live) { // Update the start position and return to IDLE to recover live start @@ -2041,7 +2075,7 @@ export default class BaseStreamController `Loading context changed while buffering sn ${chunkMeta.sn} of ${this.playlistLabel()} ${chunkMeta.level === -1 ? '' : chunkMeta.level}. This chunk will not be buffered.`, ); this.removeUnbufferedFrags(); - this.resetStartWhenNotLoaded(this.levelLastLoaded); + this.resetStartWhenNotLoaded(); this.resetLoadingState(); } @@ -2176,7 +2210,7 @@ export default class BaseStreamController this.transmuxer.destroy(); this.transmuxer = null; } - this.resetStartWhenNotLoaded(this.levelLastLoaded); + this.resetStartWhenNotLoaded(); this.resetLoadingState(); } } diff --git a/src/controller/stream-controller.ts b/src/controller/stream-controller.ts index 750cb374c26..f58d05fa040 100644 --- a/src/controller/stream-controller.ts +++ b/src/controller/stream-controller.ts @@ -215,17 +215,7 @@ export default class StreamController break; } case State.FRAG_LOADING_WAITING_RETRY: - { - const now = self.performance.now(); - const retryDate = this.retryDate; - // if current time is gt than retryDate, or if media seeking let's switch to IDLE state to retry loading - if (!retryDate || now >= retryDate || this.media?.seeking) { - const { levels, level } = this; - const currentLevel = levels?.[level]; - this.resetStartWhenNotLoaded(currentLevel || null); - this.state = State.IDLE; - } - } + this.checkRetryDate(); break; default: break; diff --git a/src/utils/error-helper.ts b/src/utils/error-helper.ts index dc1eb18aa5d..b42ac693f83 100644 --- a/src/utils/error-helper.ts +++ b/src/utils/error-helper.ts @@ -71,10 +71,14 @@ export function shouldRetry( : retry; } -export function retryForHttpStatus(httpStatus: number | undefined) { +export function retryForHttpStatus(httpStatus: number | undefined): boolean { // Do not retry on status 4xx, status 0 (CORS error), or undefined (decrypt/gap/parse error) return ( - (httpStatus === 0 && navigator.onLine === false) || + offlineHttpStatus(httpStatus) || (!!httpStatus && (httpStatus < 400 || httpStatus > 499)) ); } + +export function offlineHttpStatus(httpStatus: number | undefined): boolean { + return httpStatus === 0 && navigator.onLine === false; +} From 82e46956dd6b6a2a26fe84a883cdb019391f0b66 Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Sun, 24 Aug 2025 18:42:10 -0700 Subject: [PATCH 11/64] Improve functional test logs (#7481) --- tests/functional/auto/setup.js | 75 ++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/tests/functional/auto/setup.js b/tests/functional/auto/setup.js index 0180fe2aa59..b0244d62a7f 100644 --- a/tests/functional/auto/setup.js +++ b/tests/functional/auto/setup.js @@ -492,7 +492,7 @@ function getPageURLComponents() { }; } -describe(`testing hls.js playback in the browser on "${browserDescription}"`, function () { +describe(`Testing hls.js playback in ${browserConfig.name} ${browserConfig.version} ${browserConfig.platform ? browserConfig.platform : ''}`, function () { const failedUrls = {}; before(async function () { @@ -696,46 +696,49 @@ ${data.logs} ) { return; } - it( - `should receive video loadeddata event for ${stream.description}`, - testLoadedData.bind(null, url, config) - ); - if (stream.startSeek && !HlsjsLightBuild) { + describe(`${index + 1}. [${name}]: ${stream.description} (${stream.url})`, function () { it( - `seek back to start and play for ${stream.description}`, - testSeekBackToStart.bind(null, url, config) + `should receive video loadeddata event`, + testLoadedData.bind(null, url, config) ); - } - if (stream.abr) { - it( - `should "smooth switch" to highest level and still play after 2s for ${stream.description}`, - testSmoothSwitch.bind(null, url, config) - ); - } + if (stream.startSeek && !HlsjsLightBuild) { + it( + `seek back to start and play`, + testSeekBackToStart.bind(null, url, config) + ); + } - if (stream.live) { - it( - `should seek near the end and receive video seeked event for ${stream.description}`, - testSeekOnLive.bind(null, url, config) - ); - } else if (!HlsjsLightBuild) { - it( - `should buffer up to maxBufferLength or video.duration for ${stream.description}`, - testIdleBufferLength.bind(null, url, config) - ); - it( - `should play ${stream.description}`, - testIsPlayingVOD.bind(null, url, config) - ); + if (stream.abr) { + it( + `should "smooth switch" to highest level and still play after 2s`, + testSmoothSwitch.bind(null, url, config) + ); + } - it( - `should seek 3s from end and receive video ended event for ${stream.description} with 2 or less buffered ranges`, - testSeekOnVOD.bind(null, url, config) - ); - // TODO: Seeking to or past VOD duration should result in the video ending - // it(`should seek on end and receive video ended event for ${stream.description}`, testSeekEndVOD.bind(null, url)); - } + if (stream.live) { + it( + `should seek near the end and receive video seeked event`, + testSeekOnLive.bind(null, url, config) + ); + } else if (!HlsjsLightBuild) { + it( + `should buffer up to maxBufferLength or video.duration`, + testIdleBufferLength.bind(null, url, config) + ); + it( + `should play ${stream.description}`, + testIsPlayingVOD.bind(null, url, config) + ); + + it( + `should seek 3s from end and receive video ended event with 2 or less buffered ranges`, + testSeekOnVOD.bind(null, url, config) + ); + // TODO: Seeking to or past VOD duration should result in the video ending + // it(`should seek on end and receive video ended event`, testSeekEndVOD.bind(null, url)); + } + }); }); }); From 178333c02286cd8493b9d2077db845959c306657 Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Sun, 24 Aug 2025 19:10:26 -0700 Subject: [PATCH 12/64] Add ESLint syntax rules to restrict async syntax, no-floating-promises, and no-misused-promises (#7480) ESlint error messages explain reasoning --- .eslintrc.js | 45 +++++++++++--- src/controller/abr-controller.ts | 74 ++++++++++++----------- src/controller/buffer-controller.ts | 2 + src/controller/eme-controller.ts | 16 +++-- src/controller/level-controller.ts | 1 + src/controller/stream-controller.ts | 48 +++++++++------ src/demux/sample-aes.ts | 30 +++++---- src/utils/mediacapabilities-helper.ts | 37 +++++++----- tests/unit/controller/eme-controller.ts | 48 +++++++++------ tests/unit/controller/error-controller.ts | 10 ++- 10 files changed, 192 insertions(+), 119 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index efde38431df..b336a58425e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,3 +1,12 @@ +const asyncKeywordConstraintMsg = + 'The async keyword adds a `regenerator` dependency in the hls.js ES5 output not allowed in v1 due to bundle size constraints.'; +const selfVsWindowGlobalMsg = + 'Use `self` instead of `window` to access the global context everywhere (including workers).'; +const arrayFindCompatibilityMsg = + 'Usage of Array find methods is restricted for compatibility.'; +const arrayFindIndexCompatibilityMsg = + 'Usage of Array findIndex methods is restricted for compatibility.'; + module.exports = { env: { browser: true, commonjs: true, es6: true }, globals: { @@ -27,20 +36,13 @@ module.exports = { 2, { name: 'window', - message: - 'Use `self` instead of `window` to access the global context everywhere (including workers).', + message: selfVsWindowGlobalMsg, }, { name: 'SourceBuffer', message: 'Use `self.SourceBuffer`' }, { name: 'setTimeout', message: 'Use `self.setTimeout`' }, { name: 'setInterval', message: 'Use `self.setInterval`' }, ], - 'no-restricted-properties': [ - 2, - { property: 'findIndex' }, // Intended to block usage of Array.prototype.findIndex - { property: 'find' }, // Intended to block usage of Array.prototype.find - ], - 'import/first': 1, 'no-var': 1, 'no-empty': 1, @@ -67,6 +69,31 @@ module.exports = { 'no-unused-vars': 0, 'no-undef': 0, 'no-use-before-define': 'off', + 'no-restricted-syntax': [ + 'error', + { + selector: 'FunctionDeclaration[async=true]', + message: asyncKeywordConstraintMsg, + }, + { + selector: 'ArrowFunctionExpression[async=true]', + message: asyncKeywordConstraintMsg, + }, + { + selector: 'MethodDefinition[value.async=true]', + message: asyncKeywordConstraintMsg, + }, + { + selector: + 'MemberExpression[property.name="find"][object.type="Identifier"]', + message: arrayFindCompatibilityMsg, + }, + { + selector: + 'MemberExpression[property.name="findIndex"][object.type="Identifier"]', + message: arrayFindIndexCompatibilityMsg, + }, + ], 'import/order': [ 'warn', { @@ -98,6 +125,8 @@ module.exports = { '@typescript-eslint/consistent-type-imports': 'error', '@typescript-eslint/no-import-type-side-effects': 'error', '@typescript-eslint/no-restricted-imports': 'error', + '@typescript-eslint/no-floating-promises': 'error', + '@typescript-eslint/no-misused-promises': 'error', }, }, ], diff --git a/src/controller/abr-controller.ts b/src/controller/abr-controller.ts index ae1662f3c4d..afefdea4a44 100644 --- a/src/controller/abr-controller.ts +++ b/src/controller/abr-controller.ts @@ -845,43 +845,49 @@ class AbrController extends Logger implements AbrComponentAPI { mediaCapabilities, this.supportedCache, ); - levelInfo.supportedPromise.then((decodingInfo) => { - if (!this.hls) { - return; - } - levelInfo.supportedResult = decodingInfo; - const levels = this.hls.levels; - const index = levels.indexOf(levelInfo); - if (decodingInfo.error) { - this.warn( - `MediaCapabilities decodingInfo error: "${ - decodingInfo.error - }" for level ${index} ${stringify(decodingInfo)}`, - ); - } else if (!decodingInfo.supported) { - this.warn( - `Unsupported MediaCapabilities decodingInfo result for level ${index} ${stringify( - decodingInfo, - )}`, - ); - if (index > -1 && levels.length > 1) { - this.log(`Removing unsupported level ${index}`); - this.hls.removeLevel(index); - if (this.hls.loadLevel === -1) { - this.hls.nextLoadLevel = 0; + levelInfo.supportedPromise + .then((decodingInfo) => { + if (!this.hls) { + return; + } + levelInfo.supportedResult = decodingInfo; + const levels = this.hls.levels; + const index = levels.indexOf(levelInfo); + if (decodingInfo.error) { + this.warn( + `MediaCapabilities decodingInfo error: "${ + decodingInfo.error + }" for level ${index} ${stringify(decodingInfo)}`, + ); + } else if (!decodingInfo.supported) { + this.warn( + `Unsupported MediaCapabilities decodingInfo result for level ${index} ${stringify( + decodingInfo, + )}`, + ); + if (index > -1 && levels.length > 1) { + this.log(`Removing unsupported level ${index}`); + this.hls.removeLevel(index); + if (this.hls.loadLevel === -1) { + this.hls.nextLoadLevel = 0; + } } + } else if ( + decodingInfo.decodingInfoResults.some( + (info) => + info.smooth === false || info.powerEfficient === false, + ) + ) { + this.log( + `MediaCapabilities decodingInfo for level ${index} not smooth or powerEfficient: ${stringify(decodingInfo)}`, + ); } - } else if ( - decodingInfo.decodingInfoResults.some( - (info) => - info.smooth === false || info.powerEfficient === false, - ) - ) { - this.log( - `MediaCapabilities decodingInfo for level ${index} not smooth or powerEfficient: ${stringify(decodingInfo)}`, + }) + .catch((error) => { + this.warn( + `Error handling MediaCapabilities decodingInfo: ${error}`, ); - } - }); + }); } else { levelInfo.supportedResult = SUPPORTED_INFO_DEFAULT; } diff --git a/src/controller/buffer-controller.ts b/src/controller/buffer-controller.ts index 920e9220534..b13ec5edca5 100755 --- a/src/controller/buffer-controller.ts +++ b/src/controller/buffer-controller.ts @@ -343,6 +343,7 @@ export default class BufferController extends Logger implements ComponentAPI { : null; const trackCount = trackNames ? trackNames.length : 0; const mediaSourceOpenCallback = () => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises Promise.resolve().then(() => { if (this.media && this.mediaSourceOpenOrEnded) { this._onMediaSourceOpen(); @@ -417,6 +418,7 @@ transfer tracks: ${stringify(transferredTracks, (key, value) => (key === 'initSe >; this.sourceBuffers[sbIndex] = sbTuple as any; if (sb.updating && this.operationQueue) { + // eslint-disable-next-line @typescript-eslint/no-floating-promises this.operationQueue.prependBlocker(type); } this.trackSourceBuffer(type, track); diff --git a/src/controller/eme-controller.ts b/src/controller/eme-controller.ts index 08a06fb166b..b7e8488f787 100644 --- a/src/controller/eme-controller.ts +++ b/src/controller/eme-controller.ts @@ -357,6 +357,7 @@ class EMEController extends Logger implements ComponentAPI { } else { this.warn(`Could not renew expired session. Missing pssh initData.`); } + // eslint-disable-next-line @typescript-eslint/no-floating-promises this.removeSession(mediaKeySessionContext); } @@ -407,7 +408,7 @@ class EMEController extends Logger implements ComponentAPI { keySystemsToAttempt: KeySystems[], ): Promise { return new Promise((resolve, reject) => { - return this.getKeySystemSelectionPromise(keySystemsToAttempt) + this.getKeySystemSelectionPromise(keySystemsToAttempt) .then(({ keySystem }) => { const keySystemFormat = keySystemDomainToKeySystemFormat(keySystem); if (keySystemFormat) { @@ -774,7 +775,9 @@ class EMEController extends Logger implements ComponentAPI { }); } else if (messageType === 'license-release') { if (context.keySystem === KeySystems.FAIRPLAY) { + // eslint-disable-next-line @typescript-eslint/no-floating-promises this.updateKeySession(context, strToUtf8array('acknowledged')); + // eslint-disable-next-line @typescript-eslint/no-floating-promises this.removeSession(context); } } else { @@ -864,6 +867,7 @@ class EMEController extends Logger implements ComponentAPI { .then(() => keyUsablePromise) .catch((error) => { licenseStatus.removeAllListeners(); + // eslint-disable-next-line @typescript-eslint/no-floating-promises this.removeSession(context); throw error; }) @@ -1231,8 +1235,8 @@ class EMEController extends Logger implements ComponentAPI { } keySessionContext.licenseXhr = xhr; - this.setupLicenseXHR(xhr, url, keySessionContext, licenseChallenge).then( - ({ xhr, licenseChallenge }) => { + this.setupLicenseXHR(xhr, url, keySessionContext, licenseChallenge) + .then(({ xhr, licenseChallenge }) => { if (keySessionContext.keySystem == KeySystems.PLAYREADY) { licenseChallenge = this.unpackPlayReadyKeyMessage( xhr, @@ -1240,8 +1244,8 @@ class EMEController extends Logger implements ComponentAPI { ); } xhr.send(licenseChallenge); - }, - ); + }) + .catch(reject); }); } @@ -1407,7 +1411,7 @@ class EMEController extends Logger implements ComponentAPI { () => reject(new Error(`MediaKeySession.remove() timeout`)), 8000, ); - mediaKeysSession.remove().then(resolve); + mediaKeysSession.remove().then(resolve).catch(reject); }) : Promise.resolve(); return removePromise diff --git a/src/controller/level-controller.ts b/src/controller/level-controller.ts index 9b33b71b561..c2999e7eaf4 100644 --- a/src/controller/level-controller.ts +++ b/src/controller/level-controller.ts @@ -263,6 +263,7 @@ export default class LevelController extends BasePlaylistController { if (levels.length === 0) { // Dispatch error after MANIFEST_LOADED is done propagating + // eslint-disable-next-line @typescript-eslint/no-floating-promises Promise.resolve().then(() => { if (this.hls) { let message = 'no level with compatible codecs found in manifest'; diff --git a/src/controller/stream-controller.ts b/src/controller/stream-controller.ts index f58d05fa040..2e3402bc5cf 100644 --- a/src/controller/stream-controller.ts +++ b/src/controller/stream-controller.ts @@ -1185,26 +1185,34 @@ export default class StreamController private _loadBitrateTestFrag(fragment: Fragment, level: Level) { fragment.bitrateTest = true; - this._doFragLoad(fragment, level).then((data) => { - const { hls } = this; - const frag = data?.frag; - if (!frag || this.fragContextChanged(frag)) { - return; - } - level.fragmentError = 0; - this.state = State.IDLE; - this.startFragRequested = false; - this.bitrateTest = false; - const stats = frag.stats; - // Bitrate tests fragments are neither parsed nor buffered - stats.parsing.start = - stats.parsing.end = - stats.buffering.start = - stats.buffering.end = - self.performance.now(); - hls.trigger(Events.FRAG_LOADED, data as FragLoadedData); - frag.bitrateTest = false; - }); + this._doFragLoad(fragment, level) + .then((data) => { + const { hls } = this; + const frag = data?.frag; + if (!frag || this.fragContextChanged(frag)) { + return; + } + level.fragmentError = 0; + this.state = State.IDLE; + this.startFragRequested = false; + this.bitrateTest = false; + const stats = frag.stats; + // Bitrate tests fragments are neither parsed nor buffered + stats.parsing.start = + stats.parsing.end = + stats.buffering.start = + stats.buffering.end = + self.performance.now(); + hls.trigger(Events.FRAG_LOADED, data as FragLoadedData); + frag.bitrateTest = false; + }) + .catch((reason) => { + if (this.state === State.STOPPED || this.state === State.ERROR) { + return; + } + this.warn(reason); + this.resetFragmentLoading(fragment); + }); } private _handleTransmuxComplete(transmuxResult: TransmuxerResult) { diff --git a/src/demux/sample-aes.ts b/src/demux/sample-aes.ts index 5267bbccc97..b1ead44700b 100644 --- a/src/demux/sample-aes.ts +++ b/src/demux/sample-aes.ts @@ -56,14 +56,16 @@ class SampleAesDecrypter { encryptedData.byteOffset + encryptedData.length, ); - this.decryptBuffer(encryptedBuffer).then((decryptedBuffer: ArrayBuffer) => { - const decryptedData = new Uint8Array(decryptedBuffer); - curUnit.set(decryptedData, 16); + this.decryptBuffer(encryptedBuffer) + .then((decryptedBuffer: ArrayBuffer) => { + const decryptedData = new Uint8Array(decryptedBuffer); + curUnit.set(decryptedData, 16); - if (!this.decrypter.isSync()) { - this.decryptAacSamples(samples, sampleIndex + 1, callback); - } - }); + if (!this.decrypter.isSync()) { + this.decryptAacSamples(samples, sampleIndex + 1, callback); + } + }) + .catch(callback); } decryptAacSamples( @@ -136,13 +138,15 @@ class SampleAesDecrypter { const decodedData = discardEPB(curUnit.data); const encryptedData = this.getAvcEncryptedData(decodedData); - this.decryptBuffer(encryptedData.buffer).then((decryptedBuffer) => { - curUnit.data = this.getAvcDecryptedUnit(decodedData, decryptedBuffer); + this.decryptBuffer(encryptedData.buffer) + .then((decryptedBuffer) => { + curUnit.data = this.getAvcDecryptedUnit(decodedData, decryptedBuffer); - if (!this.decrypter.isSync()) { - this.decryptAvcSamples(samples, sampleIndex, unitIndex + 1, callback); - } - }); + if (!this.decrypter.isSync()) { + this.decryptAvcSamples(samples, sampleIndex, unitIndex + 1, callback); + } + }) + .catch(callback); } decryptAvcSamples( diff --git a/src/utils/mediacapabilities-helper.ts b/src/utils/mediacapabilities-helper.ts index a61181c25e8..280a30c9561 100644 --- a/src/utils/mediacapabilities-helper.ts +++ b/src/utils/mediacapabilities-helper.ts @@ -16,16 +16,26 @@ export type MediaDecodingInfo = { error?: Error; }; +// @ts-ignore +const supportedResult: MediaCapabilitiesDecodingInfo = { + supported: true, + powerEfficient: true, + smooth: true, + // keySystemAccess: null, +}; + +// @ts-ignore +const unsupportedResult: MediaCapabilitiesDecodingInfo = { + supported: false, + smooth: false, + powerEfficient: false, + // keySystemAccess: null, +}; + export const SUPPORTED_INFO_DEFAULT: MediaDecodingInfo = { supported: true, configurations: [] as MediaDecodingConfiguration[], - decodingInfoResults: [ - { - supported: true, - powerEfficient: true, - smooth: true, - }, - ], + decodingInfoResults: [supportedResult], } as const; export function getUnsupportedResult( @@ -35,13 +45,7 @@ export function getUnsupportedResult( return { supported: false, configurations, - decodingInfoResults: [ - { - supported: false, - smooth: false, - powerEfficient: false, - }, - ], + decodingInfoResults: [unsupportedResult], error, }; } @@ -113,7 +117,10 @@ export function getMediaDecodingInfoPromise( level: Level, audioTracksByGroup: AudioTracksByGroup, mediaCapabilities: MediaCapabilities | undefined, - cache: Record> = {}, + cache: Record< + string, + Promise | undefined + > = {}, ): Promise { const videoCodecs = level.videoCodec; if ((!videoCodecs && !level.audioCodec) || !mediaCapabilities) { diff --git a/tests/unit/controller/eme-controller.ts b/tests/unit/controller/eme-controller.ts index d84bb54d215..52aa6688a7f 100644 --- a/tests/unit/controller/eme-controller.ts +++ b/tests/unit/controller/eme-controller.ts @@ -469,17 +469,21 @@ describe('EMEController', function () { emeController.onMediaAttached(Events.MEDIA_ATTACHED, { media: media as any as HTMLMediaElement, }); - emeController.loadKey({ - frag: {}, - keyInfo: { - decryptdata: { - encrypted: true, - method: 'SAMPLE-AES', - uri: 'data://key-uri', - keyId: new Uint8Array(16), + emeController + .loadKey({ + frag: {}, + keyInfo: { + decryptdata: { + encrypted: true, + method: 'SAMPLE-AES', + uri: 'data://key-uri', + keyId: new Uint8Array(16), + }, }, - }, - } as any); + } as any) + .catch((error) => { + // expected? + }); expect( emeController.keyIdToKeySessionPromise[ @@ -545,17 +549,21 @@ describe('EMEController', function () { emeController.onMediaAttached(Events.MEDIA_ATTACHED, { media: media as any as HTMLMediaElement, }); - emeController.loadKey({ - frag: {}, - keyInfo: { - decryptdata: { - encrypted: true, - method: 'SAMPLE-AES', - uri: 'data://key-uri', - keyId: new Uint8Array(16), + emeController + .loadKey({ + frag: {}, + keyInfo: { + decryptdata: { + encrypted: true, + method: 'SAMPLE-AES', + uri: 'data://key-uri', + keyId: new Uint8Array(16), + }, }, - }, - } as any); + } as any) + .catch((error) => { + // expected? + }); expect( emeController.keyIdToKeySessionPromise[ diff --git a/tests/unit/controller/error-controller.ts b/tests/unit/controller/error-controller.ts index 9df0432a1cc..7ddef8323ac 100644 --- a/tests/unit/controller/error-controller.ts +++ b/tests/unit/controller/error-controller.ts @@ -266,9 +266,10 @@ describe('ErrorController Integration Tests', function () { hls.loadSource('noSegmentsVod.m3u8'); hls.stopLoad.should.have.been.calledOnce; return new Promise((resolve, reject) => { - hls.on(Events.ERROR, (event, data) => - Promise.resolve().then(() => resolve(data)), - ); + hls.on(Events.ERROR, (event, data) => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + Promise.resolve().then(() => resolve(data)); + }); hls.on(Events.LEVEL_LOADED, () => reject( new Error( @@ -713,6 +714,7 @@ segment.mp4 hls.on(Events.FRAG_LOADING, loadingEventCallback(server, timers)); hls.on(Events.ERROR, (event, data) => { errors.push(data); + // eslint-disable-next-line @typescript-eslint/no-floating-promises Promise.resolve().then(() => timers.tick(2000)); }); return new Promise((resolve, reject) => { @@ -823,6 +825,7 @@ segment.mp4 hls.on(Events.FRAG_LOADING, loadingEventCallback(server, timers)); hls.on(Events.ERROR, (event, data) => { errors.push(data); + // eslint-disable-next-line @typescript-eslint/no-floating-promises Promise.resolve().then(() => timers.tick(2000)); }); return new Promise((resolve, reject) => { @@ -968,6 +971,7 @@ function setupMockServerResponses(server: sinon.SinonFakeServer) { function loadingEventCallback(server, timers) { return (event, data) => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises Promise.resolve().then(() => { server.respond(); }); From 6ec4fff7487ca17964de3456141462fdd629208a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 28 Aug 2025 00:45:42 +0000 Subject: [PATCH 13/64] Update dependency lint-staged to v16.1.5 --- package-lock.json | 64 +++++++++++++++++++++++------------------------ package.json | 2 +- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/package-lock.json b/package-lock.json index caa67a406e0..045d31cd005 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,7 +58,7 @@ "karma-rollup-preprocessor": "github:jlmakes/karma-rollup-preprocessor#7a7268d91149307b3cf2888ee4e65ccd079955a3", "karma-sinon-chai": "2.0.2", "karma-sourcemap-loader": "0.4.0", - "lint-staged": "16.1.2", + "lint-staged": "16.1.5", "markdown-styles": "3.2.0", "micromatch": "4.0.8", "mocha": "11.7.1", @@ -9465,21 +9465,21 @@ } }, "node_modules/lint-staged": { - "version": "16.1.2", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.1.2.tgz", - "integrity": "sha512-sQKw2Si2g9KUZNY3XNvRuDq4UJqpHwF0/FQzZR2M7I5MvtpWvibikCjUVJzZdGE0ByurEl3KQNvsGetd1ty1/Q==", + "version": "16.1.5", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.1.5.tgz", + "integrity": "sha512-uAeQQwByI6dfV7wpt/gVqg+jAPaSp8WwOA8kKC/dv1qw14oGpnpAisY65ibGHUGDUv0rYaZ8CAJZ/1U8hUvC2A==", "dev": true, "dependencies": { - "chalk": "^5.4.1", + "chalk": "^5.5.0", "commander": "^14.0.0", "debug": "^4.4.1", "lilconfig": "^3.1.3", - "listr2": "^8.3.3", + "listr2": "^9.0.1", "micromatch": "^4.0.8", "nano-spawn": "^1.0.2", "pidtree": "^0.6.0", "string-argv": "^0.3.2", - "yaml": "^2.8.0" + "yaml": "^2.8.1" }, "bin": { "lint-staged": "bin/lint-staged.js" @@ -9492,9 +9492,9 @@ } }, "node_modules/lint-staged/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.0.tgz", + "integrity": "sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ==", "dev": true, "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" @@ -9527,9 +9527,9 @@ "dev": true }, "node_modules/listr2": { - "version": "8.3.3", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.3.3.tgz", - "integrity": "sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.2.tgz", + "integrity": "sha512-VVd7cS6W+vLJu2wmq4QmfVj14Iep7cz4r/OWNk36Aq5ZOY7G8/BfCrQFexcwB1OIxB3yERiePfE/REBjEFulag==", "dev": true, "dependencies": { "cli-truncate": "^4.0.0", @@ -9540,7 +9540,7 @@ "wrap-ansi": "^9.0.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/listr2/node_modules/ansi-regex": { @@ -14568,9 +14568,9 @@ "dev": true }, "node_modules/yaml": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", - "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", "dev": true, "bin": { "yaml": "bin.mjs" @@ -21262,27 +21262,27 @@ "dev": true }, "lint-staged": { - "version": "16.1.2", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.1.2.tgz", - "integrity": "sha512-sQKw2Si2g9KUZNY3XNvRuDq4UJqpHwF0/FQzZR2M7I5MvtpWvibikCjUVJzZdGE0ByurEl3KQNvsGetd1ty1/Q==", + "version": "16.1.5", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.1.5.tgz", + "integrity": "sha512-uAeQQwByI6dfV7wpt/gVqg+jAPaSp8WwOA8kKC/dv1qw14oGpnpAisY65ibGHUGDUv0rYaZ8CAJZ/1U8hUvC2A==", "dev": true, "requires": { - "chalk": "^5.4.1", + "chalk": "^5.5.0", "commander": "^14.0.0", "debug": "^4.4.1", "lilconfig": "^3.1.3", - "listr2": "^8.3.3", + "listr2": "^9.0.1", "micromatch": "^4.0.8", "nano-spawn": "^1.0.2", "pidtree": "^0.6.0", "string-argv": "^0.3.2", - "yaml": "^2.8.0" + "yaml": "^2.8.1" }, "dependencies": { "chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.0.tgz", + "integrity": "sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ==", "dev": true }, "debug": { @@ -21303,9 +21303,9 @@ } }, "listr2": { - "version": "8.3.3", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.3.3.tgz", - "integrity": "sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.2.tgz", + "integrity": "sha512-VVd7cS6W+vLJu2wmq4QmfVj14Iep7cz4r/OWNk36Aq5ZOY7G8/BfCrQFexcwB1OIxB3yERiePfE/REBjEFulag==", "dev": true, "requires": { "cli-truncate": "^4.0.0", @@ -24986,9 +24986,9 @@ "dev": true }, "yaml": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", - "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", "dev": true }, "yargs": { diff --git a/package.json b/package.json index 1643f6014da..2406d750f97 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,7 @@ "karma-rollup-preprocessor": "github:jlmakes/karma-rollup-preprocessor#7a7268d91149307b3cf2888ee4e65ccd079955a3", "karma-sinon-chai": "2.0.2", "karma-sourcemap-loader": "0.4.0", - "lint-staged": "16.1.2", + "lint-staged": "16.1.5", "markdown-styles": "3.2.0", "micromatch": "4.0.8", "mocha": "11.7.1", From 5d522cc4991e2789895675c76772daf54acc5313 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 28 Aug 2025 01:03:23 +0000 Subject: [PATCH 14/64] Update dependency wrangler to v4.31.0 --- package-lock.json | 202 +++++++++++++++++++++++----------------------- package.json | 2 +- 2 files changed, 102 insertions(+), 102 deletions(-) diff --git a/package-lock.json b/package-lock.json index 045d31cd005..981dbe3d9a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -75,7 +75,7 @@ "sinon-chai": "3.7.0", "typescript": "5.8.3", "url-toolkit": "2.2.5", - "wrangler": "4.26.1" + "wrangler": "4.31.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -1753,9 +1753,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-64": { - "version": "1.20250726.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250726.0.tgz", - "integrity": "sha512-SOpQqQ2blLY0io/vErve44vJC1M5i7RHuMBdrdEPIEtxiLBTdOOVp4nqZ3KchocxZjskgTc2N4N3b5hNYuKDGw==", + "version": "1.20250816.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250816.0.tgz", + "integrity": "sha512-yN1Rga4ufTdrJPCP4gEqfB47i1lWi3teY5IoeQbUuKnjnCtm4pZvXur526JzCmaw60Jx+AEWf5tizdwRd5hHBQ==", "cpu": [ "x64" ], @@ -1769,9 +1769,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-arm64": { - "version": "1.20250726.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250726.0.tgz", - "integrity": "sha512-I+TOQ+YQahxL/K7eS2GJzv5CZzSVaZoyqfB15Q71MT/+wyzPCaFDTt+fg3uXdwpaIQEMUfqFNpTQSqbKHAYNgA==", + "version": "1.20250816.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250816.0.tgz", + "integrity": "sha512-WyKPMQhbU+TTf4uDz3SA7ZObspg7WzyJMv/7J4grSddpdx2A4Y4SfPu3wsZleAOIMOAEVi0A1sYDhdltKM7Mxg==", "cpu": [ "arm64" ], @@ -1785,9 +1785,9 @@ } }, "node_modules/@cloudflare/workerd-linux-64": { - "version": "1.20250726.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250726.0.tgz", - "integrity": "sha512-WSCv4o2uOW6b++ROVazrEW+jjZdBqCmXmmt7uVVfvjVxlzoYVwK9IvV2IXe4gsJ99HG9I0YCa7AT743cZ7TNNg==", + "version": "1.20250816.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250816.0.tgz", + "integrity": "sha512-NWHOuFnVBaPRhLHw8kjPO9GJmc2P/CTYbnNlNm0EThyi57o/oDx0ldWLJqEHlrdEPOw7zEVGBqM/6M+V9agC6w==", "cpu": [ "x64" ], @@ -1801,9 +1801,9 @@ } }, "node_modules/@cloudflare/workerd-linux-arm64": { - "version": "1.20250726.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250726.0.tgz", - "integrity": "sha512-jNokAGL3EQqH+31b0dX8+tlbKdjt/0UtTLvgD1e+7bOD92lzjYMa/CixHyMIY/FVvhsN4TNqfiz4cqroABTlhg==", + "version": "1.20250816.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250816.0.tgz", + "integrity": "sha512-FR+/yhaWs7FhfC3GKsM3+usQVrGEweJ9qyh7p+R6HNwnobgKr/h5ATWvJ4obGJF6ZHHodgSe+gOSYR7fkJ1xAQ==", "cpu": [ "arm64" ], @@ -1817,9 +1817,9 @@ } }, "node_modules/@cloudflare/workerd-windows-64": { - "version": "1.20250726.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250726.0.tgz", - "integrity": "sha512-DiPTY63TNh6/ylvfutNQzYZi688x6NJDjQoqf5uiCp7xHweWx+GpVs42sZPeeXqCNvhm4dYjHjuigXJNh7t8Uw==", + "version": "1.20250816.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250816.0.tgz", + "integrity": "sha512-0lqClj2UMhFa8tCBiiX7Zhd5Bjp0V+X8oNBG6V6WsR9p9/HlIHAGgwRAM7aYkyG+8KC8xlbC89O2AXUXLpHx0g==", "cpu": [ "x64" ], @@ -10331,9 +10331,9 @@ } }, "node_modules/miniflare": { - "version": "4.20250726.0", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20250726.0.tgz", - "integrity": "sha512-7+/RQQ9dNsyGfR2XN2RDLultf7HHrJ5YltSXSeyQGUpzGU3iYlFhh9Smg+ygkkOJ3+trf0bgwixOnqnnWpc9ZQ==", + "version": "4.20250816.0", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20250816.0.tgz", + "integrity": "sha512-HuakGvmsU8aC60wsHP7Su+BgJFly1GmKbmbR/nqIz0Xlk6wcd/pp3vZ7jtbT3unf+aeBOlEO/CzcUb8xFsJLdA==", "dev": true, "dependencies": { "@cspotcode/source-map-support": "0.8.1", @@ -10344,7 +10344,7 @@ "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "^7.10.0", - "workerd": "1.20250726.0", + "workerd": "1.20250816.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "3.22.3" @@ -13652,6 +13652,19 @@ "dev": true, "license": "MIT" }, + "node_modules/unenv": { + "version": "2.0.0-rc.19", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.19.tgz", + "integrity": "sha512-t/OMHBNAkknVCI7bVB9OWjUUAwhVv9vsPIAGnNUxnu3FxPQN11rjh0sksLMzc3g7IlTgvHmOTl4JM7JHpcv5wA==", + "dev": true, + "dependencies": { + "defu": "^6.1.4", + "exsolve": "^1.0.7", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "ufo": "^1.6.1" + } + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", @@ -14282,9 +14295,9 @@ "dev": true }, "node_modules/workerd": { - "version": "1.20250726.0", - "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250726.0.tgz", - "integrity": "sha512-wDZqSKfIfQ2eVTUL6UawXdXEKPPyzRTnVdbhoKGq3NFrMxd+7v1cNH92u8775Qo1zO5S+GyWonQmZPFakXLvGw==", + "version": "1.20250816.0", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250816.0.tgz", + "integrity": "sha512-5gIvHPE/3QVlQR1Sc1NdBkWmqWj/TSgIbY/f/qs9lhiLBw/Da+HbNBTVYGjvwYqEb3NQ+XQM4gAm5b2+JJaUJg==", "dev": true, "hasInstallScript": true, "bin": { @@ -14294,11 +14307,11 @@ "node": ">=16" }, "optionalDependencies": { - "@cloudflare/workerd-darwin-64": "1.20250726.0", - "@cloudflare/workerd-darwin-arm64": "1.20250726.0", - "@cloudflare/workerd-linux-64": "1.20250726.0", - "@cloudflare/workerd-linux-arm64": "1.20250726.0", - "@cloudflare/workerd-windows-64": "1.20250726.0" + "@cloudflare/workerd-darwin-64": "1.20250816.0", + "@cloudflare/workerd-darwin-arm64": "1.20250816.0", + "@cloudflare/workerd-linux-64": "1.20250816.0", + "@cloudflare/workerd-linux-arm64": "1.20250816.0", + "@cloudflare/workerd-windows-64": "1.20250816.0" } }, "node_modules/workerpool": { @@ -14308,19 +14321,19 @@ "dev": true }, "node_modules/wrangler": { - "version": "4.26.1", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.26.1.tgz", - "integrity": "sha512-zGFEtHrjTAWOngm+zwEvYCxFwMSIBrzHa3Yu6rAxYMEzsT8PPvo2rdswyUJiUkpE9s2Depr37opceaY7JxEYFw==", + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.31.0.tgz", + "integrity": "sha512-blb8NfA4BGscvSzvLm2mEQRuUTmaMCiglkqHiR3EIque78UXG39xxVtFXlKhK32qaVvGI7ejdM//HC9plVPO3w==", "dev": true, "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", - "@cloudflare/unenv-preset": "2.5.0", + "@cloudflare/unenv-preset": "2.6.2", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", - "miniflare": "4.20250726.0", + "miniflare": "4.20250816.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.19", - "workerd": "1.20250726.0" + "workerd": "1.20250816.0" }, "bin": { "wrangler": "bin/wrangler.js", @@ -14333,7 +14346,7 @@ "fsevents": "~2.3.2" }, "peerDependencies": { - "@cloudflare/workers-types": "^4.20250726.0" + "@cloudflare/workers-types": "^4.20250816.0" }, "peerDependenciesMeta": { "@cloudflare/workers-types": { @@ -14342,13 +14355,13 @@ } }, "node_modules/wrangler/node_modules/@cloudflare/unenv-preset": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.5.0.tgz", - "integrity": "sha512-CZe9B2VbjIQjBTyc+KoZcN1oUcm4T6GgCXoel9O7647djHuSRAa6sM6G+NdxWArATZgeMMbsvn9C50GCcnIatA==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.6.2.tgz", + "integrity": "sha512-C7/tW7Qy+wGOCmHXu7xpP1TF3uIhRoi7zVY7dmu/SOSGjPilK+lSQ2lIRILulZsT467ZJNlI0jBxMbd8LzkGRg==", "dev": true, "peerDependencies": { "unenv": "2.0.0-rc.19", - "workerd": "^1.20250722.0" + "workerd": "^1.20250802.0" }, "peerDependenciesMeta": { "workerd": { @@ -14356,19 +14369,6 @@ } } }, - "node_modules/wrangler/node_modules/unenv": { - "version": "2.0.0-rc.19", - "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.19.tgz", - "integrity": "sha512-t/OMHBNAkknVCI7bVB9OWjUUAwhVv9vsPIAGnNUxnu3FxPQN11rjh0sksLMzc3g7IlTgvHmOTl4JM7JHpcv5wA==", - "dev": true, - "dependencies": { - "defu": "^6.1.4", - "exsolve": "^1.0.7", - "ohash": "^2.0.11", - "pathe": "^2.0.3", - "ufo": "^1.6.1" - } - }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -15903,37 +15903,37 @@ } }, "@cloudflare/workerd-darwin-64": { - "version": "1.20250726.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250726.0.tgz", - "integrity": "sha512-SOpQqQ2blLY0io/vErve44vJC1M5i7RHuMBdrdEPIEtxiLBTdOOVp4nqZ3KchocxZjskgTc2N4N3b5hNYuKDGw==", + "version": "1.20250816.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250816.0.tgz", + "integrity": "sha512-yN1Rga4ufTdrJPCP4gEqfB47i1lWi3teY5IoeQbUuKnjnCtm4pZvXur526JzCmaw60Jx+AEWf5tizdwRd5hHBQ==", "dev": true, "optional": true }, "@cloudflare/workerd-darwin-arm64": { - "version": "1.20250726.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250726.0.tgz", - "integrity": "sha512-I+TOQ+YQahxL/K7eS2GJzv5CZzSVaZoyqfB15Q71MT/+wyzPCaFDTt+fg3uXdwpaIQEMUfqFNpTQSqbKHAYNgA==", + "version": "1.20250816.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250816.0.tgz", + "integrity": "sha512-WyKPMQhbU+TTf4uDz3SA7ZObspg7WzyJMv/7J4grSddpdx2A4Y4SfPu3wsZleAOIMOAEVi0A1sYDhdltKM7Mxg==", "dev": true, "optional": true }, "@cloudflare/workerd-linux-64": { - "version": "1.20250726.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250726.0.tgz", - "integrity": "sha512-WSCv4o2uOW6b++ROVazrEW+jjZdBqCmXmmt7uVVfvjVxlzoYVwK9IvV2IXe4gsJ99HG9I0YCa7AT743cZ7TNNg==", + "version": "1.20250816.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250816.0.tgz", + "integrity": "sha512-NWHOuFnVBaPRhLHw8kjPO9GJmc2P/CTYbnNlNm0EThyi57o/oDx0ldWLJqEHlrdEPOw7zEVGBqM/6M+V9agC6w==", "dev": true, "optional": true }, "@cloudflare/workerd-linux-arm64": { - "version": "1.20250726.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250726.0.tgz", - "integrity": "sha512-jNokAGL3EQqH+31b0dX8+tlbKdjt/0UtTLvgD1e+7bOD92lzjYMa/CixHyMIY/FVvhsN4TNqfiz4cqroABTlhg==", + "version": "1.20250816.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250816.0.tgz", + "integrity": "sha512-FR+/yhaWs7FhfC3GKsM3+usQVrGEweJ9qyh7p+R6HNwnobgKr/h5ATWvJ4obGJF6ZHHodgSe+gOSYR7fkJ1xAQ==", "dev": true, "optional": true }, "@cloudflare/workerd-windows-64": { - "version": "1.20250726.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250726.0.tgz", - "integrity": "sha512-DiPTY63TNh6/ylvfutNQzYZi688x6NJDjQoqf5uiCp7xHweWx+GpVs42sZPeeXqCNvhm4dYjHjuigXJNh7t8Uw==", + "version": "1.20250816.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250816.0.tgz", + "integrity": "sha512-0lqClj2UMhFa8tCBiiX7Zhd5Bjp0V+X8oNBG6V6WsR9p9/HlIHAGgwRAM7aYkyG+8KC8xlbC89O2AXUXLpHx0g==", "dev": true, "optional": true }, @@ -21876,9 +21876,9 @@ "dev": true }, "miniflare": { - "version": "4.20250726.0", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20250726.0.tgz", - "integrity": "sha512-7+/RQQ9dNsyGfR2XN2RDLultf7HHrJ5YltSXSeyQGUpzGU3iYlFhh9Smg+ygkkOJ3+trf0bgwixOnqnnWpc9ZQ==", + "version": "4.20250816.0", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20250816.0.tgz", + "integrity": "sha512-HuakGvmsU8aC60wsHP7Su+BgJFly1GmKbmbR/nqIz0Xlk6wcd/pp3vZ7jtbT3unf+aeBOlEO/CzcUb8xFsJLdA==", "dev": true, "requires": { "@cspotcode/source-map-support": "0.8.1", @@ -21889,7 +21889,7 @@ "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "^7.10.0", - "workerd": "1.20250726.0", + "workerd": "1.20250816.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "3.22.3" @@ -24307,6 +24307,19 @@ "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", "dev": true }, + "unenv": { + "version": "2.0.0-rc.19", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.19.tgz", + "integrity": "sha512-t/OMHBNAkknVCI7bVB9OWjUUAwhVv9vsPIAGnNUxnu3FxPQN11rjh0sksLMzc3g7IlTgvHmOTl4JM7JHpcv5wA==", + "dev": true, + "requires": { + "defu": "^6.1.4", + "exsolve": "^1.0.7", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "ufo": "^1.6.1" + } + }, "unicode-canonical-property-names-ecmascript": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", @@ -24789,16 +24802,16 @@ "dev": true }, "workerd": { - "version": "1.20250726.0", - "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250726.0.tgz", - "integrity": "sha512-wDZqSKfIfQ2eVTUL6UawXdXEKPPyzRTnVdbhoKGq3NFrMxd+7v1cNH92u8775Qo1zO5S+GyWonQmZPFakXLvGw==", + "version": "1.20250816.0", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250816.0.tgz", + "integrity": "sha512-5gIvHPE/3QVlQR1Sc1NdBkWmqWj/TSgIbY/f/qs9lhiLBw/Da+HbNBTVYGjvwYqEb3NQ+XQM4gAm5b2+JJaUJg==", "dev": true, "requires": { - "@cloudflare/workerd-darwin-64": "1.20250726.0", - "@cloudflare/workerd-darwin-arm64": "1.20250726.0", - "@cloudflare/workerd-linux-64": "1.20250726.0", - "@cloudflare/workerd-linux-arm64": "1.20250726.0", - "@cloudflare/workerd-windows-64": "1.20250726.0" + "@cloudflare/workerd-darwin-64": "1.20250816.0", + "@cloudflare/workerd-darwin-arm64": "1.20250816.0", + "@cloudflare/workerd-linux-64": "1.20250816.0", + "@cloudflare/workerd-linux-arm64": "1.20250816.0", + "@cloudflare/workerd-windows-64": "1.20250816.0" } }, "workerpool": { @@ -24808,41 +24821,28 @@ "dev": true }, "wrangler": { - "version": "4.26.1", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.26.1.tgz", - "integrity": "sha512-zGFEtHrjTAWOngm+zwEvYCxFwMSIBrzHa3Yu6rAxYMEzsT8PPvo2rdswyUJiUkpE9s2Depr37opceaY7JxEYFw==", + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.31.0.tgz", + "integrity": "sha512-blb8NfA4BGscvSzvLm2mEQRuUTmaMCiglkqHiR3EIque78UXG39xxVtFXlKhK32qaVvGI7ejdM//HC9plVPO3w==", "dev": true, "requires": { "@cloudflare/kv-asset-handler": "0.4.0", - "@cloudflare/unenv-preset": "2.5.0", + "@cloudflare/unenv-preset": "2.6.2", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", "fsevents": "~2.3.2", - "miniflare": "4.20250726.0", + "miniflare": "4.20250816.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.19", - "workerd": "1.20250726.0" + "workerd": "1.20250816.0" }, "dependencies": { "@cloudflare/unenv-preset": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.5.0.tgz", - "integrity": "sha512-CZe9B2VbjIQjBTyc+KoZcN1oUcm4T6GgCXoel9O7647djHuSRAa6sM6G+NdxWArATZgeMMbsvn9C50GCcnIatA==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.6.2.tgz", + "integrity": "sha512-C7/tW7Qy+wGOCmHXu7xpP1TF3uIhRoi7zVY7dmu/SOSGjPilK+lSQ2lIRILulZsT467ZJNlI0jBxMbd8LzkGRg==", "dev": true, "requires": {} - }, - "unenv": { - "version": "2.0.0-rc.19", - "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.19.tgz", - "integrity": "sha512-t/OMHBNAkknVCI7bVB9OWjUUAwhVv9vsPIAGnNUxnu3FxPQN11rjh0sksLMzc3g7IlTgvHmOTl4JM7JHpcv5wA==", - "dev": true, - "requires": { - "defu": "^6.1.4", - "exsolve": "^1.0.7", - "ohash": "^2.0.11", - "pathe": "^2.0.3", - "ufo": "^1.6.1" - } } } }, diff --git a/package.json b/package.json index 2406d750f97..117eb62d826 100644 --- a/package.json +++ b/package.json @@ -133,6 +133,6 @@ "sinon-chai": "3.7.0", "typescript": "5.8.3", "url-toolkit": "2.2.5", - "wrangler": "4.26.1" + "wrangler": "4.31.0" } } From 8676a7a270397264b802bd9102227182f9d47d93 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 28 Aug 2025 02:08:11 +0000 Subject: [PATCH 15/64] Update babel monorepo to v7.28.3 --- package-lock.json | 266 +++++++++++++++++++++++----------------------- package.json | 6 +- 2 files changed, 136 insertions(+), 136 deletions(-) diff --git a/package-lock.json b/package-lock.json index 981dbe3d9a1..d1aacf36dac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,15 +7,15 @@ "name": "hls.js", "license": "Apache-2.0", "devDependencies": { - "@babel/core": "7.28.0", + "@babel/core": "7.28.3", "@babel/helper-module-imports": "7.27.1", "@babel/plugin-transform-class-properties": "7.27.1", "@babel/plugin-transform-object-assign": "7.27.1", "@babel/plugin-transform-object-rest-spread": "7.28.0", "@babel/plugin-transform-optional-chaining": "7.27.1", - "@babel/preset-env": "7.28.0", + "@babel/preset-env": "7.28.3", "@babel/preset-typescript": "7.27.1", - "@babel/register": "7.27.1", + "@babel/register": "7.28.3", "@microsoft/api-documenter": "7.26.29", "@microsoft/api-extractor": "7.52.8", "@rollup/plugin-alias": "5.1.1", @@ -124,21 +124,21 @@ } }, "node_modules/@babel/core": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", - "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", + "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.0", + "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.6", - "@babel/parser": "^7.28.0", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.3", + "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.0", - "@babel/types": "^7.28.0", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -163,13 +163,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", - "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", "dev": true, "dependencies": { - "@babel/parser": "^7.28.0", - "@babel/types": "^7.28.0", + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -241,17 +241,17 @@ "dev": true }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", - "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz", + "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.27.1", + "@babel/traverse": "^7.28.3", "semver": "^6.3.1" }, "engines": { @@ -371,14 +371,14 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", - "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", "dev": true, "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.3" + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -497,25 +497,25 @@ } }, "node_modules/@babel/helpers": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", - "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz", + "integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==", "dev": true, "dependencies": { "@babel/template": "^7.27.2", - "@babel/types": "^7.27.6" + "@babel/types": "^7.28.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", - "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", + "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", "dev": true, "dependencies": { - "@babel/types": "^7.28.0" + "@babel/types": "^7.28.2" }, "bin": { "parser": "bin/babel-parser.js" @@ -588,13 +588,13 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.27.1.tgz", - "integrity": "sha512-6BpaYGDavZqkI6yT+KSPdpZFfpnd68UKXbcjI9pJ13pvHhPrCKWOOLp+ysvMeA+DxnhuPpgIaRpxRxo5A9t5jw==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz", + "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -787,12 +787,12 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.27.1.tgz", - "integrity": "sha512-s734HmYU78MVzZ++joYM+NkJusItbdRcbm+AGRgJCt3iA+yux0QpD9cBVdz3tKyrjVYWRl7j0mHSmv4lhV0aoA==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz", + "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.28.3", "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { @@ -803,9 +803,9 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.0.tgz", - "integrity": "sha512-IjM1IoJNw72AZFlj33Cu8X0q2XK/6AaVC3jQu+cgQ5lThWD5ajnuUAml80dqRmOhmPkTH8uAwnpMu9Rvj0LTRA==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.3.tgz", + "integrity": "sha512-DoEWC5SuxuARF2KdKmGUq3ghfPMO6ZzR12Dnp5gubwbeWJo4dbNWXJPVlwvh4Zlq6Z7YVvL8VFxeSOJgjsx4Sg==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", @@ -813,7 +813,7 @@ "@babel/helper-globals": "^7.28.0", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", - "@babel/traverse": "^7.28.0" + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -1327,9 +1327,9 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.0.tgz", - "integrity": "sha512-LOAozRVbqxEVjSKfhGnuLoE4Kz4Oc5UJzuvFUhSsQzdCdaAQu06mG8zDv2GFSerM62nImUZ7K92vxnQcLSDlCQ==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.3.tgz", + "integrity": "sha512-K3/M/a4+ESb5LEldjQb+XSrpY0nF+ZBFlTCbSnKaYAMfD8v33O6PMs4uYnOk19HlcsI8WMu3McdFPTiQHF/1/A==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" @@ -1531,9 +1531,9 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.0.tgz", - "integrity": "sha512-VmaxeGOwuDqzLl5JUkIRM1X2Qu2uKGxHEQWh+cvvbl7JuJRgKGJSfsEF/bUaxFhJl/XAyxBe7q7qSuTbKFuCyg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.3.tgz", + "integrity": "sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg==", "dev": true, "dependencies": { "@babel/compat-data": "^7.28.0", @@ -1544,7 +1544,7 @@ "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-import-assertions": "^7.27.1", "@babel/plugin-syntax-import-attributes": "^7.27.1", @@ -1555,8 +1555,8 @@ "@babel/plugin-transform-block-scoped-functions": "^7.27.1", "@babel/plugin-transform-block-scoping": "^7.28.0", "@babel/plugin-transform-class-properties": "^7.27.1", - "@babel/plugin-transform-class-static-block": "^7.27.1", - "@babel/plugin-transform-classes": "^7.28.0", + "@babel/plugin-transform-class-static-block": "^7.28.3", + "@babel/plugin-transform-classes": "^7.28.3", "@babel/plugin-transform-computed-properties": "^7.27.1", "@babel/plugin-transform-destructuring": "^7.28.0", "@babel/plugin-transform-dotall-regex": "^7.27.1", @@ -1588,7 +1588,7 @@ "@babel/plugin-transform-private-methods": "^7.27.1", "@babel/plugin-transform-private-property-in-object": "^7.27.1", "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.28.0", + "@babel/plugin-transform-regenerator": "^7.28.3", "@babel/plugin-transform-regexp-modifiers": "^7.27.1", "@babel/plugin-transform-reserved-words": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", @@ -1657,9 +1657,9 @@ } }, "node_modules/@babel/register": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.27.1.tgz", - "integrity": "sha512-K13lQpoV54LATKkzBpBAEu1GGSIRzxR9f4IN4V8DCDgiUMo2UDGagEZr3lPeVNJPLkWUi5JE4hCHKneVTwQlYQ==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.28.3.tgz", + "integrity": "sha512-CieDOtd8u208eI49bYl4z1J22ySFw87IGwE+IswFEExH7e3rLgKb0WNQeumnacQ1+VoDJLYI5QFA3AJZuyZQfA==", "dev": true, "dependencies": { "clone-deep": "^4.0.1", @@ -1690,17 +1690,17 @@ } }, "node_modules/@babel/traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz", - "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz", + "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==", "dev": true, "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.0", + "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.0", + "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", - "@babel/types": "^7.28.0", + "@babel/types": "^7.28.2", "debug": "^4.3.1" }, "engines": { @@ -1708,9 +1708,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.0.tgz", - "integrity": "sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -14793,21 +14793,21 @@ "dev": true }, "@babel/core": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", - "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", + "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "dev": true, "requires": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.0", + "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.6", - "@babel/parser": "^7.28.0", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.3", + "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.0", - "@babel/types": "^7.28.0", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -14824,13 +14824,13 @@ } }, "@babel/generator": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", - "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", "dev": true, "requires": { - "@babel/parser": "^7.28.0", - "@babel/types": "^7.28.0", + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -14894,17 +14894,17 @@ } }, "@babel/helper-create-class-features-plugin": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", - "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz", + "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.27.1", + "@babel/traverse": "^7.28.3", "semver": "^6.3.1" }, "dependencies": { @@ -14992,14 +14992,14 @@ } }, "@babel/helper-module-transforms": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", - "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.3" + "@babel/traverse": "^7.28.3" } }, "@babel/helper-optimise-call-expression": { @@ -15079,22 +15079,22 @@ } }, "@babel/helpers": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", - "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz", + "integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==", "dev": true, "requires": { "@babel/template": "^7.27.2", - "@babel/types": "^7.27.6" + "@babel/types": "^7.28.2" } }, "@babel/parser": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", - "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", + "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", "dev": true, "requires": { - "@babel/types": "^7.28.0" + "@babel/types": "^7.28.2" } }, "@babel/plugin-bugfix-firefox-class-in-computed-class-key": { @@ -15137,13 +15137,13 @@ } }, "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.27.1.tgz", - "integrity": "sha512-6BpaYGDavZqkI6yT+KSPdpZFfpnd68UKXbcjI9pJ13pvHhPrCKWOOLp+ysvMeA+DxnhuPpgIaRpxRxo5A9t5jw==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz", + "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/traverse": "^7.28.3" } }, "@babel/plugin-proposal-private-property-in-object": { @@ -15259,19 +15259,19 @@ } }, "@babel/plugin-transform-class-static-block": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.27.1.tgz", - "integrity": "sha512-s734HmYU78MVzZ++joYM+NkJusItbdRcbm+AGRgJCt3iA+yux0QpD9cBVdz3tKyrjVYWRl7j0mHSmv4lhV0aoA==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz", + "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.28.3", "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-classes": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.0.tgz", - "integrity": "sha512-IjM1IoJNw72AZFlj33Cu8X0q2XK/6AaVC3jQu+cgQ5lThWD5ajnuUAml80dqRmOhmPkTH8uAwnpMu9Rvj0LTRA==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.3.tgz", + "integrity": "sha512-DoEWC5SuxuARF2KdKmGUq3ghfPMO6ZzR12Dnp5gubwbeWJo4dbNWXJPVlwvh4Zlq6Z7YVvL8VFxeSOJgjsx4Sg==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.27.3", @@ -15279,7 +15279,7 @@ "@babel/helper-globals": "^7.28.0", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", - "@babel/traverse": "^7.28.0" + "@babel/traverse": "^7.28.3" } }, "@babel/plugin-transform-computed-properties": { @@ -15595,9 +15595,9 @@ } }, "@babel/plugin-transform-regenerator": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.0.tgz", - "integrity": "sha512-LOAozRVbqxEVjSKfhGnuLoE4Kz4Oc5UJzuvFUhSsQzdCdaAQu06mG8zDv2GFSerM62nImUZ7K92vxnQcLSDlCQ==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.3.tgz", + "integrity": "sha512-K3/M/a4+ESb5LEldjQb+XSrpY0nF+ZBFlTCbSnKaYAMfD8v33O6PMs4uYnOk19HlcsI8WMu3McdFPTiQHF/1/A==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.27.1" @@ -15721,9 +15721,9 @@ } }, "@babel/preset-env": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.0.tgz", - "integrity": "sha512-VmaxeGOwuDqzLl5JUkIRM1X2Qu2uKGxHEQWh+cvvbl7JuJRgKGJSfsEF/bUaxFhJl/XAyxBe7q7qSuTbKFuCyg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.3.tgz", + "integrity": "sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg==", "dev": true, "requires": { "@babel/compat-data": "^7.28.0", @@ -15734,7 +15734,7 @@ "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-import-assertions": "^7.27.1", "@babel/plugin-syntax-import-attributes": "^7.27.1", @@ -15745,8 +15745,8 @@ "@babel/plugin-transform-block-scoped-functions": "^7.27.1", "@babel/plugin-transform-block-scoping": "^7.28.0", "@babel/plugin-transform-class-properties": "^7.27.1", - "@babel/plugin-transform-class-static-block": "^7.27.1", - "@babel/plugin-transform-classes": "^7.28.0", + "@babel/plugin-transform-class-static-block": "^7.28.3", + "@babel/plugin-transform-classes": "^7.28.3", "@babel/plugin-transform-computed-properties": "^7.27.1", "@babel/plugin-transform-destructuring": "^7.28.0", "@babel/plugin-transform-dotall-regex": "^7.27.1", @@ -15778,7 +15778,7 @@ "@babel/plugin-transform-private-methods": "^7.27.1", "@babel/plugin-transform-private-property-in-object": "^7.27.1", "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.28.0", + "@babel/plugin-transform-regenerator": "^7.28.3", "@babel/plugin-transform-regexp-modifiers": "^7.27.1", "@babel/plugin-transform-reserved-words": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", @@ -15831,9 +15831,9 @@ } }, "@babel/register": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.27.1.tgz", - "integrity": "sha512-K13lQpoV54LATKkzBpBAEu1GGSIRzxR9f4IN4V8DCDgiUMo2UDGagEZr3lPeVNJPLkWUi5JE4hCHKneVTwQlYQ==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.28.3.tgz", + "integrity": "sha512-CieDOtd8u208eI49bYl4z1J22ySFw87IGwE+IswFEExH7e3rLgKb0WNQeumnacQ1+VoDJLYI5QFA3AJZuyZQfA==", "dev": true, "requires": { "clone-deep": "^4.0.1", @@ -15855,24 +15855,24 @@ } }, "@babel/traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz", - "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz", + "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==", "dev": true, "requires": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.0", + "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.0", + "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", - "@babel/types": "^7.28.0", + "@babel/types": "^7.28.2", "debug": "^4.3.1" } }, "@babel/types": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.0.tgz", - "integrity": "sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", "dev": true, "requires": { "@babel/helper-string-parser": "^7.27.1", diff --git a/package.json b/package.json index 117eb62d826..dfe6b839319 100644 --- a/package.json +++ b/package.json @@ -65,15 +65,15 @@ "prepare": "husky" }, "devDependencies": { - "@babel/core": "7.28.0", + "@babel/core": "7.28.3", "@babel/helper-module-imports": "7.27.1", "@babel/plugin-transform-class-properties": "7.27.1", "@babel/plugin-transform-object-assign": "7.27.1", "@babel/plugin-transform-object-rest-spread": "7.28.0", "@babel/plugin-transform-optional-chaining": "7.27.1", - "@babel/preset-env": "7.28.0", + "@babel/preset-env": "7.28.3", "@babel/preset-typescript": "7.27.1", - "@babel/register": "7.27.1", + "@babel/register": "7.28.3", "@microsoft/api-documenter": "7.26.29", "@microsoft/api-extractor": "7.52.8", "@rollup/plugin-alias": "5.1.1", From 5d282983c9864cfecc8a21f4e6c3525d92f77523 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 28 Aug 2025 03:35:19 +0000 Subject: [PATCH 16/64] Update dependency @microsoft/api-documenter to v7.26.32 --- package-lock.json | 268 +++++++++++++++++++++++++++++++++++++++++++--- package.json | 2 +- 2 files changed, 254 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index d1aacf36dac..460bf7a7d9e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "@babel/preset-env": "7.28.3", "@babel/preset-typescript": "7.27.1", "@babel/register": "7.28.3", - "@microsoft/api-documenter": "7.26.29", + "@microsoft/api-documenter": "7.26.32", "@microsoft/api-extractor": "7.52.8", "@rollup/plugin-alias": "5.1.1", "@rollup/plugin-babel": "6.0.4", @@ -2978,16 +2978,16 @@ } }, "node_modules/@microsoft/api-documenter": { - "version": "7.26.29", - "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.26.29.tgz", - "integrity": "sha512-5gqnUCut1BeNmOZIE8hUJbzq3DxFcAyXL12oF6aFVtTDF8WiVs/J1HtlLYbxeIff6qbI1LfLnr16t+WOm9UVJw==", + "version": "7.26.32", + "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.26.32.tgz", + "integrity": "sha512-OnfyOuiOQMvIkzh7TK8RyPHDwtkZs7Dzu48XwzUyNHc3tyrLnlZcMNvh6XxUvPsTi/jOoe9alJezESnuGKIQYw==", "dev": true, "dependencies": { - "@microsoft/api-extractor-model": "7.30.6", + "@microsoft/api-extractor-model": "7.30.7", "@microsoft/tsdoc": "~0.15.1", - "@rushstack/node-core-library": "5.13.1", - "@rushstack/terminal": "0.15.3", - "@rushstack/ts-command-line": "5.0.1", + "@rushstack/node-core-library": "5.14.0", + "@rushstack/terminal": "0.15.4", + "@rushstack/ts-command-line": "5.0.2", "js-yaml": "~3.13.1", "resolve": "~1.22.1" }, @@ -2995,6 +2995,110 @@ "api-documenter": "bin/api-documenter" } }, + "node_modules/@microsoft/api-documenter/node_modules/@microsoft/api-extractor-model": { + "version": "7.30.7", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.30.7.tgz", + "integrity": "sha512-TBbmSI2/BHpfR9YhQA7nH0nqVmGgJ0xH0Ex4D99/qBDAUpnhA2oikGmdXanbw9AWWY/ExBYIpkmY8dBHdla3YQ==", + "dev": true, + "dependencies": { + "@microsoft/tsdoc": "~0.15.1", + "@microsoft/tsdoc-config": "~0.17.1", + "@rushstack/node-core-library": "5.14.0" + } + }, + "node_modules/@microsoft/api-documenter/node_modules/@rushstack/node-core-library": { + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.14.0.tgz", + "integrity": "sha512-eRong84/rwQUlATGFW3TMTYVyqL1vfW9Lf10PH+mVGfIb9HzU3h5AASNIw+axnBLjnD0n3rT5uQBwu9fvzATrg==", + "dev": true, + "dependencies": { + "ajv": "~8.13.0", + "ajv-draft-04": "~1.0.0", + "ajv-formats": "~3.0.1", + "fs-extra": "~11.3.0", + "import-lazy": "~4.0.0", + "jju": "~1.4.0", + "resolve": "~1.22.1", + "semver": "~7.5.4" + }, + "peerDependencies": { + "@types/node": "*" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@microsoft/api-documenter/node_modules/@rushstack/terminal": { + "version": "0.15.4", + "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.15.4.tgz", + "integrity": "sha512-OQSThV0itlwVNHV6thoXiAYZlQh4Fgvie2CzxFABsbO2MWQsI4zOh3LRNigYSTrmS+ba2j0B3EObakPzf/x6Zg==", + "dev": true, + "dependencies": { + "@rushstack/node-core-library": "5.14.0", + "supports-color": "~8.1.1" + }, + "peerDependencies": { + "@types/node": "*" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@microsoft/api-documenter/node_modules/@rushstack/ts-command-line": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-5.0.2.tgz", + "integrity": "sha512-+AkJDbu1GFMPIU8Sb7TLVXDv/Q7Mkvx+wAjEl8XiXVVq+p1FmWW6M3LYpJMmoHNckSofeMecgWg5lfMwNAAsEQ==", + "dev": true, + "dependencies": { + "@rushstack/terminal": "0.15.4", + "@types/argparse": "1.0.38", + "argparse": "~1.0.9", + "string-argv": "~0.3.1" + } + }, + "node_modules/@microsoft/api-documenter/node_modules/ajv": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", + "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@microsoft/api-documenter/node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "dev": true, + "peerDependencies": { + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/@microsoft/api-documenter/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/@microsoft/api-documenter/node_modules/js-yaml": { "version": "3.13.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", @@ -3008,6 +3112,42 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/@microsoft/api-documenter/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/@microsoft/api-documenter/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@microsoft/api-documenter/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/@microsoft/api-extractor": { "version": "7.52.8", "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.52.8.tgz", @@ -16525,20 +16665,94 @@ } }, "@microsoft/api-documenter": { - "version": "7.26.29", - "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.26.29.tgz", - "integrity": "sha512-5gqnUCut1BeNmOZIE8hUJbzq3DxFcAyXL12oF6aFVtTDF8WiVs/J1HtlLYbxeIff6qbI1LfLnr16t+WOm9UVJw==", + "version": "7.26.32", + "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.26.32.tgz", + "integrity": "sha512-OnfyOuiOQMvIkzh7TK8RyPHDwtkZs7Dzu48XwzUyNHc3tyrLnlZcMNvh6XxUvPsTi/jOoe9alJezESnuGKIQYw==", "dev": true, "requires": { - "@microsoft/api-extractor-model": "7.30.6", + "@microsoft/api-extractor-model": "7.30.7", "@microsoft/tsdoc": "~0.15.1", - "@rushstack/node-core-library": "5.13.1", - "@rushstack/terminal": "0.15.3", - "@rushstack/ts-command-line": "5.0.1", + "@rushstack/node-core-library": "5.14.0", + "@rushstack/terminal": "0.15.4", + "@rushstack/ts-command-line": "5.0.2", "js-yaml": "~3.13.1", "resolve": "~1.22.1" }, "dependencies": { + "@microsoft/api-extractor-model": { + "version": "7.30.7", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.30.7.tgz", + "integrity": "sha512-TBbmSI2/BHpfR9YhQA7nH0nqVmGgJ0xH0Ex4D99/qBDAUpnhA2oikGmdXanbw9AWWY/ExBYIpkmY8dBHdla3YQ==", + "dev": true, + "requires": { + "@microsoft/tsdoc": "~0.15.1", + "@microsoft/tsdoc-config": "~0.17.1", + "@rushstack/node-core-library": "5.14.0" + } + }, + "@rushstack/node-core-library": { + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.14.0.tgz", + "integrity": "sha512-eRong84/rwQUlATGFW3TMTYVyqL1vfW9Lf10PH+mVGfIb9HzU3h5AASNIw+axnBLjnD0n3rT5uQBwu9fvzATrg==", + "dev": true, + "requires": { + "ajv": "~8.13.0", + "ajv-draft-04": "~1.0.0", + "ajv-formats": "~3.0.1", + "fs-extra": "~11.3.0", + "import-lazy": "~4.0.0", + "jju": "~1.4.0", + "resolve": "~1.22.1", + "semver": "~7.5.4" + } + }, + "@rushstack/terminal": { + "version": "0.15.4", + "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.15.4.tgz", + "integrity": "sha512-OQSThV0itlwVNHV6thoXiAYZlQh4Fgvie2CzxFABsbO2MWQsI4zOh3LRNigYSTrmS+ba2j0B3EObakPzf/x6Zg==", + "dev": true, + "requires": { + "@rushstack/node-core-library": "5.14.0", + "supports-color": "~8.1.1" + } + }, + "@rushstack/ts-command-line": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-5.0.2.tgz", + "integrity": "sha512-+AkJDbu1GFMPIU8Sb7TLVXDv/Q7Mkvx+wAjEl8XiXVVq+p1FmWW6M3LYpJMmoHNckSofeMecgWg5lfMwNAAsEQ==", + "dev": true, + "requires": { + "@rushstack/terminal": "0.15.4", + "@types/argparse": "1.0.38", + "argparse": "~1.0.9", + "string-argv": "~0.3.1" + } + }, + "ajv": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", + "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" + } + }, + "ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "dev": true, + "requires": {} + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, "js-yaml": { "version": "3.13.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", @@ -16548,6 +16762,30 @@ "argparse": "^1.0.7", "esprima": "^4.0.0" } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } } } }, diff --git a/package.json b/package.json index dfe6b839319..86209b483b3 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "@babel/preset-env": "7.28.3", "@babel/preset-typescript": "7.27.1", "@babel/register": "7.28.3", - "@microsoft/api-documenter": "7.26.29", + "@microsoft/api-documenter": "7.26.32", "@microsoft/api-extractor": "7.52.8", "@rollup/plugin-alias": "5.1.1", "@rollup/plugin-babel": "6.0.4", From 42c3634cca42b95e5dacbbf1161353131e021acb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 28 Aug 2025 04:28:16 +0000 Subject: [PATCH 17/64] Update dependency eslint-plugin-n to v17.21.3 --- package-lock.json | 74 ++++++++++++++--------------------------------- package.json | 2 +- 2 files changed, 22 insertions(+), 54 deletions(-) diff --git a/package-lock.json b/package-lock.json index 460bf7a7d9e..1a91af7df0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,7 +43,7 @@ "eslint-config-prettier": "10.1.8", "eslint-plugin-import": "2.32.0", "eslint-plugin-mocha": "10.5.0", - "eslint-plugin-n": "17.21.0", + "eslint-plugin-n": "17.21.3", "eslint-plugin-no-for-of-loops": "1.0.1", "eslint-plugin-promise": "7.2.1", "eventemitter3": "5.0.1", @@ -7009,9 +7009,9 @@ } }, "node_modules/eslint-plugin-n": { - "version": "17.21.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.21.0.tgz", - "integrity": "sha512-1+iZ8We4ZlwVMtb/DcHG3y5/bZOdazIpa/4TySo22MLKdwrLcfrX0hbadnCvykSQCCmkAnWmIP8jZVb2AAq29A==", + "version": "17.21.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.21.3.tgz", + "integrity": "sha512-MtxYjDZhMQgsWRm/4xYLL0i2EhusWT7itDxlJ80l1NND2AL2Vi5Mvneqv/ikG9+zpran0VsVRXTEHrpLmUZRNw==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.5.0", @@ -7019,8 +7019,8 @@ "eslint-plugin-es-x": "^7.8.0", "get-tsconfig": "^4.8.1", "globals": "^15.11.0", + "globrex": "^0.1.2", "ignore": "^5.3.2", - "minimatch": "^9.0.5", "semver": "^7.6.3", "ts-declaration-location": "^1.0.6" }, @@ -7034,30 +7034,6 @@ "eslint": ">=8.23.0" } }, - "node_modules/eslint-plugin-n/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/eslint-plugin-n/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/eslint-plugin-no-for-of-loops": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/eslint-plugin-no-for-of-loops/-/eslint-plugin-no-for-of-loops-1.0.1.tgz", @@ -8038,6 +8014,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -19733,9 +19715,9 @@ } }, "eslint-plugin-n": { - "version": "17.21.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.21.0.tgz", - "integrity": "sha512-1+iZ8We4ZlwVMtb/DcHG3y5/bZOdazIpa/4TySo22MLKdwrLcfrX0hbadnCvykSQCCmkAnWmIP8jZVb2AAq29A==", + "version": "17.21.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.21.3.tgz", + "integrity": "sha512-MtxYjDZhMQgsWRm/4xYLL0i2EhusWT7itDxlJ80l1NND2AL2Vi5Mvneqv/ikG9+zpran0VsVRXTEHrpLmUZRNw==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.5.0", @@ -19743,30 +19725,10 @@ "eslint-plugin-es-x": "^7.8.0", "get-tsconfig": "^4.8.1", "globals": "^15.11.0", + "globrex": "^0.1.2", "ignore": "^5.3.2", - "minimatch": "^9.0.5", "semver": "^7.6.3", "ts-declaration-location": "^1.0.6" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } } }, "eslint-plugin-no-for-of-loops": { @@ -20381,6 +20343,12 @@ "gopd": "^1.0.1" } }, + "globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true + }, "gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", diff --git a/package.json b/package.json index 86209b483b3..7db770f230b 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "eslint-config-prettier": "10.1.8", "eslint-plugin-import": "2.32.0", "eslint-plugin-mocha": "10.5.0", - "eslint-plugin-n": "17.21.0", + "eslint-plugin-n": "17.21.3", "eslint-plugin-no-for-of-loops": "1.0.1", "eslint-plugin-promise": "7.2.1", "eventemitter3": "5.0.1", From 4966790609c6921b4ae011db9e51ec037d5ddcf3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 28 Aug 2025 05:24:17 +0000 Subject: [PATCH 18/64] Update dependency @microsoft/api-extractor to v7.52.11 --- package-lock.json | 389 +++++++++++----------------------------------- package.json | 2 +- 2 files changed, 96 insertions(+), 295 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1a91af7df0f..6e86deaa307 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@babel/preset-typescript": "7.27.1", "@babel/register": "7.28.3", "@microsoft/api-documenter": "7.26.32", - "@microsoft/api-extractor": "7.52.8", + "@microsoft/api-extractor": "7.52.11", "@rollup/plugin-alias": "5.1.1", "@rollup/plugin-babel": "6.0.4", "@rollup/plugin-commonjs": "28.0.6", @@ -2801,6 +2801,27 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "dev": true, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "dev": true, + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -2995,110 +3016,6 @@ "api-documenter": "bin/api-documenter" } }, - "node_modules/@microsoft/api-documenter/node_modules/@microsoft/api-extractor-model": { - "version": "7.30.7", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.30.7.tgz", - "integrity": "sha512-TBbmSI2/BHpfR9YhQA7nH0nqVmGgJ0xH0Ex4D99/qBDAUpnhA2oikGmdXanbw9AWWY/ExBYIpkmY8dBHdla3YQ==", - "dev": true, - "dependencies": { - "@microsoft/tsdoc": "~0.15.1", - "@microsoft/tsdoc-config": "~0.17.1", - "@rushstack/node-core-library": "5.14.0" - } - }, - "node_modules/@microsoft/api-documenter/node_modules/@rushstack/node-core-library": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.14.0.tgz", - "integrity": "sha512-eRong84/rwQUlATGFW3TMTYVyqL1vfW9Lf10PH+mVGfIb9HzU3h5AASNIw+axnBLjnD0n3rT5uQBwu9fvzATrg==", - "dev": true, - "dependencies": { - "ajv": "~8.13.0", - "ajv-draft-04": "~1.0.0", - "ajv-formats": "~3.0.1", - "fs-extra": "~11.3.0", - "import-lazy": "~4.0.0", - "jju": "~1.4.0", - "resolve": "~1.22.1", - "semver": "~7.5.4" - }, - "peerDependencies": { - "@types/node": "*" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@microsoft/api-documenter/node_modules/@rushstack/terminal": { - "version": "0.15.4", - "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.15.4.tgz", - "integrity": "sha512-OQSThV0itlwVNHV6thoXiAYZlQh4Fgvie2CzxFABsbO2MWQsI4zOh3LRNigYSTrmS+ba2j0B3EObakPzf/x6Zg==", - "dev": true, - "dependencies": { - "@rushstack/node-core-library": "5.14.0", - "supports-color": "~8.1.1" - }, - "peerDependencies": { - "@types/node": "*" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@microsoft/api-documenter/node_modules/@rushstack/ts-command-line": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-5.0.2.tgz", - "integrity": "sha512-+AkJDbu1GFMPIU8Sb7TLVXDv/Q7Mkvx+wAjEl8XiXVVq+p1FmWW6M3LYpJMmoHNckSofeMecgWg5lfMwNAAsEQ==", - "dev": true, - "dependencies": { - "@rushstack/terminal": "0.15.4", - "@types/argparse": "1.0.38", - "argparse": "~1.0.9", - "string-argv": "~0.3.1" - } - }, - "node_modules/@microsoft/api-documenter/node_modules/ajv": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", - "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.3", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.4.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@microsoft/api-documenter/node_modules/ajv-draft-04": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", - "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", - "dev": true, - "peerDependencies": { - "ajv": "^8.5.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/@microsoft/api-documenter/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/@microsoft/api-documenter/node_modules/js-yaml": { "version": "3.13.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", @@ -3112,57 +3029,21 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@microsoft/api-documenter/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/@microsoft/api-documenter/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@microsoft/api-documenter/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/@microsoft/api-extractor": { - "version": "7.52.8", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.52.8.tgz", - "integrity": "sha512-cszYIcjiNscDoMB1CIKZ3My61+JOhpERGlGr54i6bocvGLrcL/wo9o+RNXMBrb7XgLtKaizZWUpqRduQuHQLdg==", + "version": "7.52.11", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.52.11.tgz", + "integrity": "sha512-IKQ7bHg6f/Io3dQds6r9QPYk4q0OlR9A4nFDtNhUt3UUIhyitbxAqRN1CLjUVtk6IBk3xzyCMOdwwtIXQ7AlGg==", "dev": true, "dependencies": { - "@microsoft/api-extractor-model": "7.30.6", + "@microsoft/api-extractor-model": "7.30.7", "@microsoft/tsdoc": "~0.15.1", "@microsoft/tsdoc-config": "~0.17.1", - "@rushstack/node-core-library": "5.13.1", + "@rushstack/node-core-library": "5.14.0", "@rushstack/rig-package": "0.5.3", - "@rushstack/terminal": "0.15.3", - "@rushstack/ts-command-line": "5.0.1", + "@rushstack/terminal": "0.15.4", + "@rushstack/ts-command-line": "5.0.2", "lodash": "~4.17.15", - "minimatch": "~3.0.3", + "minimatch": "10.0.3", "resolve": "~1.22.1", "semver": "~7.5.4", "source-map": "~0.6.1", @@ -3173,26 +3054,29 @@ } }, "node_modules/@microsoft/api-extractor-model": { - "version": "7.30.6", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.30.6.tgz", - "integrity": "sha512-znmFn69wf/AIrwHya3fxX6uB5etSIn6vg4Q4RB/tb5VDDs1rqREc+AvMC/p19MUN13CZ7+V/8pkYPTj7q8tftg==", + "version": "7.30.7", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.30.7.tgz", + "integrity": "sha512-TBbmSI2/BHpfR9YhQA7nH0nqVmGgJ0xH0Ex4D99/qBDAUpnhA2oikGmdXanbw9AWWY/ExBYIpkmY8dBHdla3YQ==", "dev": true, "dependencies": { "@microsoft/tsdoc": "~0.15.1", "@microsoft/tsdoc-config": "~0.17.1", - "@rushstack/node-core-library": "5.13.1" + "@rushstack/node-core-library": "5.14.0" } }, "node_modules/@microsoft/api-extractor/node_modules/minimatch": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", - "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", "dev": true, "dependencies": { - "brace-expansion": "^1.1.7" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { - "node": "*" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@microsoft/api-extractor/node_modules/semver": { @@ -3832,9 +3716,9 @@ "dev": true }, "node_modules/@rushstack/node-core-library": { - "version": "5.13.1", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.13.1.tgz", - "integrity": "sha512-5yXhzPFGEkVc9Fu92wsNJ9jlvdwz4RNb2bMso+/+TH0nMm1jDDDsOIf4l8GAkPxGuwPw5DH24RliWVfSPhlW/Q==", + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.14.0.tgz", + "integrity": "sha512-eRong84/rwQUlATGFW3TMTYVyqL1vfW9Lf10PH+mVGfIb9HzU3h5AASNIw+axnBLjnD0n3rT5uQBwu9fvzATrg==", "dev": true, "dependencies": { "ajv": "~8.13.0", @@ -3917,12 +3801,12 @@ } }, "node_modules/@rushstack/terminal": { - "version": "0.15.3", - "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.15.3.tgz", - "integrity": "sha512-DGJ0B2Vm69468kZCJkPj3AH5nN+nR9SPmC0rFHtzsS4lBQ7/dgOwtwVxYP7W9JPDMuRBkJ4KHmWKr036eJsj9g==", + "version": "0.15.4", + "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.15.4.tgz", + "integrity": "sha512-OQSThV0itlwVNHV6thoXiAYZlQh4Fgvie2CzxFABsbO2MWQsI4zOh3LRNigYSTrmS+ba2j0B3EObakPzf/x6Zg==", "dev": true, "dependencies": { - "@rushstack/node-core-library": "5.13.1", + "@rushstack/node-core-library": "5.14.0", "supports-color": "~8.1.1" }, "peerDependencies": { @@ -3959,12 +3843,12 @@ } }, "node_modules/@rushstack/ts-command-line": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-5.0.1.tgz", - "integrity": "sha512-bsbUucn41UXrQK7wgM8CNM/jagBytEyJqXw/umtI8d68vFm1Jwxh1OtLrlW7uGZgjCWiiPH6ooUNa1aVsuVr3Q==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-5.0.2.tgz", + "integrity": "sha512-+AkJDbu1GFMPIU8Sb7TLVXDv/Q7Mkvx+wAjEl8XiXVVq+p1FmWW6M3LYpJMmoHNckSofeMecgWg5lfMwNAAsEQ==", "dev": true, "dependencies": { - "@rushstack/terminal": "0.15.3", + "@rushstack/terminal": "0.15.4", "@types/argparse": "1.0.38", "argparse": "~1.0.9", "string-argv": "~0.3.1" @@ -16514,6 +16398,21 @@ "dev": true, "optional": true }, + "@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "dev": true + }, + "@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "dev": true, + "requires": { + "@isaacs/balanced-match": "^4.0.1" + } + }, "@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -16661,80 +16560,6 @@ "resolve": "~1.22.1" }, "dependencies": { - "@microsoft/api-extractor-model": { - "version": "7.30.7", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.30.7.tgz", - "integrity": "sha512-TBbmSI2/BHpfR9YhQA7nH0nqVmGgJ0xH0Ex4D99/qBDAUpnhA2oikGmdXanbw9AWWY/ExBYIpkmY8dBHdla3YQ==", - "dev": true, - "requires": { - "@microsoft/tsdoc": "~0.15.1", - "@microsoft/tsdoc-config": "~0.17.1", - "@rushstack/node-core-library": "5.14.0" - } - }, - "@rushstack/node-core-library": { - "version": "5.14.0", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.14.0.tgz", - "integrity": "sha512-eRong84/rwQUlATGFW3TMTYVyqL1vfW9Lf10PH+mVGfIb9HzU3h5AASNIw+axnBLjnD0n3rT5uQBwu9fvzATrg==", - "dev": true, - "requires": { - "ajv": "~8.13.0", - "ajv-draft-04": "~1.0.0", - "ajv-formats": "~3.0.1", - "fs-extra": "~11.3.0", - "import-lazy": "~4.0.0", - "jju": "~1.4.0", - "resolve": "~1.22.1", - "semver": "~7.5.4" - } - }, - "@rushstack/terminal": { - "version": "0.15.4", - "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.15.4.tgz", - "integrity": "sha512-OQSThV0itlwVNHV6thoXiAYZlQh4Fgvie2CzxFABsbO2MWQsI4zOh3LRNigYSTrmS+ba2j0B3EObakPzf/x6Zg==", - "dev": true, - "requires": { - "@rushstack/node-core-library": "5.14.0", - "supports-color": "~8.1.1" - } - }, - "@rushstack/ts-command-line": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-5.0.2.tgz", - "integrity": "sha512-+AkJDbu1GFMPIU8Sb7TLVXDv/Q7Mkvx+wAjEl8XiXVVq+p1FmWW6M3LYpJMmoHNckSofeMecgWg5lfMwNAAsEQ==", - "dev": true, - "requires": { - "@rushstack/terminal": "0.15.4", - "@types/argparse": "1.0.38", - "argparse": "~1.0.9", - "string-argv": "~0.3.1" - } - }, - "ajv": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", - "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.3", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.4.1" - } - }, - "ajv-draft-04": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", - "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", - "dev": true, - "requires": {} - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, "js-yaml": { "version": "3.13.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", @@ -16744,48 +16569,24 @@ "argparse": "^1.0.7", "esprima": "^4.0.0" } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } } } }, "@microsoft/api-extractor": { - "version": "7.52.8", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.52.8.tgz", - "integrity": "sha512-cszYIcjiNscDoMB1CIKZ3My61+JOhpERGlGr54i6bocvGLrcL/wo9o+RNXMBrb7XgLtKaizZWUpqRduQuHQLdg==", + "version": "7.52.11", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.52.11.tgz", + "integrity": "sha512-IKQ7bHg6f/Io3dQds6r9QPYk4q0OlR9A4nFDtNhUt3UUIhyitbxAqRN1CLjUVtk6IBk3xzyCMOdwwtIXQ7AlGg==", "dev": true, "requires": { - "@microsoft/api-extractor-model": "7.30.6", + "@microsoft/api-extractor-model": "7.30.7", "@microsoft/tsdoc": "~0.15.1", "@microsoft/tsdoc-config": "~0.17.1", - "@rushstack/node-core-library": "5.13.1", + "@rushstack/node-core-library": "5.14.0", "@rushstack/rig-package": "0.5.3", - "@rushstack/terminal": "0.15.3", - "@rushstack/ts-command-line": "5.0.1", + "@rushstack/terminal": "0.15.4", + "@rushstack/ts-command-line": "5.0.2", "lodash": "~4.17.15", - "minimatch": "~3.0.3", + "minimatch": "10.0.3", "resolve": "~1.22.1", "semver": "~7.5.4", "source-map": "~0.6.1", @@ -16793,12 +16594,12 @@ }, "dependencies": { "minimatch": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", - "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", "dev": true, "requires": { - "brace-expansion": "^1.1.7" + "@isaacs/brace-expansion": "^5.0.0" } }, "semver": { @@ -16819,14 +16620,14 @@ } }, "@microsoft/api-extractor-model": { - "version": "7.30.6", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.30.6.tgz", - "integrity": "sha512-znmFn69wf/AIrwHya3fxX6uB5etSIn6vg4Q4RB/tb5VDDs1rqREc+AvMC/p19MUN13CZ7+V/8pkYPTj7q8tftg==", + "version": "7.30.7", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.30.7.tgz", + "integrity": "sha512-TBbmSI2/BHpfR9YhQA7nH0nqVmGgJ0xH0Ex4D99/qBDAUpnhA2oikGmdXanbw9AWWY/ExBYIpkmY8dBHdla3YQ==", "dev": true, "requires": { "@microsoft/tsdoc": "~0.15.1", "@microsoft/tsdoc-config": "~0.17.1", - "@rushstack/node-core-library": "5.13.1" + "@rushstack/node-core-library": "5.14.0" } }, "@microsoft/tsdoc": { @@ -17191,9 +16992,9 @@ "dev": true }, "@rushstack/node-core-library": { - "version": "5.13.1", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.13.1.tgz", - "integrity": "sha512-5yXhzPFGEkVc9Fu92wsNJ9jlvdwz4RNb2bMso+/+TH0nMm1jDDDsOIf4l8GAkPxGuwPw5DH24RliWVfSPhlW/Q==", + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.14.0.tgz", + "integrity": "sha512-eRong84/rwQUlATGFW3TMTYVyqL1vfW9Lf10PH+mVGfIb9HzU3h5AASNIw+axnBLjnD0n3rT5uQBwu9fvzATrg==", "dev": true, "requires": { "ajv": "~8.13.0", @@ -17253,12 +17054,12 @@ } }, "@rushstack/terminal": { - "version": "0.15.3", - "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.15.3.tgz", - "integrity": "sha512-DGJ0B2Vm69468kZCJkPj3AH5nN+nR9SPmC0rFHtzsS4lBQ7/dgOwtwVxYP7W9JPDMuRBkJ4KHmWKr036eJsj9g==", + "version": "0.15.4", + "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.15.4.tgz", + "integrity": "sha512-OQSThV0itlwVNHV6thoXiAYZlQh4Fgvie2CzxFABsbO2MWQsI4zOh3LRNigYSTrmS+ba2j0B3EObakPzf/x6Zg==", "dev": true, "requires": { - "@rushstack/node-core-library": "5.13.1", + "@rushstack/node-core-library": "5.14.0", "supports-color": "~8.1.1" }, "dependencies": { @@ -17280,12 +17081,12 @@ } }, "@rushstack/ts-command-line": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-5.0.1.tgz", - "integrity": "sha512-bsbUucn41UXrQK7wgM8CNM/jagBytEyJqXw/umtI8d68vFm1Jwxh1OtLrlW7uGZgjCWiiPH6ooUNa1aVsuVr3Q==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-5.0.2.tgz", + "integrity": "sha512-+AkJDbu1GFMPIU8Sb7TLVXDv/Q7Mkvx+wAjEl8XiXVVq+p1FmWW6M3LYpJMmoHNckSofeMecgWg5lfMwNAAsEQ==", "dev": true, "requires": { - "@rushstack/terminal": "0.15.3", + "@rushstack/terminal": "0.15.4", "@types/argparse": "1.0.38", "argparse": "~1.0.9", "string-argv": "~0.3.1" diff --git a/package.json b/package.json index 7db770f230b..3e327af92d4 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "@babel/preset-typescript": "7.27.1", "@babel/register": "7.28.3", "@microsoft/api-documenter": "7.26.32", - "@microsoft/api-extractor": "7.52.8", + "@microsoft/api-extractor": "7.52.11", "@rollup/plugin-alias": "5.1.1", "@rollup/plugin-babel": "6.0.4", "@rollup/plugin-commonjs": "28.0.6", From 2c2e0284dc05503025a0a8f23299cad66a3e820e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 28 Aug 2025 06:26:49 +0000 Subject: [PATCH 19/64] Update dependency rollup to v4.47.0 --- package-lock.json | 342 +++++++++++++++++++++++----------------------- package.json | 2 +- 2 files changed, 172 insertions(+), 172 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6e86deaa307..45f9bbf6bd0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -66,7 +66,7 @@ "npm-run-all2": "8.0.4", "prettier": "3.6.2", "promise-polyfill": "8.3.0", - "rollup": "4.45.1", + "rollup": "4.47.0", "rollup-plugin-istanbul": "5.0.0", "sauce-connect-launcher": "1.3.2", "selenium-webdriver": "4.34.0", @@ -3450,9 +3450,9 @@ "dev": true }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.1.tgz", - "integrity": "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.47.0.tgz", + "integrity": "sha512-Weap5hVbZs/yIvUZcFpAmIso8rLmwkO1LesddNjeX28tIhQkAKjRuVgAJ2xpj8wXTny7IZro9aBIgGov0qsL4A==", "cpu": [ "arm" ], @@ -3463,9 +3463,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.45.1.tgz", - "integrity": "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.47.0.tgz", + "integrity": "sha512-XcnlqvG5riTJByKX7bZ1ehe48GiF+eNkdnzV0ziLp85XyJ6tLPfhkXHv3e0h3cpZESTQa8IB+ZHhV/r02+8qKw==", "cpu": [ "arm64" ], @@ -3476,9 +3476,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.45.1.tgz", - "integrity": "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.47.0.tgz", + "integrity": "sha512-kZzTIzmzAUOKteh688kN88HNaL7wxwTz9XB5dDK94AQdf9nD+lxm/H5uPKQaawUFS+klBEowqPMUPjBRKGbo/g==", "cpu": [ "arm64" ], @@ -3489,9 +3489,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.45.1.tgz", - "integrity": "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.47.0.tgz", + "integrity": "sha512-WaMrgHRbFspYjvycbsbqheBmlsQBLwfZVWv/KFsT212Yz/RjEQ/9KEp1/p0Ef3ZNwbWsylmgf69St66D9NQNHw==", "cpu": [ "x64" ], @@ -3502,9 +3502,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.45.1.tgz", - "integrity": "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.47.0.tgz", + "integrity": "sha512-umfYslurvSmAK5MEyOcOGooQ6EBB2pYePQaTVlrOkIfG6uuwu9egYOlxr35lwsp6XG0NzmXW0/5o150LUioMkQ==", "cpu": [ "arm64" ], @@ -3515,9 +3515,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.45.1.tgz", - "integrity": "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.47.0.tgz", + "integrity": "sha512-EFXhIykAl8//4ihOjGNirF89HEUbOB8ev2aiw8ST8wFGwDdIPARh3enDlbp8aFnScl4CDK4DZLQYXaM6qpxzZw==", "cpu": [ "x64" ], @@ -3528,9 +3528,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.45.1.tgz", - "integrity": "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.47.0.tgz", + "integrity": "sha512-EwkC5N61ptruQ9wNkYfLgUWEGh+F3JZSGHkUWhaK2ISAK0d0xmiMKF0trFhRqPQFov5d9DmFiFIhWB5IC79OUA==", "cpu": [ "arm" ], @@ -3541,9 +3541,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.45.1.tgz", - "integrity": "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.47.0.tgz", + "integrity": "sha512-Iz/g1X94vIjppA4H9hN3VEedw4ObC+u+aua2J/VPJnENEJ0GeCAPBN15nJc5pS5M8JPlUhOd3oqhOWX6Un4RHA==", "cpu": [ "arm" ], @@ -3554,9 +3554,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.45.1.tgz", - "integrity": "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.47.0.tgz", + "integrity": "sha512-eYEYHYjFo/vb6k1l5uq5+Af9yuo9WaST/z+/8T5gkee+A0Sfx1NIPZtKMEQOLjm/oaeHFGpWaAO97gTPhouIfQ==", "cpu": [ "arm64" ], @@ -3567,9 +3567,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.45.1.tgz", - "integrity": "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.47.0.tgz", + "integrity": "sha512-LX2x0/RszFEmDfjzL6kG/vihD5CkpJ+0K6lcbqX0jAopkkXeY2ZjStngdFMFW+BK7pyrqryJgy6Jt3+oyDxrSA==", "cpu": [ "arm64" ], @@ -3580,9 +3580,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.45.1.tgz", - "integrity": "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.47.0.tgz", + "integrity": "sha512-0U+56rJmJvqBCwlPFz/BcxkvdiRdNPamBfuFHrOGQtGajSMJ2OqzlvOgwj5vReRQnSA6XMKw/JL1DaBhceil+g==", "cpu": [ "loong64" ], @@ -3592,10 +3592,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.45.1.tgz", - "integrity": "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg==", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.47.0.tgz", + "integrity": "sha512-2VKOsnNyvS05HFPKtmAWtef+nZyKCot/V3Jh/A5sYMhUvtthNjp6CjakYTtc5xZ8J8Fp5FKrUWGxptVtZ2OzEA==", "cpu": [ "ppc64" ], @@ -3606,9 +3606,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.45.1.tgz", - "integrity": "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.47.0.tgz", + "integrity": "sha512-uY5UP7YZM4DMQiiP9Fl4/7O3UbT2p3uI0qvqLXZSGWBfyYuqi2DYQ48ExylgBN3T8AJork+b+mLGq6VXsxBfuw==", "cpu": [ "riscv64" ], @@ -3619,9 +3619,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.45.1.tgz", - "integrity": "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.47.0.tgz", + "integrity": "sha512-qpcN2+/ivq3TcrXtZoHrS9WZplV3Nieh0gvnGb+SFZg7h/YkWsOXINJnjJRWHp9tEur7T8lMnMeQMPS7s9MjUg==", "cpu": [ "riscv64" ], @@ -3632,9 +3632,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.45.1.tgz", - "integrity": "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.47.0.tgz", + "integrity": "sha512-XfuI+o7a2/KA2tBeP+J1CT3siyIQyjpGEL6fFvtUdoHJK1k5iVI3qeGT2i5y6Bb+xQu08AHKBsUGJ2GsOZzXbQ==", "cpu": [ "s390x" ], @@ -3645,9 +3645,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.45.1.tgz", - "integrity": "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.47.0.tgz", + "integrity": "sha512-ylkLO6G7oUiN28mork3caDmgXHqRuopAxjYDaOqs4CoU9pkfR0R/pGQb2V1x2Zg3tlFj4b/DvxZroxC3xALX6g==", "cpu": [ "x64" ], @@ -3658,9 +3658,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.45.1.tgz", - "integrity": "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.47.0.tgz", + "integrity": "sha512-1L72a+ice8xKqJ2afsAVW9EfECOhNMAOC1jH65TgghLaHSFwNzyEdeye+1vRFDNy52OGKip/vajj0ONtX7VpAg==", "cpu": [ "x64" ], @@ -3671,9 +3671,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.45.1.tgz", - "integrity": "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.47.0.tgz", + "integrity": "sha512-wluhdd1uNLk/S+ex2Yj62WFw3un2cZo2ZKXy9cOuoti5IhaPXSDSvxT3os+SJ1cjNorE1PwAOfiJU7QUH6n3Zw==", "cpu": [ "arm64" ], @@ -3684,9 +3684,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.45.1.tgz", - "integrity": "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.47.0.tgz", + "integrity": "sha512-0SMTA6AeG7u2rfwdkKSo6aZD/obmA7oyhR+4ePwLzlwxNE8sfSI9zmjZXtchvBAZmtkVQNt/lZ6RxSl9wBj4pw==", "cpu": [ "ia32" ], @@ -3697,9 +3697,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.1.tgz", - "integrity": "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.47.0.tgz", + "integrity": "sha512-mw1/7kAGxLcfzoG7DIKFHvKr2ZUQasKOPCgT2ubkNZPgIDZOJPymqThtRWEeAlXBoipehP4BUFpBAZIrPhFg8Q==", "cpu": [ "x64" ], @@ -12101,9 +12101,9 @@ } }, "node_modules/rollup": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.1.tgz", - "integrity": "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.47.0.tgz", + "integrity": "sha512-jZVxJwlAptA83ftdZK1kjLZfi0f6o+vVX7ub3HaRzkehLO3l4VB4vYpMHyunhBt1sawv9fiRWPA8Qi/sbg9Kcw==", "dev": true, "dependencies": { "@types/estree": "1.0.8" @@ -12116,26 +12116,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.45.1", - "@rollup/rollup-android-arm64": "4.45.1", - "@rollup/rollup-darwin-arm64": "4.45.1", - "@rollup/rollup-darwin-x64": "4.45.1", - "@rollup/rollup-freebsd-arm64": "4.45.1", - "@rollup/rollup-freebsd-x64": "4.45.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.45.1", - "@rollup/rollup-linux-arm-musleabihf": "4.45.1", - "@rollup/rollup-linux-arm64-gnu": "4.45.1", - "@rollup/rollup-linux-arm64-musl": "4.45.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.45.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.45.1", - "@rollup/rollup-linux-riscv64-gnu": "4.45.1", - "@rollup/rollup-linux-riscv64-musl": "4.45.1", - "@rollup/rollup-linux-s390x-gnu": "4.45.1", - "@rollup/rollup-linux-x64-gnu": "4.45.1", - "@rollup/rollup-linux-x64-musl": "4.45.1", - "@rollup/rollup-win32-arm64-msvc": "4.45.1", - "@rollup/rollup-win32-ia32-msvc": "4.45.1", - "@rollup/rollup-win32-x64-msvc": "4.45.1", + "@rollup/rollup-android-arm-eabi": "4.47.0", + "@rollup/rollup-android-arm64": "4.47.0", + "@rollup/rollup-darwin-arm64": "4.47.0", + "@rollup/rollup-darwin-x64": "4.47.0", + "@rollup/rollup-freebsd-arm64": "4.47.0", + "@rollup/rollup-freebsd-x64": "4.47.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.47.0", + "@rollup/rollup-linux-arm-musleabihf": "4.47.0", + "@rollup/rollup-linux-arm64-gnu": "4.47.0", + "@rollup/rollup-linux-arm64-musl": "4.47.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.47.0", + "@rollup/rollup-linux-ppc64-gnu": "4.47.0", + "@rollup/rollup-linux-riscv64-gnu": "4.47.0", + "@rollup/rollup-linux-riscv64-musl": "4.47.0", + "@rollup/rollup-linux-s390x-gnu": "4.47.0", + "@rollup/rollup-linux-x64-gnu": "4.47.0", + "@rollup/rollup-linux-x64-musl": "4.47.0", + "@rollup/rollup-win32-arm64-msvc": "4.47.0", + "@rollup/rollup-win32-ia32-msvc": "4.47.0", + "@rollup/rollup-win32-x64-msvc": "4.47.0", "fsevents": "~2.3.2" } }, @@ -16846,142 +16846,142 @@ } }, "@rollup/rollup-android-arm-eabi": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.1.tgz", - "integrity": "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.47.0.tgz", + "integrity": "sha512-Weap5hVbZs/yIvUZcFpAmIso8rLmwkO1LesddNjeX28tIhQkAKjRuVgAJ2xpj8wXTny7IZro9aBIgGov0qsL4A==", "dev": true, "optional": true }, "@rollup/rollup-android-arm64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.45.1.tgz", - "integrity": "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.47.0.tgz", + "integrity": "sha512-XcnlqvG5riTJByKX7bZ1ehe48GiF+eNkdnzV0ziLp85XyJ6tLPfhkXHv3e0h3cpZESTQa8IB+ZHhV/r02+8qKw==", "dev": true, "optional": true }, "@rollup/rollup-darwin-arm64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.45.1.tgz", - "integrity": "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.47.0.tgz", + "integrity": "sha512-kZzTIzmzAUOKteh688kN88HNaL7wxwTz9XB5dDK94AQdf9nD+lxm/H5uPKQaawUFS+klBEowqPMUPjBRKGbo/g==", "dev": true, "optional": true }, "@rollup/rollup-darwin-x64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.45.1.tgz", - "integrity": "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.47.0.tgz", + "integrity": "sha512-WaMrgHRbFspYjvycbsbqheBmlsQBLwfZVWv/KFsT212Yz/RjEQ/9KEp1/p0Ef3ZNwbWsylmgf69St66D9NQNHw==", "dev": true, "optional": true }, "@rollup/rollup-freebsd-arm64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.45.1.tgz", - "integrity": "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.47.0.tgz", + "integrity": "sha512-umfYslurvSmAK5MEyOcOGooQ6EBB2pYePQaTVlrOkIfG6uuwu9egYOlxr35lwsp6XG0NzmXW0/5o150LUioMkQ==", "dev": true, "optional": true }, "@rollup/rollup-freebsd-x64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.45.1.tgz", - "integrity": "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.47.0.tgz", + "integrity": "sha512-EFXhIykAl8//4ihOjGNirF89HEUbOB8ev2aiw8ST8wFGwDdIPARh3enDlbp8aFnScl4CDK4DZLQYXaM6qpxzZw==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.45.1.tgz", - "integrity": "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.47.0.tgz", + "integrity": "sha512-EwkC5N61ptruQ9wNkYfLgUWEGh+F3JZSGHkUWhaK2ISAK0d0xmiMKF0trFhRqPQFov5d9DmFiFIhWB5IC79OUA==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm-musleabihf": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.45.1.tgz", - "integrity": "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.47.0.tgz", + "integrity": "sha512-Iz/g1X94vIjppA4H9hN3VEedw4ObC+u+aua2J/VPJnENEJ0GeCAPBN15nJc5pS5M8JPlUhOd3oqhOWX6Un4RHA==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm64-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.45.1.tgz", - "integrity": "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.47.0.tgz", + "integrity": "sha512-eYEYHYjFo/vb6k1l5uq5+Af9yuo9WaST/z+/8T5gkee+A0Sfx1NIPZtKMEQOLjm/oaeHFGpWaAO97gTPhouIfQ==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm64-musl": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.45.1.tgz", - "integrity": "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.47.0.tgz", + "integrity": "sha512-LX2x0/RszFEmDfjzL6kG/vihD5CkpJ+0K6lcbqX0jAopkkXeY2ZjStngdFMFW+BK7pyrqryJgy6Jt3+oyDxrSA==", "dev": true, "optional": true }, "@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.45.1.tgz", - "integrity": "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.47.0.tgz", + "integrity": "sha512-0U+56rJmJvqBCwlPFz/BcxkvdiRdNPamBfuFHrOGQtGajSMJ2OqzlvOgwj5vReRQnSA6XMKw/JL1DaBhceil+g==", "dev": true, "optional": true }, - "@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.45.1.tgz", - "integrity": "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg==", + "@rollup/rollup-linux-ppc64-gnu": { + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.47.0.tgz", + "integrity": "sha512-2VKOsnNyvS05HFPKtmAWtef+nZyKCot/V3Jh/A5sYMhUvtthNjp6CjakYTtc5xZ8J8Fp5FKrUWGxptVtZ2OzEA==", "dev": true, "optional": true }, "@rollup/rollup-linux-riscv64-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.45.1.tgz", - "integrity": "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.47.0.tgz", + "integrity": "sha512-uY5UP7YZM4DMQiiP9Fl4/7O3UbT2p3uI0qvqLXZSGWBfyYuqi2DYQ48ExylgBN3T8AJork+b+mLGq6VXsxBfuw==", "dev": true, "optional": true }, "@rollup/rollup-linux-riscv64-musl": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.45.1.tgz", - "integrity": "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.47.0.tgz", + "integrity": "sha512-qpcN2+/ivq3TcrXtZoHrS9WZplV3Nieh0gvnGb+SFZg7h/YkWsOXINJnjJRWHp9tEur7T8lMnMeQMPS7s9MjUg==", "dev": true, "optional": true }, "@rollup/rollup-linux-s390x-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.45.1.tgz", - "integrity": "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.47.0.tgz", + "integrity": "sha512-XfuI+o7a2/KA2tBeP+J1CT3siyIQyjpGEL6fFvtUdoHJK1k5iVI3qeGT2i5y6Bb+xQu08AHKBsUGJ2GsOZzXbQ==", "dev": true, "optional": true }, "@rollup/rollup-linux-x64-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.45.1.tgz", - "integrity": "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.47.0.tgz", + "integrity": "sha512-ylkLO6G7oUiN28mork3caDmgXHqRuopAxjYDaOqs4CoU9pkfR0R/pGQb2V1x2Zg3tlFj4b/DvxZroxC3xALX6g==", "dev": true, "optional": true }, "@rollup/rollup-linux-x64-musl": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.45.1.tgz", - "integrity": "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.47.0.tgz", + "integrity": "sha512-1L72a+ice8xKqJ2afsAVW9EfECOhNMAOC1jH65TgghLaHSFwNzyEdeye+1vRFDNy52OGKip/vajj0ONtX7VpAg==", "dev": true, "optional": true }, "@rollup/rollup-win32-arm64-msvc": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.45.1.tgz", - "integrity": "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.47.0.tgz", + "integrity": "sha512-wluhdd1uNLk/S+ex2Yj62WFw3un2cZo2ZKXy9cOuoti5IhaPXSDSvxT3os+SJ1cjNorE1PwAOfiJU7QUH6n3Zw==", "dev": true, "optional": true }, "@rollup/rollup-win32-ia32-msvc": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.45.1.tgz", - "integrity": "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.47.0.tgz", + "integrity": "sha512-0SMTA6AeG7u2rfwdkKSo6aZD/obmA7oyhR+4ePwLzlwxNE8sfSI9zmjZXtchvBAZmtkVQNt/lZ6RxSl9wBj4pw==", "dev": true, "optional": true }, "@rollup/rollup-win32-x64-msvc": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.1.tgz", - "integrity": "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA==", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.47.0.tgz", + "integrity": "sha512-mw1/7kAGxLcfzoG7DIKFHvKr2ZUQasKOPCgT2ubkNZPgIDZOJPymqThtRWEeAlXBoipehP4BUFpBAZIrPhFg8Q==", "dev": true, "optional": true }, @@ -23199,31 +23199,31 @@ } }, "rollup": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.1.tgz", - "integrity": "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==", - "dev": true, - "requires": { - "@rollup/rollup-android-arm-eabi": "4.45.1", - "@rollup/rollup-android-arm64": "4.45.1", - "@rollup/rollup-darwin-arm64": "4.45.1", - "@rollup/rollup-darwin-x64": "4.45.1", - "@rollup/rollup-freebsd-arm64": "4.45.1", - "@rollup/rollup-freebsd-x64": "4.45.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.45.1", - "@rollup/rollup-linux-arm-musleabihf": "4.45.1", - "@rollup/rollup-linux-arm64-gnu": "4.45.1", - "@rollup/rollup-linux-arm64-musl": "4.45.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.45.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.45.1", - "@rollup/rollup-linux-riscv64-gnu": "4.45.1", - "@rollup/rollup-linux-riscv64-musl": "4.45.1", - "@rollup/rollup-linux-s390x-gnu": "4.45.1", - "@rollup/rollup-linux-x64-gnu": "4.45.1", - "@rollup/rollup-linux-x64-musl": "4.45.1", - "@rollup/rollup-win32-arm64-msvc": "4.45.1", - "@rollup/rollup-win32-ia32-msvc": "4.45.1", - "@rollup/rollup-win32-x64-msvc": "4.45.1", + "version": "4.47.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.47.0.tgz", + "integrity": "sha512-jZVxJwlAptA83ftdZK1kjLZfi0f6o+vVX7ub3HaRzkehLO3l4VB4vYpMHyunhBt1sawv9fiRWPA8Qi/sbg9Kcw==", + "dev": true, + "requires": { + "@rollup/rollup-android-arm-eabi": "4.47.0", + "@rollup/rollup-android-arm64": "4.47.0", + "@rollup/rollup-darwin-arm64": "4.47.0", + "@rollup/rollup-darwin-x64": "4.47.0", + "@rollup/rollup-freebsd-arm64": "4.47.0", + "@rollup/rollup-freebsd-x64": "4.47.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.47.0", + "@rollup/rollup-linux-arm-musleabihf": "4.47.0", + "@rollup/rollup-linux-arm64-gnu": "4.47.0", + "@rollup/rollup-linux-arm64-musl": "4.47.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.47.0", + "@rollup/rollup-linux-ppc64-gnu": "4.47.0", + "@rollup/rollup-linux-riscv64-gnu": "4.47.0", + "@rollup/rollup-linux-riscv64-musl": "4.47.0", + "@rollup/rollup-linux-s390x-gnu": "4.47.0", + "@rollup/rollup-linux-x64-gnu": "4.47.0", + "@rollup/rollup-linux-x64-musl": "4.47.0", + "@rollup/rollup-win32-arm64-msvc": "4.47.0", + "@rollup/rollup-win32-ia32-msvc": "4.47.0", + "@rollup/rollup-win32-x64-msvc": "4.47.0", "@types/estree": "1.0.8", "fsevents": "~2.3.2" }, diff --git a/package.json b/package.json index 3e327af92d4..46874444557 100644 --- a/package.json +++ b/package.json @@ -124,7 +124,7 @@ "npm-run-all2": "8.0.4", "prettier": "3.6.2", "promise-polyfill": "8.3.0", - "rollup": "4.45.1", + "rollup": "4.47.0", "rollup-plugin-istanbul": "5.0.0", "sauce-connect-launcher": "1.3.2", "selenium-webdriver": "4.34.0", From d68d664e727a4e5c5812b59cf3b0e822aa5bd4d2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 28 Aug 2025 07:10:02 +0000 Subject: [PATCH 20/64] Update dependency selenium-webdriver to v4.35.0 --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 45f9bbf6bd0..a773c12926c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -69,7 +69,7 @@ "rollup": "4.47.0", "rollup-plugin-istanbul": "5.0.0", "sauce-connect-launcher": "1.3.2", - "selenium-webdriver": "4.34.0", + "selenium-webdriver": "4.35.0", "semver": "7.7.2", "sinon": "19.0.5", "sinon-chai": "3.7.0", @@ -12323,9 +12323,9 @@ "dev": true }, "node_modules/selenium-webdriver": { - "version": "4.34.0", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.34.0.tgz", - "integrity": "sha512-zGfQFcsASAv3KrYzYh+iw4fFqB7iZAgHW7BU6rRz7isK1i1X4x3LvjmZad4bUUgHDwTnAhlqTzDh21byB+zHMg==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.35.0.tgz", + "integrity": "sha512-Baaeiuyu7BIIsSYf0SI7Mi55gsNmdI00KM0Hcofw1RnAY+0QEVpdh5yAxueDxgTZS8vcbGZFU0NJ6Qc1riIrLg==", "dev": true, "funding": [ { @@ -23369,9 +23369,9 @@ "dev": true }, "selenium-webdriver": { - "version": "4.34.0", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.34.0.tgz", - "integrity": "sha512-zGfQFcsASAv3KrYzYh+iw4fFqB7iZAgHW7BU6rRz7isK1i1X4x3LvjmZad4bUUgHDwTnAhlqTzDh21byB+zHMg==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.35.0.tgz", + "integrity": "sha512-Baaeiuyu7BIIsSYf0SI7Mi55gsNmdI00KM0Hcofw1RnAY+0QEVpdh5yAxueDxgTZS8vcbGZFU0NJ6Qc1riIrLg==", "dev": true, "requires": { "@bazel/runfiles": "^6.3.1", diff --git a/package.json b/package.json index 46874444557..627175ba0b8 100644 --- a/package.json +++ b/package.json @@ -127,7 +127,7 @@ "rollup": "4.47.0", "rollup-plugin-istanbul": "5.0.0", "sauce-connect-launcher": "1.3.2", - "selenium-webdriver": "4.34.0", + "selenium-webdriver": "4.35.0", "semver": "7.7.2", "sinon": "19.0.5", "sinon-chai": "3.7.0", From 77b068fcee01b4b825ea5ce67f6c9339d6c5e4cf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 28 Aug 2025 07:50:03 +0000 Subject: [PATCH 21/64] Update dependency chromedriver to v138.0.5 --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index a773c12926c..d74de906d73 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,7 @@ "babel-plugin-transform-remove-console": "6.9.4", "chai": "4.5.0", "chart.js": "2.9.4", - "chromedriver": "138.0.3", + "chromedriver": "138.0.5", "doctoc": "2.2.1", "es-check": "9.1.4", "eslint": "8.57.1", @@ -5479,9 +5479,9 @@ } }, "node_modules/chromedriver": { - "version": "138.0.3", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-138.0.3.tgz", - "integrity": "sha512-RKcfzbUthmQzFmy91F9StQQwNZ72khp3febF/RntpkDKhhCkwor0cgop00diwzAVSUq1s2e8B54Iema9FQnynw==", + "version": "138.0.5", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-138.0.5.tgz", + "integrity": "sha512-WE5O09if9TmFfIpvydt5dyhj+TNTUttvnujoRtAShQuDghulSh1HFirBnjNrAWjEoMkXn9VUw+cCYzZ597VPJQ==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -18357,9 +18357,9 @@ "peer": true }, "chromedriver": { - "version": "138.0.3", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-138.0.3.tgz", - "integrity": "sha512-RKcfzbUthmQzFmy91F9StQQwNZ72khp3febF/RntpkDKhhCkwor0cgop00diwzAVSUq1s2e8B54Iema9FQnynw==", + "version": "138.0.5", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-138.0.5.tgz", + "integrity": "sha512-WE5O09if9TmFfIpvydt5dyhj+TNTUttvnujoRtAShQuDghulSh1HFirBnjNrAWjEoMkXn9VUw+cCYzZ597VPJQ==", "dev": true, "requires": { "@testim/chrome-version": "^1.1.4", diff --git a/package.json b/package.json index 627175ba0b8..46e2b5ee66e 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "babel-plugin-transform-remove-console": "6.9.4", "chai": "4.5.0", "chart.js": "2.9.4", - "chromedriver": "138.0.3", + "chromedriver": "138.0.5", "doctoc": "2.2.1", "es-check": "9.1.4", "eslint": "8.57.1", From 8b5be49a2aa7ae44784b99d6c8361d9c19510cd5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 28 Aug 2025 08:13:48 +0000 Subject: [PATCH 22/64] Update dependency es-check to v9.3.1 --- package-lock.json | 44 ++++++++++++++++++++++---------------------- package.json | 2 +- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/package-lock.json b/package-lock.json index d74de906d73..86cfdeed821 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,7 +38,7 @@ "chart.js": "2.9.4", "chromedriver": "138.0.5", "doctoc": "2.2.1", - "es-check": "9.1.4", + "es-check": "9.3.1", "eslint": "8.57.1", "eslint-config-prettier": "10.1.8", "eslint-plugin-import": "2.32.0", @@ -6390,12 +6390,12 @@ } }, "node_modules/es-check": { - "version": "9.1.4", - "resolved": "https://registry.npmjs.org/es-check/-/es-check-9.1.4.tgz", - "integrity": "sha512-oCElzPyMZTm7IpCbdBslOjPdpI3uoccctPVTJLJyi34k+nXSaqApMAfm0yleEKTyPGXkmtWwPFBSyN22cKUhJw==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/es-check/-/es-check-9.3.1.tgz", + "integrity": "sha512-9cDv061bddtgqtqc6xbxA1mfwNYQsNGyaiElpQhcNycTaW7Pnpk183jy+3eOjhMSbZCy/RqCfu2lZZufrlo2mA==", "dev": true, "dependencies": { - "acorn": "8.14.1", + "acorn": "8.15.0", "acorn-walk": "^8.3.4", "browserslist": "^4.23.3", "commander": "14.0.0", @@ -6413,9 +6413,9 @@ } }, "node_modules/es-check/node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -6446,12 +6446,12 @@ } }, "node_modules/es-check/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", "dev": true, "engines": { - "node": ">= 8" + "node": ">= 12" } }, "node_modules/es-check/node_modules/supports-color": { @@ -19064,12 +19064,12 @@ } }, "es-check": { - "version": "9.1.4", - "resolved": "https://registry.npmjs.org/es-check/-/es-check-9.1.4.tgz", - "integrity": "sha512-oCElzPyMZTm7IpCbdBslOjPdpI3uoccctPVTJLJyi34k+nXSaqApMAfm0yleEKTyPGXkmtWwPFBSyN22cKUhJw==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/es-check/-/es-check-9.3.1.tgz", + "integrity": "sha512-9cDv061bddtgqtqc6xbxA1mfwNYQsNGyaiElpQhcNycTaW7Pnpk183jy+3eOjhMSbZCy/RqCfu2lZZufrlo2mA==", "dev": true, "requires": { - "acorn": "8.14.1", + "acorn": "8.15.0", "acorn-walk": "^8.3.4", "browserslist": "^4.23.3", "commander": "14.0.0", @@ -19081,9 +19081,9 @@ }, "dependencies": { "acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true }, "acorn-walk": { @@ -19102,9 +19102,9 @@ "dev": true }, "source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", "dev": true }, "supports-color": { diff --git a/package.json b/package.json index 46e2b5ee66e..410c5a64cfc 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ "chart.js": "2.9.4", "chromedriver": "138.0.5", "doctoc": "2.2.1", - "es-check": "9.1.4", + "es-check": "9.3.1", "eslint": "8.57.1", "eslint-config-prettier": "10.1.8", "eslint-plugin-import": "2.32.0", From e1fd0c6de0149f00fb8f5048a49f8f49081e5cb5 Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Thu, 28 Aug 2025 10:09:19 -0700 Subject: [PATCH 23/64] Restrict the use of Array every (Android browser). Recommend negative `some` call. --- .eslintrc.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index b336a58425e..1a0d2722a64 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,9 +3,11 @@ const asyncKeywordConstraintMsg = const selfVsWindowGlobalMsg = 'Use `self` instead of `window` to access the global context everywhere (including workers).'; const arrayFindCompatibilityMsg = - 'Usage of Array find methods is restricted for compatibility.'; + 'Usage of Array find method is restricted for compatibility.'; const arrayFindIndexCompatibilityMsg = - 'Usage of Array findIndex methods is restricted for compatibility.'; + 'Usage of Array findIndex method is restricted for compatibility.'; +const arrayEveryCompatibilityMsg = + 'Usage of Array every method is restricted for compatibility. Use a negative Array some check instead.'; module.exports = { env: { browser: true, commonjs: true, es6: true }, @@ -93,6 +95,11 @@ module.exports = { 'MemberExpression[property.name="findIndex"][object.type="Identifier"]', message: arrayFindIndexCompatibilityMsg, }, + { + selector: + 'MemberExpression[property.name="every"][object.type="Identifier"]', + message: arrayEveryCompatibilityMsg, + }, ], 'import/order': [ 'warn', From c5221c76881d4a83cb12eda7df7ee94583634a60 Mon Sep 17 00:00:00 2001 From: Benjamin Clos Date: Thu, 28 Aug 2025 11:12:26 -0600 Subject: [PATCH 24/64] refactor: remove usage of any for networkDetails; (#7458) * refactor: remove usage of any for networkDetails; * chore: use new Response in unit tests; --- api-extractor/report/hls.js.api.md | 28 ++++++++++--------- src/controller/content-steering-controller.ts | 7 +++-- src/loader/fragment-loader.ts | 3 +- src/loader/interstitial-asset-list.ts | 9 +++--- src/loader/key-loader.ts | 13 +++++---- src/loader/playlist-loader.ts | 16 +++++++---- src/types/events.ts | 13 +++++---- src/types/loader.ts | 11 ++++---- src/types/network-details.ts | 3 ++ .../controller/audio-stream-controller.ts | 2 +- .../controller/interstitials-controller.ts | 12 ++++---- tests/unit/controller/level-controller.ts | 14 +++++----- tests/unit/controller/level-helper.ts | 4 +-- tests/unit/controller/stream-controller.ts | 8 +++--- .../controller/subtitle-track-controller.ts | 10 +++---- 15 files changed, 84 insertions(+), 69 deletions(-) create mode 100644 src/types/network-details.ts diff --git a/api-extractor/report/hls.js.api.md b/api-extractor/report/hls.js.api.md index efcc3d551ee..5f9595868cb 100644 --- a/api-extractor/report/hls.js.api.md +++ b/api-extractor/report/hls.js.api.md @@ -99,8 +99,10 @@ export interface AssetListLoadedData { assetListResponse: AssetListJSON; // (undocumented) event: InterstitialEventWithAssetList; + // Warning: (ae-forgotten-export) The symbol "NullableNetworkDetails" needs to be exported by the entry point hls.d.ts + // // (undocumented) - networkDetails: any; + networkDetails: NullableNetworkDetails; } // Warning: (ae-missing-release-tag) "AssetListLoadingData" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -1269,7 +1271,7 @@ export interface ErrorData { // (undocumented) mimeType?: string; // (undocumented) - networkDetails?: any; + networkDetails?: NullableNetworkDetails; // (undocumented) parent?: PlaylistLevelType; // (undocumented) @@ -1717,7 +1719,7 @@ export interface FragLoadedData { // (undocumented) frag: Fragment; // (undocumented) - networkDetails: unknown; + networkDetails: NullableNetworkDetails; // (undocumented) part: Part | null; // (undocumented) @@ -1743,7 +1745,7 @@ export interface FragLoadFailResult extends ErrorData { // (undocumented) frag: Fragment; // (undocumented) - networkDetails: any; + networkDetails: NullableNetworkDetails; // (undocumented) part?: Part; // (undocumented) @@ -2990,7 +2992,7 @@ export class KeyLoader extends Logger implements ComponentAPI { // (undocumented) abort(type?: PlaylistLevelType): void; // (undocumented) - createKeyLoadError(frag: Fragment, details: ErrorDetails | undefined, error: Error, networkDetails?: any, response?: { + createKeyLoadError(frag: Fragment, details: ErrorDetails | undefined, error: Error, networkDetails?: NullableNetworkDetails, response?: { url: string; data: undefined; code: number; @@ -3397,7 +3399,7 @@ export interface LevelLoadedData { // (undocumented) levelInfo: Level; // (undocumented) - networkDetails: any; + networkDetails: NullableNetworkDetails; // (undocumented) stats: LoaderStats; // (undocumented) @@ -3639,7 +3641,7 @@ export interface LoaderContext { // Warning: (ae-missing-release-tag) "LoaderOnAbort" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type LoaderOnAbort = (stats: LoaderStats, context: T, networkDetails: any) => void; +export type LoaderOnAbort = (stats: LoaderStats, context: T, networkDetails: NullableNetworkDetails) => void; // Warning: (ae-missing-release-tag) "LoaderOnError" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -3647,22 +3649,22 @@ export type LoaderOnAbort = (stats: LoaderStats, contex export type LoaderOnError = (error: { code: number; text: string; -}, context: T, networkDetails: any, stats: LoaderStats) => void; +}, context: T, networkDetails: NullableNetworkDetails, stats: LoaderStats) => void; // Warning: (ae-missing-release-tag) "LoaderOnProgress" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type LoaderOnProgress = (stats: LoaderStats, context: T, data: string | ArrayBuffer, networkDetails: any) => void; +export type LoaderOnProgress = (stats: LoaderStats, context: T, data: string | ArrayBuffer, networkDetails: NullableNetworkDetails) => void; // Warning: (ae-missing-release-tag) "LoaderOnSuccess" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type LoaderOnSuccess = (response: LoaderResponse, stats: LoaderStats, context: T, networkDetails: any) => void; +export type LoaderOnSuccess = (response: LoaderResponse, stats: LoaderStats, context: T, networkDetails: NullableNetworkDetails) => void; // Warning: (ae-missing-release-tag) "LoaderOnTimeout" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type LoaderOnTimeout = (stats: LoaderStats, context: T, networkDetails: any) => void; +export type LoaderOnTimeout = (stats: LoaderStats, context: T, networkDetails: NullableNetworkDetails) => void; // Warning: (ae-missing-release-tag) "LoaderResponse" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -3811,7 +3813,7 @@ export interface ManifestLoadedData { // (undocumented) levels: LevelParsed[]; // (undocumented) - networkDetails: any; + networkDetails: NullableNetworkDetails; // (undocumented) sessionData: Record | null; // (undocumented) @@ -4840,7 +4842,7 @@ export interface TrackLoadedData { // (undocumented) id: number; // (undocumented) - networkDetails: any; + networkDetails: NullableNetworkDetails; // (undocumented) stats: LoaderStats; // (undocumented) diff --git a/src/controller/content-steering-controller.ts b/src/controller/content-steering-controller.ts index 098f3fb7d25..ac7b5020d86 100644 --- a/src/controller/content-steering-controller.ts +++ b/src/controller/content-steering-controller.ts @@ -25,6 +25,7 @@ import type { SteeringManifestLoadedData, } from '../types/events'; import type { MediaAttributes, MediaPlaylist } from '../types/media-playlist'; +import type { NullableNetworkDetails } from '../types/network-details'; export type SteeringManifest = { VERSION: 1; @@ -456,7 +457,7 @@ export default class ContentSteeringController response: LoaderResponse, stats: LoaderStats, context: LoaderContext, - networkDetails: any, + networkDetails: NullableNetworkDetails, ) => { this.log(`Loaded steering manifest: "${url}"`); const steeringData = response.data as SteeringManifest; @@ -501,7 +502,7 @@ export default class ContentSteeringController onError: ( error: { code: number; text: string }, context: LoaderContext, - networkDetails: any, + networkDetails: NullableNetworkDetails, stats: LoaderStats, ) => { this.log( @@ -531,7 +532,7 @@ export default class ContentSteeringController onTimeout: ( stats: LoaderStats, context: LoaderContext, - networkDetails: any, + networkDetails: NullableNetworkDetails, ) => { this.log(`Timeout loading steering manifest (${context.url})`); this.scheduleRefresh(this.uri || context.url); diff --git a/src/loader/fragment-loader.ts b/src/loader/fragment-loader.ts index 4802df133b1..56b2192b7c8 100644 --- a/src/loader/fragment-loader.ts +++ b/src/loader/fragment-loader.ts @@ -13,6 +13,7 @@ import type { LoaderCallbacks, LoaderConfiguration, } from '../types/loader'; +import type { NullableNetworkDetails } from '../types/network-details'; const MIN_CHUNK_SIZE = Math.pow(2, 17); // 128kb @@ -394,7 +395,7 @@ export interface FragLoadFailResult extends ErrorData { text: string; url: string; }; - networkDetails: any; + networkDetails: NullableNetworkDetails; } export type FragmentLoadProgressCallback = ( diff --git a/src/loader/interstitial-asset-list.ts b/src/loader/interstitial-asset-list.ts index 67fbaa6b98a..e20c82fa3a8 100644 --- a/src/loader/interstitial-asset-list.ts +++ b/src/loader/interstitial-asset-list.ts @@ -16,6 +16,7 @@ import type { LoaderResponse, LoaderStats, } from '../types/loader'; +import type { NullableNetworkDetails } from '../types/network-details'; export class AssetListLoader { private hls: Hls; @@ -74,7 +75,7 @@ export class AssetListLoader { response: LoaderResponse, stats: LoaderStats, context: LoaderContext, - networkDetails: any, + networkDetails: NullableNetworkDetails, ) => { const assetListResponse = response.data as AssetListJSON; const assets = assetListResponse?.ASSETS; @@ -100,7 +101,7 @@ export class AssetListLoader { onError: ( error: { code: number; text: string }, context: LoaderContext, - networkDetails: any, + networkDetails: NullableNetworkDetails, stats: LoaderStats, ) => { const errorData = this.assignAssetListError( @@ -118,7 +119,7 @@ export class AssetListLoader { onTimeout: ( stats: LoaderStats, context: LoaderContext, - networkDetails: any, + networkDetails: NullableNetworkDetails, ) => { const errorData = this.assignAssetListError( interstitial, @@ -144,7 +145,7 @@ export class AssetListLoader { error: Error, url: string, stats?: LoaderStats, - networkDetails?: any, + networkDetails?: NullableNetworkDetails, ): ErrorData { interstitial.error = error; return { diff --git a/src/loader/key-loader.ts b/src/loader/key-loader.ts index 80f4e72b8c5..c2be92e08e0 100644 --- a/src/loader/key-loader.ts +++ b/src/loader/key-loader.ts @@ -22,6 +22,7 @@ import type { LoaderStats, PlaylistLevelType, } from '../types/loader'; +import type { NullableNetworkDetails } from '../types/network-details'; import type { ILogger } from '../utils/logger'; import type { KeySystemFormats } from '../utils/mediakeys-helper'; @@ -81,7 +82,7 @@ export default class KeyLoader extends Logger implements ComponentAPI { frag: Fragment, details: ErrorDetails = ErrorDetails.KEY_LOAD_ERROR, error: Error, - networkDetails?: any, + networkDetails?: NullableNetworkDetails, response?: { url: string; data: undefined; code: number; text: string }, ): LoadError { return new LoadError({ @@ -91,7 +92,7 @@ export default class KeyLoader extends Logger implements ComponentAPI { frag, response, error, - networkDetails, + networkDetails: networkDetails || null, }); } @@ -304,7 +305,7 @@ export default class KeyLoader extends Logger implements ComponentAPI { response: LoaderResponse, stats: LoaderStats, context: KeyLoaderContext, - networkDetails: any, + networkDetails: NullableNetworkDetails, ) => { const { frag, keyInfo, url: uri } = context; const id = getKeyId(keyInfo.decryptdata) || uri; @@ -332,7 +333,7 @@ export default class KeyLoader extends Logger implements ComponentAPI { onError: ( response: { code: number; text: string }, context: KeyLoaderContext, - networkDetails: any, + networkDetails: NullableNetworkDetails, stats: LoaderStats, ) => { this.resetLoader(context); @@ -352,7 +353,7 @@ export default class KeyLoader extends Logger implements ComponentAPI { onTimeout: ( stats: LoaderStats, context: KeyLoaderContext, - networkDetails: any, + networkDetails: NullableNetworkDetails, ) => { this.resetLoader(context); reject( @@ -368,7 +369,7 @@ export default class KeyLoader extends Logger implements ComponentAPI { onAbort: ( stats: LoaderStats, context: KeyLoaderContext, - networkDetails: any, + networkDetails: NullableNetworkDetails, ) => { this.resetLoader(context); reject( diff --git a/src/loader/playlist-loader.ts b/src/loader/playlist-loader.ts index b589ad1f8fc..f829e491e11 100644 --- a/src/loader/playlist-loader.ts +++ b/src/loader/playlist-loader.ts @@ -34,6 +34,7 @@ import type { PlaylistLoaderContext, } from '../types/loader'; import type { MediaAttributes, MediaPlaylist } from '../types/media-playlist'; +import type { NullableNetworkDetails } from '../types/network-details'; function mapContextToLevelType( context: PlaylistLoaderContext, @@ -413,7 +414,7 @@ class PlaylistLoader implements NetworkComponentAPI { response: LoaderResponse, stats: LoaderStats, context: PlaylistLoaderContext, - networkDetails: any, + networkDetails: NullableNetworkDetails, ): void { const hls = this.hls; const string = response.data as string; @@ -503,7 +504,7 @@ class PlaylistLoader implements NetworkComponentAPI { response: LoaderResponse, stats: LoaderStats, context: PlaylistLoaderContext, - networkDetails: any, + networkDetails: NullableNetworkDetails, loader: Loader | undefined, ): void { const hls = this.hls; @@ -574,7 +575,7 @@ class PlaylistLoader implements NetworkComponentAPI { response: LoaderResponse, context: PlaylistLoaderContext, error: Error, - networkDetails: any, + networkDetails: NullableNetworkDetails, stats: LoaderStats, ): void { this.hls.trigger(Events.ERROR, { @@ -594,7 +595,7 @@ class PlaylistLoader implements NetworkComponentAPI { private handleNetworkError( context: PlaylistLoaderContext, - networkDetails: any, + networkDetails: NullableNetworkDetails, timeout = false, response: { code: number; text: string } | undefined, stats: LoaderStats, @@ -663,7 +664,10 @@ class PlaylistLoader implements NetworkComponentAPI { }; if (response) { - const url = networkDetails?.url || context.url; + let url = context.url; + if (networkDetails && 'url' in networkDetails) { + url = networkDetails.url; + } errorData.response = { url, data: undefined as any, ...response }; } @@ -675,7 +679,7 @@ class PlaylistLoader implements NetworkComponentAPI { response: LoaderResponse, stats: LoaderStats, context: PlaylistLoaderContext, - networkDetails: any, + networkDetails: NullableNetworkDetails, loader: Loader | undefined, ): void { const hls = this.hls; diff --git a/src/types/events.ts b/src/types/events.ts index d3f5943dd93..186da9ae0f0 100644 --- a/src/types/events.ts +++ b/src/types/events.ts @@ -35,6 +35,7 @@ import type { } from '../controller/interstitials-schedule'; import type { ErrorDetails, ErrorTypes } from '../errors'; import type { HlsListeners } from '../events'; +import type { NullableNetworkDetails } from './network-details'; import type { Fragment, MediaFragment, Part } from '../loader/fragment'; import type { AssetListJSON, @@ -132,7 +133,7 @@ export interface ManifestLoadedData { captions?: MediaPlaylist[]; contentSteering: ContentSteeringOptions | null; levels: LevelParsed[]; - networkDetails: any; + networkDetails: NullableNetworkDetails; sessionData: Record | null; sessionKeys: LevelKey[] | null; startTimeOffset: number | null; @@ -208,7 +209,7 @@ export interface TrackLoadedData { details: LevelDetails; id: number; groupId: string; - networkDetails: any; + networkDetails: NullableNetworkDetails; stats: LoaderStats; deliveryDirectives: HlsUrlParameters | null; track: MediaPlaylist; @@ -219,7 +220,7 @@ export interface LevelLoadedData { id: number; level: number; levelInfo: Level; - networkDetails: any; + networkDetails: NullableNetworkDetails; stats: LoaderStats; deliveryDirectives: HlsUrlParameters | null; withoutMultiVariant?: boolean; @@ -326,7 +327,7 @@ export interface ErrorData { level?: number | undefined; levelRetry?: boolean; loader?: Loader; - networkDetails?: any; + networkDetails?: NullableNetworkDetails; stalled?: { start: number }; stats?: LoaderStats; mimeType?: string; @@ -394,7 +395,7 @@ export interface FragLoadedData { frag: Fragment; part: Part | null; payload: ArrayBuffer; - networkDetails: unknown; + networkDetails: NullableNetworkDetails; } export interface PartsLoadedData { @@ -474,7 +475,7 @@ export interface AssetListLoadingData { export interface AssetListLoadedData { event: InterstitialEventWithAssetList; assetListResponse: AssetListJSON; - networkDetails: any; + networkDetails: NullableNetworkDetails; } export interface InterstitialsUpdatedData { diff --git a/src/types/loader.ts b/src/types/loader.ts index 8040e214aa6..20e1c156940 100644 --- a/src/types/loader.ts +++ b/src/types/loader.ts @@ -1,6 +1,7 @@ import type { LoaderConfig } from '../config'; import type { HlsUrlParameters, Level } from './level'; import type { MediaPlaylist } from './media-playlist'; +import type { NullableNetworkDetails } from './network-details'; import type { Fragment } from '../loader/fragment'; import type { Part } from '../loader/fragment'; import type { KeyLoaderInfo } from '../loader/key-loader'; @@ -100,14 +101,14 @@ export type LoaderOnSuccess = ( response: LoaderResponse, stats: LoaderStats, context: T, - networkDetails: any, + networkDetails: NullableNetworkDetails, ) => void; export type LoaderOnProgress = ( stats: LoaderStats, context: T, data: string | ArrayBuffer, - networkDetails: any, + networkDetails: NullableNetworkDetails, ) => void; export type LoaderOnError = ( @@ -118,20 +119,20 @@ export type LoaderOnError = ( text: string; }, context: T, - networkDetails: any, + networkDetails: NullableNetworkDetails, stats: LoaderStats, ) => void; export type LoaderOnTimeout = ( stats: LoaderStats, context: T, - networkDetails: any, + networkDetails: NullableNetworkDetails, ) => void; export type LoaderOnAbort = ( stats: LoaderStats, context: T, - networkDetails: any, + networkDetails: NullableNetworkDetails, ) => void; export interface LoaderCallbacks { diff --git a/src/types/network-details.ts b/src/types/network-details.ts new file mode 100644 index 00000000000..08879267771 --- /dev/null +++ b/src/types/network-details.ts @@ -0,0 +1,3 @@ +export type NetworkDetails = Response | XMLHttpRequest; + +export type NullableNetworkDetails = NetworkDetails | null; diff --git a/tests/unit/controller/audio-stream-controller.ts b/tests/unit/controller/audio-stream-controller.ts index d1c24b81d68..aae6d9f79f1 100644 --- a/tests/unit/controller/audio-stream-controller.ts +++ b/tests/unit/controller/audio-stream-controller.ts @@ -212,7 +212,7 @@ describe('AudioStreamController', function () { endSN, } as unknown as LevelDetails, id: 0, - networkDetails: {}, + networkDetails: new Response('ok'), stats: new LoadStats(), deliveryDirectives: null, }; diff --git a/tests/unit/controller/interstitials-controller.ts b/tests/unit/controller/interstitials-controller.ts index 2db36842684..f20f61014d6 100644 --- a/tests/unit/controller/interstitials-controller.ts +++ b/tests/unit/controller/interstitials-controller.ts @@ -1182,7 +1182,7 @@ fileSequence3.mp4 hls.trigger(Events.ASSET_LIST_LOADED, { event: interstitial, assetListResponse: interstitial.assetListResponse, - networkDetails: {}, + networkDetails: new Response('ok'), }); const callsAfterAttach = getTriggerCalls(); expect(callsAfterAttach).to.deep.equal( @@ -1249,7 +1249,7 @@ fileSequence3.mp4 hls.trigger(Events.ASSET_LIST_LOADED, { event: interstitial, assetListResponse: interstitial.assetListResponse, - networkDetails: {}, + networkDetails: new Response('ok'), }); const callsAfterAttach = getTriggerCalls(); expect(callsAfterAttach).to.deep.equal( @@ -1337,7 +1337,7 @@ fileSequence6.mp4`; hls.trigger(Events.ASSET_LIST_LOADED, { event: interstitial, assetListResponse: interstitial.assetListResponse, - networkDetails: {}, + networkDetails: new Response('ok'), }); const callsAfterAttach = getTriggerCalls(); expect(callsAfterAttach).to.deep.equal( @@ -1451,7 +1451,7 @@ fileSequence6.mp4`; hls.trigger(Events.ASSET_LIST_LOADED, { event: interstitial, assetListResponse: interstitial.assetListResponse, - networkDetails: {}, + networkDetails: new Response('ok'), }); const callsAfterAttach = getTriggerCalls(); expect(callsAfterAttach).to.deep.equal( @@ -1828,7 +1828,7 @@ fileSequence6.mp4 hls.trigger(Events.ASSET_LIST_LOADED, { event: interstitial, assetListResponse: interstitial.assetListResponse, - networkDetails: {}, + networkDetails: new Response('ok'), }); const callsAfterAssetsLoaded = getTriggerCalls(); expect(callsAfterAssetsLoaded).to.deep.equal( @@ -2003,7 +2003,7 @@ fileSequence6.mp4`; hls.trigger(Events.ASSET_LIST_LOADED, { event: interstitial, assetListResponse: interstitial.assetListResponse, - networkDetails: {}, + networkDetails: new Response('ok'), }); const eventsAfterAssetListLoaded = getTriggerCalls(); expect(eventsAfterAssetListLoaded).to.deep.equal( diff --git a/tests/unit/controller/level-controller.ts b/tests/unit/controller/level-controller.ts index 0a2b1e660b2..812d0e0580b 100755 --- a/tests/unit/controller/level-controller.ts +++ b/tests/unit/controller/level-controller.ts @@ -117,7 +117,7 @@ describe('LevelController', function () { name: '1080', }), ], - networkDetails: '', + networkDetails: new Response('ok'), sessionData: null, sessionKeys: null, contentSteering: null, @@ -186,7 +186,7 @@ describe('LevelController', function () { levelController.onManifestLoaded(Events.MANIFEST_LOADED, { audioTracks: [], levels: [], - networkDetails: '', + networkDetails: new Response('ok'), subtitles: [], url: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8', }); @@ -232,7 +232,7 @@ describe('LevelController', function () { name: '1080', }), ], - networkDetails: '', + networkDetails: new Response('ok'), subtitles: [], sessionData: null, sessionKeys: null, @@ -317,7 +317,7 @@ http://bar.example.com/audio-only/prog_index.m3u8`, name: '144', }), ], - networkDetails: '', + networkDetails: new Response('ok'), subtitles: [], sessionData: null, sessionKeys: null, @@ -361,7 +361,7 @@ http://bar.example.com/audio-only/prog_index.m3u8`, audioCodec: 'mp4a.40.2', }), ], - networkDetails: '', + networkDetails: new Response('ok'), subtitles: [], sessionData: null, sessionKeys: null, @@ -400,7 +400,7 @@ http://bar.example.com/audio-only/prog_index.m3u8`, audioCodec: 'mp4a.40.2', }), ], - networkDetails: '', + networkDetails: new Response('ok'), subtitles: [], sessionData: null, sessionKeys: null, @@ -438,7 +438,7 @@ http://bar.example.com/audio-only/prog_index.m3u8`, name: '144', }), ], - networkDetails: '', + networkDetails: new Response('ok'), subtitles: [], sessionData: null, sessionKeys: null, diff --git a/tests/unit/controller/level-helper.ts b/tests/unit/controller/level-helper.ts index d8f932f8d89..cbc5ae1ab0e 100644 --- a/tests/unit/controller/level-helper.ts +++ b/tests/unit/controller/level-helper.ts @@ -1518,7 +1518,7 @@ audio_5441.m4s`; level: 0, id: 0, stats: new LoadStats(), - networkDetails: {}, + networkDetails: new Response('ok'), deliveryDirectives: null, }); } @@ -1530,7 +1530,7 @@ audio_5441.m4s`; id: 0, groupId: '', stats: new LoadStats(), - networkDetails: {}, + networkDetails: new Response('ok'), deliveryDirectives: null, }); } diff --git a/tests/unit/controller/stream-controller.ts b/tests/unit/controller/stream-controller.ts index 8bee6813a4c..af873be45c1 100644 --- a/tests/unit/controller/stream-controller.ts +++ b/tests/unit/controller/stream-controller.ts @@ -90,7 +90,7 @@ describe('StreamController', function () { contentSteering, url: 'http://www.example.com', stats: new LoadStats(), - networkDetails: {}, + networkDetails: new Response('ok'), sessionData, sessionKeys, startTimeOffset, @@ -135,7 +135,7 @@ describe('StreamController', function () { details, id: 0, level: 0, - networkDetails: {}, + networkDetails: new Response('ok'), stats: new LoadStats(), deliveryDirectives: null, levelInfo: new Level({ @@ -169,7 +169,7 @@ describe('StreamController', function () { contentSteering, url: 'http://www.example.com', stats: new LoadStats(), - networkDetails: {}, + networkDetails: new Response('ok'), sessionData, sessionKeys, startTimeOffset, @@ -186,7 +186,7 @@ describe('StreamController', function () { details, id: 0, level: 0, - networkDetails: {}, + networkDetails: new Response('ok'), stats: new LoadStats(), deliveryDirectives: null, levelInfo: new Level({ diff --git a/tests/unit/controller/subtitle-track-controller.ts b/tests/unit/controller/subtitle-track-controller.ts index 3d08399f617..f3a41cf0416 100644 --- a/tests/unit/controller/subtitle-track-controller.ts +++ b/tests/unit/controller/subtitle-track-controller.ts @@ -494,7 +494,7 @@ describe('SubtitleTrackController', function () { groupId: 'default-text-group', details: { foo: 'bar' } as any, stats: new LoadStats(), - networkDetails: {}, + networkDetails: new Response('ok'), deliveryDirectives: null, track: {} as any, }; @@ -527,7 +527,7 @@ describe('SubtitleTrackController', function () { groupId: 'default-text-group', details: { foo: 'bar' } as any, stats: new LoadStats(), - networkDetails: {}, + networkDetails: new Response('ok'), deliveryDirectives: null, track: {} as any, }; @@ -551,7 +551,7 @@ describe('SubtitleTrackController', function () { groupId: 'default-text-group', details, stats: new LoadStats(), - networkDetails: {}, + networkDetails: new Response('ok'), deliveryDirectives: null, track: {} as any, }); @@ -568,7 +568,7 @@ describe('SubtitleTrackController', function () { groupId: 'default-text-group', details, stats: new LoadStats(), - networkDetails: {}, + networkDetails: new Response('ok'), deliveryDirectives: null, track: {} as any, }); @@ -586,7 +586,7 @@ describe('SubtitleTrackController', function () { groupId: 'default-text-group', details, stats: new LoadStats(), - networkDetails: {}, + networkDetails: new Response('ok'), deliveryDirectives: null, track: {} as any, }); From 8c332eea20f8936467753b5aecab2bbe3608aa9b Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Thu, 28 Aug 2025 10:49:33 -0700 Subject: [PATCH 25/64] Revert "Update dependency rollup to v4.47.0" This reverts commit 2c2e0284dc05503025a0a8f23299cad66a3e820e. --- package-lock.json | 342 +++++++++++++++++++++++----------------------- package.json | 2 +- 2 files changed, 172 insertions(+), 172 deletions(-) diff --git a/package-lock.json b/package-lock.json index 86cfdeed821..fdcd5669905 100644 --- a/package-lock.json +++ b/package-lock.json @@ -66,7 +66,7 @@ "npm-run-all2": "8.0.4", "prettier": "3.6.2", "promise-polyfill": "8.3.0", - "rollup": "4.47.0", + "rollup": "4.45.1", "rollup-plugin-istanbul": "5.0.0", "sauce-connect-launcher": "1.3.2", "selenium-webdriver": "4.35.0", @@ -3450,9 +3450,9 @@ "dev": true }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.47.0.tgz", - "integrity": "sha512-Weap5hVbZs/yIvUZcFpAmIso8rLmwkO1LesddNjeX28tIhQkAKjRuVgAJ2xpj8wXTny7IZro9aBIgGov0qsL4A==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.1.tgz", + "integrity": "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==", "cpu": [ "arm" ], @@ -3463,9 +3463,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.47.0.tgz", - "integrity": "sha512-XcnlqvG5riTJByKX7bZ1ehe48GiF+eNkdnzV0ziLp85XyJ6tLPfhkXHv3e0h3cpZESTQa8IB+ZHhV/r02+8qKw==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.45.1.tgz", + "integrity": "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ==", "cpu": [ "arm64" ], @@ -3476,9 +3476,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.47.0.tgz", - "integrity": "sha512-kZzTIzmzAUOKteh688kN88HNaL7wxwTz9XB5dDK94AQdf9nD+lxm/H5uPKQaawUFS+klBEowqPMUPjBRKGbo/g==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.45.1.tgz", + "integrity": "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA==", "cpu": [ "arm64" ], @@ -3489,9 +3489,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.47.0.tgz", - "integrity": "sha512-WaMrgHRbFspYjvycbsbqheBmlsQBLwfZVWv/KFsT212Yz/RjEQ/9KEp1/p0Ef3ZNwbWsylmgf69St66D9NQNHw==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.45.1.tgz", + "integrity": "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og==", "cpu": [ "x64" ], @@ -3502,9 +3502,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.47.0.tgz", - "integrity": "sha512-umfYslurvSmAK5MEyOcOGooQ6EBB2pYePQaTVlrOkIfG6uuwu9egYOlxr35lwsp6XG0NzmXW0/5o150LUioMkQ==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.45.1.tgz", + "integrity": "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g==", "cpu": [ "arm64" ], @@ -3515,9 +3515,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.47.0.tgz", - "integrity": "sha512-EFXhIykAl8//4ihOjGNirF89HEUbOB8ev2aiw8ST8wFGwDdIPARh3enDlbp8aFnScl4CDK4DZLQYXaM6qpxzZw==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.45.1.tgz", + "integrity": "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A==", "cpu": [ "x64" ], @@ -3528,9 +3528,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.47.0.tgz", - "integrity": "sha512-EwkC5N61ptruQ9wNkYfLgUWEGh+F3JZSGHkUWhaK2ISAK0d0xmiMKF0trFhRqPQFov5d9DmFiFIhWB5IC79OUA==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.45.1.tgz", + "integrity": "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q==", "cpu": [ "arm" ], @@ -3541,9 +3541,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.47.0.tgz", - "integrity": "sha512-Iz/g1X94vIjppA4H9hN3VEedw4ObC+u+aua2J/VPJnENEJ0GeCAPBN15nJc5pS5M8JPlUhOd3oqhOWX6Un4RHA==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.45.1.tgz", + "integrity": "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q==", "cpu": [ "arm" ], @@ -3554,9 +3554,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.47.0.tgz", - "integrity": "sha512-eYEYHYjFo/vb6k1l5uq5+Af9yuo9WaST/z+/8T5gkee+A0Sfx1NIPZtKMEQOLjm/oaeHFGpWaAO97gTPhouIfQ==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.45.1.tgz", + "integrity": "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw==", "cpu": [ "arm64" ], @@ -3567,9 +3567,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.47.0.tgz", - "integrity": "sha512-LX2x0/RszFEmDfjzL6kG/vihD5CkpJ+0K6lcbqX0jAopkkXeY2ZjStngdFMFW+BK7pyrqryJgy6Jt3+oyDxrSA==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.45.1.tgz", + "integrity": "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog==", "cpu": [ "arm64" ], @@ -3580,9 +3580,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.47.0.tgz", - "integrity": "sha512-0U+56rJmJvqBCwlPFz/BcxkvdiRdNPamBfuFHrOGQtGajSMJ2OqzlvOgwj5vReRQnSA6XMKw/JL1DaBhceil+g==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.45.1.tgz", + "integrity": "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg==", "cpu": [ "loong64" ], @@ -3592,10 +3592,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.47.0.tgz", - "integrity": "sha512-2VKOsnNyvS05HFPKtmAWtef+nZyKCot/V3Jh/A5sYMhUvtthNjp6CjakYTtc5xZ8J8Fp5FKrUWGxptVtZ2OzEA==", + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.45.1.tgz", + "integrity": "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg==", "cpu": [ "ppc64" ], @@ -3606,9 +3606,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.47.0.tgz", - "integrity": "sha512-uY5UP7YZM4DMQiiP9Fl4/7O3UbT2p3uI0qvqLXZSGWBfyYuqi2DYQ48ExylgBN3T8AJork+b+mLGq6VXsxBfuw==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.45.1.tgz", + "integrity": "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw==", "cpu": [ "riscv64" ], @@ -3619,9 +3619,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.47.0.tgz", - "integrity": "sha512-qpcN2+/ivq3TcrXtZoHrS9WZplV3Nieh0gvnGb+SFZg7h/YkWsOXINJnjJRWHp9tEur7T8lMnMeQMPS7s9MjUg==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.45.1.tgz", + "integrity": "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA==", "cpu": [ "riscv64" ], @@ -3632,9 +3632,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.47.0.tgz", - "integrity": "sha512-XfuI+o7a2/KA2tBeP+J1CT3siyIQyjpGEL6fFvtUdoHJK1k5iVI3qeGT2i5y6Bb+xQu08AHKBsUGJ2GsOZzXbQ==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.45.1.tgz", + "integrity": "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw==", "cpu": [ "s390x" ], @@ -3645,9 +3645,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.47.0.tgz", - "integrity": "sha512-ylkLO6G7oUiN28mork3caDmgXHqRuopAxjYDaOqs4CoU9pkfR0R/pGQb2V1x2Zg3tlFj4b/DvxZroxC3xALX6g==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.45.1.tgz", + "integrity": "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw==", "cpu": [ "x64" ], @@ -3658,9 +3658,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.47.0.tgz", - "integrity": "sha512-1L72a+ice8xKqJ2afsAVW9EfECOhNMAOC1jH65TgghLaHSFwNzyEdeye+1vRFDNy52OGKip/vajj0ONtX7VpAg==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.45.1.tgz", + "integrity": "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw==", "cpu": [ "x64" ], @@ -3671,9 +3671,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.47.0.tgz", - "integrity": "sha512-wluhdd1uNLk/S+ex2Yj62WFw3un2cZo2ZKXy9cOuoti5IhaPXSDSvxT3os+SJ1cjNorE1PwAOfiJU7QUH6n3Zw==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.45.1.tgz", + "integrity": "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg==", "cpu": [ "arm64" ], @@ -3684,9 +3684,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.47.0.tgz", - "integrity": "sha512-0SMTA6AeG7u2rfwdkKSo6aZD/obmA7oyhR+4ePwLzlwxNE8sfSI9zmjZXtchvBAZmtkVQNt/lZ6RxSl9wBj4pw==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.45.1.tgz", + "integrity": "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw==", "cpu": [ "ia32" ], @@ -3697,9 +3697,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.47.0.tgz", - "integrity": "sha512-mw1/7kAGxLcfzoG7DIKFHvKr2ZUQasKOPCgT2ubkNZPgIDZOJPymqThtRWEeAlXBoipehP4BUFpBAZIrPhFg8Q==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.1.tgz", + "integrity": "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA==", "cpu": [ "x64" ], @@ -12101,9 +12101,9 @@ } }, "node_modules/rollup": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.47.0.tgz", - "integrity": "sha512-jZVxJwlAptA83ftdZK1kjLZfi0f6o+vVX7ub3HaRzkehLO3l4VB4vYpMHyunhBt1sawv9fiRWPA8Qi/sbg9Kcw==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.1.tgz", + "integrity": "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==", "dev": true, "dependencies": { "@types/estree": "1.0.8" @@ -12116,26 +12116,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.47.0", - "@rollup/rollup-android-arm64": "4.47.0", - "@rollup/rollup-darwin-arm64": "4.47.0", - "@rollup/rollup-darwin-x64": "4.47.0", - "@rollup/rollup-freebsd-arm64": "4.47.0", - "@rollup/rollup-freebsd-x64": "4.47.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.47.0", - "@rollup/rollup-linux-arm-musleabihf": "4.47.0", - "@rollup/rollup-linux-arm64-gnu": "4.47.0", - "@rollup/rollup-linux-arm64-musl": "4.47.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.47.0", - "@rollup/rollup-linux-ppc64-gnu": "4.47.0", - "@rollup/rollup-linux-riscv64-gnu": "4.47.0", - "@rollup/rollup-linux-riscv64-musl": "4.47.0", - "@rollup/rollup-linux-s390x-gnu": "4.47.0", - "@rollup/rollup-linux-x64-gnu": "4.47.0", - "@rollup/rollup-linux-x64-musl": "4.47.0", - "@rollup/rollup-win32-arm64-msvc": "4.47.0", - "@rollup/rollup-win32-ia32-msvc": "4.47.0", - "@rollup/rollup-win32-x64-msvc": "4.47.0", + "@rollup/rollup-android-arm-eabi": "4.45.1", + "@rollup/rollup-android-arm64": "4.45.1", + "@rollup/rollup-darwin-arm64": "4.45.1", + "@rollup/rollup-darwin-x64": "4.45.1", + "@rollup/rollup-freebsd-arm64": "4.45.1", + "@rollup/rollup-freebsd-x64": "4.45.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.45.1", + "@rollup/rollup-linux-arm-musleabihf": "4.45.1", + "@rollup/rollup-linux-arm64-gnu": "4.45.1", + "@rollup/rollup-linux-arm64-musl": "4.45.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.45.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.45.1", + "@rollup/rollup-linux-riscv64-gnu": "4.45.1", + "@rollup/rollup-linux-riscv64-musl": "4.45.1", + "@rollup/rollup-linux-s390x-gnu": "4.45.1", + "@rollup/rollup-linux-x64-gnu": "4.45.1", + "@rollup/rollup-linux-x64-musl": "4.45.1", + "@rollup/rollup-win32-arm64-msvc": "4.45.1", + "@rollup/rollup-win32-ia32-msvc": "4.45.1", + "@rollup/rollup-win32-x64-msvc": "4.45.1", "fsevents": "~2.3.2" } }, @@ -16846,142 +16846,142 @@ } }, "@rollup/rollup-android-arm-eabi": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.47.0.tgz", - "integrity": "sha512-Weap5hVbZs/yIvUZcFpAmIso8rLmwkO1LesddNjeX28tIhQkAKjRuVgAJ2xpj8wXTny7IZro9aBIgGov0qsL4A==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.1.tgz", + "integrity": "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==", "dev": true, "optional": true }, "@rollup/rollup-android-arm64": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.47.0.tgz", - "integrity": "sha512-XcnlqvG5riTJByKX7bZ1ehe48GiF+eNkdnzV0ziLp85XyJ6tLPfhkXHv3e0h3cpZESTQa8IB+ZHhV/r02+8qKw==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.45.1.tgz", + "integrity": "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ==", "dev": true, "optional": true }, "@rollup/rollup-darwin-arm64": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.47.0.tgz", - "integrity": "sha512-kZzTIzmzAUOKteh688kN88HNaL7wxwTz9XB5dDK94AQdf9nD+lxm/H5uPKQaawUFS+klBEowqPMUPjBRKGbo/g==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.45.1.tgz", + "integrity": "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA==", "dev": true, "optional": true }, "@rollup/rollup-darwin-x64": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.47.0.tgz", - "integrity": "sha512-WaMrgHRbFspYjvycbsbqheBmlsQBLwfZVWv/KFsT212Yz/RjEQ/9KEp1/p0Ef3ZNwbWsylmgf69St66D9NQNHw==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.45.1.tgz", + "integrity": "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og==", "dev": true, "optional": true }, "@rollup/rollup-freebsd-arm64": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.47.0.tgz", - "integrity": "sha512-umfYslurvSmAK5MEyOcOGooQ6EBB2pYePQaTVlrOkIfG6uuwu9egYOlxr35lwsp6XG0NzmXW0/5o150LUioMkQ==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.45.1.tgz", + "integrity": "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g==", "dev": true, "optional": true }, "@rollup/rollup-freebsd-x64": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.47.0.tgz", - "integrity": "sha512-EFXhIykAl8//4ihOjGNirF89HEUbOB8ev2aiw8ST8wFGwDdIPARh3enDlbp8aFnScl4CDK4DZLQYXaM6qpxzZw==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.45.1.tgz", + "integrity": "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.47.0.tgz", - "integrity": "sha512-EwkC5N61ptruQ9wNkYfLgUWEGh+F3JZSGHkUWhaK2ISAK0d0xmiMKF0trFhRqPQFov5d9DmFiFIhWB5IC79OUA==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.45.1.tgz", + "integrity": "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm-musleabihf": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.47.0.tgz", - "integrity": "sha512-Iz/g1X94vIjppA4H9hN3VEedw4ObC+u+aua2J/VPJnENEJ0GeCAPBN15nJc5pS5M8JPlUhOd3oqhOWX6Un4RHA==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.45.1.tgz", + "integrity": "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm64-gnu": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.47.0.tgz", - "integrity": "sha512-eYEYHYjFo/vb6k1l5uq5+Af9yuo9WaST/z+/8T5gkee+A0Sfx1NIPZtKMEQOLjm/oaeHFGpWaAO97gTPhouIfQ==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.45.1.tgz", + "integrity": "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm64-musl": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.47.0.tgz", - "integrity": "sha512-LX2x0/RszFEmDfjzL6kG/vihD5CkpJ+0K6lcbqX0jAopkkXeY2ZjStngdFMFW+BK7pyrqryJgy6Jt3+oyDxrSA==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.45.1.tgz", + "integrity": "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog==", "dev": true, "optional": true }, "@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.47.0.tgz", - "integrity": "sha512-0U+56rJmJvqBCwlPFz/BcxkvdiRdNPamBfuFHrOGQtGajSMJ2OqzlvOgwj5vReRQnSA6XMKw/JL1DaBhceil+g==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.45.1.tgz", + "integrity": "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg==", "dev": true, "optional": true }, - "@rollup/rollup-linux-ppc64-gnu": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.47.0.tgz", - "integrity": "sha512-2VKOsnNyvS05HFPKtmAWtef+nZyKCot/V3Jh/A5sYMhUvtthNjp6CjakYTtc5xZ8J8Fp5FKrUWGxptVtZ2OzEA==", + "@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.45.1.tgz", + "integrity": "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg==", "dev": true, "optional": true }, "@rollup/rollup-linux-riscv64-gnu": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.47.0.tgz", - "integrity": "sha512-uY5UP7YZM4DMQiiP9Fl4/7O3UbT2p3uI0qvqLXZSGWBfyYuqi2DYQ48ExylgBN3T8AJork+b+mLGq6VXsxBfuw==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.45.1.tgz", + "integrity": "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw==", "dev": true, "optional": true }, "@rollup/rollup-linux-riscv64-musl": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.47.0.tgz", - "integrity": "sha512-qpcN2+/ivq3TcrXtZoHrS9WZplV3Nieh0gvnGb+SFZg7h/YkWsOXINJnjJRWHp9tEur7T8lMnMeQMPS7s9MjUg==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.45.1.tgz", + "integrity": "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA==", "dev": true, "optional": true }, "@rollup/rollup-linux-s390x-gnu": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.47.0.tgz", - "integrity": "sha512-XfuI+o7a2/KA2tBeP+J1CT3siyIQyjpGEL6fFvtUdoHJK1k5iVI3qeGT2i5y6Bb+xQu08AHKBsUGJ2GsOZzXbQ==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.45.1.tgz", + "integrity": "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw==", "dev": true, "optional": true }, "@rollup/rollup-linux-x64-gnu": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.47.0.tgz", - "integrity": "sha512-ylkLO6G7oUiN28mork3caDmgXHqRuopAxjYDaOqs4CoU9pkfR0R/pGQb2V1x2Zg3tlFj4b/DvxZroxC3xALX6g==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.45.1.tgz", + "integrity": "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw==", "dev": true, "optional": true }, "@rollup/rollup-linux-x64-musl": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.47.0.tgz", - "integrity": "sha512-1L72a+ice8xKqJ2afsAVW9EfECOhNMAOC1jH65TgghLaHSFwNzyEdeye+1vRFDNy52OGKip/vajj0ONtX7VpAg==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.45.1.tgz", + "integrity": "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw==", "dev": true, "optional": true }, "@rollup/rollup-win32-arm64-msvc": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.47.0.tgz", - "integrity": "sha512-wluhdd1uNLk/S+ex2Yj62WFw3un2cZo2ZKXy9cOuoti5IhaPXSDSvxT3os+SJ1cjNorE1PwAOfiJU7QUH6n3Zw==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.45.1.tgz", + "integrity": "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg==", "dev": true, "optional": true }, "@rollup/rollup-win32-ia32-msvc": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.47.0.tgz", - "integrity": "sha512-0SMTA6AeG7u2rfwdkKSo6aZD/obmA7oyhR+4ePwLzlwxNE8sfSI9zmjZXtchvBAZmtkVQNt/lZ6RxSl9wBj4pw==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.45.1.tgz", + "integrity": "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw==", "dev": true, "optional": true }, "@rollup/rollup-win32-x64-msvc": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.47.0.tgz", - "integrity": "sha512-mw1/7kAGxLcfzoG7DIKFHvKr2ZUQasKOPCgT2ubkNZPgIDZOJPymqThtRWEeAlXBoipehP4BUFpBAZIrPhFg8Q==", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.1.tgz", + "integrity": "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA==", "dev": true, "optional": true }, @@ -23199,31 +23199,31 @@ } }, "rollup": { - "version": "4.47.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.47.0.tgz", - "integrity": "sha512-jZVxJwlAptA83ftdZK1kjLZfi0f6o+vVX7ub3HaRzkehLO3l4VB4vYpMHyunhBt1sawv9fiRWPA8Qi/sbg9Kcw==", - "dev": true, - "requires": { - "@rollup/rollup-android-arm-eabi": "4.47.0", - "@rollup/rollup-android-arm64": "4.47.0", - "@rollup/rollup-darwin-arm64": "4.47.0", - "@rollup/rollup-darwin-x64": "4.47.0", - "@rollup/rollup-freebsd-arm64": "4.47.0", - "@rollup/rollup-freebsd-x64": "4.47.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.47.0", - "@rollup/rollup-linux-arm-musleabihf": "4.47.0", - "@rollup/rollup-linux-arm64-gnu": "4.47.0", - "@rollup/rollup-linux-arm64-musl": "4.47.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.47.0", - "@rollup/rollup-linux-ppc64-gnu": "4.47.0", - "@rollup/rollup-linux-riscv64-gnu": "4.47.0", - "@rollup/rollup-linux-riscv64-musl": "4.47.0", - "@rollup/rollup-linux-s390x-gnu": "4.47.0", - "@rollup/rollup-linux-x64-gnu": "4.47.0", - "@rollup/rollup-linux-x64-musl": "4.47.0", - "@rollup/rollup-win32-arm64-msvc": "4.47.0", - "@rollup/rollup-win32-ia32-msvc": "4.47.0", - "@rollup/rollup-win32-x64-msvc": "4.47.0", + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.1.tgz", + "integrity": "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==", + "dev": true, + "requires": { + "@rollup/rollup-android-arm-eabi": "4.45.1", + "@rollup/rollup-android-arm64": "4.45.1", + "@rollup/rollup-darwin-arm64": "4.45.1", + "@rollup/rollup-darwin-x64": "4.45.1", + "@rollup/rollup-freebsd-arm64": "4.45.1", + "@rollup/rollup-freebsd-x64": "4.45.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.45.1", + "@rollup/rollup-linux-arm-musleabihf": "4.45.1", + "@rollup/rollup-linux-arm64-gnu": "4.45.1", + "@rollup/rollup-linux-arm64-musl": "4.45.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.45.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.45.1", + "@rollup/rollup-linux-riscv64-gnu": "4.45.1", + "@rollup/rollup-linux-riscv64-musl": "4.45.1", + "@rollup/rollup-linux-s390x-gnu": "4.45.1", + "@rollup/rollup-linux-x64-gnu": "4.45.1", + "@rollup/rollup-linux-x64-musl": "4.45.1", + "@rollup/rollup-win32-arm64-msvc": "4.45.1", + "@rollup/rollup-win32-ia32-msvc": "4.45.1", + "@rollup/rollup-win32-x64-msvc": "4.45.1", "@types/estree": "1.0.8", "fsevents": "~2.3.2" }, diff --git a/package.json b/package.json index 410c5a64cfc..2725e68ad83 100644 --- a/package.json +++ b/package.json @@ -124,7 +124,7 @@ "npm-run-all2": "8.0.4", "prettier": "3.6.2", "promise-polyfill": "8.3.0", - "rollup": "4.47.0", + "rollup": "4.45.1", "rollup-plugin-istanbul": "5.0.0", "sauce-connect-launcher": "1.3.2", "selenium-webdriver": "4.35.0", From a18f390d386ae749c7c54bde62054d9f28d27a7f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 28 Aug 2025 12:28:17 -0700 Subject: [PATCH 26/64] chore(deps): update node.js to v22.18.0 (#7495) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .node-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.node-version b/.node-version index 7377d130eda..91d5f6ff8e3 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -22.17.1 +22.18.0 From d5163761177677f2a3733ddc3164327d1310eb3f Mon Sep 17 00:00:00 2001 From: mitani Date: Thu, 28 Aug 2025 10:58:45 +0800 Subject: [PATCH 27/64] fix Why does AUDIO_TRACK_LOAD_TIMEOUT follow errorRetry in playlistLoadPolicy issue-7420 --- src/utils/error-helper.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/utils/error-helper.ts b/src/utils/error-helper.ts index b42ac693f83..f6376e635f5 100644 --- a/src/utils/error-helper.ts +++ b/src/utils/error-helper.ts @@ -9,6 +9,9 @@ export function isTimeoutError(error: ErrorData): boolean { case ErrorDetails.KEY_LOAD_TIMEOUT: case ErrorDetails.LEVEL_LOAD_TIMEOUT: case ErrorDetails.MANIFEST_LOAD_TIMEOUT: + case ErrorDetails.AUDIO_TRACK_LOAD_TIMEOUT: + case ErrorDetails.SUBTITLE_TRACK_LOAD_TIMEOUT: + case ErrorDetails.ASSET_LIST_LOAD_TIMEOUT: return true; } return false; From 3fd4bf3a4032e3e5dde82326292d80d8927b9925 Mon Sep 17 00:00:00 2001 From: yajin2021 <83813017+yajin2021@users.noreply.github.com> Date: Fri, 29 Aug 2025 05:20:07 +0800 Subject: [PATCH 28/64] Parse keyid from multi-key widevine PSSH (#7415) --- src/loader/level-key.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/loader/level-key.ts b/src/loader/level-key.ts index 0da3ae32ce0..aaccbcd4dfe 100644 --- a/src/loader/level-key.ts +++ b/src/loader/level-key.ts @@ -4,7 +4,7 @@ import { hexToArrayBuffer } from '../utils/hex'; import { convertDataUriToArrayBytes } from '../utils/keysystem-util'; import { logger } from '../utils/logger'; import { KeySystemFormats, parsePlayReadyWRM } from '../utils/mediakeys-helper'; -import { mp4pssh } from '../utils/mp4-tools'; +import { mp4pssh, parseMultiPssh } from '../utils/mp4-tools'; let keyUriToKeyIdMap: { [uri: string]: Uint8Array } = {}; @@ -141,7 +141,14 @@ export class LevelKey implements DecryptData { // the playlist-key before the "encrypted" event. (Comment out to only use "encrypted" path.) this.pssh = keyBytes; // In case of Widevine, if KEYID is not in the playlist, assume only two fields in the pssh KEY tag URI. - if (!this.keyId && keyBytes.length >= 22) { + if (!this.keyId) { + const [psshData] = parseMultiPssh(keyBytes.buffer); + this.keyId = + psshData && 'kids' in psshData && psshData.kids?.[0] + ? psshData.kids[0] + : null; + } + if (!this.keyId) { const offset = keyBytes.length - 22; this.keyId = keyBytes.subarray(offset, offset + 16); } From be44b2996ee0589a57e6015a9b517cca4ef4cc1d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 28 Aug 2025 21:20:30 +0000 Subject: [PATCH 29/64] chore(deps): update actions/checkout action to v5 --- .github/workflows/build.yml | 14 +++++++------- .github/workflows/codeql-analysis.yml | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c1e00f4a70f..8129d33b62f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -50,7 +50,7 @@ jobs: group: 'build:build:${{ github.ref }}' cancel-in-progress: true steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 @@ -133,7 +133,7 @@ jobs: group: 'build:test_unit:${{ github.ref }}' cancel-in-progress: true steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: cache node_modules uses: actions/cache@v4 @@ -177,7 +177,7 @@ jobs: group: 'build:cloudflare_pages:${{ github.ref }}' cancel-in-progress: true steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: cache node_modules uses: actions/cache@v4 @@ -252,7 +252,7 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: use Node.js uses: actions/setup-node@v4 @@ -288,7 +288,7 @@ jobs: permissions: id-token: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: cache node_modules uses: actions/cache@v4 @@ -352,7 +352,7 @@ jobs: uaVersion: '75.0' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: cache node_modules uses: actions/cache@v4 @@ -424,7 +424,7 @@ jobs: os: Windows 10 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: cache node_modules uses: actions/cache@v4 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 7ffb21a4197..5ef91b9b07d 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Initialize CodeQL uses: github/codeql-action/init@v3 From 947cbd81afa0ad8edc5fe97a679ff9b954f6c78c Mon Sep 17 00:00:00 2001 From: yajin2021 <83813017+yajin2021@users.noreply.github.com> Date: Fri, 29 Aug 2025 05:20:07 +0800 Subject: [PATCH 30/64] Parse keyid from multi-key widevine PSSH (#7415) (cherry picked from commit 3fd4bf3a4032e3e5dde82326292d80d8927b9925) --- src/loader/level-key.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/loader/level-key.ts b/src/loader/level-key.ts index 0da3ae32ce0..aaccbcd4dfe 100644 --- a/src/loader/level-key.ts +++ b/src/loader/level-key.ts @@ -4,7 +4,7 @@ import { hexToArrayBuffer } from '../utils/hex'; import { convertDataUriToArrayBytes } from '../utils/keysystem-util'; import { logger } from '../utils/logger'; import { KeySystemFormats, parsePlayReadyWRM } from '../utils/mediakeys-helper'; -import { mp4pssh } from '../utils/mp4-tools'; +import { mp4pssh, parseMultiPssh } from '../utils/mp4-tools'; let keyUriToKeyIdMap: { [uri: string]: Uint8Array } = {}; @@ -141,7 +141,14 @@ export class LevelKey implements DecryptData { // the playlist-key before the "encrypted" event. (Comment out to only use "encrypted" path.) this.pssh = keyBytes; // In case of Widevine, if KEYID is not in the playlist, assume only two fields in the pssh KEY tag URI. - if (!this.keyId && keyBytes.length >= 22) { + if (!this.keyId) { + const [psshData] = parseMultiPssh(keyBytes.buffer); + this.keyId = + psshData && 'kids' in psshData && psshData.kids?.[0] + ? psshData.kids[0] + : null; + } + if (!this.keyId) { const offset = keyBytes.length - 22; this.keyId = keyBytes.subarray(offset, offset + 16); } From ea5b716b5fad86817274353f00af15d60a363f23 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 28 Aug 2025 23:54:06 +0000 Subject: [PATCH 31/64] chore(deps): update actions/download-artifact action to v5 --- .github/workflows/build.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8129d33b62f..fba3c5ce9de 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -153,7 +153,7 @@ jobs: node-version-file: '.node-version' - name: download build - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: build @@ -197,7 +197,7 @@ jobs: node-version-file: '.node-version' - name: download build - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: build @@ -260,7 +260,7 @@ jobs: node-version-file: '.node-version' - name: download build - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: build @@ -308,7 +308,7 @@ jobs: node-version-file: '.node-version' - name: download build - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: build @@ -372,7 +372,7 @@ jobs: node-version-file: '.node-version' - name: download build - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: build @@ -444,7 +444,7 @@ jobs: node-version-file: '.node-version' - name: download build - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: build From 5a044160953e43f8ca7a83d0e19482a96a55f258 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 29 Aug 2025 01:46:53 +0000 Subject: [PATCH 32/64] chore(deps): update dependency chromedriver to v139 --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index fdcd5669905..0c88ff7f603 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,7 @@ "babel-plugin-transform-remove-console": "6.9.4", "chai": "4.5.0", "chart.js": "2.9.4", - "chromedriver": "138.0.5", + "chromedriver": "139.0.2", "doctoc": "2.2.1", "es-check": "9.3.1", "eslint": "8.57.1", @@ -5479,9 +5479,9 @@ } }, "node_modules/chromedriver": { - "version": "138.0.5", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-138.0.5.tgz", - "integrity": "sha512-WE5O09if9TmFfIpvydt5dyhj+TNTUttvnujoRtAShQuDghulSh1HFirBnjNrAWjEoMkXn9VUw+cCYzZ597VPJQ==", + "version": "139.0.2", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-139.0.2.tgz", + "integrity": "sha512-GEq1PM9unQBQ79iNxlsJPvMFzcw/LKIusxC39RVD+8noh1JqURNTqbhPGU887VpGUsCFJ0SCSpr+6waK/yWHRA==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -18357,9 +18357,9 @@ "peer": true }, "chromedriver": { - "version": "138.0.5", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-138.0.5.tgz", - "integrity": "sha512-WE5O09if9TmFfIpvydt5dyhj+TNTUttvnujoRtAShQuDghulSh1HFirBnjNrAWjEoMkXn9VUw+cCYzZ597VPJQ==", + "version": "139.0.2", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-139.0.2.tgz", + "integrity": "sha512-GEq1PM9unQBQ79iNxlsJPvMFzcw/LKIusxC39RVD+8noh1JqURNTqbhPGU887VpGUsCFJ0SCSpr+6waK/yWHRA==", "dev": true, "requires": { "@testim/chrome-version": "^1.1.4", diff --git a/package.json b/package.json index 2725e68ad83..de803e3b771 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "babel-plugin-transform-remove-console": "6.9.4", "chai": "4.5.0", "chart.js": "2.9.4", - "chromedriver": "138.0.5", + "chromedriver": "139.0.2", "doctoc": "2.2.1", "es-check": "9.3.1", "eslint": "8.57.1", From fea6732dc1196c0eff2f076443faeff07bc96866 Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Thu, 28 Aug 2025 22:58:55 -0700 Subject: [PATCH 33/64] Update renovate.json (#7507) --- renovate.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/renovate.json b/renovate.json index 69f0454f5f3..784b0631a13 100644 --- a/renovate.json +++ b/renovate.json @@ -2,8 +2,8 @@ "extends": ["config:recommended"], "labels": ["dependencies", "skip-change-log"], "schedule": ["* 0 28 * *"], - "prHourlyLimit": 0, - "prConcurrentLimit": 0, + "prHourlyLimit": 3, + "prConcurrentLimit": 3, "prCreation": "immediate", "minimumReleaseAge": "7 days", "internalChecksFilter": "strict", @@ -13,7 +13,7 @@ "major": { "addLabels": ["semver-major"] }, - "ignoreDeps": ["FileSaver.js", "@types/chart.js"], + "ignoreDeps": ["ace", "chart.js", "FileSaver.js"], "packageRules": [ { "matchDatasources": ["html"], From 13e45c16485d1e360ba5c5c4d9946dbd84c113c9 Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Thu, 28 Aug 2025 23:04:46 -0700 Subject: [PATCH 34/64] Update renovate.json --- renovate.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/renovate.json b/renovate.json index 784b0631a13..36d7f9a6a01 100644 --- a/renovate.json +++ b/renovate.json @@ -13,7 +13,7 @@ "major": { "addLabels": ["semver-major"] }, - "ignoreDeps": ["ace", "chart.js", "FileSaver.js"], + "ignoreDeps": ["@types/chart.js", "ace", "chart.js", "FileSaver.js"], "packageRules": [ { "matchDatasources": ["html"], From a146f29c4b2e814f51191609de553cd0f6a4e99e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 29 Aug 2025 06:05:28 +0000 Subject: [PATCH 35/64] chore(deps): update dependency chromedriver to v139.0.3 --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0c88ff7f603..7a890708f4e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,7 @@ "babel-plugin-transform-remove-console": "6.9.4", "chai": "4.5.0", "chart.js": "2.9.4", - "chromedriver": "139.0.2", + "chromedriver": "139.0.3", "doctoc": "2.2.1", "es-check": "9.3.1", "eslint": "8.57.1", @@ -5479,9 +5479,9 @@ } }, "node_modules/chromedriver": { - "version": "139.0.2", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-139.0.2.tgz", - "integrity": "sha512-GEq1PM9unQBQ79iNxlsJPvMFzcw/LKIusxC39RVD+8noh1JqURNTqbhPGU887VpGUsCFJ0SCSpr+6waK/yWHRA==", + "version": "139.0.3", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-139.0.3.tgz", + "integrity": "sha512-NrSqRL2QWXsGk1/EXk5xf9q07mEUMsIA7szr9nxSOzENSdFOi+ZvEYq4H8P3tqQL61EKS0tS9m9TnVCJoQHn2Q==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -18357,9 +18357,9 @@ "peer": true }, "chromedriver": { - "version": "139.0.2", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-139.0.2.tgz", - "integrity": "sha512-GEq1PM9unQBQ79iNxlsJPvMFzcw/LKIusxC39RVD+8noh1JqURNTqbhPGU887VpGUsCFJ0SCSpr+6waK/yWHRA==", + "version": "139.0.3", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-139.0.3.tgz", + "integrity": "sha512-NrSqRL2QWXsGk1/EXk5xf9q07mEUMsIA7szr9nxSOzENSdFOi+ZvEYq4H8P3tqQL61EKS0tS9m9TnVCJoQHn2Q==", "dev": true, "requires": { "@testim/chrome-version": "^1.1.4", diff --git a/package.json b/package.json index de803e3b771..35d02601b17 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "babel-plugin-transform-remove-console": "6.9.4", "chai": "4.5.0", "chart.js": "2.9.4", - "chromedriver": "139.0.2", + "chromedriver": "139.0.3", "doctoc": "2.2.1", "es-check": "9.3.1", "eslint": "8.57.1", From 9356bc1c17ea2a32fedeafa88330b614f841845c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 29 Aug 2025 06:22:22 +0000 Subject: [PATCH 36/64] chore(deps): update dependency rollup to v4.47.1 --- package-lock.json | 342 +++++++++++++++++++++++----------------------- package.json | 2 +- 2 files changed, 172 insertions(+), 172 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7a890708f4e..0e54bbc16c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -66,7 +66,7 @@ "npm-run-all2": "8.0.4", "prettier": "3.6.2", "promise-polyfill": "8.3.0", - "rollup": "4.45.1", + "rollup": "4.47.1", "rollup-plugin-istanbul": "5.0.0", "sauce-connect-launcher": "1.3.2", "selenium-webdriver": "4.35.0", @@ -3450,9 +3450,9 @@ "dev": true }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.1.tgz", - "integrity": "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.47.1.tgz", + "integrity": "sha512-lTahKRJip0knffA/GTNFJMrToD+CM+JJ+Qt5kjzBK/sFQ0EWqfKW3AYQSlZXN98tX0lx66083U9JYIMioMMK7g==", "cpu": [ "arm" ], @@ -3463,9 +3463,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.45.1.tgz", - "integrity": "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.47.1.tgz", + "integrity": "sha512-uqxkb3RJLzlBbh/bbNQ4r7YpSZnjgMgyoEOY7Fy6GCbelkDSAzeiogxMG9TfLsBbqmGsdDObo3mzGqa8hps4MA==", "cpu": [ "arm64" ], @@ -3476,9 +3476,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.45.1.tgz", - "integrity": "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.47.1.tgz", + "integrity": "sha512-tV6reObmxBDS4DDyLzTDIpymthNlxrLBGAoQx6m2a7eifSNEZdkXQl1PE4ZjCkEDPVgNXSzND/k9AQ3mC4IOEQ==", "cpu": [ "arm64" ], @@ -3489,9 +3489,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.45.1.tgz", - "integrity": "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.47.1.tgz", + "integrity": "sha512-XuJRPTnMk1lwsSnS3vYyVMu4x/+WIw1MMSiqj5C4j3QOWsMzbJEK90zG+SWV1h0B1ABGCQ0UZUjti+TQK35uHQ==", "cpu": [ "x64" ], @@ -3502,9 +3502,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.45.1.tgz", - "integrity": "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.47.1.tgz", + "integrity": "sha512-79BAm8Ag/tmJ5asCqgOXsb3WY28Rdd5Lxj8ONiQzWzy9LvWORd5qVuOnjlqiWWZJw+dWewEktZb5yiM1DLLaHw==", "cpu": [ "arm64" ], @@ -3515,9 +3515,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.45.1.tgz", - "integrity": "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.47.1.tgz", + "integrity": "sha512-OQ2/ZDGzdOOlyfqBiip0ZX/jVFekzYrGtUsqAfLDbWy0jh1PUU18+jYp8UMpqhly5ltEqotc2miLngf9FPSWIA==", "cpu": [ "x64" ], @@ -3528,9 +3528,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.45.1.tgz", - "integrity": "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.47.1.tgz", + "integrity": "sha512-HZZBXJL1udxlCVvoVadstgiU26seKkHbbAMLg7680gAcMnRNP9SAwTMVet02ANA94kXEI2VhBnXs4e5nf7KG2A==", "cpu": [ "arm" ], @@ -3541,9 +3541,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.45.1.tgz", - "integrity": "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.47.1.tgz", + "integrity": "sha512-sZ5p2I9UA7T950JmuZ3pgdKA6+RTBr+0FpK427ExW0t7n+QwYOcmDTK/aRlzoBrWyTpJNlS3kacgSlSTUg6P/Q==", "cpu": [ "arm" ], @@ -3554,9 +3554,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.45.1.tgz", - "integrity": "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.47.1.tgz", + "integrity": "sha512-3hBFoqPyU89Dyf1mQRXCdpc6qC6At3LV6jbbIOZd72jcx7xNk3aAp+EjzAtN6sDlmHFzsDJN5yeUySvorWeRXA==", "cpu": [ "arm64" ], @@ -3567,9 +3567,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.45.1.tgz", - "integrity": "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.47.1.tgz", + "integrity": "sha512-49J4FnMHfGodJWPw73Ve+/hsPjZgcXQGkmqBGZFvltzBKRS+cvMiWNLadOMXKGnYRhs1ToTGM0sItKISoSGUNA==", "cpu": [ "arm64" ], @@ -3580,9 +3580,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.45.1.tgz", - "integrity": "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.47.1.tgz", + "integrity": "sha512-4yYU8p7AneEpQkRX03pbpLmE21z5JNys16F1BZBZg5fP9rIlb0TkeQjn5du5w4agConCCEoYIG57sNxjryHEGg==", "cpu": [ "loong64" ], @@ -3592,10 +3592,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.45.1.tgz", - "integrity": "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg==", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.47.1.tgz", + "integrity": "sha512-fAiq+J28l2YMWgC39jz/zPi2jqc0y3GSRo1yyxlBHt6UN0yYgnegHSRPa3pnHS5amT/efXQrm0ug5+aNEu9UuQ==", "cpu": [ "ppc64" ], @@ -3606,9 +3606,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.45.1.tgz", - "integrity": "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.47.1.tgz", + "integrity": "sha512-daoT0PMENNdjVYYU9xec30Y2prb1AbEIbb64sqkcQcSaR0zYuKkoPuhIztfxuqN82KYCKKrj+tQe4Gi7OSm1ow==", "cpu": [ "riscv64" ], @@ -3619,9 +3619,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.45.1.tgz", - "integrity": "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.47.1.tgz", + "integrity": "sha512-JNyXaAhWtdzfXu5pUcHAuNwGQKevR+6z/poYQKVW+pLaYOj9G1meYc57/1Xv2u4uTxfu9qEWmNTjv/H/EpAisw==", "cpu": [ "riscv64" ], @@ -3632,9 +3632,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.45.1.tgz", - "integrity": "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.47.1.tgz", + "integrity": "sha512-U/CHbqKSwEQyZXjCpY43/GLYcTVKEXeRHw0rMBJP7fP3x6WpYG4LTJWR3ic6TeYKX6ZK7mrhltP4ppolyVhLVQ==", "cpu": [ "s390x" ], @@ -3645,9 +3645,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.45.1.tgz", - "integrity": "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.47.1.tgz", + "integrity": "sha512-uTLEakjxOTElfeZIGWkC34u2auLHB1AYS6wBjPGI00bWdxdLcCzK5awjs25YXpqB9lS8S0vbO0t9ZcBeNibA7g==", "cpu": [ "x64" ], @@ -3658,9 +3658,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.45.1.tgz", - "integrity": "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.47.1.tgz", + "integrity": "sha512-Ft+d/9DXs30BK7CHCTX11FtQGHUdpNDLJW0HHLign4lgMgBcPFN3NkdIXhC5r9iwsMwYreBBc4Rho5ieOmKNVQ==", "cpu": [ "x64" ], @@ -3671,9 +3671,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.45.1.tgz", - "integrity": "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.47.1.tgz", + "integrity": "sha512-N9X5WqGYzZnjGAFsKSfYFtAShYjwOmFJoWbLg3dYixZOZqU7hdMq+/xyS14zKLhFhZDhP9VfkzQnsdk0ZDS9IA==", "cpu": [ "arm64" ], @@ -3684,9 +3684,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.45.1.tgz", - "integrity": "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.47.1.tgz", + "integrity": "sha512-O+KcfeCORZADEY8oQJk4HK8wtEOCRE4MdOkb8qGZQNun3jzmj2nmhV/B/ZaaZOkPmJyvm/gW9n0gsB4eRa1eiQ==", "cpu": [ "ia32" ], @@ -3697,9 +3697,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.1.tgz", - "integrity": "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.47.1.tgz", + "integrity": "sha512-CpKnYa8eHthJa3c+C38v/E+/KZyF1Jdh2Cz3DyKZqEWYgrM1IHFArXNWvBLPQCKUEsAqqKX27tTqVEFbDNUcOA==", "cpu": [ "x64" ], @@ -12101,9 +12101,9 @@ } }, "node_modules/rollup": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.1.tgz", - "integrity": "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.47.1.tgz", + "integrity": "sha512-iasGAQoZ5dWDzULEUX3jiW0oB1qyFOepSyDyoU6S/OhVlDIwj5knI5QBa5RRQ0sK7OE0v+8VIi2JuV+G+3tfNg==", "dev": true, "dependencies": { "@types/estree": "1.0.8" @@ -12116,26 +12116,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.45.1", - "@rollup/rollup-android-arm64": "4.45.1", - "@rollup/rollup-darwin-arm64": "4.45.1", - "@rollup/rollup-darwin-x64": "4.45.1", - "@rollup/rollup-freebsd-arm64": "4.45.1", - "@rollup/rollup-freebsd-x64": "4.45.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.45.1", - "@rollup/rollup-linux-arm-musleabihf": "4.45.1", - "@rollup/rollup-linux-arm64-gnu": "4.45.1", - "@rollup/rollup-linux-arm64-musl": "4.45.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.45.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.45.1", - "@rollup/rollup-linux-riscv64-gnu": "4.45.1", - "@rollup/rollup-linux-riscv64-musl": "4.45.1", - "@rollup/rollup-linux-s390x-gnu": "4.45.1", - "@rollup/rollup-linux-x64-gnu": "4.45.1", - "@rollup/rollup-linux-x64-musl": "4.45.1", - "@rollup/rollup-win32-arm64-msvc": "4.45.1", - "@rollup/rollup-win32-ia32-msvc": "4.45.1", - "@rollup/rollup-win32-x64-msvc": "4.45.1", + "@rollup/rollup-android-arm-eabi": "4.47.1", + "@rollup/rollup-android-arm64": "4.47.1", + "@rollup/rollup-darwin-arm64": "4.47.1", + "@rollup/rollup-darwin-x64": "4.47.1", + "@rollup/rollup-freebsd-arm64": "4.47.1", + "@rollup/rollup-freebsd-x64": "4.47.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.47.1", + "@rollup/rollup-linux-arm-musleabihf": "4.47.1", + "@rollup/rollup-linux-arm64-gnu": "4.47.1", + "@rollup/rollup-linux-arm64-musl": "4.47.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.47.1", + "@rollup/rollup-linux-ppc64-gnu": "4.47.1", + "@rollup/rollup-linux-riscv64-gnu": "4.47.1", + "@rollup/rollup-linux-riscv64-musl": "4.47.1", + "@rollup/rollup-linux-s390x-gnu": "4.47.1", + "@rollup/rollup-linux-x64-gnu": "4.47.1", + "@rollup/rollup-linux-x64-musl": "4.47.1", + "@rollup/rollup-win32-arm64-msvc": "4.47.1", + "@rollup/rollup-win32-ia32-msvc": "4.47.1", + "@rollup/rollup-win32-x64-msvc": "4.47.1", "fsevents": "~2.3.2" } }, @@ -16846,142 +16846,142 @@ } }, "@rollup/rollup-android-arm-eabi": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.1.tgz", - "integrity": "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.47.1.tgz", + "integrity": "sha512-lTahKRJip0knffA/GTNFJMrToD+CM+JJ+Qt5kjzBK/sFQ0EWqfKW3AYQSlZXN98tX0lx66083U9JYIMioMMK7g==", "dev": true, "optional": true }, "@rollup/rollup-android-arm64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.45.1.tgz", - "integrity": "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.47.1.tgz", + "integrity": "sha512-uqxkb3RJLzlBbh/bbNQ4r7YpSZnjgMgyoEOY7Fy6GCbelkDSAzeiogxMG9TfLsBbqmGsdDObo3mzGqa8hps4MA==", "dev": true, "optional": true }, "@rollup/rollup-darwin-arm64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.45.1.tgz", - "integrity": "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.47.1.tgz", + "integrity": "sha512-tV6reObmxBDS4DDyLzTDIpymthNlxrLBGAoQx6m2a7eifSNEZdkXQl1PE4ZjCkEDPVgNXSzND/k9AQ3mC4IOEQ==", "dev": true, "optional": true }, "@rollup/rollup-darwin-x64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.45.1.tgz", - "integrity": "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.47.1.tgz", + "integrity": "sha512-XuJRPTnMk1lwsSnS3vYyVMu4x/+WIw1MMSiqj5C4j3QOWsMzbJEK90zG+SWV1h0B1ABGCQ0UZUjti+TQK35uHQ==", "dev": true, "optional": true }, "@rollup/rollup-freebsd-arm64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.45.1.tgz", - "integrity": "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.47.1.tgz", + "integrity": "sha512-79BAm8Ag/tmJ5asCqgOXsb3WY28Rdd5Lxj8ONiQzWzy9LvWORd5qVuOnjlqiWWZJw+dWewEktZb5yiM1DLLaHw==", "dev": true, "optional": true }, "@rollup/rollup-freebsd-x64": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.45.1.tgz", - "integrity": "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.47.1.tgz", + "integrity": "sha512-OQ2/ZDGzdOOlyfqBiip0ZX/jVFekzYrGtUsqAfLDbWy0jh1PUU18+jYp8UMpqhly5ltEqotc2miLngf9FPSWIA==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.45.1.tgz", - "integrity": "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.47.1.tgz", + "integrity": "sha512-HZZBXJL1udxlCVvoVadstgiU26seKkHbbAMLg7680gAcMnRNP9SAwTMVet02ANA94kXEI2VhBnXs4e5nf7KG2A==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm-musleabihf": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.45.1.tgz", - "integrity": "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.47.1.tgz", + "integrity": "sha512-sZ5p2I9UA7T950JmuZ3pgdKA6+RTBr+0FpK427ExW0t7n+QwYOcmDTK/aRlzoBrWyTpJNlS3kacgSlSTUg6P/Q==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm64-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.45.1.tgz", - "integrity": "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.47.1.tgz", + "integrity": "sha512-3hBFoqPyU89Dyf1mQRXCdpc6qC6At3LV6jbbIOZd72jcx7xNk3aAp+EjzAtN6sDlmHFzsDJN5yeUySvorWeRXA==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm64-musl": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.45.1.tgz", - "integrity": "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.47.1.tgz", + "integrity": "sha512-49J4FnMHfGodJWPw73Ve+/hsPjZgcXQGkmqBGZFvltzBKRS+cvMiWNLadOMXKGnYRhs1ToTGM0sItKISoSGUNA==", "dev": true, "optional": true }, "@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.45.1.tgz", - "integrity": "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.47.1.tgz", + "integrity": "sha512-4yYU8p7AneEpQkRX03pbpLmE21z5JNys16F1BZBZg5fP9rIlb0TkeQjn5du5w4agConCCEoYIG57sNxjryHEGg==", "dev": true, "optional": true }, - "@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.45.1.tgz", - "integrity": "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg==", + "@rollup/rollup-linux-ppc64-gnu": { + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.47.1.tgz", + "integrity": "sha512-fAiq+J28l2YMWgC39jz/zPi2jqc0y3GSRo1yyxlBHt6UN0yYgnegHSRPa3pnHS5amT/efXQrm0ug5+aNEu9UuQ==", "dev": true, "optional": true }, "@rollup/rollup-linux-riscv64-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.45.1.tgz", - "integrity": "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.47.1.tgz", + "integrity": "sha512-daoT0PMENNdjVYYU9xec30Y2prb1AbEIbb64sqkcQcSaR0zYuKkoPuhIztfxuqN82KYCKKrj+tQe4Gi7OSm1ow==", "dev": true, "optional": true }, "@rollup/rollup-linux-riscv64-musl": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.45.1.tgz", - "integrity": "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.47.1.tgz", + "integrity": "sha512-JNyXaAhWtdzfXu5pUcHAuNwGQKevR+6z/poYQKVW+pLaYOj9G1meYc57/1Xv2u4uTxfu9qEWmNTjv/H/EpAisw==", "dev": true, "optional": true }, "@rollup/rollup-linux-s390x-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.45.1.tgz", - "integrity": "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.47.1.tgz", + "integrity": "sha512-U/CHbqKSwEQyZXjCpY43/GLYcTVKEXeRHw0rMBJP7fP3x6WpYG4LTJWR3ic6TeYKX6ZK7mrhltP4ppolyVhLVQ==", "dev": true, "optional": true }, "@rollup/rollup-linux-x64-gnu": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.45.1.tgz", - "integrity": "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.47.1.tgz", + "integrity": "sha512-uTLEakjxOTElfeZIGWkC34u2auLHB1AYS6wBjPGI00bWdxdLcCzK5awjs25YXpqB9lS8S0vbO0t9ZcBeNibA7g==", "dev": true, "optional": true }, "@rollup/rollup-linux-x64-musl": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.45.1.tgz", - "integrity": "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.47.1.tgz", + "integrity": "sha512-Ft+d/9DXs30BK7CHCTX11FtQGHUdpNDLJW0HHLign4lgMgBcPFN3NkdIXhC5r9iwsMwYreBBc4Rho5ieOmKNVQ==", "dev": true, "optional": true }, "@rollup/rollup-win32-arm64-msvc": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.45.1.tgz", - "integrity": "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.47.1.tgz", + "integrity": "sha512-N9X5WqGYzZnjGAFsKSfYFtAShYjwOmFJoWbLg3dYixZOZqU7hdMq+/xyS14zKLhFhZDhP9VfkzQnsdk0ZDS9IA==", "dev": true, "optional": true }, "@rollup/rollup-win32-ia32-msvc": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.45.1.tgz", - "integrity": "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.47.1.tgz", + "integrity": "sha512-O+KcfeCORZADEY8oQJk4HK8wtEOCRE4MdOkb8qGZQNun3jzmj2nmhV/B/ZaaZOkPmJyvm/gW9n0gsB4eRa1eiQ==", "dev": true, "optional": true }, "@rollup/rollup-win32-x64-msvc": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.1.tgz", - "integrity": "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA==", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.47.1.tgz", + "integrity": "sha512-CpKnYa8eHthJa3c+C38v/E+/KZyF1Jdh2Cz3DyKZqEWYgrM1IHFArXNWvBLPQCKUEsAqqKX27tTqVEFbDNUcOA==", "dev": true, "optional": true }, @@ -23199,31 +23199,31 @@ } }, "rollup": { - "version": "4.45.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.1.tgz", - "integrity": "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==", - "dev": true, - "requires": { - "@rollup/rollup-android-arm-eabi": "4.45.1", - "@rollup/rollup-android-arm64": "4.45.1", - "@rollup/rollup-darwin-arm64": "4.45.1", - "@rollup/rollup-darwin-x64": "4.45.1", - "@rollup/rollup-freebsd-arm64": "4.45.1", - "@rollup/rollup-freebsd-x64": "4.45.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.45.1", - "@rollup/rollup-linux-arm-musleabihf": "4.45.1", - "@rollup/rollup-linux-arm64-gnu": "4.45.1", - "@rollup/rollup-linux-arm64-musl": "4.45.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.45.1", - "@rollup/rollup-linux-powerpc64le-gnu": "4.45.1", - "@rollup/rollup-linux-riscv64-gnu": "4.45.1", - "@rollup/rollup-linux-riscv64-musl": "4.45.1", - "@rollup/rollup-linux-s390x-gnu": "4.45.1", - "@rollup/rollup-linux-x64-gnu": "4.45.1", - "@rollup/rollup-linux-x64-musl": "4.45.1", - "@rollup/rollup-win32-arm64-msvc": "4.45.1", - "@rollup/rollup-win32-ia32-msvc": "4.45.1", - "@rollup/rollup-win32-x64-msvc": "4.45.1", + "version": "4.47.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.47.1.tgz", + "integrity": "sha512-iasGAQoZ5dWDzULEUX3jiW0oB1qyFOepSyDyoU6S/OhVlDIwj5knI5QBa5RRQ0sK7OE0v+8VIi2JuV+G+3tfNg==", + "dev": true, + "requires": { + "@rollup/rollup-android-arm-eabi": "4.47.1", + "@rollup/rollup-android-arm64": "4.47.1", + "@rollup/rollup-darwin-arm64": "4.47.1", + "@rollup/rollup-darwin-x64": "4.47.1", + "@rollup/rollup-freebsd-arm64": "4.47.1", + "@rollup/rollup-freebsd-x64": "4.47.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.47.1", + "@rollup/rollup-linux-arm-musleabihf": "4.47.1", + "@rollup/rollup-linux-arm64-gnu": "4.47.1", + "@rollup/rollup-linux-arm64-musl": "4.47.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.47.1", + "@rollup/rollup-linux-ppc64-gnu": "4.47.1", + "@rollup/rollup-linux-riscv64-gnu": "4.47.1", + "@rollup/rollup-linux-riscv64-musl": "4.47.1", + "@rollup/rollup-linux-s390x-gnu": "4.47.1", + "@rollup/rollup-linux-x64-gnu": "4.47.1", + "@rollup/rollup-linux-x64-musl": "4.47.1", + "@rollup/rollup-win32-arm64-msvc": "4.47.1", + "@rollup/rollup-win32-ia32-msvc": "4.47.1", + "@rollup/rollup-win32-x64-msvc": "4.47.1", "@types/estree": "1.0.8", "fsevents": "~2.3.2" }, diff --git a/package.json b/package.json index 35d02601b17..9c5082902c5 100644 --- a/package.json +++ b/package.json @@ -124,7 +124,7 @@ "npm-run-all2": "8.0.4", "prettier": "3.6.2", "promise-polyfill": "8.3.0", - "rollup": "4.45.1", + "rollup": "4.47.1", "rollup-plugin-istanbul": "5.0.0", "sauce-connect-launcher": "1.3.2", "selenium-webdriver": "4.35.0", From f9e23c04461c6ca07c156d47de7acf235746d3fb Mon Sep 17 00:00:00 2001 From: Shubham Sharma <124438439+ShubhamSharma2311@users.noreply.github.com> Date: Sat, 30 Aug 2025 01:00:13 +0530 Subject: [PATCH 37/64] Fix PlayReady key endianness (#7510) --- src/controller/eme-controller.ts | 69 +++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 6 deletions(-) diff --git a/src/controller/eme-controller.ts b/src/controller/eme-controller.ts index b7e8488f787..f6999809bc4 100644 --- a/src/controller/eme-controller.ts +++ b/src/controller/eme-controller.ts @@ -12,6 +12,7 @@ import { removeEventListener, } from '../utils/event-listener-helper'; import { arrayToHex } from '../utils/hex'; +import { changeEndianness } from '../utils/keysystem-util'; import { Logger } from '../utils/logger'; import { getKeySystemsForConfig, @@ -881,6 +882,10 @@ class EMEController extends Logger implements ComponentAPI { const sessionLevelKeyId = arrayToHex( new Uint8Array(mediaKeySessionContext.decryptdata.keyId || []), ); + + let hasMatchedKey = false; + const keyStatuses: { status: MediaKeyStatus; keyId: string }[] = []; + mediaKeySessionContext.mediaKeysSession.keyStatuses.forEach( (status: MediaKeyStatus, keyId: BufferSource) => { // keyStatuses.forEach is not standard API so the callback value looks weird on xboxone @@ -890,27 +895,79 @@ class EMEController extends Logger implements ComponentAPI { keyId = status; status = temp; } - const keyIdWithStatusChange = arrayToHex( + + const keyIdArray: Uint8Array = 'buffer' in keyId ? new Uint8Array(keyId.buffer, keyId.byteOffset, keyId.byteLength) - : new Uint8Array(keyId), + : new Uint8Array(keyId); + + // Handle PlayReady little-endian key ID conversion for status comparison only + // Don't modify the original key ID from playlist parsing + if ( + mediaKeySessionContext.keySystem === KeySystems.PLAYREADY && + keyIdArray.length === 16 + ) { + changeEndianness(keyIdArray); + } + + const keyIdWithStatusChange = arrayToHex( + keyIdArray as Uint8Array, ); + // Store all key statuses for processing + keyStatuses.push({ status, keyId: keyIdWithStatusChange }); + // Error immediately when encountering a key ID with this status again if (status === 'internal-error') { this.bannedKeyIds[keyIdWithStatusChange] = status; } - // Only acknowledge status changes for level-key ID + // Check if this key matches the session-level key ID const matched = keyIdWithStatusChange === sessionLevelKeyId; - this.log( - `${matched ? '' : 'un'}matched key status change "${status}" for keyStatuses keyId: ${keyIdWithStatusChange} session keyId: ${sessionLevelKeyId} uri: ${mediaKeySessionContext.decryptdata.uri}`, - ); if (matched) { + hasMatchedKey = true; mediaKeySessionContext.keyStatus = status; + this.log( + `matched key status change "${status}" for keyStatuses keyId: ${keyIdWithStatusChange} session keyId: ${sessionLevelKeyId} uri: ${mediaKeySessionContext.decryptdata.uri}`, + ); + } else { + this.log( + `unmatched key status change "${status}" for keyStatuses keyId: ${keyIdWithStatusChange} session keyId: ${sessionLevelKeyId} uri: ${mediaKeySessionContext.decryptdata.uri}`, + ); } }, ); + + // Handle case where no keys matched but all have the same status + // This can happen with PlayReady when key IDs don't align properly + if (!hasMatchedKey && keyStatuses.length > 0) { + const firstStatus = keyStatuses[0].status; + const allSameStatus = !keyStatuses.some( + ({ status }) => status !== firstStatus, + ); + + if ( + allSameStatus && + (firstStatus === 'usable' || firstStatus.startsWith('usable')) + ) { + this.log( + `No key matched session keyId ${sessionLevelKeyId}, but all keys have usable status "${firstStatus}". Treating as usable.`, + ); + mediaKeySessionContext.keyStatus = firstStatus; + } else if ( + allSameStatus && + (firstStatus === 'internal-error' || firstStatus === 'expired') + ) { + this.log( + `No key matched session keyId ${sessionLevelKeyId}, but all keys have error status "${firstStatus}". Applying to session.`, + ); + mediaKeySessionContext.keyStatus = firstStatus; + } else { + this.log( + `No key matched session keyId ${sessionLevelKeyId}. Key statuses: ${keyStatuses.map(({ keyId, status }) => `${keyId}:${status}`).join(', ')}`, + ); + } + } } private fetchServerCertificate( From 4a334e9d31255b40c96b62c4dc26daa08d775c06 Mon Sep 17 00:00:00 2001 From: Shubham Sharma <124438439+ShubhamSharma2311@users.noreply.github.com> Date: Sat, 30 Aug 2025 01:00:13 +0530 Subject: [PATCH 38/64] Fix PlayReady key endianness (#7510) (cherry picked from commit f9e23c04461c6ca07c156d47de7acf235746d3fb) --- src/controller/eme-controller.ts | 69 +++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 6 deletions(-) diff --git a/src/controller/eme-controller.ts b/src/controller/eme-controller.ts index b7e8488f787..f6999809bc4 100644 --- a/src/controller/eme-controller.ts +++ b/src/controller/eme-controller.ts @@ -12,6 +12,7 @@ import { removeEventListener, } from '../utils/event-listener-helper'; import { arrayToHex } from '../utils/hex'; +import { changeEndianness } from '../utils/keysystem-util'; import { Logger } from '../utils/logger'; import { getKeySystemsForConfig, @@ -881,6 +882,10 @@ class EMEController extends Logger implements ComponentAPI { const sessionLevelKeyId = arrayToHex( new Uint8Array(mediaKeySessionContext.decryptdata.keyId || []), ); + + let hasMatchedKey = false; + const keyStatuses: { status: MediaKeyStatus; keyId: string }[] = []; + mediaKeySessionContext.mediaKeysSession.keyStatuses.forEach( (status: MediaKeyStatus, keyId: BufferSource) => { // keyStatuses.forEach is not standard API so the callback value looks weird on xboxone @@ -890,27 +895,79 @@ class EMEController extends Logger implements ComponentAPI { keyId = status; status = temp; } - const keyIdWithStatusChange = arrayToHex( + + const keyIdArray: Uint8Array = 'buffer' in keyId ? new Uint8Array(keyId.buffer, keyId.byteOffset, keyId.byteLength) - : new Uint8Array(keyId), + : new Uint8Array(keyId); + + // Handle PlayReady little-endian key ID conversion for status comparison only + // Don't modify the original key ID from playlist parsing + if ( + mediaKeySessionContext.keySystem === KeySystems.PLAYREADY && + keyIdArray.length === 16 + ) { + changeEndianness(keyIdArray); + } + + const keyIdWithStatusChange = arrayToHex( + keyIdArray as Uint8Array, ); + // Store all key statuses for processing + keyStatuses.push({ status, keyId: keyIdWithStatusChange }); + // Error immediately when encountering a key ID with this status again if (status === 'internal-error') { this.bannedKeyIds[keyIdWithStatusChange] = status; } - // Only acknowledge status changes for level-key ID + // Check if this key matches the session-level key ID const matched = keyIdWithStatusChange === sessionLevelKeyId; - this.log( - `${matched ? '' : 'un'}matched key status change "${status}" for keyStatuses keyId: ${keyIdWithStatusChange} session keyId: ${sessionLevelKeyId} uri: ${mediaKeySessionContext.decryptdata.uri}`, - ); if (matched) { + hasMatchedKey = true; mediaKeySessionContext.keyStatus = status; + this.log( + `matched key status change "${status}" for keyStatuses keyId: ${keyIdWithStatusChange} session keyId: ${sessionLevelKeyId} uri: ${mediaKeySessionContext.decryptdata.uri}`, + ); + } else { + this.log( + `unmatched key status change "${status}" for keyStatuses keyId: ${keyIdWithStatusChange} session keyId: ${sessionLevelKeyId} uri: ${mediaKeySessionContext.decryptdata.uri}`, + ); } }, ); + + // Handle case where no keys matched but all have the same status + // This can happen with PlayReady when key IDs don't align properly + if (!hasMatchedKey && keyStatuses.length > 0) { + const firstStatus = keyStatuses[0].status; + const allSameStatus = !keyStatuses.some( + ({ status }) => status !== firstStatus, + ); + + if ( + allSameStatus && + (firstStatus === 'usable' || firstStatus.startsWith('usable')) + ) { + this.log( + `No key matched session keyId ${sessionLevelKeyId}, but all keys have usable status "${firstStatus}". Treating as usable.`, + ); + mediaKeySessionContext.keyStatus = firstStatus; + } else if ( + allSameStatus && + (firstStatus === 'internal-error' || firstStatus === 'expired') + ) { + this.log( + `No key matched session keyId ${sessionLevelKeyId}, but all keys have error status "${firstStatus}". Applying to session.`, + ); + mediaKeySessionContext.keyStatus = firstStatus; + } else { + this.log( + `No key matched session keyId ${sessionLevelKeyId}. Key statuses: ${keyStatuses.map(({ keyId, status }) => `${keyId}:${status}`).join(', ')}`, + ); + } + } } private fetchServerCertificate( From 656bff4809db9c1866b9345aad2395cfea4f7671 Mon Sep 17 00:00:00 2001 From: xiao Date: Fri, 5 Sep 2025 07:55:17 +0800 Subject: [PATCH 39/64] Allow removing TextTracks created by hls.js (#7515) * allow removing text tracks * add unit tests for id3 track controller * fix textTracks may loss existing cues on Safari --- api-extractor/report/hls.js.api.md | 3 +- demo/main.js | 28 +- src/controller/buffer-controller.ts | 2 - src/controller/id3-track-controller.ts | 52 +--- src/controller/interstitials-controller.ts | 1 - src/controller/subtitle-track-controller.ts | 234 ++++++++------- src/controller/timeline-controller.ts | 259 ++++------------ src/hls.ts | 8 +- src/types/buffer.ts | 1 - src/types/media-playlist.ts | 2 + src/utils/media-option-attributes.ts | 12 - src/utils/texttrack-utils.ts | 93 +++--- tests/index.js | 1 + tests/unit/controller/id3-track-controller.js | 28 ++ .../controller/interstitials-controller.ts | 5 + .../controller/subtitle-track-controller.ts | 283 +++++++++++++----- tests/unit/controller/timeline-controller.ts | 176 +---------- tests/unit/utils/texttrack-utils.js | 38 +-- 18 files changed, 499 insertions(+), 727 deletions(-) create mode 100644 tests/unit/controller/id3-track-controller.js diff --git a/api-extractor/report/hls.js.api.md b/api-extractor/report/hls.js.api.md index 5f9595868cb..eb9591fb944 100644 --- a/api-extractor/report/hls.js.api.md +++ b/api-extractor/report/hls.js.api.md @@ -4015,7 +4015,6 @@ export interface MediaKeySessionContext { export type MediaOverrides = { duration?: number; endOfStream?: boolean; - cueRemoval?: boolean; }; // Warning: (ae-missing-release-tag) "MediaPlaylist" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -4057,6 +4056,8 @@ export interface MediaPlaylist { // (undocumented) textCodec?: string; // (undocumented) + trackNode?: HTMLTrackElement; + // (undocumented) type: MediaPlaylistType | 'main'; // (undocumented) unknownCodecs?: string[]; diff --git a/demo/main.js b/demo/main.js index 50514a9ef2e..16c48875493 100644 --- a/demo/main.js +++ b/demo/main.js @@ -43,7 +43,7 @@ let stopOnStall = getDemoConfigPropOrDefault('stopOnStall', false); let bufferingIdx = -1; let selectedTestStream = null; -let video = document.querySelector('#video'); +const video = document.querySelector('#video'); const startTime = Date.now(); let lastSeekingIdx; @@ -222,6 +222,7 @@ $(document).ready(function () { $('#metricsButtonWindow').toggle(self.windowSliding); $('#metricsButtonFixed').toggle(!self.windowSliding); + addVideoEventListeners(video); loadSelectedStream(); let tabIndexesCSV = localStorage.getItem(STORAGE_KEYS.demo_tabs); @@ -353,32 +354,7 @@ function loadSelectedStream() { logStatus('Loading manifest and attaching video element...'); - const expiredTracks = [].filter.call( - video.textTracks, - (track) => track.kind !== 'metadata' - ); - if (expiredTracks.length) { - const kinds = expiredTracks - .map((track) => track.kind) - .filter((kind, index, self) => self.indexOf(kind) === index); - logStatus( - `Replacing video element to remove ${kinds.join(' and ')} text tracks` - ); - const videoWithExpiredTextTracks = video; - video = videoWithExpiredTextTracks.cloneNode(false); - video.removeAttribute('src'); - video.volume = videoWithExpiredTextTracks.volume; - video.muted = videoWithExpiredTextTracks.muted; - videoWithExpiredTextTracks.parentNode.insertBefore( - video, - videoWithExpiredTextTracks - ); - videoWithExpiredTextTracks.parentNode.removeChild( - videoWithExpiredTextTracks - ); - } addChartEventListeners(hls); - addVideoEventListeners(video); hls.loadSource(url); hls.autoLevelCapping = levelCapping; diff --git a/src/controller/buffer-controller.ts b/src/controller/buffer-controller.ts index b13ec5edca5..d27c56c4d8a 100755 --- a/src/controller/buffer-controller.ts +++ b/src/controller/buffer-controller.ts @@ -544,8 +544,6 @@ transfer tracks: ${stringify(transferredTracks, (key, value) => (key === 'initSe } this.media = null; } - - this.hls.trigger(Events.MEDIA_DETACHED, data); } private onBufferReset() { diff --git a/src/controller/id3-track-controller.ts b/src/controller/id3-track-controller.ts index 24a8d0d1e43..9bd02ab6f37 100644 --- a/src/controller/id3-track-controller.ts +++ b/src/controller/id3-track-controller.ts @@ -8,11 +8,7 @@ import { import { MetadataSchema } from '../types/demuxer'; import { hexToArrayBuffer } from '../utils/hex'; import { stringify } from '../utils/safe-json-stringify'; -import { - clearCurrentCues, - removeCuesInRange, - sendAddTrackEvent, -} from '../utils/texttrack-utils'; +import { createTrackNode, removeCuesInRange } from '../utils/texttrack-utils'; import type { MediaFragment } from '../hls'; import type Hls from '../hls'; import type { DateRange } from '../loader/date-range'; @@ -77,7 +73,7 @@ const MAX_CUE_ENDTIME = (() => { class ID3TrackController implements ComponentAPI { private hls: Hls | null; - private id3Track: TextTrack | null = null; + private id3Track: HTMLTrackElement | null = null; private media: HTMLMediaElement | null = null; private dateRangeCuesAppended: Record< string, @@ -146,9 +142,6 @@ class ID3TrackController implements ComponentAPI { data: MediaAttachingData, ): void { this.media = data.media; - if (data.overrides?.cueRemoval === false) { - this.removeCues = false; - } } private onMediaAttached() { @@ -168,9 +161,7 @@ class ID3TrackController implements ComponentAPI { return; } if (this.id3Track) { - if (this.removeCues) { - clearCurrentCues(this.id3Track, this.onEventCueEnter); - } + this.id3Track.remove(); this.id3Track = null; } this.dateRangeCuesAppended = {}; @@ -180,27 +171,8 @@ class ID3TrackController implements ComponentAPI { this.dateRangeCuesAppended = {}; } - private createTrack(media: HTMLMediaElement): TextTrack { - const track = this.getID3Track(media.textTracks) as TextTrack; - track.mode = 'hidden'; - return track; - } - - private getID3Track(textTracks: TextTrackList): TextTrack | void { - if (!this.media) { - return; - } - for (let i = 0; i < textTracks.length; i++) { - const textTrack: TextTrack = textTracks[i]; - if (textTrack.kind === 'metadata' && textTrack.label === 'id3') { - // send 'addtrack' when reusing the textTrack for metadata, - // same as what we do for captions - sendAddTrackEvent(textTrack, this.media); - - return textTrack; - } - } - return this.media.addTextTrack('metadata', 'id3'); + private createTrack(media: HTMLMediaElement): HTMLTrackElement { + return createTrackNode(media, 'metadata', 'id3', '', 'hidden'); } private onFragParsingMetadata( @@ -264,7 +236,7 @@ class ID3TrackController implements ComponentAPI { type, ); if (cue) { - this.id3Track.addCue(cue); + this.id3Track.track.addCue(cue); } } } @@ -272,7 +244,7 @@ class ID3TrackController implements ComponentAPI { } private updateId3CueEnds(startTime: number, type: MetadataSchema) { - const cues = this.id3Track?.cues; + const cues = this.id3Track?.track.cues; if (cues) { for (let i = cues.length; i--; ) { const cue = cues[i] as any; @@ -315,7 +287,7 @@ class ID3TrackController implements ComponentAPI { enableID3MetadataCues) || ((cue as any).type === MetadataSchema.emsg && enableEmsgMetadataCues); } - removeCuesInRange(id3Track, startOffset, endOffset, predicate); + removeCuesInRange(id3Track.track, startOffset, endOffset, predicate); } } @@ -372,7 +344,7 @@ class ID3TrackController implements ComponentAPI { if (cue) { cue.id = assetPlayerId; this.id3Track ||= this.createTrack(this.media); - this.id3Track.addCue(cue); + this.id3Track.track.addCue(cue); cue.addEventListener('enter', this.onEventCueEnter); } } @@ -387,7 +359,7 @@ class ID3TrackController implements ComponentAPI { let dateRangeCuesAppended = this.dateRangeCuesAppended; // Remove cues from track not found in details.dateRanges if (id3Track && removeOldCues) { - if (id3Track.cues?.length) { + if (id3Track.track.cues?.length) { const idsToRemove = Object.keys(dateRangeCuesAppended).filter( (id) => !ids.includes(id), ); @@ -401,7 +373,7 @@ class ID3TrackController implements ComponentAPI { if (cue) { cue.removeEventListener('enter', this.onEventCueEnter); try { - id3Track.removeCue(cue); + id3Track.track.removeCue(cue); } catch (e) { /* no-op */ } @@ -492,7 +464,7 @@ class ID3TrackController implements ComponentAPI { ); if (cue) { cue.id = id; - this.id3Track.addCue(cue); + this.id3Track.track.addCue(cue); cues[key] = cue; if (__USE_INTERSTITIALS__ && interstitialsController) { if (key === 'X-ASSET-LIST' || key === 'X-ASSET-URL') { diff --git a/src/controller/interstitials-controller.ts b/src/controller/interstitials-controller.ts index abe29884b49..594707dc87c 100644 --- a/src/controller/interstitials-controller.ts +++ b/src/controller/interstitials-controller.ts @@ -871,7 +871,6 @@ export default class InterstitialsController dataToAttach.overrides = { duration: schedule.duration, endOfStream: !isAssetPlayer || isAssetAtEndOfSchedule, - cueRemoval: !isAssetPlayer, }; } player.attachMedia(dataToAttach); diff --git a/src/controller/subtitle-track-controller.ts b/src/controller/subtitle-track-controller.ts index 976961dd98c..5f4c2be6dda 100644 --- a/src/controller/subtitle-track-controller.ts +++ b/src/controller/subtitle-track-controller.ts @@ -1,15 +1,10 @@ import BasePlaylistController from './base-playlist-controller'; import { Events } from '../events'; import { PlaylistContextType } from '../types/loader'; -import { - mediaAttributesIdentical, - subtitleTrackMatchesTextTrack, -} from '../utils/media-option-attributes'; +import { IMSC1_CODEC } from '../utils/imsc1-ttml-parser'; +import { mediaAttributesIdentical } from '../utils/media-option-attributes'; import { findMatchingOption, matchesOption } from '../utils/rendition-helper'; -import { - clearCurrentCues, - filterSubtitleTracks, -} from '../utils/texttrack-utils'; +import { createTrackNode, getTrackKind } from '../utils/texttrack-utils'; import type Hls from '../hls'; import type { ErrorData, @@ -92,31 +87,51 @@ class SubtitleTrackController extends BasePlaylistController { hls.off(Events.ERROR, this.onError, this); } + private createTracksInGroup() { + if (!this.media || !this.hls.config.renderTextTracksNatively) { + return; + } + this.tracksInGroup.forEach((track) => { + if (!track.trackNode) { + track.trackNode = createTrackNode( + this.media!, + getTrackKind(track), + track.name, + track.lang, + ); + } + }); + const track = this.currentTrack?.trackNode?.track; + // new tracks are disable before appending + if (track?.mode === 'disabled') { + track.mode = this._subtitleDisplay ? 'showing' : 'hidden'; + } + } + // Listen for subtitle track change, then extract the current track ID. protected onMediaAttached( event: Events.MEDIA_ATTACHED, data: MediaAttachedData, ): void { - this.media = data.media; - if (!this.media) { - return; - } + const media = data.media; + this.media = media; + let trackId = this.trackId; if (this.queuedDefaultTrack > -1) { - this.subtitleTrack = this.queuedDefaultTrack; + trackId = this.queuedDefaultTrack; this.queuedDefaultTrack = -1; } + this.setSubtitleTrack(trackId); + this.createTracksInGroup(); + this.useTextTrackPolling = !( - this.media.textTracks && 'onchange' in this.media.textTracks + media.textTracks && 'onchange' in media.textTracks ); if (this.useTextTrackPolling) { this.pollTrackChange(500); } else { - this.media.textTracks.addEventListener( - 'change', - this.asyncPollTrackChange, - ); + media.textTracks.addEventListener('change', this.asyncPollTrackChange); } } @@ -145,20 +160,24 @@ class SubtitleTrackController extends BasePlaylistController { if (this.trackId > -1) { this.queuedDefaultTrack = this.trackId; + + // Disable all subtitle tracks before detachment so when reattached only tracks in that content are enabled. + this.setSubtitleTrack(-1); } - // Disable all subtitle tracks before detachment so when reattached only tracks in that content are enabled. - this.subtitleTrack = -1; this.media = null; if (transferringMedia) { return; } - const textTracks = filterSubtitleTracks(media.textTracks); - // Clear loaded cues on media detachment from tracks - textTracks.forEach((track) => { - clearCurrentCues(track); - }); + if (this.hls.config.renderTextTracksNatively) { + this.tracksInGroup.forEach((track) => { + if (track.trackNode) { + track.trackNode.remove(); + track.trackNode = undefined; + } + }); + } } protected onManifestLoading(): void { @@ -231,13 +250,36 @@ class SubtitleTrackController extends BasePlaylistController { subtitleGroups?.some((groupId) => currentGroups?.indexOf(groupId) === -1) ) { this.groupIds = subtitleGroups; - this.trackId = -1; - this.currentTrack = null; - - const subtitleTracks = this.tracks.filter( - (track): boolean => - !subtitleGroups || subtitleGroups.indexOf(track.groupId) !== -1, - ); + const subtitleTracks: MediaPlaylist[] = []; + this.tracks.forEach((track) => { + if ( + track.textCodec === IMSC1_CODEC + ? this.hls.config.enableIMSC1 + : this.hls.config.enableWebVTT + ) { + if (!subtitleGroups || subtitleGroups.includes(track.groupId)) { + // track.id should match hls.subtitleTracks index + track.id = subtitleTracks.length; + subtitleTracks.push(track); + } else if (track.trackNode) { + track.trackNode.remove(); + track.trackNode = undefined; + } + } + }); + if (subtitleTracks.length === this.tracksInGroup.length) { + let diff = false; + for (let i = 0; i < subtitleTracks.length; i++) { + if (subtitleTracks[i] !== this.tracksInGroup[i]) { + diff = true; + break; + } + } + if (!diff) { + // Do not dispatch SUBTITLE_TRACKS_UPDATED if there are no changes + return; + } + } if (subtitleTracks.length) { // Disable selectDefaultTrack if there are no default tracks if ( @@ -246,15 +288,10 @@ class SubtitleTrackController extends BasePlaylistController { ) { this.selectDefaultTrack = false; } - // track.id should match hls.audioTracks index - subtitleTracks.forEach((track, i) => { - track.id = i; - }); - } else if (!currentTrack && !this.tracksInGroup.length) { - // Do not dispatch SUBTITLE_TRACKS_UPDATED when there were and are no tracks - return; } this.tracksInGroup = subtitleTracks; + this.trackId = -1; + this.currentTrack = null; // Find preferred track const subtitlePreference = this.hls.config.subtitlePreference; @@ -288,10 +325,8 @@ class SubtitleTrackController extends BasePlaylistController { } track(s) found in "${subtitleGroups?.join(',')}" group-id`, ); this.hls.trigger(Events.SUBTITLE_TRACKS_UPDATED, subtitleTracksUpdated); - - if (trackId !== -1 && this.trackId === -1) { - this.setSubtitleTrack(trackId); - } + this.setSubtitleTrack(trackId); + this.createTracksInGroup(); } } @@ -337,19 +372,6 @@ class SubtitleTrackController extends BasePlaylistController { return -1; } - private findTrackForTextTrack(textTrack: TextTrack | null): number { - if (textTrack) { - const tracks = this.tracksInGroup; - for (let i = 0; i < tracks.length; i++) { - const track = tracks[i]; - if (subtitleTrackMatchesTextTrack(track, textTrack)) { - return i; - } - } - } - return -1; - } - protected onError(event: Events.ERROR, data: ErrorData): void { if (data.fatal || !data.context) { return; @@ -380,7 +402,7 @@ class SubtitleTrackController extends BasePlaylistController { set subtitleTrack(newId: number) { this.selectDefaultTrack = false; - this.setSubtitleTrack(newId); + this.setSubtitleTrack(newId, true); } public setSubtitleOption( @@ -389,7 +411,7 @@ class SubtitleTrackController extends BasePlaylistController { this.hls.config.subtitlePreference = subtitleOption; if (subtitleOption) { if (subtitleOption.id === -1) { - this.setSubtitleTrack(-1); + this.setSubtitleTrack(-1, true); return null; } const allSubtitleTracks = this.allSubtitleTracks; @@ -407,7 +429,7 @@ class SubtitleTrackController extends BasePlaylistController { ); if (groupIndex > -1) { const track = this.tracksInGroup[groupIndex]; - this.setSubtitleTrack(groupIndex); + this.setSubtitleTrack(groupIndex, true); return track; } else if (currentTrack) { // If this is not the initial selection return null @@ -467,42 +489,32 @@ class SubtitleTrackController extends BasePlaylistController { * A value of -1 will disable all subtitle tracks. */ private toggleTrackModes(): void { - const { media } = this; - if (!media) { + if (!this.media || !this.hls.config.renderTextTracksNatively) { return; } - const textTracks = filterSubtitleTracks(media.textTracks); - const currentTrack = this.currentTrack; - let nextTrack; - if (currentTrack) { - nextTrack = textTracks.filter((textTrack) => - subtitleTrackMatchesTextTrack(currentTrack, textTrack), - )[0]; - if (!nextTrack) { - this.warn( - `Unable to find subtitle TextTrack with name "${currentTrack.name}" and language "${currentTrack.lang}"`, - ); - } - } - [].slice.call(textTracks).forEach((track) => { - if (track.mode !== 'disabled' && track !== nextTrack) { - track.mode = 'disabled'; + const nextTrack = this.currentTrack; + this.tracksInGroup.forEach((track) => { + if (track.trackNode) { + const mode = + track === nextTrack + ? this._subtitleDisplay + ? 'showing' + : 'hidden' + : 'disabled'; + const textTrack = track.trackNode.track; + if (textTrack.mode !== mode) { + textTrack.mode = mode; + } } }); - if (nextTrack) { - const mode = this.subtitleDisplay ? 'showing' : 'hidden'; - if (nextTrack.mode !== mode) { - nextTrack.mode = mode; - } - } } /** * This method is responsible for validating the subtitle index and periodically reloading if live. * Dispatches the SUBTITLE_TRACK_SWITCH event, which instructs the subtitle-stream-controller to load the selected track. */ - private setSubtitleTrack(newId: number): void { + private setSubtitleTrack(newId: number, toggleModes: boolean = false): void { const tracks = this.tracksInGroup; // setting this.subtitleTrack will trigger internal logic @@ -520,19 +532,22 @@ class SubtitleTrackController extends BasePlaylistController { return; } - this.selectDefaultTrack = false; const lastTrack = this.currentTrack; const track: MediaPlaylist | null = tracks[newId] || null; this.trackId = newId; this.currentTrack = track; - this.toggleTrackModes(); + if (toggleModes) { + this.toggleTrackModes(); + } if (!track) { - // switch to -1 - this.hls.trigger(Events.SUBTITLE_TRACK_SWITCH, { id: newId }); + if (lastTrack) { + // switch to -1 + this.hls.trigger(Events.SUBTITLE_TRACK_SWITCH, { id: newId }); + } return; } const trackLoaded = !!track.details && !track.details.live; - if (newId === this.trackId && track === lastTrack && trackLoaded) { + if (track === lastTrack && trackLoaded) { return; } this.log( @@ -565,24 +580,35 @@ class SubtitleTrackController extends BasePlaylistController { if (!this.media || !this.hls.config.renderTextTracksNatively) { return; } - - let textTrack: TextTrack | null = null; - const tracks = filterSubtitleTracks(this.media.textTracks); - for (let i = 0; i < tracks.length; i++) { - if (tracks[i].mode === 'hidden') { - // Do not break in case there is a following track with showing. - textTrack = tracks[i]; - } else if (tracks[i].mode === 'showing') { - textTrack = tracks[i]; - break; + let trackId = -1; + let found = false; + // Prefer previously selected track + if (this.currentTrack) { + const mode = this.currentTrack.trackNode?.track.mode; + if (mode === 'showing') { + trackId = this.trackId; + found = true; + } else if (mode === 'hidden') { + trackId = this.trackId; } } - - // Find internal track index for TextTrack - const trackId = this.findTrackForTextTrack(textTrack); - if (this.subtitleTrack !== trackId) { - this.setSubtitleTrack(trackId); + if (!found) { + for (let i = 0; i < this.tracksInGroup.length; i++) { + const mode = this.tracksInGroup[i].trackNode?.track.mode; + if (mode === 'showing') { + trackId = i; + break; + } else if (trackId < 0 && mode === 'hidden') { + // If there is no showing track, we can use the hidden track + trackId = i; + } + } + } + if (trackId > -1) { + this._subtitleDisplay = + this.tracksInGroup[trackId].trackNode?.track.mode === 'showing'; } + this.setSubtitleTrack(trackId, true); }; } diff --git a/src/controller/timeline-controller.ts b/src/controller/timeline-controller.ts index 7f8e38240fd..b90c2968aba 100644 --- a/src/controller/timeline-controller.ts +++ b/src/controller/timeline-controller.ts @@ -2,18 +2,13 @@ import { Events } from '../events'; import { PlaylistLevelType } from '../types/loader'; import Cea608Parser from '../utils/cea-608-parser'; import { IMSC1_CODEC, parseIMSC1 } from '../utils/imsc1-ttml-parser'; -import { - subtitleOptionsIdentical, - subtitleTrackMatchesTextTrack, -} from '../utils/media-option-attributes'; +import { subtitleOptionsIdentical } from '../utils/media-option-attributes'; import { appendUint8Array } from '../utils/mp4-tools'; import OutputFilter from '../utils/output-filter'; import { addCueToTrack, - clearCurrentCues, - filterSubtitleTracks, + createTrackNode, removeCuesInRange, - sendAddTrackEvent, } from '../utils/texttrack-utils'; import { parseWebVTT } from '../utils/webvtt-parser'; import type { HlsConfig } from '../config'; @@ -59,11 +54,10 @@ export class TimelineController implements ComponentAPI { private config: HlsConfig; private enabled: boolean = true; private Cues: CuesInterface; - private textTracks: Array = []; private tracks: Array = []; private initPTS: TimestampOffset[] = []; private unparsedVttFrags: Array = []; - private captionsTracks: Record = {}; + private captionsTracks: Record = {}; private nonNativeCaptionsTracks: Record = {}; private cea608Parser1?: Cea608Parser; private cea608Parser2?: Cea608Parser; @@ -176,7 +170,7 @@ export class TimelineController implements ComponentAPI { } if (this.config.renderTextTracksNatively) { - const track = this.captionsTracks[trackName]; + const track = this.captionsTracks[trackName].track; this.Cues.newCue(track, startTime, endTime, screen); } else { const cues = this.Cues.newCue(null, startTime, endTime, screen); @@ -218,27 +212,6 @@ export class TimelineController implements ComponentAPI { } } - private getExistingTrack(label: string, language: string): TextTrack | null { - const { media } = this; - if (media) { - for (let i = 0; i < media.textTracks.length; i++) { - const textTrack = media.textTracks[i]; - if ( - canReuseVttTextTrack(textTrack, { - name: label, - lang: language, - characteristics: - 'transcribes-spoken-dialog,describes-music-and-sound', - attrs: {} as any, - }) - ) { - return textTrack; - } - } - } - return null; - } - public createCaptionsTrack(trackName: string) { if (this.config.renderTextTracksNatively) { this.createNativeTrack(trackName); @@ -253,19 +226,13 @@ export class TimelineController implements ComponentAPI { } const { captionsProperties, captionsTracks, media } = this; const { label, languageCode } = captionsProperties[trackName]; - // Enable reuse of existing text track. - const existingTrack = this.getExistingTrack(label, languageCode); - if (!existingTrack) { - const textTrack = this.createTextTrack('captions', label, languageCode); - if (textTrack) { - // Set a special property on the track so we know it's managed by Hls.js - textTrack[trackName] = true; - captionsTracks[trackName] = textTrack; - } - } else { - captionsTracks[trackName] = existingTrack; - clearCurrentCues(captionsTracks[trackName]); - sendAddTrackEvent(captionsTracks[trackName], media as HTMLMediaElement); + if (media) { + captionsTracks[trackName] = createTrackNode( + media, + 'captions', + label, + languageCode, + ); } } @@ -290,26 +257,11 @@ export class TimelineController implements ComponentAPI { this.hls.trigger(Events.NON_NATIVE_TEXT_TRACKS_FOUND, { tracks: [track] }); } - private createTextTrack( - kind: TextTrackKind, - label: string, - lang?: string, - ): TextTrack | undefined { - const media = this.media; - if (!media) { - return; - } - return media.addTextTrack(kind, label, lang); - } - private onMediaAttaching( event: Events.MEDIA_ATTACHING, data: MediaAttachingData, ) { this.media = data.media; - if (!data.mediaSource) { - this._cleanTracks(); - } } private onMediaDetaching( @@ -322,11 +274,13 @@ export class TimelineController implements ComponentAPI { return; } - const { captionsTracks } = this; - Object.keys(captionsTracks).forEach((trackName) => { - clearCurrentCues(captionsTracks[trackName]); - delete captionsTracks[trackName]; - }); + if (this.config.renderTextTracksNatively) { + const { captionsTracks } = this; + for (const trackName in captionsTracks) { + captionsTracks[trackName].remove(); + } + } + this.captionsTracks = {}; this.nonNativeCaptionsTracks = {}; } @@ -338,12 +292,9 @@ export class TimelineController implements ComponentAPI { // Detect discontinuity in subtitle manifests this.prevCC = -1; this.vttCCs = newVTTCCs(); - // Reset tracks - this._cleanTracks(); this.tracks = []; this.captionsTracks = {}; this.nonNativeCaptionsTracks = {}; - this.textTracks = []; this.unparsedVttFrags = []; this.initPTS = []; if (this.cea608Parser1 && this.cea608Parser2) { @@ -352,105 +303,30 @@ export class TimelineController implements ComponentAPI { } } - private _cleanTracks() { - // clear outdated subtitles - const { media } = this; - if (!media) { - return; - } - const textTracks = media.textTracks; - if (textTracks) { - for (let i = 0; i < textTracks.length; i++) { - clearCurrentCues(textTracks[i]); - } - } - } - private onSubtitleTracksUpdated( event: Events.SUBTITLE_TRACKS_UPDATED, data: SubtitleTracksUpdatedData, ) { const tracks: Array = data.subtitleTracks || []; - const hasIMSC1 = tracks.some((track) => track.textCodec === IMSC1_CODEC); - if (this.config.enableWebVTT || (hasIMSC1 && this.config.enableIMSC1)) { - const listIsIdentical = subtitleOptionsIdentical(this.tracks, tracks); - if (listIsIdentical) { - this.tracks = tracks; - return; - } - this.textTracks = []; - this.tracks = tracks; - - if (this.config.renderTextTracksNatively) { - const media = this.media; - const inUseTracks: (TextTrack | null)[] | null = media - ? filterSubtitleTracks(media.textTracks) - : null; - - this.tracks.forEach((track, index) => { - // Reuse tracks with the same label and lang, but do not reuse 608/708 tracks - let textTrack: TextTrack | undefined; - if (inUseTracks) { - let inUseTrack: TextTrack | null = null; - for (let i = 0; i < inUseTracks.length; i++) { - if ( - inUseTracks[i] && - canReuseVttTextTrack(inUseTracks[i], track) - ) { - inUseTrack = inUseTracks[i]; - inUseTracks[i] = null; - break; - } - } - if (inUseTrack) { - textTrack = inUseTrack; - } - } - if (textTrack) { - clearCurrentCues(textTrack); - } else { - const textTrackKind = captionsOrSubtitlesFromCharacteristics(track); - textTrack = this.createTextTrack( - textTrackKind, - track.name, - track.lang, - ); - if (textTrack) { - textTrack.mode = 'disabled'; - } - } - if (textTrack) { - this.textTracks.push(textTrack); - } - }); - // Warn when video element has captions or subtitle TextTracks carried over from another source - if (inUseTracks?.length) { - const unusedTextTracks = inUseTracks - .filter((t) => t !== null) - .map((t) => (t as TextTrack).label); - if (unusedTextTracks.length) { - this.hls.logger.warn( - `Media element contains unused subtitle tracks: ${unusedTextTracks.join( - ', ', - )}. Replace media element for each source to clear TextTracks and captions menu.`, - ); - } - } - } else if (this.tracks.length) { - // Create a list of tracks for the provider to consume - const tracksList = this.tracks.map((track) => { - return { - label: track.name, - kind: track.type.toLowerCase(), - default: track.default, - subtitleTrack: track, - }; - }); - this.hls.trigger(Events.NON_NATIVE_TEXT_TRACKS_FOUND, { - tracks: tracksList, - }); - } + if ( + tracks.length && + !this.config.renderTextTracksNatively && + !subtitleOptionsIdentical(this.tracks, tracks) + ) { + // Create a list of tracks for the provider to consume + const tracksList = tracks.map((track) => { + return { + label: track.name, + kind: track.type.toLowerCase(), + default: track.default, + subtitleTrack: track, + }; + }); + this.hls.trigger(Events.NON_NATIVE_TEXT_TRACKS_FOUND, { + tracks: tracksList, + }); } + this.tracks = tracks; } private onManifestLoaded( @@ -513,7 +389,10 @@ export class TimelineController implements ComponentAPI { data: FragDecryptedData | FragLoadedData, ) { const { frag, payload } = data; - if (frag.type === PlaylistLevelType.SUBTITLE) { + if ( + frag.level < this.tracks.length && + frag.type === PlaylistLevelType.SUBTITLE + ) { // If fragment is subtitle type, parse as WebVTT. if (payload.byteLength) { const decryptData = frag.decryptdata; @@ -531,10 +410,7 @@ export class TimelineController implements ComponentAPI { }; this.prevCC = frag.cc; } - if ( - trackPlaylistMedia && - trackPlaylistMedia.textCodec === IMSC1_CODEC - ) { + if (trackPlaylistMedia.textCodec === IMSC1_CODEC) { this._parseIMSC1(frag, payload); } else { this._parseVTTs(data); @@ -607,7 +483,7 @@ export class TimelineController implements ComponentAPI { error.message === 'Missing initPTS for VTT MPEGTS'; if (missingInitPTS) { unparsedVttFrags.push(data); - } else { + } else if (this.config.enableIMSC1) { this._fallbackToIMSC1(frag, payload); } // Something went wrong while parsing. Trigger event with success false. @@ -645,7 +521,7 @@ export class TimelineController implements ComponentAPI { private _appendCues(cues: VTTCue[], fragLevel: number) { const hls = this.hls; if (this.config.renderTextTracksNatively) { - const textTrack = this.textTracks[fragLevel]; + const textTrack = this.tracks[fragLevel].trackNode?.track; // WebVTTParser.parse is an async method and if the currently selected text track mode is set to "disabled" // before parsing is done then don't try to access currentTrack.cues.getCueById as cues will be null // and trying to access getCueById method of cues will throw an exception @@ -721,20 +597,22 @@ export class TimelineController implements ComponentAPI { if (!type || type === 'video') { const { captionsTracks } = this; Object.keys(captionsTracks).forEach((trackName) => - removeCuesInRange(captionsTracks[trackName], startOffset, endOffset), + removeCuesInRange( + captionsTracks[trackName].track, + startOffset, + endOffset, + ), ); } if (this.config.renderTextTracksNatively) { // Clear VTT/IMSC1 subtitle cues from the subtitle TextTracks when the back buffer is flushed if (startOffset === 0 && endOffsetSubtitles !== undefined) { - const { textTracks } = this; - Object.keys(textTracks).forEach((trackName) => - removeCuesInRange( - textTracks[trackName], - startOffset, - endOffsetSubtitles, - ), - ); + this.tracks.forEach((track) => { + const textTrack = track.trackNode?.track; + if (textTrack) { + removeCuesInRange(textTrack, startOffset, endOffsetSubtitles); + } + }); } } } @@ -768,35 +646,6 @@ export class TimelineController implements ComponentAPI { } } -function captionsOrSubtitlesFromCharacteristics( - track: Pick, -): TextTrackKind { - if (track.characteristics) { - if ( - /transcribes-spoken-dialog/gi.test(track.characteristics) && - /describes-music-and-sound/gi.test(track.characteristics) - ) { - return 'captions'; - } - } - - return 'subtitles'; -} - -function canReuseVttTextTrack( - inUseTrack: TextTrack | null, - manifestTrack: Pick< - MediaPlaylist, - 'name' | 'lang' | 'attrs' | 'characteristics' - >, -): boolean { - return ( - !!inUseTrack && - inUseTrack.kind === captionsOrSubtitlesFromCharacteristics(manifestTrack) && - subtitleTrackMatchesTextTrack(manifestTrack, inUseTrack) - ); -} - function intersection(x1: number, x2: number, y1: number, y2: number): number { return Math.min(x2, y2) - Math.max(x1, y1); } diff --git a/src/hls.ts b/src/hls.ts index b0df771dac2..cf61a67b01a 100644 --- a/src/hls.ts +++ b/src/hls.ts @@ -480,8 +480,10 @@ export default class Hls implements HlsEventEmitter { */ detachMedia() { this.logger.log('detachMedia'); - this.trigger(Events.MEDIA_DETACHING, {}); + const data = {}; + this.trigger(Events.MEDIA_DETACHING, data); this._media = null; + this.trigger(Events.MEDIA_DETACHED, data); } /** @@ -490,7 +492,9 @@ export default class Hls implements HlsEventEmitter { transferMedia(): AttachMediaSourceData | null { this._media = null; const transferMedia = this.bufferController.transferMedia(); - this.trigger(Events.MEDIA_DETACHING, { transferMedia }); + const data = { transferMedia }; + this.trigger(Events.MEDIA_DETACHING, data); + this.trigger(Events.MEDIA_DETACHED, data); return transferMedia; } diff --git a/src/types/buffer.ts b/src/types/buffer.ts index a5eb5848884..4dbc1af689b 100644 --- a/src/types/buffer.ts +++ b/src/types/buffer.ts @@ -74,7 +74,6 @@ export type SourceBuffersTuple = [ export type MediaOverrides = { duration?: number; endOfStream?: boolean; - cueRemoval?: boolean; }; export interface BufferOperationQueues { diff --git a/src/types/media-playlist.ts b/src/types/media-playlist.ts index 947745a4458..e3748b8a8f6 100644 --- a/src/types/media-playlist.ts +++ b/src/types/media-playlist.ts @@ -70,6 +70,8 @@ export interface MediaPlaylist { url: string; videoCodec?: string; width?: number; + // keep a reference to the track node that is associated with this MediaPlaylist + trackNode?: HTMLTrackElement; } export interface MediaAttributes extends AttrList { diff --git a/src/utils/media-option-attributes.ts b/src/utils/media-option-attributes.ts index ff298f16015..2c31307b7a8 100644 --- a/src/utils/media-option-attributes.ts +++ b/src/utils/media-option-attributes.ts @@ -47,15 +47,3 @@ export function mediaAttributesIdentical( attrs1[subtitleAttribute] !== attrs2[subtitleAttribute], ); } - -export function subtitleTrackMatchesTextTrack( - subtitleTrack: Pick, - textTrack: TextTrack, -) { - return ( - textTrack.label.toLowerCase() === subtitleTrack.name.toLowerCase() && - (!textTrack.language || - textTrack.language.toLowerCase() === - (subtitleTrack.lang || '').toLowerCase()) - ); -} diff --git a/src/utils/texttrack-utils.ts b/src/utils/texttrack-utils.ts index 6ae14eeece6..56349eda211 100644 --- a/src/utils/texttrack-utils.ts +++ b/src/utils/texttrack-utils.ts @@ -1,16 +1,48 @@ import { logger } from './logger'; +import type { MediaPlaylist } from '../hls'; -export function sendAddTrackEvent(track: TextTrack, videoEl: HTMLMediaElement) { - let event: Event; - try { - event = new Event('addtrack'); - } catch (err) { - // for IE11 - event = document.createEvent('Event'); - event.initEvent('addtrack', false, false); +// This is a replacement of the native addTextTrack method. +// TextTracks created by the native method are unremovable since their livecycles +// are neither associated with the current media nor managed by user code. +export function createTrackNode( + videoEl: HTMLMediaElement, + kind: TextTrackKind | 'forced', + label: string, + lang: string = '', + mode: TextTrackMode = 'disabled', +): HTMLTrackElement { + const el = videoEl.ownerDocument.createElement('track'); + el.kind = kind; + el.label = label; + if (lang) { + el.srclang = lang; } - (event as any).track = track; - videoEl.dispatchEvent(event); + // To avoid an issue of the built-in captions menu in Chrome + // https://github.com/video-dev/hls.js/issues/2198#issuecomment-761614248 + // It also prevents Safari from removing cues after setting track.mode to `hidden` or `disabled` + // https://github.com/video-dev/hls.js/pull/7515#issuecomment-3251091932 + el.src = 'data:,WEBVTT'; + el.track.mode = mode; + videoEl.appendChild(el); + return el; +} + +export function getTrackKind(track: MediaPlaylist): TextTrackKind | 'forced' { + if ( + track.forced && + (navigator.vendor.includes('Apple') || + /iPhone|iPad|iPod/.test(navigator.userAgent)) + ) { + return 'forced'; + } else if ( + track.characteristics && + /transcribes-spoken-dialog/gi.test(track.characteristics) && + /describes-music-and-sound/gi.test(track.characteristics) + ) { + return 'captions'; + } + + return 'subtitles'; } export function addCueToTrack(track: TextTrack, cue: VTTCue) { @@ -49,30 +81,6 @@ export function addCueToTrack(track: TextTrack, cue: VTTCue) { } } -export function clearCurrentCues( - track: TextTrack, - enterHandler?: (e?: Event) => void, -) { - // When track.mode is disabled, track.cues will be null. - // To guarantee the removal of cues, we need to temporarily - // change the mode to hidden - const mode = track.mode; - if (mode === 'disabled') { - track.mode = 'hidden'; - } - if (track.cues) { - for (let i = track.cues.length; i--; ) { - if (enterHandler) { - track.cues[i].removeEventListener('enter', enterHandler); - } - track.removeCue(track.cues[i]); - } - } - if (mode === 'disabled') { - track.mode = mode; - } -} - export function removeCuesInRange( track: TextTrack, start: number, @@ -154,20 +162,3 @@ export function getCuesInRange( } return cuesFound; } - -export function filterSubtitleTracks( - textTrackList: TextTrackList, -): TextTrack[] { - const tracks: TextTrack[] = []; - for (let i = 0; i < textTrackList.length; i++) { - const track = textTrackList[i]; - // Edge adds a track without a label; we don't want to use it - if ( - (track.kind === 'subtitles' || track.kind === 'captions') && - track.label - ) { - tracks.push(textTrackList[i]); - } - } - return tracks; -} diff --git a/tests/index.js b/tests/index.js index 6490a348464..e80d09f1c04 100644 --- a/tests/index.js +++ b/tests/index.js @@ -17,6 +17,7 @@ import './unit/controller/ewma-bandwidth-estimator'; import './unit/controller/fragment-finders'; import './unit/controller/fragment-tracker'; import './unit/controller/gap-controller'; +import './unit/controller/id3-track-controller'; import './unit/controller/interstitials-controller'; import './unit/controller/latency-controller'; import './unit/controller/level-controller'; diff --git a/tests/unit/controller/id3-track-controller.js b/tests/unit/controller/id3-track-controller.js new file mode 100644 index 00000000000..4c4eca761ed --- /dev/null +++ b/tests/unit/controller/id3-track-controller.js @@ -0,0 +1,28 @@ +import ID3TrackController from '../../../src/controller/id3-track-controller'; +import Hls from '../../../src/hls'; +import { Events } from '../../../src/events'; + +describe('TimelineController', function () { + let id3TrackController; + let hls; + let videoElement; + + beforeEach(function () { + videoElement = document.createElement('video'); + hls = new Hls({ enableID3MetadataCues: true }); + id3TrackController = new ID3TrackController(hls); + id3TrackController.media = videoElement; + }); + + describe('createCaptionsTrack', function () { + it('should create new TextTrack in onFragParsingMetadata() and remove it in onMediaDetaching()', function () { + expect(videoElement.textTracks.length).to.equal(0); + id3TrackController.onFragParsingMetadata(Events.FRAG_PARSING_METADATA, { + samples: [], + }); + expect(videoElement.textTracks.length).to.equal(1); + id3TrackController.onMediaDetaching(Events.MEDIA_DETACHING, {}); + expect(videoElement.textTracks.length).to.equal(0); + }); + }); +}); diff --git a/tests/unit/controller/interstitials-controller.ts b/tests/unit/controller/interstitials-controller.ts index f20f61014d6..0e8e2a1cfce 100644 --- a/tests/unit/controller/interstitials-controller.ts +++ b/tests/unit/controller/interstitials-controller.ts @@ -1112,6 +1112,7 @@ fileSequence5.mp4`; Events.INTERSTITIAL_STARTED, Events.INTERSTITIAL_ASSET_STARTED, Events.MEDIA_DETACHING, + Events.MEDIA_DETACHED, ]; expect(callsWithPrerollAfterAttach).to.deep.equal( expectedEvents, @@ -1346,6 +1347,7 @@ fileSequence6.mp4`; Events.INTERSTITIAL_ASSET_PLAYER_CREATED, Events.INTERSTITIAL_ASSET_STARTED, Events.MEDIA_DETACHING, + Events.MEDIA_DETACHED, ], `Actual events after asset-list`, ); @@ -1460,6 +1462,7 @@ fileSequence6.mp4`; Events.INTERSTITIAL_ASSET_PLAYER_CREATED, Events.INTERSTITIAL_ASSET_STARTED, Events.MEDIA_DETACHING, + Events.MEDIA_DETACHED, ], `Actual events after asset-list`, ); @@ -1839,6 +1842,7 @@ fileSequence6.mp4 Events.INTERSTITIAL_ASSET_PLAYER_CREATED, Events.INTERSTITIAL_ASSET_STARTED, Events.MEDIA_DETACHING, + Events.MEDIA_DETACHED, ], `Actual events after asset-list`, ); @@ -2012,6 +2016,7 @@ fileSequence6.mp4`; Events.INTERSTITIAL_ASSET_PLAYER_CREATED, Events.INTERSTITIAL_ASSET_STARTED, Events.MEDIA_DETACHING, + Events.MEDIA_DETACHED, ], `Actual events after asset-list`, ); diff --git a/tests/unit/controller/subtitle-track-controller.ts b/tests/unit/controller/subtitle-track-controller.ts index f3a41cf0416..086768a2428 100644 --- a/tests/unit/controller/subtitle-track-controller.ts +++ b/tests/unit/controller/subtitle-track-controller.ts @@ -7,6 +7,7 @@ import Hls from '../../../src/hls'; import { LevelDetails } from '../../../src/loader/level-details'; import { LoadStats } from '../../../src/loader/load-stats'; import { AttrList } from '../../../src/utils/attr-list'; +import { IMSC1_CODEC } from '../../../src/utils/imsc1-ttml-parser'; import type { ComponentAPI, NetworkComponentAPI, @@ -40,7 +41,10 @@ describe('SubtitleTrackController', function () { let sandbox; beforeEach(function () { - hls = new Hls() as unknown as HlsTestable; + hls = new Hls({ + enableWebVTT: true, + enableIMSC1: false, + }) as unknown as HlsTestable; hls.networkControllers.forEach((component) => component.destroy()); hls.networkControllers.length = 0; hls.coreComponents.forEach((component) => component.destroy()); @@ -73,6 +77,8 @@ describe('SubtitleTrackController', function () { name: 'English', type: 'SUBTITLES', url: 'baz', + textCodec: 'webvtt', + characteristics: 'public.accessibility.transcribes-spoken-dialog', // details: { live: false }, }, { @@ -87,6 +93,7 @@ describe('SubtitleTrackController', function () { name: 'Swedish', type: 'SUBTITLES', url: 'bar', + textCodec: 'webvtt', }, { attrs: new AttrList({}) as MediaAttributes, @@ -100,9 +107,47 @@ describe('SubtitleTrackController', function () { name: 'Untitled CC', type: 'SUBTITLES', url: 'foo', + characteristics: + 'public.accessibility.transcribes-spoken-dialog,public.accessibility.describes-music-and-sound', + textCodec: 'webvtt', + // details: { live: true }, + }, + { + attrs: new AttrList({}) as MediaAttributes, + autoselect: true, + bitrate: 0, + default: false, + forced: false, + id: 3, + groupId: 'other-text-group', // different groupId + lang: 'en-US', + name: 'Untitled CC', + type: 'SUBTITLES', + url: 'foo', + characteristics: + 'public.accessibility.transcribes-spoken-dialog,public.accessibility.describes-music-and-sound', + textCodec: 'webvtt', + // details: { live: true }, + }, + { + attrs: new AttrList({}) as MediaAttributes, + autoselect: true, + bitrate: 0, + default: false, + forced: false, + id: 4, + groupId: 'default-text-group', + lang: 'en-US', + name: 'Untitled CC', + type: 'SUBTITLES', + url: 'foo', + characteristics: + 'public.accessibility.transcribes-spoken-dialog,public.accessibility.describes-music-and-sound', + textCodec: IMSC1_CODEC, // IMSC1 is disabled in this test // details: { live: true }, }, ]; + const levels = [ { subtitleGroups: ['default-text-group'], @@ -132,25 +177,6 @@ describe('SubtitleTrackController', function () { }); }; - const textTrack1 = videoElement.addTextTrack( - 'subtitles', - 'English', - 'en-US', - ); - const textTrack2 = videoElement.addTextTrack('subtitles', 'Swedish', 'sv'); - const textTrack3 = videoElement.addTextTrack( - 'captions', - 'Untitled CC', - 'en-US', - ); - - textTrack1.groupId = 'default-text-group'; - textTrack2.groupId = 'default-text-group'; - textTrack3.groupId = 'default-text-group'; - - textTrack1.mode = 'disabled'; - textTrack2.mode = 'disabled'; - textTrack3.mode = 'disabled'; sandbox = sinon.createSandbox(); }); @@ -158,71 +184,170 @@ describe('SubtitleTrackController', function () { sandbox.restore(); }); + describe('text track kind', function () { + it('the kind of TextTrack should depends on characteristics', function () { + switchLevel(); + expect(videoElement.textTracks[0].kind).to.equal('subtitles'); + expect(videoElement.textTracks[1].kind).to.equal('subtitles'); + expect(videoElement.textTracks[2].kind).to.equal('captions'); + }); + }); + + describe('removable TextTracks', function () { + it('should remove TextTracks in onMediaDetaching and add them again in onMediaAttached', function () { + switchLevel(); + expect(videoElement.textTracks.length).to.equal(3); + hls.trigger(Events.MEDIA_DETACHING, {}); + expect(videoElement.textTracks.length).to.equal(0); + hls.trigger(Events.MEDIA_ATTACHED, { + media: videoElement, + }); + expect(videoElement.textTracks.length).to.equal(3); + }); + }); + describe('onTextTracksChanged', function () { beforeEach(function () { switchLevel(); }); - it('should set subtitleTrack to -1 if disabled', function () { - expect(subtitleTrackController.subtitleTrack).to.equal(-1); - const onTextTracksChanged = sinon.spy( - subtitleTrackController, - 'onTextTracksChanged' as any, - ); + it('should not respond to unmanaged tracks', function () { + return new Promise((resolve) => { + self.setTimeout(() => { + const triggerSpy = sandbox.spy(hls, 'trigger'); - videoElement.textTracks[0].mode = 'showing'; + expect(subtitleTrackController.subtitleTrack).to.equal(-1); + const track = videoElement.addTextTrack('subtitles', 'foo', 'en'); + track.mode = 'showing'; + self.setTimeout(() => { + expect(triggerSpy).to.have.not.been.calledWith( + 'hlsSubtitleTrackSwitch', + ); + expect(track.mode).to.equal('showing'); + resolve(true); + }, 500); + }, 0); + }); + }); + it('should not touch unmanaged tracks', function () { return new Promise((resolve) => { self.setTimeout(() => { - expect(subtitleTrackController.subtitleTrack).to.equal(0); - expect(onTextTracksChanged).to.have.been.calledOnce; - videoElement.textTracks[0].mode = 'disabled'; + expect(subtitleTrackController.subtitleTrack).to.equal(-1); + + const unmanagedTrack = videoElement.addTextTrack( + 'subtitles', + 'foo', + 'en', + ); + unmanagedTrack.mode = 'showing'; + self.setTimeout(() => { - expect(subtitleTrackController.subtitleTrack).to.equal(-1); - expect(onTextTracksChanged).to.have.been.calledTwice; - resolve(true); + hls.once(Events.SUBTITLE_TRACK_SWITCH, () => { + expect(videoElement.textTracks[1].mode).to.equal('showing'); + expect(unmanagedTrack.mode).to.equal('showing'); + resolve(true); + }); + subtitleTrackController.subtitleTrack = 1; }, 500); - }, 500); + }, 0); }); }); - it('should set subtitleTrack to 0 if hidden', function () { - expect(subtitleTrackController.subtitleTrack).to.equal(-1); + it('should set subtitleTrack to -1 and keep unmanagedTrack showing', function () { + return new Promise((resolve) => { + self.setTimeout(() => { + expect(subtitleTrackController.subtitleTrack).to.equal(-1); + subtitleTrackController.subtitleTrack = 1; - videoElement.textTracks[0].mode = 'hidden'; + self.setTimeout(() => { + expect(subtitleTrackController.subtitleTrack).to.equal(1); + + const unmanagedTrack = videoElement.addTextTrack( + 'subtitles', + 'foo', + 'en', + ); + unmanagedTrack.mode = 'showing'; + videoElement.textTracks[0].mode = 'disabled'; + videoElement.textTracks[1].mode = 'disabled'; + videoElement.textTracks[2].mode = 'disabled'; + + hls.once(Events.SUBTITLE_TRACK_SWITCH, () => { + expect(subtitleTrackController.subtitleTrack).to.equal(-1); + expect(unmanagedTrack.mode).to.equal('showing'); + resolve(true); + }); + }, 500); + }, 0); + }); + }); + it('should set subtitleTrack to -1 if disabled', function () { return new Promise((resolve) => { - hls.on(Events.SUBTITLE_TRACK_SWITCH, () => { - expect(subtitleTrackController.subtitleTrack).to.equal(0); - resolve(true); - }); + self.setTimeout(() => { + expect(subtitleTrackController.subtitleTrack).to.equal(-1); + + const onTextTracksChanged = sinon.spy( + subtitleTrackController, + 'onTextTracksChanged' as any, + ); + + videoElement.textTracks[0].mode = 'showing'; + self.setTimeout(() => { + expect(subtitleTrackController.subtitleTrack).to.equal(0); + expect(onTextTracksChanged).to.have.been.calledOnce; + videoElement.textTracks[0].mode = 'disabled'; + self.setTimeout(() => { + expect(subtitleTrackController.subtitleTrack).to.equal(-1); + expect(onTextTracksChanged).to.have.been.calledTwice; + resolve(true); + }, 500); + }, 500); + }, 0); }); }); - it('should set subtitleTrack to 0 if showing', function () { - expect(subtitleTrackController.subtitleTrack).to.equal(-1); + it('should set subtitleTrack to 0 if hidden', function () { + return new Promise((resolve) => { + self.setTimeout(() => { + expect(subtitleTrackController.subtitleTrack).to.equal(-1); - videoElement.textTracks[0].mode = 'showing'; + videoElement.textTracks[0].mode = 'hidden'; + hls.on(Events.SUBTITLE_TRACK_SWITCH, () => { + expect(subtitleTrackController.subtitleTrack).to.equal(0); + resolve(true); + }); + }, 0); + }); + }); + it('should set subtitleTrack to 0 if showing', function () { return new Promise((resolve) => { - hls.on(Events.SUBTITLE_TRACK_SWITCH, () => { - expect(subtitleTrackController.subtitleTrack).to.equal(0); - resolve(true); - }); + expect(subtitleTrackController.subtitleTrack).to.equal(-1); + + videoElement.textTracks[0].mode = 'showing'; + self.setTimeout(() => { + hls.on(Events.SUBTITLE_TRACK_SWITCH, () => { + expect(subtitleTrackController.subtitleTrack).to.equal(0); + resolve(true); + }); + }, 0); }); }); it('should set subtitleTrack id captions track is showing', function () { - expect(subtitleTrackController.subtitleTrack).to.equal(-1); + return new Promise((resolve) => { + self.setTimeout(() => { + expect(subtitleTrackController.subtitleTrack).to.equal(-1); - videoElement.textTracks[2].mode = 'showing'; + videoElement.textTracks[2].mode = 'showing'; - return new Promise((resolve) => { - hls.on(Events.SUBTITLE_TRACK_SWITCH, () => { - expect(videoElement.textTracks[2].kind).to.equal('captions'); - expect(subtitleTrackController.subtitleTrack).to.equal(2); - resolve(true); - }); + hls.on(Events.SUBTITLE_TRACK_SWITCH, () => { + expect(subtitleTrackController.subtitleTrack).to.equal(2); + resolve(true); + }); + }, 0); }); }); }); @@ -320,28 +445,29 @@ describe('SubtitleTrackController', function () { }); it('should disable previous track', function () { - expect(subtitleTrackController.subtitleTrack).to.equal(-1); - - const onTextTracksChanged = sinon.spy( - subtitleTrackController, - 'onTextTracksChanged' as any, - ); - - videoElement.textTracks[0].mode = 'showing'; - return new Promise((resolve) => { self.setTimeout(() => { - expect(subtitleTrackController.subtitleTrack).to.equal(0); - expect(videoElement.textTracks[0].mode).to.equal('showing'); - expect(onTextTracksChanged).to.have.been.calledOnce; - subtitleTrackController.subtitleTrack = 1; + expect(subtitleTrackController.subtitleTrack).to.equal(-1); + + const onTextTracksChanged = sinon.spy( + subtitleTrackController, + 'onTextTracksChanged' as any, + ); + + videoElement.textTracks[0].mode = 'showing'; self.setTimeout(() => { - expect(videoElement.textTracks[0].mode).to.equal('disabled'); - expect(videoElement.textTracks[1].mode).to.equal('showing'); - expect(onTextTracksChanged).to.have.been.calledTwice; - resolve(true); + expect(subtitleTrackController.subtitleTrack).to.equal(0); + expect(videoElement.textTracks[0].mode).to.equal('showing'); + expect(onTextTracksChanged).to.have.been.calledOnce; + subtitleTrackController.subtitleTrack = 1; + self.setTimeout(() => { + expect(videoElement.textTracks[0].mode).to.equal('disabled'); + expect(videoElement.textTracks[1].mode).to.equal('showing'); + expect(onTextTracksChanged).to.have.been.calledTwice; + resolve(true); + }, 500); }, 500); - }, 500); + }, 0); }); }); @@ -411,15 +537,6 @@ describe('SubtitleTrackController', function () { ); }); - it('should trigger SUBTITLE_TRACK_SWITCH if passed -1', function () { - const triggerSpy = sandbox.spy(hls, 'trigger'); - subtitleTrackController.subtitleTrack = -1; - expect(triggerSpy.firstCall).to.have.been.calledWith( - 'hlsSubtitleTrackSwitch', - { id: -1 }, - ); - }); - it('should trigger SUBTITLE_TRACK_LOADING if the track is live and needs to be reloaded', function () { const triggerSpy = sandbox.spy(hls, 'trigger'); subtitleTracks[2].details = { diff --git a/tests/unit/controller/timeline-controller.ts b/tests/unit/controller/timeline-controller.ts index 900815a4702..8b44f3889a0 100644 --- a/tests/unit/controller/timeline-controller.ts +++ b/tests/unit/controller/timeline-controller.ts @@ -10,177 +10,25 @@ const expect = chai.expect; describe('TimelineController', function () { let timelineController; let hls; + let videoElement; beforeEach(function () { + videoElement = document.createElement('video'); hls = new Hls(); hls.config.enableWebVTT = true; - hls.config.renderNatively = true; + hls.config.renderTextTracksNatively = true; timelineController = new TimelineController(hls); - timelineController.media = document.createElement('video'); + timelineController.config.renderTextTracksNatively = true; + timelineController.media = videoElement; }); - describe('reuse text track', function () { - it('should reuse text track when track order is same between manifests', function () { - hls.subtitleTrackController = { subtitleDisplay: false }; - - timelineController.onSubtitleTracksUpdated( - Events.SUBTITLE_TRACKS_UPDATED, - { - subtitleTracks: [ - { id: 0, name: 'en', attrs: { LANGUAGE: 'en', NAME: 'en' } }, - { id: 1, name: 'ru', attrs: { LANGUAGE: 'ru', NAME: 'ru' } }, - ], - }, - ); - - // text tracks model contain only newly added manifest tracks, in same order as in manifest - expect(timelineController.textTracks[0].label).to.equal('en'); - expect(timelineController.textTracks[1].label).to.equal('ru'); - expect(timelineController.textTracks.length).to.equal(2); - // text tracks of the media contain the newly added text tracks - expect(timelineController.media.textTracks[0].label).to.equal('en'); - expect(timelineController.media.textTracks[1].label).to.equal('ru'); - expect(timelineController.media.textTracks.length).to.equal(2); - - timelineController.onSubtitleTracksUpdated( - Events.SUBTITLE_TRACKS_UPDATED, - { - subtitleTracks: [ - { id: 0, name: 'en', attrs: { LANGUAGE: 'en', NAME: 'en' } }, - { id: 1, name: 'ru', attrs: { LANGUAGE: 'ru', NAME: 'ru' } }, - ], - }, - ); - - // text tracks model contain only newly added manifest tracks, in same order - expect(timelineController.textTracks[0].label).to.equal('en'); - expect(timelineController.textTracks[1].label).to.equal('ru'); - expect(timelineController.textTracks.length).to.equal(2); - // text tracks of the media contain the previously added text tracks, in same order as the manifest order - expect(timelineController.media.textTracks[0].label).to.equal('en'); - expect(timelineController.media.textTracks[1].label).to.equal('ru'); - expect(timelineController.media.textTracks.length).to.equal(2); - }); - - it('should reuse text track when track order is not same between manifests', function () { - hls.subtitleTrackController = { subtitleDisplay: false }; - - timelineController.onSubtitleTracksUpdated(Events.MANIFEST_LOADED, { - subtitleTracks: [ - { - id: 0, - name: 'en', - lang: 'en', - attrs: { LANGUAGE: 'en', NAME: 'en' }, - }, - { - id: 1, - name: 'ru', - lang: 'ru', - attrs: { LANGUAGE: 'ru', NAME: 'ru' }, - }, - ], - }); - - // text tracks model contain only newly added manifest tracks, in same order as in manifest - expect(timelineController.textTracks[0].label).to.equal('en'); - expect(timelineController.textTracks[1].label).to.equal('ru'); - expect(timelineController.textTracks.length).to.equal(2); - // text tracks of the media contain the newly added text tracks - expect(timelineController.media.textTracks[0].label).to.equal('en'); - expect(timelineController.media.textTracks[1].label).to.equal('ru'); - expect(timelineController.media.textTracks.length).to.equal(2); - - timelineController.onSubtitleTracksUpdated(Events.MANIFEST_LOADED, { - subtitleTracks: [ - { - id: 0, - name: 'ru', - lang: 'ru', - attrs: { LANGUAGE: 'ru', NAME: 'ru' }, - }, - { - id: 1, - name: 'en', - lang: 'en', - attrs: { LANGUAGE: 'en', NAME: 'en' }, - }, - ], - }); - - // text tracks model contain only newly added manifest tracks, in same order - expect(timelineController.textTracks[0].label).to.equal('ru'); - expect(timelineController.textTracks[1].label).to.equal('en'); - expect(timelineController.textTracks.length).to.equal(2); - // text tracks of the media contain the previously added text tracks).to.equal(in opposite order to the manifest order - expect(timelineController.media.textTracks[0].label).to.equal('en'); - expect(timelineController.media.textTracks[1].label).to.equal('ru'); - expect(timelineController.media.textTracks.length).to.equal(2); - }); - }); - - describe('text track kind', function () { - it('should be kind captions when there is both transcribes-spoken-dialog and describes-music-and-sound', function () { - hls.subtitleTrackController = { subtitleDisplay: false }; - const characteristics = - 'public.accessibility.transcribes-spoken-dialog,public.accessibility.describes-music-and-sound'; - timelineController.onSubtitleTracksUpdated(Events.MANIFEST_LOADED, { - subtitleTracks: [ - { - id: 0, - name: 'en', - characteristics, - attrs: { - CHARACTERISTICS: characteristics, - }, - }, - ], - }); - - // text tracks model contain only newly added manifest tracks, in same order as in manifest - expect(timelineController.textTracks[0].kind).to.equal('captions'); - // text tracks of the media contain the newly added text tracks - expect(timelineController.media.textTracks[0].kind).to.equal('captions'); - }); - - it('should be kind subtitles when there is no describes-music-and-sound', function () { - hls.subtitleTrackController = { subtitleDisplay: false }; - - timelineController.onSubtitleTracksUpdated(Events.MANIFEST_LOADED, { - subtitleTracks: [ - { - id: 0, - name: 'en', - attrs: { - CHARACTERISTICS: 'public.accessibility.transcribes-spoken-dialog', - }, - }, - ], - }); - - // text tracks model contain only newly added manifest tracks, in same order as in manifest - expect(timelineController.textTracks[0].kind).to.equal('subtitles'); - // text tracks of the media contain the newly added text tracks - expect(timelineController.media.textTracks[0].kind).to.equal('subtitles'); - }); - - it('should be kind subtitles when there is no CHARACTERISTICS', function () { - hls.subtitleTrackController = { subtitleDisplay: false }; - - timelineController.onSubtitleTracksUpdated(Events.MANIFEST_LOADED, { - subtitleTracks: [ - { - id: 0, - name: 'en', - attrs: {}, - }, - ], - }); - - // text tracks model contain only newly added manifest tracks, in same order as in manifest - expect(timelineController.textTracks[0].kind).to.equal('subtitles'); - // text tracks of the media contain the newly added text tracks - expect(timelineController.media.textTracks[0].kind).to.equal('subtitles'); + describe('createCaptionsTrack', function () { + it('should create new TextTrack after calling and remove it when detaching', function () { + expect(videoElement.textTracks.length).to.equal(0); + timelineController.createCaptionsTrack('textTrack1'); + expect(videoElement.textTracks.length).to.equal(1); + timelineController.onMediaDetaching(Events.MEDIA_DETACHING, {}); + expect(videoElement.textTracks.length).to.equal(0); }); }); }); diff --git a/tests/unit/utils/texttrack-utils.js b/tests/unit/utils/texttrack-utils.js index fa4a0289477..bc562c44dad 100644 --- a/tests/unit/utils/texttrack-utils.js +++ b/tests/unit/utils/texttrack-utils.js @@ -1,8 +1,4 @@ -import { - sendAddTrackEvent, - clearCurrentCues, -} from '../../../src/utils/texttrack-utils'; -import sinon from 'sinon'; +import { removeCuesInRange } from '../../../src/utils/texttrack-utils'; describe('text track utils', function () { const cues = [ @@ -29,42 +25,14 @@ describe('text track utils', function () { }); }); - describe('synthetic addtrack event', function () { - it('should have the provided track as data', function (done) { - const dispatchSpy = sinon.spy(video, 'dispatchEvent'); - video.addEventListener('addtrack', function (e) { - expect(e.track).to.equal(track); - done(); - }); - - sendAddTrackEvent(track, video); - expect(dispatchSpy.calledOnce).to.be.true; - }); - - it('should fallback to document.createEvent if window.Event constructor throws', function (done) { - const stub = sinon.stub(self, 'Event'); - stub.throws(); - - const spy = sinon.spy(document, 'createEvent'); - - video.addEventListener('addtrack', function (e) { - expect(e.track).to.equal(track); - done(); - }); - - sendAddTrackEvent(track, video); - expect(spy.calledOnce).to.be.true; - }); - }); - describe('clear current cues', function () { it('should not fail with empty cue list', function () { const emptyTrack = video.addTextTrack('subtitles', 'empty'); - expect(clearCurrentCues(emptyTrack)).to.not.throw; + expect(removeCuesInRange(emptyTrack, 0, 10)).to.not.throw; }); it('should clear the cues from track', function () { - clearCurrentCues(track); + removeCuesInRange(track, 0, 10); expect(track.cues.length).to.equal(0); }); }); From 5a6228024b4ecacf4dd57866bcdff07ec163a468 Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Fri, 5 Sep 2025 14:35:35 -0700 Subject: [PATCH 40/64] Error handling for PlayReady key with no status change (#7527) * Error handling for PlayReady key with no status change #7508 * Remove hanging promises (except for renewal) * Log append queing --- api-extractor/report/hls.js.api.md | 10 +- src/controller/base-stream-controller.ts | 16 +- src/controller/buffer-controller.ts | 3 + src/controller/eme-controller.ts | 323 +++++++++++++---------- src/controller/error-controller.ts | 46 ++-- src/loader/key-loader.ts | 34 ++- src/loader/level-key.ts | 43 +-- src/loader/m3u8-parser.ts | 4 +- src/utils/error-helper.ts | 8 + src/utils/mp4-tools.ts | 1 + tests/unit/controller/eme-controller.ts | 243 ++++++++++------- 11 files changed, 451 insertions(+), 280 deletions(-) diff --git a/api-extractor/report/hls.js.api.md b/api-extractor/report/hls.js.api.md index efcc3d551ee..c88337b5f27 100644 --- a/api-extractor/report/hls.js.api.md +++ b/api-extractor/report/hls.js.api.md @@ -1170,6 +1170,8 @@ export class EMEController extends Logger implements ComponentAPI { // (undocumented) destroy(): void; // (undocumented) + getKeyStatus(decryptdata: LevelKey): MediaKeyStatus | undefined; + // (undocumented) getKeySystemAccess(keySystemsToAttempt: KeySystems[]): Promise; // (undocumented) getSelectedKeySystemFormats(): KeySystemFormats[]; @@ -3379,6 +3381,8 @@ export class LevelKey implements DecryptData { // (undocumented) pssh: Uint8Array | null; // (undocumented) + static setKeyIdForUri(uri: string, keyId: Uint8Array): void; + // (undocumented) readonly uri: string; } @@ -3992,7 +3996,11 @@ export interface MediaKeySessionContext { // (undocumented) decryptdata: LevelKey; // (undocumented) - keyStatus: MediaKeyStatus; + keyStatus?: MediaKeyStatus; + // (undocumented) + keyStatusTimeouts?: { + [keyId: string]: number; + }; // (undocumented) keySystem: KeySystems; // (undocumented) diff --git a/src/controller/base-stream-controller.ts b/src/controller/base-stream-controller.ts index a26c14380be..f00b94a1a2a 100644 --- a/src/controller/base-stream-controller.ts +++ b/src/controller/base-stream-controller.ts @@ -24,7 +24,11 @@ import { getAesModeFromFullSegmentMethod, isFullSegmentEncryption, } from '../utils/encryption-methods-util'; -import { getRetryDelay, offlineHttpStatus } from '../utils/error-helper'; +import { + getRetryDelay, + isUnusableKeyError, + offlineHttpStatus, +} from '../utils/error-helper'; import { addEventListener, removeEventListener, @@ -727,7 +731,9 @@ export default class BaseStreamController ) { const media = this.media; const error = new Error( - `Encrypted track with no key in ${this.fragInfo(frag)} (media ${media ? 'attached mediaKeys: ' + media.mediaKeys : 'detached'})`, + __USE_EME_DRM__ + ? `Encrypted track with no key in ${this.fragInfo(frag)} (media ${media ? 'attached mediaKeys: ' + media.mediaKeys : 'detached'})` + : 'EME not supported (light build)', ); this.warn(error.message); // Ignore if media is detached or mediaKeys are set @@ -737,7 +743,7 @@ export default class BaseStreamController this.hls.trigger(Events.ERROR, { type: ErrorTypes.KEY_SYSTEM_ERROR, details: ErrorDetails.KEY_SYSTEM_NO_KEYS, - fatal: false, + fatal: !__USE_EME_DRM__, error, frag, }); @@ -1052,6 +1058,7 @@ export default class BaseStreamController this.handleFragLoadAborted(data.frag, data.part); } else if (data.frag && data.type === ErrorTypes.KEY_SYSTEM_ERROR) { data.frag.abortRequests(); + this.resetStartWhenNotLoaded(); this.resetFragmentLoading(data.frag); } else { this.hls.trigger(Events.ERROR, data as ErrorData); @@ -1904,7 +1911,8 @@ export default class BaseStreamController noAlternate && isMediaFragment(frag) && !frag.endList && - live + live && + !isUnusableKeyError(data) ) { this.resetFragmentErrors(filterType); this.treatAsGap(frag); diff --git a/src/controller/buffer-controller.ts b/src/controller/buffer-controller.ts index b13ec5edca5..388e0dc33ed 100755 --- a/src/controller/buffer-controller.ts +++ b/src/controller/buffer-controller.ts @@ -937,6 +937,9 @@ transfer tracks: ${stringify(transferredTracks, (key, value) => (key === 'initSe this.hls.trigger(Events.ERROR, event); }, }; + this.log( + `queuing "${type}" append sn: ${sn}${part ? ' p: ' + part.index : ''} of ${frag.type === PlaylistLevelType.MAIN ? 'level' : 'track'} ${frag.level} cc: ${cc}`, + ); this.append(operation, type, this.isPending(this.tracks[type])); } diff --git a/src/controller/eme-controller.ts b/src/controller/eme-controller.ts index f6999809bc4..a4ea86f8854 100644 --- a/src/controller/eme-controller.ts +++ b/src/controller/eme-controller.ts @@ -7,6 +7,7 @@ import { EventEmitter } from 'eventemitter3'; import { ErrorDetails, ErrorTypes } from '../errors'; import { Events } from '../events'; import { LevelKey } from '../loader/level-key'; +import { arrayValuesMatch } from '../utils/arrays'; import { addEventListener, removeEventListener, @@ -56,9 +57,10 @@ interface KeySystemAccessPromises { export interface MediaKeySessionContext { keySystem: KeySystems; mediaKeys: MediaKeys; - decryptdata: LevelKey; // FIXME: LevelKey has a URI which should be bound to the session, but is dependent one KeyId specifically. Session context should be allowed to adopt multiple level keys. + decryptdata: LevelKey; mediaKeysSession: MediaKeySession; - keyStatus: MediaKeyStatus; // FIXME: MediaKeySession can manage multiple keys with each with its own status + keyStatus?: MediaKeyStatus; + keyStatusTimeouts?: { [keyId: string]: number }; licenseXhr?: XMLHttpRequest; _onmessage?: (this: MediaKeySession, ev: MediaKeyMessageEvent) => any; _onkeystatuseschange?: (this: MediaKeySession, ev: Event) => any; @@ -322,7 +324,7 @@ class EMEController extends Logger implements ComponentAPI { this.log( `Creating key-system session "${keySystem}" keyId: ${arrayToHex( decryptdata.keyId || ([] as number[]), - )}`, + )} keyUri: ${decryptdata.uri}`, ); const mediaKeysSession = mediaKeys.createSession(); @@ -346,7 +348,7 @@ class EMEController extends Logger implements ComponentAPI { const keySessionContext = this.createMediaKeySessionContext( mediaKeySessionContext, ); - const keyId = this.getKeyIdString(decryptdata); + const keyId = getKeyIdString(decryptdata); const scheme = 'cenc'; this.keyIdToKeySessionPromise[keyId] = this.generateRequestWithPreferredKeySession( @@ -362,16 +364,6 @@ class EMEController extends Logger implements ComponentAPI { this.removeSession(mediaKeySessionContext); } - private getKeyIdString(decryptdata: DecryptData | undefined): string | never { - if (!decryptdata) { - throw new Error('Could not read keyId of undefined decryptdata'); - } - if (decryptdata.keyId === null) { - throw new Error('keyId is null'); - } - return arrayToHex(decryptdata.keyId); - } - private updateKeySession( mediaKeySessionContext: MediaKeySessionContext, data: Uint8Array, @@ -450,13 +442,27 @@ class EMEController extends Logger implements ComponentAPI { return this.selectKeySystem(keySystemsToAttempt); } + public getKeyStatus(decryptdata: LevelKey): MediaKeyStatus | undefined { + const { mediaKeySessions } = this; + for (let i = 0; i < mediaKeySessions.length; i++) { + const status = getKeyStatus(decryptdata, mediaKeySessions[i]); + if (status) { + return status; + } + } + return undefined; + } + public loadKey(data: KeyLoadedData): Promise { const decryptdata = data.keyInfo.decryptdata; - const keyId = this.getKeyIdString(decryptdata); + const keyId = getKeyIdString(decryptdata); const badStatus = this.bannedKeyIds[keyId]; - if (badStatus) { - const error = getKeyStatusError(badStatus, decryptdata); + if (badStatus || this.getKeyStatus(decryptdata) === 'internal-error') { + const error = getKeyStatusError( + badStatus || 'internal-error', + decryptdata, + ); this.handleError(error, data.frag); return Promise.reject(error); } @@ -503,6 +509,18 @@ class EMEController extends Logger implements ComponentAPI { return keySessionContextPromise; } + // Re-emit error for playlist key loading + keyContextPromise.catch((error) => { + if (error instanceof EMEKeyError) { + const errorData = { ...error.data }; + if (this.getKeyStatus(decryptdata) === 'internal-error') { + errorData.decryptdata = decryptdata; + } + const clonedError = new EMEKeyError(errorData, error.message); + this.handleError(clonedError, data.frag); + } + }); + return keyContextPromise; } @@ -516,13 +534,20 @@ class EMEController extends Logger implements ComponentAPI { if (!this.hls as any) { return; } - this.error(error.message); + if (error instanceof EMEKeyError) { if (frag) { error.data.frag = frag; } + const levelKey = error.data.decryptdata; + this.error( + `${error.message}${ + levelKey ? ` (${arrayToHex(levelKey.keyId || [])})` : '' + }`, + ); this.hls.trigger(Events.ERROR, error.data); } else { + this.error(error.message); this.hls.trigger(Events.ERROR, { type: ErrorTypes.KEY_SYSTEM_ERROR, details: ErrorDetails.KEY_SYSTEM_NO_KEYS, @@ -535,7 +560,7 @@ class EMEController extends Logger implements ComponentAPI { private getKeySystemForKeyPromise( decryptdata: LevelKey, ): Promise<{ keySystem: KeySystems; mediaKeys: MediaKeys }> { - const keyId = this.getKeyIdString(decryptdata); + const keyId = getKeyIdString(decryptdata); const mediaKeySessionContext = this.keyIdToKeySessionPromise[keyId]; if (!mediaKeySessionContext) { const keySystem = keySystemFormatToKeySystemDomain( @@ -633,7 +658,7 @@ class EMEController extends Logger implements ComponentAPI { } const oldKeyIdHex = arrayToHex(decryptdata.keyId); if ( - keyIdHex === oldKeyIdHex || + arrayValuesMatch(keyId, decryptdata.keyId) || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1 ) { keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex]; @@ -744,9 +769,10 @@ class EMEController extends Logger implements ComponentAPI { return Promise.resolve(context); } - const keyId = this.getKeyIdString(context.decryptdata); + const keyId = getKeyIdString(context.decryptdata); + const keyUri = context.decryptdata.uri; this.log( - `Generating key-session request for "${reason}": ${keyId} (init data type: ${initDataType} length: ${ + `Generating key-session request for "${reason}" keyId: ${keyId} URI: ${keyUri} (init data type: ${initDataType} length: ${ initData.byteLength })`, ); @@ -776,16 +802,48 @@ class EMEController extends Logger implements ComponentAPI { }); } else if (messageType === 'license-release') { if (context.keySystem === KeySystems.FAIRPLAY) { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.updateKeySession(context, strToUtf8array('acknowledged')); - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.removeSession(context); + this.updateKeySession(context, strToUtf8array('acknowledged')) + .then(() => this.removeSession(context)) + .catch((error) => this.handleError(error)); } } else { this.warn(`unhandled media key message type "${messageType}"`); } }); + const handleKeyStatus = ( + keyStatus: MediaKeyStatus, + context: MediaKeySessionContext, + ) => { + context.keyStatus = keyStatus; + let keyError: EMEKeyError | Error | undefined; + if (keyStatus.startsWith('usable')) { + licenseStatus.emit('resolved'); + } else if ( + keyStatus === 'internal-error' || + keyStatus === 'output-restricted' || + keyStatus === 'output-downscaled' + ) { + keyError = getKeyStatusError(keyStatus, context.decryptdata); + } else if (keyStatus === 'expired') { + keyError = new Error(`key expired (keyId: ${keyId})`); + } else if (keyStatus === 'released') { + keyError = new Error(`key released`); + } else if (keyStatus === 'status-pending') { + /* no-op */ + } else { + this.warn( + `unhandled key status change "${keyStatus}" (keyId: ${keyId})`, + ); + } + if (keyError) { + if (licenseStatus.eventNames().length) { + licenseStatus.emit('error', keyError); + } else { + this.handleError(keyError); + } + } + }; const onkeystatuseschange = (context._onkeystatuseschange = ( event: Event, ) => { @@ -794,15 +852,56 @@ class EMEController extends Logger implements ComponentAPI { licenseStatus.emit('error', new Error('invalid state')); return; } - const initialStatus = context.keyStatus; - this.onKeyStatusChange(context); - const status = context.keyStatus; - if (status !== initialStatus) { - licenseStatus.emit('keyStatus', status, context); - if (status === 'expired') { - this.log(`${context.keySystem} expired for key ${keyId}`); - this.renewKeySession(context); - } + + const keyStatuses = this.getKeyStatuses(context); + const keyIds = Object.keys(keyStatuses); + + // exit if all keys are status-pending + if (!keyIds.some((id) => keyStatuses[id] !== 'status-pending')) { + return; + } + + // renew when a key status for a levelKey comes back expired + if (keyStatuses[keyId] === 'expired') { + // renew when a key status comes back expired + this.log( + `Expired key ${stringify(keyStatuses)} in key-session "${context.mediaKeysSession.sessionId}"`, + ); + this.renewKeySession(context); + return; + } + + let keyStatus = keyStatuses[keyId] as MediaKeyStatus | undefined; + if (keyStatus) { + // handle status of current key + handleKeyStatus(keyStatus, context); + } else { + // Timeout key-status + const timeout = 0; + context.keyStatusTimeouts ||= {}; + context.keyStatusTimeouts[keyId] ||= self.setTimeout(() => { + if ((!context.mediaKeysSession as any) || !this.mediaKeys) { + return; + } + + // Find key status in another session if missing (PlayReady #7519 no key-status "single-key" setup with shared key) + const sessionKeyStatus = this.getKeyStatus(context.decryptdata); + if (sessionKeyStatus && sessionKeyStatus !== 'status-pending') { + this.log( + `No status for keyId ${keyId} in key-session "${context.mediaKeysSession.sessionId}". Using session key-status ${sessionKeyStatus} from other session.`, + ); + return handleKeyStatus(sessionKeyStatus, context); + } + + // Timeout key with internal-error + this.log( + `key status for ${keyId} in key-session "${context.mediaKeysSession.sessionId}" timed out after ${timeout}ms`, + ); + keyStatus = 'internal-error'; + handleKeyStatus(keyStatus, context); + }, timeout); + + this.log(`No status for keyId ${keyId} (${stringify(keyStatuses)}).`); } }); @@ -816,33 +915,7 @@ class EMEController extends Logger implements ComponentAPI { const keyUsablePromise = new Promise( (resolve: (value?: void) => void, reject) => { licenseStatus.on('error', reject); - - licenseStatus.on( - 'keyStatus', - ( - keyStatus: MediaKeyStatus, - { decryptdata }: MediaKeySessionContext, - ) => { - if (keyStatus.startsWith('usable')) { - resolve(); - } else if ( - keyStatus === 'internal-error' || - keyStatus === 'output-restricted' - ) { - reject(getKeyStatusError(keyStatus, decryptdata)); - } else if (keyStatus === 'expired') { - reject( - new Error( - `key expired while generating request (keyId: ${keyId})`, - ), - ); - } else { - this.warn( - `unhandled key status change "${keyStatus}" (keyId: ${keyId})`, - ); - } - }, - ); + licenseStatus.on('resolved', resolve); }, ); @@ -850,7 +923,7 @@ class EMEController extends Logger implements ComponentAPI { .generateRequest(initDataType, initData) .then(() => { this.log( - `Request generated for key-session "${context.mediaKeysSession.sessionId}" keyId: ${keyId}`, + `Request generated for key-session "${context.mediaKeysSession.sessionId}" keyId: ${keyId} URI: ${keyUri}`, ); }) .catch((error) => { @@ -868,9 +941,9 @@ class EMEController extends Logger implements ComponentAPI { .then(() => keyUsablePromise) .catch((error) => { licenseStatus.removeAllListeners(); - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.removeSession(context); - throw error; + return this.removeSession(context).then(() => { + throw error; + }); }) .then(() => { licenseStatus.removeAllListeners(); @@ -878,14 +951,10 @@ class EMEController extends Logger implements ComponentAPI { }); } - private onKeyStatusChange(mediaKeySessionContext: MediaKeySessionContext) { - const sessionLevelKeyId = arrayToHex( - new Uint8Array(mediaKeySessionContext.decryptdata.keyId || []), - ); - - let hasMatchedKey = false; - const keyStatuses: { status: MediaKeyStatus; keyId: string }[] = []; - + private getKeyStatuses(mediaKeySessionContext: MediaKeySessionContext): { + [keyId: string]: MediaKeyStatus; + } { + const keyStatuses: { [keyId: string]: MediaKeyStatus } = {}; mediaKeySessionContext.mediaKeysSession.keyStatuses.forEach( (status: MediaKeyStatus, keyId: BufferSource) => { // keyStatuses.forEach is not standard API so the callback value looks weird on xboxone @@ -895,79 +964,28 @@ class EMEController extends Logger implements ComponentAPI { keyId = status; status = temp; } - - const keyIdArray: Uint8Array = + const keyIdArray = 'buffer' in keyId ? new Uint8Array(keyId.buffer, keyId.byteOffset, keyId.byteLength) : new Uint8Array(keyId); - - // Handle PlayReady little-endian key ID conversion for status comparison only - // Don't modify the original key ID from playlist parsing if ( mediaKeySessionContext.keySystem === KeySystems.PLAYREADY && keyIdArray.length === 16 ) { changeEndianness(keyIdArray); } - - const keyIdWithStatusChange = arrayToHex( - keyIdArray as Uint8Array, - ); - - // Store all key statuses for processing - keyStatuses.push({ status, keyId: keyIdWithStatusChange }); - - // Error immediately when encountering a key ID with this status again + const keyIdWithStatusChange = arrayToHex(keyIdArray); + // Add to banned keys to prevent playlist usage and license requests if (status === 'internal-error') { this.bannedKeyIds[keyIdWithStatusChange] = status; } - - // Check if this key matches the session-level key ID - const matched = keyIdWithStatusChange === sessionLevelKeyId; - if (matched) { - hasMatchedKey = true; - mediaKeySessionContext.keyStatus = status; - this.log( - `matched key status change "${status}" for keyStatuses keyId: ${keyIdWithStatusChange} session keyId: ${sessionLevelKeyId} uri: ${mediaKeySessionContext.decryptdata.uri}`, - ); - } else { - this.log( - `unmatched key status change "${status}" for keyStatuses keyId: ${keyIdWithStatusChange} session keyId: ${sessionLevelKeyId} uri: ${mediaKeySessionContext.decryptdata.uri}`, - ); - } - }, - ); - - // Handle case where no keys matched but all have the same status - // This can happen with PlayReady when key IDs don't align properly - if (!hasMatchedKey && keyStatuses.length > 0) { - const firstStatus = keyStatuses[0].status; - const allSameStatus = !keyStatuses.some( - ({ status }) => status !== firstStatus, - ); - - if ( - allSameStatus && - (firstStatus === 'usable' || firstStatus.startsWith('usable')) - ) { - this.log( - `No key matched session keyId ${sessionLevelKeyId}, but all keys have usable status "${firstStatus}". Treating as usable.`, - ); - mediaKeySessionContext.keyStatus = firstStatus; - } else if ( - allSameStatus && - (firstStatus === 'internal-error' || firstStatus === 'expired') - ) { - this.log( - `No key matched session keyId ${sessionLevelKeyId}, but all keys have error status "${firstStatus}". Applying to session.`, - ); - mediaKeySessionContext.keyStatus = firstStatus; - } else { this.log( - `No key matched session keyId ${sessionLevelKeyId}. Key statuses: ${keyStatuses.map(({ keyId, status }) => `${keyId}:${status}`).join(', ')}`, + `key status change "${status}" for keyStatuses keyId: ${keyIdWithStatusChange} key-session "${mediaKeySessionContext.mediaKeysSession.sessionId}"`, ); - } - } + keyStatuses[keyIdWithStatusChange] = status; + }, + ); + return keyStatuses; } private fetchServerCertificate( @@ -1372,7 +1390,7 @@ class EMEController extends Logger implements ComponentAPI { error: new Error(`Could not clear media keys: ${error}`), }); }, - ), + ) || Promise.resolve(), ), ) .catch((error) => { @@ -1428,7 +1446,7 @@ class EMEController extends Logger implements ComponentAPI { private removeSession( mediaKeySessionContext: MediaKeySessionContext, - ): Promise | void { + ): Promise { const { mediaKeysSession, licenseXhr, decryptdata } = mediaKeySessionContext; if (mediaKeysSession as MediaKeySession | undefined) { @@ -1461,6 +1479,12 @@ class EMEController extends Logger implements ComponentAPI { if (index > -1) { this.mediaKeySessions.splice(index, 1); } + const { keyStatusTimeouts } = mediaKeySessionContext; + if (keyStatusTimeouts) { + Object.keys(keyStatusTimeouts).forEach((keyId) => + self.clearTimeout(keyStatusTimeouts[keyId]), + ); + } const { drmSystemOptions } = this.config; const removePromise = isPersistentSessionType(drmSystemOptions) ? new Promise((resolve, reject) => { @@ -1496,10 +1520,37 @@ class EMEController extends Logger implements ComponentAPI { }); }); } + return Promise.resolve(); + } +} + +function getKeyIdString(decryptdata: DecryptData | undefined): string | never { + if (!decryptdata) { + throw new Error('Could not read keyId of undefined decryptdata'); + } + if (decryptdata.keyId === null) { + throw new Error('keyId is null'); + } + return arrayToHex(decryptdata.keyId); +} + +function getKeyStatus( + decryptdata: LevelKey, + keyContext: MediaKeySessionContext, +): MediaKeyStatus | undefined { + if ( + decryptdata.keyId && + keyContext.mediaKeysSession.keyStatuses.has(decryptdata.keyId) + ) { + return keyContext.mediaKeysSession.keyStatuses.get(decryptdata.keyId); + } + if (decryptdata.matches(keyContext.decryptdata)) { + return keyContext.keyStatus; } + return undefined; } -class EMEKeyError extends Error { +export class EMEKeyError extends Error { public readonly data: ErrorData; constructor( data: Omit & { error?: Error }, diff --git a/src/controller/error-controller.ts b/src/controller/error-controller.ts index 171a3fe1ad6..2650db155eb 100644 --- a/src/controller/error-controller.ts +++ b/src/controller/error-controller.ts @@ -6,7 +6,9 @@ import { PlaylistContextType, PlaylistLevelType } from '../types/loader'; import { getCodecsForMimeType } from '../utils/codecs'; import { getRetryConfig, + isKeyError, isTimeoutError, + isUnusableKeyError, shouldRetry, } from '../utils/error-helper'; import { arrayToHex } from '../utils/hex'; @@ -302,7 +304,7 @@ export default class ErrorController const level = hls.levels[variantLevelIndex]; const { fragLoadPolicy, keyLoadPolicy } = hls.config; const retryConfig = getRetryConfig( - data.details.startsWith('key') ? keyLoadPolicy : fragLoadPolicy, + isKeyError(data) ? keyLoadPolicy : fragLoadPolicy, data, ); const fragmentErrors = hls.levels.reduce( @@ -314,19 +316,21 @@ export default class ErrorController if (data.details !== ErrorDetails.FRAG_GAP) { level.fragmentError++; } - const retry = shouldRetry( - retryConfig, - fragmentErrors, - isTimeoutError(data), - data.response, - ); - if (retry) { - return { - action: NetworkErrorAction.RetryRequest, - flags: ErrorActionFlags.None, + if (!isUnusableKeyError(data)) { + const retry = shouldRetry( retryConfig, - retryCount: fragmentErrors, - }; + fragmentErrors, + isTimeoutError(data), + data.response, + ); + if (retry) { + return { + action: NetworkErrorAction.RetryRequest, + flags: ErrorActionFlags.None, + retryConfig, + retryCount: fragmentErrors, + }; + } } } // Reach max retry count, or Missing level reference @@ -509,7 +513,9 @@ export default class ErrorController 'HDCP-LEVEL' ]; errorAction.hdcpLevel = restrictedHdcpLevel; - if (restrictedHdcpLevel) { + if (restrictedHdcpLevel === 'NONE') { + this.warn(`HDCP policy resticted output with HDCP-LEVEL=NONE`); + } else if (restrictedHdcpLevel) { hls.maxHdcpLevel = HdcpLevels[HdcpLevels.indexOf(restrictedHdcpLevel) - 1]; errorAction.resolved = true; @@ -526,7 +532,8 @@ export default class ErrorController if (levelKey) { // Penalize all levels with key const levels = this.hls.levels; - for (let i = levels.length; i--; ) { + const levelCountWithError = levels.length; + for (let i = levelCountWithError; i--; ) { if (this.variantHasKey(levels[i], levelKey)) { this.log( `Banned key found in level ${i} (${levels[i].bitrate}bps) or audio group "${levels[i].audioGroups?.join(',')}" (${data.frag?.type} fragment) ${arrayToHex(levelKey.keyId || [])}`, @@ -537,8 +544,15 @@ export default class ErrorController this.hls.removeLevel(i); } } - if (levels.length) { + const frag = data.frag; + if (this.hls.levels.length < levelCountWithError) { errorAction.resolved = true; + } else if (frag && frag.type !== PlaylistLevelType.MAIN) { + // Ignore key error for audio track with unmatched key (main session error) + const fragLevelKey = frag.decryptdata; + if (fragLevelKey && !levelKey.matches(fragLevelKey)) { + errorAction.resolved = true; + } } } break; diff --git a/src/loader/key-loader.ts b/src/loader/key-loader.ts index 80f4e72b8c5..27c85dc5daa 100644 --- a/src/loader/key-loader.ts +++ b/src/loader/key-loader.ts @@ -7,10 +7,14 @@ import { getKeySystemsForConfig, keySystemFormatToKeySystemDomain, } from '../utils/mediakeys-helper'; +import { KeySystemFormats } from '../utils/mediakeys-helper'; import type { LevelKey } from './level-key'; import type { HlsConfig } from '../config'; import type EMEController from '../controller/eme-controller'; -import type { MediaKeySessionContext } from '../controller/eme-controller'; +import type { + EMEKeyError, + MediaKeySessionContext, +} from '../controller/eme-controller'; import type { ComponentAPI } from '../types/component-api'; import type { KeyLoadedData } from '../types/events'; import type { @@ -23,7 +27,6 @@ import type { PlaylistLevelType, } from '../types/loader'; import type { ILogger } from '../utils/logger'; -import type { KeySystemFormats } from '../utils/mediakeys-helper'; export interface KeyLoaderInfo { decryptdata: LevelKey; @@ -101,6 +104,7 @@ export default class KeyLoader extends Logger implements ComponentAPI { startFragRequested: boolean, ): null | Promise { if ( + __USE_EME_DRM__ && this.emeController && this.config.emeEnabled && !this.emeController.getSelectedKeySystemFormats().length @@ -165,7 +169,7 @@ export default class KeyLoader extends Logger implements ComponentAPI { frag: Fragment, keySystemFormat?: KeySystemFormats, ): Promise { - if (keySystemFormat) { + if (__USE_EME_DRM__ && keySystemFormat) { frag.setKeyFormat(keySystemFormat); } const decryptdata = frag.decryptdata; @@ -173,7 +177,7 @@ export default class KeyLoader extends Logger implements ComponentAPI { const error = new Error( keySystemFormat ? `Expected frag.decryptdata to be defined after setting format ${keySystemFormat}` - : 'Missing decryption data on fragment in onKeyLoading', + : `Missing decryption data on fragment in onKeyLoading (emeEnabled with controller: ${this.emeController && this.config.emeEnabled})`, ); return Promise.reject( this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, error), @@ -197,8 +201,8 @@ export default class KeyLoader extends Logger implements ComponentAPI { return Promise.resolve({ frag, keyInfo }); } // Return key load promise once it has a mediakey session with an usable key status - if (keyInfo?.keyLoadPromise) { - const keyStatus = keyInfo.mediaKeySessionContext?.keyStatus; + if (this.emeController && keyInfo?.keyLoadPromise) { + const keyStatus = this.emeController.getKeyStatus(keyInfo.decryptdata); switch (keyStatus) { case 'usable': case 'usable-in-future': @@ -215,7 +219,7 @@ export default class KeyLoader extends Logger implements ComponentAPI { // Load the key or return the loading promise this.log( - `Loading key ${arrayToHex(decryptdata.keyId || [])} from ${frag.type} ${frag.level}`, + `${this.keyIdToKeyInfo[id] ? 'Rel' : 'L'}oading${decryptdata.keyId ? ' keyId: ' + arrayToHex(decryptdata.keyId) : ''} URI: ${decryptdata.uri} from ${frag.type} ${frag.level}`, ); keyInfo = this.keyIdToKeyInfo[id] = { @@ -261,10 +265,10 @@ export default class KeyLoader extends Logger implements ComponentAPI { keyInfo.mediaKeySessionContext = keySessionContext; return keyLoadedData; }, - )).catch((error) => { + )).catch((error: EMEKeyError | Error) => { // Remove promise for license renewal or retry keyInfo.keyLoadPromise = null; - if (error.data) { + if ('data' in error) { error.data.frag = frag; } throw error; @@ -306,8 +310,8 @@ export default class KeyLoader extends Logger implements ComponentAPI { context: KeyLoaderContext, networkDetails: any, ) => { - const { frag, keyInfo, url: uri } = context; - const id = getKeyId(keyInfo.decryptdata) || uri; + const { frag, keyInfo } = context; + const id = getKeyId(keyInfo.decryptdata); if (!frag.decryptdata || keyInfo !== this.keyIdToKeyInfo[id]) { return reject( this.createKeyLoadError( @@ -402,9 +406,11 @@ export default class KeyLoader extends Logger implements ComponentAPI { } function getKeyId(decryptdata: LevelKey) { - const keyId = decryptdata.keyId; - if (keyId) { - return arrayToHex(keyId); + if (__USE_EME_DRM__ && decryptdata.keyFormat !== KeySystemFormats.FAIRPLAY) { + const keyId = decryptdata.keyId; + if (keyId) { + return arrayToHex(keyId); + } } return decryptdata.uri; } diff --git a/src/loader/level-key.ts b/src/loader/level-key.ts index aaccbcd4dfe..2c6a8d076cb 100644 --- a/src/loader/level-key.ts +++ b/src/loader/level-key.ts @@ -37,6 +37,10 @@ export class LevelKey implements DecryptData { keyUriToKeyIdMap = {}; } + static setKeyIdForUri(uri: string, keyId: Uint8Array) { + keyUriToKeyIdMap[uri] = keyId; + } + constructor( method: string, uri: string, @@ -101,19 +105,22 @@ export class LevelKey implements DecryptData { return null; } - if (isFullSegmentEncryption(this.method) && this.uri && !this.iv) { - if (typeof sn !== 'number') { - // We are fetching decryption data for a initialization segment - // If the segment was encrypted with AES-128/256 - // It must have an IV defined. We cannot substitute the Segment Number in. - logger.warn( - `missing IV for initialization segment with method="${this.method}" - compliance issue`, - ); - - // Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation. - sn = 0; + if (isFullSegmentEncryption(this.method)) { + let iv = this.iv; + if (!iv) { + if (typeof sn !== 'number') { + // We are fetching decryption data for a initialization segment + // If the segment was encrypted with AES-128/256 + // It must have an IV defined. We cannot substitute the Segment Number in. + logger.warn( + `missing IV for initialization segment with method="${this.method}" - compliance issue`, + ); + + // Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation. + sn = 0; + } + iv = createInitializationVector(sn); } - const iv = createInitializationVector(sn); const decryptdata = new LevelKey( this.method, this.uri, @@ -142,11 +149,11 @@ export class LevelKey implements DecryptData { this.pssh = keyBytes; // In case of Widevine, if KEYID is not in the playlist, assume only two fields in the pssh KEY tag URI. if (!this.keyId) { - const [psshData] = parseMultiPssh(keyBytes.buffer); - this.keyId = - psshData && 'kids' in psshData && psshData.kids?.[0] - ? psshData.kids[0] - : null; + const results = parseMultiPssh(keyBytes.buffer); + if (results.length) { + const psshData = results[0]; + this.keyId = psshData.kids?.length ? psshData.kids[0] : null; + } } if (!this.keyId) { const offset = keyBytes.length - 22; @@ -189,7 +196,7 @@ export class LevelKey implements DecryptData { keyId = new Uint8Array(16); const dv = new DataView(keyId.buffer, 12, 4); // Just set the last 4 bytes dv.setUint32(0, val); - keyUriToKeyIdMap[this.uri] = keyId; + LevelKey.setKeyIdForUri(this.uri, keyId); } this.keyId = keyId; } diff --git a/src/loader/m3u8-parser.ts b/src/loader/m3u8-parser.ts index a7765fdfc76..66c73d2744d 100644 --- a/src/loader/m3u8-parser.ts +++ b/src/loader/m3u8-parser.ts @@ -584,7 +584,9 @@ export default class M3U8Parser { levelkeys[levelKey.keyFormat] = levelKey; } } else { - logger.warn(`[Keys] Ignoring invalid EXT-X-KEY tag: "${value1}"`); + logger.warn( + `[Keys] Ignoring unsupported EXT-X-KEY tag: "${value1}"${__USE_EME_DRM__ ? '' : ' (light build)'}`, + ); } break; } diff --git a/src/utils/error-helper.ts b/src/utils/error-helper.ts index b42ac693f83..f58553a7344 100644 --- a/src/utils/error-helper.ts +++ b/src/utils/error-helper.ts @@ -14,6 +14,14 @@ export function isTimeoutError(error: ErrorData): boolean { return false; } +export function isKeyError(error: ErrorData): boolean { + return error.details.startsWith('key'); +} + +export function isUnusableKeyError(error: ErrorData): boolean { + return isKeyError(error) && !!error.frag && !error.frag.decryptdata; +} + export function getRetryConfig( loadPolicy: LoadPolicy, error: ErrorData, diff --git a/src/utils/mp4-tools.ts b/src/utils/mp4-tools.ts index 86d536f7047..be093141986 100644 --- a/src/utils/mp4-tools.ts +++ b/src/utils/mp4-tools.ts @@ -1361,6 +1361,7 @@ export type PsshData = { export type PsshInvalidResult = { systemId?: undefined; + kids?: undefined; offset: number; size: number; }; diff --git a/tests/unit/controller/eme-controller.ts b/tests/unit/controller/eme-controller.ts index 52aa6688a7f..2acf1c2db14 100644 --- a/tests/unit/controller/eme-controller.ts +++ b/tests/unit/controller/eme-controller.ts @@ -5,7 +5,11 @@ import sinonChai from 'sinon-chai'; import EMEController from '../../../src/controller/eme-controller'; import { ErrorDetails } from '../../../src/errors'; import { Events } from '../../../src/events'; -import { KeySystemFormats } from '../../../src/utils/mediakeys-helper'; +import { LevelKey } from '../../../src/loader/level-key'; +import { + KeySystemFormats, + KeySystems, +} from '../../../src/utils/mediakeys-helper'; import HlsMock from '../../mocks/hls.mock'; import type { MediaKeySessionContext } from '../../../src/controller/eme-controller'; import type { MediaAttachedData } from '../../../src/types/events'; @@ -15,20 +19,22 @@ const expect = chai.expect; type EMEControllerTestable = Omit< EMEController, - 'hls' | 'keyIdToKeySessionPromise' | 'mediaKeySessions' + 'hls' | 'keyUriToSessionPromise' | 'mediaKeySessions' | 'keyUriToLevelKeys' > & { hls: HlsMock; + mediaKeySessions: MediaKeySessionContext[]; keyIdToKeySessionPromise: { - [keyId: string]: Promise; + [keyId: string]: Promise | undefined; }; - mediaKeySessions: MediaKeySessionContext[]; onMediaAttached: ( event: Events.MEDIA_ATTACHED, data: MediaAttachedData, ) => void; onMediaDetached: () => void; media: HTMLMediaElement | null; - onKeyStatusChange: (mediaKeySessionContext: MediaKeySessionContext) => void; + getKeyStatuses: (mediaKeySessionContext: MediaKeySessionContext) => { + [keyId: string]: MediaKeyStatus; + }; }; class MediaMock extends EventEmitter { @@ -45,15 +51,64 @@ class MediaMock extends EventEmitter { } } -class MediaKeySessionMock extends EventEmitter { +class MediaKeysMock implements MediaKeys { + createSession(/* sessionType?: MediaKeySessionType */) { + return new MediaKeySessionMock(); + } + getStatusForPolicy(/* policy?: MediaKeysPolicy */) { + // "output-restricted" | "released" | "status-pending" | "usable" | "usable-in-future" + return Promise.resolve('usable' as MediaKeyStatus); + } + setServerCertificate(/* serverCertificate: BufferSource */) { + return Promise.resolve(true); + } +} + +class MediaKeySessionMock extends EventEmitter implements MediaKeySession { addEventListener: any; removeEventListener: any; - keyStatuses: Map; + private _resolveClose: (reason: MediaKeySessionClosedReason) => void = + () => {}; + protected _keyStatuses: Map; + readonly closed: Promise; + readonly keyStatuses: MediaKeyStatusMap; + readonly expiration: number; + readonly onkeystatuseschange = null; // use add/removeEventListener + readonly onmessage = null; // use add/removeEventListener + readonly sessionId: string; + constructor() { super(); - this.keyStatuses = new Map(); this.addEventListener = this.addListener.bind(this); this.removeEventListener = this.removeListener.bind(this); + this.closed = new Promise((resolve) => { + this._resolveClose = resolve; + }); + this.expiration = NaN; + this.sessionId = ''; + + const keyStatuses = (this._keyStatuses = new Map()); + this.keyStatuses = { + get size() { + return keyStatuses.size; + }, + get(keyId) { + return keyStatuses.get(keyId); + }, + has(keyId) { + return keyStatuses.has(keyId); + }, + forEach(callbackfn, thisArg?) { + return keyStatuses.forEach(callbackfn, thisArg); + }, + }; + } + dispatchEvent() { + return true; + } + close() { + this._resolveClose('release-acknoledged' as MediaKeySessionClosedReason); + return this.closed.then(() => {}); } generateRequest() { return Promise.resolve().then(() => { @@ -61,10 +116,13 @@ class MediaKeySessionMock extends EventEmitter { messageType: 'license-request', message: new Uint8Array(0), }); - this.keyStatuses.set(new Uint8Array(16), 'usable'); + this._keyStatuses.set(new Uint8Array(16), 'usable'); this.emit('keystatuseschange', {}); }); } + load(sessionId: string) { + return Promise.reject(new Error('not supported')); + } remove() { return Promise.resolve(); } @@ -94,6 +152,16 @@ const setupEach = function (config) { sinonFakeXMLHttpRequestStatic = sinon.useFakeXMLHttpRequest(); }; +const getParsedLevelKey = ( + uri: string = 'data://key-uri', + format: string = 'com.apple.streamingkeydelivery', +) => { + const levelKey = new LevelKey('SAMPLE-AES', uri, format); + levelKey.keyId = new Uint8Array(16); + levelKey.pssh = new Uint8Array(16); + return levelKey; +}; + describe('EMEController', function () { beforeEach(function () { setupEach({}); @@ -104,16 +172,12 @@ describe('EMEController', function () { }); it('should request keysystem access based on key format when `emeEnabled` is true', function () { + const mediaKeys = new MediaKeysMock(); const reqMediaKsAccessSpy = sinon.spy(function () { return Promise.resolve({ // Media-keys mock keySystem: 'com.apple.fps', - createMediaKeys: sinon.spy(() => - Promise.resolve({ - setServerCertificate: () => Promise.resolve(), - createSession: () => new MediaKeySessionMock(), - }), - ), + createMediaKeys: sinon.spy(() => Promise.resolve(mediaKeys)), }); }); @@ -143,19 +207,16 @@ describe('EMEController', function () { expect(media.setMediaKeys).callCount(0); expect(reqMediaKsAccessSpy).callCount(0); + const levelKey = getParsedLevelKey(); const emePromise = emeController.loadKey({ - frag: {}, + frag: {} as any, keyInfo: { - decryptdata: { - encrypted: true, - method: 'SAMPLE-AES', - keyFormat: 'com.apple.streamingkeydelivery', - uri: 'data://key-uri', - keyId: new Uint8Array(16), - pssh: new Uint8Array(16), - }, + decryptdata: levelKey, + keyLoadPromise: null, + loader: null, + mediaKeySessionContext: null, }, - } as any); + }); expect(emePromise).to.be.a('Promise'); return emePromise.finally(() => { @@ -317,7 +378,7 @@ describe('EMEController', function () { class MediaKeySessionMock2 extends MediaKeySessionMock { constructor() { super(); - this.keyStatuses.set(new Uint8Array(16), 'usable'); + this._keyStatuses.set(new Uint8Array(16), 'usable'); } } @@ -331,24 +392,21 @@ describe('EMEController', function () { }, }); + const levelKey = getParsedLevelKey(); const keySession = new MediaKeySessionMock2(); - const mockMediaKeySessionContext = { + const mockMediaKeySessionContext: MediaKeySessionContext = { + decryptdata: levelKey, + keySystem: KeySystems.FAIRPLAY, + mediaKeys: new MediaKeysMock(), mediaKeysSession: keySession, - decryptdata: { - encrypted: true, - method: 'SAMPLE-AES', - keyFormat: 'com.apple.streamingkeydelivery', - uri: 'data://key-uri', - keyId: new Uint8Array(16), - pssh: new Uint8Array(16), - }, - keyStatus: 'status-pending', }; - emeController.onKeyStatusChange( - mockMediaKeySessionContext as unknown as MediaKeySessionContext, + const keyStatuses = emeController.getKeyStatuses( + mockMediaKeySessionContext, ); - expect(mockMediaKeySessionContext.keyStatus).to.be.equal('usable'); + expect(keyStatuses) + .to.have.property('00000000000000000000000000000000') + .which.equals('usable'); }); it('should fetch the server certificate and set it into the session', function () { @@ -411,7 +469,7 @@ describe('EMEController', function () { ).to.be.a('Promise'); return emeController.keyIdToKeySessionPromise[ '00000000000000000000000000000000' - ].finally(() => { + ]!.finally(() => { expect(mediaKeysSetServerCertificateSpy).to.have.been.calledOnce; expect(mediaKeysSetServerCertificateSpy).to.have.been.calledWith( sinon.match({ byteLength: 6 }), @@ -492,19 +550,17 @@ describe('EMEController', function () { ).to.be.a('Promise'); return emeController.keyIdToKeySessionPromise[ '00000000000000000000000000000000' - ] - .catch(() => {}) - .finally(() => { - expect(mediaKeysSetServerCertificateSpy).to.have.been.calledOnce; - expect((mediaKeysSetServerCertificateSpy.args[0] as any)[0]).to.equal( - xhrInstance.response, - ); + ]!.catch(() => {}).finally(() => { + expect(mediaKeysSetServerCertificateSpy).to.have.been.calledOnce; + expect((mediaKeysSetServerCertificateSpy.args[0] as any)[0]).to.equal( + xhrInstance.response, + ); - expect(emeController.hls.trigger).to.have.been.calledOnce; - expect(emeController.hls.trigger.args[0][1].details).to.equal( - ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_UPDATE_FAILED, - ); - }); + expect(emeController.hls.trigger).to.have.been.calledOnce; + expect(emeController.hls.trigger.args[0][1].details).to.equal( + ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_UPDATE_FAILED, + ); + }); }); it('should fetch the server certificate and trigger request failed error', function () { @@ -572,14 +628,12 @@ describe('EMEController', function () { ).to.be.a('Promise'); return emeController.keyIdToKeySessionPromise[ '00000000000000000000000000000000' - ] - .catch(() => {}) - .finally(() => { - expect(emeController.hls.trigger).to.have.been.calledOnce; - expect(emeController.hls.trigger.args[0][1].details).to.equal( - ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED, - ); - }); + ]!.catch(() => {}).finally(() => { + expect(emeController.hls.trigger).to.have.been.calledOnce; + expect(emeController.hls.trigger.args[0][1].details).to.equal( + ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED, + ); + }); }); it('should remove media property when media is detached', function () { @@ -602,8 +656,6 @@ describe('EMEController', function () { ), }); }); - const keySessionRemoveSpy = sinon.spy(() => Promise.resolve()); - const keySessionCloseSpy = sinon.spy(() => Promise.resolve()); setupEach({ emeEnabled: true, @@ -613,14 +665,19 @@ describe('EMEController', function () { emeController.onMediaAttached(Events.MEDIA_ATTACHED, { media: media as any as HTMLMediaElement, }); - emeController.mediaKeySessions = [ - { - mediaKeysSession: { - remove: keySessionRemoveSpy, - close: keySessionCloseSpy, - }, - } as any, - ]; + + const levelKey = getParsedLevelKey(); + const keySession = new MediaKeySessionMock(); + const mockMediaKeySessionContext: MediaKeySessionContext = { + decryptdata: levelKey, + keySystem: KeySystems.FAIRPLAY, + mediaKeys: new MediaKeysMock(), + mediaKeysSession: keySession, + }; + sinon.stub(keySession, 'remove'); + sinon.stub(keySession, 'close'); + + emeController.mediaKeySessions = [mockMediaKeySessionContext]; emeController.destroy(); expect(emeController.media).to.equal(null); @@ -646,8 +703,6 @@ describe('EMEController', function () { ), }); }); - const keySessionRemoveSpy = sinon.spy(() => Promise.resolve()); - const keySessionCloseSpy = sinon.spy(() => Promise.resolve()); setupEach({ emeEnabled: true, @@ -657,14 +712,19 @@ describe('EMEController', function () { emeController.onMediaAttached(Events.MEDIA_ATTACHED, { media: media as any as HTMLMediaElement, }); - emeController.mediaKeySessions = [ - { - mediaKeysSession: { - remove: keySessionRemoveSpy, - close: keySessionCloseSpy, - }, - } as any, - ]; + + const levelKey = getParsedLevelKey(); + const keySession = new MediaKeySessionMock(); + const mockMediaKeySessionContext: MediaKeySessionContext = { + decryptdata: levelKey, + keySystem: KeySystems.FAIRPLAY, + mediaKeys: new MediaKeysMock(), + mediaKeysSession: keySession, + }; + sinon.stub(keySession, 'remove'); + const keySessionCloseSpy = sinon.stub(keySession, 'close'); + + emeController.mediaKeySessions = [mockMediaKeySessionContext]; emeController.destroy(); expect(EMEController.CDMCleanupPromise).to.be.a('Promise'); @@ -698,8 +758,6 @@ describe('EMEController', function () { ), }); }); - const keySessionRemoveSpy = sinon.spy(() => Promise.resolve()); - const keySessionCloseSpy = sinon.spy(() => Promise.resolve()); setupEach({ emeEnabled: true, @@ -712,14 +770,19 @@ describe('EMEController', function () { emeController.onMediaAttached(Events.MEDIA_ATTACHED, { media: media as any as HTMLMediaElement, }); - emeController.mediaKeySessions = [ - { - mediaKeysSession: { - remove: keySessionRemoveSpy, - close: keySessionCloseSpy, - }, - } as any, - ]; + + const levelKey = getParsedLevelKey(); + const keySession = new MediaKeySessionMock(); + const mockMediaKeySessionContext: MediaKeySessionContext = { + decryptdata: levelKey, + keySystem: KeySystems.FAIRPLAY, + mediaKeys: new MediaKeysMock(), + mediaKeysSession: keySession, + }; + sinon.stub(keySession, 'remove'); + const keySessionCloseSpy = sinon.stub(keySession, 'close'); + + emeController.mediaKeySessions = [mockMediaKeySessionContext]; emeController.destroy(); expect(EMEController.CDMCleanupPromise).to.be.a('Promise'); From 293c8dac4d505f699145a29e513535a0792c4bea Mon Sep 17 00:00:00 2001 From: Quentin Mazars-Simon Date: Sat, 6 Sep 2025 03:05:28 +0100 Subject: [PATCH 41/64] Add an `hlsjsConfig` query param to populate the demo editor (#7473) * Add an `hlsjsConfig` query param to populate the demo editor * Do not load config from local storage if we loaded them from the URL --- demo/main.js | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/demo/main.js b/demo/main.js index 16c48875493..4ae20f18d6a 100644 --- a/demo/main.js +++ b/demo/main.js @@ -19,11 +19,14 @@ const testStreams = require('../tests/test-streams'); const defaultTestStreamUrl = testStreams[Object.keys(testStreams)[0]].url; const sourceURL = decodeURIComponent(getURLParam('src', defaultTestStreamUrl)); -let demoConfig = getURLParam('demoConfig', null); -if (demoConfig) { - demoConfig = JSON.parse(atob(demoConfig)); -} else { - demoConfig = {}; +let demoConfig = {}; +const demoConfigParam = getURLParam('demoConfig', null); +if (demoConfigParam) { + try { + demoConfig = JSON.parse(atob(demoConfigParam)); + } catch (error) { + console.warn('Failed to parse demoConfig:', error); + } } const hlsjsDefaults = { @@ -33,6 +36,18 @@ const hlsjsDefaults = { backBufferLength: 60 * 1.5, }; +const hlsjsConfigParam = getURLParam('hlsjsConfig', null); +let hlsjsConfig = hlsjsDefaults; +let hlsjsConfigLoadedFromUrl = false; +if (hlsjsConfigParam) { + try { + hlsjsConfig = JSON.parse(atob(hlsjsConfigParam)); + hlsjsConfigLoadedFromUrl = true; + } catch (error) { + console.warn('Failed to parse hlsjsConfig:', error); + } +} + let enableStreaming = getDemoConfigPropOrDefault('enableStreaming', true); let autoRecoverError = getDemoConfigPropOrDefault('autoRecoverError', true); let levelCapping = getDemoConfigPropOrDefault('levelCapping', -1); @@ -1473,12 +1488,17 @@ function onDemoConfigChanged(firstLoad) { persistEditorValue(); } + updatePermalink(firstLoad); +} + +function updatePermalink(firstLoad) { const serializedDemoConfig = btoa(JSON.stringify(demoConfig)); + const serializedHlsjsConfig = btoa(JSON.stringify(hlsjsConfig)); const baseURL = document.URL.split('?')[0]; const streamURL = $('#streamURL').val(); const permalinkURL = `${baseURL}?src=${encodeURIComponent( streamURL - )}&demoConfig=${serializedDemoConfig}`; + )}&demoConfig=${serializedDemoConfig}&hlsjsConfig=${serializedHlsjsConfig}`; $('#StreamPermalink').html(`${permalinkURL}`); if (!firstLoad && self.location.href !== permalinkURL) { @@ -1541,8 +1561,9 @@ function setupConfigEditor() { configEditor.setTheme('ace/theme/github'); configEditor.session.setMode('ace/mode/json'); - const contents = hlsjsDefaults; + const contents = hlsjsConfig; const shouldRestorePersisted = + !hlsjsConfigLoadedFromUrl && JSON.parse(localStorage.getItem(STORAGE_KEYS.Editor_Persistence)) === true; if (shouldRestorePersisted) { @@ -1713,6 +1734,8 @@ function updateConfigEditorValue(obj) { function applyConfigEditorValue() { onDemoConfigChanged(); + hlsjsConfig = getEditorValue({ parse: true }); + updatePermalink(false); loadSelectedStream(); } From 708e10f2e53064c81373cf41498fdc6988255ca6 Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Fri, 5 Sep 2025 20:41:08 -0700 Subject: [PATCH 42/64] Multivariant Playlist parsing fixes (#7523) * Move unknown codecs handling to playlist-loader * Add `stats.parsing.end` timing prior to emitting MANIFEST_PARSED on MANIFEST_LOADED (or MANIFEST_PARSING_ERROR) Resolves #7518 * Add mvp loading and parsing test with `stats.parsing.end` assertion (#7518) * Fix incorrect LEVEL_LOADED `level` index when lower level removed while loading --- src/controller/interstitials-controller.ts | 3 + src/controller/level-controller.ts | 29 +----- src/loader/playlist-loader.ts | 68 +++++++++++++- tests/index.js | 1 + .../controller/content-steering-controller.ts | 74 ++++++++++++++- tests/unit/controller/level-controller.ts | 86 +++++++++++++++--- tests/unit/loader/playlist-loader.ts | 91 +++++++++++++++++++ 7 files changed, 306 insertions(+), 46 deletions(-) create mode 100644 tests/unit/loader/playlist-loader.ts diff --git a/src/controller/interstitials-controller.ts b/src/controller/interstitials-controller.ts index 594707dc87c..d31c1e1daae 100644 --- a/src/controller/interstitials-controller.ts +++ b/src/controller/interstitials-controller.ts @@ -1532,6 +1532,9 @@ export default class InterstitialsController return; } const main = this.hls.levels[data.level]; + if (!main.details) { + return; + } const currentSelection = { ...(this.mediaSelection || this.altSelection), main, diff --git a/src/controller/level-controller.ts b/src/controller/level-controller.ts index c2999e7eaf4..fdd518c1b88 100644 --- a/src/controller/level-controller.ts +++ b/src/controller/level-controller.ts @@ -8,7 +8,6 @@ import { codecsSetSelectionPreferenceValue, convertAVC1ToAVCOTI, getCodecCompatibleName, - sampleEntryCodesISO, videoCodecPreferenceValue, } from '../utils/codecs'; import { reassignFragmentLevelIndexes } from '../utils/level-helper'; @@ -133,29 +132,8 @@ export default class LevelController extends BasePlaylistController { // only keep levels with supported audio/video codecs const { width, height, unknownCodecs } = levelParsed; - let unknownUnsupportedCodecCount = unknownCodecs - ? unknownCodecs.length - : 0; - if (unknownCodecs) { - // Treat unknown codec as audio or video codec based on passing `isTypeSupported` check - // (allows for playback of any supported codec even if not indexed in utils/codecs) - for (let i = unknownUnsupportedCodecCount; i--; ) { - const unknownCodec = unknownCodecs[i]; - if (this.isAudioSupported(unknownCodec)) { - levelParsed.audioCodec = audioCodec = audioCodec - ? `${audioCodec},${unknownCodec}` - : unknownCodec; - unknownUnsupportedCodecCount--; - sampleEntryCodesISO.audio[audioCodec.substring(0, 4)] = 2; - } else if (this.isVideoSupported(unknownCodec)) { - levelParsed.videoCodec = videoCodec = videoCodec - ? `${videoCodec},${unknownCodec}` - : unknownCodec; - unknownUnsupportedCodecCount--; - sampleEntryCodesISO.video[videoCodec.substring(0, 4)] = 2; - } - } - } + const unknownUnsupportedCodecCount = unknownCodecs?.length || 0; + resolutionFound ||= !!(width && height); videoCodecFound ||= !!videoCodec; audioCodecFound ||= !!audioCodec; @@ -252,6 +230,7 @@ export default class LevelController extends BasePlaylistController { let audioTracks: MediaPlaylist[] = []; let subtitleTracks: MediaPlaylist[] = []; let levels = filteredLevels; + const statsParsing = data.stats?.parsing || {}; // remove audio-only and invalid video-range levels if we also have levels with video codecs or RESOLUTION signalled if ((resolutionFound || videoCodecFound) && audioCodecFound) { @@ -290,6 +269,7 @@ export default class LevelController extends BasePlaylistController { }); } }); + statsParsing.end = performance.now(); return; } @@ -408,6 +388,7 @@ export default class LevelController extends BasePlaylistController { altAudio: altAudioEnabled && !audioOnly && audioTracks.some((t) => !!t.url), }; + statsParsing.end = performance.now(); this.hls.trigger(Events.MANIFEST_PARSED, edata); } diff --git a/src/loader/playlist-loader.ts b/src/loader/playlist-loader.ts index f829e491e11..057c6a36d9e 100644 --- a/src/loader/playlist-loader.ts +++ b/src/loader/playlist-loader.ts @@ -11,6 +11,10 @@ import { ErrorDetails, ErrorTypes } from '../errors'; import { Events } from '../events'; import { PlaylistContextType, PlaylistLevelType } from '../types/loader'; import { AttrList } from '../utils/attr-list'; +import { + areCodecsMediaSourceSupported, + sampleEntryCodesISO, +} from '../utils/codecs'; import { computeReloadInterval } from '../utils/level-helper'; import type { LevelDetails } from './level-details'; import type { LoaderConfig, RetryConfig } from '../config'; @@ -424,6 +428,7 @@ class PlaylistLoader implements NetworkComponentAPI { const parsedResult = M3U8Parser.parseMasterPlaylist(string, url); if (parsedResult.playlistParsingError) { + stats.parsing.end = performance.now(); this.handleManifestParsingError( response, context, @@ -445,6 +450,44 @@ class PlaylistLoader implements NetworkComponentAPI { this.variableList = variableList; + // Treat unknown codec as audio or video codec based on passing `isTypeSupported` check + // (allows for playback of any supported codec even if not indexed in utils/codecs) + levels.forEach((levelParsed: LevelParsed) => { + const { unknownCodecs } = levelParsed; + if (unknownCodecs) { + const { preferManagedMediaSource } = this.hls.config; + let { audioCodec, videoCodec } = levelParsed; + for (let i = unknownCodecs.length; i--; ) { + const unknownCodec = unknownCodecs[i]; + if ( + areCodecsMediaSourceSupported( + unknownCodec, + 'audio', + preferManagedMediaSource, + ) + ) { + levelParsed.audioCodec = audioCodec = audioCodec + ? `${audioCodec},${unknownCodec}` + : unknownCodec; + sampleEntryCodesISO.audio[audioCodec.substring(0, 4)] = 2; + unknownCodecs.splice(i, 1); + } else if ( + areCodecsMediaSourceSupported( + unknownCodec, + 'video', + preferManagedMediaSource, + ) + ) { + levelParsed.videoCodec = videoCodec = videoCodec + ? `${videoCodec},${unknownCodec}` + : unknownCodec; + sampleEntryCodesISO.video[videoCodec.substring(0, 4)] = 2; + unknownCodecs.splice(i, 1); + } + } + } + }); + const { AUDIO: audioTracks = [], SUBTITLES: subtitles, @@ -683,10 +726,11 @@ class PlaylistLoader implements NetworkComponentAPI { loader: Loader | undefined, ): void { const hls = this.hls; - const { type, level, id, groupId, deliveryDirectives } = context; + const { type, level, levelOrTrack, id, groupId, deliveryDirectives } = + context; const url = getResponseUrl(response, context); const parent = mapContextToLevelType(context); - const levelIndex = + let levelIndex = typeof context.level === 'number' && parent === PlaylistLevelType.MAIN ? (level as number) : undefined; @@ -748,9 +792,23 @@ class PlaylistLoader implements NetworkComponentAPI { switch (type) { case PlaylistContextType.MANIFEST: case PlaylistContextType.LEVEL: + if (levelIndex) { + if (!levelOrTrack) { + // fall-through to hls.levels[0] + levelIndex = 0; + } else { + if (levelOrTrack !== hls.levels[levelIndex]) { + // correct levelIndex when lower levels were removed from hls.levels + const updatedIndex = hls.levels.indexOf(levelOrTrack as Level); + if (updatedIndex > -1) { + levelIndex = updatedIndex; + } + } + } + } hls.trigger(Events.LEVEL_LOADED, { details: levelDetails, - levelInfo: (context.levelOrTrack as Level) || hls.levels[0], + levelInfo: (levelOrTrack as Level | null) || hls.levels[0], level: levelIndex || 0, id: id || 0, stats, @@ -762,7 +820,7 @@ class PlaylistLoader implements NetworkComponentAPI { case PlaylistContextType.AUDIO_TRACK: hls.trigger(Events.AUDIO_TRACK_LOADED, { details: levelDetails, - track: context.levelOrTrack as MediaPlaylist, + track: levelOrTrack as MediaPlaylist, id: id || 0, groupId: groupId || '', stats, @@ -773,7 +831,7 @@ class PlaylistLoader implements NetworkComponentAPI { case PlaylistContextType.SUBTITLE_TRACK: hls.trigger(Events.SUBTITLE_TRACK_LOADED, { details: levelDetails, - track: context.levelOrTrack as MediaPlaylist, + track: levelOrTrack as MediaPlaylist, id: id || 0, groupId: groupId || '', stats, diff --git a/tests/index.js b/tests/index.js index e80d09f1c04..d4d347f240c 100644 --- a/tests/index.js +++ b/tests/index.js @@ -36,6 +36,7 @@ import './unit/loader/fragment-loader'; import './unit/loader/fragment'; import './unit/loader/level'; import './unit/loader/m3u8-parser'; +import './unit/loader/playlist-loader'; import './unit/remux/mp4-remuxer'; import './unit/utils/attr-list'; import './unit/utils/binary-search'; diff --git a/tests/unit/controller/content-steering-controller.ts b/tests/unit/controller/content-steering-controller.ts index 79b5c3d3c7e..104da9b15fa 100644 --- a/tests/unit/controller/content-steering-controller.ts +++ b/tests/unit/controller/content-steering-controller.ts @@ -54,7 +54,7 @@ type ConentSteeringControllerTestable = Omit< audioTracks: MediaPlaylist[] | null; subtitleTracks: MediaPlaylist[] | null; onManifestLoading: () => void; - onManifestLoaded: (event: string, data: Partial) => void; + onManifestLoaded: (event: string, data: ManifestLoadedData) => void; }; describe('ContentSteeringController', function () { @@ -100,6 +100,16 @@ describe('ContentSteeringController', function () { uri: 'http://example.com/manifest.json', pathwayId: 'pathway-2', }, + levels: [], + audioTracks: [], + subtitles: [], + networkDetails: new Response('ok'), + url: 'https://example.com/prog.m3u8', + stats: new LoadStats(), + sessionData: null, + sessionKeys: null, + startTimeOffset: null, + variableList: null, }); expect(contentSteeringController.uri).to.equal( 'http://example.com/manifest.json', @@ -114,6 +124,16 @@ describe('ContentSteeringController', function () { uri: 'http://example.com/manifest.json', pathwayId: 'pathway-2', }, + levels: [], + audioTracks: [], + subtitles: [], + networkDetails: new Response('ok'), + url: 'https://example.com/prog.m3u8', + stats: new LoadStats(), + sessionData: null, + sessionKeys: null, + startTimeOffset: null, + variableList: null, }); contentSteeringController.stopLoad(); expect(contentSteeringController).to.have.property('loader').that.is.null; @@ -128,6 +148,16 @@ describe('ContentSteeringController', function () { uri: 'http://example.com/manifest.json', pathwayId: 'pathway-2', }, + levels: [], + audioTracks: [], + subtitles: [], + networkDetails: new Response('ok'), + url: 'https://example.com/prog.m3u8', + stats: new LoadStats(), + sessionData: null, + sessionKeys: null, + startTimeOffset: null, + variableList: null, }); expect(contentSteeringController.stopLoad).to.be.a('function'); contentSteeringController.stopLoad(); @@ -149,6 +179,16 @@ describe('ContentSteeringController', function () { uri: 'http://example.com/manifest.json', pathwayId: 'pathway-2', }, + levels: [], + audioTracks: [], + subtitles: [], + networkDetails: new Response('ok'), + url: 'https://example.com/prog.m3u8', + stats: new LoadStats(), + sessionData: null, + sessionKeys: null, + startTimeOffset: null, + variableList: null, }); expect(contentSteeringController.loader) .to.have.property('context') @@ -166,6 +206,16 @@ describe('ContentSteeringController', function () { uri: 'http://example.com/manifest.json', pathwayId: 'pathway-2', }, + levels: [], + audioTracks: [], + subtitles: [], + networkDetails: new Response('ok'), + url: 'https://example.com/prog.m3u8', + stats: new LoadStats(), + sessionData: null, + sessionKeys: null, + startTimeOffset: null, + variableList: null, }); expect(contentSteeringController.uri).to.equal( 'http://example.com/manifest.json', @@ -209,11 +259,18 @@ http://a.example.com/md/prog_index.m3u8`; 'http://example.com/main.m3u8', parsedMultivariant, ); - const manifestLoadedData = { + const manifestLoadedData: ManifestLoadedData = { contentSteering: parsedMultivariant.contentSteering, levels: parsedMultivariant.levels, - audioTracks: parsedMediaOptions.AUDIO, + audioTracks: parsedMediaOptions.AUDIO!, subtitles: parsedMediaOptions.SUBTITLES, + networkDetails: new Response('ok'), + url: 'https://example.com/prog.m3u8', + stats: new LoadStats(), + sessionData: null, + sessionKeys: null, + startTimeOffset: null, + variableList: null, }; const levelController: any = (hls.levelController = new LevelController( hls as any, @@ -310,11 +367,18 @@ http://a.example.com/md/prog_index.m3u8`; 'http://example.com/main.m3u8', parsedMultivariant, ); - const manifestLoadedData = { + const manifestLoadedData: ManifestLoadedData = { contentSteering: parsedMultivariant.contentSteering, levels: parsedMultivariant.levels, - audioTracks: parsedMediaOptions.AUDIO, + audioTracks: parsedMediaOptions.AUDIO!, subtitles: parsedMediaOptions.SUBTITLES, + networkDetails: new Response('ok'), + url: 'https://example.com/prog.m3u8', + stats: new LoadStats(), + sessionData: null, + sessionKeys: null, + startTimeOffset: null, + variableList: null, }; levelController = hls.levelController = new LevelController( hls as any, diff --git a/tests/unit/controller/level-controller.ts b/tests/unit/controller/level-controller.ts index 812d0e0580b..d6bf4a37014 100755 --- a/tests/unit/controller/level-controller.ts +++ b/tests/unit/controller/level-controller.ts @@ -4,6 +4,7 @@ import sinonChai from 'sinon-chai'; import LevelController from '../../../src/controller/level-controller'; import { ErrorDetails, ErrorTypes } from '../../../src/errors'; import { Events } from '../../../src/events'; +import { LoadStats } from '../../../src/loader/load-stats'; import M3U8Parser from '../../../src/loader/m3u8-parser'; import { Level } from '../../../src/types/level'; import { PlaylistLevelType } from '../../../src/types/loader'; @@ -29,7 +30,7 @@ chai.use(sinonChai); const expect = chai.expect; type LevelControllerTestable = Omit & { - onManifestLoaded: (event: string, data: Partial) => void; + onManifestLoaded: (event: string, data: ManifestLoadedData) => void; onAudioTrackSwitched: (event: string, data: { id: number }) => void; onError: ( event: string, @@ -123,7 +124,7 @@ describe('LevelController', function () { contentSteering: null, startTimeOffset: null, variableList: null, - stats: {} as any, + stats: new LoadStats(), subtitles: [], url: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8', }; @@ -189,6 +190,12 @@ describe('LevelController', function () { networkDetails: new Response('ok'), subtitles: [], url: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8', + stats: new LoadStats(), + sessionData: null, + sessionKeys: null, + contentSteering: null, + startTimeOffset: null, + variableList: null, }); return Promise.resolve().then(() => { @@ -204,6 +211,7 @@ describe('LevelController', function () { }); it('emits MANIFEST_PARSED when levels are found in the manifest', function () { + const stats = new LoadStats(); const data: ManifestLoadedData = { audioTracks: [], levels: [ @@ -239,7 +247,7 @@ describe('LevelController', function () { contentSteering: null, startTimeOffset: null, variableList: null, - stats: {} as any, + stats, url: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8', }; @@ -252,7 +260,7 @@ describe('LevelController', function () { sessionData: null, sessionKeys: null, firstLevel: 0, - stats: {}, + stats, audio: false, video: false, altAudio: false, @@ -280,6 +288,15 @@ http://bar.example.com/audio-only/prog_index.m3u8`, levelController.onManifestLoaded(Events.MANIFEST_LOADED, { levels: parsedLevels, audioTracks: [], + subtitles: [], + networkDetails: new Response('ok'), + url: 'https://example.com/prog.m3u8', + stats: new LoadStats(), + sessionData: null, + sessionKeys: null, + contentSteering: null, + startTimeOffset: null, + variableList: null, }); const { name, @@ -304,6 +321,7 @@ http://bar.example.com/audio-only/prog_index.m3u8`, describe('Manifest Parsed Alt-Audio', function () { it('emits MANIFEST_PARSED with `altAudio = true` when there are no codec attributes in MANIFEST_LOADED', function () { + const stats = new LoadStats(); const data: ManifestLoadedData = { audioTracks: [ mediaPlaylist({ @@ -324,7 +342,7 @@ http://bar.example.com/audio-only/prog_index.m3u8`, contentSteering: null, startTimeOffset: null, variableList: null, - stats: {} as any, + stats, url: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8', }; @@ -335,7 +353,7 @@ http://bar.example.com/audio-only/prog_index.m3u8`, sessionData: null, sessionKeys: null, firstLevel: 0, - stats: {} as any, + stats, audio: false, video: false, altAudio: true, @@ -349,6 +367,7 @@ http://bar.example.com/audio-only/prog_index.m3u8`, }); it('emits MANIFEST_PARSED with `altAudio = true` when there are codec attributes in MANIFEST_LOADED', function () { + const stats = new LoadStats(); const data: ManifestLoadedData = { audioTracks: [ mediaPlaylist({ audioCodec: 'mp4a.40.5', url: 'audio-track.m3u8' }), @@ -368,7 +387,7 @@ http://bar.example.com/audio-only/prog_index.m3u8`, contentSteering: null, startTimeOffset: null, variableList: null, - stats: {} as any, + stats, url: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8', }; @@ -380,7 +399,7 @@ http://bar.example.com/audio-only/prog_index.m3u8`, sessionData: null, sessionKeys: null, firstLevel: 0, - stats: {}, + stats, audio: true, video: true, altAudio: true, @@ -388,6 +407,7 @@ http://bar.example.com/audio-only/prog_index.m3u8`, }); it('emits MANIFEST_PARSED with `altAudio = false` when Variant(s) are audio-only with audio Media Playlists in MANIFEST_LOADED', function () { + const stats = new LoadStats(); const data: ManifestLoadedData = { audioTracks: [ mediaPlaylist({ audioCodec: 'mp4a.40.5', name: 'main' }), @@ -407,7 +427,7 @@ http://bar.example.com/audio-only/prog_index.m3u8`, contentSteering: null, startTimeOffset: null, variableList: null, - stats: {} as any, + stats, url: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8', }; @@ -419,7 +439,7 @@ http://bar.example.com/audio-only/prog_index.m3u8`, sessionData: null, sessionKeys: null, firstLevel: 0, - stats: {}, + stats, audio: true, video: false, altAudio: false, @@ -445,7 +465,7 @@ http://bar.example.com/audio-only/prog_index.m3u8`, contentSteering: null, startTimeOffset: null, variableList: null, - stats: {} as any, + stats: new LoadStats(), url: 'foo', }; }); @@ -664,6 +684,15 @@ http://bar.example.com/md/prog_index.m3u8`, levelController.onManifestLoaded(Events.MANIFEST_LOADED, { levels: parsedLevels, audioTracks: [], + subtitles: [], + networkDetails: new Response('ok'), + url: 'https://example.com/prog.m3u8', + stats: new LoadStats(), + sessionData: null, + sessionKeys: null, + contentSteering: null, + startTimeOffset: null, + variableList: null, }); const { name, @@ -718,7 +747,16 @@ http://bar.example.com/md/prog_index.m3u8`; expect(parsedSubs).to.be.undefined; levelController.onManifestLoaded(Events.MANIFEST_LOADED, { levels: parsedLevels, - audioTracks: parsedAudio, + audioTracks: parsedAudio!, + subtitles: [], + networkDetails: new Response('ok'), + url: 'https://example.com/prog.m3u8', + stats: new LoadStats(), + sessionData: null, + sessionKeys: null, + contentSteering: null, + startTimeOffset: null, + variableList: null, }); const { name, @@ -774,6 +812,14 @@ http://bar.example.com/md/prog_index.m3u8`; levels: parsedLevels, audioTracks: parsedAudio, subtitles: parsedSubs, + networkDetails: new Response('ok'), + url: 'https://example.com/prog.m3u8', + stats: new LoadStats(), + sessionData: null, + sessionKeys: null, + contentSteering: null, + startTimeOffset: null, + variableList: null, }); const { name, payload } = hls.getEventData(0) as { name: string; @@ -909,6 +955,14 @@ http://bar.example.com/md/prog_index.m3u8`; levels: parsedLevels, audioTracks: parsedAudio, subtitles: parsedSubs, + networkDetails: new Response('ok'), + url: 'https://example.com/prog.m3u8', + stats: new LoadStats(), + sessionData: null, + sessionKeys: null, + contentSteering: null, + startTimeOffset: null, + variableList: null, }); const { name, payload } = hls.getEventData(0) as { name: string; @@ -995,6 +1049,14 @@ http://bar.example.com/md/prog_index.m3u8`; levels: parsedLevels, audioTracks: parsedAudio, subtitles: parsedSubs, + networkDetails: new Response('ok'), + url: 'https://example.com/prog.m3u8', + stats: new LoadStats(), + sessionData: null, + sessionKeys: null, + contentSteering: null, + startTimeOffset: null, + variableList: null, }); const { name, payload } = hls.getEventData(0) as { name: string; diff --git a/tests/unit/loader/playlist-loader.ts b/tests/unit/loader/playlist-loader.ts new file mode 100644 index 00000000000..8dc8c76705a --- /dev/null +++ b/tests/unit/loader/playlist-loader.ts @@ -0,0 +1,91 @@ +import chai from 'chai'; +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +import LevelController from '../../../src/controller/level-controller'; +import { Events } from '../../../src/events'; +import { LoadStats } from '../../../src/loader/load-stats'; +import PlaylistLoader from '../../../src/loader/playlist-loader'; +import HlsMock from '../../mocks/hls.mock'; +import { multivariantPlaylistWithPathways } from '../controller/level-controller'; +import type Hls from '../../../src/hls'; +import type { + LoaderCallbacks, + LoaderConfiguration, + PlaylistLoaderContext, +} from '../../../src/types/loader'; + +chai.use(sinonChai); +const expect = chai.expect; + +describe('PlaylistLoader tests', function () { + const sandbox = sinon.createSandbox(); + let hls: Hls; // HlsMock + let playlistLoader: PlaylistLoader; + let levelController: LevelController; // level-controller finishes manifest parsing + let response; + + beforeEach(function () { + hls = new HlsMock({}) as unknown as Hls; + playlistLoader = new PlaylistLoader(hls); + levelController = new LevelController(hls, null); + }); + + afterEach(function () { + sandbox.restore(); + playlistLoader.destroy(); + levelController.destroy(); + }); + + it('handles multivariant playlist loading and parsing (with level-controller) on MANIFEST_LOADING', function () { + return new Promise((resolve, reject) => { + const stats = new LoadStats(); + const networkDetails = new Response('ok'); + sinon.stub(playlistLoader as any, 'createInternalLoader').returns({ + load: ( + context: PlaylistLoaderContext, + config: LoaderConfiguration, + callback: LoaderCallbacks, + ) => { + expect(context.type).eq('manifest'); + Promise.resolve() + .then(() => { + response = { data: multivariantPlaylistWithPathways }; + callback.onSuccess(response, stats, context, networkDetails); + }) + .catch(reject); + }, + abort: () => {}, + }); + + hls.on(Events.MANIFEST_LOADED, (type, data) => { + expect(data).includes({ + url: 'http://example.cpm/program.m3u8', + stats, + networkDetails, + captions: undefined, + sessionData: null, + sessionKeys: null, + startTimeOffset: null, + variableList: null, + }); + expect(data.levels, 'levels.length').to.have.lengthOf(30); + expect(data.audioTracks, 'audioTracks.length').to.have.lengthOf(6); + expect(data.subtitles, 'subtitles.length').to.have.lengthOf(6); + expect(data.contentSteering, 'contentSteering').to.includes({ + uri: 'http://example.com/manifest.json', + pathwayId: 'Bar', + }); + expect(stats.parsing.start, 'parsing.start').to.be.greaterThan(0); + expect(stats.parsing.end, 'parsing.end').to.be.greaterThanOrEqual( + stats.parsing.start, + ); + + resolve(); + }); + + hls.trigger(Events.MANIFEST_LOADING, { + url: 'http://example.cpm/program.m3u8', + }); + }); + }); +}); From a3c63bd39eb9090a13da919d30884f4515641267 Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Fri, 5 Sep 2025 20:41:08 -0700 Subject: [PATCH 43/64] Multivariant Playlist parsing fixes (#7523) * Move unknown codecs handling to playlist-loader * Add `stats.parsing.end` timing prior to emitting MANIFEST_PARSED on MANIFEST_LOADED (or MANIFEST_PARSING_ERROR) Resolves #7518 * Add mvp loading and parsing test with `stats.parsing.end` assertion (#7518) * Fix incorrect LEVEL_LOADED `level` index when lower level removed while loading --- src/controller/interstitials-controller.ts | 3 + src/controller/level-controller.ts | 29 +----- src/loader/playlist-loader.ts | 68 +++++++++++++- tests/index.js | 1 + .../controller/content-steering-controller.ts | 74 ++++++++++++++- tests/unit/controller/level-controller.ts | 86 +++++++++++++++--- tests/unit/loader/playlist-loader.ts | 91 +++++++++++++++++++ 7 files changed, 306 insertions(+), 46 deletions(-) create mode 100644 tests/unit/loader/playlist-loader.ts diff --git a/src/controller/interstitials-controller.ts b/src/controller/interstitials-controller.ts index abe29884b49..e99944c1ccc 100644 --- a/src/controller/interstitials-controller.ts +++ b/src/controller/interstitials-controller.ts @@ -1533,6 +1533,9 @@ export default class InterstitialsController return; } const main = this.hls.levels[data.level]; + if (!main.details) { + return; + } const currentSelection = { ...(this.mediaSelection || this.altSelection), main, diff --git a/src/controller/level-controller.ts b/src/controller/level-controller.ts index c2999e7eaf4..fdd518c1b88 100644 --- a/src/controller/level-controller.ts +++ b/src/controller/level-controller.ts @@ -8,7 +8,6 @@ import { codecsSetSelectionPreferenceValue, convertAVC1ToAVCOTI, getCodecCompatibleName, - sampleEntryCodesISO, videoCodecPreferenceValue, } from '../utils/codecs'; import { reassignFragmentLevelIndexes } from '../utils/level-helper'; @@ -133,29 +132,8 @@ export default class LevelController extends BasePlaylistController { // only keep levels with supported audio/video codecs const { width, height, unknownCodecs } = levelParsed; - let unknownUnsupportedCodecCount = unknownCodecs - ? unknownCodecs.length - : 0; - if (unknownCodecs) { - // Treat unknown codec as audio or video codec based on passing `isTypeSupported` check - // (allows for playback of any supported codec even if not indexed in utils/codecs) - for (let i = unknownUnsupportedCodecCount; i--; ) { - const unknownCodec = unknownCodecs[i]; - if (this.isAudioSupported(unknownCodec)) { - levelParsed.audioCodec = audioCodec = audioCodec - ? `${audioCodec},${unknownCodec}` - : unknownCodec; - unknownUnsupportedCodecCount--; - sampleEntryCodesISO.audio[audioCodec.substring(0, 4)] = 2; - } else if (this.isVideoSupported(unknownCodec)) { - levelParsed.videoCodec = videoCodec = videoCodec - ? `${videoCodec},${unknownCodec}` - : unknownCodec; - unknownUnsupportedCodecCount--; - sampleEntryCodesISO.video[videoCodec.substring(0, 4)] = 2; - } - } - } + const unknownUnsupportedCodecCount = unknownCodecs?.length || 0; + resolutionFound ||= !!(width && height); videoCodecFound ||= !!videoCodec; audioCodecFound ||= !!audioCodec; @@ -252,6 +230,7 @@ export default class LevelController extends BasePlaylistController { let audioTracks: MediaPlaylist[] = []; let subtitleTracks: MediaPlaylist[] = []; let levels = filteredLevels; + const statsParsing = data.stats?.parsing || {}; // remove audio-only and invalid video-range levels if we also have levels with video codecs or RESOLUTION signalled if ((resolutionFound || videoCodecFound) && audioCodecFound) { @@ -290,6 +269,7 @@ export default class LevelController extends BasePlaylistController { }); } }); + statsParsing.end = performance.now(); return; } @@ -408,6 +388,7 @@ export default class LevelController extends BasePlaylistController { altAudio: altAudioEnabled && !audioOnly && audioTracks.some((t) => !!t.url), }; + statsParsing.end = performance.now(); this.hls.trigger(Events.MANIFEST_PARSED, edata); } diff --git a/src/loader/playlist-loader.ts b/src/loader/playlist-loader.ts index b589ad1f8fc..81e637cee01 100644 --- a/src/loader/playlist-loader.ts +++ b/src/loader/playlist-loader.ts @@ -11,6 +11,10 @@ import { ErrorDetails, ErrorTypes } from '../errors'; import { Events } from '../events'; import { PlaylistContextType, PlaylistLevelType } from '../types/loader'; import { AttrList } from '../utils/attr-list'; +import { + areCodecsMediaSourceSupported, + sampleEntryCodesISO, +} from '../utils/codecs'; import { computeReloadInterval } from '../utils/level-helper'; import type { LevelDetails } from './level-details'; import type { LoaderConfig, RetryConfig } from '../config'; @@ -423,6 +427,7 @@ class PlaylistLoader implements NetworkComponentAPI { const parsedResult = M3U8Parser.parseMasterPlaylist(string, url); if (parsedResult.playlistParsingError) { + stats.parsing.end = performance.now(); this.handleManifestParsingError( response, context, @@ -444,6 +449,44 @@ class PlaylistLoader implements NetworkComponentAPI { this.variableList = variableList; + // Treat unknown codec as audio or video codec based on passing `isTypeSupported` check + // (allows for playback of any supported codec even if not indexed in utils/codecs) + levels.forEach((levelParsed: LevelParsed) => { + const { unknownCodecs } = levelParsed; + if (unknownCodecs) { + const { preferManagedMediaSource } = this.hls.config; + let { audioCodec, videoCodec } = levelParsed; + for (let i = unknownCodecs.length; i--; ) { + const unknownCodec = unknownCodecs[i]; + if ( + areCodecsMediaSourceSupported( + unknownCodec, + 'audio', + preferManagedMediaSource, + ) + ) { + levelParsed.audioCodec = audioCodec = audioCodec + ? `${audioCodec},${unknownCodec}` + : unknownCodec; + sampleEntryCodesISO.audio[audioCodec.substring(0, 4)] = 2; + unknownCodecs.splice(i, 1); + } else if ( + areCodecsMediaSourceSupported( + unknownCodec, + 'video', + preferManagedMediaSource, + ) + ) { + levelParsed.videoCodec = videoCodec = videoCodec + ? `${videoCodec},${unknownCodec}` + : unknownCodec; + sampleEntryCodesISO.video[videoCodec.substring(0, 4)] = 2; + unknownCodecs.splice(i, 1); + } + } + } + }); + const { AUDIO: audioTracks = [], SUBTITLES: subtitles, @@ -679,10 +722,11 @@ class PlaylistLoader implements NetworkComponentAPI { loader: Loader | undefined, ): void { const hls = this.hls; - const { type, level, id, groupId, deliveryDirectives } = context; + const { type, level, levelOrTrack, id, groupId, deliveryDirectives } = + context; const url = getResponseUrl(response, context); const parent = mapContextToLevelType(context); - const levelIndex = + let levelIndex = typeof context.level === 'number' && parent === PlaylistLevelType.MAIN ? (level as number) : undefined; @@ -744,9 +788,23 @@ class PlaylistLoader implements NetworkComponentAPI { switch (type) { case PlaylistContextType.MANIFEST: case PlaylistContextType.LEVEL: + if (levelIndex) { + if (!levelOrTrack) { + // fall-through to hls.levels[0] + levelIndex = 0; + } else { + if (levelOrTrack !== hls.levels[levelIndex]) { + // correct levelIndex when lower levels were removed from hls.levels + const updatedIndex = hls.levels.indexOf(levelOrTrack as Level); + if (updatedIndex > -1) { + levelIndex = updatedIndex; + } + } + } + } hls.trigger(Events.LEVEL_LOADED, { details: levelDetails, - levelInfo: (context.levelOrTrack as Level) || hls.levels[0], + levelInfo: (levelOrTrack as Level | null) || hls.levels[0], level: levelIndex || 0, id: id || 0, stats, @@ -758,7 +816,7 @@ class PlaylistLoader implements NetworkComponentAPI { case PlaylistContextType.AUDIO_TRACK: hls.trigger(Events.AUDIO_TRACK_LOADED, { details: levelDetails, - track: context.levelOrTrack as MediaPlaylist, + track: levelOrTrack as MediaPlaylist, id: id || 0, groupId: groupId || '', stats, @@ -769,7 +827,7 @@ class PlaylistLoader implements NetworkComponentAPI { case PlaylistContextType.SUBTITLE_TRACK: hls.trigger(Events.SUBTITLE_TRACK_LOADED, { details: levelDetails, - track: context.levelOrTrack as MediaPlaylist, + track: levelOrTrack as MediaPlaylist, id: id || 0, groupId: groupId || '', stats, diff --git a/tests/index.js b/tests/index.js index 6490a348464..105d2aa9aa7 100644 --- a/tests/index.js +++ b/tests/index.js @@ -35,6 +35,7 @@ import './unit/loader/fragment-loader'; import './unit/loader/fragment'; import './unit/loader/level'; import './unit/loader/m3u8-parser'; +import './unit/loader/playlist-loader'; import './unit/remux/mp4-remuxer'; import './unit/utils/attr-list'; import './unit/utils/binary-search'; diff --git a/tests/unit/controller/content-steering-controller.ts b/tests/unit/controller/content-steering-controller.ts index 79b5c3d3c7e..104da9b15fa 100644 --- a/tests/unit/controller/content-steering-controller.ts +++ b/tests/unit/controller/content-steering-controller.ts @@ -54,7 +54,7 @@ type ConentSteeringControllerTestable = Omit< audioTracks: MediaPlaylist[] | null; subtitleTracks: MediaPlaylist[] | null; onManifestLoading: () => void; - onManifestLoaded: (event: string, data: Partial) => void; + onManifestLoaded: (event: string, data: ManifestLoadedData) => void; }; describe('ContentSteeringController', function () { @@ -100,6 +100,16 @@ describe('ContentSteeringController', function () { uri: 'http://example.com/manifest.json', pathwayId: 'pathway-2', }, + levels: [], + audioTracks: [], + subtitles: [], + networkDetails: new Response('ok'), + url: 'https://example.com/prog.m3u8', + stats: new LoadStats(), + sessionData: null, + sessionKeys: null, + startTimeOffset: null, + variableList: null, }); expect(contentSteeringController.uri).to.equal( 'http://example.com/manifest.json', @@ -114,6 +124,16 @@ describe('ContentSteeringController', function () { uri: 'http://example.com/manifest.json', pathwayId: 'pathway-2', }, + levels: [], + audioTracks: [], + subtitles: [], + networkDetails: new Response('ok'), + url: 'https://example.com/prog.m3u8', + stats: new LoadStats(), + sessionData: null, + sessionKeys: null, + startTimeOffset: null, + variableList: null, }); contentSteeringController.stopLoad(); expect(contentSteeringController).to.have.property('loader').that.is.null; @@ -128,6 +148,16 @@ describe('ContentSteeringController', function () { uri: 'http://example.com/manifest.json', pathwayId: 'pathway-2', }, + levels: [], + audioTracks: [], + subtitles: [], + networkDetails: new Response('ok'), + url: 'https://example.com/prog.m3u8', + stats: new LoadStats(), + sessionData: null, + sessionKeys: null, + startTimeOffset: null, + variableList: null, }); expect(contentSteeringController.stopLoad).to.be.a('function'); contentSteeringController.stopLoad(); @@ -149,6 +179,16 @@ describe('ContentSteeringController', function () { uri: 'http://example.com/manifest.json', pathwayId: 'pathway-2', }, + levels: [], + audioTracks: [], + subtitles: [], + networkDetails: new Response('ok'), + url: 'https://example.com/prog.m3u8', + stats: new LoadStats(), + sessionData: null, + sessionKeys: null, + startTimeOffset: null, + variableList: null, }); expect(contentSteeringController.loader) .to.have.property('context') @@ -166,6 +206,16 @@ describe('ContentSteeringController', function () { uri: 'http://example.com/manifest.json', pathwayId: 'pathway-2', }, + levels: [], + audioTracks: [], + subtitles: [], + networkDetails: new Response('ok'), + url: 'https://example.com/prog.m3u8', + stats: new LoadStats(), + sessionData: null, + sessionKeys: null, + startTimeOffset: null, + variableList: null, }); expect(contentSteeringController.uri).to.equal( 'http://example.com/manifest.json', @@ -209,11 +259,18 @@ http://a.example.com/md/prog_index.m3u8`; 'http://example.com/main.m3u8', parsedMultivariant, ); - const manifestLoadedData = { + const manifestLoadedData: ManifestLoadedData = { contentSteering: parsedMultivariant.contentSteering, levels: parsedMultivariant.levels, - audioTracks: parsedMediaOptions.AUDIO, + audioTracks: parsedMediaOptions.AUDIO!, subtitles: parsedMediaOptions.SUBTITLES, + networkDetails: new Response('ok'), + url: 'https://example.com/prog.m3u8', + stats: new LoadStats(), + sessionData: null, + sessionKeys: null, + startTimeOffset: null, + variableList: null, }; const levelController: any = (hls.levelController = new LevelController( hls as any, @@ -310,11 +367,18 @@ http://a.example.com/md/prog_index.m3u8`; 'http://example.com/main.m3u8', parsedMultivariant, ); - const manifestLoadedData = { + const manifestLoadedData: ManifestLoadedData = { contentSteering: parsedMultivariant.contentSteering, levels: parsedMultivariant.levels, - audioTracks: parsedMediaOptions.AUDIO, + audioTracks: parsedMediaOptions.AUDIO!, subtitles: parsedMediaOptions.SUBTITLES, + networkDetails: new Response('ok'), + url: 'https://example.com/prog.m3u8', + stats: new LoadStats(), + sessionData: null, + sessionKeys: null, + startTimeOffset: null, + variableList: null, }; levelController = hls.levelController = new LevelController( hls as any, diff --git a/tests/unit/controller/level-controller.ts b/tests/unit/controller/level-controller.ts index 0a2b1e660b2..7b95ffbd48d 100755 --- a/tests/unit/controller/level-controller.ts +++ b/tests/unit/controller/level-controller.ts @@ -4,6 +4,7 @@ import sinonChai from 'sinon-chai'; import LevelController from '../../../src/controller/level-controller'; import { ErrorDetails, ErrorTypes } from '../../../src/errors'; import { Events } from '../../../src/events'; +import { LoadStats } from '../../../src/loader/load-stats'; import M3U8Parser from '../../../src/loader/m3u8-parser'; import { Level } from '../../../src/types/level'; import { PlaylistLevelType } from '../../../src/types/loader'; @@ -29,7 +30,7 @@ chai.use(sinonChai); const expect = chai.expect; type LevelControllerTestable = Omit & { - onManifestLoaded: (event: string, data: Partial) => void; + onManifestLoaded: (event: string, data: ManifestLoadedData) => void; onAudioTrackSwitched: (event: string, data: { id: number }) => void; onError: ( event: string, @@ -123,7 +124,7 @@ describe('LevelController', function () { contentSteering: null, startTimeOffset: null, variableList: null, - stats: {} as any, + stats: new LoadStats(), subtitles: [], url: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8', }; @@ -189,6 +190,12 @@ describe('LevelController', function () { networkDetails: '', subtitles: [], url: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8', + stats: new LoadStats(), + sessionData: null, + sessionKeys: null, + contentSteering: null, + startTimeOffset: null, + variableList: null, }); return Promise.resolve().then(() => { @@ -204,6 +211,7 @@ describe('LevelController', function () { }); it('emits MANIFEST_PARSED when levels are found in the manifest', function () { + const stats = new LoadStats(); const data: ManifestLoadedData = { audioTracks: [], levels: [ @@ -239,7 +247,7 @@ describe('LevelController', function () { contentSteering: null, startTimeOffset: null, variableList: null, - stats: {} as any, + stats, url: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8', }; @@ -252,7 +260,7 @@ describe('LevelController', function () { sessionData: null, sessionKeys: null, firstLevel: 0, - stats: {}, + stats, audio: false, video: false, altAudio: false, @@ -280,6 +288,15 @@ http://bar.example.com/audio-only/prog_index.m3u8`, levelController.onManifestLoaded(Events.MANIFEST_LOADED, { levels: parsedLevels, audioTracks: [], + subtitles: [], + networkDetails: new Response('ok'), + url: 'https://example.com/prog.m3u8', + stats: new LoadStats(), + sessionData: null, + sessionKeys: null, + contentSteering: null, + startTimeOffset: null, + variableList: null, }); const { name, @@ -304,6 +321,7 @@ http://bar.example.com/audio-only/prog_index.m3u8`, describe('Manifest Parsed Alt-Audio', function () { it('emits MANIFEST_PARSED with `altAudio = true` when there are no codec attributes in MANIFEST_LOADED', function () { + const stats = new LoadStats(); const data: ManifestLoadedData = { audioTracks: [ mediaPlaylist({ @@ -324,7 +342,7 @@ http://bar.example.com/audio-only/prog_index.m3u8`, contentSteering: null, startTimeOffset: null, variableList: null, - stats: {} as any, + stats, url: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8', }; @@ -335,7 +353,7 @@ http://bar.example.com/audio-only/prog_index.m3u8`, sessionData: null, sessionKeys: null, firstLevel: 0, - stats: {} as any, + stats, audio: false, video: false, altAudio: true, @@ -349,6 +367,7 @@ http://bar.example.com/audio-only/prog_index.m3u8`, }); it('emits MANIFEST_PARSED with `altAudio = true` when there are codec attributes in MANIFEST_LOADED', function () { + const stats = new LoadStats(); const data: ManifestLoadedData = { audioTracks: [ mediaPlaylist({ audioCodec: 'mp4a.40.5', url: 'audio-track.m3u8' }), @@ -368,7 +387,7 @@ http://bar.example.com/audio-only/prog_index.m3u8`, contentSteering: null, startTimeOffset: null, variableList: null, - stats: {} as any, + stats, url: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8', }; @@ -380,7 +399,7 @@ http://bar.example.com/audio-only/prog_index.m3u8`, sessionData: null, sessionKeys: null, firstLevel: 0, - stats: {}, + stats, audio: true, video: true, altAudio: true, @@ -388,6 +407,7 @@ http://bar.example.com/audio-only/prog_index.m3u8`, }); it('emits MANIFEST_PARSED with `altAudio = false` when Variant(s) are audio-only with audio Media Playlists in MANIFEST_LOADED', function () { + const stats = new LoadStats(); const data: ManifestLoadedData = { audioTracks: [ mediaPlaylist({ audioCodec: 'mp4a.40.5', name: 'main' }), @@ -407,7 +427,7 @@ http://bar.example.com/audio-only/prog_index.m3u8`, contentSteering: null, startTimeOffset: null, variableList: null, - stats: {} as any, + stats, url: 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8', }; @@ -419,7 +439,7 @@ http://bar.example.com/audio-only/prog_index.m3u8`, sessionData: null, sessionKeys: null, firstLevel: 0, - stats: {}, + stats, audio: true, video: false, altAudio: false, @@ -445,7 +465,7 @@ http://bar.example.com/audio-only/prog_index.m3u8`, contentSteering: null, startTimeOffset: null, variableList: null, - stats: {} as any, + stats: new LoadStats(), url: 'foo', }; }); @@ -664,6 +684,15 @@ http://bar.example.com/md/prog_index.m3u8`, levelController.onManifestLoaded(Events.MANIFEST_LOADED, { levels: parsedLevels, audioTracks: [], + subtitles: [], + networkDetails: new Response('ok'), + url: 'https://example.com/prog.m3u8', + stats: new LoadStats(), + sessionData: null, + sessionKeys: null, + contentSteering: null, + startTimeOffset: null, + variableList: null, }); const { name, @@ -718,7 +747,16 @@ http://bar.example.com/md/prog_index.m3u8`; expect(parsedSubs).to.be.undefined; levelController.onManifestLoaded(Events.MANIFEST_LOADED, { levels: parsedLevels, - audioTracks: parsedAudio, + audioTracks: parsedAudio!, + subtitles: [], + networkDetails: new Response('ok'), + url: 'https://example.com/prog.m3u8', + stats: new LoadStats(), + sessionData: null, + sessionKeys: null, + contentSteering: null, + startTimeOffset: null, + variableList: null, }); const { name, @@ -774,6 +812,14 @@ http://bar.example.com/md/prog_index.m3u8`; levels: parsedLevels, audioTracks: parsedAudio, subtitles: parsedSubs, + networkDetails: new Response('ok'), + url: 'https://example.com/prog.m3u8', + stats: new LoadStats(), + sessionData: null, + sessionKeys: null, + contentSteering: null, + startTimeOffset: null, + variableList: null, }); const { name, payload } = hls.getEventData(0) as { name: string; @@ -909,6 +955,14 @@ http://bar.example.com/md/prog_index.m3u8`; levels: parsedLevels, audioTracks: parsedAudio, subtitles: parsedSubs, + networkDetails: new Response('ok'), + url: 'https://example.com/prog.m3u8', + stats: new LoadStats(), + sessionData: null, + sessionKeys: null, + contentSteering: null, + startTimeOffset: null, + variableList: null, }); const { name, payload } = hls.getEventData(0) as { name: string; @@ -995,6 +1049,14 @@ http://bar.example.com/md/prog_index.m3u8`; levels: parsedLevels, audioTracks: parsedAudio, subtitles: parsedSubs, + networkDetails: new Response('ok'), + url: 'https://example.com/prog.m3u8', + stats: new LoadStats(), + sessionData: null, + sessionKeys: null, + contentSteering: null, + startTimeOffset: null, + variableList: null, }); const { name, payload } = hls.getEventData(0) as { name: string; diff --git a/tests/unit/loader/playlist-loader.ts b/tests/unit/loader/playlist-loader.ts new file mode 100644 index 00000000000..8dc8c76705a --- /dev/null +++ b/tests/unit/loader/playlist-loader.ts @@ -0,0 +1,91 @@ +import chai from 'chai'; +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +import LevelController from '../../../src/controller/level-controller'; +import { Events } from '../../../src/events'; +import { LoadStats } from '../../../src/loader/load-stats'; +import PlaylistLoader from '../../../src/loader/playlist-loader'; +import HlsMock from '../../mocks/hls.mock'; +import { multivariantPlaylistWithPathways } from '../controller/level-controller'; +import type Hls from '../../../src/hls'; +import type { + LoaderCallbacks, + LoaderConfiguration, + PlaylistLoaderContext, +} from '../../../src/types/loader'; + +chai.use(sinonChai); +const expect = chai.expect; + +describe('PlaylistLoader tests', function () { + const sandbox = sinon.createSandbox(); + let hls: Hls; // HlsMock + let playlistLoader: PlaylistLoader; + let levelController: LevelController; // level-controller finishes manifest parsing + let response; + + beforeEach(function () { + hls = new HlsMock({}) as unknown as Hls; + playlistLoader = new PlaylistLoader(hls); + levelController = new LevelController(hls, null); + }); + + afterEach(function () { + sandbox.restore(); + playlistLoader.destroy(); + levelController.destroy(); + }); + + it('handles multivariant playlist loading and parsing (with level-controller) on MANIFEST_LOADING', function () { + return new Promise((resolve, reject) => { + const stats = new LoadStats(); + const networkDetails = new Response('ok'); + sinon.stub(playlistLoader as any, 'createInternalLoader').returns({ + load: ( + context: PlaylistLoaderContext, + config: LoaderConfiguration, + callback: LoaderCallbacks, + ) => { + expect(context.type).eq('manifest'); + Promise.resolve() + .then(() => { + response = { data: multivariantPlaylistWithPathways }; + callback.onSuccess(response, stats, context, networkDetails); + }) + .catch(reject); + }, + abort: () => {}, + }); + + hls.on(Events.MANIFEST_LOADED, (type, data) => { + expect(data).includes({ + url: 'http://example.cpm/program.m3u8', + stats, + networkDetails, + captions: undefined, + sessionData: null, + sessionKeys: null, + startTimeOffset: null, + variableList: null, + }); + expect(data.levels, 'levels.length').to.have.lengthOf(30); + expect(data.audioTracks, 'audioTracks.length').to.have.lengthOf(6); + expect(data.subtitles, 'subtitles.length').to.have.lengthOf(6); + expect(data.contentSteering, 'contentSteering').to.includes({ + uri: 'http://example.com/manifest.json', + pathwayId: 'Bar', + }); + expect(stats.parsing.start, 'parsing.start').to.be.greaterThan(0); + expect(stats.parsing.end, 'parsing.end').to.be.greaterThanOrEqual( + stats.parsing.start, + ); + + resolve(); + }); + + hls.trigger(Events.MANIFEST_LOADING, { + url: 'http://example.cpm/program.m3u8', + }); + }); + }); +}); From 0638d9aa0ac1536980bfcfe1e46078aac653e235 Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Mon, 8 Sep 2025 12:41:57 -0700 Subject: [PATCH 44/64] Setup as audio-only when main segment has no video Fixes #7524 --- src/controller/buffer-controller.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/controller/buffer-controller.ts b/src/controller/buffer-controller.ts index 388e0dc33ed..1836e7bb010 100755 --- a/src/controller/buffer-controller.ts +++ b/src/controller/buffer-controller.ts @@ -664,6 +664,18 @@ transfer tracks: ${stringify(transferredTracks, (key, value) => (key === 'initSe if (this.sourceBufferCount) { return; } + + if ( + this.bufferCodecEventsTotal > 1 && + !this.tracks.video && + !data.video && + data.audio?.id === 'main' + ) { + // MVP is missing CODECS and only audio was found in main segment (#7524) + this.log(`Main audio-only`); + this.bufferCodecEventsTotal = 1; + } + if (this.mediaSourceOpenOrEnded) { this.checkPendingTracks(); } From 903f463311c6487a9e21b9a7c9ef059cc7593772 Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Tue, 9 Sep 2025 07:05:15 -0700 Subject: [PATCH 45/64] Fix "Missing format identifier #EXTM3U" playlist parsing errors Fixes #7531 --- src/loader/m3u8-parser.ts | 9 +++--- src/loader/playlist-loader.ts | 55 ++++++++++++-------------------- tests/unit/loader/m3u8-parser.ts | 4 +-- 3 files changed, 27 insertions(+), 41 deletions(-) diff --git a/src/loader/m3u8-parser.ts b/src/loader/m3u8-parser.ts index 66c73d2744d..1b24c734a60 100644 --- a/src/loader/m3u8-parser.ts +++ b/src/loader/m3u8-parser.ts @@ -119,7 +119,10 @@ export default class M3U8Parser { const levelsWithKnownCodecs: LevelParsed[] = []; MASTER_PLAYLIST_REGEX.lastIndex = 0; - + if (!string.startsWith('#EXTM3U')) { + parsed.playlistParsingError = new Error('no EXTM3U delimiter'); + return parsed; + } let result: RegExpExecArray | null; while ((result = MASTER_PLAYLIST_REGEX.exec(string)) != null) { if (result[1]) { @@ -710,9 +713,7 @@ export default class M3U8Parser { } } if (!level.targetduration) { - level.playlistParsingError = new Error( - `#EXT-X-TARGETDURATION is required`, - ); + level.playlistParsingError = new Error(`Missing Target Duration`); } const fragmentLength = fragments.length; const firstFragment = fragments[0]; diff --git a/src/loader/playlist-loader.ts b/src/loader/playlist-loader.ts index 057c6a36d9e..2403f2ee828 100644 --- a/src/loader/playlist-loader.ts +++ b/src/loader/playlist-loader.ts @@ -345,18 +345,6 @@ class PlaylistLoader implements NetworkComponentAPI { const string = response.data as string; - // Validate if it is an M3U8 at all - if (string.indexOf('#EXTM3U') !== 0) { - this.handleManifestParsingError( - response, - context, - new Error('no EXTM3U delimiter'), - networkDetails || null, - stats, - ); - return; - } - stats.parsing.start = performance.now(); if ( M3U8Parser.isMediaPlaylist(string) || @@ -734,29 +722,6 @@ class PlaylistLoader implements NetworkComponentAPI { typeof context.level === 'number' && parent === PlaylistLevelType.MAIN ? (level as number) : undefined; - if (!levelDetails.fragments.length) { - const error = (levelDetails.playlistParsingError = new Error( - 'No Segments found in Playlist', - )); - hls.trigger(Events.ERROR, { - type: ErrorTypes.NETWORK_ERROR, - details: ErrorDetails.LEVEL_EMPTY_ERROR, - fatal: false, - url, - error, - reason: error.message, - response, - context, - level: levelIndex, - parent, - networkDetails, - stats, - }); - return; - } - if (!levelDetails.targetduration) { - levelDetails.playlistParsingError = new Error('Missing Target Duration'); - } const error = levelDetails.playlistParsingError; if (error) { this.hls.logger.warn(`${error} ${levelDetails.url}`); @@ -779,6 +744,26 @@ class PlaylistLoader implements NetworkComponentAPI { } levelDetails.playlistParsingError = null; } + if (!levelDetails.fragments.length) { + const error = (levelDetails.playlistParsingError = new Error( + 'No Segments found in Playlist', + )); + hls.trigger(Events.ERROR, { + type: ErrorTypes.NETWORK_ERROR, + details: ErrorDetails.LEVEL_EMPTY_ERROR, + fatal: false, + url, + error, + reason: error.message, + response, + context, + level: levelIndex, + parent, + networkDetails, + stats, + }); + return; + } if (levelDetails.live && loader) { if (loader.getCacheAge) { diff --git a/tests/unit/loader/m3u8-parser.ts b/tests/unit/loader/m3u8-parser.ts index 1549f308ce7..ed5249d7e85 100644 --- a/tests/unit/loader/m3u8-parser.ts +++ b/tests/unit/loader/m3u8-parser.ts @@ -19,11 +19,11 @@ describe('M3U8Parser', function () { ); expect(result.levels).to.deep.equal([]); expect(result.sessionData).to.equal(null); - expectPlaylistParsingError(result, 'no levels found in manifest'); + expectPlaylistParsingError(result, 'no EXTM3U delimiter'); }); it('manifest with broken syntax returns empty array', function () { - const manifest = `#EXTXSTREAMINF:PROGRAM-ID=1,BANDWIDTH=836280,CODECS="mp4a.40.2,avc1.64001f",RESOLUTION=848x360,NAME="480" + const manifest = `#EXTM3U#EXTXSTREAMINF:PROGRAM-ID=1,BANDWIDTH=836280,CODECS="mp4a.40.2,avc1.64001f",RESOLUTION=848x360,NAME="480" http://proxy-62.dailymotion.com/sec(3ae40f708f79ca9471f52b86da76a3a8)/video/107/282/158282701_mp4_h264_aac_hq.m3u8#cell=core`; const result = M3U8Parser.parseMasterPlaylist( manifest, From c4d9bf38489bf9528b910a3c3088eec6eb31bf93 Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Tue, 9 Sep 2025 07:05:15 -0700 Subject: [PATCH 46/64] Fix "Missing format identifier #EXTM3U" playlist parsing errors Fixes #7531 --- src/loader/m3u8-parser.ts | 9 +++--- src/loader/playlist-loader.ts | 55 ++++++++++++-------------------- tests/unit/loader/m3u8-parser.ts | 4 +-- 3 files changed, 27 insertions(+), 41 deletions(-) diff --git a/src/loader/m3u8-parser.ts b/src/loader/m3u8-parser.ts index 66c73d2744d..1b24c734a60 100644 --- a/src/loader/m3u8-parser.ts +++ b/src/loader/m3u8-parser.ts @@ -119,7 +119,10 @@ export default class M3U8Parser { const levelsWithKnownCodecs: LevelParsed[] = []; MASTER_PLAYLIST_REGEX.lastIndex = 0; - + if (!string.startsWith('#EXTM3U')) { + parsed.playlistParsingError = new Error('no EXTM3U delimiter'); + return parsed; + } let result: RegExpExecArray | null; while ((result = MASTER_PLAYLIST_REGEX.exec(string)) != null) { if (result[1]) { @@ -710,9 +713,7 @@ export default class M3U8Parser { } } if (!level.targetduration) { - level.playlistParsingError = new Error( - `#EXT-X-TARGETDURATION is required`, - ); + level.playlistParsingError = new Error(`Missing Target Duration`); } const fragmentLength = fragments.length; const firstFragment = fragments[0]; diff --git a/src/loader/playlist-loader.ts b/src/loader/playlist-loader.ts index 81e637cee01..70c05fcca33 100644 --- a/src/loader/playlist-loader.ts +++ b/src/loader/playlist-loader.ts @@ -344,18 +344,6 @@ class PlaylistLoader implements NetworkComponentAPI { const string = response.data as string; - // Validate if it is an M3U8 at all - if (string.indexOf('#EXTM3U') !== 0) { - this.handleManifestParsingError( - response, - context, - new Error('no EXTM3U delimiter'), - networkDetails || null, - stats, - ); - return; - } - stats.parsing.start = performance.now(); if ( M3U8Parser.isMediaPlaylist(string) || @@ -730,29 +718,6 @@ class PlaylistLoader implements NetworkComponentAPI { typeof context.level === 'number' && parent === PlaylistLevelType.MAIN ? (level as number) : undefined; - if (!levelDetails.fragments.length) { - const error = (levelDetails.playlistParsingError = new Error( - 'No Segments found in Playlist', - )); - hls.trigger(Events.ERROR, { - type: ErrorTypes.NETWORK_ERROR, - details: ErrorDetails.LEVEL_EMPTY_ERROR, - fatal: false, - url, - error, - reason: error.message, - response, - context, - level: levelIndex, - parent, - networkDetails, - stats, - }); - return; - } - if (!levelDetails.targetduration) { - levelDetails.playlistParsingError = new Error('Missing Target Duration'); - } const error = levelDetails.playlistParsingError; if (error) { this.hls.logger.warn(`${error} ${levelDetails.url}`); @@ -775,6 +740,26 @@ class PlaylistLoader implements NetworkComponentAPI { } levelDetails.playlistParsingError = null; } + if (!levelDetails.fragments.length) { + const error = (levelDetails.playlistParsingError = new Error( + 'No Segments found in Playlist', + )); + hls.trigger(Events.ERROR, { + type: ErrorTypes.NETWORK_ERROR, + details: ErrorDetails.LEVEL_EMPTY_ERROR, + fatal: false, + url, + error, + reason: error.message, + response, + context, + level: levelIndex, + parent, + networkDetails, + stats, + }); + return; + } if (levelDetails.live && loader) { if (loader.getCacheAge) { diff --git a/tests/unit/loader/m3u8-parser.ts b/tests/unit/loader/m3u8-parser.ts index 1549f308ce7..ed5249d7e85 100644 --- a/tests/unit/loader/m3u8-parser.ts +++ b/tests/unit/loader/m3u8-parser.ts @@ -19,11 +19,11 @@ describe('M3U8Parser', function () { ); expect(result.levels).to.deep.equal([]); expect(result.sessionData).to.equal(null); - expectPlaylistParsingError(result, 'no levels found in manifest'); + expectPlaylistParsingError(result, 'no EXTM3U delimiter'); }); it('manifest with broken syntax returns empty array', function () { - const manifest = `#EXTXSTREAMINF:PROGRAM-ID=1,BANDWIDTH=836280,CODECS="mp4a.40.2,avc1.64001f",RESOLUTION=848x360,NAME="480" + const manifest = `#EXTM3U#EXTXSTREAMINF:PROGRAM-ID=1,BANDWIDTH=836280,CODECS="mp4a.40.2,avc1.64001f",RESOLUTION=848x360,NAME="480" http://proxy-62.dailymotion.com/sec(3ae40f708f79ca9471f52b86da76a3a8)/video/107/282/158282701_mp4_h264_aac_hq.m3u8#cell=core`; const result = M3U8Parser.parseMasterPlaylist( manifest, From f525f4783ed714bb2eddd1d3ca7e188b5f5601f4 Mon Sep 17 00:00:00 2001 From: Tom Jenkinson Date: Thu, 18 Sep 2025 17:58:10 +0100 Subject: [PATCH 47/64] Use GitHub `release` env for npm publish (#7544) * Use GitHub `release` env for npm publish As this is used for npm OIDC. Also stops passing the token given OIDC doesn't need it * Do not write token to config --- .github/workflows/build.yml | 4 +++- scripts/publish-npm.sh | 3 --- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fba3c5ce9de..ba5fa5561e8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -279,6 +279,9 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} release_npm: + # npm oidc is configured for this environment + # https://docs.npmjs.com/trusted-publishers#for-github-actions + environment: release needs: [config, test_unit] if: needs.config.outputs.tag || needs.config.outputs.isMainBranch == 'true' runs-on: ubuntu-latest @@ -323,7 +326,6 @@ jobs: ./scripts/publish-npm.sh env: CI: true - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} TAG: ${{ needs.config.outputs.tag }} test_functional_required: diff --git a/scripts/publish-npm.sh b/scripts/publish-npm.sh index fd95fb16c46..6f1e3cb06cd 100755 --- a/scripts/publish-npm.sh +++ b/scripts/publish-npm.sh @@ -2,9 +2,6 @@ set -e if [[ $(node ./scripts/check-already-published.js) = "not published" ]]; then - # write the token to config - # see https://docs.npmjs.com/private-modules/ci-server-config - echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" >> .npmrc if [[ -z "$TAG" ]]; then npm publish --provenance --tag canary echo "Published canary." From 58205bd671beb84c9175a002b8cf1e9c94745be0 Mon Sep 17 00:00:00 2001 From: Tom Jenkinson Date: Thu, 18 Sep 2025 18:14:48 +0100 Subject: [PATCH 48/64] Update npm to v11 before publish Because the OIDC flow needs v11 --- .github/workflows/build.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ba5fa5561e8..e3edb42fd63 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -321,6 +321,10 @@ jobs: env: CI: true + # Publishing with OICD needs >= 11.5.1 + - name: Update npm to v11 + run: npm install -g npm@11 + - name: publish to npm run: | ./scripts/publish-npm.sh From f3c5805e22fb58ab8d562cbaee59d96343faef39 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 18 Sep 2025 17:16:04 +0000 Subject: [PATCH 49/64] chore(deps): update dependency chromedriver to v140 --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0e54bbc16c7..051b1b857c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,7 @@ "babel-plugin-transform-remove-console": "6.9.4", "chai": "4.5.0", "chart.js": "2.9.4", - "chromedriver": "139.0.3", + "chromedriver": "140.0.1", "doctoc": "2.2.1", "es-check": "9.3.1", "eslint": "8.57.1", @@ -5479,9 +5479,9 @@ } }, "node_modules/chromedriver": { - "version": "139.0.3", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-139.0.3.tgz", - "integrity": "sha512-NrSqRL2QWXsGk1/EXk5xf9q07mEUMsIA7szr9nxSOzENSdFOi+ZvEYq4H8P3tqQL61EKS0tS9m9TnVCJoQHn2Q==", + "version": "140.0.1", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-140.0.1.tgz", + "integrity": "sha512-YFEKOaqxUMFTJkAJGj06/VizWegsokIYxgHqrs1h+/acFPCB9k0Pnnai4N0+eySf/ce04UG++xH1bnGw2U/71g==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -18357,9 +18357,9 @@ "peer": true }, "chromedriver": { - "version": "139.0.3", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-139.0.3.tgz", - "integrity": "sha512-NrSqRL2QWXsGk1/EXk5xf9q07mEUMsIA7szr9nxSOzENSdFOi+ZvEYq4H8P3tqQL61EKS0tS9m9TnVCJoQHn2Q==", + "version": "140.0.1", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-140.0.1.tgz", + "integrity": "sha512-YFEKOaqxUMFTJkAJGj06/VizWegsokIYxgHqrs1h+/acFPCB9k0Pnnai4N0+eySf/ce04UG++xH1bnGw2U/71g==", "dev": true, "requires": { "@testim/chrome-version": "^1.1.4", diff --git a/package.json b/package.json index 9c5082902c5..b5719c1d50d 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "babel-plugin-transform-remove-console": "6.9.4", "chai": "4.5.0", "chart.js": "2.9.4", - "chromedriver": "139.0.3", + "chromedriver": "140.0.1", "doctoc": "2.2.1", "es-check": "9.3.1", "eslint": "8.57.1", From e49270c4c0ab15bb920ce27bae7914296b63e83e Mon Sep 17 00:00:00 2001 From: Tom Jenkinson Date: Thu, 18 Sep 2025 17:58:10 +0100 Subject: [PATCH 50/64] Use GitHub `release` env for npm publish (#7544) * Use GitHub `release` env for npm publish As this is used for npm OIDC. Also stops passing the token given OIDC doesn't need it * Do not write token to config --- .github/workflows/build.yml | 4 +++- scripts/publish-npm.sh | 3 --- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c1e00f4a70f..a0479a2c121 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -279,6 +279,9 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} release_npm: + # npm oidc is configured for this environment + # https://docs.npmjs.com/trusted-publishers#for-github-actions + environment: release needs: [config, test_unit] if: needs.config.outputs.tag || needs.config.outputs.isMainBranch == 'true' runs-on: ubuntu-latest @@ -323,7 +326,6 @@ jobs: ./scripts/publish-npm.sh env: CI: true - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} TAG: ${{ needs.config.outputs.tag }} test_functional_required: diff --git a/scripts/publish-npm.sh b/scripts/publish-npm.sh index fd95fb16c46..6f1e3cb06cd 100755 --- a/scripts/publish-npm.sh +++ b/scripts/publish-npm.sh @@ -2,9 +2,6 @@ set -e if [[ $(node ./scripts/check-already-published.js) = "not published" ]]; then - # write the token to config - # see https://docs.npmjs.com/private-modules/ci-server-config - echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" >> .npmrc if [[ -z "$TAG" ]]; then npm publish --provenance --tag canary echo "Published canary." From 785c0a510583d2fd0ef4d5c38cf881e11c3f8d9b Mon Sep 17 00:00:00 2001 From: Tom Jenkinson Date: Thu, 18 Sep 2025 18:14:48 +0100 Subject: [PATCH 51/64] Update npm to v11 before publish Because the OIDC flow needs v11 --- .github/workflows/build.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a0479a2c121..79261a5bafd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -321,6 +321,10 @@ jobs: env: CI: true + # Publishing with OICD needs >= 11.5.1 + - name: Update npm to v11 + run: npm install -g npm@11 + - name: publish to npm run: | ./scripts/publish-npm.sh From 6f91d50a7e97b1631ceca8750c4e0e36cbb87284 Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Thu, 18 Sep 2025 10:25:44 -0700 Subject: [PATCH 52/64] Use Widevine KEYID or parse Playready when level keys are present Update keyUriToKeyIdMap set after KEY_LOADING Fixes #7541 #7542 --- api-extractor/report/hls.js.api.md | 4 +- src/loader/fragment.ts | 9 ++-- src/loader/level-key.ts | 67 +++++++++++++++++++++++++----- tests/unit/loader/m3u8-parser.ts | 55 ++++++++++++++++++++++++ 4 files changed, 119 insertions(+), 16 deletions(-) diff --git a/api-extractor/report/hls.js.api.md b/api-extractor/report/hls.js.api.md index c88337b5f27..d57e381bedc 100644 --- a/api-extractor/report/hls.js.api.md +++ b/api-extractor/report/hls.js.api.md @@ -3359,7 +3359,9 @@ export class LevelKey implements DecryptData { // (undocumented) readonly encrypted: boolean; // (undocumented) - getDecryptData(sn: number | 'initSegment'): LevelKey | null; + getDecryptData(sn: number | 'initSegment', levelKeys?: { + [key: string]: LevelKey | undefined; + }): LevelKey | null; // (undocumented) readonly isCommonEncryption: boolean; // (undocumented) diff --git a/src/loader/fragment.ts b/src/loader/fragment.ts index 97e5e08cd95..6cc21fffeda 100644 --- a/src/loader/fragment.ts +++ b/src/loader/fragment.ts @@ -273,7 +273,7 @@ export class Fragment extends BaseSegment { const levelKey = (this._decryptdata = this.levelkeys[keyFormats[0]] || null); if (levelKey) { - return levelKey.getDecryptData(this.sn); + return levelKey.getDecryptData(this.sn, this.levelkeys); } } else { // Multiple keys. key-loader to call Fragment.setKeyFormat based on selected key-system. @@ -364,10 +364,11 @@ export class Fragment extends BaseSegment { } setKeyFormat(keyFormat: KeySystemFormats) { - if (this.levelkeys) { - const key = this.levelkeys[keyFormat]; + const levelkeys = this.levelkeys; + if (levelkeys) { + const key = levelkeys[keyFormat]; if (key && !this._decryptdata) { - this._decryptdata = key.getDecryptData(this.sn); + this._decryptdata = key.getDecryptData(this.sn, this.levelkeys); } } } diff --git a/src/loader/level-key.ts b/src/loader/level-key.ts index 2c6a8d076cb..c67e9873d4a 100644 --- a/src/loader/level-key.ts +++ b/src/loader/level-key.ts @@ -100,7 +100,10 @@ export class LevelKey implements DecryptData { return false; } - public getDecryptData(sn: number | 'initSegment'): LevelKey | null { + public getDecryptData( + sn: number | 'initSegment', + levelKeys?: { [key: string]: LevelKey | undefined }, + ): LevelKey | null { if (!this.encrypted || !this.uri) { return null; } @@ -135,10 +138,19 @@ export class LevelKey implements DecryptData { return this; } - if (this.pssh && this.keyId) { - return this; + if (this.keyId) { + // Handle case where key id is changed in KEY_LOADING event handler #7542#issuecomment-3305203929 + const assignedKeyId = keyUriToKeyIdMap[this.uri]; + if (assignedKeyId && !arrayValuesMatch(this.keyId, assignedKeyId)) { + LevelKey.setKeyIdForUri(this.uri, this.keyId); + } + + if (this.pssh) { + return this; + } } + // Key bytes are signalled the KEYID attribute, typically only found on WideVine KEY tags // Initialize keyId if possible const keyBytes = convertDataUriToArrayBytes(this.uri); if (keyBytes) { @@ -156,8 +168,11 @@ export class LevelKey implements DecryptData { } } if (!this.keyId) { - const offset = keyBytes.length - 22; - this.keyId = keyBytes.subarray(offset, offset + 16); + this.keyId = getKeyIdFromPlayReadyKey(levelKeys); + if (!this.keyId) { + const offset = keyBytes.length - 22; + this.keyId = keyBytes.subarray(offset, offset + 16); + } } break; case KeySystemFormats.PLAYREADY: { @@ -189,13 +204,20 @@ export class LevelKey implements DecryptData { // Default behavior: assign a new keyId for each uri if (!this.keyId || this.keyId.byteLength !== 16) { - let keyId = keyUriToKeyIdMap[this.uri]; + let keyId: Uint8Array | null | undefined = + keyUriToKeyIdMap[this.uri]; if (!keyId) { - const val = - Object.keys(keyUriToKeyIdMap).length % Number.MAX_SAFE_INTEGER; - keyId = new Uint8Array(16); - const dv = new DataView(keyId.buffer, 12, 4); // Just set the last 4 bytes - dv.setUint32(0, val); + keyId = getKeyIdFromWidevineKey(levelKeys); + if (!keyId) { + keyId = getKeyIdFromPlayReadyKey(levelKeys); + if (!keyId) { + const val = + Object.keys(keyUriToKeyIdMap).length % Number.MAX_SAFE_INTEGER; + keyId = new Uint8Array(16); + const dv = new DataView(keyId.buffer, 12, 4); // Just set the last 4 bytes + dv.setUint32(0, val); + } + } LevelKey.setKeyIdForUri(this.uri, keyId); } this.keyId = keyId; @@ -205,6 +227,29 @@ export class LevelKey implements DecryptData { } } +function getKeyIdFromWidevineKey( + levelKeys: { [key: string]: LevelKey | undefined } | undefined, +) { + const widevineKey = levelKeys?.[KeySystemFormats.WIDEVINE]; + if (widevineKey) { + return widevineKey.keyId; + } + return null; +} + +function getKeyIdFromPlayReadyKey( + levelKeys: { [key: string]: LevelKey | undefined } | undefined, +) { + const playReadyKey = levelKeys?.[KeySystemFormats.PLAYREADY]; + if (playReadyKey) { + const playReadyKeyBytes = convertDataUriToArrayBytes(playReadyKey.uri); + if (playReadyKeyBytes) { + return parsePlayReadyWRM(playReadyKeyBytes); + } + } + return null; +} + function createInitializationVector(segmentNumber: number) { const uint8View = new Uint8Array(16); for (let i = 12; i < 16; i++) { diff --git a/tests/unit/loader/m3u8-parser.ts b/tests/unit/loader/m3u8-parser.ts index ed5249d7e85..6570cf593fb 100644 --- a/tests/unit/loader/m3u8-parser.ts +++ b/tests/unit/loader/m3u8-parser.ts @@ -4,6 +4,8 @@ import { LoadStats } from '../../../src/loader/load-stats'; import M3U8Parser from '../../../src/loader/m3u8-parser'; import { PlaylistLevelType } from '../../../src/types/loader'; import { AttrList } from '../../../src/utils/attr-list'; +import { arrayToHex } from '../../../src/utils/hex'; +import { KeySystemFormats } from '../../../src/utils/mediakeys-helper'; import type { Fragment, Part } from '../../../src/loader/fragment'; import type { LevelKey } from '../../../src/loader/level-key'; @@ -2414,6 +2416,59 @@ media_1638278.m4s`; .which.has.members([result.fragments[2], result.fragments[6]]); }); + it('parse KEYID other keys when available', function () { + const level = `#EXTM3U +#EXT-X-VERSION:6 +#EXT-X-TARGETDURATION:6 +#EXT-X-KEY:METHOD=SAMPLE-AES,URI="skd://any",KEYFORMAT="com.apple.streamingkeydelivery",KEYFORMATVERSIONS="1" +#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,AAAA",KEYID=0x50906c6be9179460c3d1a59f16ee9804,KEYFORMAT="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed",KEYFORMATVERSIONS="1" +#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;charset=UTF-16;base64,UAIA",KEYFORMATVERSIONS="1",KEYFORMAT="com.microsoft.playready" +#EXT-X-MAP:URI="init.mp4" +#EXTINF:5.0, +1.mp4`; + const result = M3U8Parser.parseLevelPlaylist( + level, + 'http://foo.com/adaptive/test.m3u8', + 0, + PlaylistLevelType.MAIN, + 0, + null, + ); + expect(result.playlistParsingError).to.be.null; + expect(result.fragments.length).to.equal(1); + expect(result.fragments[0].levelkeys, 'first segment has three keys') + .to.be.an('object') + .with.keys([ + 'com.apple.streamingkeydelivery', + 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed', + 'com.microsoft.playready', + ]); + const fragment = result.fragments[0]; + expect(result) + .to.have.property('encryptedFragments') + .which.is.an('array') + .which.has.members([fragment]); + expect( + fragment.decryptdata, + 'decryptdata should by null until format is selected', + ).to.be.null; + fragment.setKeyFormat(KeySystemFormats.FAIRPLAY); + expect(fragment.decryptdata) + .to.include({ + uri: 'skd://any', + method: 'SAMPLE-AES', + keyFormat: 'com.apple.streamingkeydelivery', + encrypted: true, + isCommonEncryption: true, + pssh: null, + }) + .and.have.property('keyId') + .which.is.a('Uint8Array'); + expect(arrayToHex(fragment.decryptdata!.keyId!)).to.equal( + '50906c6be9179460c3d1a59f16ee9804', + ); + }); + it('parses manifest with EXT-X-SESSION-KEYs', function () { const manifest = `#EXTM3U #EXT-X-SESSION-DATA:DATA-ID="key",VALUE="value" From d819b457973e591227e84a7a5e425c4404760928 Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Thu, 11 Sep 2025 16:21:36 -0700 Subject: [PATCH 53/64] Roll initPTS forward when needed by earlier segments Fixes #7536 --- src/remux/mp4-remuxer.ts | 74 ++++++++++++++++++++++++++----- src/utils/timescale-conversion.ts | 5 +++ 2 files changed, 69 insertions(+), 10 deletions(-) diff --git a/src/remux/mp4-remuxer.ts b/src/remux/mp4-remuxer.ts index 9bd59545f98..de70558cce2 100644 --- a/src/remux/mp4-remuxer.ts +++ b/src/remux/mp4-remuxer.ts @@ -4,7 +4,10 @@ import { ErrorDetails, ErrorTypes } from '../errors'; import { Events } from '../events'; import { PlaylistLevelType } from '../types/loader'; import { type ILogger, Logger } from '../utils/logger'; -import { toMsFromMpegTsClock } from '../utils/timescale-conversion'; +import { + timestampToString, + toMsFromMpegTsClock, +} from '../utils/timescale-conversion'; import type { HlsConfig } from '../config'; import type { HlsEventEmitter } from '../events'; import type { SourceBufferName } from '../types/buffer'; @@ -107,7 +110,19 @@ export default class MP4Remuxer extends Logger implements Remuxer { } resetTimeStamp(defaultTimeStamp: TimestampOffset | null) { - this.log('initPTS & initDTS reset'); + const initPTS = this._initPTS; + if ( + !initPTS || + !defaultTimeStamp || + defaultTimeStamp.trackId !== initPTS.trackId || + defaultTimeStamp.baseTime !== initPTS.baseTime || + defaultTimeStamp.timescale !== initPTS.timescale + ) { + this.log( + `Reset initPTS: ${initPTS ? timestampToString(initPTS) : initPTS} > ${defaultTimeStamp ? timestampToString(defaultTimeStamp) : defaultTimeStamp}`, + ); + } + this._initPTS = this._initDTS = defaultTimeStamp; } @@ -334,6 +349,25 @@ export default class MP4Remuxer extends Logger implements Remuxer { }; } + computeInitPts( + basetime: number, + timescale: number, + presentationTime: number, + type: 'audio' | 'video', + ): number { + const offset = Math.round(presentationTime * timescale); + let timestamp = normalizePts(basetime, offset); + if (timestamp < offset + timescale) { + this.log( + `Adjusting PTS for rollover in timeline near ${(offset - timestamp) / timescale} ${type}`, + ); + while (timestamp < offset + timescale) { + timestamp += 8589934592; + } + } + return timestamp - offset; + } + generateIS( audioTrack: DemuxedAudioTrack, videoTrack: DemuxedVideoTrack, @@ -395,8 +429,12 @@ export default class MP4Remuxer extends Logger implements Remuxer { timescale = audioTrack.inputTimeScale; if (!_initPTS || timescale !== _initPTS.timescale) { // remember first PTS of this demuxing context. for audio, PTS = DTS - initPTS = initDTS = - audioSamples[0].pts - Math.round(timescale * timeOffset); + initPTS = initDTS = this.computeInitPts( + audioSamples[0].pts, + timescale, + timeOffset, + 'audio', + ); } else { computePTSDTS = false; } @@ -421,13 +459,22 @@ export default class MP4Remuxer extends Logger implements Remuxer { trackId = videoTrack.id; timescale = videoTrack.inputTimeScale; if (!_initPTS || timescale !== _initPTS.timescale) { - const startPTS = this.getVideoStartPts(videoSamples); - const startOffset = Math.round(timescale * timeOffset); - initDTS = Math.min( - initDTS as number, - normalizePts(videoSamples[0].dts, startPTS) - startOffset, + const basePTS = this.getVideoStartPts(videoSamples); + const baseDTS = normalizePts(videoSamples[0].dts, basePTS); + const videoInitDTS = this.computeInitPts( + baseDTS, + timescale, + timeOffset, + 'video', + ); + const videoInitPTS = this.computeInitPts( + basePTS, + timescale, + timeOffset, + 'video', ); - initPTS = Math.min(initPTS as number, startPTS - startOffset); + initDTS = Math.min(initDTS as number, videoInitDTS); + initPTS = Math.min(initPTS as number, videoInitPTS); } else { computePTSDTS = false; } @@ -897,11 +944,18 @@ export default class MP4Remuxer extends Logger implements Remuxer { }); if (!contiguous || nextAudioTs < 0) { + const sampleCount = inputSamples.length; // filter out sample with negative PTS that are not playable anyway // if we don't remove these negative samples, they will shift all audio samples forward. // leading to audio overlap between current / next fragment inputSamples = inputSamples.filter((sample) => sample.pts >= 0); + if (sampleCount !== inputSamples.length) { + this.warn( + `Removed ${inputSamples.length - sampleCount} of ${sampleCount} samples (initPTS ${initTime} / ${inputTimeScale})`, + ); + } + // in case all samples have negative PTS, and have been filtered out, return now if (!inputSamples.length) { return; diff --git a/src/utils/timescale-conversion.ts b/src/utils/timescale-conversion.ts index c2762a6ba32..90c5c5b459d 100644 --- a/src/utils/timescale-conversion.ts +++ b/src/utils/timescale-conversion.ts @@ -39,3 +39,8 @@ export function toMpegTsClockFromTimescale( ): number { return toTimescaleFromBase(baseTime, MPEG_TS_CLOCK_FREQ_HZ, 1 / srcScale); } + +export function timestampToString(timestamp: TimestampOffset): string { + const { baseTime, timescale, trackId } = timestamp; + return `${baseTime / timescale} (${baseTime}/${timescale}) trackId: ${trackId}`; +} From b282f03105a40de28cb79d4561fd46dbc222cb7a Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Thu, 11 Sep 2025 16:21:36 -0700 Subject: [PATCH 54/64] Roll initPTS forward when needed by earlier segments Fixes #7536 --- src/remux/mp4-remuxer.ts | 74 ++++++++++++++++++++++++++----- src/utils/timescale-conversion.ts | 5 +++ 2 files changed, 69 insertions(+), 10 deletions(-) diff --git a/src/remux/mp4-remuxer.ts b/src/remux/mp4-remuxer.ts index 9bd59545f98..de70558cce2 100644 --- a/src/remux/mp4-remuxer.ts +++ b/src/remux/mp4-remuxer.ts @@ -4,7 +4,10 @@ import { ErrorDetails, ErrorTypes } from '../errors'; import { Events } from '../events'; import { PlaylistLevelType } from '../types/loader'; import { type ILogger, Logger } from '../utils/logger'; -import { toMsFromMpegTsClock } from '../utils/timescale-conversion'; +import { + timestampToString, + toMsFromMpegTsClock, +} from '../utils/timescale-conversion'; import type { HlsConfig } from '../config'; import type { HlsEventEmitter } from '../events'; import type { SourceBufferName } from '../types/buffer'; @@ -107,7 +110,19 @@ export default class MP4Remuxer extends Logger implements Remuxer { } resetTimeStamp(defaultTimeStamp: TimestampOffset | null) { - this.log('initPTS & initDTS reset'); + const initPTS = this._initPTS; + if ( + !initPTS || + !defaultTimeStamp || + defaultTimeStamp.trackId !== initPTS.trackId || + defaultTimeStamp.baseTime !== initPTS.baseTime || + defaultTimeStamp.timescale !== initPTS.timescale + ) { + this.log( + `Reset initPTS: ${initPTS ? timestampToString(initPTS) : initPTS} > ${defaultTimeStamp ? timestampToString(defaultTimeStamp) : defaultTimeStamp}`, + ); + } + this._initPTS = this._initDTS = defaultTimeStamp; } @@ -334,6 +349,25 @@ export default class MP4Remuxer extends Logger implements Remuxer { }; } + computeInitPts( + basetime: number, + timescale: number, + presentationTime: number, + type: 'audio' | 'video', + ): number { + const offset = Math.round(presentationTime * timescale); + let timestamp = normalizePts(basetime, offset); + if (timestamp < offset + timescale) { + this.log( + `Adjusting PTS for rollover in timeline near ${(offset - timestamp) / timescale} ${type}`, + ); + while (timestamp < offset + timescale) { + timestamp += 8589934592; + } + } + return timestamp - offset; + } + generateIS( audioTrack: DemuxedAudioTrack, videoTrack: DemuxedVideoTrack, @@ -395,8 +429,12 @@ export default class MP4Remuxer extends Logger implements Remuxer { timescale = audioTrack.inputTimeScale; if (!_initPTS || timescale !== _initPTS.timescale) { // remember first PTS of this demuxing context. for audio, PTS = DTS - initPTS = initDTS = - audioSamples[0].pts - Math.round(timescale * timeOffset); + initPTS = initDTS = this.computeInitPts( + audioSamples[0].pts, + timescale, + timeOffset, + 'audio', + ); } else { computePTSDTS = false; } @@ -421,13 +459,22 @@ export default class MP4Remuxer extends Logger implements Remuxer { trackId = videoTrack.id; timescale = videoTrack.inputTimeScale; if (!_initPTS || timescale !== _initPTS.timescale) { - const startPTS = this.getVideoStartPts(videoSamples); - const startOffset = Math.round(timescale * timeOffset); - initDTS = Math.min( - initDTS as number, - normalizePts(videoSamples[0].dts, startPTS) - startOffset, + const basePTS = this.getVideoStartPts(videoSamples); + const baseDTS = normalizePts(videoSamples[0].dts, basePTS); + const videoInitDTS = this.computeInitPts( + baseDTS, + timescale, + timeOffset, + 'video', + ); + const videoInitPTS = this.computeInitPts( + basePTS, + timescale, + timeOffset, + 'video', ); - initPTS = Math.min(initPTS as number, startPTS - startOffset); + initDTS = Math.min(initDTS as number, videoInitDTS); + initPTS = Math.min(initPTS as number, videoInitPTS); } else { computePTSDTS = false; } @@ -897,11 +944,18 @@ export default class MP4Remuxer extends Logger implements Remuxer { }); if (!contiguous || nextAudioTs < 0) { + const sampleCount = inputSamples.length; // filter out sample with negative PTS that are not playable anyway // if we don't remove these negative samples, they will shift all audio samples forward. // leading to audio overlap between current / next fragment inputSamples = inputSamples.filter((sample) => sample.pts >= 0); + if (sampleCount !== inputSamples.length) { + this.warn( + `Removed ${inputSamples.length - sampleCount} of ${sampleCount} samples (initPTS ${initTime} / ${inputTimeScale})`, + ); + } + // in case all samples have negative PTS, and have been filtered out, return now if (!inputSamples.length) { return; diff --git a/src/utils/timescale-conversion.ts b/src/utils/timescale-conversion.ts index c2762a6ba32..90c5c5b459d 100644 --- a/src/utils/timescale-conversion.ts +++ b/src/utils/timescale-conversion.ts @@ -39,3 +39,8 @@ export function toMpegTsClockFromTimescale( ): number { return toTimescaleFromBase(baseTime, MPEG_TS_CLOCK_FREQ_HZ, 1 / srcScale); } + +export function timestampToString(timestamp: TimestampOffset): string { + const { baseTime, timescale, trackId } = timestamp; + return `${baseTime / timescale} (${baseTime}/${timescale}) trackId: ${trackId}`; +} From abb6945bd29eadc04c25b2522528ca6aa619992f Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Thu, 18 Sep 2025 20:16:38 -0700 Subject: [PATCH 55/64] Get KEYID from init segment 'tenc' when not found elsewhere Fixes #7541 --- src/controller/eme-controller.ts | 2 +- src/loader/fragment.ts | 33 ++++++----- src/loader/key-loader.ts | 16 +++++- src/loader/level-key.ts | 25 +++----- src/utils/mp4-tools.ts | 97 ++++++++++++++++++++------------ 5 files changed, 101 insertions(+), 72 deletions(-) diff --git a/src/controller/eme-controller.ts b/src/controller/eme-controller.ts index a4ea86f8854..a2ff484bd50 100644 --- a/src/controller/eme-controller.ts +++ b/src/controller/eme-controller.ts @@ -877,7 +877,7 @@ class EMEController extends Logger implements ComponentAPI { handleKeyStatus(keyStatus, context); } else { // Timeout key-status - const timeout = 0; + const timeout = 1000; context.keyStatusTimeouts ||= {}; context.keyStatusTimeouts[keyId] ||= self.setTimeout(() => { if ((!context.mediaKeysSession as any) || !this.mediaKeys) { diff --git a/src/loader/fragment.ts b/src/loader/fragment.ts index 6cc21fffeda..621647eb563 100644 --- a/src/loader/fragment.ts +++ b/src/loader/fragment.ts @@ -259,25 +259,24 @@ export class Fragment extends BaseSegment { get decryptdata(): LevelKey | null { const { levelkeys } = this; - if (!levelkeys && !this._decryptdata) { + + if (!levelkeys || levelkeys.NONE) { return null; } - if (!this._decryptdata && this.levelkeys && !this.levelkeys.NONE) { - const key = this.levelkeys.identity; - if (key) { - this._decryptdata = key.getDecryptData(this.sn); - } else { - const keyFormats = Object.keys(this.levelkeys); - if (keyFormats.length === 1) { - const levelKey = (this._decryptdata = - this.levelkeys[keyFormats[0]] || null); - if (levelKey) { - return levelKey.getDecryptData(this.sn, this.levelkeys); - } - } else { - // Multiple keys. key-loader to call Fragment.setKeyFormat based on selected key-system. + if (levelkeys.identity) { + if (!this._decryptdata) { + this._decryptdata = levelkeys.identity.getDecryptData(this.sn); + } + } else if (!this._decryptdata?.keyId) { + const keyFormats = Object.keys(levelkeys); + if (keyFormats.length === 1) { + const levelKey = (this._decryptdata = levelkeys[keyFormats[0]] || null); + if (levelKey) { + this._decryptdata = levelKey.getDecryptData(this.sn, levelkeys); } + } else { + // Multiple keys. key-loader to call Fragment.setKeyFormat based on selected key-system. } } @@ -367,8 +366,8 @@ export class Fragment extends BaseSegment { const levelkeys = this.levelkeys; if (levelkeys) { const key = levelkeys[keyFormat]; - if (key && !this._decryptdata) { - this._decryptdata = key.getDecryptData(this.sn, this.levelkeys); + if (key && !this._decryptdata?.keyId) { + this._decryptdata = key.getDecryptData(this.sn, levelkeys); } } } diff --git a/src/loader/key-loader.ts b/src/loader/key-loader.ts index 27c85dc5daa..70d70ed0855 100644 --- a/src/loader/key-loader.ts +++ b/src/loader/key-loader.ts @@ -1,4 +1,5 @@ import { LoadError } from './fragment-loader'; +import { LevelKey } from './level-key'; import { ErrorDetails, ErrorTypes } from '../errors'; import { type Fragment, isMediaFragment } from '../loader/fragment'; import { arrayToHex } from '../utils/hex'; @@ -8,7 +9,7 @@ import { keySystemFormatToKeySystemDomain, } from '../utils/mediakeys-helper'; import { KeySystemFormats } from '../utils/mediakeys-helper'; -import type { LevelKey } from './level-key'; +import { parseKeyIdsFromTenc } from '../utils/mp4-tools'; import type { HlsConfig } from '../config'; import type EMEController from '../controller/eme-controller'; import type { @@ -258,6 +259,19 @@ export default class KeyLoader extends Logger implements ComponentAPI { loadKeyEME(keyInfo: KeyLoaderInfo, frag: Fragment): Promise { const keyLoadedData: KeyLoadedData = { frag, keyInfo }; if (this.emeController && this.config.emeEnabled) { + if (!keyInfo.decryptdata.keyId && frag.initSegment?.data) { + const keyIds = parseKeyIdsFromTenc( + frag.initSegment.data as Uint8Array, + ); + if (keyIds.length) { + const keyId = keyIds[0]; + if (keyId.some((b) => b !== 0)) { + this.log(`Using keyId found in init segment ${arrayToHex(keyId)}`); + keyInfo.decryptdata.keyId = keyId; + LevelKey.setKeyIdForUri(keyInfo.decryptdata.uri, keyId); + } + } + } const keySessionContextPromise = this.emeController.loadKey(keyLoadedData); return (keyInfo.keyLoadPromise = keySessionContextPromise.then( diff --git a/src/loader/level-key.ts b/src/loader/level-key.ts index c67e9873d4a..31a7c077d3d 100644 --- a/src/loader/level-key.ts +++ b/src/loader/level-key.ts @@ -169,10 +169,6 @@ export class LevelKey implements DecryptData { } if (!this.keyId) { this.keyId = getKeyIdFromPlayReadyKey(levelKeys); - if (!this.keyId) { - const offset = keyBytes.length - 22; - this.keyId = keyBytes.subarray(offset, offset + 16); - } } break; case KeySystemFormats.PLAYREADY: { @@ -202,25 +198,20 @@ export class LevelKey implements DecryptData { } } - // Default behavior: assign a new keyId for each uri + // Default behavior: get keyId from other KEY tag or URI lookup if (!this.keyId || this.keyId.byteLength !== 16) { - let keyId: Uint8Array | null | undefined = - keyUriToKeyIdMap[this.uri]; + let keyId: Uint8Array | null | undefined; + keyId = getKeyIdFromWidevineKey(levelKeys); if (!keyId) { - keyId = getKeyIdFromWidevineKey(levelKeys); + keyId = getKeyIdFromPlayReadyKey(levelKeys); if (!keyId) { - keyId = getKeyIdFromPlayReadyKey(levelKeys); - if (!keyId) { - const val = - Object.keys(keyUriToKeyIdMap).length % Number.MAX_SAFE_INTEGER; - keyId = new Uint8Array(16); - const dv = new DataView(keyId.buffer, 12, 4); // Just set the last 4 bytes - dv.setUint32(0, val); - } + keyId = keyUriToKeyIdMap[this.uri]; } + } + if (keyId) { + this.keyId = keyId; LevelKey.setKeyIdForUri(this.uri, keyId); } - this.keyId = keyId; } return this; diff --git a/src/utils/mp4-tools.ts b/src/utils/mp4-tools.ts index be093141986..4d6d836db2f 100644 --- a/src/utils/mp4-tools.ts +++ b/src/utils/mp4-tools.ts @@ -7,6 +7,8 @@ import type { DecryptData } from '../loader/level-key'; import type { PassthroughTrack, UserdataSample } from '../types/demuxer'; import type { ILogger } from '../utils/logger'; +type BoxDataOrUndefined = Uint8Array | undefined; + const UINT32_MAX = Math.pow(2, 32) - 1; const push = [].push; @@ -573,51 +575,74 @@ export function patchEncyptionData( } const keyId = decryptdata.keyId; if (keyId && decryptdata.isCommonEncryption) { - const traks = findBox(initSegment, ['moov', 'trak']); - traks.forEach((trak) => { - const stsd = findBox(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0]; - - // skip the sample entry count - const sampleEntries = stsd.subarray(8); - let encBoxes = findBox(sampleEntries, ['enca']); - const isAudio = encBoxes.length > 0; - if (!isAudio) { - encBoxes = findBox(sampleEntries, ['encv']); + applyToTencBoxes(initSegment, (tenc, isAudio) => { + // Look for default key id (keyID offset is always 8 within the tenc box): + const tencKeyId = tenc.subarray(8, 24); + if (!tencKeyId.some((b) => b !== 0)) { + logger.log( + `[eme] Patching keyId in 'enc${ + isAudio ? 'a' : 'v' + }>sinf>>tenc' box: ${arrayToHex(tencKeyId)} -> ${arrayToHex(keyId)}`, + ); + tenc.set(keyId, 8); } - encBoxes.forEach((enc) => { - const encBoxChildren = isAudio ? enc.subarray(28) : enc.subarray(78); - const sinfBoxes = findBox(encBoxChildren, ['sinf']); - sinfBoxes.forEach((sinf) => { - const tenc = parseSinf(sinf); - if (tenc) { - // Look for default key id (keyID offset is always 8 within the tenc box): - const tencKeyId = tenc.subarray(8, 24) as Uint8Array; - if (!tencKeyId.some((b) => b !== 0)) { - logger.log( - `[eme] Patching keyId in 'enc${ - isAudio ? 'a' : 'v' - }>sinf>>tenc' box: ${arrayToHex(tencKeyId)} -> ${arrayToHex( - keyId, - )}`, - ); - tenc.set(keyId, 8); - } - } - }); - }); }); } } -export function parseSinf(sinf: Uint8Array): Uint8Array | null { - const schm = findBox(sinf, ['schm'])[0]; - if (schm as any) { +export function parseKeyIdsFromTenc( + initSegment: Uint8Array, +): Uint8Array[] { + const keyIds: Uint8Array[] = []; + applyToTencBoxes(initSegment, (tenc) => keyIds.push(tenc.subarray(8, 24))); + return keyIds; +} + +function applyToTencBoxes( + initSegment: Uint8Array, + predicate: (tenc: Uint8Array, isAudio: boolean) => void, +) { + const traks = findBox(initSegment, ['moov', 'trak']); + traks.forEach((trak) => { + const stsd = findBox(trak, [ + 'mdia', + 'minf', + 'stbl', + 'stsd', + ])[0] as BoxDataOrUndefined; + if (!stsd) return; + const sampleEntries = stsd.subarray(8); + let encBoxes = findBox(sampleEntries, ['enca']); + const isAudio = encBoxes.length > 0; + if (!isAudio) { + encBoxes = findBox(sampleEntries, ['encv']); + } + encBoxes.forEach((enc) => { + const encBoxChildren = isAudio ? enc.subarray(28) : enc.subarray(78); + const sinfBoxes = findBox(encBoxChildren, ['sinf']); + sinfBoxes.forEach((sinf) => { + const tenc = parseSinf(sinf); + if (tenc) { + predicate(tenc, isAudio); + } + }); + }); + }); +} + +export function parseSinf(sinf: Uint8Array): BoxDataOrUndefined { + const schm = findBox(sinf, ['schm'])[0] as BoxDataOrUndefined; + if (schm) { const scheme = bin2str(schm.subarray(4, 8)); if (scheme === 'cbcs' || scheme === 'cenc') { - return findBox(sinf, ['schi', 'tenc'])[0]; + const tenc = findBox(sinf, ['schi', 'tenc'])[0] as BoxDataOrUndefined; + if (tenc) { + return tenc; + } + } else if (scheme === 'cbc2') { + /* no-op */ } } - return null; } /* From ec1464933213e6dfef923a9b7d6602f9e9f2ba5c Mon Sep 17 00:00:00 2001 From: Chris Triantafilis Date: Sat, 20 Sep 2025 07:46:05 -0400 Subject: [PATCH 56/64] Update dependency sinon to v21 --- package-lock.json | 17 ++++++++--------- package.json | 3 ++- tests/unit/controller/eme-controller.ts | 3 ++- tests/unit/controller/error-controller.ts | 3 ++- tests/unit/controller/stream-controller.ts | 3 ++- 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 051b1b857c1..70647041e6a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -62,6 +62,7 @@ "markdown-styles": "3.2.0", "micromatch": "4.0.8", "mocha": "11.7.1", + "nise": "6.1.1", "node-fetch": "3.3.2", "npm-run-all2": "8.0.4", "prettier": "3.6.2", @@ -71,7 +72,7 @@ "sauce-connect-launcher": "1.3.2", "selenium-webdriver": "4.35.0", "semver": "7.7.2", - "sinon": "19.0.5", + "sinon": "21.0.0", "sinon-chai": "3.7.0", "typescript": "5.8.3", "url-toolkit": "2.2.5", @@ -12656,16 +12657,15 @@ "dev": true }, "node_modules/sinon": { - "version": "19.0.5", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-19.0.5.tgz", - "integrity": "sha512-r15s9/s+ub/d4bxNXqIUmwp6imVSdTorIRaxoecYjqTVLZ8RuoXr/4EDGwIBo6Waxn7f2gnURX9zuhAfCwaF6Q==", + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-21.0.0.tgz", + "integrity": "sha512-TOgRcwFPbfGtpqvZw+hyqJDvqfapr1qUlOizROIk4bBLjlsjlB00Pg6wMFXNtJRpu+eCZuVOaLatG7M8105kAw==", "dev": true, "dependencies": { "@sinonjs/commons": "^3.0.1", "@sinonjs/fake-timers": "^13.0.5", "@sinonjs/samsam": "^8.0.1", "diff": "^7.0.0", - "nise": "^6.1.1", "supports-color": "^7.2.0" }, "funding": { @@ -23606,16 +23606,15 @@ } }, "sinon": { - "version": "19.0.5", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-19.0.5.tgz", - "integrity": "sha512-r15s9/s+ub/d4bxNXqIUmwp6imVSdTorIRaxoecYjqTVLZ8RuoXr/4EDGwIBo6Waxn7f2gnURX9zuhAfCwaF6Q==", + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-21.0.0.tgz", + "integrity": "sha512-TOgRcwFPbfGtpqvZw+hyqJDvqfapr1qUlOizROIk4bBLjlsjlB00Pg6wMFXNtJRpu+eCZuVOaLatG7M8105kAw==", "dev": true, "requires": { "@sinonjs/commons": "^3.0.1", "@sinonjs/fake-timers": "^13.0.5", "@sinonjs/samsam": "^8.0.1", "diff": "^7.0.0", - "nise": "^6.1.1", "supports-color": "^7.2.0" }, "dependencies": { diff --git a/package.json b/package.json index b5719c1d50d..e569286150e 100644 --- a/package.json +++ b/package.json @@ -120,6 +120,7 @@ "markdown-styles": "3.2.0", "micromatch": "4.0.8", "mocha": "11.7.1", + "nise": "6.1.1", "node-fetch": "3.3.2", "npm-run-all2": "8.0.4", "prettier": "3.6.2", @@ -129,7 +130,7 @@ "sauce-connect-launcher": "1.3.2", "selenium-webdriver": "4.35.0", "semver": "7.7.2", - "sinon": "19.0.5", + "sinon": "21.0.0", "sinon-chai": "3.7.0", "typescript": "5.8.3", "url-toolkit": "2.2.5", diff --git a/tests/unit/controller/eme-controller.ts b/tests/unit/controller/eme-controller.ts index 2acf1c2db14..f8fd2164d36 100644 --- a/tests/unit/controller/eme-controller.ts +++ b/tests/unit/controller/eme-controller.ts @@ -1,5 +1,6 @@ import chai from 'chai'; import { EventEmitter } from 'eventemitter3'; +import { fakeXhr } from 'nise'; import sinon from 'sinon'; import sinonChai from 'sinon-chai'; import EMEController from '../../../src/controller/eme-controller'; @@ -149,7 +150,7 @@ const setupEach = function (config) { }; media = new MediaMock(); emeController = new EMEController(hls as any) as any as EMEControllerTestable; - sinonFakeXMLHttpRequestStatic = sinon.useFakeXMLHttpRequest(); + sinonFakeXMLHttpRequestStatic = fakeXhr.useFakeXMLHttpRequest(); }; const getParsedLevelKey = ( diff --git a/tests/unit/controller/error-controller.ts b/tests/unit/controller/error-controller.ts index 7ddef8323ac..e7200de6e6c 100644 --- a/tests/unit/controller/error-controller.ts +++ b/tests/unit/controller/error-controller.ts @@ -1,4 +1,5 @@ import chai from 'chai'; +import { fakeServer } from 'nise'; import sinon from 'sinon'; import sinonChai from 'sinon-chai'; import { multivariantPlaylistWithRedundantFallbacks } from './level-controller'; @@ -20,7 +21,7 @@ describe('ErrorController Integration Tests', function () { let hls: Hls; beforeEach(function () { - server = sinon.fakeServer.create(); + server = fakeServer.create(); setupMockServerResponses(server); timers = sinon.useFakeTimers({ shouldClearNativeTimers: true } as any); diff --git a/tests/unit/controller/stream-controller.ts b/tests/unit/controller/stream-controller.ts index af873be45c1..714cfdebdce 100644 --- a/tests/unit/controller/stream-controller.ts +++ b/tests/unit/controller/stream-controller.ts @@ -1,5 +1,6 @@ /* eslint-disable dot-notation */ import chai from 'chai'; +import { fakeXhr } from 'nise'; import sinon from 'sinon'; import sinonChai from 'sinon-chai'; import { State } from '../../../src/controller/base-stream-controller'; @@ -32,7 +33,7 @@ describe('StreamController', function () { const mockFragments = mockFragmentArray as MediaFragment[]; beforeEach(function () { - fake = sinon.useFakeXMLHttpRequest(); + fake = fakeXhr.useFakeXMLHttpRequest(); hls = new Hls({ // Enable debug to catch callback errors and enable logging in these tests: // debug: true, From 1613d266eecc47e2dea10c1becb3bb7faef09e7f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 22 Sep 2025 15:35:04 +0000 Subject: [PATCH 57/64] chore(deps): update dependency chromedriver to v140.0.2 --- package-lock.json | 98 ++++++++++++++++++++++++----------------------- package.json | 2 +- 2 files changed, 51 insertions(+), 49 deletions(-) diff --git a/package-lock.json b/package-lock.json index 70647041e6a..4f47a13d419 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,7 @@ "babel-plugin-transform-remove-console": "6.9.4", "chai": "4.5.0", "chart.js": "2.9.4", - "chromedriver": "140.0.1", + "chromedriver": "140.0.2", "doctoc": "2.2.1", "es-check": "9.3.1", "eslint": "8.57.1", @@ -4935,7 +4935,7 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, "node_modules/available-typed-arrays": { @@ -4954,30 +4954,16 @@ } }, "node_modules/axios": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", - "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", + "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", "dev": true, "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, - "node_modules/axios/node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/babel-loader": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-10.0.0.tgz", @@ -5480,14 +5466,14 @@ } }, "node_modules/chromedriver": { - "version": "140.0.1", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-140.0.1.tgz", - "integrity": "sha512-YFEKOaqxUMFTJkAJGj06/VizWegsokIYxgHqrs1h+/acFPCB9k0Pnnai4N0+eySf/ce04UG++xH1bnGw2U/71g==", + "version": "140.0.2", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-140.0.2.tgz", + "integrity": "sha512-XWRFxCTOh0AncsajXM/z7GDkljNByF2TmrsZF1gYvprTn7x7ReKpKIT3eTs9ktvUbblVszxY7bJjj04ELKjk8Q==", "dev": true, "hasInstallScript": true, "dependencies": { "@testim/chrome-version": "^1.1.4", - "axios": "^1.7.4", + "axios": "^1.12.0", "compare-versions": "^6.1.0", "extract-zip": "^2.0.1", "proxy-agent": "^6.4.0", @@ -5983,7 +5969,7 @@ "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true, "engines": { "node": ">=0.4.0" @@ -7589,6 +7575,22 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/format": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", @@ -17956,7 +17958,7 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, "available-typed-arrays": { @@ -17969,27 +17971,14 @@ } }, "axios": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", - "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", + "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", "dev": true, "requires": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" - }, - "dependencies": { - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - } } }, "babel-loader": { @@ -18357,13 +18346,13 @@ "peer": true }, "chromedriver": { - "version": "140.0.1", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-140.0.1.tgz", - "integrity": "sha512-YFEKOaqxUMFTJkAJGj06/VizWegsokIYxgHqrs1h+/acFPCB9k0Pnnai4N0+eySf/ce04UG++xH1bnGw2U/71g==", + "version": "140.0.2", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-140.0.2.tgz", + "integrity": "sha512-XWRFxCTOh0AncsajXM/z7GDkljNByF2TmrsZF1gYvprTn7x7ReKpKIT3eTs9ktvUbblVszxY7bJjj04ELKjk8Q==", "dev": true, "requires": { "@testim/chrome-version": "^1.1.4", - "axios": "^1.7.4", + "axios": "^1.12.0", "compare-versions": "^6.1.0", "extract-zip": "^2.0.1", "proxy-agent": "^6.4.0", @@ -18753,7 +18742,7 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true }, "depd": { @@ -19927,6 +19916,19 @@ "signal-exit": "^4.0.1" } }, + "form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + } + }, "format": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", diff --git a/package.json b/package.json index e569286150e..db6bbe9adc4 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "babel-plugin-transform-remove-console": "6.9.4", "chai": "4.5.0", "chart.js": "2.9.4", - "chromedriver": "140.0.1", + "chromedriver": "140.0.2", "doctoc": "2.2.1", "es-check": "9.3.1", "eslint": "8.57.1", From a7ac8494359062b6eec5010490b2099c4c6dd5f3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 23 Sep 2025 14:19:27 +0000 Subject: [PATCH 58/64] chore(deps): update actions/setup-node action to v5 --- .github/workflows/build.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e3edb42fd63..856c2e671c1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -81,7 +81,7 @@ jobs: ${{ runner.os }}- - name: use Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version-file: '.node-version' @@ -148,7 +148,7 @@ jobs: ${{ runner.os }}- - name: use Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version-file: '.node-version' @@ -192,7 +192,7 @@ jobs: ${{ runner.os }}- - name: use Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version-file: '.node-version' @@ -255,7 +255,7 @@ jobs: - uses: actions/checkout@v5 - name: use Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version-file: '.node-version' @@ -306,7 +306,7 @@ jobs: ${{ runner.os }}- - name: use Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version-file: '.node-version' @@ -373,7 +373,7 @@ jobs: ${{ runner.os }}- - name: use Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version-file: '.node-version' @@ -445,7 +445,7 @@ jobs: ${{ runner.os }}- - name: use Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: node-version-file: '.node-version' From b7e1f4dc3c040130f3bf5cbfb513459fc8cc2ced Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 Sep 2025 13:38:39 +0200 Subject: [PATCH 59/64] chore(deps): update actions/github-script action to v8 (#7553) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 856c2e671c1..a8b34d4bba6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,7 +35,7 @@ jobs: SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }} - name: extract tag id: extract_tag - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | const prefix = 'refs/tags/'; @@ -55,7 +55,7 @@ jobs: fetch-depth: 0 - name: check package-lock.json version - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | const fs = require('fs'); From e6b01d8c416d0cd674e94d30105bb859fab34b51 Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Mon, 17 Mar 2025 18:25:19 -0700 Subject: [PATCH 60/64] Use ResizeObserver to cap level to player size --- src/controller/cap-level-controller.ts | 121 ++++++++++++------ tests/unit/controller/cap-level-controller.ts | 16 +-- 2 files changed, 90 insertions(+), 47 deletions(-) diff --git a/src/controller/cap-level-controller.ts b/src/controller/cap-level-controller.ts index 5a4cd7e281e..adb0e48c2fc 100644 --- a/src/controller/cap-level-controller.ts +++ b/src/controller/cap-level-controller.ts @@ -17,22 +17,20 @@ import type { Level } from '../types/level'; type RestrictedLevel = { width: number; height: number; bitrate: number }; class CapLevelController implements ComponentAPI { - private hls: Hls; + private hls: Hls | null = null; private autoLevelCapping: number; - private firstLevel: number; private media: HTMLVideoElement | null; private restrictedLevels: RestrictedLevel[]; - private timer: number | undefined; + private timer?: number; + private observer?: ResizeObserver; private clientRect: { width: number; height: number } | null; private streamController?: StreamController; constructor(hls: Hls) { this.hls = hls; this.autoLevelCapping = Number.POSITIVE_INFINITY; - this.firstLevel = -1; this.media = null; this.restrictedLevels = []; - this.timer = undefined; this.clientRect = null; this.registerListeners(); @@ -46,39 +44,45 @@ class CapLevelController implements ComponentAPI { if (this.hls) { this.unregisterListener(); } - if (this.timer) { + if (this.timer || this.observer) { this.stopCapping(); } - this.media = null; - this.clientRect = null; + this.media = this.clientRect = this.hls = null; // @ts-ignore - this.hls = this.streamController = null; + this.streamController = undefined; } protected registerListeners() { const { hls } = this; - hls.on(Events.FPS_DROP_LEVEL_CAPPING, this.onFpsDropLevelCapping, this); - hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); - hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this); - hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this); - hls.on(Events.BUFFER_CODECS, this.onBufferCodecs, this); - hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); + if (hls) { + hls.on(Events.FPS_DROP_LEVEL_CAPPING, this.onFpsDropLevelCapping, this); + hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); + hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this); + hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this); + hls.on(Events.BUFFER_CODECS, this.onBufferCodecs, this); + hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); + } } protected unregisterListener() { const { hls } = this; - hls.off(Events.FPS_DROP_LEVEL_CAPPING, this.onFpsDropLevelCapping, this); - hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); - hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this); - hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this); - hls.off(Events.BUFFER_CODECS, this.onBufferCodecs, this); - hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); + if (hls) { + hls.off(Events.FPS_DROP_LEVEL_CAPPING, this.onFpsDropLevelCapping, this); + hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); + hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this); + hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this); + hls.off(Events.BUFFER_CODECS, this.onBufferCodecs, this); + hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); + } } protected onFpsDropLevelCapping( event: Events.FPS_DROP_LEVEL_CAPPING, data: FPSDropLevelCappingData, ) { + if (!this.hls) { + return; + } // Don't add a restricted level more than once const level = this.hls.levels[data.droppedLevel]; if (this.isLevelAllowed(level)) { @@ -94,9 +98,20 @@ class CapLevelController implements ComponentAPI { event: Events.MEDIA_ATTACHING, data: MediaAttachingData, ) { - this.media = data.media instanceof HTMLVideoElement ? data.media : null; + const media = data.media; this.clientRect = null; - if (this.timer && this.hls.levels.length) { + if (!this.hls) { + return; + } + if (media instanceof HTMLVideoElement) { + this.media = media; + if (this.hls.config.capLevelToPlayerSize) { + this.observe(); + } + } else { + this.media = null; + } + if ((this.timer || this.observer) && this.hls.levels.length) { this.detectPlayerSize(); } } @@ -107,8 +122,7 @@ class CapLevelController implements ComponentAPI { ) { const hls = this.hls; this.restrictedLevels = []; - this.firstLevel = data.firstLevel; - if (hls.config.capLevelToPlayerSize && data.video) { + if (hls?.config.capLevelToPlayerSize && data.video) { // Start capping immediately if the manifest has signaled video codecs this.startCapping(); } @@ -118,7 +132,10 @@ class CapLevelController implements ComponentAPI { event: Events.LEVELS_UPDATED, data: LevelsUpdatedData, ) { - if (this.timer && Number.isFinite(this.autoLevelCapping)) { + if ( + (this.timer || this.observer) && + Number.isFinite(this.autoLevelCapping) + ) { this.detectPlayerSize(); } } @@ -130,7 +147,7 @@ class CapLevelController implements ComponentAPI { data: BufferCodecsData, ) { const hls = this.hls; - if (hls.config.capLevelToPlayerSize && data.video) { + if (hls?.config.capLevelToPlayerSize && data.video) { // If the manifest did not signal a video codec capping has been deferred until we're certain video is present this.startCapping(); } @@ -143,7 +160,7 @@ class CapLevelController implements ComponentAPI { detectPlayerSize() { if (this.media) { - if (this.mediaHeight <= 0 || this.mediaWidth <= 0) { + if (this.mediaHeight <= 0 || this.mediaWidth <= 0 || !this.hls) { this.clientRect = null; return; } @@ -175,6 +192,9 @@ class CapLevelController implements ComponentAPI { * returns level should be the one with the dimensions equal or greater than the media (player) dimensions (so the video will be downscaled) */ getMaxLevel(capLevelIndex: number): number { + if (!this.hls) { + return -1; + } const levels = this.hls.levels; if (!levels.length) { return -1; @@ -183,8 +203,9 @@ class CapLevelController implements ComponentAPI { const validLevels = levels.filter( (level, index) => this.isLevelAllowed(level) && index <= capLevelIndex, ); - - this.clientRect = null; + if (!this.observer) { + this.clientRect = null; + } return CapLevelController.getMaxLevelByMediaSize( validLevels, this.mediaWidth, @@ -192,25 +213,48 @@ class CapLevelController implements ComponentAPI { ); } + private observe() { + const ResizeObserver = self.ResizeObserver; + if (ResizeObserver) { + this.observer = new ResizeObserver((entries) => { + const bounds = entries[0]?.contentRect; + if (bounds) { + this.clientRect = bounds; + this.detectPlayerSize(); + } + }); + } + if (this.observer && this.media) { + this.observer.observe(this.media); + } + } + startCapping() { - if (this.timer) { + if (this.timer || this.observer) { // Don't reset capping if started twice; this can happen if the manifest signals a video codec return; } - this.autoLevelCapping = Number.POSITIVE_INFINITY; self.clearInterval(this.timer); - this.timer = self.setInterval(this.detectPlayerSize.bind(this), 1000); + this.timer = undefined; + this.autoLevelCapping = Number.POSITIVE_INFINITY; + this.observe(); + if (!this.observer) { + this.timer = self.setInterval(this.detectPlayerSize.bind(this), 1000); + } this.detectPlayerSize(); } stopCapping() { this.restrictedLevels = []; - this.firstLevel = -1; this.autoLevelCapping = Number.POSITIVE_INFINITY; if (this.timer) { self.clearInterval(this.timer); this.timer = undefined; } + if (this.observer) { + this.observer.disconnect(); + this.observer = undefined; + } } getDimensions(): { width: number; height: number } { @@ -250,15 +294,18 @@ class CapLevelController implements ComponentAPI { get contentScaleFactor(): number { let pixelRatio = 1; - if (!this.hls.config.ignoreDevicePixelRatio) { + if (!this.hls) { + return pixelRatio; + } + const { ignoreDevicePixelRatio, maxDevicePixelRatio } = this.hls.config; + if (!ignoreDevicePixelRatio) { try { pixelRatio = self.devicePixelRatio; } catch (e) { /* no-op */ } } - - return Math.min(pixelRatio, this.hls.config.maxDevicePixelRatio); + return Math.min(pixelRatio, maxDevicePixelRatio); } private isLevelAllowed(level: Level): boolean { diff --git a/tests/unit/controller/cap-level-controller.ts b/tests/unit/controller/cap-level-controller.ts index 401a51eee42..cd143b60051 100644 --- a/tests/unit/controller/cap-level-controller.ts +++ b/tests/unit/controller/cap-level-controller.ts @@ -195,14 +195,15 @@ describe('CapLevelController', function () { }); describe('start and stop', function () { - it('immediately caps and sets a timer for monitoring size size', function () { + it('immediately caps and begins monitoring size', function () { const detectPlayerSizeSpy = sinon.spy( capLevelController, 'detectPlayerSize', ); capLevelController.startCapping(); - expect(capLevelController.timer).to.exist; + expect(capLevelController.timer || capLevelController.observer).to + .exist; expect(detectPlayerSizeSpy.callCount).to.equal(1); }); @@ -215,7 +216,6 @@ describe('CapLevelController', function () { Number.POSITIVE_INFINITY, ); expect(capLevelController.restrictedLevels).to.be.empty; - expect(capLevelController.firstLevel).to.equal(-1); expect(capLevelController.timer).to.not.exist; }); }); @@ -246,15 +246,11 @@ describe('CapLevelController', function () { expect(startCappingSpy.calledOnce).to.be.true; }); - it('receives level information from the MANIFEST_PARSED event', function () { + it('resets restrictedLevels on MANIFEST_PARSED', function () { capLevelController.restrictedLevels = [1]; - const data = { + capLevelController.onManifestParsed(Events.MANIFEST_PARSED, { levels: [{ foo: 'bar' }], - firstLevel: 0, - }; - - capLevelController.onManifestParsed(Events.MANIFEST_PARSED, data); - expect(capLevelController.firstLevel).to.equal(data.firstLevel); + }); expect(capLevelController.restrictedLevels).to.be.empty; }); From 917155dc8d71f3478f2aac559f87d257564cf180 Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Mon, 14 Jul 2025 16:37:49 -0700 Subject: [PATCH 61/64] Make `nextLevelSwitch` more responsive and accurate --- src/controller/abr-controller.ts | 2 +- src/controller/stream-controller.ts | 75 +++++++++++++++-------------- 2 files changed, 41 insertions(+), 36 deletions(-) diff --git a/src/controller/abr-controller.ts b/src/controller/abr-controller.ts index afefdea4a44..05a05853655 100644 --- a/src/controller/abr-controller.ts +++ b/src/controller/abr-controller.ts @@ -942,7 +942,7 @@ class AbrController extends Logger implements AbrComponentAPI { ttfbEstimateSec, adjustedbw, bitrate * avgDuration, - levelDetails === undefined, + !levelDetails || levelDetails.live, ); const canSwitchWithinTolerance = diff --git a/src/controller/stream-controller.ts b/src/controller/stream-controller.ts index 2e3402bc5cf..d7bac6ec2f4 100644 --- a/src/controller/stream-controller.ts +++ b/src/controller/stream-controller.ts @@ -70,7 +70,6 @@ export default class StreamController private altAudio: AlternateAudio = AlternateAudio.DISABLED; private audioOnly: boolean = false; private fragPlaying: Fragment | null = null; - private fragLastKbps: number = 0; private couldBacktrack: boolean = false; private backtrackFragment: Fragment | null = null; private audioCodecSwitch: boolean = false; @@ -436,43 +435,46 @@ export default class StreamController * we should take into account new segment fetch time */ public nextLevelSwitch() { - const { levels, media } = this; + const { levels, media, hls, config } = this; // ensure that media is defined and that metadata are available (to retrieve currentTime) - if (media?.readyState) { - let fetchdelay; - const fragPlayingCurrent = this.getAppendedFrag(media.currentTime); - if (fragPlayingCurrent && fragPlayingCurrent.start > 1) { - // flush buffer preceding current fragment (flush until current fragment start offset) - // minus 1s to avoid video freezing, that could happen if we flush keyframe of current video ... - this.flushMainBuffer(0, fragPlayingCurrent.start - 1); + if (media?.readyState && levels && hls && config) { + const bufferInfo = this.getMainFwdBufferInfo(); + if (!bufferInfo) { + return; } const levelDetails = this.getLevelDetails(); - if (levelDetails?.live) { - const bufferInfo = this.getMainFwdBufferInfo(); - // Do not flush in live stream with low buffer - if (!bufferInfo || bufferInfo.len < levelDetails.targetduration * 2) { - return; - } - } - if (!media.paused && levels) { + + let fetchdelay = 0; + if (!media.paused) { // add a safety delay of 1s - const nextLevelId = this.hls.nextLoadLevel; + const ttfbSec = 1 + hls.ttfbEstimate / 1000; + const bandwidth = hls.bandwidthEstimate * config.abrBandWidthUpFactor; + const nextLevelId = hls.nextLoadLevel; const nextLevel = levels[nextLevelId]; - const fragLastKbps = this.fragLastKbps; - if (fragLastKbps && this.fragCurrent) { - fetchdelay = - (this.fragCurrent.duration * nextLevel.maxBitrate) / - (1000 * fragLastKbps) + - 1; - } else { - fetchdelay = 0; + const fragDuration = + (levelDetails && + (this.loadingParts + ? levelDetails.partTarget + : levelDetails.averagetargetduration)) || + this.fragCurrent?.duration || + 6; + fetchdelay = + ttfbSec + (nextLevel.maxBitrate * fragDuration) / bandwidth; + if (!nextLevel.details) { + fetchdelay += ttfbSec; } - } else { - fetchdelay = 0; } - // this.log('fetchdelay:'+fetchdelay); + + // Do not flush in live stream with low buffer + + const okToFlushForwardBuffer = + !levelDetails?.live || + (bufferInfo.len || 0) > levelDetails.targetduration * 2; + // find buffer range that will be reached once new fragment will be fetched - const bufferedFrag = this.getBufferedFrag(media.currentTime + fetchdelay); + const bufferedFrag = okToFlushForwardBuffer + ? this.getBufferedFrag(media.currentTime + fetchdelay) + : null; if (bufferedFrag) { // we can flush buffer range following this one without stalling playback const nextBufferedFrag = this.followingBufferedFrag(bufferedFrag); @@ -498,7 +500,15 @@ export default class StreamController this.flushMainBuffer(startPts, Number.POSITIVE_INFINITY); } } + // remove back-buffer + const fragPlayingCurrent = this.getAppendedFrag(media.currentTime); + if (fragPlayingCurrent && fragPlayingCurrent.start > 1) { + // flush buffer preceding current fragment (flush until current fragment start offset) + // minus 1s to avoid video freezing, that could happen if we flush keyframe of current video ... + this.flushMainBuffer(0, fragPlayingCurrent.start - 1); + } } + this.tickImmediate(); } private abortCurrentFrag() { @@ -601,7 +611,6 @@ export default class StreamController this.log('Trigger BUFFER_RESET'); this.hls.trigger(Events.BUFFER_RESET, undefined); this.couldBacktrack = false; - this.fragLastKbps = 0; this.fragPlaying = this.backtrackFragment = null; this.altAudio = AlternateAudio.DISABLED; this.audioOnly = false; @@ -987,10 +996,6 @@ export default class StreamController } return; } - const stats = part ? part.stats : frag.stats; - this.fragLastKbps = Math.round( - (8 * stats.total) / (stats.buffering.end - stats.loading.first), - ); if (isMediaFragment(frag)) { this.fragPrevious = frag; } From 610e8aedc12837a6403e5727aaa1aab150765c83 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 28 Sep 2025 00:49:22 +0000 Subject: [PATCH 62/64] chore(deps): update dependency chromedriver to v140.0.3 --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4f47a13d419..62aab6f13dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,7 @@ "babel-plugin-transform-remove-console": "6.9.4", "chai": "4.5.0", "chart.js": "2.9.4", - "chromedriver": "140.0.2", + "chromedriver": "140.0.3", "doctoc": "2.2.1", "es-check": "9.3.1", "eslint": "8.57.1", @@ -5466,9 +5466,9 @@ } }, "node_modules/chromedriver": { - "version": "140.0.2", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-140.0.2.tgz", - "integrity": "sha512-XWRFxCTOh0AncsajXM/z7GDkljNByF2TmrsZF1gYvprTn7x7ReKpKIT3eTs9ktvUbblVszxY7bJjj04ELKjk8Q==", + "version": "140.0.3", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-140.0.3.tgz", + "integrity": "sha512-2UdIHhkGy8U5hODjIitUnm6coDJiEpcWAiDCSG8bwTHnK3hivHetW/KAvApXEMdCGdGZVCBwhycJG3HVFTxKpA==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -18346,9 +18346,9 @@ "peer": true }, "chromedriver": { - "version": "140.0.2", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-140.0.2.tgz", - "integrity": "sha512-XWRFxCTOh0AncsajXM/z7GDkljNByF2TmrsZF1gYvprTn7x7ReKpKIT3eTs9ktvUbblVszxY7bJjj04ELKjk8Q==", + "version": "140.0.3", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-140.0.3.tgz", + "integrity": "sha512-2UdIHhkGy8U5hODjIitUnm6coDJiEpcWAiDCSG8bwTHnK3hivHetW/KAvApXEMdCGdGZVCBwhycJG3HVFTxKpA==", "dev": true, "requires": { "@testim/chrome-version": "^1.1.4", diff --git a/package.json b/package.json index db6bbe9adc4..d16b8c641d1 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "babel-plugin-transform-remove-console": "6.9.4", "chai": "4.5.0", "chart.js": "2.9.4", - "chromedriver": "140.0.2", + "chromedriver": "140.0.3", "doctoc": "2.2.1", "es-check": "9.3.1", "eslint": "8.57.1", From 3b4ef10f762bc6ae037d6e6ca2df071787f370db Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 28 Sep 2025 01:05:26 +0000 Subject: [PATCH 63/64] chore(deps): update dependency rollup to v4.52.0 --- package-lock.json | 386 ++++++++++++++++++++++++++-------------------- package.json | 2 +- 2 files changed, 216 insertions(+), 172 deletions(-) diff --git a/package-lock.json b/package-lock.json index 62aab6f13dc..f3258859d93 100644 --- a/package-lock.json +++ b/package-lock.json @@ -67,7 +67,7 @@ "npm-run-all2": "8.0.4", "prettier": "3.6.2", "promise-polyfill": "8.3.0", - "rollup": "4.47.1", + "rollup": "4.52.0", "rollup-plugin-istanbul": "5.0.0", "sauce-connect-launcher": "1.3.2", "selenium-webdriver": "4.35.0", @@ -3451,9 +3451,9 @@ "dev": true }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.47.1.tgz", - "integrity": "sha512-lTahKRJip0knffA/GTNFJMrToD+CM+JJ+Qt5kjzBK/sFQ0EWqfKW3AYQSlZXN98tX0lx66083U9JYIMioMMK7g==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.0.tgz", + "integrity": "sha512-VxDYCDqOaR7NXzAtvRx7G1u54d2kEHopb28YH/pKzY6y0qmogP3gG7CSiWsq9WvDFxOQMpNEyjVAHZFXfH3o/A==", "cpu": [ "arm" ], @@ -3464,9 +3464,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.47.1.tgz", - "integrity": "sha512-uqxkb3RJLzlBbh/bbNQ4r7YpSZnjgMgyoEOY7Fy6GCbelkDSAzeiogxMG9TfLsBbqmGsdDObo3mzGqa8hps4MA==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.0.tgz", + "integrity": "sha512-pqDirm8koABIKvzL59YI9W9DWbRlTX7RWhN+auR8HXJxo89m4mjqbah7nJZjeKNTNYopqL+yGg+0mhCpf3xZtQ==", "cpu": [ "arm64" ], @@ -3477,9 +3477,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.47.1.tgz", - "integrity": "sha512-tV6reObmxBDS4DDyLzTDIpymthNlxrLBGAoQx6m2a7eifSNEZdkXQl1PE4ZjCkEDPVgNXSzND/k9AQ3mC4IOEQ==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.0.tgz", + "integrity": "sha512-YCdWlY/8ltN6H78HnMsRHYlPiKvqKagBP1r+D7SSylxX+HnsgXGCmLiV3Y4nSyY9hW8qr8U9LDUx/Lo7M6MfmQ==", "cpu": [ "arm64" ], @@ -3490,9 +3490,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.47.1.tgz", - "integrity": "sha512-XuJRPTnMk1lwsSnS3vYyVMu4x/+WIw1MMSiqj5C4j3QOWsMzbJEK90zG+SWV1h0B1ABGCQ0UZUjti+TQK35uHQ==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.0.tgz", + "integrity": "sha512-z4nw6y1j+OOSGzuVbSWdIp1IUks9qNw4dc7z7lWuWDKojY38VMWBlEN7F9jk5UXOkUcp97vA1N213DF+Lz8BRg==", "cpu": [ "x64" ], @@ -3503,9 +3503,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.47.1.tgz", - "integrity": "sha512-79BAm8Ag/tmJ5asCqgOXsb3WY28Rdd5Lxj8ONiQzWzy9LvWORd5qVuOnjlqiWWZJw+dWewEktZb5yiM1DLLaHw==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.0.tgz", + "integrity": "sha512-Q/dv9Yvyr5rKlK8WQJZVrp5g2SOYeZUs9u/t2f9cQ2E0gJjYB/BWoedXfUT0EcDJefi2zzVfhcOj8drWCzTviw==", "cpu": [ "arm64" ], @@ -3516,9 +3516,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.47.1.tgz", - "integrity": "sha512-OQ2/ZDGzdOOlyfqBiip0ZX/jVFekzYrGtUsqAfLDbWy0jh1PUU18+jYp8UMpqhly5ltEqotc2miLngf9FPSWIA==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.0.tgz", + "integrity": "sha512-kdBsLs4Uile/fbjZVvCRcKB4q64R+1mUq0Yd7oU1CMm1Av336ajIFqNFovByipciuUQjBCPMxwJhCgfG2re3rg==", "cpu": [ "x64" ], @@ -3529,9 +3529,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.47.1.tgz", - "integrity": "sha512-HZZBXJL1udxlCVvoVadstgiU26seKkHbbAMLg7680gAcMnRNP9SAwTMVet02ANA94kXEI2VhBnXs4e5nf7KG2A==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.0.tgz", + "integrity": "sha512-aL6hRwu0k7MTUESgkg7QHY6CoqPgr6gdQXRJI1/VbFlUMwsSzPGSR7sG5d+MCbYnJmJwThc2ol3nixj1fvI/zQ==", "cpu": [ "arm" ], @@ -3542,9 +3542,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.47.1.tgz", - "integrity": "sha512-sZ5p2I9UA7T950JmuZ3pgdKA6+RTBr+0FpK427ExW0t7n+QwYOcmDTK/aRlzoBrWyTpJNlS3kacgSlSTUg6P/Q==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.0.tgz", + "integrity": "sha512-BTs0M5s1EJejgIBJhCeiFo7GZZ2IXWkFGcyZhxX4+8usnIo5Mti57108vjXFIQmmJaRyDwmV59Tw64Ap1dkwMw==", "cpu": [ "arm" ], @@ -3555,9 +3555,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.47.1.tgz", - "integrity": "sha512-3hBFoqPyU89Dyf1mQRXCdpc6qC6At3LV6jbbIOZd72jcx7xNk3aAp+EjzAtN6sDlmHFzsDJN5yeUySvorWeRXA==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.0.tgz", + "integrity": "sha512-uj672IVOU9m08DBGvoPKPi/J8jlVgjh12C9GmjjBxCTQc3XtVmRkRKyeHSmIKQpvJ7fIm1EJieBUcnGSzDVFyw==", "cpu": [ "arm64" ], @@ -3568,9 +3568,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.47.1.tgz", - "integrity": "sha512-49J4FnMHfGodJWPw73Ve+/hsPjZgcXQGkmqBGZFvltzBKRS+cvMiWNLadOMXKGnYRhs1ToTGM0sItKISoSGUNA==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.0.tgz", + "integrity": "sha512-/+IVbeDMDCtB/HP/wiWsSzduD10SEGzIZX2945KSgZRNi4TSkjHqRJtNTVtVb8IRwhJ65ssI56krlLik+zFWkw==", "cpu": [ "arm64" ], @@ -3580,10 +3580,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.47.1.tgz", - "integrity": "sha512-4yYU8p7AneEpQkRX03pbpLmE21z5JNys16F1BZBZg5fP9rIlb0TkeQjn5du5w4agConCCEoYIG57sNxjryHEGg==", + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.0.tgz", + "integrity": "sha512-U1vVzvSWtSMWKKrGoROPBXMh3Vwn93TA9V35PldokHGqiUbF6erSzox/5qrSMKp6SzakvyjcPiVF8yB1xKr9Pg==", "cpu": [ "loong64" ], @@ -3594,9 +3594,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.47.1.tgz", - "integrity": "sha512-fAiq+J28l2YMWgC39jz/zPi2jqc0y3GSRo1yyxlBHt6UN0yYgnegHSRPa3pnHS5amT/efXQrm0ug5+aNEu9UuQ==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.0.tgz", + "integrity": "sha512-X/4WfuBAdQRH8cK3DYl8zC00XEE6aM472W+QCycpQJeLWVnHfkv7RyBFVaTqNUMsTgIX8ihMjCvFF9OUgeABzw==", "cpu": [ "ppc64" ], @@ -3607,9 +3607,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.47.1.tgz", - "integrity": "sha512-daoT0PMENNdjVYYU9xec30Y2prb1AbEIbb64sqkcQcSaR0zYuKkoPuhIztfxuqN82KYCKKrj+tQe4Gi7OSm1ow==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.0.tgz", + "integrity": "sha512-xIRYc58HfWDBZoLmWfWXg2Sq8VCa2iJ32B7mqfWnkx5mekekl0tMe7FHpY8I72RXEcUkaWawRvl3qA55og+cwQ==", "cpu": [ "riscv64" ], @@ -3620,9 +3620,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.47.1.tgz", - "integrity": "sha512-JNyXaAhWtdzfXu5pUcHAuNwGQKevR+6z/poYQKVW+pLaYOj9G1meYc57/1Xv2u4uTxfu9qEWmNTjv/H/EpAisw==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.0.tgz", + "integrity": "sha512-mbsoUey05WJIOz8U1WzNdf+6UMYGwE3fZZnQqsM22FZ3wh1N887HT6jAOjXs6CNEK3Ntu2OBsyQDXfIjouI4dw==", "cpu": [ "riscv64" ], @@ -3633,9 +3633,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.47.1.tgz", - "integrity": "sha512-U/CHbqKSwEQyZXjCpY43/GLYcTVKEXeRHw0rMBJP7fP3x6WpYG4LTJWR3ic6TeYKX6ZK7mrhltP4ppolyVhLVQ==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.0.tgz", + "integrity": "sha512-qP6aP970bucEi5KKKR4AuPFd8aTx9EF6BvutvYxmZuWLJHmnq4LvBfp0U+yFDMGwJ+AIJEH5sIP+SNypauMWzg==", "cpu": [ "s390x" ], @@ -3646,9 +3646,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.47.1.tgz", - "integrity": "sha512-uTLEakjxOTElfeZIGWkC34u2auLHB1AYS6wBjPGI00bWdxdLcCzK5awjs25YXpqB9lS8S0vbO0t9ZcBeNibA7g==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.0.tgz", + "integrity": "sha512-nmSVN+F2i1yKZ7rJNKO3G7ZzmxJgoQBQZ/6c4MuS553Grmr7WqR7LLDcYG53Z2m9409z3JLt4sCOhLdbKQ3HmA==", "cpu": [ "x64" ], @@ -3659,9 +3659,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.47.1.tgz", - "integrity": "sha512-Ft+d/9DXs30BK7CHCTX11FtQGHUdpNDLJW0HHLign4lgMgBcPFN3NkdIXhC5r9iwsMwYreBBc4Rho5ieOmKNVQ==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.0.tgz", + "integrity": "sha512-2d0qRo33G6TfQVjaMR71P+yJVGODrt5V6+T0BDYH4EMfGgdC/2HWDVjSSFw888GSzAZUwuska3+zxNUCDco6rQ==", "cpu": [ "x64" ], @@ -3671,10 +3671,23 @@ "linux" ] }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.0.tgz", + "integrity": "sha512-A1JalX4MOaFAAyGgpO7XP5khquv/7xKzLIyLmhNrbiCxWpMlnsTYr8dnsWM7sEeotNmxvSOEL7F65j0HXFcFsw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ] + }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.47.1.tgz", - "integrity": "sha512-N9X5WqGYzZnjGAFsKSfYFtAShYjwOmFJoWbLg3dYixZOZqU7hdMq+/xyS14zKLhFhZDhP9VfkzQnsdk0ZDS9IA==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.0.tgz", + "integrity": "sha512-YQugafP/rH0eOOHGjmNgDURrpYHrIX0yuojOI8bwCyXwxC9ZdTd3vYkmddPX0oHONLXu9Rb1dDmT0VNpjkzGGw==", "cpu": [ "arm64" ], @@ -3685,9 +3698,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.47.1.tgz", - "integrity": "sha512-O+KcfeCORZADEY8oQJk4HK8wtEOCRE4MdOkb8qGZQNun3jzmj2nmhV/B/ZaaZOkPmJyvm/gW9n0gsB4eRa1eiQ==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.0.tgz", + "integrity": "sha512-zYdUYhi3Qe2fndujBqL5FjAFzvNeLxtIqfzNEVKD1I7C37/chv1VxhscWSQHTNfjPCrBFQMnynwA3kpZpZ8w4A==", "cpu": [ "ia32" ], @@ -3697,10 +3710,23 @@ "win32" ] }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.0.tgz", + "integrity": "sha512-fGk03kQylNaCOQ96HDMeT7E2n91EqvCDd3RwvT5k+xNdFCeMGnj5b5hEgTGrQuyidqSsD3zJDQ21QIaxXqTBJw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.47.1.tgz", - "integrity": "sha512-CpKnYa8eHthJa3c+C38v/E+/KZyF1Jdh2Cz3DyKZqEWYgrM1IHFArXNWvBLPQCKUEsAqqKX27tTqVEFbDNUcOA==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.0.tgz", + "integrity": "sha512-6iKDCVSIUQ8jPMoIV0OytRKniaYyy5EbY/RRydmLW8ZR3cEBhxbWl5ro0rkUNe0ef6sScvhbY79HrjRm8i3vDQ==", "cpu": [ "x64" ], @@ -12104,9 +12130,9 @@ } }, "node_modules/rollup": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.47.1.tgz", - "integrity": "sha512-iasGAQoZ5dWDzULEUX3jiW0oB1qyFOepSyDyoU6S/OhVlDIwj5knI5QBa5RRQ0sK7OE0v+8VIi2JuV+G+3tfNg==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.0.tgz", + "integrity": "sha512-+IuescNkTJQgX7AkIDtITipZdIGcWF0pnVvZTWStiazUmcGA2ag8dfg0urest2XlXUi9kuhfQ+qmdc5Stc3z7g==", "dev": true, "dependencies": { "@types/estree": "1.0.8" @@ -12119,26 +12145,28 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.47.1", - "@rollup/rollup-android-arm64": "4.47.1", - "@rollup/rollup-darwin-arm64": "4.47.1", - "@rollup/rollup-darwin-x64": "4.47.1", - "@rollup/rollup-freebsd-arm64": "4.47.1", - "@rollup/rollup-freebsd-x64": "4.47.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.47.1", - "@rollup/rollup-linux-arm-musleabihf": "4.47.1", - "@rollup/rollup-linux-arm64-gnu": "4.47.1", - "@rollup/rollup-linux-arm64-musl": "4.47.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.47.1", - "@rollup/rollup-linux-ppc64-gnu": "4.47.1", - "@rollup/rollup-linux-riscv64-gnu": "4.47.1", - "@rollup/rollup-linux-riscv64-musl": "4.47.1", - "@rollup/rollup-linux-s390x-gnu": "4.47.1", - "@rollup/rollup-linux-x64-gnu": "4.47.1", - "@rollup/rollup-linux-x64-musl": "4.47.1", - "@rollup/rollup-win32-arm64-msvc": "4.47.1", - "@rollup/rollup-win32-ia32-msvc": "4.47.1", - "@rollup/rollup-win32-x64-msvc": "4.47.1", + "@rollup/rollup-android-arm-eabi": "4.52.0", + "@rollup/rollup-android-arm64": "4.52.0", + "@rollup/rollup-darwin-arm64": "4.52.0", + "@rollup/rollup-darwin-x64": "4.52.0", + "@rollup/rollup-freebsd-arm64": "4.52.0", + "@rollup/rollup-freebsd-x64": "4.52.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.0", + "@rollup/rollup-linux-arm-musleabihf": "4.52.0", + "@rollup/rollup-linux-arm64-gnu": "4.52.0", + "@rollup/rollup-linux-arm64-musl": "4.52.0", + "@rollup/rollup-linux-loong64-gnu": "4.52.0", + "@rollup/rollup-linux-ppc64-gnu": "4.52.0", + "@rollup/rollup-linux-riscv64-gnu": "4.52.0", + "@rollup/rollup-linux-riscv64-musl": "4.52.0", + "@rollup/rollup-linux-s390x-gnu": "4.52.0", + "@rollup/rollup-linux-x64-gnu": "4.52.0", + "@rollup/rollup-linux-x64-musl": "4.52.0", + "@rollup/rollup-openharmony-arm64": "4.52.0", + "@rollup/rollup-win32-arm64-msvc": "4.52.0", + "@rollup/rollup-win32-ia32-msvc": "4.52.0", + "@rollup/rollup-win32-x64-gnu": "4.52.0", + "@rollup/rollup-win32-x64-msvc": "4.52.0", "fsevents": "~2.3.2" } }, @@ -16848,142 +16876,156 @@ } }, "@rollup/rollup-android-arm-eabi": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.47.1.tgz", - "integrity": "sha512-lTahKRJip0knffA/GTNFJMrToD+CM+JJ+Qt5kjzBK/sFQ0EWqfKW3AYQSlZXN98tX0lx66083U9JYIMioMMK7g==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.0.tgz", + "integrity": "sha512-VxDYCDqOaR7NXzAtvRx7G1u54d2kEHopb28YH/pKzY6y0qmogP3gG7CSiWsq9WvDFxOQMpNEyjVAHZFXfH3o/A==", "dev": true, "optional": true }, "@rollup/rollup-android-arm64": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.47.1.tgz", - "integrity": "sha512-uqxkb3RJLzlBbh/bbNQ4r7YpSZnjgMgyoEOY7Fy6GCbelkDSAzeiogxMG9TfLsBbqmGsdDObo3mzGqa8hps4MA==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.0.tgz", + "integrity": "sha512-pqDirm8koABIKvzL59YI9W9DWbRlTX7RWhN+auR8HXJxo89m4mjqbah7nJZjeKNTNYopqL+yGg+0mhCpf3xZtQ==", "dev": true, "optional": true }, "@rollup/rollup-darwin-arm64": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.47.1.tgz", - "integrity": "sha512-tV6reObmxBDS4DDyLzTDIpymthNlxrLBGAoQx6m2a7eifSNEZdkXQl1PE4ZjCkEDPVgNXSzND/k9AQ3mC4IOEQ==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.0.tgz", + "integrity": "sha512-YCdWlY/8ltN6H78HnMsRHYlPiKvqKagBP1r+D7SSylxX+HnsgXGCmLiV3Y4nSyY9hW8qr8U9LDUx/Lo7M6MfmQ==", "dev": true, "optional": true }, "@rollup/rollup-darwin-x64": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.47.1.tgz", - "integrity": "sha512-XuJRPTnMk1lwsSnS3vYyVMu4x/+WIw1MMSiqj5C4j3QOWsMzbJEK90zG+SWV1h0B1ABGCQ0UZUjti+TQK35uHQ==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.0.tgz", + "integrity": "sha512-z4nw6y1j+OOSGzuVbSWdIp1IUks9qNw4dc7z7lWuWDKojY38VMWBlEN7F9jk5UXOkUcp97vA1N213DF+Lz8BRg==", "dev": true, "optional": true }, "@rollup/rollup-freebsd-arm64": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.47.1.tgz", - "integrity": "sha512-79BAm8Ag/tmJ5asCqgOXsb3WY28Rdd5Lxj8ONiQzWzy9LvWORd5qVuOnjlqiWWZJw+dWewEktZb5yiM1DLLaHw==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.0.tgz", + "integrity": "sha512-Q/dv9Yvyr5rKlK8WQJZVrp5g2SOYeZUs9u/t2f9cQ2E0gJjYB/BWoedXfUT0EcDJefi2zzVfhcOj8drWCzTviw==", "dev": true, "optional": true }, "@rollup/rollup-freebsd-x64": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.47.1.tgz", - "integrity": "sha512-OQ2/ZDGzdOOlyfqBiip0ZX/jVFekzYrGtUsqAfLDbWy0jh1PUU18+jYp8UMpqhly5ltEqotc2miLngf9FPSWIA==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.0.tgz", + "integrity": "sha512-kdBsLs4Uile/fbjZVvCRcKB4q64R+1mUq0Yd7oU1CMm1Av336ajIFqNFovByipciuUQjBCPMxwJhCgfG2re3rg==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.47.1.tgz", - "integrity": "sha512-HZZBXJL1udxlCVvoVadstgiU26seKkHbbAMLg7680gAcMnRNP9SAwTMVet02ANA94kXEI2VhBnXs4e5nf7KG2A==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.0.tgz", + "integrity": "sha512-aL6hRwu0k7MTUESgkg7QHY6CoqPgr6gdQXRJI1/VbFlUMwsSzPGSR7sG5d+MCbYnJmJwThc2ol3nixj1fvI/zQ==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm-musleabihf": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.47.1.tgz", - "integrity": "sha512-sZ5p2I9UA7T950JmuZ3pgdKA6+RTBr+0FpK427ExW0t7n+QwYOcmDTK/aRlzoBrWyTpJNlS3kacgSlSTUg6P/Q==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.0.tgz", + "integrity": "sha512-BTs0M5s1EJejgIBJhCeiFo7GZZ2IXWkFGcyZhxX4+8usnIo5Mti57108vjXFIQmmJaRyDwmV59Tw64Ap1dkwMw==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm64-gnu": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.47.1.tgz", - "integrity": "sha512-3hBFoqPyU89Dyf1mQRXCdpc6qC6At3LV6jbbIOZd72jcx7xNk3aAp+EjzAtN6sDlmHFzsDJN5yeUySvorWeRXA==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.0.tgz", + "integrity": "sha512-uj672IVOU9m08DBGvoPKPi/J8jlVgjh12C9GmjjBxCTQc3XtVmRkRKyeHSmIKQpvJ7fIm1EJieBUcnGSzDVFyw==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm64-musl": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.47.1.tgz", - "integrity": "sha512-49J4FnMHfGodJWPw73Ve+/hsPjZgcXQGkmqBGZFvltzBKRS+cvMiWNLadOMXKGnYRhs1ToTGM0sItKISoSGUNA==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.0.tgz", + "integrity": "sha512-/+IVbeDMDCtB/HP/wiWsSzduD10SEGzIZX2945KSgZRNi4TSkjHqRJtNTVtVb8IRwhJ65ssI56krlLik+zFWkw==", "dev": true, "optional": true }, - "@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.47.1.tgz", - "integrity": "sha512-4yYU8p7AneEpQkRX03pbpLmE21z5JNys16F1BZBZg5fP9rIlb0TkeQjn5du5w4agConCCEoYIG57sNxjryHEGg==", + "@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.0.tgz", + "integrity": "sha512-U1vVzvSWtSMWKKrGoROPBXMh3Vwn93TA9V35PldokHGqiUbF6erSzox/5qrSMKp6SzakvyjcPiVF8yB1xKr9Pg==", "dev": true, "optional": true }, "@rollup/rollup-linux-ppc64-gnu": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.47.1.tgz", - "integrity": "sha512-fAiq+J28l2YMWgC39jz/zPi2jqc0y3GSRo1yyxlBHt6UN0yYgnegHSRPa3pnHS5amT/efXQrm0ug5+aNEu9UuQ==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.0.tgz", + "integrity": "sha512-X/4WfuBAdQRH8cK3DYl8zC00XEE6aM472W+QCycpQJeLWVnHfkv7RyBFVaTqNUMsTgIX8ihMjCvFF9OUgeABzw==", "dev": true, "optional": true }, "@rollup/rollup-linux-riscv64-gnu": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.47.1.tgz", - "integrity": "sha512-daoT0PMENNdjVYYU9xec30Y2prb1AbEIbb64sqkcQcSaR0zYuKkoPuhIztfxuqN82KYCKKrj+tQe4Gi7OSm1ow==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.0.tgz", + "integrity": "sha512-xIRYc58HfWDBZoLmWfWXg2Sq8VCa2iJ32B7mqfWnkx5mekekl0tMe7FHpY8I72RXEcUkaWawRvl3qA55og+cwQ==", "dev": true, "optional": true }, "@rollup/rollup-linux-riscv64-musl": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.47.1.tgz", - "integrity": "sha512-JNyXaAhWtdzfXu5pUcHAuNwGQKevR+6z/poYQKVW+pLaYOj9G1meYc57/1Xv2u4uTxfu9qEWmNTjv/H/EpAisw==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.0.tgz", + "integrity": "sha512-mbsoUey05WJIOz8U1WzNdf+6UMYGwE3fZZnQqsM22FZ3wh1N887HT6jAOjXs6CNEK3Ntu2OBsyQDXfIjouI4dw==", "dev": true, "optional": true }, "@rollup/rollup-linux-s390x-gnu": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.47.1.tgz", - "integrity": "sha512-U/CHbqKSwEQyZXjCpY43/GLYcTVKEXeRHw0rMBJP7fP3x6WpYG4LTJWR3ic6TeYKX6ZK7mrhltP4ppolyVhLVQ==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.0.tgz", + "integrity": "sha512-qP6aP970bucEi5KKKR4AuPFd8aTx9EF6BvutvYxmZuWLJHmnq4LvBfp0U+yFDMGwJ+AIJEH5sIP+SNypauMWzg==", "dev": true, "optional": true }, "@rollup/rollup-linux-x64-gnu": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.47.1.tgz", - "integrity": "sha512-uTLEakjxOTElfeZIGWkC34u2auLHB1AYS6wBjPGI00bWdxdLcCzK5awjs25YXpqB9lS8S0vbO0t9ZcBeNibA7g==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.0.tgz", + "integrity": "sha512-nmSVN+F2i1yKZ7rJNKO3G7ZzmxJgoQBQZ/6c4MuS553Grmr7WqR7LLDcYG53Z2m9409z3JLt4sCOhLdbKQ3HmA==", "dev": true, "optional": true }, "@rollup/rollup-linux-x64-musl": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.47.1.tgz", - "integrity": "sha512-Ft+d/9DXs30BK7CHCTX11FtQGHUdpNDLJW0HHLign4lgMgBcPFN3NkdIXhC5r9iwsMwYreBBc4Rho5ieOmKNVQ==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.0.tgz", + "integrity": "sha512-2d0qRo33G6TfQVjaMR71P+yJVGODrt5V6+T0BDYH4EMfGgdC/2HWDVjSSFw888GSzAZUwuska3+zxNUCDco6rQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-openharmony-arm64": { + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.0.tgz", + "integrity": "sha512-A1JalX4MOaFAAyGgpO7XP5khquv/7xKzLIyLmhNrbiCxWpMlnsTYr8dnsWM7sEeotNmxvSOEL7F65j0HXFcFsw==", "dev": true, "optional": true }, "@rollup/rollup-win32-arm64-msvc": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.47.1.tgz", - "integrity": "sha512-N9X5WqGYzZnjGAFsKSfYFtAShYjwOmFJoWbLg3dYixZOZqU7hdMq+/xyS14zKLhFhZDhP9VfkzQnsdk0ZDS9IA==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.0.tgz", + "integrity": "sha512-YQugafP/rH0eOOHGjmNgDURrpYHrIX0yuojOI8bwCyXwxC9ZdTd3vYkmddPX0oHONLXu9Rb1dDmT0VNpjkzGGw==", "dev": true, "optional": true }, "@rollup/rollup-win32-ia32-msvc": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.47.1.tgz", - "integrity": "sha512-O+KcfeCORZADEY8oQJk4HK8wtEOCRE4MdOkb8qGZQNun3jzmj2nmhV/B/ZaaZOkPmJyvm/gW9n0gsB4eRa1eiQ==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.0.tgz", + "integrity": "sha512-zYdUYhi3Qe2fndujBqL5FjAFzvNeLxtIqfzNEVKD1I7C37/chv1VxhscWSQHTNfjPCrBFQMnynwA3kpZpZ8w4A==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-x64-gnu": { + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.0.tgz", + "integrity": "sha512-fGk03kQylNaCOQ96HDMeT7E2n91EqvCDd3RwvT5k+xNdFCeMGnj5b5hEgTGrQuyidqSsD3zJDQ21QIaxXqTBJw==", "dev": true, "optional": true }, "@rollup/rollup-win32-x64-msvc": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.47.1.tgz", - "integrity": "sha512-CpKnYa8eHthJa3c+C38v/E+/KZyF1Jdh2Cz3DyKZqEWYgrM1IHFArXNWvBLPQCKUEsAqqKX27tTqVEFbDNUcOA==", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.0.tgz", + "integrity": "sha512-6iKDCVSIUQ8jPMoIV0OytRKniaYyy5EbY/RRydmLW8ZR3cEBhxbWl5ro0rkUNe0ef6sScvhbY79HrjRm8i3vDQ==", "dev": true, "optional": true }, @@ -23201,31 +23243,33 @@ } }, "rollup": { - "version": "4.47.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.47.1.tgz", - "integrity": "sha512-iasGAQoZ5dWDzULEUX3jiW0oB1qyFOepSyDyoU6S/OhVlDIwj5knI5QBa5RRQ0sK7OE0v+8VIi2JuV+G+3tfNg==", - "dev": true, - "requires": { - "@rollup/rollup-android-arm-eabi": "4.47.1", - "@rollup/rollup-android-arm64": "4.47.1", - "@rollup/rollup-darwin-arm64": "4.47.1", - "@rollup/rollup-darwin-x64": "4.47.1", - "@rollup/rollup-freebsd-arm64": "4.47.1", - "@rollup/rollup-freebsd-x64": "4.47.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.47.1", - "@rollup/rollup-linux-arm-musleabihf": "4.47.1", - "@rollup/rollup-linux-arm64-gnu": "4.47.1", - "@rollup/rollup-linux-arm64-musl": "4.47.1", - "@rollup/rollup-linux-loongarch64-gnu": "4.47.1", - "@rollup/rollup-linux-ppc64-gnu": "4.47.1", - "@rollup/rollup-linux-riscv64-gnu": "4.47.1", - "@rollup/rollup-linux-riscv64-musl": "4.47.1", - "@rollup/rollup-linux-s390x-gnu": "4.47.1", - "@rollup/rollup-linux-x64-gnu": "4.47.1", - "@rollup/rollup-linux-x64-musl": "4.47.1", - "@rollup/rollup-win32-arm64-msvc": "4.47.1", - "@rollup/rollup-win32-ia32-msvc": "4.47.1", - "@rollup/rollup-win32-x64-msvc": "4.47.1", + "version": "4.52.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.0.tgz", + "integrity": "sha512-+IuescNkTJQgX7AkIDtITipZdIGcWF0pnVvZTWStiazUmcGA2ag8dfg0urest2XlXUi9kuhfQ+qmdc5Stc3z7g==", + "dev": true, + "requires": { + "@rollup/rollup-android-arm-eabi": "4.52.0", + "@rollup/rollup-android-arm64": "4.52.0", + "@rollup/rollup-darwin-arm64": "4.52.0", + "@rollup/rollup-darwin-x64": "4.52.0", + "@rollup/rollup-freebsd-arm64": "4.52.0", + "@rollup/rollup-freebsd-x64": "4.52.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.0", + "@rollup/rollup-linux-arm-musleabihf": "4.52.0", + "@rollup/rollup-linux-arm64-gnu": "4.52.0", + "@rollup/rollup-linux-arm64-musl": "4.52.0", + "@rollup/rollup-linux-loong64-gnu": "4.52.0", + "@rollup/rollup-linux-ppc64-gnu": "4.52.0", + "@rollup/rollup-linux-riscv64-gnu": "4.52.0", + "@rollup/rollup-linux-riscv64-musl": "4.52.0", + "@rollup/rollup-linux-s390x-gnu": "4.52.0", + "@rollup/rollup-linux-x64-gnu": "4.52.0", + "@rollup/rollup-linux-x64-musl": "4.52.0", + "@rollup/rollup-openharmony-arm64": "4.52.0", + "@rollup/rollup-win32-arm64-msvc": "4.52.0", + "@rollup/rollup-win32-ia32-msvc": "4.52.0", + "@rollup/rollup-win32-x64-gnu": "4.52.0", + "@rollup/rollup-win32-x64-msvc": "4.52.0", "@types/estree": "1.0.8", "fsevents": "~2.3.2" }, diff --git a/package.json b/package.json index d16b8c641d1..7bb742be8bf 100644 --- a/package.json +++ b/package.json @@ -125,7 +125,7 @@ "npm-run-all2": "8.0.4", "prettier": "3.6.2", "promise-polyfill": "8.3.0", - "rollup": "4.47.1", + "rollup": "4.52.0", "rollup-plugin-istanbul": "5.0.0", "sauce-connect-launcher": "1.3.2", "selenium-webdriver": "4.35.0", From b358e43a8f43acefca19eb9776709bb18cbdc8cf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 28 Sep 2025 01:22:01 +0000 Subject: [PATCH 64/64] chore(deps): update dependency wrangler to v4.38.0 --- package-lock.json | 184 +++++++++++++++++++++++----------------------- package.json | 2 +- 2 files changed, 93 insertions(+), 93 deletions(-) diff --git a/package-lock.json b/package-lock.json index f3258859d93..78eebd3abf4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -76,7 +76,7 @@ "sinon-chai": "3.7.0", "typescript": "5.8.3", "url-toolkit": "2.2.5", - "wrangler": "4.31.0" + "wrangler": "4.38.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -1754,9 +1754,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-64": { - "version": "1.20250816.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250816.0.tgz", - "integrity": "sha512-yN1Rga4ufTdrJPCP4gEqfB47i1lWi3teY5IoeQbUuKnjnCtm4pZvXur526JzCmaw60Jx+AEWf5tizdwRd5hHBQ==", + "version": "1.20250917.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250917.0.tgz", + "integrity": "sha512-0kL/kFnKUSycoo7b3PgM0nRyZ+1MGQAKaXtE6a2+SAeUkZ2FLnuFWmASi0s4rlWGsf/rlTw4AwXROePir9dUcQ==", "cpu": [ "x64" ], @@ -1770,9 +1770,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-arm64": { - "version": "1.20250816.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250816.0.tgz", - "integrity": "sha512-WyKPMQhbU+TTf4uDz3SA7ZObspg7WzyJMv/7J4grSddpdx2A4Y4SfPu3wsZleAOIMOAEVi0A1sYDhdltKM7Mxg==", + "version": "1.20250917.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250917.0.tgz", + "integrity": "sha512-3/N1QmEJsC8Byxt1SGgVp5o0r+eKjuUEMbIL2yzLk/jrMdErPXy/DGf/tXZoACU68a/gMEbbT1itkYrm85iQHg==", "cpu": [ "arm64" ], @@ -1786,9 +1786,9 @@ } }, "node_modules/@cloudflare/workerd-linux-64": { - "version": "1.20250816.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250816.0.tgz", - "integrity": "sha512-NWHOuFnVBaPRhLHw8kjPO9GJmc2P/CTYbnNlNm0EThyi57o/oDx0ldWLJqEHlrdEPOw7zEVGBqM/6M+V9agC6w==", + "version": "1.20250917.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250917.0.tgz", + "integrity": "sha512-E7sEow7CErbWY3olMmlbj6iss9r7Xb2uMyc+MKzYC9/J6yFlJd/dNHvjey9QIdxzbkC9qGe90a+KxQrjs+fspA==", "cpu": [ "x64" ], @@ -1802,9 +1802,9 @@ } }, "node_modules/@cloudflare/workerd-linux-arm64": { - "version": "1.20250816.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250816.0.tgz", - "integrity": "sha512-FR+/yhaWs7FhfC3GKsM3+usQVrGEweJ9qyh7p+R6HNwnobgKr/h5ATWvJ4obGJF6ZHHodgSe+gOSYR7fkJ1xAQ==", + "version": "1.20250917.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250917.0.tgz", + "integrity": "sha512-roOnRjxut2FUxo6HA9spbfs32naXAsnSQqsgku3iq6BYKv1QqGiFoY5bReK72N5uxmhxo7+RiTo8ZEkxA/vMIQ==", "cpu": [ "arm64" ], @@ -1818,9 +1818,9 @@ } }, "node_modules/@cloudflare/workerd-windows-64": { - "version": "1.20250816.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250816.0.tgz", - "integrity": "sha512-0lqClj2UMhFa8tCBiiX7Zhd5Bjp0V+X8oNBG6V6WsR9p9/HlIHAGgwRAM7aYkyG+8KC8xlbC89O2AXUXLpHx0g==", + "version": "1.20250917.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250917.0.tgz", + "integrity": "sha512-gslh6Ou9+kshHjR1BJX47OsbPw3/cZCvGDompvaW/URCgr7aMzljbgmBb7p0uhwGy1qCXcIt31St6pd3IEcLng==", "cpu": [ "x64" ], @@ -10366,9 +10366,9 @@ } }, "node_modules/miniflare": { - "version": "4.20250816.0", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20250816.0.tgz", - "integrity": "sha512-HuakGvmsU8aC60wsHP7Su+BgJFly1GmKbmbR/nqIz0Xlk6wcd/pp3vZ7jtbT3unf+aeBOlEO/CzcUb8xFsJLdA==", + "version": "4.20250917.0", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20250917.0.tgz", + "integrity": "sha512-A7kYEc/Y6ohiiTji4W/qGJj3aJNc/9IMj/6wLy2phD/iMjcoY8t35654gR5mHbMx0AgUolDdr3HOsHB0cYBf+Q==", "dev": true, "dependencies": { "@cspotcode/source-map-support": "0.8.1", @@ -10378,8 +10378,8 @@ "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", - "undici": "^7.10.0", - "workerd": "1.20250816.0", + "undici": "7.14.0", + "workerd": "1.20250917.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "3.22.3" @@ -13673,9 +13673,9 @@ "dev": true }, "node_modules/undici": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.11.0.tgz", - "integrity": "sha512-heTSIac3iLhsmZhUCjyS3JQEkZELateufzZuBaVM5RHXdSBMb1LPMQf5x+FH7qjsZYDP0ttAc3nnVpUB+wYbOg==", + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.14.0.tgz", + "integrity": "sha512-Vqs8HTzjpQXZeXdpsfChQTlafcMQaaIwnGwLam1wudSSjlJeQ3bw1j+TLPePgrCnCpUXx7Ba5Pdpf5OBih62NQ==", "dev": true, "engines": { "node": ">=20.18.1" @@ -13689,9 +13689,9 @@ "license": "MIT" }, "node_modules/unenv": { - "version": "2.0.0-rc.19", - "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.19.tgz", - "integrity": "sha512-t/OMHBNAkknVCI7bVB9OWjUUAwhVv9vsPIAGnNUxnu3FxPQN11rjh0sksLMzc3g7IlTgvHmOTl4JM7JHpcv5wA==", + "version": "2.0.0-rc.21", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.21.tgz", + "integrity": "sha512-Wj7/AMtE9MRnAXa6Su3Lk0LNCfqDYgfwVjwRFVum9U7wsto1imuHqk4kTm7Jni+5A0Hn7dttL6O/zjvUvoo+8A==", "dev": true, "dependencies": { "defu": "^6.1.4", @@ -14331,9 +14331,9 @@ "dev": true }, "node_modules/workerd": { - "version": "1.20250816.0", - "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250816.0.tgz", - "integrity": "sha512-5gIvHPE/3QVlQR1Sc1NdBkWmqWj/TSgIbY/f/qs9lhiLBw/Da+HbNBTVYGjvwYqEb3NQ+XQM4gAm5b2+JJaUJg==", + "version": "1.20250917.0", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250917.0.tgz", + "integrity": "sha512-0D+wWaccyYQb2Zx2DZDC77YDn9kOpkpGMCgyKgIHilghut5hBQ/adUIEseS4iuIZxBPeFSn6zFtICP0SxZ3z0g==", "dev": true, "hasInstallScript": true, "bin": { @@ -14343,11 +14343,11 @@ "node": ">=16" }, "optionalDependencies": { - "@cloudflare/workerd-darwin-64": "1.20250816.0", - "@cloudflare/workerd-darwin-arm64": "1.20250816.0", - "@cloudflare/workerd-linux-64": "1.20250816.0", - "@cloudflare/workerd-linux-arm64": "1.20250816.0", - "@cloudflare/workerd-windows-64": "1.20250816.0" + "@cloudflare/workerd-darwin-64": "1.20250917.0", + "@cloudflare/workerd-darwin-arm64": "1.20250917.0", + "@cloudflare/workerd-linux-64": "1.20250917.0", + "@cloudflare/workerd-linux-arm64": "1.20250917.0", + "@cloudflare/workerd-windows-64": "1.20250917.0" } }, "node_modules/workerpool": { @@ -14357,19 +14357,19 @@ "dev": true }, "node_modules/wrangler": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.31.0.tgz", - "integrity": "sha512-blb8NfA4BGscvSzvLm2mEQRuUTmaMCiglkqHiR3EIque78UXG39xxVtFXlKhK32qaVvGI7ejdM//HC9plVPO3w==", + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.38.0.tgz", + "integrity": "sha512-ITL4VZ4KWs8LMDEttDTrAKLktwtv1NxHBd5QIqHOczvcjnAQr+GQoE6XYQws+w8jlOjDV7KyvbFqAdyRh5om3g==", "dev": true, "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", - "@cloudflare/unenv-preset": "2.6.2", + "@cloudflare/unenv-preset": "2.7.4", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", - "miniflare": "4.20250816.0", + "miniflare": "4.20250917.0", "path-to-regexp": "6.3.0", - "unenv": "2.0.0-rc.19", - "workerd": "1.20250816.0" + "unenv": "2.0.0-rc.21", + "workerd": "1.20250917.0" }, "bin": { "wrangler": "bin/wrangler.js", @@ -14382,7 +14382,7 @@ "fsevents": "~2.3.2" }, "peerDependencies": { - "@cloudflare/workers-types": "^4.20250816.0" + "@cloudflare/workers-types": "^4.20250917.0" }, "peerDependenciesMeta": { "@cloudflare/workers-types": { @@ -14391,13 +14391,13 @@ } }, "node_modules/wrangler/node_modules/@cloudflare/unenv-preset": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.6.2.tgz", - "integrity": "sha512-C7/tW7Qy+wGOCmHXu7xpP1TF3uIhRoi7zVY7dmu/SOSGjPilK+lSQ2lIRILulZsT467ZJNlI0jBxMbd8LzkGRg==", + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.7.4.tgz", + "integrity": "sha512-KIjbu/Dt50zseJIoOOK5y4eYpSojD9+xxkePYVK1Rg9k/p/st4YyMtz1Clju/zrenJHrOH+AAcjNArOPMwH4Bw==", "dev": true, "peerDependencies": { - "unenv": "2.0.0-rc.19", - "workerd": "^1.20250802.0" + "unenv": "2.0.0-rc.21", + "workerd": "^1.20250912.0" }, "peerDependenciesMeta": { "workerd": { @@ -15939,37 +15939,37 @@ } }, "@cloudflare/workerd-darwin-64": { - "version": "1.20250816.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250816.0.tgz", - "integrity": "sha512-yN1Rga4ufTdrJPCP4gEqfB47i1lWi3teY5IoeQbUuKnjnCtm4pZvXur526JzCmaw60Jx+AEWf5tizdwRd5hHBQ==", + "version": "1.20250917.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250917.0.tgz", + "integrity": "sha512-0kL/kFnKUSycoo7b3PgM0nRyZ+1MGQAKaXtE6a2+SAeUkZ2FLnuFWmASi0s4rlWGsf/rlTw4AwXROePir9dUcQ==", "dev": true, "optional": true }, "@cloudflare/workerd-darwin-arm64": { - "version": "1.20250816.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250816.0.tgz", - "integrity": "sha512-WyKPMQhbU+TTf4uDz3SA7ZObspg7WzyJMv/7J4grSddpdx2A4Y4SfPu3wsZleAOIMOAEVi0A1sYDhdltKM7Mxg==", + "version": "1.20250917.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250917.0.tgz", + "integrity": "sha512-3/N1QmEJsC8Byxt1SGgVp5o0r+eKjuUEMbIL2yzLk/jrMdErPXy/DGf/tXZoACU68a/gMEbbT1itkYrm85iQHg==", "dev": true, "optional": true }, "@cloudflare/workerd-linux-64": { - "version": "1.20250816.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250816.0.tgz", - "integrity": "sha512-NWHOuFnVBaPRhLHw8kjPO9GJmc2P/CTYbnNlNm0EThyi57o/oDx0ldWLJqEHlrdEPOw7zEVGBqM/6M+V9agC6w==", + "version": "1.20250917.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250917.0.tgz", + "integrity": "sha512-E7sEow7CErbWY3olMmlbj6iss9r7Xb2uMyc+MKzYC9/J6yFlJd/dNHvjey9QIdxzbkC9qGe90a+KxQrjs+fspA==", "dev": true, "optional": true }, "@cloudflare/workerd-linux-arm64": { - "version": "1.20250816.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250816.0.tgz", - "integrity": "sha512-FR+/yhaWs7FhfC3GKsM3+usQVrGEweJ9qyh7p+R6HNwnobgKr/h5ATWvJ4obGJF6ZHHodgSe+gOSYR7fkJ1xAQ==", + "version": "1.20250917.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250917.0.tgz", + "integrity": "sha512-roOnRjxut2FUxo6HA9spbfs32naXAsnSQqsgku3iq6BYKv1QqGiFoY5bReK72N5uxmhxo7+RiTo8ZEkxA/vMIQ==", "dev": true, "optional": true }, "@cloudflare/workerd-windows-64": { - "version": "1.20250816.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250816.0.tgz", - "integrity": "sha512-0lqClj2UMhFa8tCBiiX7Zhd5Bjp0V+X8oNBG6V6WsR9p9/HlIHAGgwRAM7aYkyG+8KC8xlbC89O2AXUXLpHx0g==", + "version": "1.20250917.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250917.0.tgz", + "integrity": "sha512-gslh6Ou9+kshHjR1BJX47OsbPw3/cZCvGDompvaW/URCgr7aMzljbgmBb7p0uhwGy1qCXcIt31St6pd3IEcLng==", "dev": true, "optional": true }, @@ -21927,9 +21927,9 @@ "dev": true }, "miniflare": { - "version": "4.20250816.0", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20250816.0.tgz", - "integrity": "sha512-HuakGvmsU8aC60wsHP7Su+BgJFly1GmKbmbR/nqIz0Xlk6wcd/pp3vZ7jtbT3unf+aeBOlEO/CzcUb8xFsJLdA==", + "version": "4.20250917.0", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20250917.0.tgz", + "integrity": "sha512-A7kYEc/Y6ohiiTji4W/qGJj3aJNc/9IMj/6wLy2phD/iMjcoY8t35654gR5mHbMx0AgUolDdr3HOsHB0cYBf+Q==", "dev": true, "requires": { "@cspotcode/source-map-support": "0.8.1", @@ -21939,8 +21939,8 @@ "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", - "undici": "^7.10.0", - "workerd": "1.20250816.0", + "undici": "7.14.0", + "workerd": "1.20250917.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "3.22.3" @@ -24348,9 +24348,9 @@ "dev": true }, "undici": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.11.0.tgz", - "integrity": "sha512-heTSIac3iLhsmZhUCjyS3JQEkZELateufzZuBaVM5RHXdSBMb1LPMQf5x+FH7qjsZYDP0ttAc3nnVpUB+wYbOg==", + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.14.0.tgz", + "integrity": "sha512-Vqs8HTzjpQXZeXdpsfChQTlafcMQaaIwnGwLam1wudSSjlJeQ3bw1j+TLPePgrCnCpUXx7Ba5Pdpf5OBih62NQ==", "dev": true }, "undici-types": { @@ -24360,9 +24360,9 @@ "dev": true }, "unenv": { - "version": "2.0.0-rc.19", - "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.19.tgz", - "integrity": "sha512-t/OMHBNAkknVCI7bVB9OWjUUAwhVv9vsPIAGnNUxnu3FxPQN11rjh0sksLMzc3g7IlTgvHmOTl4JM7JHpcv5wA==", + "version": "2.0.0-rc.21", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.21.tgz", + "integrity": "sha512-Wj7/AMtE9MRnAXa6Su3Lk0LNCfqDYgfwVjwRFVum9U7wsto1imuHqk4kTm7Jni+5A0Hn7dttL6O/zjvUvoo+8A==", "dev": true, "requires": { "defu": "^6.1.4", @@ -24854,16 +24854,16 @@ "dev": true }, "workerd": { - "version": "1.20250816.0", - "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250816.0.tgz", - "integrity": "sha512-5gIvHPE/3QVlQR1Sc1NdBkWmqWj/TSgIbY/f/qs9lhiLBw/Da+HbNBTVYGjvwYqEb3NQ+XQM4gAm5b2+JJaUJg==", + "version": "1.20250917.0", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250917.0.tgz", + "integrity": "sha512-0D+wWaccyYQb2Zx2DZDC77YDn9kOpkpGMCgyKgIHilghut5hBQ/adUIEseS4iuIZxBPeFSn6zFtICP0SxZ3z0g==", "dev": true, "requires": { - "@cloudflare/workerd-darwin-64": "1.20250816.0", - "@cloudflare/workerd-darwin-arm64": "1.20250816.0", - "@cloudflare/workerd-linux-64": "1.20250816.0", - "@cloudflare/workerd-linux-arm64": "1.20250816.0", - "@cloudflare/workerd-windows-64": "1.20250816.0" + "@cloudflare/workerd-darwin-64": "1.20250917.0", + "@cloudflare/workerd-darwin-arm64": "1.20250917.0", + "@cloudflare/workerd-linux-64": "1.20250917.0", + "@cloudflare/workerd-linux-arm64": "1.20250917.0", + "@cloudflare/workerd-windows-64": "1.20250917.0" } }, "workerpool": { @@ -24873,26 +24873,26 @@ "dev": true }, "wrangler": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.31.0.tgz", - "integrity": "sha512-blb8NfA4BGscvSzvLm2mEQRuUTmaMCiglkqHiR3EIque78UXG39xxVtFXlKhK32qaVvGI7ejdM//HC9plVPO3w==", + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.38.0.tgz", + "integrity": "sha512-ITL4VZ4KWs8LMDEttDTrAKLktwtv1NxHBd5QIqHOczvcjnAQr+GQoE6XYQws+w8jlOjDV7KyvbFqAdyRh5om3g==", "dev": true, "requires": { "@cloudflare/kv-asset-handler": "0.4.0", - "@cloudflare/unenv-preset": "2.6.2", + "@cloudflare/unenv-preset": "2.7.4", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", "fsevents": "~2.3.2", - "miniflare": "4.20250816.0", + "miniflare": "4.20250917.0", "path-to-regexp": "6.3.0", - "unenv": "2.0.0-rc.19", - "workerd": "1.20250816.0" + "unenv": "2.0.0-rc.21", + "workerd": "1.20250917.0" }, "dependencies": { "@cloudflare/unenv-preset": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.6.2.tgz", - "integrity": "sha512-C7/tW7Qy+wGOCmHXu7xpP1TF3uIhRoi7zVY7dmu/SOSGjPilK+lSQ2lIRILulZsT467ZJNlI0jBxMbd8LzkGRg==", + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.7.4.tgz", + "integrity": "sha512-KIjbu/Dt50zseJIoOOK5y4eYpSojD9+xxkePYVK1Rg9k/p/st4YyMtz1Clju/zrenJHrOH+AAcjNArOPMwH4Bw==", "dev": true, "requires": {} } diff --git a/package.json b/package.json index 7bb742be8bf..e89f91da820 100644 --- a/package.json +++ b/package.json @@ -134,6 +134,6 @@ "sinon-chai": "3.7.0", "typescript": "5.8.3", "url-toolkit": "2.2.5", - "wrangler": "4.31.0" + "wrangler": "4.38.0" } }