diff --git a/.eslintrc.js b/.eslintrc.js index 6826aa5413b..1a0d2722a64 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,9 +1,16 @@ +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 method is restricted for compatibility.'; +const arrayFindIndexCompatibilityMsg = + '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, - }, + env: { browser: true, commonjs: true, es6: true }, globals: { // Allowed globals console: true, @@ -24,49 +31,25 @@ module.exports = { // 'prettier' (https://github.com/prettier/eslint-config-prettier) must be last extends: ['eslint:recommended', 'prettier'], parser: '@typescript-eslint/parser', - parserOptions: { - sourceType: 'module', - project: './tsconfig.json', - }, - plugins: ['@typescript-eslint', 'import'], + parserOptions: { sourceType: 'module', project: './tsconfig.json' }, + plugins: ['@typescript-eslint', 'import', 'no-for-of-loops'], rules: { 'no-restricted-globals': [ 2, { name: 'window', - message: - 'Use `self` instead of `window` to access the global context everywhere (including workers).', - }, - { - name: 'SourceBuffer', - message: 'Use `self.SourceBuffer`', - }, - { - name: 'setTimeout', - message: 'Use `self.setTimeout`', - }, - { - name: 'setInterval', - message: 'Use `self.setInterval`', + message: selfVsWindowGlobalMsg, }, - ], - - 'no-restricted-properties': [ - 2, - { property: 'findIndex' }, // Intended to block usage of Array.prototype.findIndex - { property: 'find' }, // Intended to block usage of Array.prototype.find + { name: 'SourceBuffer', message: 'Use `self.SourceBuffer`' }, + { name: 'setTimeout', message: 'Use `self.setTimeout`' }, + { name: 'setInterval', message: 'Use `self.setInterval`' }, ], 'import/first': 1, 'no-var': 1, 'no-empty': 1, 'no-unused-vars': 'warn', - 'no-console': [ - 1, - { - allow: ['assert'], - }, - ], + 'no-console': [1, { allow: ['assert'] }], 'no-fallthrough': 1, 'no-case-declarations': 2, 'no-self-assign': 1, @@ -78,23 +61,50 @@ module.exports = { 'no-void': 2, 'no-useless-catch': 2, 'no-prototype-builtins': 0, + 'no-for-of-loops/no-for-of-loops': 2, }, overrides: [ { - parserOptions: { - project: ['./tsconfig.json'], - }, + parserOptions: { project: ['./tsconfig.json'] }, files: ['*.ts'], rules: { '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, + }, + { + selector: + 'MemberExpression[property.name="every"][object.type="Identifier"]', + message: arrayEveryCompatibilityMsg, + }, + ], 'import/order': [ 'warn', { - alphabetize: { - order: 'asc', - }, + alphabetize: { order: 'asc' }, groups: [ 'builtin', 'external', @@ -108,29 +118,22 @@ module.exports = { ], 'sort-imports': [ 'error', - { - ignoreCase: true, - ignoreDeclarationSort: true, - }, + { ignoreCase: true, ignoreDeclarationSort: true }, ], '@typescript-eslint/no-unused-vars': [ 'warn', - { - args: 'none', - caughtErrors: 'none', - }, + { args: 'none', caughtErrors: 'none' }, ], '@typescript-eslint/prefer-optional-chain': 2, '@typescript-eslint/consistent-type-assertions': [ 2, - { - assertionStyle: 'as', - objectLiteralTypeAssertions: 'never', - }, + { assertionStyle: 'as', objectLiteralTypeAssertions: 'never' }, ], '@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/.github/workflows/build.yml b/.github/workflows/build.yml index c1e00f4a70f..fba3c5ce9de 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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 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 diff --git a/.node-version b/.node-version index 7d41c735d71..91d5f6ff8e3 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -22.14.0 +22.18.0 diff --git a/MIGRATING.md b/MIGRATING.md index a6f83122cba..04a37bb42c4 100644 --- a/MIGRATING.md +++ b/MIGRATING.md @@ -4,7 +4,10 @@ The 1.4 version of hls.js now ships with an ESM version of the library (`dist/hl If you are using the UMD version (`dist/hls.js`), no changes are required. -**Important Note:** If you are using a bundler, such as webpack, the ESM version of the package will likely be used by default. If this is the case, make sure to add the `workerPath` config option after upgrading to hls.js 1.4 or above. +**Important Notes:** + +- If you are using a bundler, such as webpack, the ESM version of the package will likely be used by default. If this is the case, make sure to add the `workerPath` config option after upgrading to hls.js 1.4 or above. +- Older web engines may experience severe performance degradation when using the ESM version of this package. If targeting older web engines, consider bundling your client application in `loose` mode to avoid additional function overhead. Alternatively, use the UMD/ES5 version of the package. # Migrating from hls.js 0.x to 1.x diff --git a/README.md b/README.md index 1766a9f902d..43f189ce659 100644 --- a/README.md +++ b/README.md @@ -443,6 +443,7 @@ The following players integrate HLS.js for HLS playback: - [CDNBye](https://github.com/cdnbye/hlsjs-p2p-engine), a p2p engine for hls.js powered by WebRTC Datachannel. - [M3U IPTV](http://m3u-ip.tv/browser/) - [ArtPlayer](https://artplayer.org/?libs=https://cdnjs.cloudflare.com/ajax/libs/hls.js/1.5.17/hls.min.js&example=hls) +- [IPTV Player](https://iptvplayer.stream), A free web-based HLS player that lets you play HLS,DASH and MP4 streams ### They use HLS.js in production! diff --git a/api-extractor/report/hls.js.api.md b/api-extractor/report/hls.js.api.md index cef61beaeb1..eb9591fb944 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) @@ -183,10 +185,10 @@ export type AudioSelectionOption = { export class AudioStreamController extends BaseStreamController implements NetworkComponentAPI { constructor(hls: Hls, fragmentTracker: FragmentTracker, keyLoader: KeyLoader); // (undocumented) - clearWaitingFragment(): void; - // (undocumented) doTick(): void; // (undocumented) + protected getLoadPosition(): number; + // (undocumented) protected _handleFragmentLoadComplete(fragLoadedData: FragLoadedData): void; // (undocumented) _handleFragmentLoadProgress(data: FragLoadedData): void; @@ -197,7 +199,7 @@ export class AudioStreamController extends BaseStreamController implements Netwo // (undocumented) protected onHandlerDestroying(): void; // (undocumented) - onInitPtsFound(event: Events.INIT_PTS_FOUND, { frag, id, initPTS, timescale }: InitPTSFoundData): void; + onInitPtsFound(event: Events.INIT_PTS_FOUND, { frag, id, initPTS, timescale, trackId }: InitPTSFoundData): void; // (undocumented) protected onManifestLoading(): void; // (undocumented) @@ -392,6 +394,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; @@ -434,7 +438,7 @@ export class BaseStreamController extends TaskLoop implements NetworkComponentAP // (undocumented) protected getFwdBufferInfoAtPos(bufferable: Bufferable | null, pos: number, type: PlaylistLevelType, maxBufferHole: number): BufferInfo | null; // (undocumented) - protected getInitialLiveFragment(levelDetails: LevelDetails, fragments: MediaFragment[]): MediaFragment | null; + protected getInitialLiveFragment(levelDetails: LevelDetails): MediaFragment | null; // (undocumented) getLevelDetails(): LevelDetails | undefined; // (undocumented) @@ -458,7 +462,7 @@ export class BaseStreamController extends TaskLoop implements NetworkComponentAP // (undocumented) get inFlightFrag(): InFlightData; // (undocumented) - protected initPTS: RationalTimestamp[]; + protected initPTS: TimestampOffset[]; // (undocumented) protected isLoopLoading(frag: Fragment, targetBufferTime: number): boolean; // (undocumented) @@ -528,8 +532,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; @@ -563,6 +565,8 @@ export class BaseStreamController extends TaskLoop implements NetworkComponentAP // (undocumented) protected transmuxer: TransmuxerInterface | null; // (undocumented) + protected unhandledEncryptionError(initSegment: InitSegmentData, frag: Fragment): boolean; + // (undocumented) protected unregisterListeners(): void; // (undocumented) protected waitForCdnTuneIn(details: LevelDetails): boolean | 0; @@ -579,6 +583,8 @@ export interface BaseTrack { // (undocumented) container: string; // (undocumented) + encrypted?: boolean; + // (undocumented) id: 'audio' | 'main'; // (undocumented) levelCodec?: string; @@ -631,10 +637,12 @@ export interface BufferAppendingData { // (undocumented) chunkMeta: ChunkMetadata; // (undocumented) - data: Uint8Array; + data: Uint8Array; // (undocumented) frag: Fragment; // (undocumented) + offset?: number | undefined; + // (undocumented) parent: PlaylistLevelType; // (undocumented) part: Part | null; @@ -960,6 +968,11 @@ export type ContentSteeringOptions = { pathwayId: string; }; +// Warning: (ae-missing-release-tag) "Cues" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const Cues: CuesInterface; + // Warning: (ae-missing-release-tag) "CuesInterface" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1159,8 +1172,14 @@ export class EMEController extends Logger implements ComponentAPI { // (undocumented) destroy(): void; // (undocumented) + getKeySystemAccess(keySystemsToAttempt: KeySystems[]): Promise; + // (undocumented) + getSelectedKeySystemFormats(): KeySystemFormats[]; + // (undocumented) loadKey(data: KeyLoadedData): Promise; // (undocumented) + selectKeySystem(keySystemsToAttempt: KeySystems[]): Promise; + // (undocumented) selectKeySystemFormat(frag: Fragment): Promise; } @@ -1172,9 +1191,10 @@ 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; }; // Warning: (ae-missing-release-tag) "ErrorActionFlags" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -1186,9 +1206,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) @@ -1221,6 +1243,8 @@ export interface ErrorData { // (undocumented) context?: PlaylistLoaderContext; // (undocumented) + decryptdata?: LevelKey; + // (undocumented) details: ErrorDetails; // @deprecated (undocumented) err?: { @@ -1247,7 +1271,7 @@ export interface ErrorData { // (undocumented) mimeType?: string; // (undocumented) - networkDetails?: any; + networkDetails?: NullableNetworkDetails; // (undocumented) parent?: PlaylistLevelType; // (undocumented) @@ -1576,6 +1600,29 @@ export type ExtendedSourceBuffer = SourceBuffer & { onbufferedchange?: ((this: SourceBuffer, ev: Event) => any) | null; }; +// Warning: (ae-missing-release-tag) "FetchLoader" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export class FetchLoader implements Loader { + constructor(config: HlsConfig); + // (undocumented) + abort(): void; + // (undocumented) + abortInternal(): void; + // (undocumented) + context: LoaderContext | null; + // (undocumented) + destroy(): void; + // (undocumented) + getCacheAge(): number | null; + // (undocumented) + getResponseHeader(name: string): string | null; + // (undocumented) + load(context: LoaderContext, config: LoaderConfiguration, callbacks: LoaderCallbacks): void; + // (undocumented) + stats: LoaderStats; +} + // Warning: (ae-missing-release-tag) "FPSController" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1672,7 +1719,7 @@ export interface FragLoadedData { // (undocumented) frag: Fragment; // (undocumented) - networkDetails: unknown; + networkDetails: NullableNetworkDetails; // (undocumented) part: Part | null; // (undocumented) @@ -1698,7 +1745,7 @@ export interface FragLoadFailResult extends ErrorData { // (undocumented) frag: Fragment; // (undocumented) - networkDetails: any; + networkDetails: NullableNetworkDetails; // (undocumented) part?: Part; // (undocumented) @@ -1770,7 +1817,7 @@ export class Fragment extends BaseSegment { level: number; // (undocumented) levelkeys?: { - [key: string]: LevelKey; + [key: string]: LevelKey | undefined; }; // (undocumented) loader: Loader | null; @@ -1888,7 +1935,7 @@ export class FragmentTracker implements ComponentAPI { detectPartialFragments(data: FragBufferedData): void; // (undocumented) fragBuffered(frag: MediaFragment, force?: true): void; - getAppendedFrag(position: number, levelType: PlaylistLevelType): Fragment | Part | null; + getAppendedFrag(position: number, levelType: PlaylistLevelType): MediaFragment | Part | null; getBufferedFrag(position: number, levelType: PlaylistLevelType): MediaFragment | null; // (undocumented) getFragAtPos(position: number, levelType: PlaylistLevelType, buffered?: boolean): MediaFragment | null; @@ -2127,7 +2174,9 @@ export default Hls; // // @public (undocumented) export class HlsAssetPlayer { - constructor(HlsPlayerClass: typeof Hls, userConfig: Partial, interstitial: InterstitialEvent, assetItem: InterstitialAssetItem); + constructor(HlsPlayerClass: typeof Hls, userConfig: HlsAssetPlayerConfig, interstitial: InterstitialEvent, assetItem: InterstitialAssetItem); + // (undocumented) + get appendInPlace(): boolean; // (undocumented) get assetId(): InterstitialAssetId; // (undocumented) @@ -2149,12 +2198,14 @@ export class HlsAssetPlayer { // (undocumented) get duration(): number; // (undocumented) - readonly hls: Hls; + hls: Hls | null; // (undocumented) - readonly interstitial: InterstitialEvent; + interstitial: InterstitialEvent; // (undocumented) get interstitialId(): InterstitialId; // (undocumented) + loadSource(): void; + // (undocumented) get media(): HTMLMediaElement | null; // (undocumented) off(event: E, listener: HlsListeners[E], context?: Context): void; @@ -2167,6 +2218,8 @@ export class HlsAssetPlayer { // (undocumented) get remaining(): number; // (undocumented) + resetDetails(): void; + // (undocumented) resumeBuffering(): void; // (undocumented) get startOffset(): number; @@ -2181,6 +2234,11 @@ export class HlsAssetPlayer { transferMedia(): AttachMediaSourceData | null; } +// Warning: (ae-missing-release-tag) "HlsAssetPlayerConfig" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type HlsAssetPlayerConfig = Partial & Required>; + // Warning: (ae-missing-release-tag) "HlsChunkPerformanceTiming" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -2203,6 +2261,7 @@ export type HlsConfig = { ignoreDevicePixelRatio: boolean; maxDevicePixelRatio: number; preferManagedMediaSource: boolean; + preserveManualLevelOnError: boolean; timelineOffset?: number; ignorePlaylistParsingErrors: boolean; loader: { @@ -2537,6 +2596,8 @@ export interface InitPTSFoundData { initPTS: number; // (undocumented) timescale: number; + // (undocumented) + trackId: number; } // Warning: (ae-missing-release-tag) "InitSegmentData" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -2548,6 +2609,8 @@ export interface InitSegmentData { // (undocumented) timescale: number | undefined; // (undocumented) + trackId: number | undefined; + // (undocumented) tracks?: TrackSet; } @@ -2749,6 +2812,8 @@ export interface InterstitialPlayer { // (undocumented) assetPlayers: (HlsAssetPlayer | null)[]; // (undocumented) + bufferedEnd: number; + // (undocumented) currentTime: number; // (undocumented) duration: number; @@ -2922,12 +2987,12 @@ 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) - 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; @@ -2940,13 +3005,9 @@ 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[]): void | Promise; + loadClear(loadingFrag: Fragment, encryptedFragments: Fragment[], startFragRequested: boolean): null | Promise; // (undocumented) loadInternal(frag: Fragment, keySystemFormat?: KeySystemFormats): Promise; // (undocumented) @@ -3185,7 +3246,7 @@ export class LevelDetails { // (undocumented) canSkipUntil: number; // (undocumented) - dateRanges: Record; + dateRanges: Record; // (undocumented) dateRangeTagCount: number; // (undocumented) @@ -3219,6 +3280,8 @@ export class LevelDetails { // (undocumented) get fragmentStart(): number; // (undocumented) + hasKey(levelKey: LevelKey): boolean; + // (undocumented) get hasProgramDateTime(): boolean; // (undocumented) hasVariableRefs: boolean; @@ -3290,7 +3353,7 @@ export class LevelDetails { // // @public (undocumented) export class LevelKey implements DecryptData { - constructor(method: string, uri: string, format: string, formatversions?: number[], iv?: Uint8Array | null); + constructor(method: string, uri: string, format: string, formatversions?: number[], iv?: Uint8Array | null, keyId?: string); // (undocumented) static clearKeyUriToKeyIdMap(): void; // (undocumented) @@ -3312,6 +3375,8 @@ export class LevelKey implements DecryptData { // (undocumented) keyId: Uint8Array | null; // (undocumented) + matches(key: LevelKey): boolean; + // (undocumented) readonly method: string; // (undocumented) pssh: Uint8Array | null; @@ -3334,7 +3399,7 @@ export interface LevelLoadedData { // (undocumented) levelInfo: Level; // (undocumented) - networkDetails: any; + networkDetails: NullableNetworkDetails; // (undocumented) stats: LoaderStats; // (undocumented) @@ -3576,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) // @@ -3584,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) // @@ -3698,6 +3763,38 @@ export class Logger implements ILogger { warn: ILogFunction; } +// Warning: (ae-missing-release-tag) "M3U8Parser" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export class M3U8Parser { + // (undocumented) + static findGroup(groups: ({ + id?: string; + audioCodec?: string; + } | { + id?: string; + textCodec?: string; + })[], mediaGroupId: string): { + id?: string; + audioCodec?: string; + } | { + id?: string; + textCodec?: string; + } | undefined; + // (undocumented) + static isMediaPlaylist(str: string): boolean; + // (undocumented) + static parseLevelPlaylist(string: string, baseurl: string, id: number, type: PlaylistLevelType, levelUrlId: number, multivariantVariableList: VariableMap | null): LevelDetails; + // (undocumented) + static parseMasterPlaylist(string: string, baseurl: string): ParsedMultivariantPlaylist; + // Warning: (ae-forgotten-export) The symbol "ParsedMultivariantMediaOptions" needs to be exported by the entry point hls.d.ts + // + // (undocumented) + static parseMasterPlaylistMedia(string: string, baseurl: string, parsed: ParsedMultivariantPlaylist): ParsedMultivariantMediaOptions; + // (undocumented) + static resolve(url: any, baseUrl: any): string; +} + // Warning: (ae-missing-release-tag) "MainPlaylistType" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -3716,7 +3813,7 @@ export interface ManifestLoadedData { // (undocumented) levels: LevelParsed[]; // (undocumented) - networkDetails: any; + networkDetails: NullableNetworkDetails; // (undocumented) sessionData: Record | null; // (undocumented) @@ -3918,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) @@ -3960,6 +4056,8 @@ export interface MediaPlaylist { // (undocumented) textCodec?: string; // (undocumented) + trackNode?: HTMLTrackElement; + // (undocumented) type: MediaPlaylistType | 'main'; // (undocumented) unknownCodecs?: string[]; @@ -4263,12 +4361,14 @@ export interface RemuxedMetadata { // @public (undocumented) export interface RemuxedTrack { // (undocumented) - data1: Uint8Array; + data1: Uint8Array; // (undocumented) - data2?: Uint8Array; + data2?: Uint8Array; // (undocumented) dropped?: number; // (undocumented) + encrypted?: boolean; + // (undocumented) endDTS: number; // (undocumented) endPTS: number; @@ -4497,6 +4597,8 @@ export type StreamControllerConfig = { maxMaxBufferLength: number; startFragPrefetch: boolean; testBandwidth: boolean; + liveSyncMode?: 'edge' | 'buffered'; + startOnSegmentBoundary: boolean; }; // Warning: (ae-missing-release-tag) "SubtitleFragProcessedData" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -4711,6 +4813,13 @@ export enum TimelineOccupancy { Range = 1 } +// Warning: (ae-missing-release-tag) "TimestampOffset" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type TimestampOffset = RationalTimestamp & { + trackId: number; +}; + // Warning: (ae-missing-release-tag) "Track" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -4734,7 +4843,7 @@ export interface TrackLoadedData { // (undocumented) id: number; // (undocumented) - networkDetails: any; + networkDetails: NullableNetworkDetails; // (undocumented) stats: LoaderStats; // (undocumented) @@ -4781,7 +4890,7 @@ export class TransmuxerInterface { // (undocumented) flush(chunkMeta: ChunkMetadata): void; // (undocumented) - push(data: ArrayBuffer, initSegmentData: Uint8Array | undefined, audioCodec: string | undefined, videoCodec: string | undefined, frag: MediaFragment, part: Part | null, duration: number, accurateTimeOffset: boolean, chunkMeta: ChunkMetadata, defaultInitPTS?: RationalTimestamp): void; + push(data: ArrayBuffer, initSegmentData: Uint8Array | undefined, audioCodec: string | undefined, videoCodec: string | undefined, frag: MediaFragment, part: Part | null, duration: number, accurateTimeOffset: boolean, chunkMeta: ChunkMetadata, defaultInitPTS?: TimestampOffset): void; // (undocumented) reset(): void; } @@ -4863,6 +4972,41 @@ export type VideoSelectionOption = { videoCodec?: string; }; +// Warning: (ae-missing-release-tag) "XhrLoader" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export class XhrLoader implements Loader { + constructor(config: HlsConfig); + // (undocumented) + abort(): void; + // (undocumented) + abortInternal(): void; + // (undocumented) + context: LoaderContext | null; + // (undocumented) + destroy(): void; + // (undocumented) + getCacheAge(): number | null; + // (undocumented) + getResponseHeader(name: string): string | null; + // (undocumented) + load(context: LoaderContext, config: LoaderConfiguration, callbacks: LoaderCallbacks): void; + // (undocumented) + loadInternal(): void; + // (undocumented) + loadprogress(event: ProgressEvent): void; + // (undocumented) + loadtimeout(): void; + // (undocumented) + openAndSendXhr(xhr: XMLHttpRequest, context: LoaderContext, config: LoaderConfiguration): void; + // (undocumented) + readystatechange(): void; + // (undocumented) + retry(retryConfig: RetryConfig): void; + // (undocumented) + stats: LoaderStats; +} + // (No @packageDocumentation comment for this package) ``` diff --git a/build-config.js b/build-config.js index 78dbc2464e5..755e6baea58 100644 --- a/build-config.js +++ b/build-config.js @@ -136,12 +136,12 @@ const babelTsWithPresetEnvTargets = ({ targets, stripConsole }) => ], plugins: [ [ - '@babel/plugin-proposal-class-properties', + '@babel/plugin-transform-class-properties', { loose: true, }, ], - '@babel/plugin-proposal-object-rest-spread', + '@babel/plugin-transform-object-rest-spread', { visitor: { CallExpression: function (espath) { @@ -172,7 +172,7 @@ const babelTsWithPresetEnvTargets = ({ targets, stripConsole }) => }, }, ['@babel/plugin-transform-object-assign'], - ['@babel/plugin-proposal-optional-chaining'], + ['@babel/plugin-transform-optional-chaining'], ...(stripConsole ? [ 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/docs/API.md b/docs/API.md index 5a0583a7e1d..359c5ddbd7d 100644 --- a/docs/API.md +++ b/docs/API.md @@ -30,6 +30,7 @@ See [API Reference](https://hlsjs-dev.video-dev.org/api-docs/) for a complete li - [`maxBufferLength`](#maxbufferlength) - [`backBufferLength`](#backbufferlength) - [`frontBufferFlushThreshold`](#frontbufferflushthreshold) + - [`startOnSegmentBoundary`](#startonsegmentboundary) - [`maxBufferSize`](#maxbuffersize) - [`maxBufferHole`](#maxbufferhole) - [`maxStarvationDelay`](#maxstarvationdelay) @@ -42,6 +43,7 @@ See [API Reference](https://hlsjs-dev.video-dev.org/api-docs/) for a complete li - [`nudgeOnVideoHole`](#nudgeonvideohole) - [`maxFragLookUpTolerance`](#maxfraglookuptolerance) - [`maxMaxBufferLength`](#maxmaxbufferlength) + - [`liveSyncMode`](#livesyncmode) - [`liveSyncDurationCount`](#livesyncdurationcount) - [`liveSyncOnStallIncrease`](#livesynconstallincrease) - [`liveMaxLatencyDurationCount`](#livemaxlatencydurationcount) @@ -348,22 +350,52 @@ hls.on(Hls.Events.ERROR, function (event, data) { #### Fatal Error Recovery -HLS.js provides several methods for attempting playback recover in the event of a decoding error in the HTMLMediaElement: +HLS.js provides methods for attempting playback recover in the event of a decoding error in the HTMLMediaElement: ##### `hls.recoverMediaError()` -Resets the MediaSource and restarts streaming from the last known playhead position. +Resets the MediaSource and restarts streaming from the last known playhead position. This should only be used when the media element is in an error state. +It should not be used in response to non-fatal hls.js error events. ###### Error recovery sample code ```js -hls.on(Hls.Events.ERROR, function (event, data) { +let attemptedErrorRecovery = null; + +video.addEventListener('error', (event) => { + const mediaError = event.currentTarget.error; + if (mediaError.code === mediaError.MEDIA_ERR_DECODE) { + const now = Date.now(); + if (!attemptedErrorRecovery || now - attemptedErrorRecovery > 5000) { + attemptedErrorRecovery = now; + hls.recoverMediaError(); + } + } +}); + +hls.on(Hls.Events.ERROR, function (name, data) { + // Special handling is only needed to errors flagged as `fatal`. if (data.fatal) { switch (data.type) { - case Hls.ErrorTypes.MEDIA_ERROR: - console.log('fatal media error encountered, try to recover'); - hls.recoverMediaError(); + 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', + ); + attemptedErrorRecovery = now; + hls.recoverMediaError(); + } else { + console.log( + 'Skipping media error recovery (only ' + + (now - attemptedErrorRecovery) + + 'ms since last error)', + ); + } break; + } case Hls.ErrorTypes.NETWORK_ERROR: console.error('fatal network error encountered', data); // All retries and media options have been exhausted. @@ -537,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;`. @@ -590,6 +624,13 @@ The maximum duration of buffered media to keep once it has been played, in secon The maximum duration of buffered media, in seconds, from the play position to keep before evicting non-contiguous forward ranges. A value of `Infinity` means no active eviction will take place; This value will always be at least the `maxBufferLength`. +### `startOnSegmentBoundary` + +(default: `false`) + +When set to `true`, the player will align the live start position with the closest video segment boundary when preparing playback. This ensures playback starts at a clean segment boundary rather than potentially in the middle of a segment, which can prevent some segment skipping. This is helpful for when liveSyncDurationCount or liveSyncDuration, do not calculate start position to be the start position of a segment. +Setting this to `true` may increase initial live playback latency slightly, but can provide more stable playback start. When set to `false`, playback will start at the exact position determined by the player's live sync calculations, which could be in the middle of a segment. + ### `maxBufferSize` (default: 60 MB) @@ -693,6 +734,15 @@ this is to mimic the browser behaviour (the buffer eviction algorithm is startin `maxBufferLength` is the minimum guaranteed buffer length that HLS.js will try to achieve, even if that value exceeds the amount of bytes 60 MB of memory. `maxMaxBufferLength` acts as a capping value, as if bitrate is really low, you could need more than one hour of buffer to fill 60 MB. +### `liveSyncMode` + +(default: `'edge'`) + +Controls how playback synchronizes to the live edge: + +- `'edge'`: When the playhead moves outside the prescribed distance from the live edge, immediately jump to `liveSyncPosition`. +- `'buffered'`: When the playhead moves outside the prescribed distance from the live edge, if `liveSyncPosition` is buffered, seek there; otherwise, continue playback from the start of the next buffered segment. + ### `liveSyncDurationCount` (default: `3`) @@ -716,10 +766,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` @@ -736,7 +786,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. diff --git a/docs/design.md b/docs/design.md index 5e2c8244cad..be372055927 100644 --- a/docs/design.md +++ b/docs/design.md @@ -64,7 +64,6 @@ design idea is pretty simple : - [src/controller/id3-track-controller.ts][] - in charge of creating the id3 metadata text track and adding cues to that track in response to the FRAG_PARSING_METADATA event. the raw id3 data is base64 encoded and stored in the cue's text property. - [src/controller/level-controller.ts][] - - handling quality level set/get ((re)loading stream manifest/switching levels) - in charge of scheduling playlist (re)loading - monitors fragment and key loading errors. Performs fragment hunt by switching between primary and backup streams and down-shifting a level till `fragLoadingMaxRetry` limit is reached. @@ -83,7 +82,6 @@ design idea is pretty simple : **Retry Recommendations** By not having multiple renditions, recovery logic will not be able to add extra value to your platform. In order to have good results for dual constraint media hunt, specify big enough limits for fragments and levels retries. - - Level: don't use total retry less than `3 - 4` - Fragment: don't use total retry less than `4 - 6` - Implement short burst retries (i.e. small retry delay `0.5 - 4` seconds), and when library returns fatal error switch to a different CDN diff --git a/package-lock.json b/package-lock.json index a50fd65161a..0e54bbc16c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,43 +7,44 @@ "name": "hls.js", "license": "Apache-2.0", "devDependencies": { - "@babel/core": "7.26.10", - "@babel/helper-module-imports": "7.25.9", - "@babel/plugin-proposal-class-properties": "7.18.6", - "@babel/plugin-proposal-object-rest-spread": "7.20.7", - "@babel/plugin-proposal-optional-chaining": "7.21.0", - "@babel/plugin-transform-object-assign": "7.25.9", - "@babel/preset-env": "7.26.9", - "@babel/preset-typescript": "7.27.0", - "@babel/register": "7.25.9", - "@microsoft/api-documenter": "7.26.20", - "@microsoft/api-extractor": "7.52.3", + "@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.3", + "@babel/preset-typescript": "7.27.1", + "@babel/register": "7.28.3", + "@microsoft/api-documenter": "7.26.32", + "@microsoft/api-extractor": "7.52.11", "@rollup/plugin-alias": "5.1.1", "@rollup/plugin-babel": "6.0.4", - "@rollup/plugin-commonjs": "28.0.3", + "@rollup/plugin-commonjs": "28.0.6", "@rollup/plugin-node-resolve": "16.0.1", "@rollup/plugin-replace": "6.0.2", "@rollup/plugin-terser": "0.4.4", - "@rollup/plugin-typescript": "12.1.2", - "@svta/common-media-library": "0.11.0", + "@rollup/plugin-typescript": "12.1.4", + "@svta/common-media-library": "0.17.1", "@types/chai": "4.3.20", "@types/chart.js": "2.9.41", "@types/mocha": "10.0.10", "@types/sinon-chai": "3.2.12", - "@typescript-eslint/eslint-plugin": "8.31.0", - "@typescript-eslint/parser": "8.31.0", + "@typescript-eslint/eslint-plugin": "8.38.0", + "@typescript-eslint/parser": "8.38.0", "babel-loader": "10.0.0", "babel-plugin-transform-remove-console": "6.9.4", "chai": "4.5.0", "chart.js": "2.9.4", - "chromedriver": "135.0.1", + "chromedriver": "139.0.3", "doctoc": "2.2.1", - "es-check": "8.0.2", + "es-check": "9.3.1", "eslint": "8.57.1", - "eslint-config-prettier": "10.1.2", - "eslint-plugin-import": "2.31.0", + "eslint-config-prettier": "10.1.8", + "eslint-plugin-import": "2.32.0", "eslint-plugin-mocha": "10.5.0", - "eslint-plugin-n": "17.17.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", "http-server": "14.1.1", @@ -57,24 +58,24 @@ "karma-rollup-preprocessor": "github:jlmakes/karma-rollup-preprocessor#7a7268d91149307b3cf2888ee4e65ccd079955a3", "karma-sinon-chai": "2.0.2", "karma-sourcemap-loader": "0.4.0", - "lint-staged": "15.5.1", + "lint-staged": "16.1.5", "markdown-styles": "3.2.0", "micromatch": "4.0.8", - "mocha": "11.1.0", + "mocha": "11.7.1", "node-fetch": "3.3.2", - "npm-run-all2": "7.0.2", - "prettier": "3.5.3", + "npm-run-all2": "8.0.4", + "prettier": "3.6.2", "promise-polyfill": "8.3.0", - "rollup": "4.40.0", + "rollup": "4.47.1", "rollup-plugin-istanbul": "5.0.0", "sauce-connect-launcher": "1.3.2", - "selenium-webdriver": "4.31.0", - "semver": "7.7.1", + "selenium-webdriver": "4.35.0", + "semver": "7.7.2", "sinon": "19.0.5", "sinon-chai": "3.7.0", "typescript": "5.8.3", "url-toolkit": "2.2.5", - "wrangler": "3.114.6" + "wrangler": "4.31.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -100,44 +101,44 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", - "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", + "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", - "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "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.26.2", - "@babel/generator": "^7.26.10", - "@babel/helper-compilation-targets": "^7.26.5", - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helpers": "^7.26.10", - "@babel/parser": "^7.26.10", - "@babel/template": "^7.26.9", - "@babel/traverse": "^7.26.10", - "@babel/types": "^7.26.10", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@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.3", + "@babel/types": "^7.28.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -162,15 +163,15 @@ } }, "node_modules/@babel/generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", - "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", + "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.27.0", - "@babel/types": "^7.27.0", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", + "@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" }, "engines": { @@ -178,39 +179,35 @@ } }, "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", "dev": true, "dependencies": { - "@babel/types": "^7.25.9" + "@babel/types": "^7.27.3" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", - "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.26.5", - "@babel/helper-validator-option": "^7.25.9", + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -244,17 +241,17 @@ "dev": true }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.0.tgz", - "integrity": "sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg==", + "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.25.9", - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/helper-replace-supers": "^7.26.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/traverse": "^7.27.0", + "@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.28.3", "semver": "^6.3.1" }, "engines": { @@ -274,13 +271,13 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.9.tgz", - "integrity": "sha512-ORPNZ3h6ZRkOyAa/SaHU+XsLZr0UQzRwuDQ0cczIA17nAzZ+85G5cVkOJIj7QavLZGSe8QXUmNFxSZzjcZF9bw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", + "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "regexpu-core": "^6.1.1", + "@babel/helper-annotate-as-pure": "^7.27.1", + "regexpu-core": "^6.2.0", "semver": "^6.3.1" }, "engines": { @@ -300,56 +297,88 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz", - "integrity": "sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", + "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", "dev": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "debug": "^4.4.1", "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" + "resolve": "^1.22.10" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/@babel/helper-define-polyfill-provider/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/helper-define-polyfill-provider/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", - "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", + "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", "dev": true, "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", - "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", "dev": true, "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", - "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "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.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -359,35 +388,35 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", - "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", "dev": true, "dependencies": { - "@babel/types": "^7.25.9" + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", - "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", - "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-wrap-function": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -397,14 +426,14 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz", - "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", "dev": true, "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/traverse": "^7.26.5" + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -414,79 +443,79 @@ } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", - "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", "dev": true, "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", - "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", - "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.27.1.tgz", + "integrity": "sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ==", "dev": true, "dependencies": { - "@babel/template": "^7.25.9", - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/template": "^7.27.1", + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.10.tgz", - "integrity": "sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==", + "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.26.9", - "@babel/types": "^7.26.10" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", - "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "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.27.0" + "@babel/types": "^7.28.2" }, "bin": { "parser": "bin/babel-parser.js" @@ -496,13 +525,13 @@ } }, "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz", - "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", + "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -512,12 +541,12 @@ } }, "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz", - "integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -527,12 +556,12 @@ } }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz", - "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -542,14 +571,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", - "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/plugin-transform-optional-chaining": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -559,13 +588,13 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", - "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", + "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.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -574,58 +603,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", - "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-optional-chaining": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", - "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-proposal-private-property-in-object": { "version": "7.21.0-placeholder-for-preset-env.2", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", @@ -639,12 +616,12 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz", - "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", + "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -654,12 +631,12 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", - "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -669,12 +646,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", - "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -683,37 +660,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", - "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -739,12 +692,12 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", - "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -754,14 +707,14 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.26.8.tgz", - "integrity": "sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", + "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-remap-async-to-generator": "^7.25.9", - "@babel/traverse": "^7.26.8" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.28.0" }, "engines": { "node": ">=6.9.0" @@ -771,14 +724,14 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz", - "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", + "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", "dev": true, "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-remap-async-to-generator": "^7.25.9" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -788,12 +741,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.26.5.tgz", - "integrity": "sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -803,12 +756,12 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz", - "integrity": "sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.0.tgz", + "integrity": "sha512-gKKnwjpdx5sER/wl0WN0efUBFzF/56YZO0RJrSYP4CljXnP31ByY7fol89AzomdlLNzI36AvOTmYHsnZTCkq8Q==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -818,13 +771,13 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", - "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", + "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -834,13 +787,13 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz", - "integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==", + "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.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-class-features-plugin": "^7.28.3", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -850,17 +803,17 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", - "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", + "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.25.9", - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9", - "@babel/traverse": "^7.25.9", - "globals": "^11.1.0" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -870,13 +823,13 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", - "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", + "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/template": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/template": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -886,12 +839,13 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", - "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.0.tgz", + "integrity": "sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.0" }, "engines": { "node": ">=6.9.0" @@ -901,13 +855,13 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", - "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", + "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -917,12 +871,12 @@ } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", - "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -932,13 +886,13 @@ } }, "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz", - "integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -948,12 +902,28 @@ } }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", - "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz", + "integrity": "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0" }, "engines": { "node": ">=6.9.0" @@ -963,12 +933,12 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz", - "integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz", + "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -978,12 +948,12 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", - "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -993,13 +963,13 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.26.9.tgz", - "integrity": "sha512-Hry8AusVm8LW5BVFgiyUReuoGzPUpdHQQqJY5bZnbbf+ngOHWuCuYFKw/BqaaWlvEUrF91HMhDtEaI1hZzNbLg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1009,14 +979,14 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", - "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", "dev": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1026,12 +996,12 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", - "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", + "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1041,12 +1011,12 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", - "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1056,12 +1026,12 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", - "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", + "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1071,12 +1041,12 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", - "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1086,13 +1056,13 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", - "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1102,13 +1072,13 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", - "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", + "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1118,15 +1088,15 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", - "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", + "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1136,13 +1106,13 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", - "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1152,13 +1122,13 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", - "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1168,12 +1138,12 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", - "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1183,12 +1153,12 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.26.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz", - "integrity": "sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", + "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1198,12 +1168,12 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", - "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", + "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1213,12 +1183,12 @@ } }, "node_modules/@babel/plugin-transform-object-assign": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.25.9.tgz", - "integrity": "sha512-I/Vl1aQnPsrrn837oLbo+VQtkNcjuuiATqwmuweg4fTauwHHQoxyjmjjOVKyO8OaTxgqYTKW3LuQsykXjDf5Ag==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.27.1.tgz", + "integrity": "sha512-LP6tsnirA6iy13uBKiYgjJsfQrodmlSrpZModtlo1Vk8sOO68gfo7dfA9TGJyEgxTiO7czK4EGZm8FJEZtk4kQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1228,14 +1198,16 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", - "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.0.tgz", + "integrity": "sha512-9VNGikXxzu5eCiQjdE4IZn8sb9q7Xsk5EXLDBKUYg1e/Tve8/05+KJEtcxGxAgCY5t/BpKQM+JEL/yT4tvgiUA==", "dev": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/plugin-transform-parameters": "^7.25.9" + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.0" }, "engines": { "node": ">=6.9.0" @@ -1245,13 +1217,13 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", - "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1261,12 +1233,12 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", - "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", + "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1276,13 +1248,13 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", - "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", + "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1292,12 +1264,12 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", - "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1307,13 +1279,13 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", - "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", + "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1323,14 +1295,14 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", - "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", + "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1340,12 +1312,12 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", - "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1355,13 +1327,12 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz", - "integrity": "sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==", + "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.25.9", - "regenerator-transform": "^0.15.2" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1371,13 +1342,13 @@ } }, "node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz", - "integrity": "sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", + "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1387,12 +1358,12 @@ } }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", - "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1402,12 +1373,12 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", - "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1417,13 +1388,13 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", - "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", + "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1433,12 +1404,12 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", - "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1448,12 +1419,12 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.26.8.tgz", - "integrity": "sha512-OmGDL5/J0CJPJZTHZbi2XpO0tyT2Ia7fzpW5GURwdtp2X3fMmN8au/ej6peC/T33/+CRiIpA8Krse8hFGVmT5Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1463,12 +1434,12 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.26.7.tgz", - "integrity": "sha512-jfoTXXZTgGg36BmhqT3cAYK5qkmqvJpvNrPhaK/52Vgjhw4Rq29s9UqpWWV0D6yuRmgiFH/BUVlkl96zJWqnaw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1478,16 +1449,16 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.0.tgz", - "integrity": "sha512-fRGGjO2UEGPjvEcyAZXRXAS8AfdaQoq7HnxAbJoAoW10B9xOKesmmndJv+Sym2a+9FHWZ9KbyyLCe9s0Sn5jtg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.1.tgz", + "integrity": "sha512-Q5sT5+O4QUebHdbwKedFBEwRLb02zJ7r4A5Gg2hUoLuU3FjdMcyqcywqUrLCaDsFCxzokf7u9kuy7qz51YUuAg==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-create-class-features-plugin": "^7.27.0", - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/plugin-syntax-typescript": "^7.25.9" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1497,12 +1468,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", - "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1512,13 +1483,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", - "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", + "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1528,13 +1499,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", - "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1544,13 +1515,13 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", - "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", + "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1560,79 +1531,80 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.9.tgz", - "integrity": "sha512-vX3qPGE8sEKEAZCWk05k3cpTAE3/nOYca++JA+Rd0z2NCNzabmYvEiSShKzm10zdquOIAVXsy2Ei/DTW34KlKQ==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.26.8", - "@babel/helper-compilation-targets": "^7.26.5", - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-validator-option": "^7.25.9", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.9", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.9", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.9", + "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", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", + "@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.28.3", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.26.0", - "@babel/plugin-syntax-import-attributes": "^7.26.0", + "@babel/plugin-syntax-import-assertions": "^7.27.1", + "@babel/plugin-syntax-import-attributes": "^7.27.1", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.25.9", - "@babel/plugin-transform-async-generator-functions": "^7.26.8", - "@babel/plugin-transform-async-to-generator": "^7.25.9", - "@babel/plugin-transform-block-scoped-functions": "^7.26.5", - "@babel/plugin-transform-block-scoping": "^7.25.9", - "@babel/plugin-transform-class-properties": "^7.25.9", - "@babel/plugin-transform-class-static-block": "^7.26.0", - "@babel/plugin-transform-classes": "^7.25.9", - "@babel/plugin-transform-computed-properties": "^7.25.9", - "@babel/plugin-transform-destructuring": "^7.25.9", - "@babel/plugin-transform-dotall-regex": "^7.25.9", - "@babel/plugin-transform-duplicate-keys": "^7.25.9", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9", - "@babel/plugin-transform-dynamic-import": "^7.25.9", - "@babel/plugin-transform-exponentiation-operator": "^7.26.3", - "@babel/plugin-transform-export-namespace-from": "^7.25.9", - "@babel/plugin-transform-for-of": "^7.26.9", - "@babel/plugin-transform-function-name": "^7.25.9", - "@babel/plugin-transform-json-strings": "^7.25.9", - "@babel/plugin-transform-literals": "^7.25.9", - "@babel/plugin-transform-logical-assignment-operators": "^7.25.9", - "@babel/plugin-transform-member-expression-literals": "^7.25.9", - "@babel/plugin-transform-modules-amd": "^7.25.9", - "@babel/plugin-transform-modules-commonjs": "^7.26.3", - "@babel/plugin-transform-modules-systemjs": "^7.25.9", - "@babel/plugin-transform-modules-umd": "^7.25.9", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9", - "@babel/plugin-transform-new-target": "^7.25.9", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.26.6", - "@babel/plugin-transform-numeric-separator": "^7.25.9", - "@babel/plugin-transform-object-rest-spread": "^7.25.9", - "@babel/plugin-transform-object-super": "^7.25.9", - "@babel/plugin-transform-optional-catch-binding": "^7.25.9", - "@babel/plugin-transform-optional-chaining": "^7.25.9", - "@babel/plugin-transform-parameters": "^7.25.9", - "@babel/plugin-transform-private-methods": "^7.25.9", - "@babel/plugin-transform-private-property-in-object": "^7.25.9", - "@babel/plugin-transform-property-literals": "^7.25.9", - "@babel/plugin-transform-regenerator": "^7.25.9", - "@babel/plugin-transform-regexp-modifiers": "^7.26.0", - "@babel/plugin-transform-reserved-words": "^7.25.9", - "@babel/plugin-transform-shorthand-properties": "^7.25.9", - "@babel/plugin-transform-spread": "^7.25.9", - "@babel/plugin-transform-sticky-regex": "^7.25.9", - "@babel/plugin-transform-template-literals": "^7.26.8", - "@babel/plugin-transform-typeof-symbol": "^7.26.7", - "@babel/plugin-transform-unicode-escapes": "^7.25.9", - "@babel/plugin-transform-unicode-property-regex": "^7.25.9", - "@babel/plugin-transform-unicode-regex": "^7.25.9", - "@babel/plugin-transform-unicode-sets-regex": "^7.25.9", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.28.0", + "@babel/plugin-transform-async-to-generator": "^7.27.1", + "@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.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", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.0", + "@babel/plugin-transform-exponentiation-operator": "^7.27.1", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.27.1", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-modules-systemjs": "^7.27.1", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", + "@babel/plugin-transform-numeric-separator": "^7.27.1", + "@babel/plugin-transform-object-rest-spread": "^7.28.0", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1", + "@babel/plugin-transform-parameters": "^7.27.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.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", + "@babel/plugin-transform-spread": "^7.27.1", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.27.1", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.11.0", - "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.40.0", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "core-js-compat": "^3.43.0", "semver": "^6.3.1" }, "engines": { @@ -1666,16 +1638,16 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.0.tgz", - "integrity": "sha512-vxaPFfJtHhgeOVXRKuHpHPAOgymmy8V8I65T1q53R7GCZlefKeCaTyDs3zOPHTTbmquvNlQYC5klEvWsBAtrBQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", + "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-validator-option": "^7.25.9", - "@babel/plugin-syntax-jsx": "^7.25.9", - "@babel/plugin-transform-modules-commonjs": "^7.26.3", - "@babel/plugin-transform-typescript": "^7.27.0" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1685,9 +1657,9 @@ } }, "node_modules/@babel/register": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.25.9.tgz", - "integrity": "sha512-8D43jXtGsYmEeDvm4MWHYUpWf8iiXgWYx3fW7E7Wb7Oe6FWqJPl5K6TuFW0dOwNZzEE5rjlaSJYH9JjrUKJszA==", + "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", @@ -1703,58 +1675,46 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/runtime": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz", - "integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==", - "dev": true, - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/template": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", - "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/parser": "^7.27.0", - "@babel/types": "^7.27.0" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", - "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", + "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.26.2", - "@babel/generator": "^7.27.0", - "@babel/parser": "^7.27.0", - "@babel/template": "^7.27.0", - "@babel/types": "^7.27.0", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.3", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/types": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", - "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "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.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1767,15 +1727,16 @@ "dev": true }, "node_modules/@cloudflare/kv-asset-handler": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.4.tgz", - "integrity": "sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.4.0.tgz", + "integrity": "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA==", "dev": true, + "license": "MIT OR Apache-2.0", "dependencies": { "mime": "^3.0.0" }, "engines": { - "node": ">=16.13" + "node": ">=18.0.0" } }, "node_modules/@cloudflare/kv-asset-handler/node_modules/mime": { @@ -1783,6 +1744,7 @@ "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", "dev": true, + "license": "MIT", "bin": { "mime": "cli.js" }, @@ -1790,25 +1752,10 @@ "node": ">=10.0.0" } }, - "node_modules/@cloudflare/unenv-preset": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.0.2.tgz", - "integrity": "sha512-nyzYnlZjjV5xT3LizahG1Iu6mnrCaxglJ04rZLpDwlDVDZ7v46lNsfxhV3A/xtfgQuSHmLnc6SVI+KwBpc3Lwg==", - "dev": true, - "peerDependencies": { - "unenv": "2.0.0-rc.14", - "workerd": "^1.20250124.0" - }, - "peerDependenciesMeta": { - "workerd": { - "optional": true - } - } - }, "node_modules/@cloudflare/workerd-darwin-64": { - "version": "1.20250408.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250408.0.tgz", - "integrity": "sha512-bxhIwBWxaNItZLXDNOKY2dCv0FHjDiDkfJFpwv4HvtvU5MKcrivZHVmmfDzLW85rqzfcDOmKbZeMPVfiKxdBZw==", + "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" ], @@ -1822,9 +1769,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-arm64": { - "version": "1.20250408.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250408.0.tgz", - "integrity": "sha512-5XZ2Oykr8bSo7zBmERtHh18h5BZYC/6H1YFWVxEj3PtalF3+6SHsO4KZsbGvDml9Pu7sHV277jiZE5eny8Hlyw==", + "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" ], @@ -1838,9 +1785,9 @@ } }, "node_modules/@cloudflare/workerd-linux-64": { - "version": "1.20250408.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250408.0.tgz", - "integrity": "sha512-WbgItXWln6G5d7GvYLWcuOzAVwafysZaWunH3UEfsm95wPuRofpYnlDD861gdWJX10IHSVgMStGESUcs7FLerQ==", + "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" ], @@ -1854,9 +1801,9 @@ } }, "node_modules/@cloudflare/workerd-linux-arm64": { - "version": "1.20250408.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250408.0.tgz", - "integrity": "sha512-pAhEywPPvr92SLylnQfZEPgXz+9pOG9G9haAPLpEatncZwYiYd9yiR6HYWhKp2erzCoNrOqKg9IlQwU3z1IDiw==", + "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" ], @@ -1870,9 +1817,9 @@ } }, "node_modules/@cloudflare/workerd-windows-64": { - "version": "1.20250408.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250408.0.tgz", - "integrity": "sha512-nJ3RjMKGae2aF2rZ/CNeBvQPM+W5V1SUK0FYWG/uomyr7uQ2l4IayHna1ODg/OHHTEgIjwom0Mbn58iXb0WOcQ==", + "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" ], @@ -1899,6 +1846,7 @@ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -1911,6 +1859,7 @@ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -1928,405 +1877,445 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", - "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", + "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "tslib": "^2.4.0" } }, - "node_modules/@esbuild-plugins/node-globals-polyfill": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-globals-polyfill/-/node-globals-polyfill-0.2.3.tgz", - "integrity": "sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==", - "dev": true, - "peerDependencies": { - "esbuild": "*" - } - }, - "node_modules/@esbuild-plugins/node-modules-polyfill": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-modules-polyfill/-/node-modules-polyfill-0.2.2.tgz", - "integrity": "sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^4.0.0", - "rollup-plugin-node-polyfills": "^0.2.1" - }, - "peerDependencies": { - "esbuild": "*" - } - }, - "node_modules/@esbuild-plugins/node-modules-polyfill/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", + "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", + "cpu": [ + "ppc64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", - "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz", + "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", - "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz", + "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", - "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz", + "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", - "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz", + "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", - "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz", + "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", - "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz", + "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", - "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz", + "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", - "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz", + "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", - "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz", + "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", - "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz", + "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", - "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz", + "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", "cpu": [ "loong64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", - "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz", + "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", "cpu": [ "mips64el" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", - "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz", + "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", - "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz", + "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", - "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz", + "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", - "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz", + "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz", + "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", - "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz", + "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz", + "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", - "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz", + "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", - "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz", + "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", - "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz", + "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", - "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz", + "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", - "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz", + "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz", - "integrity": "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", "dev": true, "dependencies": { "eslint-visitor-keys": "^3.4.3" @@ -2397,15 +2386,6 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@fastify/busboy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", - "dev": true, - "engines": { - "node": ">=14" - } - }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -2449,6 +2429,7 @@ "arm64" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "darwin" @@ -2471,6 +2452,7 @@ "x64" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "darwin" @@ -2493,6 +2475,7 @@ "arm64" ], "dev": true, + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "darwin" @@ -2509,6 +2492,7 @@ "x64" ], "dev": true, + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "darwin" @@ -2525,6 +2509,7 @@ "arm" ], "dev": true, + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -2541,6 +2526,7 @@ "arm64" ], "dev": true, + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -2557,6 +2543,7 @@ "s390x" ], "dev": true, + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -2573,6 +2560,7 @@ "x64" ], "dev": true, + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -2589,6 +2577,7 @@ "arm64" ], "dev": true, + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -2605,6 +2594,7 @@ "x64" ], "dev": true, + "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -2621,6 +2611,7 @@ "arm" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -2643,6 +2634,7 @@ "arm64" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -2665,6 +2657,7 @@ "s390x" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -2687,6 +2680,7 @@ "x64" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -2709,6 +2703,7 @@ "arm64" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -2731,6 +2726,7 @@ "x64" ], "dev": true, + "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -2753,6 +2749,7 @@ "wasm32" ], "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { "@emnapi/runtime": "^1.2.0" @@ -2772,6 +2769,7 @@ "ia32" ], "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" @@ -2791,6 +2789,7 @@ "x64" ], "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" @@ -2802,14 +2801,35 @@ "url": "https://opencollective.com/libvips" } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "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, - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", + "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", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", @@ -2969,9 +2989,9 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -2979,16 +2999,16 @@ } }, "node_modules/@microsoft/api-documenter": { - "version": "7.26.20", - "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.26.20.tgz", - "integrity": "sha512-lTWhpKXLkiovr+IrFjrno4bbctknd3bVrVfTj1NZnMjaSXiplS3V8r9EKhf0mqa0twAznGXylwoBm+pP2mwjGw==", + "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.5", + "@microsoft/api-extractor-model": "7.30.7", "@microsoft/tsdoc": "~0.15.1", - "@rushstack/node-core-library": "5.13.0", - "@rushstack/terminal": "0.15.2", - "@rushstack/ts-command-line": "4.23.7", + "@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" }, @@ -3010,20 +3030,20 @@ } }, "node_modules/@microsoft/api-extractor": { - "version": "7.52.3", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.52.3.tgz", - "integrity": "sha512-QEs6l8h7p9eOSHrQ9NBBUZhUuq+j/2QKcRgigbSs2YQepKz8glvsqmsUOp+nvuaY60ps7KkpVVYQCj81WLoMVQ==", + "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.5", + "@microsoft/api-extractor-model": "7.30.7", "@microsoft/tsdoc": "~0.15.1", "@microsoft/tsdoc-config": "~0.17.1", - "@rushstack/node-core-library": "5.13.0", + "@rushstack/node-core-library": "5.14.0", "@rushstack/rig-package": "0.5.3", - "@rushstack/terminal": "0.15.2", - "@rushstack/ts-command-line": "4.23.7", + "@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", @@ -3034,26 +3054,29 @@ } }, "node_modules/@microsoft/api-extractor-model": { - "version": "7.30.5", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.30.5.tgz", - "integrity": "sha512-0ic4rcbcDZHz833RaTZWTGu+NpNgrxVNjVaor0ZDUymfDFzjA/Uuk8hYziIUIOEOSTfmIQqyzVwlzxZxPe7tOA==", + "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.0" + "@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": { @@ -3172,6 +3195,44 @@ "node": ">=14" } }, + "node_modules/@poppinss/colors": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.5.tgz", + "integrity": "sha512-FvdDqtcRCtz6hThExcFOgW0cWX+xwSMWcRuQe5ZEb2m7cVQOAVZOIMt+/v9RxGiD9/OY16qJBXK4CVKWAPalBw==", + "dev": true, + "dependencies": { + "kleur": "^4.1.5" + } + }, + "node_modules/@poppinss/dumper": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.4.tgz", + "integrity": "sha512-iG0TIdqv8xJ3Lt9O8DrPRxw1MRLjNpoqiSGU03P/wNLP/s0ra0udPJ1J2Tx5M0J3H/cVyEgpbn8xUKRY9j59kQ==", + "dev": true, + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@sindresorhus/is": "^7.0.2", + "supports-color": "^10.0.0" + } + }, + "node_modules/@poppinss/dumper/node_modules/supports-color": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.0.0.tgz", + "integrity": "sha512-HRVVSbCCMbj7/kdWF9Q+bbckjBHLtHMEoJWlkmYzzdwhYMkjkOwubLM6t7NbWKjgKamGDrWL1++KrjUO1t9oAQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/@poppinss/exception": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.2.tgz", + "integrity": "sha512-m7bpKCD4QMlFCjA/nKTs23fuvoVFoA83brRKmObCUNmi/9tVu8Ve3w4YQAnJu4q3Tjf5fr685HYIC/IA2zHRSg==", + "dev": true + }, "node_modules/@rollup/plugin-alias": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-5.1.1.tgz", @@ -3216,9 +3277,9 @@ } }, "node_modules/@rollup/plugin-commonjs": { - "version": "28.0.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.3.tgz", - "integrity": "sha512-pyltgilam1QPdn+Zd9gaCfOLcnjMEJ9gV+bTw6/r73INdvzf1ah9zLIJBm+kW7R6IUFIQ1YO+VqZtYxZNWFPEQ==", + "version": "28.0.6", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.6.tgz", + "integrity": "sha512-XSQB1K7FUU5QP+3lOQmVCE3I0FcbbNvmNT4VJSj93iUjayaARrTQeoRdiYQoftAJBLrR9t2agwAd3ekaTgHNlw==", "dev": true, "dependencies": { "@rollup/pluginutils": "^5.0.1", @@ -3241,16 +3302,10 @@ } } }, - "node_modules/@rollup/plugin-commonjs/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true - }, "node_modules/@rollup/plugin-commonjs/node_modules/fdir": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", - "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", "dev": true, "peerDependencies": { "picomatch": "^3 || ^4" @@ -3261,15 +3316,6 @@ } } }, - "node_modules/@rollup/plugin-commonjs/node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "dev": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, "node_modules/@rollup/plugin-commonjs/node_modules/picomatch": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", @@ -3327,15 +3373,6 @@ } } }, - "node_modules/@rollup/plugin-replace/node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "dev": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, "node_modules/@rollup/plugin-terser": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", @@ -3359,9 +3396,9 @@ } }, "node_modules/@rollup/plugin-typescript": { - "version": "12.1.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-12.1.2.tgz", - "integrity": "sha512-cdtSp154H5sv637uMr1a8OTWB0L1SWDSm1rDGiyfcGcvQ6cuTs4MDk2BVEBGysUWago4OJN4EQZqOTl/QY3Jgg==", + "version": "12.1.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-12.1.4.tgz", + "integrity": "sha512-s5Hx+EtN60LMlDBvl5f04bEiFZmAepk27Q+mr85L/00zPDn1jtzlTV6FWn81MaIwqfWzKxmOJrBWHU6vtQyedQ==", "dev": true, "dependencies": { "@rollup/pluginutils": "^5.1.0", @@ -3412,16 +3449,10 @@ "integrity": "sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==", "dev": true }, - "node_modules/@rollup/pluginutils/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true - }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", - "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==", + "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" ], @@ -3432,9 +3463,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz", - "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==", + "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" ], @@ -3445,9 +3476,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz", - "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==", + "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" ], @@ -3458,9 +3489,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz", - "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==", + "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" ], @@ -3471,9 +3502,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz", - "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==", + "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" ], @@ -3484,9 +3515,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz", - "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==", + "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" ], @@ -3497,9 +3528,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz", - "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==", + "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" ], @@ -3510,9 +3541,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz", - "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==", + "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" ], @@ -3523,9 +3554,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz", - "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==", + "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" ], @@ -3536,9 +3567,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz", - "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==", + "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" ], @@ -3549,9 +3580,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz", - "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==", + "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" ], @@ -3561,10 +3592,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz", - "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==", + "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" ], @@ -3575,9 +3606,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz", - "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==", + "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" ], @@ -3588,9 +3619,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz", - "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==", + "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" ], @@ -3601,9 +3632,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz", - "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==", + "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" ], @@ -3614,9 +3645,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", - "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", + "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" ], @@ -3627,9 +3658,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz", - "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==", + "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" ], @@ -3640,9 +3671,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz", - "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==", + "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" ], @@ -3653,9 +3684,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz", - "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==", + "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" ], @@ -3666,9 +3697,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz", - "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==", + "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" ], @@ -3685,9 +3716,9 @@ "dev": true }, "node_modules/@rushstack/node-core-library": { - "version": "5.13.0", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.13.0.tgz", - "integrity": "sha512-IGVhy+JgUacAdCGXKUrRhwHMTzqhWwZUI+qEPcdzsb80heOw0QPbhhoVsoiMF7Klp8eYsp7hzpScMXmOa3Uhfg==", + "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", @@ -3770,12 +3801,12 @@ } }, "node_modules/@rushstack/terminal": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.15.2.tgz", - "integrity": "sha512-7Hmc0ysK5077R/IkLS9hYu0QuNafm+TbZbtYVzCMbeOdMjaRboLKrhryjwZSRJGJzu+TV1ON7qZHeqf58XfLpA==", + "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.0", + "@rushstack/node-core-library": "5.14.0", "supports-color": "~8.1.1" }, "peerDependencies": { @@ -3812,17 +3843,29 @@ } }, "node_modules/@rushstack/ts-command-line": { - "version": "4.23.7", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.23.7.tgz", - "integrity": "sha512-Gr9cB7DGe6uz5vq2wdr89WbVDKz0UeuFEn5H2CfWDe7JvjFFaiV15gi6mqDBTbHhHCWS7w8mF1h3BnIfUndqdA==", + "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.2", + "@rushstack/terminal": "0.15.4", "@types/argparse": "1.0.38", "argparse": "~1.0.9", "string-argv": "~0.3.1" } }, + "node_modules/@sindresorhus/is": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.0.2.tgz", + "integrity": "sha512-d9xRovfKNz1SKieM0qJdO+PQonjnnIfSNWfHYnBSJ9hkjm0ZPw6HlxscDXYstp3z+7V2GOFHc+J0CYrYTjqCJw==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, "node_modules/@sinonjs/commons": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", @@ -3873,12 +3916,21 @@ "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", "dev": true }, - "node_modules/@svta/common-media-library": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@svta/common-media-library/-/common-media-library-0.11.0.tgz", - "integrity": "sha512-O+j71s605qywuB55meW68/dxHaOdq1AMRDA3iqECru7uUsOijrn8m4xj9uRaY4as3Bjv6rS0J1fMKJo98R2Naw==", + "node_modules/@speed-highlight/core": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.7.tgz", + "integrity": "sha512-0dxmVj4gxg3Jg879kvFS/msl4s9F3T9UXC1InxgOf7t5NvcPD97u/WTA5vL/IxWHMn7qSxBozqrnnE2wvl1m8g==", "dev": true }, + "node_modules/@svta/common-media-library": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@svta/common-media-library/-/common-media-library-0.17.1.tgz", + "integrity": "sha512-UcmqRe1cJ/OloNEeXqKJjbw6MAKPaGZUk4yLsy1QOwtzUmo7hDVvZeIoqXlmoXZNQRQ/P1MsNuboKirsTw9e7w==", + "dev": true, + "engines": { + "node": ">=20" + } + }, "node_modules/@testim/chrome-version": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@testim/chrome-version/-/chrome-version-1.1.4.tgz", @@ -4071,20 +4123,20 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.0.tgz", - "integrity": "sha512-evaQJZ/J/S4wisevDvC1KFZkPzRetH8kYZbkgcTRyql3mcKsf+ZFDV1BVWUGTCAW5pQHoqn5gK5b8kn7ou9aFQ==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.38.0.tgz", + "integrity": "sha512-CPoznzpuAnIOl4nhj4tRr4gIPj5AfKgkiJmGQDaq+fQnRJTYlcBjbX3wbciGmpoPf8DREufuPRe1tNMZnGdanA==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.31.0", - "@typescript-eslint/type-utils": "8.31.0", - "@typescript-eslint/utils": "8.31.0", - "@typescript-eslint/visitor-keys": "8.31.0", + "@typescript-eslint/scope-manager": "8.38.0", + "@typescript-eslint/type-utils": "8.38.0", + "@typescript-eslint/utils": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0", "graphemer": "^1.4.0", - "ignore": "^5.3.1", + "ignore": "^7.0.0", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.0.1" + "ts-api-utils": "^2.1.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4094,21 +4146,30 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "@typescript-eslint/parser": "^8.38.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/@typescript-eslint/parser": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.31.0.tgz", - "integrity": "sha512-67kYYShjBR0jNI5vsf/c3WG4u+zDnCTHTPqVMQguffaWWFs7artgwKmfwdifl+r6XyM5LYLas/dInj2T0SgJyw==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.38.0.tgz", + "integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.31.0", - "@typescript-eslint/types": "8.31.0", - "@typescript-eslint/typescript-estree": "8.31.0", - "@typescript-eslint/visitor-keys": "8.31.0", + "@typescript-eslint/scope-manager": "8.38.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/typescript-estree": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0", "debug": "^4.3.4" }, "engines": { @@ -4123,14 +4184,35 @@ "typescript": ">=4.8.4 <5.9.0" } }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.38.0.tgz", + "integrity": "sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==", + "dev": true, + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.38.0", + "@typescript-eslint/types": "^8.38.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.31.0.tgz", - "integrity": "sha512-knO8UyF78Nt8O/B64i7TlGXod69ko7z6vJD9uhSlm0qkAbGeRUSudcm0+K/4CrRjrpiHfBCjMWlc08Vav1xwcw==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.38.0.tgz", + "integrity": "sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.31.0", - "@typescript-eslint/visitor-keys": "8.31.0" + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4140,16 +4222,33 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz", + "integrity": "sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.31.0.tgz", - "integrity": "sha512-DJ1N1GdjI7IS7uRlzJuEDCgDQix3ZVYVtgeWEyhyn4iaoitpMBX6Ndd488mXSx0xah/cONAkEaYyylDyAeHMHg==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.38.0.tgz", + "integrity": "sha512-c7jAvGEZVf0ao2z+nnz8BUaHZD09Agbh+DY7qvBQqLiz8uJzRgVPj5YvOh8I8uEiH8oIUGIfHzMwUcGVco/SJg==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "8.31.0", - "@typescript-eslint/utils": "8.31.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/typescript-estree": "8.38.0", + "@typescript-eslint/utils": "8.38.0", "debug": "^4.3.4", - "ts-api-utils": "^2.0.1" + "ts-api-utils": "^2.1.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4164,9 +4263,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.31.0.tgz", - "integrity": "sha512-Ch8oSjVyYyJxPQk8pMiP2FFGYatqXQfQIaMp+TpuuLlDachRWpUAeEu1u9B/v/8LToehUIWyiKcA/w5hUFRKuQ==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.38.0.tgz", + "integrity": "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4177,19 +4276,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.0.tgz", - "integrity": "sha512-xLmgn4Yl46xi6aDSZ9KkyfhhtnYI15/CvHbpOy/eR5NWhK/BK8wc709KKwhAR0m4ZKRP7h07bm4BWUYOCuRpQQ==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz", + "integrity": "sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.31.0", - "@typescript-eslint/visitor-keys": "8.31.0", + "@typescript-eslint/project-service": "8.38.0", + "@typescript-eslint/tsconfig-utils": "8.38.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^2.0.1" + "ts-api-utils": "^2.1.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4203,9 +4304,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "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" @@ -4227,15 +4328,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.31.0.tgz", - "integrity": "sha512-qi6uPLt9cjTFxAb1zGNgTob4x9ur7xC6mHQJ8GwEzGMGE9tYniublmJaowOJ9V2jUzxrltTPfdG2nKlWsq0+Ww==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.38.0.tgz", + "integrity": "sha512-hHcMA86Hgt+ijJlrD8fX0j1j8w4C92zue/8LOPAFioIno+W0+L7KqE8QZKCcPGc/92Vs9x36w/4MPTJhqXdyvg==", "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.31.0", - "@typescript-eslint/types": "8.31.0", - "@typescript-eslint/typescript-estree": "8.31.0" + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.38.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/typescript-estree": "8.38.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4250,13 +4351,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.0.tgz", - "integrity": "sha512-QcGHmlRHWOl93o64ZUMNewCdwKGU6WItOU52H0djgNmn1EOrhVudrDzXz4OycCRSCPwFCDrE2iIt5vmuUdHxuQ==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.38.0.tgz", + "integrity": "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.31.0", - "eslint-visitor-keys": "^4.2.0" + "@typescript-eslint/types": "8.38.0", + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4267,9 +4368,9 @@ } }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4508,6 +4609,7 @@ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -4616,15 +4718,6 @@ "integrity": "sha512-xAEnNCT3w2Tg6MA7ly6QqYJvEoY1tm9iIjJ3yMKK9JPlWuRHAMoe5iETwQnx3M9TVbFMfsrBgWKR+IsmswwNjg==", "dev": true }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/ansi-escapes": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", @@ -4684,13 +4777,13 @@ } }, "node_modules/array-buffer-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", - "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", "dev": true, "dependencies": { - "call-bind": "^1.0.5", - "is-array-buffer": "^3.0.4" + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" }, "engines": { "node": ">= 0.4" @@ -4700,17 +4793,19 @@ } }, "node_modules/array-includes": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", - "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.4", - "is-string": "^1.0.7" + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -4720,17 +4815,18 @@ } }, "node_modules/array.prototype.findlastindex": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", - "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", + "es-abstract": "^1.23.9", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -4740,15 +4836,15 @@ } }, "node_modules/array.prototype.flat": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", - "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -4758,15 +4854,15 @@ } }, "node_modules/array.prototype.flatmap": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", - "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -4776,19 +4872,18 @@ } }, "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", - "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", "dev": true, "dependencies": { "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.5", + "call-bind": "^1.0.8", "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.2.1", - "get-intrinsic": "^1.2.3", - "is-array-buffer": "^3.0.4", - "is-shared-array-buffer": "^1.0.2" + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" }, "engines": { "node": ">= 0.4" @@ -4797,15 +4892,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/as-table": { - "version": "1.0.55", - "resolved": "https://registry.npmjs.org/as-table/-/as-table-1.0.55.tgz", - "integrity": "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==", - "dev": true, - "dependencies": { - "printable-characters": "^1.0.42" - } - }, "node_modules/assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -4836,6 +4922,15 @@ "lodash": "^4.17.14" } }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -4899,13 +4994,13 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.10.tgz", - "integrity": "sha512-rpIuu//y5OX6jVU+a5BCn1R5RSZYWAl2Nar76iwaOdycqb6JPxediskWFMMl7stfwNJR4b7eiQvh5fB5TEQJTQ==", + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", + "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.1", + "@babel/compat-data": "^7.27.7", + "@babel/helper-define-polyfill-provider": "^0.6.5", "semver": "^6.3.1" }, "peerDependencies": { @@ -4922,25 +5017,25 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz", - "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", "dev": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.3", - "core-js-compat": "^3.40.0" + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.1.tgz", - "integrity": "sha512-JfTApdE++cgcTWjsiCQlLyFBMbTUft9ja17saCc93lgV33h4tuCVj7tlvu//qpLwaG+3yEz7/KhahGrUMkVq9g==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", + "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", "dev": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.1" + "@babel/helper-define-polyfill-provider": "^0.6.5" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -5081,9 +5176,9 @@ "dev": true }, "node_modules/browserslist": { - "version": "4.24.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", - "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", "dev": true, "funding": [ { @@ -5100,10 +5195,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" + "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" @@ -5137,16 +5232,44 @@ } }, "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", "dev": true, "dependencies": { + "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { "node": ">= 0.4" @@ -5165,9 +5288,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001699", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001699.tgz", - "integrity": "sha512-b+uH5BakXZ9Do9iK+CkDmctUSEqZl+SP056vc5usa0PL+ev5OHw003rZXcnjNDv3L8P5j6rwT6C0BPKSikW08w==", + "version": "1.0.30001726", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", + "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", "dev": true, "funding": [ { @@ -5356,9 +5479,9 @@ } }, "node_modules/chromedriver": { - "version": "135.0.1", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-135.0.1.tgz", - "integrity": "sha512-MLAS4t9dkttp1R1O2o/1nvtNIxg1dBTx7OE3ZCSrrFz+EFowd0wRAO7H5j918hw0i8+30yODq99p8CumvqRS9Q==", + "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": { @@ -5374,7 +5497,7 @@ "chromedriver": "bin/chromedriver" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/cli-cursor": { @@ -5541,12 +5664,12 @@ } }, "node_modules/commander": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", - "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", + "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==", "dev": true, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/commondir": { @@ -5622,12 +5745,12 @@ } }, "node_modules/core-js-compat": { - "version": "3.40.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.40.0.tgz", - "integrity": "sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ==", + "version": "3.43.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.43.0.tgz", + "integrity": "sha512-2GML2ZsCc5LR7hZYz4AXmjQw8zuy2T//2QntwdnpuYI7jteT6GVYJL7F6C2C57R7gSYrcqVW3lAALefdbhBLDA==", "dev": true, "dependencies": { - "browserslist": "^4.24.3" + "browserslist": "^4.25.0" }, "funding": { "type": "opencollective", @@ -5692,14 +5815,14 @@ } }, "node_modules/data-view-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", - "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.6", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" + "is-data-view": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -5709,29 +5832,29 @@ } }, "node_modules/data-view-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", - "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" + "is-data-view": "^1.0.2" }, "engines": { "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/inspect-js" } }, "node_modules/data-view-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", - "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.6", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" }, @@ -5839,7 +5962,8 @@ "version": "6.1.4", "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/degenerator": { "version": "5.0.1", @@ -5884,11 +6008,11 @@ } }, "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", "dev": true, - "optional": true, + "license": "Apache-2.0", "engines": { "node": ">=8" } @@ -5900,9 +6024,9 @@ "dev": true }, "node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", "dev": true, "engines": { "node": ">=0.3.1" @@ -6035,6 +6159,20 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -6048,9 +6186,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.5.101", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.101.tgz", - "integrity": "sha512-L0ISiQrP/56Acgu4/i/kfPwWSgrzYZUnQrC0+QPFuhqlLP1Ir7qzPPDVS9BcKIyWTRU8+o6CC8dKw38tSWhYIA==", + "version": "1.5.179", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.179.tgz", + "integrity": "sha512-UWKi/EbBopgfFsc5k61wFpV7WrnnSlSzW/e2XcBmS6qKYTivZlLtoll5/rdqRTxGglGHkmkW0j0pFNJG10EUIQ==", "dev": true }, "node_modules/emoji-regex": { @@ -6174,58 +6312,75 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/error-stack-parser-es": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/es-abstract": { - "version": "1.23.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", - "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", "dev": true, "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "arraybuffer.prototype.slice": "^1.0.3", + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "data-view-buffer": "^1.0.1", - "data-view-byte-length": "^1.0.1", - "data-view-byte-offset": "^1.0.0", - "es-define-property": "^1.0.0", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-set-tostringtag": "^2.0.3", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.4", - "get-symbol-description": "^1.0.2", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.3", - "has-symbols": "^1.0.3", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", "hasown": "^2.0.2", - "internal-slot": "^1.0.7", - "is-array-buffer": "^3.0.4", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", - "is-data-view": "^1.0.1", + "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.3", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.13", - "is-weakref": "^1.0.2", - "object-inspect": "^1.13.1", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", "object-keys": "^1.1.1", - "object.assign": "^4.1.5", - "regexp.prototype.flags": "^1.5.2", - "safe-array-concat": "^1.1.2", - "safe-regex-test": "^1.0.3", - "string.prototype.trim": "^1.2.9", - "string.prototype.trimend": "^1.0.8", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.2", - "typed-array-byte-length": "^1.0.1", - "typed-array-byte-offset": "^1.0.2", - "typed-array-length": "^1.0.6", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.15" + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" }, "engines": { "node": ">= 0.4" @@ -6235,15 +6390,18 @@ } }, "node_modules/es-check": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/es-check/-/es-check-8.0.2.tgz", - "integrity": "sha512-m/8jzwE6rasRx+Lhz7J5OIZzt2LmEC53kBPkUqQHk/6BMjo77HoHsdoQKeps8mgLASlb8nRuKZnuRai/iZHrHg==", + "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.0", + "acorn": "8.15.0", "acorn-walk": "^8.3.4", - "commander": "13.1.0", + "browserslist": "^4.23.3", + "commander": "14.0.0", "fast-glob": "^3.3.3", + "lilconfig": "^3.1.3", + "source-map": "^0.7.4", "supports-color": "8.1.1", "winston": "3.17.0" }, @@ -6254,6 +6412,18 @@ "node": ">= 4" } }, + "node_modules/es-check/node_modules/acorn": { + "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" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/es-check/node_modules/acorn-walk": { "version": "8.3.4", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", @@ -6275,8 +6445,17 @@ "node": ">=8" } }, - "node_modules/es-check/node_modules/supports-color": { - "version": "8.1.1", + "node_modules/es-check/node_modules/source-map": { + "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": ">= 12" + } + }, + "node_modules/es-check/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, @@ -6291,13 +6470,10 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.4" - }, "engines": { "node": ">= 0.4" } @@ -6319,9 +6495,9 @@ "peer": true }, "node_modules/es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "dev": true, "dependencies": { "es-errors": "^1.3.0" @@ -6331,37 +6507,41 @@ } }, "node_modules/es-set-tostringtag": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.4", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" } }, "node_modules/es-shim-unscopables": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", - "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", "dev": true, "dependencies": { - "hasown": "^2.0.0" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" } }, "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", "dev": true, "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" }, "engines": { "node": ">= 0.4" @@ -6371,40 +6551,44 @@ } }, "node_modules/esbuild": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", - "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", + "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", "dev": true, "hasInstallScript": true, + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/android-arm": "0.17.19", - "@esbuild/android-arm64": "0.17.19", - "@esbuild/android-x64": "0.17.19", - "@esbuild/darwin-arm64": "0.17.19", - "@esbuild/darwin-x64": "0.17.19", - "@esbuild/freebsd-arm64": "0.17.19", - "@esbuild/freebsd-x64": "0.17.19", - "@esbuild/linux-arm": "0.17.19", - "@esbuild/linux-arm64": "0.17.19", - "@esbuild/linux-ia32": "0.17.19", - "@esbuild/linux-loong64": "0.17.19", - "@esbuild/linux-mips64el": "0.17.19", - "@esbuild/linux-ppc64": "0.17.19", - "@esbuild/linux-riscv64": "0.17.19", - "@esbuild/linux-s390x": "0.17.19", - "@esbuild/linux-x64": "0.17.19", - "@esbuild/netbsd-x64": "0.17.19", - "@esbuild/openbsd-x64": "0.17.19", - "@esbuild/sunos-x64": "0.17.19", - "@esbuild/win32-arm64": "0.17.19", - "@esbuild/win32-ia32": "0.17.19", - "@esbuild/win32-x64": "0.17.19" + "@esbuild/aix-ppc64": "0.25.4", + "@esbuild/android-arm": "0.25.4", + "@esbuild/android-arm64": "0.25.4", + "@esbuild/android-x64": "0.25.4", + "@esbuild/darwin-arm64": "0.25.4", + "@esbuild/darwin-x64": "0.25.4", + "@esbuild/freebsd-arm64": "0.25.4", + "@esbuild/freebsd-x64": "0.25.4", + "@esbuild/linux-arm": "0.25.4", + "@esbuild/linux-arm64": "0.25.4", + "@esbuild/linux-ia32": "0.25.4", + "@esbuild/linux-loong64": "0.25.4", + "@esbuild/linux-mips64el": "0.25.4", + "@esbuild/linux-ppc64": "0.25.4", + "@esbuild/linux-riscv64": "0.25.4", + "@esbuild/linux-s390x": "0.25.4", + "@esbuild/linux-x64": "0.25.4", + "@esbuild/netbsd-arm64": "0.25.4", + "@esbuild/netbsd-x64": "0.25.4", + "@esbuild/openbsd-arm64": "0.25.4", + "@esbuild/openbsd-x64": "0.25.4", + "@esbuild/sunos-x64": "0.25.4", + "@esbuild/win32-arm64": "0.25.4", + "@esbuild/win32-ia32": "0.25.4", + "@esbuild/win32-x64": "0.25.4" } }, "node_modules/escalade": { @@ -6532,13 +6716,16 @@ } }, "node_modules/eslint-config-prettier": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.2.tgz", - "integrity": "sha512-Epgp/EofAUeEpIdZkW60MHKvPyru1ruQJxPL+WIycnaPApuseK0Zpkrh/FwL9oIpQvIhJwV7ptOy0DWUjTlCiA==", + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, "peerDependencies": { "eslint": ">=7.0.0" } @@ -6564,9 +6751,9 @@ } }, "node_modules/eslint-module-utils": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", - "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", "dev": true, "dependencies": { "debug": "^3.2.7" @@ -6611,29 +6798,29 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.31.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", - "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "dependencies": { "@rtsao/scc": "^1.1.0", - "array-includes": "^3.1.8", - "array.prototype.findlastindex": "^1.2.5", - "array.prototype.flat": "^1.3.2", - "array.prototype.flatmap": "^1.3.2", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.12.0", + "eslint-module-utils": "^2.12.1", "hasown": "^2.0.2", - "is-core-module": "^2.15.1", + "is-core-module": "^2.16.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "object.groupby": "^1.0.3", - "object.values": "^1.2.0", + "object.values": "^1.2.1", "semver": "^6.3.1", - "string.prototype.trimend": "^1.0.8", + "string.prototype.trimend": "^1.0.9", "tsconfig-paths": "^3.15.0" }, "engines": { @@ -6706,9 +6893,9 @@ } }, "node_modules/eslint-plugin-n": { - "version": "17.17.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.17.0.tgz", - "integrity": "sha512-2VvPK7Mo73z1rDFb6pTvkH6kFibAmnTubFq5l83vePxu0WiY1s0LOtj2WHb6Sa40R3w4mnh8GFYbHBQyMlotKw==", + "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", @@ -6716,9 +6903,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" + "semver": "^7.6.3", + "ts-declaration-location": "^1.0.6" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6730,40 +6918,13 @@ "eslint": ">=8.23.0" } }, - "node_modules/eslint-plugin-n/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/eslint-plugin-n/node_modules/globals": { - "version": "15.15.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", - "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", + "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", + "integrity": "sha512-uCotzBHt2W+HbLw2srRmqDJHOPbJGzeVLstKh8YyxS3ppduq2P50qdpJfHKoD+UGbnqA/zhy8NRgPH6p0y8bnA==", "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "peerDependencies": { + "eslint": ">=2.0.0" } }, "node_modules/eslint-plugin-promise": { @@ -7036,9 +7197,9 @@ } }, "node_modules/estree-walker": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", - "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "dev": true }, "node_modules/esutils": { @@ -7066,85 +7227,12 @@ "node": ">=0.8.x" } }, - "node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/execa/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/execa/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/execa/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/execa/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/exit-hook": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz", "integrity": "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" }, @@ -7153,9 +7241,9 @@ } }, "node_modules/exsolve": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.4.tgz", - "integrity": "sha512-xsZH6PXaER4XoV+NiT7JHp1bJodJVT+cxeSH1G0f0tlT0lJqYuHUP3bUx2HtfTDvOagMINYp8rsqusxud3RXhw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", + "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", "dev": true }, "node_modules/extend": { @@ -7470,12 +7558,18 @@ } }, "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "dev": true, "dependencies": { - "is-callable": "^1.1.3" + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/foreground-child": { @@ -7560,15 +7654,17 @@ } }, "node_modules/function.prototype.name": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", - "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "functions-have-names": "^1.2.3" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" }, "engines": { "node": ">= 0.4" @@ -7626,16 +7722,21 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -7644,22 +7745,19 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-source": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/get-source/-/get-source-2.0.12.tgz", - "integrity": "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==", + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "dev": true, "dependencies": { - "data-uri-to-buffer": "^2.0.0", - "source-map": "^0.6.1" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" } }, - "node_modules/get-source/node_modules/data-uri-to-buffer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz", - "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==", - "dev": true - }, "node_modules/get-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", @@ -7676,14 +7774,14 @@ } }, "node_modules/get-symbol-description": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", - "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", "dev": true, "dependencies": { - "call-bind": "^1.0.5", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4" + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -7773,21 +7871,25 @@ "dev": true }, "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", "dev": true, "engines": { - "node": ">=4" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/globalthis": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", "dev": true, "dependencies": { - "define-properties": "^1.1.3" + "define-properties": "^1.2.1", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -7796,13 +7898,19 @@ "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.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.3" + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7842,10 +7950,13 @@ } }, "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", "dev": true, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -7872,10 +7983,13 @@ } }, "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", "dev": true, + "dependencies": { + "dunder-proto": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -7884,9 +7998,9 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, "engines": { "node": ">= 0.4" @@ -8132,15 +8246,6 @@ "node": ">= 6" } }, - "node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "engines": { - "node": ">=16.17.0" - } - }, "node_modules/husky": { "version": "9.1.7", "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", @@ -8235,14 +8340,14 @@ "dev": true }, "node_modules/internal-slot": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", - "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", "dev": true, "dependencies": { "es-errors": "^1.3.0", - "hasown": "^2.0.0", - "side-channel": "^1.0.4" + "hasown": "^2.0.2", + "side-channel": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -8301,13 +8406,33 @@ } }, "node_modules/is-array-buffer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", - "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1" + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -8317,12 +8442,15 @@ } }, "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", "dev": true, "dependencies": { - "has-bigints": "^1.0.1" + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8341,13 +8469,13 @@ } }, "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -8392,9 +8520,9 @@ } }, "node_modules/is-core-module": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", - "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, "dependencies": { "hasown": "^2.0.2" @@ -8407,11 +8535,13 @@ } }, "node_modules/is-data-view": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", - "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", "dev": true, "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", "is-typed-array": "^1.1.13" }, "engines": { @@ -8422,12 +8552,13 @@ } }, "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", "dev": true, "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -8455,6 +8586,21 @@ "node": ">=0.10.0" } }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-fullwidth-code-point": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", @@ -8467,6 +8613,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -8489,6 +8653,18 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", @@ -8517,12 +8693,13 @@ } }, "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", "dev": true, "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -8562,13 +8739,15 @@ } }, "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -8577,14 +8756,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", - "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "dev": true, - "dependencies": { - "call-bind": "^1.0.7" - }, "engines": { "node": ">= 0.4" }, @@ -8592,8 +8768,23 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-stream": { - "version": "2.0.1", + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, @@ -8605,12 +8796,13 @@ } }, "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", "dev": true, "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -8620,12 +8812,14 @@ } }, "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", "dev": true, "dependencies": { - "has-symbols": "^1.0.2" + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -8635,12 +8829,12 @@ } }, "node_modules/is-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", - "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "dev": true, "dependencies": { - "which-typed-array": "^1.1.14" + "which-typed-array": "^1.1.16" }, "engines": { "node": ">= 0.4" @@ -8667,13 +8861,44 @@ "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", "dev": true }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.2" + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -9196,6 +9421,15 @@ "node": ">=0.10.0" } }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/kuler": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", @@ -9237,36 +9471,36 @@ } }, "node_modules/lint-staged": { - "version": "15.5.1", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.5.1.tgz", - "integrity": "sha512-6m7u8mue4Xn6wK6gZvSCQwBvMBR36xfY24nF5bMTf2MHDYG6S3yhJuOgdYVw99hsjyDt2d4z168b3naI8+NWtQ==", + "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", - "commander": "^13.1.0", - "debug": "^4.4.0", - "execa": "^8.0.1", + "chalk": "^5.5.0", + "commander": "^14.0.0", + "debug": "^4.4.1", "lilconfig": "^3.1.3", - "listr2": "^8.2.5", + "listr2": "^9.0.1", "micromatch": "^4.0.8", + "nano-spawn": "^1.0.2", "pidtree": "^0.6.0", "string-argv": "^0.3.2", - "yaml": "^2.7.0" + "yaml": "^2.8.1" }, "bin": { "lint-staged": "bin/lint-staged.js" }, "engines": { - "node": ">=18.12.0" + "node": ">=20.17" }, "funding": { "url": "https://opencollective.com/lint-staged" } }, "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" @@ -9276,9 +9510,9 @@ } }, "node_modules/lint-staged/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "dev": true, "dependencies": { "ms": "^2.1.3" @@ -9299,9 +9533,9 @@ "dev": true }, "node_modules/listr2": { - "version": "8.2.5", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.5.tgz", - "integrity": "sha512-iyAZCeyD+c1gPyE9qpFu8af0Y+MRtmKOncdGoA2S5EY8iFq99dmmvkNnHiWo+pj0s7yH7l3KPIgee77tKpXPWQ==", + "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", @@ -9312,7 +9546,7 @@ "wrap-ansi": "^9.0.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/listr2/node_modules/ansi-regex": { @@ -9612,12 +9846,12 @@ } }, "node_modules/magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "dev": true, "dependencies": { - "sourcemap-codec": "^1.4.8" + "@jridgewell/sourcemap-codec": "^1.5.0" } }, "node_modules/make-dir": { @@ -9698,6 +9932,15 @@ "node": ">= 10" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/mdast-util-find-and-replace": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-1.1.1.tgz", @@ -9891,7 +10134,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true + "dev": true, + "peer": true }, "node_modules/merge2": { "version": "1.4.1", @@ -10093,9 +10337,9 @@ } }, "node_modules/miniflare": { - "version": "3.20250408.0", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20250408.0.tgz", - "integrity": "sha512-URXD7+b0tLbBtchPM/MfWYujymHUrmPtd3EDQbe51qrPPF1zQCdSeNbA4f/GRQMoQIEE6EIhvEYjVjL+hiN+Og==", + "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", @@ -10103,18 +10347,19 @@ "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", + "sharp": "^0.33.5", "stoppable": "1.1.0", - "undici": "^5.28.5", - "workerd": "1.20250408.0", + "undici": "^7.10.0", + "workerd": "1.20250816.0", "ws": "8.18.0", - "youch": "3.3.4", + "youch": "4.1.0-beta.10", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" }, "engines": { - "node": ">=16.13" + "node": ">=18.0.0" } }, "node_modules/minimatch": { @@ -10173,28 +10418,28 @@ "dev": true }, "node_modules/mocha": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.1.0.tgz", - "integrity": "sha512-8uJR5RTC2NgpY3GrYcgpZrsEd9zKbPDpob1RezyR2upGHRQtHWofmzTMzTMSV6dru3tj5Ukt0+Vnq1qhFEEwAg==", + "version": "11.7.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.1.tgz", + "integrity": "sha512-5EK+Cty6KheMS/YLPPMJC64g5V61gIR25KsRItHw6x4hEKT6Njp1n9LOlH4gpevuwMVS66SXaBBpg+RWZkza4A==", "dev": true, "dependencies": { - "ansi-colors": "^4.1.3", "browser-stdout": "^1.3.1", - "chokidar": "^3.5.3", + "chokidar": "^4.0.1", "debug": "^4.3.5", - "diff": "^5.2.0", + "diff": "^7.0.0", "escape-string-regexp": "^4.0.0", "find-up": "^5.0.0", "glob": "^10.4.5", "he": "^1.2.0", "js-yaml": "^4.1.0", "log-symbols": "^4.1.0", - "minimatch": "^5.1.6", + "minimatch": "^9.0.5", "ms": "^2.1.3", + "picocolors": "^1.1.1", "serialize-javascript": "^6.0.2", "strip-json-comments": "^3.1.1", "supports-color": "^8.1.1", - "workerpool": "^6.5.1", + "workerpool": "^9.2.0", "yargs": "^17.7.2", "yargs-parser": "^21.1.1", "yargs-unparser": "^2.0.0" @@ -10223,9 +10468,9 @@ } }, "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "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" @@ -10259,6 +10504,21 @@ "node": ">=8" } }, + "node_modules/mocha/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/mocha/node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -10323,21 +10583,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/mocha/node_modules/glob/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/mocha/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -10373,15 +10618,18 @@ } }, "node_modules/mocha/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "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": ">=10" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/mocha/node_modules/ms": { @@ -10390,6 +10638,19 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, + "node_modules/mocha/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/mocha/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -10461,13 +10722,16 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/mustache": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", - "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "node_modules/nano-spawn": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-1.0.2.tgz", + "integrity": "sha512-21t+ozMQDAL/UGgQVBbZ/xXvNO10++ZPuTmKRO8k9V3AClVRht49ahtDjfY8l1q6nSHOrE5ASfthzH3ol6R/hg==", "dev": true, - "bin": { - "mustache": "bin/mustache" + "engines": { + "node": ">=20.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/nano-spawn?sponsor=1" } }, "node_modules/natural-compare": { @@ -10584,15 +10848,15 @@ } }, "node_modules/npm-run-all2": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/npm-run-all2/-/npm-run-all2-7.0.2.tgz", - "integrity": "sha512-7tXR+r9hzRNOPNTvXegM+QzCuMjzUIIq66VDunL6j60O4RrExx32XUhlrS7UK4VcdGw5/Wxzb3kfNcFix9JKDA==", + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/npm-run-all2/-/npm-run-all2-8.0.4.tgz", + "integrity": "sha512-wdbB5My48XKp2ZfJUlhnLVihzeuA1hgBnqB2J9ahV77wLS+/YAJAlN8I+X3DIFIPZ3m5L7nplmlbhNiFDmXRDA==", "dev": true, "dependencies": { "ansi-styles": "^6.2.1", "cross-spawn": "^7.0.6", "memorystream": "^0.3.1", - "minimatch": "^9.0.0", + "picomatch": "^4.0.2", "pidtree": "^0.6.0", "read-package-json-fast": "^4.0.0", "shell-quote": "^1.7.3", @@ -10605,8 +10869,8 @@ "run-s": "bin/run-s/index.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0", - "npm": ">= 9" + "node": "^20.5.0 || >=22.0.0", + "npm": ">= 10" } }, "node_modules/npm-run-all2/node_modules/ansi-styles": { @@ -10621,15 +10885,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/npm-run-all2/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/npm-run-all2/node_modules/isexe": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", @@ -10639,19 +10894,16 @@ "node": ">=16" } }, - "node_modules/npm-run-all2/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "node_modules/npm-run-all2/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/npm-run-all2/node_modules/which": { @@ -10669,33 +10921,6 @@ "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/npm-run-path": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", - "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", - "dev": true, - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -10706,9 +10931,9 @@ } }, "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true, "engines": { "node": ">= 0.4" @@ -10727,14 +10952,16 @@ } }, "node_modules/object.assign": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", - "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", "dev": true, "dependencies": { - "call-bind": "^1.0.5", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "define-properties": "^1.2.1", - "has-symbols": "^1.0.3", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", "object-keys": "^1.1.1" }, "engines": { @@ -10777,12 +11004,13 @@ } }, "node_modules/object.values": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", - "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" }, @@ -10797,7 +11025,8 @@ "version": "2.0.11", "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/on-finished": { "version": "2.4.1", @@ -10876,6 +11105,23 @@ "node": ">= 0.8.0" } }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -11079,7 +11325,8 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/pathval": { "version": "1.1.1", @@ -11330,9 +11577,9 @@ } }, "node_modules/possible-typed-array-names": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", - "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", "dev": true, "engines": { "node": ">= 0.4" @@ -11348,9 +11595,9 @@ } }, "node_modules/prettier": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", - "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -11362,12 +11609,6 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/printable-characters": { - "version": "1.0.42", - "resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz", - "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==", - "dev": true - }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -11590,6 +11831,28 @@ "node": ">=8.10.0" } }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -11608,31 +11871,18 @@ "node": ">=4" } }, - "node_modules/regenerator-runtime": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==", - "dev": true - }, - "node_modules/regenerator-transform": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", - "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, "node_modules/regexp.prototype.flags": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", - "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", "dev": true, "dependencies": { - "call-bind": "^1.0.6", + "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", - "set-function-name": "^2.0.1" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -11642,15 +11892,15 @@ } }, "node_modules/regexpu-core": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.1.1.tgz", - "integrity": "sha512-k67Nb9jvwJcJmVpw0jPttR1/zVfnKf8Km0IPatrU/zJ5XeG3+Slx0xLXs9HByJSzXzrlz5EDvN6yLNMDc2qdnw==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", + "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", "dev": true, "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.0", "regjsgen": "^0.8.0", - "regjsparser": "^0.11.0", + "regjsparser": "^0.12.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.1.0" }, @@ -11665,9 +11915,9 @@ "dev": true }, "node_modules/regjsparser": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.11.1.tgz", - "integrity": "sha512-1DHODs4B8p/mQHU9kr+jv8+wIC9mtG4eBHxWxIq5mhjE3D5oORhCc6deRKzTjs9DcfRFmj9BHSDguZklqCGFWQ==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", + "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", "dev": true, "dependencies": { "jsesc": "~3.0.2" @@ -11766,18 +12016,21 @@ "dev": true }, "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", "dev": true, "dependencies": { - "is-core-module": "^2.13.0", + "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -11848,12 +12101,12 @@ } }, "node_modules/rollup": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz", - "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", + "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.7" + "@types/estree": "1.0.8" }, "bin": { "rollup": "dist/bin/rollup" @@ -11863,41 +12116,29 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.40.0", - "@rollup/rollup-android-arm64": "4.40.0", - "@rollup/rollup-darwin-arm64": "4.40.0", - "@rollup/rollup-darwin-x64": "4.40.0", - "@rollup/rollup-freebsd-arm64": "4.40.0", - "@rollup/rollup-freebsd-x64": "4.40.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", - "@rollup/rollup-linux-arm-musleabihf": "4.40.0", - "@rollup/rollup-linux-arm64-gnu": "4.40.0", - "@rollup/rollup-linux-arm64-musl": "4.40.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", - "@rollup/rollup-linux-riscv64-gnu": "4.40.0", - "@rollup/rollup-linux-riscv64-musl": "4.40.0", - "@rollup/rollup-linux-s390x-gnu": "4.40.0", - "@rollup/rollup-linux-x64-gnu": "4.40.0", - "@rollup/rollup-linux-x64-musl": "4.40.0", - "@rollup/rollup-win32-arm64-msvc": "4.40.0", - "@rollup/rollup-win32-ia32-msvc": "4.40.0", - "@rollup/rollup-win32-x64-msvc": "4.40.0", + "@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" } }, - "node_modules/rollup-plugin-inject": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz", - "integrity": "sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==", - "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject.", - "dev": true, - "dependencies": { - "estree-walker": "^0.6.1", - "magic-string": "^0.25.3", - "rollup-pluginutils": "^2.8.1" - } - }, "node_modules/rollup-plugin-istanbul": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/rollup-plugin-istanbul/-/rollup-plugin-istanbul-5.0.0.tgz", @@ -11932,28 +12173,10 @@ "node": ">=10" } }, - "node_modules/rollup-plugin-node-polyfills": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/rollup-plugin-node-polyfills/-/rollup-plugin-node-polyfills-0.2.1.tgz", - "integrity": "sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==", - "dev": true, - "dependencies": { - "rollup-plugin-inject": "^3.0.0" - } - }, - "node_modules/rollup-pluginutils": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", - "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", - "dev": true, - "dependencies": { - "estree-walker": "^0.6.1" - } - }, "node_modules/rollup/node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true }, "node_modules/run-parallel": { @@ -11980,14 +12203,15 @@ } }, "node_modules/safe-array-concat": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", - "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", - "get-intrinsic": "^1.2.4", - "has-symbols": "^1.0.3", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", "isarray": "^2.0.5" }, "engines": { @@ -12009,15 +12233,37 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, "node_modules/safe-regex-test": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", - "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", "dev": true, "dependencies": { - "call-bind": "^1.0.6", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", - "is-regex": "^1.1.4" + "is-regex": "^1.2.1" }, "engines": { "node": ">= 0.4" @@ -12077,9 +12323,9 @@ "dev": true }, "node_modules/selenium-webdriver": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.31.0.tgz", - "integrity": "sha512-0MWEwypM0+c1NnZ87UEMxZdwphKoaK2UJ2qXzKWrJiM0gazFjgNVimxlHTOO90G2cOhphZqwpqSCJy62NTEzyA==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.35.0.tgz", + "integrity": "sha512-Baaeiuyu7BIIsSYf0SI7Mi55gsNmdI00KM0Hcofw1RnAY+0QEVpdh5yAxueDxgTZS8vcbGZFU0NJ6Qc1riIrLg==", "dev": true, "funding": [ { @@ -12095,16 +12341,37 @@ "@bazel/runfiles": "^6.3.1", "jszip": "^3.10.1", "tmp": "^0.2.3", - "ws": "^8.18.0" + "ws": "^8.18.2" }, "engines": { - "node": ">= 18.20.5" + "node": ">= 20.0.0" + } + }, + "node_modules/selenium-webdriver/node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -12154,6 +12421,20 @@ "node": ">= 0.4" } }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -12184,7 +12465,7 @@ "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", "dev": true, "hasInstallScript": true, - "optional": true, + "license": "Apache-2.0", "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", @@ -12223,7 +12504,7 @@ "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", "dev": true, - "optional": true, + "license": "MIT", "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" @@ -12237,7 +12518,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "optional": true, + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -12276,14 +12557,72 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "dev": true, "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -12344,15 +12683,6 @@ "sinon": ">=4.0.0" } }, - "node_modules/sinon/node_modules/diff": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", - "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/sinon/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -12539,13 +12869,6 @@ "source-map": "^0.6.0" } }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "deprecated": "Please use @jridgewell/sourcemap-codec instead", - "dev": true - }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -12561,16 +12884,6 @@ "node": "*" } }, - "node_modules/stacktracey": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/stacktracey/-/stacktracey-2.1.8.tgz", - "integrity": "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==", - "dev": true, - "dependencies": { - "as-table": "^1.0.36", - "get-source": "^2.0.12" - } - }, "node_modules/statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", @@ -12580,11 +12893,25 @@ "node": ">= 0.6" } }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/stoppable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", "dev": true, + "license": "MIT", "engines": { "node": ">=4", "npm": ">=6" @@ -12711,15 +13038,18 @@ } }, "node_modules/string.prototype.trim": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", - "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.0", - "es-object-atoms": "^1.0.0" + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -12729,15 +13059,19 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", - "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -12793,18 +13127,6 @@ "node": ">=4" } }, - "node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -13049,11 +13371,10 @@ } }, "node_modules/ts-api-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz", - "integrity": "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=18.12" }, @@ -13061,6 +13382,40 @@ "typescript": ">=4.8.4" } }, + "node_modules/ts-declaration-location": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/ts-declaration-location/-/ts-declaration-location-1.0.7.tgz", + "integrity": "sha512-EDyGAwH1gO0Ausm9gV6T2nUvBgXT5kGoCMJPllOaooZ+4VvJiKBdZE7wK18N1deEowhcUptS+5GXZK8U/fvpwA==", + "dev": true, + "funding": [ + { + "type": "ko-fi", + "url": "https://ko-fi.com/rebeccastevens" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/ts-declaration-location" + } + ], + "dependencies": { + "picomatch": "^4.0.2" + }, + "peerDependencies": { + "typescript": ">=4.0.0" + } + }, + "node_modules/ts-declaration-location/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -13138,30 +13493,30 @@ } }, "node_modules/typed-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", - "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "is-typed-array": "^1.1.13" + "is-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" } }, "node_modules/typed-array-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", - "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -13171,17 +13526,18 @@ } }, "node_modules/typed-array-byte-offset": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", - "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", "dev": true, "dependencies": { "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" }, "engines": { "node": ">= 0.4" @@ -13191,17 +13547,17 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", - "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", "dev": true, "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-proto": "^1.0.3", "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0" + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" }, "engines": { "node": ">= 0.4" @@ -13243,10 +13599,11 @@ } }, "node_modules/ufo": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", - "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", - "dev": true + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "dev": true, + "license": "MIT" }, "node_modules/uglify-js": { "version": "3.17.4", @@ -13262,15 +13619,18 @@ } }, "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", + "call-bound": "^1.0.3", "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -13283,15 +13643,12 @@ "dev": true }, "node_modules/undici": { - "version": "5.28.5", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.5.tgz", - "integrity": "sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.11.0.tgz", + "integrity": "sha512-heTSIac3iLhsmZhUCjyS3JQEkZELateufzZuBaVM5RHXdSBMb1LPMQf5x+FH7qjsZYDP0ttAc3nnVpUB+wYbOg==", "dev": true, - "dependencies": { - "@fastify/busboy": "^2.0.0" - }, "engines": { - "node": ">=14.0" + "node": ">=20.18.1" } }, "node_modules/undici-types": { @@ -13302,16 +13659,16 @@ "license": "MIT" }, "node_modules/unenv": { - "version": "2.0.0-rc.14", - "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.14.tgz", - "integrity": "sha512-od496pShMen7nOy5VmVJCnq8rptd45vh6Nx/r2iPbrba6pa6p+tS2ywuIHRZ/OBvSbQZB0kWvpO9XBNVFXHD3Q==", + "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.1", - "ohash": "^2.0.10", + "exsolve": "^1.0.7", + "ohash": "^2.0.11", "pathe": "^2.0.3", - "ufo": "^1.5.4" + "ufo": "^1.6.1" } }, "node_modules/unicode-canonical-property-names-ecmascript": { @@ -13449,9 +13806,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", - "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "dev": true, "funding": [ { @@ -13704,35 +14061,91 @@ "node-which": "bin/node-which" }, "engines": { - "node": ">= 8" + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/which-boxed-primitive": { + "node_modules/which-builtin-type/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/which-collection": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "dev": true, "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/which-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", - "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", "dev": true, "dependencies": { "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" }, "engines": { @@ -13888,9 +14301,9 @@ "dev": true }, "node_modules/workerd": { - "version": "1.20250408.0", - "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250408.0.tgz", - "integrity": "sha512-bBUX+UsvpzAqiWFNeZrlZmDGddiGZdBBbftZJz2wE6iUg/cIAJeVQYTtS/3ahaicguoLBz4nJiDo8luqM9fx1A==", + "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": { @@ -13900,49 +14313,46 @@ "node": ">=16" }, "optionalDependencies": { - "@cloudflare/workerd-darwin-64": "1.20250408.0", - "@cloudflare/workerd-darwin-arm64": "1.20250408.0", - "@cloudflare/workerd-linux-64": "1.20250408.0", - "@cloudflare/workerd-linux-arm64": "1.20250408.0", - "@cloudflare/workerd-windows-64": "1.20250408.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": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", - "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.2.tgz", + "integrity": "sha512-Xz4Nm9c+LiBHhDR5bDLnNzmj6+5F+cyEAWPMkbs2awq/dYazR/efelZzUAjB/y3kNHL+uzkHvxVVpaOfGCPV7A==", "dev": true }, "node_modules/wrangler": { - "version": "3.114.6", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.114.6.tgz", - "integrity": "sha512-05Ov/Bg8BQEy+/x/aRTeEUiXYspCiE0wmdgg4TIQwYLeEZaoBLE6KhqxEiLd8WNea0IRpzpBQOtAZ64Tjl0znQ==", + "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.3.4", - "@cloudflare/unenv-preset": "2.0.2", - "@esbuild-plugins/node-globals-polyfill": "0.2.3", - "@esbuild-plugins/node-modules-polyfill": "0.2.2", + "@cloudflare/kv-asset-handler": "0.4.0", + "@cloudflare/unenv-preset": "2.6.2", "blake3-wasm": "2.1.5", - "esbuild": "0.17.19", - "miniflare": "3.20250408.0", + "esbuild": "0.25.4", + "miniflare": "4.20250816.0", "path-to-regexp": "6.3.0", - "unenv": "2.0.0-rc.14", - "workerd": "1.20250408.0" + "unenv": "2.0.0-rc.19", + "workerd": "1.20250816.0" }, "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" }, "engines": { - "node": ">=16.17.0" + "node": ">=18.0.0" }, "optionalDependencies": { - "fsevents": "~2.3.2", - "sharp": "^0.33.5" + "fsevents": "~2.3.2" }, "peerDependencies": { - "@cloudflare/workers-types": "^4.20250408.0" + "@cloudflare/workers-types": "^4.20250816.0" }, "peerDependenciesMeta": { "@cloudflare/workers-types": { @@ -13950,6 +14360,21 @@ } } }, + "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==", + "dev": true, + "peerDependencies": { + "unenv": "2.0.0-rc.19", + "workerd": "^1.20250802.0" + }, + "peerDependenciesMeta": { + "workerd": { + "optional": true + } + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -14149,15 +14574,15 @@ "dev": true }, "node_modules/yaml": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", - "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", + "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" }, "engines": { - "node": ">= 14" + "node": ">= 14.6" } }, "node_modules/yargs": { @@ -14287,23 +14712,35 @@ } }, "node_modules/youch": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/youch/-/youch-3.3.4.tgz", - "integrity": "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg==", + "version": "4.1.0-beta.10", + "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz", + "integrity": "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==", + "dev": true, + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@poppinss/dumper": "^0.6.4", + "@speed-highlight/core": "^1.2.7", + "cookie": "^1.0.2", + "youch-core": "^0.3.3" + } + }, + "node_modules/youch-core": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz", + "integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==", "dev": true, "dependencies": { - "cookie": "^0.7.1", - "mustache": "^4.2.0", - "stacktracey": "^2.1.8" + "@poppinss/exception": "^1.2.2", + "error-stack-parser-es": "^1.0.5" } }, "node_modules/youch/node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", "dev": true, "engines": { - "node": ">= 0.6" + "node": ">=18" } }, "node_modules/zod": { @@ -14311,6 +14748,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz", "integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -14344,38 +14782,38 @@ } }, "@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" } }, "@babel/compat-data": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", - "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", + "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", "dev": true }, "@babel/core": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", - "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "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.26.2", - "@babel/generator": "^7.26.10", - "@babel/helper-compilation-targets": "^7.26.5", - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helpers": "^7.26.10", - "@babel/parser": "^7.26.10", - "@babel/template": "^7.26.9", - "@babel/traverse": "^7.26.10", - "@babel/types": "^7.26.10", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@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.3", + "@babel/types": "^7.28.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -14392,48 +14830,47 @@ } }, "@babel/generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", - "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", + "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.27.0", - "@babel/types": "^7.27.0", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", + "@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" }, "dependencies": { "@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", "dev": true, "requires": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } } } }, "@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", "dev": true, "requires": { - "@babel/types": "^7.25.9" + "@babel/types": "^7.27.3" } }, "@babel/helper-compilation-targets": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", - "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", "dev": true, "requires": { - "@babel/compat-data": "^7.26.5", - "@babel/helper-validator-option": "^7.25.9", + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -14463,17 +14900,17 @@ } }, "@babel/helper-create-class-features-plugin": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.0.tgz", - "integrity": "sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg==", + "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.25.9", - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/helper-replace-supers": "^7.26.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/traverse": "^7.27.0", + "@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.28.3", "semver": "^6.3.1" }, "dependencies": { @@ -14486,13 +14923,13 @@ } }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.9.tgz", - "integrity": "sha512-ORPNZ3h6ZRkOyAa/SaHU+XsLZr0UQzRwuDQ0cczIA17nAzZ+85G5cVkOJIj7QavLZGSe8QXUmNFxSZzjcZF9bw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", + "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "regexpu-core": "^6.1.1", + "@babel/helper-annotate-as-pure": "^7.27.1", + "regexpu-core": "^6.2.0", "semver": "^6.3.1" }, "dependencies": { @@ -14505,225 +14942,214 @@ } }, "@babel/helper-define-polyfill-provider": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz", - "integrity": "sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", + "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", "dev": true, "requires": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "debug": "^4.4.1", "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" + "resolve": "^1.22.10" + }, + "dependencies": { + "debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "requires": { + "ms": "^2.1.3" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } } }, + "@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true + }, "@babel/helper-member-expression-to-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", - "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", + "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", "dev": true, "requires": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" } }, "@babel/helper-module-imports": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", - "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", "dev": true, "requires": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" } }, "@babel/helper-module-transforms": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", - "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "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.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" } }, "@babel/helper-optimise-call-expression": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", - "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", "dev": true, "requires": { - "@babel/types": "^7.25.9" + "@babel/types": "^7.27.1" } }, "@babel/helper-plugin-utils": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", - "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", "dev": true }, "@babel/helper-remap-async-to-generator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", - "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-wrap-function": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" } }, "@babel/helper-replace-supers": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz", - "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/traverse": "^7.26.5" + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" } }, "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", - "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", "dev": true, "requires": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" } }, "@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true }, "@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "dev": true }, "@babel/helper-validator-option": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", - "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true }, "@babel/helper-wrap-function": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", - "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.27.1.tgz", + "integrity": "sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ==", "dev": true, "requires": { - "@babel/template": "^7.25.9", - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/template": "^7.27.1", + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" } }, "@babel/helpers": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.10.tgz", - "integrity": "sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==", + "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.26.9", - "@babel/types": "^7.26.10" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2" } }, "@babel/parser": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", - "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "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.27.0" + "@babel/types": "^7.28.2" } }, "@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz", - "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", + "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" } }, "@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz", - "integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz", - "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", - "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/plugin-transform-optional-chaining": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" } }, "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", - "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" - } - }, - "@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", - "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.20.7" - } - }, - "@babel/plugin-proposal-optional-chaining": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", - "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", + "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.20.2", - "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.3" } }, "@babel/plugin-proposal-private-property-in-object": { @@ -14734,57 +15160,39 @@ "requires": {} }, "@babel/plugin-syntax-import-assertions": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz", - "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", + "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-syntax-import-attributes": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", - "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-syntax-jsx": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", - "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.25.9" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-syntax-typescript": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", - "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-syntax-unicode-sets-regex": { @@ -14798,588 +15206,601 @@ } }, "@babel/plugin-transform-arrow-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", - "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-async-generator-functions": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.26.8.tgz", - "integrity": "sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", + "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-remap-async-to-generator": "^7.25.9", - "@babel/traverse": "^7.26.8" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.28.0" } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz", - "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", + "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-remap-async-to-generator": "^7.25.9" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1" } }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.26.5.tgz", - "integrity": "sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.26.5" + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz", - "integrity": "sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.0.tgz", + "integrity": "sha512-gKKnwjpdx5sER/wl0WN0efUBFzF/56YZO0RJrSYP4CljXnP31ByY7fol89AzomdlLNzI36AvOTmYHsnZTCkq8Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-class-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", - "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", + "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-class-static-block": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz", - "integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==", + "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.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-class-features-plugin": "^7.28.3", + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-classes": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", - "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", + "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.25.9", - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9", - "@babel/traverse": "^7.25.9", - "globals": "^11.1.0" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/traverse": "^7.28.3" } }, "@babel/plugin-transform-computed-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", - "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", + "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/template": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/template": "^7.27.1" } }, "@babel/plugin-transform-destructuring": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", - "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.0.tgz", + "integrity": "sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.0" } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", - "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", + "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", - "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz", - "integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-dynamic-import": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", - "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.27.1" + } + }, + "@babel/plugin-transform-explicit-resource-management": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz", + "integrity": "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0" } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz", - "integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz", + "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-export-namespace-from": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", - "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-for-of": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.26.9.tgz", - "integrity": "sha512-Hry8AusVm8LW5BVFgiyUReuoGzPUpdHQQqJY5bZnbbf+ngOHWuCuYFKw/BqaaWlvEUrF91HMhDtEaI1hZzNbLg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" } }, "@babel/plugin-transform-function-name": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", - "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", "dev": true, "requires": { - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" } }, "@babel/plugin-transform-json-strings": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", - "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", + "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", - "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-logical-assignment-operators": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", - "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", + "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-member-expression-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", - "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-modules-amd": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", - "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", - "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", + "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", - "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", + "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.1" } }, "@babel/plugin-transform-modules-umd": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", - "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", - "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-new-target": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", - "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.26.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz", - "integrity": "sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", + "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.26.5" + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-numeric-separator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", - "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", + "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-object-assign": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.25.9.tgz", - "integrity": "sha512-I/Vl1aQnPsrrn837oLbo+VQtkNcjuuiATqwmuweg4fTauwHHQoxyjmjjOVKyO8OaTxgqYTKW3LuQsykXjDf5Ag==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.27.1.tgz", + "integrity": "sha512-LP6tsnirA6iy13uBKiYgjJsfQrodmlSrpZModtlo1Vk8sOO68gfo7dfA9TGJyEgxTiO7czK4EGZm8FJEZtk4kQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-object-rest-spread": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", - "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.0.tgz", + "integrity": "sha512-9VNGikXxzu5eCiQjdE4IZn8sb9q7Xsk5EXLDBKUYg1e/Tve8/05+KJEtcxGxAgCY5t/BpKQM+JEL/yT4tvgiUA==", "dev": true, "requires": { - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/plugin-transform-parameters": "^7.25.9" + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.0" } }, "@babel/plugin-transform-object-super": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", - "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" } }, "@babel/plugin-transform-optional-catch-binding": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", - "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", + "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-optional-chaining": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", - "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", + "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" } }, "@babel/plugin-transform-parameters": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", - "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-private-methods": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", - "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", + "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-private-property-in-object": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", - "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", + "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-property-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", - "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-regenerator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz", - "integrity": "sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==", + "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.25.9", - "regenerator-transform": "^0.15.2" + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-regexp-modifiers": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz", - "integrity": "sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", + "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-reserved-words": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", - "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", - "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-spread": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", - "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", + "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" } }, "@babel/plugin-transform-sticky-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", - "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-template-literals": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.26.8.tgz", - "integrity": "sha512-OmGDL5/J0CJPJZTHZbi2XpO0tyT2Ia7fzpW5GURwdtp2X3fMmN8au/ej6peC/T33/+CRiIpA8Krse8hFGVmT5Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.26.5" + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.26.7.tgz", - "integrity": "sha512-jfoTXXZTgGg36BmhqT3cAYK5qkmqvJpvNrPhaK/52Vgjhw4Rq29s9UqpWWV0D6yuRmgiFH/BUVlkl96zJWqnaw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.26.5" + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-typescript": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.0.tgz", - "integrity": "sha512-fRGGjO2UEGPjvEcyAZXRXAS8AfdaQoq7HnxAbJoAoW10B9xOKesmmndJv+Sym2a+9FHWZ9KbyyLCe9s0Sn5jtg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.1.tgz", + "integrity": "sha512-Q5sT5+O4QUebHdbwKedFBEwRLb02zJ7r4A5Gg2hUoLuU3FjdMcyqcywqUrLCaDsFCxzokf7u9kuy7qz51YUuAg==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-create-class-features-plugin": "^7.27.0", - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/plugin-syntax-typescript": "^7.25.9" + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1" } }, "@babel/plugin-transform-unicode-escapes": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", - "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-unicode-property-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", - "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", + "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", - "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/plugin-transform-unicode-sets-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", - "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", + "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" } }, "@babel/preset-env": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.9.tgz", - "integrity": "sha512-vX3qPGE8sEKEAZCWk05k3cpTAE3/nOYca++JA+Rd0z2NCNzabmYvEiSShKzm10zdquOIAVXsy2Ei/DTW34KlKQ==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.26.8", - "@babel/helper-compilation-targets": "^7.26.5", - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-validator-option": "^7.25.9", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.9", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.9", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.9", + "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", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", + "@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.28.3", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.26.0", - "@babel/plugin-syntax-import-attributes": "^7.26.0", + "@babel/plugin-syntax-import-assertions": "^7.27.1", + "@babel/plugin-syntax-import-attributes": "^7.27.1", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.25.9", - "@babel/plugin-transform-async-generator-functions": "^7.26.8", - "@babel/plugin-transform-async-to-generator": "^7.25.9", - "@babel/plugin-transform-block-scoped-functions": "^7.26.5", - "@babel/plugin-transform-block-scoping": "^7.25.9", - "@babel/plugin-transform-class-properties": "^7.25.9", - "@babel/plugin-transform-class-static-block": "^7.26.0", - "@babel/plugin-transform-classes": "^7.25.9", - "@babel/plugin-transform-computed-properties": "^7.25.9", - "@babel/plugin-transform-destructuring": "^7.25.9", - "@babel/plugin-transform-dotall-regex": "^7.25.9", - "@babel/plugin-transform-duplicate-keys": "^7.25.9", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9", - "@babel/plugin-transform-dynamic-import": "^7.25.9", - "@babel/plugin-transform-exponentiation-operator": "^7.26.3", - "@babel/plugin-transform-export-namespace-from": "^7.25.9", - "@babel/plugin-transform-for-of": "^7.26.9", - "@babel/plugin-transform-function-name": "^7.25.9", - "@babel/plugin-transform-json-strings": "^7.25.9", - "@babel/plugin-transform-literals": "^7.25.9", - "@babel/plugin-transform-logical-assignment-operators": "^7.25.9", - "@babel/plugin-transform-member-expression-literals": "^7.25.9", - "@babel/plugin-transform-modules-amd": "^7.25.9", - "@babel/plugin-transform-modules-commonjs": "^7.26.3", - "@babel/plugin-transform-modules-systemjs": "^7.25.9", - "@babel/plugin-transform-modules-umd": "^7.25.9", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9", - "@babel/plugin-transform-new-target": "^7.25.9", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.26.6", - "@babel/plugin-transform-numeric-separator": "^7.25.9", - "@babel/plugin-transform-object-rest-spread": "^7.25.9", - "@babel/plugin-transform-object-super": "^7.25.9", - "@babel/plugin-transform-optional-catch-binding": "^7.25.9", - "@babel/plugin-transform-optional-chaining": "^7.25.9", - "@babel/plugin-transform-parameters": "^7.25.9", - "@babel/plugin-transform-private-methods": "^7.25.9", - "@babel/plugin-transform-private-property-in-object": "^7.25.9", - "@babel/plugin-transform-property-literals": "^7.25.9", - "@babel/plugin-transform-regenerator": "^7.25.9", - "@babel/plugin-transform-regexp-modifiers": "^7.26.0", - "@babel/plugin-transform-reserved-words": "^7.25.9", - "@babel/plugin-transform-shorthand-properties": "^7.25.9", - "@babel/plugin-transform-spread": "^7.25.9", - "@babel/plugin-transform-sticky-regex": "^7.25.9", - "@babel/plugin-transform-template-literals": "^7.26.8", - "@babel/plugin-transform-typeof-symbol": "^7.26.7", - "@babel/plugin-transform-unicode-escapes": "^7.25.9", - "@babel/plugin-transform-unicode-property-regex": "^7.25.9", - "@babel/plugin-transform-unicode-regex": "^7.25.9", - "@babel/plugin-transform-unicode-sets-regex": "^7.25.9", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.28.0", + "@babel/plugin-transform-async-to-generator": "^7.27.1", + "@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.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", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.0", + "@babel/plugin-transform-exponentiation-operator": "^7.27.1", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.27.1", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-modules-systemjs": "^7.27.1", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", + "@babel/plugin-transform-numeric-separator": "^7.27.1", + "@babel/plugin-transform-object-rest-spread": "^7.28.0", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1", + "@babel/plugin-transform-parameters": "^7.27.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.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", + "@babel/plugin-transform-spread": "^7.27.1", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.27.1", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.11.0", - "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.40.0", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "core-js-compat": "^3.43.0", "semver": "^6.3.1" }, "dependencies": { @@ -15403,22 +15824,22 @@ } }, "@babel/preset-typescript": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.0.tgz", - "integrity": "sha512-vxaPFfJtHhgeOVXRKuHpHPAOgymmy8V8I65T1q53R7GCZlefKeCaTyDs3zOPHTTbmquvNlQYC5klEvWsBAtrBQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", + "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-validator-option": "^7.25.9", - "@babel/plugin-syntax-jsx": "^7.25.9", - "@babel/plugin-transform-modules-commonjs": "^7.26.3", - "@babel/plugin-transform-typescript": "^7.27.0" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.27.1" } }, "@babel/register": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.25.9.tgz", - "integrity": "sha512-8D43jXtGsYmEeDvm4MWHYUpWf8iiXgWYx3fW7E7Wb7Oe6FWqJPl5K6TuFW0dOwNZzEE5rjlaSJYH9JjrUKJszA==", + "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", @@ -15428,49 +15849,40 @@ "source-map-support": "^0.5.16" } }, - "@babel/runtime": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz", - "integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.14.0" - } - }, "@babel/template": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", - "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "dev": true, "requires": { - "@babel/code-frame": "^7.26.2", - "@babel/parser": "^7.27.0", - "@babel/types": "^7.27.0" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" } }, "@babel/traverse": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", - "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", + "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.26.2", - "@babel/generator": "^7.27.0", - "@babel/parser": "^7.27.0", - "@babel/template": "^7.27.0", - "@babel/types": "^7.27.0", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.3", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2", + "debug": "^4.3.1" } }, "@babel/types": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", - "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "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.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" } }, "@bazel/runfiles": { @@ -15480,9 +15892,9 @@ "dev": true }, "@cloudflare/kv-asset-handler": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.4.tgz", - "integrity": "sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.4.0.tgz", + "integrity": "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA==", "dev": true, "requires": { "mime": "^3.0.0" @@ -15496,45 +15908,38 @@ } } }, - "@cloudflare/unenv-preset": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.0.2.tgz", - "integrity": "sha512-nyzYnlZjjV5xT3LizahG1Iu6mnrCaxglJ04rZLpDwlDVDZ7v46lNsfxhV3A/xtfgQuSHmLnc6SVI+KwBpc3Lwg==", - "dev": true, - "requires": {} - }, "@cloudflare/workerd-darwin-64": { - "version": "1.20250408.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20250408.0.tgz", - "integrity": "sha512-bxhIwBWxaNItZLXDNOKY2dCv0FHjDiDkfJFpwv4HvtvU5MKcrivZHVmmfDzLW85rqzfcDOmKbZeMPVfiKxdBZw==", + "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.20250408.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20250408.0.tgz", - "integrity": "sha512-5XZ2Oykr8bSo7zBmERtHh18h5BZYC/6H1YFWVxEj3PtalF3+6SHsO4KZsbGvDml9Pu7sHV277jiZE5eny8Hlyw==", + "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.20250408.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20250408.0.tgz", - "integrity": "sha512-WbgItXWln6G5d7GvYLWcuOzAVwafysZaWunH3UEfsm95wPuRofpYnlDD861gdWJX10IHSVgMStGESUcs7FLerQ==", + "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.20250408.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20250408.0.tgz", - "integrity": "sha512-pAhEywPPvr92SLylnQfZEPgXz+9pOG9G9haAPLpEatncZwYiYd9yiR6HYWhKp2erzCoNrOqKg9IlQwU3z1IDiw==", + "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.20250408.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20250408.0.tgz", - "integrity": "sha512-nJ3RjMKGae2aF2rZ/CNeBvQPM+W5V1SUK0FYWG/uomyr7uQ2l4IayHna1ODg/OHHTEgIjwom0Mbn58iXb0WOcQ==", + "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 }, @@ -15577,198 +15982,194 @@ } }, "@emnapi/runtime": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", - "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", + "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", "dev": true, "optional": true, "requires": { "tslib": "^2.4.0" } }, - "@esbuild-plugins/node-globals-polyfill": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-globals-polyfill/-/node-globals-polyfill-0.2.3.tgz", - "integrity": "sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==", - "dev": true, - "requires": {} - }, - "@esbuild-plugins/node-modules-polyfill": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-modules-polyfill/-/node-modules-polyfill-0.2.2.tgz", - "integrity": "sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA==", + "@esbuild/aix-ppc64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", + "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", "dev": true, - "requires": { - "escape-string-regexp": "^4.0.0", - "rollup-plugin-node-polyfills": "^0.2.1" - }, - "dependencies": { - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - } - } + "optional": true }, "@esbuild/android-arm": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", - "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz", + "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", "dev": true, "optional": true }, "@esbuild/android-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", - "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz", + "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", "dev": true, "optional": true }, "@esbuild/android-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", - "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz", + "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", "dev": true, "optional": true }, "@esbuild/darwin-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", - "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz", + "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", "dev": true, "optional": true }, "@esbuild/darwin-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", - "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz", + "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", "dev": true, "optional": true }, "@esbuild/freebsd-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", - "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz", + "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", "dev": true, "optional": true }, "@esbuild/freebsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", - "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz", + "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", "dev": true, "optional": true }, "@esbuild/linux-arm": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", - "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz", + "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", "dev": true, "optional": true }, "@esbuild/linux-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", - "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz", + "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", "dev": true, "optional": true }, "@esbuild/linux-ia32": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", - "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz", + "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", "dev": true, "optional": true }, "@esbuild/linux-loong64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", - "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz", + "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", "dev": true, "optional": true }, "@esbuild/linux-mips64el": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", - "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz", + "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", "dev": true, "optional": true }, "@esbuild/linux-ppc64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", - "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz", + "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", "dev": true, "optional": true }, "@esbuild/linux-riscv64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", - "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz", + "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", "dev": true, "optional": true }, "@esbuild/linux-s390x": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", - "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz", + "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", "dev": true, "optional": true }, "@esbuild/linux-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", - "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz", + "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz", + "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", "dev": true, "optional": true }, "@esbuild/netbsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", - "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz", + "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz", + "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", "dev": true, "optional": true }, "@esbuild/openbsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", - "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz", + "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", "dev": true, "optional": true }, "@esbuild/sunos-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", - "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz", + "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", "dev": true, "optional": true }, "@esbuild/win32-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", - "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz", + "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", "dev": true, "optional": true }, "@esbuild/win32-ia32": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", - "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz", + "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", "dev": true, "optional": true }, "@esbuild/win32-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", - "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz", + "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", "dev": true, "optional": true }, "@eslint-community/eslint-utils": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz", - "integrity": "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", "dev": true, "requires": { "eslint-visitor-keys": "^3.4.3" @@ -15814,12 +16215,6 @@ "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true }, - "@fastify/busboy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", - "dev": true - }, "@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -16003,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", @@ -16126,9 +16536,9 @@ "dev": true }, "@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", "dev": true, "requires": { "@jridgewell/resolve-uri": "^3.1.0", @@ -16136,16 +16546,16 @@ } }, "@microsoft/api-documenter": { - "version": "7.26.20", - "resolved": "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.26.20.tgz", - "integrity": "sha512-lTWhpKXLkiovr+IrFjrno4bbctknd3bVrVfTj1NZnMjaSXiplS3V8r9EKhf0mqa0twAznGXylwoBm+pP2mwjGw==", + "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.5", + "@microsoft/api-extractor-model": "7.30.7", "@microsoft/tsdoc": "~0.15.1", - "@rushstack/node-core-library": "5.13.0", - "@rushstack/terminal": "0.15.2", - "@rushstack/ts-command-line": "4.23.7", + "@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" }, @@ -16163,20 +16573,20 @@ } }, "@microsoft/api-extractor": { - "version": "7.52.3", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.52.3.tgz", - "integrity": "sha512-QEs6l8h7p9eOSHrQ9NBBUZhUuq+j/2QKcRgigbSs2YQepKz8glvsqmsUOp+nvuaY60ps7KkpVVYQCj81WLoMVQ==", + "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.5", + "@microsoft/api-extractor-model": "7.30.7", "@microsoft/tsdoc": "~0.15.1", "@microsoft/tsdoc-config": "~0.17.1", - "@rushstack/node-core-library": "5.13.0", + "@rushstack/node-core-library": "5.14.0", "@rushstack/rig-package": "0.5.3", - "@rushstack/terminal": "0.15.2", - "@rushstack/ts-command-line": "4.23.7", + "@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", @@ -16184,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": { @@ -16210,14 +16620,14 @@ } }, "@microsoft/api-extractor-model": { - "version": "7.30.5", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.30.5.tgz", - "integrity": "sha512-0ic4rcbcDZHz833RaTZWTGu+NpNgrxVNjVaor0ZDUymfDFzjA/Uuk8hYziIUIOEOSTfmIQqyzVwlzxZxPe7tOA==", + "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.0" + "@rushstack/node-core-library": "5.14.0" } }, "@microsoft/tsdoc": { @@ -16291,6 +16701,40 @@ "dev": true, "optional": true }, + "@poppinss/colors": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.5.tgz", + "integrity": "sha512-FvdDqtcRCtz6hThExcFOgW0cWX+xwSMWcRuQe5ZEb2m7cVQOAVZOIMt+/v9RxGiD9/OY16qJBXK4CVKWAPalBw==", + "dev": true, + "requires": { + "kleur": "^4.1.5" + } + }, + "@poppinss/dumper": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.4.tgz", + "integrity": "sha512-iG0TIdqv8xJ3Lt9O8DrPRxw1MRLjNpoqiSGU03P/wNLP/s0ra0udPJ1J2Tx5M0J3H/cVyEgpbn8xUKRY9j59kQ==", + "dev": true, + "requires": { + "@poppinss/colors": "^4.1.5", + "@sindresorhus/is": "^7.0.2", + "supports-color": "^10.0.0" + }, + "dependencies": { + "supports-color": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.0.0.tgz", + "integrity": "sha512-HRVVSbCCMbj7/kdWF9Q+bbckjBHLtHMEoJWlkmYzzdwhYMkjkOwubLM6t7NbWKjgKamGDrWL1++KrjUO1t9oAQ==", + "dev": true + } + } + }, + "@poppinss/exception": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.2.tgz", + "integrity": "sha512-m7bpKCD4QMlFCjA/nKTs23fuvoVFoA83brRKmObCUNmi/9tVu8Ve3w4YQAnJu4q3Tjf5fr685HYIC/IA2zHRSg==", + "dev": true + }, "@rollup/plugin-alias": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-5.1.1.tgz", @@ -16309,9 +16753,9 @@ } }, "@rollup/plugin-commonjs": { - "version": "28.0.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.3.tgz", - "integrity": "sha512-pyltgilam1QPdn+Zd9gaCfOLcnjMEJ9gV+bTw6/r73INdvzf1ah9zLIJBm+kW7R6IUFIQ1YO+VqZtYxZNWFPEQ==", + "version": "28.0.6", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.6.tgz", + "integrity": "sha512-XSQB1K7FUU5QP+3lOQmVCE3I0FcbbNvmNT4VJSj93iUjayaARrTQeoRdiYQoftAJBLrR9t2agwAd3ekaTgHNlw==", "dev": true, "requires": { "@rollup/pluginutils": "^5.0.1", @@ -16323,28 +16767,13 @@ "picomatch": "^4.0.2" }, "dependencies": { - "estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true - }, "fdir": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", - "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", "dev": true, "requires": {} }, - "magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "dev": true, - "requires": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, "picomatch": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", @@ -16374,17 +16803,6 @@ "requires": { "@rollup/pluginutils": "^5.0.1", "magic-string": "^0.30.3" - }, - "dependencies": { - "magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "dev": true, - "requires": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - } } }, "@rollup/plugin-terser": { @@ -16399,9 +16817,9 @@ } }, "@rollup/plugin-typescript": { - "version": "12.1.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-12.1.2.tgz", - "integrity": "sha512-cdtSp154H5sv637uMr1a8OTWB0L1SWDSm1rDGiyfcGcvQ6cuTs4MDk2BVEBGysUWago4OJN4EQZqOTl/QY3Jgg==", + "version": "12.1.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-12.1.4.tgz", + "integrity": "sha512-s5Hx+EtN60LMlDBvl5f04bEiFZmAepk27Q+mr85L/00zPDn1jtzlTV6FWn81MaIwqfWzKxmOJrBWHU6vtQyedQ==", "dev": true, "requires": { "@rollup/pluginutils": "^5.1.0", @@ -16424,152 +16842,146 @@ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.2.tgz", "integrity": "sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==", "dev": true - }, - "estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true } } }, "@rollup/rollup-android-arm-eabi": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", - "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==", + "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.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz", - "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==", + "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.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz", - "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==", + "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.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz", - "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==", + "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.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz", - "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==", + "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.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz", - "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==", + "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.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz", - "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==", + "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.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz", - "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==", + "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.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz", - "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==", + "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.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz", - "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==", + "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.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz", - "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==", + "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.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz", - "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==", + "@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.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz", - "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==", + "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.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz", - "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==", + "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.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz", - "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==", + "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.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", - "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", + "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.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz", - "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==", + "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.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz", - "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==", + "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.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz", - "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==", + "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.40.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz", - "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==", + "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 }, @@ -16580,9 +16992,9 @@ "dev": true }, "@rushstack/node-core-library": { - "version": "5.13.0", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.13.0.tgz", - "integrity": "sha512-IGVhy+JgUacAdCGXKUrRhwHMTzqhWwZUI+qEPcdzsb80heOw0QPbhhoVsoiMF7Klp8eYsp7hzpScMXmOa3Uhfg==", + "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", @@ -16642,12 +17054,12 @@ } }, "@rushstack/terminal": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.15.2.tgz", - "integrity": "sha512-7Hmc0ysK5077R/IkLS9hYu0QuNafm+TbZbtYVzCMbeOdMjaRboLKrhryjwZSRJGJzu+TV1ON7qZHeqf58XfLpA==", + "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.0", + "@rushstack/node-core-library": "5.14.0", "supports-color": "~8.1.1" }, "dependencies": { @@ -16669,17 +17081,23 @@ } }, "@rushstack/ts-command-line": { - "version": "4.23.7", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.23.7.tgz", - "integrity": "sha512-Gr9cB7DGe6uz5vq2wdr89WbVDKz0UeuFEn5H2CfWDe7JvjFFaiV15gi6mqDBTbHhHCWS7w8mF1h3BnIfUndqdA==", + "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.2", + "@rushstack/terminal": "0.15.4", "@types/argparse": "1.0.38", "argparse": "~1.0.9", "string-argv": "~0.3.1" } }, + "@sindresorhus/is": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.0.2.tgz", + "integrity": "sha512-d9xRovfKNz1SKieM0qJdO+PQonjnnIfSNWfHYnBSJ9hkjm0ZPw6HlxscDXYstp3z+7V2GOFHc+J0CYrYTjqCJw==", + "dev": true + }, "@sinonjs/commons": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", @@ -16729,10 +17147,16 @@ "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", "dev": true }, + "@speed-highlight/core": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.7.tgz", + "integrity": "sha512-0dxmVj4gxg3Jg879kvFS/msl4s9F3T9UXC1InxgOf7t5NvcPD97u/WTA5vL/IxWHMn7qSxBozqrnnE2wvl1m8g==", + "dev": true + }, "@svta/common-media-library": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@svta/common-media-library/-/common-media-library-0.11.0.tgz", - "integrity": "sha512-O+j71s605qywuB55meW68/dxHaOdq1AMRDA3iqECru7uUsOijrn8m4xj9uRaY4as3Bjv6rS0J1fMKJo98R2Naw==", + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@svta/common-media-library/-/common-media-library-0.17.1.tgz", + "integrity": "sha512-UcmqRe1cJ/OloNEeXqKJjbw6MAKPaGZUk4yLsy1QOwtzUmo7hDVvZeIoqXlmoXZNQRQ/P1MsNuboKirsTw9e7w==", "dev": true }, "@testim/chrome-version": { @@ -16925,83 +17349,112 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.0.tgz", - "integrity": "sha512-evaQJZ/J/S4wisevDvC1KFZkPzRetH8kYZbkgcTRyql3mcKsf+ZFDV1BVWUGTCAW5pQHoqn5gK5b8kn7ou9aFQ==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.38.0.tgz", + "integrity": "sha512-CPoznzpuAnIOl4nhj4tRr4gIPj5AfKgkiJmGQDaq+fQnRJTYlcBjbX3wbciGmpoPf8DREufuPRe1tNMZnGdanA==", "dev": true, "requires": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.31.0", - "@typescript-eslint/type-utils": "8.31.0", - "@typescript-eslint/utils": "8.31.0", - "@typescript-eslint/visitor-keys": "8.31.0", + "@typescript-eslint/scope-manager": "8.38.0", + "@typescript-eslint/type-utils": "8.38.0", + "@typescript-eslint/utils": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0", "graphemer": "^1.4.0", - "ignore": "^5.3.1", + "ignore": "^7.0.0", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.0.1" + "ts-api-utils": "^2.1.0" + }, + "dependencies": { + "ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true + } } }, "@typescript-eslint/parser": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.31.0.tgz", - "integrity": "sha512-67kYYShjBR0jNI5vsf/c3WG4u+zDnCTHTPqVMQguffaWWFs7artgwKmfwdifl+r6XyM5LYLas/dInj2T0SgJyw==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.38.0.tgz", + "integrity": "sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "8.38.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/typescript-estree": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0", + "debug": "^4.3.4" + } + }, + "@typescript-eslint/project-service": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.38.0.tgz", + "integrity": "sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "8.31.0", - "@typescript-eslint/types": "8.31.0", - "@typescript-eslint/typescript-estree": "8.31.0", - "@typescript-eslint/visitor-keys": "8.31.0", + "@typescript-eslint/tsconfig-utils": "^8.38.0", + "@typescript-eslint/types": "^8.38.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.31.0.tgz", - "integrity": "sha512-knO8UyF78Nt8O/B64i7TlGXod69ko7z6vJD9uhSlm0qkAbGeRUSudcm0+K/4CrRjrpiHfBCjMWlc08Vav1xwcw==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.38.0.tgz", + "integrity": "sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==", "dev": true, "requires": { - "@typescript-eslint/types": "8.31.0", - "@typescript-eslint/visitor-keys": "8.31.0" + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0" } }, + "@typescript-eslint/tsconfig-utils": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.38.0.tgz", + "integrity": "sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==", + "dev": true, + "requires": {} + }, "@typescript-eslint/type-utils": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.31.0.tgz", - "integrity": "sha512-DJ1N1GdjI7IS7uRlzJuEDCgDQix3ZVYVtgeWEyhyn4iaoitpMBX6Ndd488mXSx0xah/cONAkEaYyylDyAeHMHg==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.38.0.tgz", + "integrity": "sha512-c7jAvGEZVf0ao2z+nnz8BUaHZD09Agbh+DY7qvBQqLiz8uJzRgVPj5YvOh8I8uEiH8oIUGIfHzMwUcGVco/SJg==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "8.31.0", - "@typescript-eslint/utils": "8.31.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/typescript-estree": "8.38.0", + "@typescript-eslint/utils": "8.38.0", "debug": "^4.3.4", - "ts-api-utils": "^2.0.1" + "ts-api-utils": "^2.1.0" } }, "@typescript-eslint/types": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.31.0.tgz", - "integrity": "sha512-Ch8oSjVyYyJxPQk8pMiP2FFGYatqXQfQIaMp+TpuuLlDachRWpUAeEu1u9B/v/8LToehUIWyiKcA/w5hUFRKuQ==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.38.0.tgz", + "integrity": "sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.0.tgz", - "integrity": "sha512-xLmgn4Yl46xi6aDSZ9KkyfhhtnYI15/CvHbpOy/eR5NWhK/BK8wc709KKwhAR0m4ZKRP7h07bm4BWUYOCuRpQQ==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.38.0.tgz", + "integrity": "sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==", "dev": true, "requires": { - "@typescript-eslint/types": "8.31.0", - "@typescript-eslint/visitor-keys": "8.31.0", + "@typescript-eslint/project-service": "8.38.0", + "@typescript-eslint/tsconfig-utils": "8.38.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/visitor-keys": "8.38.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^2.0.1" + "ts-api-utils": "^2.1.0" }, "dependencies": { "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "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" @@ -17019,31 +17472,31 @@ } }, "@typescript-eslint/utils": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.31.0.tgz", - "integrity": "sha512-qi6uPLt9cjTFxAb1zGNgTob4x9ur7xC6mHQJ8GwEzGMGE9tYniublmJaowOJ9V2jUzxrltTPfdG2nKlWsq0+Ww==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.38.0.tgz", + "integrity": "sha512-hHcMA86Hgt+ijJlrD8fX0j1j8w4C92zue/8LOPAFioIno+W0+L7KqE8QZKCcPGc/92Vs9x36w/4MPTJhqXdyvg==", "dev": true, "requires": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.31.0", - "@typescript-eslint/types": "8.31.0", - "@typescript-eslint/typescript-estree": "8.31.0" + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.38.0", + "@typescript-eslint/types": "8.38.0", + "@typescript-eslint/typescript-estree": "8.38.0" } }, "@typescript-eslint/visitor-keys": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.0.tgz", - "integrity": "sha512-QcGHmlRHWOl93o64ZUMNewCdwKGU6WItOU52H0djgNmn1EOrhVudrDzXz4OycCRSCPwFCDrE2iIt5vmuUdHxuQ==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.38.0.tgz", + "integrity": "sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==", "dev": true, "requires": { - "@typescript-eslint/types": "8.31.0", - "eslint-visitor-keys": "^4.2.0" + "@typescript-eslint/types": "8.38.0", + "eslint-visitor-keys": "^4.2.1" }, "dependencies": { "eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true } } @@ -17347,12 +17800,6 @@ } } }, - "ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true - }, "ansi-escapes": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", @@ -17397,90 +17844,83 @@ } }, "array-buffer-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", - "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", "dev": true, "requires": { - "call-bind": "^1.0.5", - "is-array-buffer": "^3.0.4" + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" } }, "array-includes": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", - "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", "dev": true, "requires": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.4", - "is-string": "^1.0.7" + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" } }, "array.prototype.findlastindex": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", - "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", "dev": true, "requires": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", + "es-abstract": "^1.23.9", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" } }, "array.prototype.flat": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", - "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", "dev": true, "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" } }, "array.prototype.flatmap": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", - "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", "dev": true, "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" } }, "arraybuffer.prototype.slice": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", - "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", "dev": true, "requires": { "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.5", + "call-bind": "^1.0.8", "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.2.1", - "get-intrinsic": "^1.2.3", - "is-array-buffer": "^3.0.4", - "is-shared-array-buffer": "^1.0.2" - } - }, - "as-table": { - "version": "1.0.55", - "resolved": "https://registry.npmjs.org/as-table/-/as-table-1.0.55.tgz", - "integrity": "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==", - "dev": true, - "requires": { - "printable-characters": "^1.0.42" + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" } }, "assertion-error": { @@ -17507,6 +17947,12 @@ "lodash": "^4.17.14" } }, + "async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -17556,13 +18002,13 @@ } }, "babel-plugin-polyfill-corejs2": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.10.tgz", - "integrity": "sha512-rpIuu//y5OX6jVU+a5BCn1R5RSZYWAl2Nar76iwaOdycqb6JPxediskWFMMl7stfwNJR4b7eiQvh5fB5TEQJTQ==", + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", + "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", "dev": true, "requires": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.1", + "@babel/compat-data": "^7.27.7", + "@babel/helper-define-polyfill-provider": "^0.6.5", "semver": "^6.3.1" }, "dependencies": { @@ -17575,22 +18021,22 @@ } }, "babel-plugin-polyfill-corejs3": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz", - "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", "dev": true, "requires": { - "@babel/helper-define-polyfill-provider": "^0.6.3", - "core-js-compat": "^3.40.0" + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" } }, "babel-plugin-polyfill-regenerator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.1.tgz", - "integrity": "sha512-JfTApdE++cgcTWjsiCQlLyFBMbTUft9ja17saCc93lgV33h4tuCVj7tlvu//qpLwaG+3yEz7/KhahGrUMkVq9g==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", + "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", "dev": true, "requires": { - "@babel/helper-define-polyfill-provider": "^0.6.1" + "@babel/helper-define-polyfill-provider": "^0.6.5" } }, "babel-plugin-transform-remove-console": { @@ -17707,15 +18153,15 @@ "dev": true }, "browserslist": { - "version": "4.24.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", - "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" + "update-browserslist-db": "^1.1.3" } }, "buffer-crc32": { @@ -17737,16 +18183,35 @@ "dev": true }, "call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", "dev": true, "requires": { + "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "set-function-length": "^1.2.2" + } + }, + "call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + } + }, + "call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" } }, "callsites": { @@ -17756,9 +18221,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001699", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001699.tgz", - "integrity": "sha512-b+uH5BakXZ9Do9iK+CkDmctUSEqZl+SP056vc5usa0PL+ev5OHw003rZXcnjNDv3L8P5j6rwT6C0BPKSikW08w==", + "version": "1.0.30001726", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", + "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", "dev": true }, "ccount": { @@ -17892,9 +18357,9 @@ "peer": true }, "chromedriver": { - "version": "135.0.1", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-135.0.1.tgz", - "integrity": "sha512-MLAS4t9dkttp1R1O2o/1nvtNIxg1dBTx7OE3ZCSrrFz+EFowd0wRAO7H5j918hw0i8+30yODq99p8CumvqRS9Q==", + "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", @@ -18047,9 +18512,9 @@ } }, "commander": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", - "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", + "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==", "dev": true }, "commondir": { @@ -18118,12 +18583,12 @@ "dev": true }, "core-js-compat": { - "version": "3.40.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.40.0.tgz", - "integrity": "sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ==", + "version": "3.43.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.43.0.tgz", + "integrity": "sha512-2GML2ZsCc5LR7hZYz4AXmjQw8zuy2T//2QntwdnpuYI7jteT6GVYJL7F6C2C57R7gSYrcqVW3lAALefdbhBLDA==", "dev": true, "requires": { - "browserslist": "^4.24.3" + "browserslist": "^4.25.0" } }, "core-util-is": { @@ -18172,34 +18637,34 @@ "dev": true }, "data-view-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", - "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", "dev": true, "requires": { - "call-bind": "^1.0.6", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" + "is-data-view": "^1.0.2" } }, "data-view-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", - "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", "dev": true, "requires": { - "call-bind": "^1.0.7", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" + "is-data-view": "^1.0.2" } }, "data-view-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", - "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", "dev": true, "requires": { - "call-bind": "^1.0.6", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" } @@ -18304,11 +18769,10 @@ "dev": true }, "detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", - "dev": true, - "optional": true + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "dev": true }, "di": { "version": "0.0.1", @@ -18317,9 +18781,9 @@ "dev": true }, "diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", "dev": true }, "doctoc": { @@ -18414,6 +18878,17 @@ "domhandler": "^4.2.0" } }, + "dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + } + }, "eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -18427,9 +18902,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.5.101", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.101.tgz", - "integrity": "sha512-L0ISiQrP/56Acgu4/i/kfPwWSgrzYZUnQrC0+QPFuhqlLP1Ir7qzPPDVS9BcKIyWTRU8+o6CC8dKw38tSWhYIA==", + "version": "1.5.179", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.179.tgz", + "integrity": "sha512-UWKi/EbBopgfFsc5k61wFpV7WrnnSlSzW/e2XcBmS6qKYTivZlLtoll5/rdqRTxGglGHkmkW0j0pFNJG10EUIQ==", "dev": true }, "emoji-regex": { @@ -18520,74 +18995,97 @@ "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", "dev": true }, + "error-stack-parser-es": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", + "dev": true + }, "es-abstract": { - "version": "1.23.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", - "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", "dev": true, "requires": { - "array-buffer-byte-length": "^1.0.1", - "arraybuffer.prototype.slice": "^1.0.3", + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "data-view-buffer": "^1.0.1", - "data-view-byte-length": "^1.0.1", - "data-view-byte-offset": "^1.0.0", - "es-define-property": "^1.0.0", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-set-tostringtag": "^2.0.3", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.4", - "get-symbol-description": "^1.0.2", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.3", - "has-symbols": "^1.0.3", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", "hasown": "^2.0.2", - "internal-slot": "^1.0.7", - "is-array-buffer": "^3.0.4", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", - "is-data-view": "^1.0.1", + "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.3", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.13", - "is-weakref": "^1.0.2", - "object-inspect": "^1.13.1", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", "object-keys": "^1.1.1", - "object.assign": "^4.1.5", - "regexp.prototype.flags": "^1.5.2", - "safe-array-concat": "^1.1.2", - "safe-regex-test": "^1.0.3", - "string.prototype.trim": "^1.2.9", - "string.prototype.trimend": "^1.0.8", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.2", - "typed-array-byte-length": "^1.0.1", - "typed-array-byte-offset": "^1.0.2", - "typed-array-length": "^1.0.6", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.15" + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" } }, "es-check": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/es-check/-/es-check-8.0.2.tgz", - "integrity": "sha512-m/8jzwE6rasRx+Lhz7J5OIZzt2LmEC53kBPkUqQHk/6BMjo77HoHsdoQKeps8mgLASlb8nRuKZnuRai/iZHrHg==", + "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.0", + "acorn": "8.15.0", "acorn-walk": "^8.3.4", - "commander": "13.1.0", + "browserslist": "^4.23.3", + "commander": "14.0.0", "fast-glob": "^3.3.3", + "lilconfig": "^3.1.3", + "source-map": "^0.7.4", "supports-color": "8.1.1", "winston": "3.17.0" }, "dependencies": { + "acorn": { + "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": { "version": "8.3.4", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", @@ -18603,6 +19101,12 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "source-map": { + "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": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -18615,13 +19119,10 @@ } }, "es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dev": true, - "requires": { - "get-intrinsic": "^1.2.4" - } + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true }, "es-errors": { "version": "1.3.0", @@ -18637,73 +19138,77 @@ "peer": true }, "es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "dev": true, "requires": { "es-errors": "^1.3.0" } }, "es-set-tostringtag": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, "requires": { - "get-intrinsic": "^1.2.4", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" + "hasown": "^2.0.2" } }, "es-shim-unscopables": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", - "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", "dev": true, "requires": { - "hasown": "^2.0.0" + "hasown": "^2.0.2" } }, "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", "dev": true, "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" } }, "esbuild": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", - "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", - "dev": true, - "requires": { - "@esbuild/android-arm": "0.17.19", - "@esbuild/android-arm64": "0.17.19", - "@esbuild/android-x64": "0.17.19", - "@esbuild/darwin-arm64": "0.17.19", - "@esbuild/darwin-x64": "0.17.19", - "@esbuild/freebsd-arm64": "0.17.19", - "@esbuild/freebsd-x64": "0.17.19", - "@esbuild/linux-arm": "0.17.19", - "@esbuild/linux-arm64": "0.17.19", - "@esbuild/linux-ia32": "0.17.19", - "@esbuild/linux-loong64": "0.17.19", - "@esbuild/linux-mips64el": "0.17.19", - "@esbuild/linux-ppc64": "0.17.19", - "@esbuild/linux-riscv64": "0.17.19", - "@esbuild/linux-s390x": "0.17.19", - "@esbuild/linux-x64": "0.17.19", - "@esbuild/netbsd-x64": "0.17.19", - "@esbuild/openbsd-x64": "0.17.19", - "@esbuild/sunos-x64": "0.17.19", - "@esbuild/win32-arm64": "0.17.19", - "@esbuild/win32-ia32": "0.17.19", - "@esbuild/win32-x64": "0.17.19" + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", + "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", + "dev": true, + "requires": { + "@esbuild/aix-ppc64": "0.25.4", + "@esbuild/android-arm": "0.25.4", + "@esbuild/android-arm64": "0.25.4", + "@esbuild/android-x64": "0.25.4", + "@esbuild/darwin-arm64": "0.25.4", + "@esbuild/darwin-x64": "0.25.4", + "@esbuild/freebsd-arm64": "0.25.4", + "@esbuild/freebsd-x64": "0.25.4", + "@esbuild/linux-arm": "0.25.4", + "@esbuild/linux-arm64": "0.25.4", + "@esbuild/linux-ia32": "0.25.4", + "@esbuild/linux-loong64": "0.25.4", + "@esbuild/linux-mips64el": "0.25.4", + "@esbuild/linux-ppc64": "0.25.4", + "@esbuild/linux-riscv64": "0.25.4", + "@esbuild/linux-s390x": "0.25.4", + "@esbuild/linux-x64": "0.25.4", + "@esbuild/netbsd-arm64": "0.25.4", + "@esbuild/netbsd-x64": "0.25.4", + "@esbuild/openbsd-arm64": "0.25.4", + "@esbuild/openbsd-x64": "0.25.4", + "@esbuild/sunos-x64": "0.25.4", + "@esbuild/win32-arm64": "0.25.4", + "@esbuild/win32-ia32": "0.25.4", + "@esbuild/win32-x64": "0.25.4" } }, "escalade": { @@ -18876,9 +19381,9 @@ } }, "eslint-config-prettier": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.2.tgz", - "integrity": "sha512-Epgp/EofAUeEpIdZkW60MHKvPyru1ruQJxPL+WIycnaPApuseK0Zpkrh/FwL9oIpQvIhJwV7ptOy0DWUjTlCiA==", + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "requires": {} }, @@ -18905,9 +19410,9 @@ } }, "eslint-module-utils": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", - "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", "dev": true, "requires": { "debug": "^3.2.7" @@ -18936,29 +19441,29 @@ } }, "eslint-plugin-import": { - "version": "2.31.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", - "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "requires": { "@rtsao/scc": "^1.1.0", - "array-includes": "^3.1.8", - "array.prototype.findlastindex": "^1.2.5", - "array.prototype.flat": "^1.3.2", - "array.prototype.flatmap": "^1.3.2", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.12.0", + "eslint-module-utils": "^2.12.1", "hasown": "^2.0.2", - "is-core-module": "^2.15.1", + "is-core-module": "^2.16.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "object.groupby": "^1.0.3", - "object.values": "^1.2.0", + "object.values": "^1.2.1", "semver": "^6.3.1", - "string.prototype.trimend": "^1.0.8", + "string.prototype.trimend": "^1.0.9", "tsconfig-paths": "^3.15.0" }, "dependencies": { @@ -19011,9 +19516,9 @@ } }, "eslint-plugin-n": { - "version": "17.17.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.17.0.tgz", - "integrity": "sha512-2VvPK7Mo73z1rDFb6pTvkH6kFibAmnTubFq5l83vePxu0WiY1s0LOtj2WHb6Sa40R3w4mnh8GFYbHBQyMlotKw==", + "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", @@ -19021,37 +19526,19 @@ "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" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "globals": { - "version": "15.15.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", - "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", - "dev": true - }, - "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" - } - } + "semver": "^7.6.3", + "ts-declaration-location": "^1.0.6" } }, + "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", + "integrity": "sha512-uCotzBHt2W+HbLw2srRmqDJHOPbJGzeVLstKh8YyxS3ppduq2P50qdpJfHKoD+UGbnqA/zhy8NRgPH6p0y8bnA==", + "dev": true, + "requires": {} + }, "eslint-plugin-promise": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-7.2.1.tgz", @@ -19154,9 +19641,9 @@ "peer": true }, "estree-walker": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", - "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "dev": true }, "esutils": { @@ -19178,52 +19665,6 @@ "dev": true, "peer": true }, - "execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "dependencies": { - "get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true - }, - "is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true - }, - "mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true - }, - "onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "requires": { - "mimic-fn": "^4.0.0" - } - } - } - }, "exit-hook": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz", @@ -19231,9 +19672,9 @@ "dev": true }, "exsolve": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.4.tgz", - "integrity": "sha512-xsZH6PXaER4XoV+NiT7JHp1bJodJVT+cxeSH1G0f0tlT0lJqYuHUP3bUx2HtfTDvOagMINYp8rsqusxud3RXhw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", + "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", "dev": true }, "extend": { @@ -19468,12 +19909,12 @@ "dev": true }, "for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "dev": true, "requires": { - "is-callable": "^1.1.3" + "is-callable": "^1.2.7" } }, "foreground-child": { @@ -19532,15 +19973,17 @@ "dev": true }, "function.prototype.name": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", - "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", "dev": true, "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "functions-have-names": "^1.2.3" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" } }, "functions-have-names": { @@ -19574,34 +20017,31 @@ "dev": true }, "get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, "requires": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" } }, - "get-source": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/get-source/-/get-source-2.0.12.tgz", - "integrity": "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==", + "get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "dev": true, "requires": { - "data-uri-to-buffer": "^2.0.0", - "source-map": "^0.6.1" - }, - "dependencies": { - "data-uri-to-buffer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz", - "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==", - "dev": true - } + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" } }, "get-stream": { @@ -19614,14 +20054,14 @@ } }, "get-symbol-description": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", - "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", "dev": true, "requires": { - "call-bind": "^1.0.5", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4" + "get-intrinsic": "^1.2.6" } }, "get-tsconfig": { @@ -19689,28 +20129,32 @@ "dev": true }, "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", "dev": true }, "globalthis": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", "dev": true, "requires": { - "define-properties": "^1.1.3" + "define-properties": "^1.2.1", + "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.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.3" - } + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true }, "graceful-fs": { "version": "4.2.10", @@ -19738,9 +20182,9 @@ } }, "has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", "dev": true }, "has-flag": { @@ -19759,15 +20203,18 @@ } }, "has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "dev": true + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "requires": { + "dunder-proto": "^1.0.0" + } }, "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true }, "has-tostringtag": { @@ -19952,12 +20399,6 @@ "debug": "4" } }, - "human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true - }, "husky": { "version": "9.1.7", "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", @@ -20024,14 +20465,14 @@ "dev": true }, "internal-slot": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", - "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", "dev": true, "requires": { "es-errors": "^1.3.0", - "hasown": "^2.0.0", - "side-channel": "^1.0.4" + "hasown": "^2.0.2", + "side-channel": "^1.1.0" } }, "ip-address": { @@ -20075,22 +20516,36 @@ } }, "is-array-buffer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", - "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + } + }, + "is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", "dev": true, "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1" + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" } }, "is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", "dev": true, "requires": { - "has-bigints": "^1.0.1" + "has-bigints": "^1.0.2" } }, "is-binary-path": { @@ -20103,13 +20558,13 @@ } }, "is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", "dev": true, "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" } }, "is-buffer": { @@ -20125,30 +20580,33 @@ "dev": true }, "is-core-module": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", - "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, "requires": { "hasown": "^2.0.2" } }, "is-data-view": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", - "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", "dev": true, "requires": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", "is-typed-array": "^1.1.13" } }, "is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", "dev": true, "requires": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" } }, "is-decimal": { @@ -20163,12 +20621,33 @@ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, + "is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "requires": { + "call-bound": "^1.0.3" + } + }, "is-fullwidth-code-point": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", "dev": true }, + "is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + } + }, "is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -20184,6 +20663,12 @@ "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", "dev": true }, + "is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true + }, "is-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", @@ -20203,12 +20688,13 @@ "dev": true }, "is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", "dev": true, "requires": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" } }, "is-path-inside": { @@ -20236,22 +20722,30 @@ } }, "is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "dev": true, "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" } }, + "is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true + }, "is-shared-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", - "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", "dev": true, "requires": { - "call-bind": "^1.0.7" + "call-bound": "^1.0.3" } }, "is-stream": { @@ -20261,30 +20755,33 @@ "dev": true }, "is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", "dev": true, "requires": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" } }, "is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", "dev": true, "requires": { - "has-symbols": "^1.0.2" + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" } }, "is-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", - "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "dev": true, "requires": { - "which-typed-array": "^1.1.14" + "which-typed-array": "^1.1.16" } }, "is-unicode-supported": { @@ -20299,13 +20796,29 @@ "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", "dev": true }, + "is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true + }, "is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "requires": { + "call-bound": "^1.0.3" + } + }, + "is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", "dev": true, "requires": { - "call-bind": "^1.0.2" + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" } }, "is2": { @@ -20718,6 +21231,12 @@ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, + "kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true + }, "kuler": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", @@ -20750,33 +21269,33 @@ "dev": true }, "lint-staged": { - "version": "15.5.1", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.5.1.tgz", - "integrity": "sha512-6m7u8mue4Xn6wK6gZvSCQwBvMBR36xfY24nF5bMTf2MHDYG6S3yhJuOgdYVw99hsjyDt2d4z168b3naI8+NWtQ==", + "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", - "commander": "^13.1.0", - "debug": "^4.4.0", - "execa": "^8.0.1", + "chalk": "^5.5.0", + "commander": "^14.0.0", + "debug": "^4.4.1", "lilconfig": "^3.1.3", - "listr2": "^8.2.5", + "listr2": "^9.0.1", "micromatch": "^4.0.8", + "nano-spawn": "^1.0.2", "pidtree": "^0.6.0", "string-argv": "^0.3.2", - "yaml": "^2.7.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": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "dev": true, "requires": { "ms": "^2.1.3" @@ -20791,9 +21310,9 @@ } }, "listr2": { - "version": "8.2.5", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.5.tgz", - "integrity": "sha512-iyAZCeyD+c1gPyE9qpFu8af0Y+MRtmKOncdGoA2S5EY8iFq99dmmvkNnHiWo+pj0s7yH7l3KPIgee77tKpXPWQ==", + "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", @@ -21013,12 +21532,12 @@ } }, "magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "dev": true, "requires": { - "sourcemap-codec": "^1.4.8" + "@jridgewell/sourcemap-codec": "^1.5.0" } }, "make-dir": { @@ -21082,6 +21601,12 @@ "integrity": "sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA==", "dev": true }, + "math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true + }, "mdast-util-find-and-replace": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-1.1.1.tgz", @@ -21221,7 +21746,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true + "dev": true, + "peer": true }, "merge2": { "version": "1.4.1", @@ -21357,9 +21883,9 @@ "dev": true }, "miniflare": { - "version": "3.20250408.0", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20250408.0.tgz", - "integrity": "sha512-URXD7+b0tLbBtchPM/MfWYujymHUrmPtd3EDQbe51qrPPF1zQCdSeNbA4f/GRQMoQIEE6EIhvEYjVjL+hiN+Og==", + "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", @@ -21367,11 +21893,12 @@ "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", + "sharp": "^0.33.5", "stoppable": "1.1.0", - "undici": "^5.28.5", - "workerd": "1.20250408.0", + "undici": "^7.10.0", + "workerd": "1.20250816.0", "ws": "8.18.0", - "youch": "3.3.4", + "youch": "4.1.0-beta.10", "zod": "3.22.3" } }, @@ -21422,28 +21949,28 @@ "dev": true }, "mocha": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.1.0.tgz", - "integrity": "sha512-8uJR5RTC2NgpY3GrYcgpZrsEd9zKbPDpob1RezyR2upGHRQtHWofmzTMzTMSV6dru3tj5Ukt0+Vnq1qhFEEwAg==", + "version": "11.7.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.1.tgz", + "integrity": "sha512-5EK+Cty6KheMS/YLPPMJC64g5V61gIR25KsRItHw6x4hEKT6Njp1n9LOlH4gpevuwMVS66SXaBBpg+RWZkza4A==", "dev": true, "requires": { - "ansi-colors": "^4.1.3", "browser-stdout": "^1.3.1", - "chokidar": "^3.5.3", + "chokidar": "^4.0.1", "debug": "^4.3.5", - "diff": "^5.2.0", + "diff": "^7.0.0", "escape-string-regexp": "^4.0.0", "find-up": "^5.0.0", "glob": "^10.4.5", "he": "^1.2.0", "js-yaml": "^4.1.0", "log-symbols": "^4.1.0", - "minimatch": "^5.1.6", + "minimatch": "^9.0.5", "ms": "^2.1.3", + "picocolors": "^1.1.1", "serialize-javascript": "^6.0.2", "strip-json-comments": "^3.1.1", "supports-color": "^8.1.1", - "workerpool": "^6.5.1", + "workerpool": "^9.2.0", "yargs": "^17.7.2", "yargs-parser": "^21.1.1", "yargs-unparser": "^2.0.0" @@ -21459,9 +21986,9 @@ } }, "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "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" @@ -21488,6 +22015,15 @@ } } }, + "chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "requires": { + "readdirp": "^4.0.1" + } + }, "cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -21529,20 +22065,9 @@ "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "dependencies": { - "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" - } - } + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" } }, "has-flag": { @@ -21568,9 +22093,9 @@ } }, "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "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" @@ -21582,6 +22107,12 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, + "readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true + }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -21637,10 +22168,10 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "mustache": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", - "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "nano-spawn": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-1.0.2.tgz", + "integrity": "sha512-21t+ozMQDAL/UGgQVBbZ/xXvNO10++ZPuTmKRO8k9V3AClVRht49ahtDjfY8l1q6nSHOrE5ASfthzH3ol6R/hg==", "dev": true }, "natural-compare": { @@ -21724,15 +22255,15 @@ "dev": true }, "npm-run-all2": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/npm-run-all2/-/npm-run-all2-7.0.2.tgz", - "integrity": "sha512-7tXR+r9hzRNOPNTvXegM+QzCuMjzUIIq66VDunL6j60O4RrExx32XUhlrS7UK4VcdGw5/Wxzb3kfNcFix9JKDA==", + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/npm-run-all2/-/npm-run-all2-8.0.4.tgz", + "integrity": "sha512-wdbB5My48XKp2ZfJUlhnLVihzeuA1hgBnqB2J9ahV77wLS+/YAJAlN8I+X3DIFIPZ3m5L7nplmlbhNiFDmXRDA==", "dev": true, "requires": { "ansi-styles": "^6.2.1", "cross-spawn": "^7.0.6", "memorystream": "^0.3.1", - "minimatch": "^9.0.0", + "picomatch": "^4.0.2", "pidtree": "^0.6.0", "read-package-json-fast": "^4.0.0", "shell-quote": "^1.7.3", @@ -21745,29 +22276,17 @@ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true }, - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, "isexe": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true }, - "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" - } + "picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true }, "which": { "version": "5.0.0", @@ -21780,23 +22299,6 @@ } } }, - "npm-run-path": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", - "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", - "dev": true, - "requires": { - "path-key": "^4.0.0" - }, - "dependencies": { - "path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true - } - } - }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -21804,9 +22306,9 @@ "dev": true }, "object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true }, "object-keys": { @@ -21816,14 +22318,16 @@ "dev": true }, "object.assign": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", - "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", "dev": true, "requires": { - "call-bind": "^1.0.5", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "define-properties": "^1.2.1", - "has-symbols": "^1.0.3", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, @@ -21851,12 +22355,13 @@ } }, "object.values": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", - "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", "dev": true, "requires": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } @@ -21929,6 +22434,17 @@ "type-check": "^0.4.0" } }, + "own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "requires": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + } + }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -22301,9 +22817,9 @@ } }, "possible-typed-array-names": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", - "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", "dev": true }, "prelude-ls": { @@ -22313,15 +22829,9 @@ "dev": true }, "prettier": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", - "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", - "dev": true - }, - "printable-characters": { - "version": "1.0.42", - "resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz", - "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true }, "process-nextick-args": { @@ -22497,6 +23007,22 @@ "picomatch": "^2.2.1" } }, + "reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + } + }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -22512,43 +23038,30 @@ "regenerate": "^1.4.2" } }, - "regenerator-runtime": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==", - "dev": true - }, - "regenerator-transform": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", - "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", - "dev": true, - "requires": { - "@babel/runtime": "^7.8.4" - } - }, "regexp.prototype.flags": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", - "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", "dev": true, "requires": { - "call-bind": "^1.0.6", + "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", - "set-function-name": "^2.0.1" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" } }, "regexpu-core": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.1.1.tgz", - "integrity": "sha512-k67Nb9jvwJcJmVpw0jPttR1/zVfnKf8Km0IPatrU/zJ5XeG3+Slx0xLXs9HByJSzXzrlz5EDvN6yLNMDc2qdnw==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", + "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", "dev": true, "requires": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.0", "regjsgen": "^0.8.0", - "regjsparser": "^0.11.0", + "regjsparser": "^0.12.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.1.0" } @@ -22560,9 +23073,9 @@ "dev": true }, "regjsparser": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.11.1.tgz", - "integrity": "sha512-1DHODs4B8p/mQHU9kr+jv8+wIC9mtG4eBHxWxIq5mhjE3D5oORhCc6deRKzTjs9DcfRFmj9BHSDguZklqCGFWQ==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", + "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", "dev": true, "requires": { "jsesc": "~3.0.2" @@ -22632,12 +23145,12 @@ "dev": true }, "resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", "dev": true, "requires": { - "is-core-module": "^2.13.0", + "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" } @@ -22686,54 +23199,43 @@ } }, "rollup": { - "version": "4.40.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz", - "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", - "dev": true, - "requires": { - "@rollup/rollup-android-arm-eabi": "4.40.0", - "@rollup/rollup-android-arm64": "4.40.0", - "@rollup/rollup-darwin-arm64": "4.40.0", - "@rollup/rollup-darwin-x64": "4.40.0", - "@rollup/rollup-freebsd-arm64": "4.40.0", - "@rollup/rollup-freebsd-x64": "4.40.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", - "@rollup/rollup-linux-arm-musleabihf": "4.40.0", - "@rollup/rollup-linux-arm64-gnu": "4.40.0", - "@rollup/rollup-linux-arm64-musl": "4.40.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", - "@rollup/rollup-linux-riscv64-gnu": "4.40.0", - "@rollup/rollup-linux-riscv64-musl": "4.40.0", - "@rollup/rollup-linux-s390x-gnu": "4.40.0", - "@rollup/rollup-linux-x64-gnu": "4.40.0", - "@rollup/rollup-linux-x64-musl": "4.40.0", - "@rollup/rollup-win32-arm64-msvc": "4.40.0", - "@rollup/rollup-win32-ia32-msvc": "4.40.0", - "@rollup/rollup-win32-x64-msvc": "4.40.0", - "@types/estree": "1.0.7", + "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" }, "dependencies": { "@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true } } }, - "rollup-plugin-inject": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz", - "integrity": "sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==", - "dev": true, - "requires": { - "estree-walker": "^0.6.1", - "magic-string": "^0.25.3", - "rollup-pluginutils": "^2.8.1" - } - }, "rollup-plugin-istanbul": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/rollup-plugin-istanbul/-/rollup-plugin-istanbul-5.0.0.tgz", @@ -22759,24 +23261,6 @@ } } }, - "rollup-plugin-node-polyfills": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/rollup-plugin-node-polyfills/-/rollup-plugin-node-polyfills-0.2.1.tgz", - "integrity": "sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==", - "dev": true, - "requires": { - "rollup-plugin-inject": "^3.0.0" - } - }, - "rollup-pluginutils": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", - "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", - "dev": true, - "requires": { - "estree-walker": "^0.6.1" - } - }, "run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -22787,14 +23271,15 @@ } }, "safe-array-concat": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", - "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", "dev": true, "requires": { - "call-bind": "^1.0.7", - "get-intrinsic": "^1.2.4", - "has-symbols": "^1.0.3", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", "isarray": "^2.0.5" }, "dependencies": { @@ -22812,15 +23297,33 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, + "safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + } + } + }, "safe-regex-test": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", - "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", "dev": true, "requires": { - "call-bind": "^1.0.6", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", - "is-regex": "^1.1.4" + "is-regex": "^1.2.1" } }, "safe-stable-stringify": { @@ -22866,21 +23369,30 @@ "dev": true }, "selenium-webdriver": { - "version": "4.31.0", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.31.0.tgz", - "integrity": "sha512-0MWEwypM0+c1NnZ87UEMxZdwphKoaK2UJ2qXzKWrJiM0gazFjgNVimxlHTOO90G2cOhphZqwpqSCJy62NTEzyA==", + "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", "jszip": "^3.10.1", "tmp": "^0.2.3", - "ws": "^8.18.0" + "ws": "^8.18.2" + }, + "dependencies": { + "ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "requires": {} + } } }, "semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true }, "serialize-javascript": { @@ -22918,6 +23430,17 @@ "has-property-descriptors": "^1.0.2" } }, + "set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "requires": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + } + }, "setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -22944,7 +23467,6 @@ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", "dev": true, - "optional": true, "requires": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", @@ -22975,7 +23497,6 @@ "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", "dev": true, - "optional": true, "requires": { "color-convert": "^2.0.1", "color-string": "^1.9.0" @@ -22986,7 +23507,6 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "optional": true, "requires": { "color-name": "~1.1.4" } @@ -23015,14 +23535,51 @@ "dev": true }, "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + } + }, + "side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + } + }, + "side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + } + }, + "side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "dev": true, "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" } }, "signal-exit": { @@ -23062,12 +23619,6 @@ "supports-color": "^7.2.0" }, "dependencies": { - "diff": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", - "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", - "dev": true - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -23214,12 +23765,6 @@ "source-map": "^0.6.0" } }, - "sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "dev": true - }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -23232,22 +23777,22 @@ "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", "dev": true }, - "stacktracey": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/stacktracey/-/stacktracey-2.1.8.tgz", - "integrity": "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==", - "dev": true, - "requires": { - "as-table": "^1.0.36", - "get-source": "^2.0.12" - } - }, "statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", "dev": true }, + "stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + } + }, "stoppable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", @@ -23347,24 +23892,28 @@ } }, "string.prototype.trim": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", - "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", "dev": true, "requires": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.0", - "es-object-atoms": "^1.0.0" + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" } }, "string.prototype.trimend": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", - "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", "dev": true, "requires": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } @@ -23404,12 +23953,6 @@ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true }, - "strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true - }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -23580,12 +24123,29 @@ "dev": true }, "ts-api-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz", - "integrity": "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", "dev": true, "requires": {} }, + "ts-declaration-location": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/ts-declaration-location/-/ts-declaration-location-1.0.7.tgz", + "integrity": "sha512-EDyGAwH1gO0Ausm9gV6T2nUvBgXT5kGoCMJPllOaooZ+4VvJiKBdZE7wK18N1deEowhcUptS+5GXZK8U/fvpwA==", + "dev": true, + "requires": { + "picomatch": "^4.0.2" + }, + "dependencies": { + "picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true + } + } + }, "tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -23647,55 +24207,56 @@ } }, "typed-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", - "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", "dev": true, "requires": { - "call-bind": "^1.0.7", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "is-typed-array": "^1.1.13" + "is-typed-array": "^1.1.14" } }, "typed-array-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", - "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", "dev": true, "requires": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" } }, "typed-array-byte-offset": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", - "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", "dev": true, "requires": { "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" } }, "typed-array-length": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", - "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", "dev": true, "requires": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-proto": "^1.0.3", "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0" + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" } }, "typescript": { @@ -23711,9 +24272,9 @@ "dev": true }, "ufo": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", - "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", "dev": true }, "uglify-js": { @@ -23724,15 +24285,15 @@ "optional": true }, "unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", "dev": true, "requires": { - "call-bind": "^1.0.2", + "call-bound": "^1.0.3", "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" } }, "underscore": { @@ -23742,13 +24303,10 @@ "dev": true }, "undici": { - "version": "5.28.5", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.5.tgz", - "integrity": "sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA==", - "dev": true, - "requires": { - "@fastify/busboy": "^2.0.0" - } + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.11.0.tgz", + "integrity": "sha512-heTSIac3iLhsmZhUCjyS3JQEkZELateufzZuBaVM5RHXdSBMb1LPMQf5x+FH7qjsZYDP0ttAc3nnVpUB+wYbOg==", + "dev": true }, "undici-types": { "version": "6.20.0", @@ -23757,16 +24315,16 @@ "dev": true }, "unenv": { - "version": "2.0.0-rc.14", - "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.14.tgz", - "integrity": "sha512-od496pShMen7nOy5VmVJCnq8rptd45vh6Nx/r2iPbrba6pa6p+tS2ywuIHRZ/OBvSbQZB0kWvpO9XBNVFXHD3Q==", + "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.1", - "ohash": "^2.0.10", + "exsolve": "^1.0.7", + "ohash": "^2.0.11", "pathe": "^2.0.3", - "ufo": "^1.5.4" + "ufo": "^1.6.1" } }, "unicode-canonical-property-names-ecmascript": { @@ -23866,9 +24424,9 @@ "dev": true }, "update-browserslist-db": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", - "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "dev": true, "requires": { "escalade": "^3.2.0", @@ -24049,28 +24607,71 @@ } }, "which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "requires": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + } + }, + "which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + } + } + }, + "which-collection": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "dev": true, "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" } }, "which-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", - "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", "dev": true, "requires": { "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, @@ -24208,42 +24809,48 @@ "dev": true }, "workerd": { - "version": "1.20250408.0", - "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20250408.0.tgz", - "integrity": "sha512-bBUX+UsvpzAqiWFNeZrlZmDGddiGZdBBbftZJz2wE6iUg/cIAJeVQYTtS/3ahaicguoLBz4nJiDo8luqM9fx1A==", + "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.20250408.0", - "@cloudflare/workerd-darwin-arm64": "1.20250408.0", - "@cloudflare/workerd-linux-64": "1.20250408.0", - "@cloudflare/workerd-linux-arm64": "1.20250408.0", - "@cloudflare/workerd-windows-64": "1.20250408.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": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", - "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.2.tgz", + "integrity": "sha512-Xz4Nm9c+LiBHhDR5bDLnNzmj6+5F+cyEAWPMkbs2awq/dYazR/efelZzUAjB/y3kNHL+uzkHvxVVpaOfGCPV7A==", "dev": true }, "wrangler": { - "version": "3.114.6", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.114.6.tgz", - "integrity": "sha512-05Ov/Bg8BQEy+/x/aRTeEUiXYspCiE0wmdgg4TIQwYLeEZaoBLE6KhqxEiLd8WNea0IRpzpBQOtAZ64Tjl0znQ==", + "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.3.4", - "@cloudflare/unenv-preset": "2.0.2", - "@esbuild-plugins/node-globals-polyfill": "0.2.3", - "@esbuild-plugins/node-modules-polyfill": "0.2.2", + "@cloudflare/kv-asset-handler": "0.4.0", + "@cloudflare/unenv-preset": "2.6.2", "blake3-wasm": "2.1.5", - "esbuild": "0.17.19", + "esbuild": "0.25.4", "fsevents": "~2.3.2", - "miniflare": "3.20250408.0", + "miniflare": "4.20250816.0", "path-to-regexp": "6.3.0", - "sharp": "^0.33.5", - "unenv": "2.0.0-rc.14", - "workerd": "1.20250408.0" + "unenv": "2.0.0-rc.19", + "workerd": "1.20250816.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==", + "dev": true, + "requires": {} + } } }, "wrap-ansi": { @@ -24386,9 +24993,9 @@ "dev": true }, "yaml": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", - "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", + "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": { @@ -24486,24 +25093,36 @@ "dev": true }, "youch": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/youch/-/youch-3.3.4.tgz", - "integrity": "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg==", + "version": "4.1.0-beta.10", + "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz", + "integrity": "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==", "dev": true, "requires": { - "cookie": "^0.7.1", - "mustache": "^4.2.0", - "stacktracey": "^2.1.8" + "@poppinss/colors": "^4.1.5", + "@poppinss/dumper": "^0.6.4", + "@speed-highlight/core": "^1.2.7", + "cookie": "^1.0.2", + "youch-core": "^0.3.3" }, "dependencies": { "cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", "dev": true } } }, + "youch-core": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz", + "integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==", + "dev": true, + "requires": { + "@poppinss/exception": "^1.2.2", + "error-stack-parser-es": "^1.0.5" + } + }, "zod": { "version": "3.22.3", "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz", diff --git a/package.json b/package.json index c5fb5af3a42..9c5082902c5 100644 --- a/package.json +++ b/package.json @@ -65,43 +65,44 @@ "prepare": "husky" }, "devDependencies": { - "@babel/core": "7.26.10", - "@babel/helper-module-imports": "7.25.9", - "@babel/plugin-proposal-class-properties": "7.18.6", - "@babel/plugin-proposal-object-rest-spread": "7.20.7", - "@babel/plugin-proposal-optional-chaining": "7.21.0", - "@babel/plugin-transform-object-assign": "7.25.9", - "@babel/preset-env": "7.26.9", - "@babel/preset-typescript": "7.27.0", - "@babel/register": "7.25.9", - "@microsoft/api-documenter": "7.26.20", - "@microsoft/api-extractor": "7.52.3", + "@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.3", + "@babel/preset-typescript": "7.27.1", + "@babel/register": "7.28.3", + "@microsoft/api-documenter": "7.26.32", + "@microsoft/api-extractor": "7.52.11", "@rollup/plugin-alias": "5.1.1", "@rollup/plugin-babel": "6.0.4", - "@rollup/plugin-commonjs": "28.0.3", + "@rollup/plugin-commonjs": "28.0.6", "@rollup/plugin-node-resolve": "16.0.1", "@rollup/plugin-replace": "6.0.2", "@rollup/plugin-terser": "0.4.4", - "@rollup/plugin-typescript": "12.1.2", - "@svta/common-media-library": "0.11.0", + "@rollup/plugin-typescript": "12.1.4", + "@svta/common-media-library": "0.17.1", "@types/chai": "4.3.20", "@types/chart.js": "2.9.41", "@types/mocha": "10.0.10", "@types/sinon-chai": "3.2.12", - "@typescript-eslint/eslint-plugin": "8.31.0", - "@typescript-eslint/parser": "8.31.0", + "@typescript-eslint/eslint-plugin": "8.38.0", + "@typescript-eslint/parser": "8.38.0", "babel-loader": "10.0.0", "babel-plugin-transform-remove-console": "6.9.4", "chai": "4.5.0", "chart.js": "2.9.4", - "chromedriver": "135.0.1", + "chromedriver": "139.0.3", "doctoc": "2.2.1", - "es-check": "8.0.2", + "es-check": "9.3.1", "eslint": "8.57.1", - "eslint-config-prettier": "10.1.2", - "eslint-plugin-import": "2.31.0", + "eslint-config-prettier": "10.1.8", + "eslint-plugin-import": "2.32.0", "eslint-plugin-mocha": "10.5.0", - "eslint-plugin-n": "17.17.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", "http-server": "14.1.1", @@ -115,23 +116,23 @@ "karma-rollup-preprocessor": "github:jlmakes/karma-rollup-preprocessor#7a7268d91149307b3cf2888ee4e65ccd079955a3", "karma-sinon-chai": "2.0.2", "karma-sourcemap-loader": "0.4.0", - "lint-staged": "15.5.1", + "lint-staged": "16.1.5", "markdown-styles": "3.2.0", "micromatch": "4.0.8", - "mocha": "11.1.0", + "mocha": "11.7.1", "node-fetch": "3.3.2", - "npm-run-all2": "7.0.2", - "prettier": "3.5.3", + "npm-run-all2": "8.0.4", + "prettier": "3.6.2", "promise-polyfill": "8.3.0", - "rollup": "4.40.0", + "rollup": "4.47.1", "rollup-plugin-istanbul": "5.0.0", "sauce-connect-launcher": "1.3.2", - "selenium-webdriver": "4.31.0", - "semver": "7.7.1", + "selenium-webdriver": "4.35.0", + "semver": "7.7.2", "sinon": "19.0.5", "sinon-chai": "3.7.0", "typescript": "5.8.3", "url-toolkit": "2.2.5", - "wrangler": "3.114.6" + "wrangler": "4.31.0" } } diff --git a/renovate.json b/renovate.json index 21919c957eb..36d7f9a6a01 100644 --- a/renovate.json +++ b/renovate.json @@ -1,8 +1,9 @@ { - "extends": ["config:base"], + "extends": ["config:recommended"], "labels": ["dependencies", "skip-change-log"], - "prHourlyLimit": 0, - "prConcurrentLimit": 0, + "schedule": ["* 0 28 * *"], + "prHourlyLimit": 3, + "prConcurrentLimit": 3, "prCreation": "immediate", "minimumReleaseAge": "7 days", "internalChecksFilter": "strict", @@ -12,10 +13,15 @@ "major": { "addLabels": ["semver-major"] }, + "ignoreDeps": ["@types/chart.js", "ace", "chart.js", "FileSaver.js"], "packageRules": [ { - "matchPackagePatterns": ["*"], - "rangeStrategy": "bump" + "matchDatasources": ["html"], + "enabled": false + }, + { + "rangeStrategy": "bump", + "matchPackageNames": ["*"] }, { "matchDepTypes": ["devDependencies"], diff --git a/src/config.ts b/src/config.ts index b5b07c1bccd..7ba0901fb2f 100644 --- a/src/config.ts +++ b/src/config.ts @@ -119,9 +119,10 @@ export type EMEControllerConfig = { ) => ArrayBuffer; emeEnabled: boolean; widevineLicenseUrl?: string; - drmSystems: DRMSystemsConfiguration; - drmSystemOptions: DRMSystemOptions; + drmSystems: DRMSystemsConfiguration | undefined; + drmSystemOptions: DRMSystemOptions | undefined; requestMediaKeySystemAccessFunc: MediaKeyFunc | null; + requireKeySystemAccessOnStart: boolean; }; export interface FragmentLoaderConstructor { @@ -219,6 +220,8 @@ export type StreamControllerConfig = { maxMaxBufferLength: number; startFragPrefetch: boolean; testBandwidth: boolean; + liveSyncMode?: 'edge' | 'buffered'; + startOnSegmentBoundary: boolean; }; export type GapControllerConfig = { @@ -280,6 +283,7 @@ export type HlsConfig = { ignoreDevicePixelRatio: boolean; maxDevicePixelRatio: number; preferManagedMediaSource: boolean; + preserveManualLevelOnError: boolean; timelineOffset?: number; ignorePlaylistParsingErrors: boolean; loader: { new (confg: HlsConfig): Loader }; @@ -371,6 +375,7 @@ export const hlsDefaultConfig: HlsConfig = { maxBufferLength: 30, // used by stream-controller backBufferLength: Infinity, // used by buffer-controller frontBufferFlushThreshold: Infinity, + startOnSegmentBoundary: false, // used by stream-controller maxBufferSize: 60 * 1000 * 1000, // used by stream-controller maxFragLookUpTolerance: 0.25, // used by stream-controller maxBufferHole: 0.1, // used by stream-controller and gap-controller @@ -379,6 +384,7 @@ export const hlsDefaultConfig: HlsConfig = { nudgeOffset: 0.1, // used by gap-controller nudgeMaxRetry: 3, // used by gap-controller nudgeOnVideoHole: true, // used by gap-controller + liveSyncMode: 'edge', // used by stream-controller liveSyncDurationCount: 3, // used by latency-controller liveSyncOnStallIncrease: 1, // used by latency-controller liveMaxLatencyDurationCount: Infinity, // used by latency-controller @@ -434,6 +440,7 @@ export const hlsDefaultConfig: HlsConfig = { requestMediaKeySystemAccessFunc: __USE_EME_DRM__ ? requestMediaKeySystemAccess : null, // used by eme-controller + requireKeySystemAccessOnStart: false, // used by eme-controller testBandwidth: true, progressive: false, lowLatencyMode: true, @@ -446,6 +453,7 @@ export const hlsDefaultConfig: HlsConfig = { interstitialAppendInPlace: true, interstitialLiveLookAhead: 10, useMediaCapabilities: __USE_MEDIA_CAPABILITIES__, + preserveManualLevelOnError: false, certLoadPolicy: { default: defaultLoadPolicy, diff --git a/src/controller/abr-controller.ts b/src/controller/abr-controller.ts index ac18a41f848..afefdea4a44 100644 --- a/src/controller/abr-controller.ts +++ b/src/controller/abr-controller.ts @@ -8,7 +8,6 @@ import { requiresMediaCapabilitiesDecodingInfo, SUPPORTED_INFO_DEFAULT, } from '../utils/mediacapabilities-helper'; -import { isHEVC } from '../utils/mp4-tools'; import { type AudioTracksByGroup, type CodecSetTier, @@ -47,6 +46,10 @@ class AbrController extends Logger implements AbrComponentAPI { private partCurrent: Part | null = null; private bitrateTestDelay: number = 0; private rebufferNotice: number = -1; + private supportedCache: Record< + string, + Promise + > = {}; public bwEstimator: EwmaBandWidthEstimator; @@ -108,7 +111,7 @@ class AbrController extends Logger implements AbrComponentAPI { this.unregisterListeners(); this.clearTimer(); // @ts-ignore - this.hls = this._abandonRulesCheck = null; + this.hls = this._abandonRulesCheck = this.supportedCache = null; this.fragCurrent = this.partCurrent = null; } @@ -119,6 +122,7 @@ class AbrController extends Logger implements AbrComponentAPI { this.lastLoadedFragLevel = -1; this.firstSelection = -1; this.lastLevelLoadSec = 0; + this.supportedCache = {}; this.fragCurrent = this.partCurrent = null; this.onLevelsUpdated(); this.clearTimer(); @@ -826,49 +830,64 @@ class AbrController extends Logger implements AbrComponentAPI { | undefined; if ( typeof mediaCapabilities?.decodingInfo === 'function' && - (requiresMediaCapabilitiesDecodingInfo( + requiresMediaCapabilitiesDecodingInfo( levelInfo, audioTracksByGroup, currentVideoRange, currentFrameRate, currentBw, audioPreference, - ) || - isHEVC(levelInfo.videoCodec)) // Force media capabilities check for HEVC to avoid failure on Windows + ) ) { levelInfo.supportedPromise = getMediaDecodingInfoPromise( levelInfo, audioTracksByGroup, 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)}`, + ); } - } - }); + }) + .catch((error) => { + this.warn( + `Error handling MediaCapabilities decodingInfo: ${error}`, + ); + }); } else { levelInfo.supportedResult = SUPPORTED_INFO_DEFAULT; } @@ -883,8 +902,9 @@ class AbrController extends Logger implements AbrComponentAPI { (!upSwitch && currentFrameRate > 0 && currentFrameRate < levelInfo.frameRate) || - (levelInfo.supportedResult && - !levelInfo.supportedResult.decodingInfoResults?.[0].smooth) + levelInfo.supportedResult?.decodingInfoResults?.some( + (info) => info.smooth === false, + ) ) { if (!firstSelection || i !== minStartIndex) { levelsSkipped.push(i); diff --git a/src/controller/audio-stream-controller.ts b/src/controller/audio-stream-controller.ts index 2ea1369da61..d4cc58c5c70 100644 --- a/src/controller/audio-stream-controller.ts +++ b/src/controller/audio-stream-controller.ts @@ -1,5 +1,5 @@ import BaseStreamController, { State } from './base-stream-controller'; -import { findFragWithCC, findNearestWithCC } from './fragment-finders'; +import { findNearestWithCC } from './fragment-finders'; import { FragmentState } from './fragment-tracker'; import ChunkCache from '../demux/chunk-cache'; import TransmuxerInterface from '../demux/transmuxer-interface'; @@ -142,16 +142,16 @@ class AudioStreamController // INIT_PTS_FOUND is triggered when the video track parsed in the stream-controller has a new PTS value onInitPtsFound( event: Events.INIT_PTS_FOUND, - { frag, id, initPTS, timescale }: InitPTSFoundData, + { frag, id, initPTS, timescale, trackId }: InitPTSFoundData, ) { // Always update the new INIT PTS // Can change due level switch if (id === PlaylistLevelType.MAIN) { const cc = frag.cc; const inFlightFrag = this.fragCurrent; - this.initPTS[cc] = { baseTime: initPTS, timescale }; + this.initPTS[cc] = { baseTime: initPTS, timescale, trackId }; this.log( - `InitPTS for cc: ${cc} found from main: ${initPTS}/${timescale}`, + `InitPTS for cc: ${cc} found from main: ${initPTS / timescale} (${initPTS}/${timescale}) trackId: ${trackId}`, ); this.mainAnchor = frag; // If we are waiting, tick immediately to unblock audio fragment transmuxing @@ -161,32 +161,54 @@ class AudioStreamController (!waitingData && !this.loadingParts) || (waitingData && waitingData.frag.cc !== cc) ) { - this.nextLoadPosition = this.findSyncFrag(frag).start; + this.syncWithAnchor(frag, waitingData?.frag); } - this.tick(); } else if ( !this.hls.hasEnoughToStart && inFlightFrag && inFlightFrag.cc !== cc ) { - this.startFragRequested = false; - this.nextLoadPosition = this.findSyncFrag(frag).start; inFlightFrag.abortRequests(); - this.resetLoadingState(); + this.syncWithAnchor(frag, inFlightFrag); } else if (this.state === State.IDLE) { this.tick(); } } } - private findSyncFrag(mainFrag: MediaFragment): MediaFragment { + protected getLoadPosition(): number { + if (!this.startFragRequested && this.nextLoadPosition >= 0) { + return this.nextLoadPosition; + } + return super.getLoadPosition(); + } + + private syncWithAnchor( + mainAnchor: MediaFragment, + waitingToAppend: Fragment | undefined, + ) { + // Drop waiting fragment if videoTrackCC has changed since waitingFragment was set and initPTS was not found + const mainFragLoading = this.mainFragLoading?.frag || null; + if (waitingToAppend) { + if (mainFragLoading?.cc === waitingToAppend.cc) { + // Wait for loading frag to complete and INIT_PTS_FOUND + return; + } + } + const targetDiscontinuity = (mainFragLoading || mainAnchor).cc; const trackDetails = this.getLevelDetails(); - const cc = mainFrag.cc; - return ( - findNearestWithCC(trackDetails, cc, mainFrag) || - (trackDetails && findFragWithCC(trackDetails.fragments, cc)) || - mainFrag - ); + const pos = this.getLoadPosition(); + const syncFrag = findNearestWithCC(trackDetails, targetDiscontinuity, pos); + // Only stop waiting for audioFrag.cc if an audio segment of the same discontinuity domain (cc) is found + if (syncFrag) { + this.log(`Syncing with main frag at ${syncFrag.start} cc ${syncFrag.cc}`); + this.startFragRequested = false; + this.nextLoadPosition = syncFrag.start; + this.resetLoadingState(); + if (this.state === State.IDLE) { + this.doTickIdle(); + } + } } startLoad(startPosition: number, skipSeekToStartPosition?: boolean) { @@ -233,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: { @@ -265,12 +279,7 @@ class AudioStreamController super._handleFragmentLoadComplete(data); } } else if (mainAnchor && mainAnchor.cc !== waitingData.frag.cc) { - // Drop waiting fragment if videoTrackCC has changed since waitingFragment was set and initPTS was not found - this.log( - `Waiting fragment cc (${frag.cc}) cancelled because video is at cc ${mainAnchor.cc}`, - ); - this.nextLoadPosition = this.findSyncFrag(mainAnchor).start; - this.clearWaitingFragment(); + this.syncWithAnchor(mainAnchor, waitingData.frag); } } else { this.state = State.IDLE; @@ -281,23 +290,12 @@ class AudioStreamController this.onTickEnd(); } - clearWaitingFragment() { + protected resetLoadingState() { const waitingData = this.waitingData; if (waitingData) { - if (!this.hls.hasEnoughToStart) { - // Load overlapping fragment on start when discontinuity start times are not aligned - this.startFragRequested = false; - } this.fragmentTracker.removeFragment(waitingData.frag); this.waitingData = null; - if (this.state !== State.STOPPED) { - this.state = State.IDLE; - } } - } - - protected resetLoadingState() { - this.clearWaitingFragment(); super.resetLoadingState(); } @@ -802,7 +800,9 @@ class AudioStreamController if (data.parent !== 'audio') { return; } - this.resetLoadingState(); + if (!this.reduceLengthAndFlushBuffer(data)) { + this.resetLoadingState(); + } break; case ErrorDetails.BUFFER_FULL_ERROR: if (data.parent !== 'audio') { @@ -876,6 +876,9 @@ class AudioStreamController if (initSegment?.tracks) { const mapFragment = frag.initSegment || frag; + if (this.unhandledEncryptionError(initSegment, frag)) { + return; + } this._bufferInitSegment( level, initSegment.tracks, diff --git a/src/controller/base-playlist-controller.ts b/src/controller/base-playlist-controller.ts index 6ff6a58cd63..e12ff9d9412 100644 --- a/src/controller/base-playlist-controller.ts +++ b/src/controller/base-playlist-controller.ts @@ -87,8 +87,8 @@ export default class BasePlaylistController } if (foundIndex !== -1) { const attr = renditionReports[foundIndex]; - const msn = parseInt(attr['LAST-MSN']) || previous?.lastPartSn; - let part = parseInt(attr['LAST-PART']) || previous?.lastPartIndex; + const msn = parseInt(attr['LAST-MSN']) || previous.lastPartSn; + let part = parseInt(attr['LAST-PART']) || previous.lastPartIndex; if (this.hls.config.lowLatencyMode) { const currentGoal = Math.min( previous.age - previous.partTarget, @@ -164,7 +164,7 @@ export default class BasePlaylistController const offset = Math.max(timelineOffset || 0, 0); details.appliedTimelineOffset = offset; details.fragments.forEach((frag) => { - frag.start = frag.playlistOffset + offset; + frag.setStart(frag.playlistOffset + offset); }); } @@ -174,7 +174,7 @@ export default class BasePlaylistController details.reloaded(previousDetails); // Merge live playlists to adjust fragment starts and fill in delta playlist skipped segments if (previousDetails && details.fragments.length > 0) { - mergeDetails(previousDetails, details); + mergeDetails(previousDetails, details, this); const error = details.playlistParsingError; if (error) { this.warn(error); diff --git a/src/controller/base-stream-controller.ts b/src/controller/base-stream-controller.ts index 058fa33dd8f..a26c14380be 100644 --- a/src/controller/base-stream-controller.ts +++ b/src/controller/base-stream-controller.ts @@ -2,7 +2,7 @@ import { ErrorActionFlags, NetworkErrorAction } from './error-controller'; import { findFragmentByPDT, findFragmentByPTS, - findFragWithCC, + findNearestWithCC, } from './fragment-finders'; import { FragmentState } from './fragment-tracker'; import Decrypter from '../crypt/decrypter'; @@ -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, @@ -57,9 +61,9 @@ import type { PartsLoadedData, } from '../types/events'; import type { Level } from '../types/level'; -import type { RemuxedTrack } from '../types/remuxer'; +import type { InitSegmentData, RemuxedTrack } from '../types/remuxer'; import type { Bufferable, BufferInfo } from '../utils/buffer-helper'; -import type { RationalTimestamp } from '../utils/timescale-conversion'; +import type { TimestampOffset } from '../utils/timescale-conversion'; type ResolveFragLoaded = (FragLoadedEndData) => void; type RejectFragLoaded = (LoadError) => void; @@ -111,7 +115,7 @@ export default class BaseStreamController protected levelLastLoaded: Level | null = null; protected startFragRequested: boolean = false; protected decrypter: Decrypter; - protected initPTS: RationalTimestamp[] = []; + protected initPTS: TimestampOffset[] = []; protected buffering: boolean = true; protected loadingParts: boolean = false; private loopSn?: string | number; @@ -217,6 +221,19 @@ export default class BaseStreamController return false; } // Stream is not "ended" when there is a second buffered range starting before the end of the playlist + const bufferedRanges = bufferInfo.buffered; + if ( + this.config.maxBufferHole && + bufferedRanges && + bufferedRanges.length > 1 + ) { + // make sure bufferInfo accounts for any gaps + bufferInfo = BufferHelper.bufferedInfo( + bufferedRanges, + bufferInfo.start, + 0, + ); + } const nextStart = bufferInfo.nextStart; const hasSecondBufferedRange = nextStart && nextStart > timelineStart && nextStart < levelDetails.edge; @@ -251,7 +268,7 @@ export default class BaseStreamController public getLevelDetails(): LevelDetails | undefined { if (this.levels && this.levelLastLoaded !== null) { - return this.levelLastLoaded?.details; + return this.levelLastLoaded.details; } } @@ -271,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); @@ -296,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(); @@ -331,11 +346,12 @@ export default class BaseStreamController currentTime, config.maxBufferHole, ); + const noFowardBuffer = !bufferInfo.len; this.log( - `media seeking to ${ + `Media seeking to ${ Number.isFinite(currentTime) ? currentTime.toFixed(3) : currentTime - }, state: ${state}`, + }, state: ${state}, ${noFowardBuffer ? 'out of' : 'in'} buffer`, ); if (this.state === State.ENDED) { @@ -348,7 +364,7 @@ export default class BaseStreamController fragCurrent.start + fragCurrent.duration + tolerance; // if seeking out of buffered range or into new one if ( - !bufferInfo.len || + noFowardBuffer || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end ) { @@ -357,7 +373,7 @@ export default class BaseStreamController if (currentTime < fragStartOffset || pastFragment) { if (pastFragment && fragCurrent.loader) { this.log( - 'seeking outside of buffer while fragment load in progress, cancel fragment load', + `Cancelling fragment load for seek (sn: ${fragCurrent.sn})`, ); fragCurrent.abortRequests(); this.resetLoadingState(); @@ -400,15 +416,20 @@ export default class BaseStreamController } // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target - if (!this.hls.hasEnoughToStart && !bufferInfo.len) { + if (!this.hls.hasEnoughToStart) { this.log( - `setting startPosition to ${currentTime} because of seek before start`, + `Setting ${noFowardBuffer ? 'startPosition' : 'nextLoadPosition'} to ${currentTime} for seek without enough to start`, ); - this.nextLoadPosition = this.startPosition = currentTime; + this.nextLoadPosition = currentTime; + if (noFowardBuffer) { + this.startPosition = currentTime; + } } - // Async tick to speed up processing - this.tickImmediate(); + if (noFowardBuffer && this.state === State.IDLE) { + // Async tick to speed up processing + this.tickImmediate(); + } }; protected onMediaEnded = () => { @@ -693,6 +714,39 @@ export default class BaseStreamController this.tick(); } + protected unhandledEncryptionError( + initSegment: InitSegmentData, + frag: Fragment, + ): boolean { + const tracks = initSegment.tracks; + if ( + tracks && + !frag.encrypted && + (tracks.audio?.encrypted || tracks.video?.encrypted) && + (!this.config.emeEnabled || !this.keyLoader.emeController) + ) { + const media = this.media; + const error = new Error( + `Encrypted track with no key in ${this.fragInfo(frag)} (media ${media ? 'attached mediaKeys: ' + media.mediaKeys : 'detached'})`, + ); + this.warn(error.message); + // Ignore if media is detached or mediaKeys are set + if (!media || media.mediaKeys) { + return false; + } + this.hls.trigger(Events.ERROR, { + type: ErrorTypes.KEY_SYSTEM_ERROR, + details: ErrorDetails.KEY_SYSTEM_NO_KEYS, + fatal: false, + error, + frag, + }); + this.resetTransmuxer(); + return true; + } + return false; + } + protected fragContextChanged(frag: Fragment | null) { const { fragCurrent } = this; return ( @@ -767,7 +821,7 @@ export default class BaseStreamController progressCallback?: FragmentLoadProgressCallback, ): Promise { this.fragCurrent = frag; - const details = level?.details; + const details = level.details; if (!this.levels || !details) { throw new Error( `frag load aborted, missing level${details ? '' : ' detail'}s`, @@ -791,13 +845,19 @@ 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( + frag, + details.encryptedFragments, + this.startFragRequested, + ); + if (keyLoadingPromise) { + this.log(`[eme] blocking frag load until media-keys acquired`); } - } else if (!frag.encrypted && details.encryptedFragments.length) { - this.keyLoader.loadClear(frag, details.encryptedFragments); } const fragPrevious = this.fragPrevious; @@ -819,7 +879,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); @@ -888,7 +948,7 @@ export default class BaseStreamController this.log( `LL-Part loading OFF after next part miss @${targetBufferTime.toFixed( 2, - )}`, + )} Check buffer at sn: ${frag.sn} loaded parts: ${details.partList?.filter((p) => p.loaded).map((p) => `[${p.start}-${p.end}]`)}`, ); this.loadingParts = false; } else if (!frag.url) { @@ -898,7 +958,7 @@ export default class BaseStreamController this.log( `Loading ${frag.type} sn: ${frag.sn} of ${this.fragInfo(frag, false)}) cc: ${frag.cc} ${ - details ? '[' + details.startSN + '-' + details.endSN + ']' : '' + '[' + details.startSN + '-' + details.endSN + ']' }, target: ${parseFloat(targetBufferTime.toFixed(3))}`, ); // Don't update nextLoadPosition for fragments which are not buffered @@ -913,7 +973,7 @@ export default class BaseStreamController if (dataOnProgress && keyLoadingPromise) { result = keyLoadingPromise .then((keyLoadedData) => { - if (!keyLoadedData || this.fragContextChanged(keyLoadedData?.frag)) { + if (!keyLoadedData || this.fragContextChanged(keyLoadedData.frag)) { return null; } return this.fragmentLoader.load(frag, progressCallback); @@ -930,7 +990,7 @@ export default class BaseStreamController keyLoadingPromise, ]) .then(([fragLoadedData]) => { - if (!dataOnProgress && fragLoadedData && progressCallback) { + if (!dataOnProgress && progressCallback) { progressCallback(fragLoadedData); } return fragLoadedData; @@ -983,11 +1043,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 (error.data && 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); } @@ -1045,10 +1110,14 @@ export default class BaseStreamController if (!details) { return this.loadingParts; } - if (details?.partList) { + if (details.partList) { // 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) { @@ -1099,26 +1168,32 @@ export default class BaseStreamController chunkMeta: ChunkMetadata, noBacktracking?: boolean, ) { - if (!data || this.state !== State.PARSING) { + if (this.state !== State.PARSING) { return; } const { data1, data2 } = data; let buffer = data1; - if (data1 && data2) { + if (data2) { // Combine the moof + mdat so that we buffer with a single append buffer = appendUint8Array(data1, data2); } - if (!buffer?.length) { + if (!buffer.length) { return; } - + const offsetTimestamp = this.initPTS[frag.cc] as + | TimestampOffset + | undefined; + const offset = offsetTimestamp + ? -offsetTimestamp.baseTime / offsetTimestamp.timescale + : undefined; const segment: BufferAppendingData = { type: data.type, frag, part, chunkMeta, + offset, parent: frag.type, data: buffer, }; @@ -1236,10 +1311,9 @@ export default class BaseStreamController position: number, playlistType: PlaylistLevelType = PlaylistLevelType.MAIN, ): Fragment | null { - const fragOrPart = this.fragmentTracker?.getAppendedFrag( - position, - playlistType, - ); + const fragOrPart = (this.fragmentTracker as any) + ? this.fragmentTracker.getAppendedFrag(position, playlistType) + : null; if (fragOrPart && 'fragment' in fragOrPart) { return fragOrPart.fragment; } @@ -1285,7 +1359,7 @@ export default class BaseStreamController this.log(`LL-Part loading ON for initial live fragment`); this.loadingParts = true; } - frag = this.getInitialLiveFragment(levelDetails, fragments); + frag = this.getInitialLiveFragment(levelDetails); const mainStart = this.hls.startPosition; const liveSyncPosition = this.hls.liveSyncPosition; const startPosition = frag @@ -1369,7 +1443,7 @@ export default class BaseStreamController } protected get primaryPrefetch(): boolean { - if (interstitialsEnabled(this.hls.config)) { + if (interstitialsEnabled(this.config)) { const playingInterstitial = this.hls.interstitialsManager?.playingItem?.event; if (playingInterstitial) { @@ -1387,7 +1461,7 @@ export default class BaseStreamController return frag; } if ( - interstitialsEnabled(this.hls.config) && + interstitialsEnabled(this.config) && frag.type !== PlaylistLevelType.SUBTITLE ) { // Do not load fragments outside the buffering schedule segment @@ -1443,7 +1517,7 @@ export default class BaseStreamController mapToInitFragWhenRequired(frag: Fragment | null): typeof frag { // If an initSegment is present, it must be buffered first - if (frag?.initSegment && !frag?.initSegment.data && !this.bitrateTest) { + if (frag?.initSegment && !frag.initSegment.data && !this.bitrateTest) { return frag.initSegment; } @@ -1468,9 +1542,14 @@ export default class BaseStreamController if (loaded) { nextPart = -1; } else if ( - (contiguous || part.independent || independentAttrOmitted) && - part.fragment === frag + contiguous || + ((part.independent || independentAttrOmitted) && part.fragment === frag) ) { + if (part.fragment !== frag) { + this.warn( + `Need buffer at ${targetBufferTime} but next unloaded part starts at ${part.start}`, + ); + } nextPart = i; } contiguous = loaded; @@ -1482,8 +1561,17 @@ export default class BaseStreamController partList: Part[], targetBufferTime: number, ): boolean { - const lastPart = partList[partList.length - 1]; - return lastPart && targetBufferTime > lastPart.start && lastPart.loaded; + let part: Part; + for (let i = partList.length; i--; ) { + part = partList[i]; + if (!part.loaded) { + return false; + } + if (targetBufferTime > part.start) { + return true; + } + } + return false; } /* @@ -1493,8 +1581,8 @@ export default class BaseStreamController */ protected getInitialLiveFragment( levelDetails: LevelDetails, - fragments: MediaFragment[], ): MediaFragment | null { + const fragments = levelDetails.fragments; const fragPrevious = this.fragPrevious; let frag: MediaFragment | null = null; if (fragPrevious) { @@ -1530,7 +1618,11 @@ export default class BaseStreamController // It's important to stay within the continuity range if available; otherwise the fragments in the playlist // will have the wrong start times if (!frag) { - frag = findFragWithCC(fragments, fragPrevious.cc); + frag = findNearestWithCC( + levelDetails, + fragPrevious.cc, + fragPrevious.end, + ); if (frag) { this.log( `Live playlist, switching playlist, load frag with same CC: ${frag.sn}`, @@ -1575,7 +1667,6 @@ export default class BaseStreamController ); if ( loadingParts && - fragmentHint && !this.bitrateTest && partList[partList.length - 1].fragment.sn === fragmentHint.sn ) { @@ -1623,11 +1714,11 @@ export default class BaseStreamController frag.sn === fragPrevious.sn && (!loadingParts || partList[0].fragment.sn > frag.sn || - (!levelDetails.live && !loadingParts)) + !levelDetails.live) ) { // Force the next fragment to load if the previous one was already selected. This can occasionally happen with // non-uniform fragment durations - const sameLevel = fragPrevious && frag.level === fragPrevious.level; + const sameLevel = frag.level === fragPrevious.level; if (sameLevel) { const nextFrag = fragments[curSNIdx + 1]; if ( @@ -1743,14 +1834,14 @@ 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 && isMediaFragment(frag) && frag.stats.aborted ) { - this.warn( + this.log( `Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of ${this.playlistLabel()} ${ frag.level } was aborted`, @@ -1796,20 +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; - if (!retry && noAlternate && isMediaFragment(frag) && !frag.endList) { + const live = this.hls.latestLevelDetails?.live; + if ( + !retry && + noAlternate && + isMediaFragment(frag) && + !frag.endList && + 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 @@ -1817,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 @@ -1845,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) { @@ -1866,7 +1993,7 @@ export default class BaseStreamController // this happens on IE/Edge, refer to https://github.com/video-dev/hls.js/pull/708 // in that case flush the whole audio buffer to recover this.warn( - `Buffer full error while media.currentTime is not buffered, flush ${playlistType} buffer`, + `Buffer full error while media.currentTime (${this.getLoadPosition()}) is not buffered, flush ${playlistType} buffer`, ); } if (frag) { @@ -1924,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 @@ -1943,11 +2071,11 @@ 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); + this.resetStartWhenNotLoaded(); this.resetLoadingState(); } @@ -1995,6 +2123,7 @@ export default class BaseStreamController info.endPTS, info.startDTS, info.endDTS, + this, ); this.hls.trigger(Events.LEVEL_PTS_UPDATED, { details, @@ -2081,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/buffer-controller.ts b/src/controller/buffer-controller.ts index d2bd67014ac..d27c56c4d8a 100755 --- a/src/controller/buffer-controller.ts +++ b/src/controller/buffer-controller.ts @@ -278,9 +278,9 @@ export default class BufferController extends Logger implements ComponentAPI { data: MediaAttachingData, ) { const media = (this.media = data.media); - const MediaSource = getMediaSource(this.appendSource); this.transferData = this.overrides = undefined; - if (media && MediaSource) { + const MediaSource = getMediaSource(this.appendSource); + if (MediaSource) { const transferringMedia = !!data.mediaSource; if (transferringMedia || data.overrides) { this.transferData = data; @@ -318,7 +318,7 @@ export default class BufferController extends Logger implements ComponentAPI { private assignMediaSource(ms: MediaSource) { this.log( - `${this.transferData?.mediaSource === ms ? 'transferred' : 'created'} media source: ${ms.constructor?.name}`, + `${this.transferData?.mediaSource === ms ? 'transferred' : 'created'} media source: ${(ms.constructor as any)?.name}`, ); // MediaSource listeners are arrow functions with a lexical scope, and do not need to be bound ms.addEventListener('sourceopen', this._onMediaSourceOpen); @@ -343,9 +343,12 @@ export default class BufferController extends Logger implements ComponentAPI { : null; const trackCount = trackNames ? trackNames.length : 0; const mediaSourceOpenCallback = () => { - if (this.media && this.mediaSourceOpenOrEnded) { - this._onMediaSourceOpen(); - } + // eslint-disable-next-line @typescript-eslint/no-floating-promises + Promise.resolve().then(() => { + if (this.media && this.mediaSourceOpenOrEnded) { + this._onMediaSourceOpen(); + } + }); }; if (transferredTracks && trackNames && trackCount) { if (!this.tracksReady) { @@ -415,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); @@ -435,7 +439,7 @@ transfer tracks: ${stringify(transferredTracks, (key, value) => (key === 'initSe } private _onEndStreaming = (event) => { - if (!this.hls) { + if (!this.hls as any) { return; } if (this.mediaSource?.readyState !== 'open') { @@ -445,7 +449,7 @@ transfer tracks: ${stringify(transferredTracks, (key, value) => (key === 'initSe }; private _onStartStreaming = (event) => { - if (!this.hls) { + if (!this.hls as any) { return; } this.hls.resumeBuffering(); @@ -540,8 +544,6 @@ transfer tracks: ${stringify(transferredTracks, (key, value) => (key === 'initSe } this.media = null; } - - this.hls.trigger(Events.MEDIA_DETACHED, data); } private onBufferReset() { @@ -755,9 +757,9 @@ transfer tracks: ${stringify(transferredTracks, (key, value) => (key === 'initSe eventData: BufferAppendingData, ) { const { tracks } = this; - const { data, type, parent, frag, part, chunkMeta } = eventData; + const { data, type, parent, frag, part, chunkMeta, offset } = eventData; const chunkStats = chunkMeta.buffering[type]; - const sn = frag.sn; + const { sn, cc } = frag; const bufferAppendingStart = self.performance.now(); chunkStats.start = bufferAppendingStart; const fragBuffering = frag.stats.buffering; @@ -785,12 +787,17 @@ transfer tracks: ${stringify(transferredTracks, (key, value) => (key === 'initSe } // Block audio append until overlapping video append - const videoTrack = this.tracks.video; + const videoTrack = tracks.video; const videoSb = videoTrack?.buffer; if (videoSb && sn !== 'initSegment') { const partOrFrag = part || (frag as MediaFragment); const blockedAudioAppend = this.blockedAudioAppend; - if (type === 'audio' && parent !== 'main' && !this.blockedAudioAppend) { + if ( + type === 'audio' && + parent !== 'main' && + !this.blockedAudioAppend && + !(videoTrack.ending || videoTrack.ended) + ) { const pStart = partOrFrag.start; const pTime = pStart + partOrFrag.duration * 0.05; const vbuffered = videoSb.buffered; @@ -827,19 +834,13 @@ transfer tracks: ${stringify(transferredTracks, (key, value) => (key === 'initSe label: `append-${type}`, execute: () => { chunkStats.executeStart = self.performance.now(); - if (checkTimestampOffset) { - const track = this.tracks[type]; - if (track) { - const sb = track.buffer; - if (sb) { - const delta = fragStart - sb.timestampOffset; - if (Math.abs(delta) >= 0.1) { - this.log( - `Updating audio SourceBuffer timestampOffset to ${fragStart} (delta: ${delta}) sn: ${sn})`, - ); - sb.timestampOffset = fragStart; - } - } + + const sb = this.tracks[type]?.buffer; + if (sb) { + if (checkTimestampOffset) { + this.updateTimestampOffset(sb, fragStart, 0.1, type, sn, cc); + } else if (offset !== undefined && Number.isFinite(offset)) { + this.updateTimestampOffset(sb, offset, 0.000001, type, sn, cc); } } this.appendExecutor(data, type); @@ -895,7 +896,11 @@ transfer tracks: ${stringify(transferredTracks, (key, value) => (key === 'initSe fatal: false, }; const mediaError = this.media?.error; - if ((error as DOMException).code === DOMException.QUOTA_EXCEEDED_ERR) { + if ( + (error as DOMException).code === DOMException.QUOTA_EXCEEDED_ERR || + error.name == 'QuotaExceededError' || + `quota` in error + ) { // QuotaExceededError: http://www.w3.org/TR/html5/infrastructure.html#quotaexceedederror // let's stop appending any segments, and report BUFFER_FULL_ERROR error event.details = ErrorDetails.BUFFER_FULL_ERROR; @@ -1027,10 +1032,15 @@ transfer tracks: ${stringify(transferredTracks, (key, value) => (key === 'initSe public get bufferedToEnd(): boolean { return ( this.sourceBufferCount > 0 && - !this.sourceBuffers.some( - ([type]) => - type && (!this.tracks[type]?.ended || this.tracks[type]?.ending), - ) + !this.sourceBuffers.some(([type]) => { + if (type) { + const track = this.tracks[type]; + if (track) { + return !track.ended || track.ending; + } + } + return false; + }) ); } @@ -1079,6 +1089,9 @@ transfer tracks: ${stringify(transferredTracks, (key, value) => (key === 'initSe this.tracksEnded(); this.hls.trigger(Events.BUFFERED_TO_END, undefined); } + } else if (data.type === 'video') { + // Make sure pending audio appends are unblocked when video reaches end + this.unblockAudio(); } } @@ -1105,11 +1118,13 @@ transfer tracks: ${stringify(transferredTracks, (key, value) => (key === 'initSe } private updateDuration() { - const durationAndRange = this.getDurationAndRange(); - if (!durationAndRange) { - return; - } - this.blockUntilOpen(() => this.updateMediaSource(durationAndRange)); + this.blockUntilOpen(() => { + const durationAndRange = this.getDurationAndRange(); + if (!durationAndRange) { + return; + } + this.updateMediaSource(durationAndRange); + }); } private onError(event: Events.ERROR, data: ErrorData) { @@ -1162,13 +1177,14 @@ transfer tracks: ${stringify(transferredTracks, (key, value) => (key === 'initSe ); } + const frontBufferFlushThreshold = config.frontBufferFlushThreshold; if ( - Number.isFinite(config.frontBufferFlushThreshold) && - config.frontBufferFlushThreshold > 0 + Number.isFinite(frontBufferFlushThreshold) && + frontBufferFlushThreshold > 0 ) { const frontBufferLength = Math.max( config.maxBufferLength, - config.frontBufferFlushThreshold, + frontBufferFlushThreshold, ); const maxFrontBufferLength = Math.max(frontBufferLength, targetDuration); @@ -1273,7 +1289,7 @@ transfer tracks: ${stringify(transferredTracks, (key, value) => (key === 'initSe const playlistEnd = details.edge; if (details.live && this.hls.config.liveDurationInfinity) { const len = details.fragments.length; - if (len && details.live && !!mediaSource.setLiveSeekableRange) { + if (len && !!(mediaSource as any).setLiveSeekableRange) { const start = Math.max(0, details.fragmentStart); const end = Math.max(start, playlistEnd); @@ -1547,7 +1563,7 @@ transfer tracks: ${stringify(transferredTracks, (key, value) => (key === 'initSe }; private get mediaSrc(): string | undefined { - const media = this.media?.querySelector?.('source') || this.media; + const media = (this.media?.querySelector as any)?.('source') || this.media; return media?.src; } @@ -1593,6 +1609,23 @@ transfer tracks: ${stringify(transferredTracks, (key, value) => (key === 'initSe } } + private updateTimestampOffset( + sb: ExtendedSourceBuffer, + timestampOffset: number, + tolerance: number, + type: SourceBufferName, + sn: number | 'initSegment', + cc: number, + ) { + const delta = timestampOffset - sb.timestampOffset; + if (Math.abs(delta) >= tolerance) { + this.log( + `Updating ${type} SourceBuffer timestampOffset to ${timestampOffset} (sn: ${sn} cc: ${cc})`, + ); + sb.timestampOffset = timestampOffset; + } + } + // This method must result in an updateend event; if remove is not called, onSBUpdateEnd must be called manually private removeExecutor( type: SourceBufferName, @@ -1630,7 +1663,10 @@ transfer tracks: ${stringify(transferredTracks, (key, value) => (key === 'initSe } // This method must result in an updateend event; if append is not called, onSBUpdateEnd must be called manually - private appendExecutor(data: Uint8Array, type: SourceBufferName) { + private appendExecutor( + data: Uint8Array, + type: SourceBufferName, + ) { const track = this.tracks[type]; const sb = track?.buffer; if (!sb) { diff --git a/src/controller/cmcd-controller.ts b/src/controller/cmcd-controller.ts index 91a15c57814..f0d48b26f79 100644 --- a/src/controller/cmcd-controller.ts +++ b/src/controller/cmcd-controller.ts @@ -175,9 +175,7 @@ export default class CMCDController implements ComponentAPI { }, {}); } - const options: CmcdEncodeOptions = { - baseUrl: context.url, - }; + const options: CmcdEncodeOptions = { baseUrl: context.url }; if (this.useHeaders) { if (!context.headers) { @@ -212,10 +210,7 @@ export default class CMCDController implements ComponentAPI { const { frag, part } = context; const level = this.hls.levels[frag.level]; const ot = this.getObjectType(frag); - const data: Cmcd = { - d: (part || frag).duration * 1000, - ot, - }; + const data: Cmcd = { d: (part || frag).duration * 1000, ot }; if ( ot === CmcdObjectType.VIDEO || @@ -311,11 +306,11 @@ export default class CMCDController implements ComponentAPI { levels = hls.levels.slice(0, len); } - for (const level of levels) { + levels.forEach((level) => { if (level.bitrate > bitrate) { bitrate = level.bitrate; } - } + }); return bitrate > 0 ? bitrate : NaN; } diff --git a/src/controller/content-steering-controller.ts b/src/controller/content-steering-controller.ts index 7ccb060f973..ac7b5020d86 100644 --- a/src/controller/content-steering-controller.ts +++ b/src/controller/content-steering-controller.ts @@ -1,4 +1,5 @@ import { ErrorActionFlags, NetworkErrorAction } from './error-controller'; +import { ErrorDetails } from '../errors'; import { Events } from '../events'; import { Level } from '../types/level'; import { @@ -24,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; @@ -216,7 +218,11 @@ export default class ContentSteeringController this.updatePathwayPriority(pathwayPriority); errorAction.resolved = this.pathwayId !== errorPathway; } - if (!errorAction.resolved) { + if (data.details === ErrorDetails.BUFFER_APPEND_ERROR && !data.fatal) { + // Error will become fatal in buffer-controller when reaching `appendErrorMaxRetry` + // Stream-controllers are expected to reduce buffer length even if this is not deemed a QuotaExceededError + errorAction.resolved = true; + } else if (!errorAction.resolved) { this.warn( `Could not resolve ${data.details} ("${ data.error.message @@ -451,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; @@ -496,7 +502,7 @@ export default class ContentSteeringController onError: ( error: { code: number; text: string }, context: LoaderContext, - networkDetails: any, + networkDetails: NullableNetworkDetails, stats: LoaderStats, ) => { this.log( @@ -526,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/controller/eme-controller.ts b/src/controller/eme-controller.ts index ecbd1b0e0ad..b7e8488f787 100644 --- a/src/controller/eme-controller.ts +++ b/src/controller/eme-controller.ts @@ -7,26 +7,22 @@ import { EventEmitter } from 'eventemitter3'; import { ErrorDetails, ErrorTypes } from '../errors'; import { Events } from '../events'; import { LevelKey } from '../loader/level-key'; -import Hex from '../utils/hex'; +import { + addEventListener, + removeEventListener, +} from '../utils/event-listener-helper'; +import { arrayToHex } from '../utils/hex'; import { Logger } from '../utils/logger'; import { getKeySystemsForConfig, getSupportedMediaKeySystemConfigurations, isPersistentSessionType, + keySystemDomainToKeySystemFormat, keySystemFormatToKeySystemDomain, - keySystemIdToKeySystemDomain, KeySystems, - keySystemDomainToKeySystemFormat as keySystemToKeySystemFormat, - parsePlayReadyWRM, requestMediaKeySystemAccess, } from '../utils/mediakeys-helper'; -import { - bin2str, - parseMultiPssh, - parseSinf, - type PsshData, - type PsshInvalidResult, -} from '../utils/mp4-tools'; +import { bin2str, parseSinf } from '../utils/mp4-tools'; import { base64Decode } from '../utils/numeric-encoding-utils'; import { stringify } from '../utils/safe-json-stringify'; import { strToUtf8array } from '../utils/utf8-utils'; @@ -48,18 +44,20 @@ import type { LoaderContext, } from '../types/loader'; import type { KeySystemFormats } from '../utils/mediakeys-helper'; + interface KeySystemAccessPromises { keySystemAccess: Promise; mediaKeys?: Promise; certificate?: Promise; + hasMediaKeys?: boolean; } 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; @@ -84,16 +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); @@ -103,10 +103,8 @@ class EMEController extends Logger implements ComponentAPI { } public destroy() { - const media = this.media; - this.unregisterListeners(); + this.onDestroying(); this.onMediaDetached(); - this._clear(media); // Remove any references that could be held in config options or callbacks const config = this.config; config.requestMediaKeySystemAccessFunc = null; @@ -123,6 +121,7 @@ class EMEController extends Logger implements ComponentAPI { this.hls.on(Events.MEDIA_DETACHED, this.onMediaDetached, this); this.hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); this.hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this); + this.hls.on(Events.DESTROYING, this.onDestroying, this); } private unregisterListeners() { @@ -130,11 +129,12 @@ class EMEController extends Logger implements ComponentAPI { this.hls.off(Events.MEDIA_DETACHED, this.onMediaDetached, this); this.hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); this.hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this); + this.hls.off(Events.DESTROYING, this.onDestroying, this); } private getLicenseServerUrl(keySystem: KeySystems): string | undefined { const { drmSystems, widevineLicenseUrl } = this.config; - const keySystemConfiguration = drmSystems[keySystem]; + const keySystemConfiguration = drmSystems?.[keySystem]; if (keySystemConfiguration) { return keySystemConfiguration.licenseUrl; @@ -158,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; @@ -249,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( @@ -264,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}`, @@ -281,10 +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}"`); + keySystemAccessPromisesNew.hasMediaKeys = true; return certificateRequest.then((certificate) => { if (certificate) { return this.setMediaKeysServerCertificate( @@ -295,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({ @@ -319,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[]), )}`, ); @@ -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); } @@ -367,23 +368,61 @@ 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( mediaKeySessionContext: MediaKeySessionContext, - data: Uint8Array, + data: Uint8Array, ): 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); } + public getSelectedKeySystemFormats(): KeySystemFormats[] { + return (Object.keys(this.keySystemAccessPromises) as KeySystems[]) + .map((keySystem) => ({ + keySystem, + hasMediaKeys: this.keySystemAccessPromises[keySystem]!.hasMediaKeys, + })) + .filter(({ hasMediaKeys }) => !!hasMediaKeys) + .map(({ keySystem }) => keySystemDomainToKeySystemFormat(keySystem)) + .filter((keySystem): keySystem is KeySystemFormats => !!keySystem); + } + + public getKeySystemAccess(keySystemsToAttempt: KeySystems[]): Promise { + return this.getKeySystemSelectionPromise(keySystemsToAttempt).then( + ({ keySystem, mediaKeys }) => { + return this.attemptSetMediaKeys(keySystem, mediaKeys); + }, + ); + } + + public selectKeySystem( + keySystemsToAttempt: KeySystems[], + ): Promise { + return new Promise((resolve, reject) => { + this.getKeySystemSelectionPromise(keySystemsToAttempt) + .then(({ keySystem }) => { + const keySystemFormat = keySystemDomainToKeySystemFormat(keySystem); + if (keySystemFormat) { + resolve(keySystemFormat); + } else { + reject( + new Error(`Unable to find format for key-system "${keySystem}"`), + ); + } + }) + .catch(reject); + }); + } + public selectKeySystemFormat(frag: Fragment): Promise { const keyFormats = Object.keys(frag.levelkeys || {}) as KeySystemFormats[]; if (!this.keyFormatPromise) { @@ -400,40 +439,36 @@ class EMEController extends Logger implements ComponentAPI { private getKeyFormatPromise( keyFormats: KeySystemFormats[], ): Promise { - return new Promise((resolve, reject) => { - const keySystemsInConfig = getKeySystemsForConfig(this.config); - const keySystemsToAttempt = keyFormats - .map(keySystemFormatToKeySystemDomain) - .filter( - (value) => !!value && keySystemsInConfig.indexOf(value) !== -1, - ) as any as KeySystems[]; - return this.getKeySystemSelectionPromise(keySystemsToAttempt) - .then(({ keySystem }) => { - const keySystemFormat = keySystemToKeySystemFormat(keySystem); - if (keySystemFormat) { - resolve(keySystemFormat); - } else { - reject( - new Error(`Unable to find format for key-system "${keySystem}"`), - ); - } - }) - .catch(reject); - }); + const keySystemsInConfig = getKeySystemsForConfig(this.config); + const keySystemsToAttempt = keyFormats + .map(keySystemFormatToKeySystemDomain) + .filter( + (value) => !!value && keySystemsInConfig.indexOf(value) !== -1, + ) as any as KeySystems[]; + + return this.selectKeySystem(keySystemsToAttempt); } public loadKey(data: KeyLoadedData): Promise { 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}`, @@ -447,11 +482,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( @@ -460,26 +492,34 @@ class EMEController extends Logger implements ComponentAPI { initData, 'playlist-key', ); - })); + }); - keySessionContextPromise.catch((error) => this.handleError(error)); + keySessionContextPromise.catch((error) => + this.handleError(error, data.frag), + ); + this.keyIdToKeySessionPromise[keyId] = keySessionContextPromise; + + 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, { @@ -547,27 +587,25 @@ class EMEController extends Logger implements ComponentAPI { keySystems = getKeySystemsForConfig(this.config); } const keyFormats = keySystems - .map(keySystemToKeySystemFormat) + .map(keySystemDomainToKeySystemFormat) .filter((k) => !!k) as KeySystemFormats[]; this.keyFormatPromise = this.getKeyFormatPromise(keyFormats); } - this.keyFormatPromise.then((keySystemFormat) => { - const keySystem = keySystemFormatToKeySystemDomain(keySystemFormat); - - let keyId: Uint8Array | null | undefined; - let keySystemDomain: KeySystems | undefined; - - if (initDataType === 'sinf') { - if (keySystem !== KeySystems.FAIRPLAY) { - this.warn( - `Ignoring unexpected "${event.type}" event with init data type: "${initDataType}" for selected key-system ${keySystem}`, + this.keyFormatPromise + .then((keySystemFormat) => { + const keySystem = keySystemFormatToKeySystemDomain(keySystemFormat); + if (initDataType !== 'sinf' || keySystem !== KeySystems.FAIRPLAY) { + this.log( + `Ignoring "${event.type}" event with init data type: "${initDataType}" for selected key-system ${keySystem}`, ); return; } + // Match sinf keyId to playlist skd://keyId= - const json = bin2str(new Uint8Array(initData)); + let keyId: Uint8Array | undefined; try { + const json = bin2str(new Uint8Array(initData)); const sinf = base64Decode(JSON.parse(json).sinf); const tenc = parseSinf(sinf); if (!tenc) { @@ -576,148 +614,60 @@ class EMEController extends Logger implements ComponentAPI { ); } keyId = new Uint8Array(tenc.subarray(8, 24)); - keySystemDomain = KeySystems.FAIRPLAY; } catch (error) { this.warn(`${logMessage} Failed to parse sinf: ${error}`); return; } - } else { - if ( - keySystem !== KeySystems.WIDEVINE && - keySystem !== KeySystems.PLAYREADY - ) { - this.warn( - `Ignoring unexpected "${event.type}" event with init data type: "${initDataType}" for selected key-system ${keySystem}`, - ); - return; - } - // Support Widevine/PlayReady clear-lead key-session creation (otherwise depend on playlist keys) - const psshResults = parseMultiPssh(initData); - - const psshInfos = psshResults.filter( - (pssh): pssh is PsshData => - !!pssh.systemId && - keySystemIdToKeySystemDomain(pssh.systemId) === keySystem, - ); - - if (psshInfos.length > 1) { - this.warn( - `${logMessage} Using first of ${psshInfos.length} pssh found for selected key-system ${keySystem}`, - ); - } - const psshInfo = psshInfos[0]; + const keyIdHex = arrayToHex(keyId); + const { keyIdToKeySessionPromise, mediaKeySessions } = this; + let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex]; - if (!psshInfo) { + for (let i = 0; i < mediaKeySessions.length; i++) { + // Match playlist key + const keyContext = mediaKeySessions[i]; + const decryptdata = keyContext.decryptdata; + if (!decryptdata.keyId) { + continue; + } + const oldKeyIdHex = arrayToHex(decryptdata.keyId); if ( - psshResults.length === 0 || - psshResults.some( - (pssh): pssh is PsshInvalidResult => !pssh.systemId, - ) + keyIdHex === oldKeyIdHex || + decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1 ) { - this.warn(`${logMessage} contains incomplete or invalid pssh data`); - } else { - this.log( - `ignoring ${logMessage} for ${(psshResults as PsshData[]) - .map((pssh) => keySystemIdToKeySystemDomain(pssh.systemId)) - .join(',')} pssh data in favor of playlist keys`, - ); - } - return; - } - - keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId); - if (psshInfo.version === 0 && psshInfo.data) { - if (keySystemDomain === KeySystems.WIDEVINE) { - const offset = psshInfo.data.length - 22; - keyId = new Uint8Array(psshInfo.data.subarray(offset, offset + 16)); - } else if (keySystemDomain === KeySystems.PLAYREADY) { - keyId = parsePlayReadyWRM(psshInfo.data); - } - } - } - - if (!keySystemDomain || !keyId) { - return; - } - - const keyIdHex = Hex.hexDump(keyId); - const { keyIdToKeySessionPromise, mediaKeySessions } = this; - - let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex]; - for (let i = 0; i < mediaKeySessions.length; i++) { - // Match playlist key - const keyContext = mediaKeySessions[i]; - const decryptdata = keyContext.decryptdata; - if (!decryptdata.keyId) { - continue; - } - const oldKeyIdHex = Hex.hexDump(decryptdata.keyId); - if ( - keyIdHex === oldKeyIdHex || - decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1 - ) { - keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex]; - if (decryptdata.pssh) { - break; - } - delete keyIdToKeySessionPromise[oldKeyIdHex]; - decryptdata.pssh = new Uint8Array(initData); - decryptdata.keyId = keyId; - keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = - keySessionContextPromise.then(() => { - return this.generateRequestWithPreferredKeySession( - keyContext, - initDataType, - initData, - 'encrypted-event-key-match', - ); - }); - keySessionContextPromise.catch((error) => this.handleError(error)); - break; - } - } - - if (!keySessionContextPromise) { - if (keySystemDomain !== keySystem) { - this.log( - `Ignoring "${event.type}" event with ${keySystemDomain} init data for selected key-system ${keySystem}`, - ); - return; - } - // "Clear-lead" (misc key not encountered in playlist) - keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = - this.getKeySystemSelectionPromise([keySystemDomain]).then( - ({ keySystem, mediaKeys }) => { - this.throwIfDestroyed(); - - const decryptdata = new LevelKey( - 'ISO-23001-7', - keyIdHex, - keySystemToKeySystemFormat(keySystem) ?? '', - ); - decryptdata.pssh = new Uint8Array(initData); - decryptdata.keyId = keyId; - return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => { - this.throwIfDestroyed(); - const keySessionContext = this.createMediaKeySessionContext({ - decryptdata, - keySystem, - mediaKeys, - }); + keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex]; + if (!keySessionContextPromise) { + continue; + } + if (decryptdata.pssh) { + break; + } + delete keyIdToKeySessionPromise[oldKeyIdHex]; + decryptdata.pssh = new Uint8Array(initData); + decryptdata.keyId = keyId; + keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = + keySessionContextPromise.then(() => { return this.generateRequestWithPreferredKeySession( - keySessionContext, + keyContext, initDataType, initData, - 'encrypted-event-no-match', + 'encrypted-event-key-match', ); }); - }, - ); + keySessionContextPromise.catch((error) => this.handleError(error)); + break; + } + } - keySessionContextPromise.catch((error) => this.handleError(error)); - } - }); + if (!keySessionContextPromise) { + this.handleError( + new Error( + `Key ID ${keyIdHex} not encountered in playlist. Key-system sessions ${mediaKeySessions.length}.`, + ), + ); + } + }) + .catch((error) => this.handleError(error)); }; private onWaitingForKey = (event: Event) => { @@ -728,6 +678,9 @@ class EMEController extends Logger implements ComponentAPI { keySystem: KeySystems, mediaKeys: MediaKeys, ): Promise { + if (this.mediaKeys === mediaKeys) { + return Promise.resolve(); + } const queue = this.setMediaKeysQueue.slice(); this.log(`Setting media-keys for "${keySystem}"`); @@ -735,12 +688,14 @@ class EMEController extends Logger implements ComponentAPI { // can be queued for execution for multiple key sessions. const setMediaKeysPromise = Promise.all(queue).then(() => { if (!this.media) { + this.mediaKeys = null; throw new Error( 'Attempted to set mediaKeys without media element attached', ); } return this.media.setMediaKeys(mediaKeys); }); + this.mediaKeys = mediaKeys; this.setMediaKeysQueue.push(setMediaKeysPromise); return setMediaKeysPromise.then(() => { this.log(`Media-keys set for "${keySystem}"`); @@ -777,7 +732,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; } } @@ -791,7 +746,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 })`, ); @@ -799,7 +754,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; } @@ -820,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 { @@ -832,21 +789,25 @@ 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); + } } }); - context.mediaKeysSession.addEventListener('message', onmessage); - context.mediaKeysSession.addEventListener( + addEventListener(context.mediaKeysSession, 'message', onmessage); + addEventListener( + context.mediaKeysSession, 'keystatuseschange', onkeystatuseschange, ); @@ -855,37 +816,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})`, + ); + } + }, + ); }, ); @@ -893,7 +849,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) => { @@ -902,6 +858,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}`, @@ -910,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; }) @@ -920,6 +878,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 @@ -929,16 +890,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; + } }, ); } @@ -1028,7 +998,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); }) @@ -1061,8 +1031,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, ); @@ -1074,8 +1045,8 @@ class EMEController extends Logger implements ComponentAPI { private unpackPlayReadyKeyMessage( xhr: XMLHttpRequest, - licenseChallenge: Uint8Array, - ): Uint8Array { + licenseChallenge: Uint8Array, + ): Uint8Array { // On Edge, the raw license message is UTF-16-encoded XML. We need // to unpack the Challenge element (base64-encoded string containing the // actual license request) and any HttpHeader elements (sent as request @@ -1122,8 +1093,11 @@ class EMEController extends Logger implements ComponentAPI { xhr: XMLHttpRequest, url: string, keysListItem: MediaKeySessionContext, - licenseChallenge: Uint8Array, - ): Promise<{ xhr: XMLHttpRequest; licenseChallenge: Uint8Array }> { + licenseChallenge: Uint8Array, + ): Promise<{ + xhr: XMLHttpRequest; + licenseChallenge: Uint8Array; + }> { const licenseXhrSetup = this.config.licenseXhrSetup; if (!licenseXhrSetup) { @@ -1134,7 +1108,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( @@ -1146,7 +1120,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; } @@ -1175,7 +1149,7 @@ class EMEController extends Logger implements ComponentAPI { private requestLicense( keySessionContext: MediaKeySessionContext, - licenseChallenge: Uint8Array, + licenseChallenge: Uint8Array, ): Promise { const keyLoadPolicy = this.config.keyLoadPolicy.default; return new Promise((resolve, reject) => { @@ -1184,7 +1158,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) { @@ -1223,6 +1200,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: { @@ -1257,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, @@ -1266,11 +1244,16 @@ class EMEController extends Logger implements ComponentAPI { ); } xhr.send(licenseChallenge); - }, - ); + }) + .catch(reject); }); } + private onDestroying() { + this.unregisterListeners(); + this._clear(); + } + private onMediaAttached( event: Events.MEDIA_ATTACHED, data: MediaAttachedData, @@ -1284,28 +1267,33 @@ class EMEController extends Logger implements ComponentAPI { // keep reference of media this.media = media; - media.removeEventListener('encrypted', this.onMediaEncrypted); - media.removeEventListener('waitingforkey', this.onWaitingForKey); - media.addEventListener('encrypted', this.onMediaEncrypted); - media.addEventListener('waitingforkey', this.onWaitingForKey); + addEventListener(media, 'encrypted', this.onMediaEncrypted); + addEventListener(media, 'waitingforkey', this.onWaitingForKey); } private onMediaDetached() { const media = this.media; if (media) { - media.removeEventListener('encrypted', this.onMediaEncrypted); - media.removeEventListener('waitingforkey', this.onWaitingForKey); + removeEventListener(media, 'encrypted', this.onMediaEncrypted); + removeEventListener(media, 'waitingforkey', this.onWaitingForKey); this.media = null; + this.mediaKeys = null; } } - private _clear(media) { - const mediaKeysList = this.mediaKeySessions; + private _clear() { this._requestLicenseFailureCount = 0; - this.setMediaKeysQueue = []; - this.mediaKeySessions = []; this.keyIdToKeySessionPromise = {}; + this.bannedKeyIds = {}; + if (!this.mediaKeys && !this.mediaKeySessions.length) { + return; + } + const media = this.media; + const mediaKeysList = this.mediaKeySessions.slice(); + this.mediaKeySessions = []; + this.mediaKeys = null; + LevelKey.clearKeyUriToKeyIdMap(); // Close all sessions and remove media keys from the video element. @@ -1316,26 +1304,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}`), + }); + }, + ), ), ) - .then(() => { - if (keySessionCount) { - this.log('finished closing key sessions and clearing media keys'); - mediaKeysList.length = 0; - } - }) .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, @@ -1343,11 +1329,18 @@ class EMEController extends Logger implements ComponentAPI { `Could not close sessions and clear media keys: ${error}`, ), }); + }) + + .then(() => { + if (keySessionCount) { + this.log('finished closing key sessions and clearing media keys'); + } }); } private onManifestLoading() { this.keyFormatPromise = null; + this.bannedKeyIds = {}; } private onManifestLoaded( @@ -1379,10 +1372,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( @@ -1417,13 +1411,14 @@ 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 .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, @@ -1435,7 +1430,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, @@ -1459,4 +1455,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 abd743e9bfd..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, @@ -341,7 +358,7 @@ export default class ErrorController // Search for next level to retry let nextLevel = -1; const { levels, loadLevel, minAutoLevel, maxAutoLevel } = hls; - if (!hls.autoLevelEnabled) { + if (!hls.autoLevelEnabled && !hls.config.preserveManualLevelOnError) { hls.loadLevel = -1; } const fragErrorType = data.frag?.type; @@ -413,10 +430,10 @@ export default class ErrorController )) || (findAudioCodecAlternate && level.audioCodec === levelCandidate.audioCodec) || - (!findAudioCodecAlternate && - level.audioCodec !== levelCandidate.audioCodec) || (findVideoCodecAlternate && - level.codecSet === levelCandidate.codecSet) + level.codecSet === levelCandidate.codecSet) || + (!findAudioCodecAlternate && + level.codecSet !== levelCandidate.codecSet) ) { // For video/audio/subs frag errors find another group ID or fallthrough to redundant fail-over continue; @@ -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/controller/fragment-finders.ts b/src/controller/fragment-finders.ts index 94317304372..508454678e4 100644 --- a/src/controller/fragment-finders.ts +++ b/src/controller/fragment-finders.ts @@ -33,7 +33,6 @@ export function findFragmentByPDT( return null; } - maxFragLookUpTolerance = maxFragLookUpTolerance || 0; for (let seg = 0; seg < fragments.length; ++seg) { const frag = fragments[seg]; if (pdtWithinToleranceTest(PDTValue, maxFragLookUpTolerance, frag)) { @@ -225,28 +224,33 @@ export function findFragWithCC( export function findNearestWithCC( details: LevelDetails | undefined, cc: number, - fragment: MediaFragment, + pos: number, ): MediaFragment | null { if (details) { if (details.startCC <= cc && details.endCC >= cc) { - const start = fragment.start; - const end = fragment.end; let fragments = details.fragments; - if (!fragment.relurl) { - const { fragmentHint } = details; - if (fragmentHint) { - fragments = fragments.concat(fragmentHint); - } + const { fragmentHint } = details; + if (fragmentHint) { + fragments = fragments.concat(fragmentHint); } - return BinarySearch.search(fragments, (candidate) => { - if (candidate.cc < cc || candidate.end <= start) { + let closest: MediaFragment | undefined; + BinarySearch.search(fragments, (candidate) => { + if (candidate.cc < cc) { + return 1; + } + if (candidate.cc > cc) { + return -1; + } + closest = candidate; + if (candidate.end <= pos) { return 1; - } else if (candidate.cc > cc || candidate.start >= end) { + } + if (candidate.start > pos) { return -1; - } else { - return 0; } + return 0; }); + return closest || null; } } return null; diff --git a/src/controller/fragment-tracker.ts b/src/controller/fragment-tracker.ts index 6ffb3b7f87b..af3b0583751 100644 --- a/src/controller/fragment-tracker.ts +++ b/src/controller/fragment-tracker.ts @@ -36,7 +36,7 @@ export class FragmentTracker implements ComponentAPI { | null = Object.create(null); private bufferPadding: number = 0.2; - private hls: Hls; + private hls: Hls | null; private hasGaps: boolean = false; constructor(hls: Hls) { @@ -47,24 +47,30 @@ export class FragmentTracker implements ComponentAPI { private _registerListeners() { const { hls } = this; - hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); - hls.on(Events.BUFFER_APPENDED, this.onBufferAppended, this); - hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this); - hls.on(Events.FRAG_LOADED, this.onFragLoaded, this); + if (hls) { + hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); + hls.on(Events.BUFFER_APPENDED, this.onBufferAppended, this); + hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this); + hls.on(Events.FRAG_LOADED, this.onFragLoaded, this); + } } private _unregisterListeners() { const { hls } = this; - hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); - hls.off(Events.BUFFER_APPENDED, this.onBufferAppended, this); - hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this); - hls.off(Events.FRAG_LOADED, this.onFragLoaded, this); + if (hls) { + hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); + hls.off(Events.BUFFER_APPENDED, this.onBufferAppended, this); + hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this); + hls.off(Events.FRAG_LOADED, this.onFragLoaded, this); + } } public destroy() { this._unregisterListeners(); // @ts-ignore - this.fragments = + this.hls = + // @ts-ignore + this.fragments = // @ts-ignore this.activePartLists = // @ts-ignore @@ -80,19 +86,18 @@ export class FragmentTracker implements ComponentAPI { public getAppendedFrag( position: number, levelType: PlaylistLevelType, - ): Fragment | Part | null { + ): MediaFragment | Part | null { const activeParts = this.activePartLists[levelType]; if (activeParts) { for (let i = activeParts.length; i--; ) { const activePart = activeParts[i]; - if (!activePart) { + if (!activePart as any) { break; } - const appendedPTS = activePart.end; if ( activePart.start <= position && - appendedPTS !== null && - position <= appendedPTS + position <= activePart.end && + activePart.loaded ) { return activePart; } @@ -534,10 +539,12 @@ export class FragmentTracker implements ComponentAPI { function isPartial(fragmentEntity: FragmentEntity): boolean { return ( fragmentEntity.buffered && - (fragmentEntity.body.gap || + !!( + fragmentEntity.body.gap || fragmentEntity.range.video?.partial || fragmentEntity.range.audio?.partial || - fragmentEntity.range.audiovideo?.partial) + fragmentEntity.range.audiovideo?.partial + ) ); } diff --git a/src/controller/gap-controller.ts b/src/controller/gap-controller.ts index 4c77f1aeaf0..5ef6869ce37 100644 --- a/src/controller/gap-controller.ts +++ b/src/controller/gap-controller.ts @@ -13,13 +13,14 @@ import type { InFlightData } from './base-stream-controller'; import type { InFlightFragments } from '../hls'; import type Hls from '../hls'; import type { FragmentTracker } from './fragment-tracker'; -import type { Fragment, MediaFragment } from '../loader/fragment'; +import type { Fragment, MediaFragment, Part } from '../loader/fragment'; import type { SourceBufferName } from '../types/buffer'; import type { BufferAppendedData, MediaAttachedData, MediaDetachingData, } from '../types/events'; +import type { ErrorData } from '../types/events'; import type { BufferInfo } from '../utils/buffer-helper'; export const MAX_START_GAP_JUMP = 2.0; @@ -28,8 +29,8 @@ export const SKIP_BUFFER_RANGE_START = 0.05; const TICK_INTERVAL = 100; export default class GapController extends TaskLoop { - private hls: Hls | null = null; - private fragmentTracker: FragmentTracker | null = null; + private hls: Hls | null; + private fragmentTracker: FragmentTracker | null; private media: HTMLMediaElement | null = null; private mediaSource?: MediaSource; @@ -158,7 +159,7 @@ export default class GapController extends TaskLoop { if (!config) { return; } - const { media, stalled } = this; + const media = this.media; if (!media) { return; } @@ -267,10 +268,10 @@ export default class GapController extends TaskLoop { const maxStartGapJump = isLive ? levelDetails!.targetduration * 2 : MAX_START_GAP_JUMP; - const partialOrGap = fragmentTracker.getPartialFragment(currentTime); - if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) { + const appended = appendedFragAtPosition(currentTime, fragmentTracker); + if (startJump > 0 && (startJump <= maxStartGapJump || appended)) { if (!media.paused) { - this._trySkipBufferHole(partialOrGap); + this._trySkipBufferHole(appended); } return; } @@ -280,14 +281,15 @@ export default class GapController extends TaskLoop { const detectStallWithCurrentTimeMs = config.detectStallWithCurrentTimeMs; const tnow = self.performance.now(); const tWaiting = this.waiting; + let stalled = this.stalled; if (stalled === null) { // Use time of recent "waiting" event if (tWaiting > 0 && tnow - tWaiting < detectStallWithCurrentTimeMs) { - this.stalled = tWaiting; + stalled = this.stalled = tWaiting; } else { this.stalled = tnow; + return; } - return; } const stalledDuration = tnow - stalled; @@ -313,7 +315,7 @@ export default class GapController extends TaskLoop { } // Report stalling after trying to fix this._reportStall(bufferInfo); - if (!this.media || !this.hls) { + if (!this.media || (!this.hls as any)) { return; } } @@ -323,7 +325,7 @@ export default class GapController extends TaskLoop { currentTime, config.maxBufferHole, ); - this._tryFixBufferStall(bufferedWithHoles, stalledDuration); + this._tryFixBufferStall(bufferedWithHoles, stalledDuration, currentTime); } private stallResolved(currentTime: number) { @@ -397,8 +399,13 @@ export default class GapController extends TaskLoop { this.warn(error.message); // Magic number to flush the pipeline without interuption to audio playback: this.media.currentTime += 0.000001; - const frag = - this.fragmentTracker.getPartialFragment(currentTime) || undefined; + let frag: MediaFragment | Part | null | undefined = + appendedFragAtPosition(currentTime, this.fragmentTracker); + if (frag && 'fragment' in frag) { + frag = frag.fragment; + } else if (!frag) { + frag = undefined; + } const bufferInfo = BufferHelper.bufferInfo( this.media, currentTime, @@ -429,6 +436,7 @@ export default class GapController extends TaskLoop { private _tryFixBufferStall( bufferInfo: BufferInfo, stalledDurationMs: number, + currentTime: number, ) { const { fragmentTracker, media } = this; const config = this.hls?.config; @@ -436,16 +444,15 @@ export default class GapController extends TaskLoop { return; } - const currentTime = media.currentTime; const levelDetails = this.hls?.latestLevelDetails; - const partial = fragmentTracker.getPartialFragment(currentTime); + const appended = appendedFragAtPosition(currentTime, fragmentTracker); if ( - partial || + appended || (levelDetails?.live && currentTime < levelDetails.fragmentStart) ) { // Try to skip over the buffer hole caused by a partial fragment // This method isn't limited by the size of the gap between buffered ranges - const targetTime = this._trySkipBufferHole(partial); + const targetTime = this._trySkipBufferHole(appended); // we return here in this case, meaning // the branch below only executes when we haven't seeked to a new position if (targetTime || !this.media) { @@ -458,12 +465,14 @@ export default class GapController extends TaskLoop { // needs to cross some sort of threshold covering all source-buffers content // to start playing properly. const bufferedRanges = bufferInfo.buffered; + const adjacentTraversal = this.adjacentTraversal(bufferInfo, currentTime); if ( ((bufferedRanges && bufferedRanges.length > 1 && bufferInfo.len > config.maxBufferHole) || (bufferInfo.nextStart && - bufferInfo.nextStart - currentTime < config.maxBufferHole)) && + (bufferInfo.nextStart - currentTime < config.maxBufferHole || + adjacentTraversal))) && (stalledDurationMs > config.highBufferWatchdogPeriod * 1000 || this.waiting) ) { @@ -474,6 +483,25 @@ export default class GapController extends TaskLoop { } } + private adjacentTraversal(bufferInfo: BufferInfo, currentTime: number) { + const fragmentTracker = this.fragmentTracker; + const nextStart = bufferInfo.nextStart; + if (fragmentTracker && nextStart) { + const current = fragmentTracker.getFragAtPos( + currentTime, + PlaylistLevelType.MAIN, + ); + const next = fragmentTracker.getFragAtPos( + nextStart, + PlaylistLevelType.MAIN, + ); + if (current && next) { + return next.sn - current.sn < 2; + } + } + return false; + } + /** * Triggers a BUFFER_STALLED_ERROR event, but only once per stall period. * @param bufferLen - The playhead distance from the end of the current buffer segment. @@ -504,10 +532,10 @@ export default class GapController extends TaskLoop { /** * Attempts to fix buffer stalls by jumping over known gaps caused by partial fragments - * @param partial - The partial fragment found at the current time (where playback is stalling). + * @param appended - The fragment or part found at the current time (where playback is stalling). * @private */ - private _trySkipBufferHole(partial: MediaFragment | null): number { + private _trySkipBufferHole(appended: MediaFragment | Part | null): number { const { fragmentTracker, media } = this; const config = this.hls?.config; if (!media || !fragmentTracker || !config) { @@ -537,42 +565,34 @@ export default class GapController extends TaskLoop { startGap = true; } } - if (!startGap) { - const startProvisioned = - partial || - fragmentTracker.getAppendedFrag( - currentTime, - PlaylistLevelType.MAIN, - ); - if (startProvisioned) { - // Do not seek when selected variant playlist is unloaded - if (!this.hls.loadLevelObj?.details) { - return 0; - } - // Do not seek when required fragments are inflight or appending - const inFlightDependency = getInFlightDependency( - this.hls.inFlightFragments, - startTime, - ); - if (inFlightDependency) { - return 0; - } - // Do not seek if we can't walk tracked fragments to end of gap - let moreToLoad = false; - let pos = startProvisioned.end; - while (pos < startTime) { - const provisioned = fragmentTracker.getPartialFragment(pos); - if (provisioned) { - pos += provisioned.duration; - } else { - moreToLoad = true; - break; - } - } - if (moreToLoad) { - return 0; + if (!startGap && appended) { + // Do not seek when selected variant playlist is unloaded + if (!this.hls.loadLevelObj?.details) { + return 0; + } + // Do not seek when required fragments are inflight or appending + const inFlightDependency = getInFlightDependency( + this.hls.inFlightFragments, + startTime, + ); + if (inFlightDependency) { + return 0; + } + // Do not seek if we can't walk tracked fragments to end of gap + let moreToLoad = false; + let pos = appended.end; + while (pos < startTime) { + const provisioned = appendedFragAtPosition(pos, fragmentTracker); + if (provisioned) { + pos += provisioned.duration; + } else { + moreToLoad = true; + break; } } + if (moreToLoad) { + return 0; + } } } const targetTime = Math.max( @@ -584,20 +604,27 @@ export default class GapController extends TaskLoop { ); this.moved = true; media.currentTime = targetTime; - if (!partial?.gap) { + if (!appended?.gap) { const error = new Error( `fragment loaded with buffer holes, seeking from ${currentTime} to ${targetTime}`, ); - this.hls.trigger(Events.ERROR, { + const errorData: ErrorData = { type: ErrorTypes.MEDIA_ERROR, details: ErrorDetails.BUFFER_SEEK_OVER_HOLE, fatal: false, error, reason: error.message, - frag: partial || undefined, buffer: bufferInfo.len, bufferInfo, - }); + }; + if (appended) { + if ('fragment' in appended) { + errorData.part = appended; + } else { + errorData.frag = appended; + } + } + this.hls.trigger(Events.ERROR, errorData); } return targetTime; } @@ -679,3 +706,10 @@ function inFlight(inFlightData: InFlightData | undefined): Fragment | null { } return inFlightData.frag; } + +function appendedFragAtPosition(pos: number, fragmentTracker: FragmentTracker) { + return ( + fragmentTracker.getAppendedFrag(pos, PlaylistLevelType.MAIN) || + fragmentTracker.getPartialFragment(pos) + ); +} diff --git a/src/controller/id3-track-controller.ts b/src/controller/id3-track-controller.ts index 48692d81a7a..9bd02ab6f37 100644 --- a/src/controller/id3-track-controller.ts +++ b/src/controller/id3-track-controller.ts @@ -6,12 +6,10 @@ import { isSCTE35Attribute, } from '../loader/date-range'; 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'; import type { LevelDetails } from '../loader/level-details'; @@ -35,7 +33,7 @@ const MIN_CUE_DURATION = 0.25; function getCueClass(): typeof VTTCue | typeof TextTrackCue | undefined { if (typeof self === 'undefined') return undefined; - return self.VTTCue || self.TextTrackCue; + return (self.VTTCue as typeof VTTCue | undefined) || self.TextTrackCue; } function createCueWithDataFields( @@ -73,28 +71,21 @@ const MAX_CUE_ENDTIME = (() => { return Number.POSITIVE_INFINITY; })(); -function hexToArrayBuffer(str): ArrayBuffer { - return Uint8Array.from( - str - .replace(/^0x/, '') - .replace(/([\da-fA-F]{2}) ?/g, '0x$1 ') - .replace(/ +$/, '') - .split(' '), - ).buffer; -} class ID3TrackController implements ComponentAPI { - private hls: Hls; - private id3Track: TextTrack | null = null; + private hls: Hls | null; + private id3Track: HTMLTrackElement | null = null; private media: HTMLMediaElement | null = null; private dateRangeCuesAppended: Record< string, - { - cues: Record; - dateRange: DateRange; - durationKnown: boolean; - } + | { + cues: Record; + dateRange: DateRange; + durationKnown: boolean; + } + | undefined > = {}; private removeCues: boolean = true; + private assetCue?: VTTCue | TextTrackCue; constructor(hls) { this.hls = hls; @@ -112,26 +103,30 @@ class ID3TrackController implements ComponentAPI { private _registerListeners() { const { hls } = this; - hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); - hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this); - hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); - hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); - hls.on(Events.FRAG_PARSING_METADATA, this.onFragParsingMetadata, this); - hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); - hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this); - hls.on(Events.LEVEL_PTS_UPDATED, this.onLevelPtsUpdated, this); + if (hls) { + hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); + hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this); + hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); + hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); + hls.on(Events.FRAG_PARSING_METADATA, this.onFragParsingMetadata, this); + hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); + hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this); + hls.on(Events.LEVEL_PTS_UPDATED, this.onLevelPtsUpdated, this); + } } private _unregisterListeners() { const { hls } = this; - hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); - hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this); - hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); - hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); - hls.off(Events.FRAG_PARSING_METADATA, this.onFragParsingMetadata, this); - hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); - hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this); - hls.off(Events.LEVEL_PTS_UPDATED, this.onLevelPtsUpdated, this); + if (hls) { + hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); + hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this); + hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); + hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); + hls.off(Events.FRAG_PARSING_METADATA, this.onFragParsingMetadata, this); + hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this); + hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this); + hls.off(Events.LEVEL_PTS_UPDATED, this.onLevelPtsUpdated, this); + } } private onEventCueEnter = () => { @@ -147,13 +142,10 @@ class ID3TrackController implements ComponentAPI { data: MediaAttachingData, ): void { this.media = data.media; - if (data.overrides?.cueRemoval === false) { - this.removeCues = false; - } } private onMediaAttached() { - const details = this.hls.latestLevelDetails; + const details = this.hls?.latestLevelDetails; if (details) { this.updateDateRangeCues(details); } @@ -169,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 = {}; @@ -181,42 +171,19 @@ 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( event: Events.FRAG_PARSING_METADATA, data: FragParsingMetadataData, ) { - if (!this.media) { + if (!this.media || !this.hls) { return; } - const { - hls: { - config: { enableEmsgMetadataCues, enableID3MetadataCues }, - }, - } = this; + const { enableEmsgMetadataCues, enableID3MetadataCues } = this.hls.config; if (!enableEmsgMetadataCues && !enableID3MetadataCues) { return; } @@ -243,35 +210,33 @@ class ID3TrackController implements ComponentAPI { } const frames = getId3Frames(samples[i].data); - if (frames) { - const startTime = samples[i].pts; - let endTime: number = startTime + samples[i].duration; + const startTime = samples[i].pts; + let endTime: number = startTime + samples[i].duration; - if (endTime > MAX_CUE_ENDTIME) { - endTime = MAX_CUE_ENDTIME; - } + if (endTime > MAX_CUE_ENDTIME) { + endTime = MAX_CUE_ENDTIME; + } - const timeDiff = endTime - startTime; - if (timeDiff <= 0) { - endTime = startTime + MIN_CUE_DURATION; - } + const timeDiff = endTime - startTime; + if (timeDiff <= 0) { + endTime = startTime + MIN_CUE_DURATION; + } - for (let j = 0; j < frames.length; j++) { - const frame = frames[j]; - // Safari doesn't put the timestamp frame in the TextTrack - if (!isId3TimestampFrame(frame)) { - // add a bounds to any unbounded cues - this.updateId3CueEnds(startTime, type); - const cue = createCueWithDataFields( - Cue, - startTime, - endTime, - frame, - type, - ); - if (cue) { - this.id3Track.addCue(cue); - } + for (let j = 0; j < frames.length; j++) { + const frame = frames[j]; + // Safari doesn't put the timestamp frame in the TextTrack + if (!isId3TimestampFrame(frame)) { + // add a bounds to any unbounded cues + this.updateId3CueEnds(startTime, type); + const cue = createCueWithDataFields( + Cue, + startTime, + endTime, + frame, + type, + ); + if (cue) { + this.id3Track.track.addCue(cue); } } } @@ -279,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; @@ -322,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); } } @@ -343,11 +308,49 @@ class ID3TrackController implements ComponentAPI { } private updateDateRangeCues(details: LevelDetails, removeOldCues?: true) { + if (!this.hls || !this.media) { + return; + } + const { + assetPlayerId, + timelineOffset, + enableDateRangeMetadataCues, + interstitialsController, + } = this.hls.config; + if (!enableDateRangeMetadataCues) { + return; + } + + const Cue = getCueClass(); if ( - !this.media || - !details.hasProgramDateTime || - !this.hls.config.enableDateRangeMetadataCues + __USE_INTERSTITIALS__ && + assetPlayerId && + timelineOffset && + !interstitialsController ) { + const { fragmentStart, fragmentEnd } = details; + let cue = this.assetCue; + if (cue) { + cue.startTime = fragmentStart; + cue.endTime = fragmentEnd; + } else if (Cue) { + cue = this.assetCue = createCueWithDataFields( + Cue, + fragmentStart, + fragmentEnd, + { assetPlayerId: this.hls.config.assetPlayerId }, + 'hlsjs.interstitial.asset', + ); + if (cue) { + cue.id = assetPlayerId; + this.id3Track ||= this.createTrack(this.media); + this.id3Track.track.addCue(cue); + cue.addEventListener('enter', this.onEventCueEnter); + } + } + } + + if (!details.hasProgramDateTime) { return; } const { id3Track } = this; @@ -356,42 +359,45 @@ 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), ); for (let i = idsToRemove.length; i--; ) { const id = idsToRemove[i]; - const cues = dateRangeCuesAppended[id].cues; + const cues = dateRangeCuesAppended[id]?.cues; delete dateRangeCuesAppended[id]; - Object.keys(cues).forEach((key) => { - try { + if (cues) { + Object.keys(cues).forEach((key) => { const cue = cues[key]; - cue.removeEventListener('enter', this.onEventCueEnter); - id3Track.removeCue(cue); - } catch (e) { - /* no-op */ - } - }); + if (cue) { + cue.removeEventListener('enter', this.onEventCueEnter); + try { + id3Track.track.removeCue(cue); + } catch (e) { + /* no-op */ + } + } + }); + } } } else { dateRangeCuesAppended = this.dateRangeCuesAppended = {}; } } // Exit if the playlist does not have Date Ranges or does not have Program Date Time - const lastFragment = details.fragments[details.fragments.length - 1]; + const lastFragment = details.fragments[details.fragments.length - 1] as + | MediaFragment + | undefined; if (ids.length === 0 || !Number.isFinite(lastFragment?.programDateTime)) { return; } - if (!this.id3Track) { - this.id3Track = this.createTrack(this.media); - } + this.id3Track ||= this.createTrack(this.media); - const Cue = getCueClass(); for (let i = 0; i < ids.length; i++) { const id = ids[i]; - const dateRange = dateRanges[id]; + const dateRange = dateRanges[id]!; const startTime = dateRange.startTime; // Process DateRanges to determine end-time (known DURATION, END-DATE, or END-ON-NEXT) @@ -407,7 +413,7 @@ class ID3TrackController implements ComponentAPI { const nextDateRangeWithSameClass = ids.reduce( (candidateDateRange: DateRange | null, id) => { if (id !== dateRange.id) { - const otherDateRange = dateRanges[id]; + const otherDateRange = dateRanges[id]!; if ( otherDateRange.class === dateRange.class && otherDateRange.startDate > dateRange.startDate && @@ -437,7 +443,7 @@ class ID3TrackController implements ComponentAPI { } const cue = cues[key]; if (cue) { - if (durationKnown && !appendedDateRangeCues.durationKnown) { + if (durationKnown && !appendedDateRangeCues?.durationKnown) { cue.endTime = endTime; } else if (Math.abs(cue.startTime - startTime) > 0.01) { cue.startTime = startTime; @@ -458,12 +464,9 @@ class ID3TrackController implements ComponentAPI { ); if (cue) { cue.id = id; - this.id3Track.addCue(cue); + this.id3Track.track.addCue(cue); cues[key] = cue; - if ( - __USE_INTERSTITIALS__ && - this.hls.config.interstitialsController - ) { + if (__USE_INTERSTITIALS__ && interstitialsController) { if (key === 'X-ASSET-LIST' || key === 'X-ASSET-URL') { cue.addEventListener('enter', this.onEventCueEnter); } diff --git a/src/controller/interstitial-player.ts b/src/controller/interstitial-player.ts index 54893588a6f..7a89633bb38 100644 --- a/src/controller/interstitial-player.ts +++ b/src/controller/interstitial-player.ts @@ -14,15 +14,20 @@ import type Hls from '../hls'; import type { BufferCodecsData, MediaAttachingData } from '../types/events'; export interface InterstitialPlayer { + bufferedEnd: number; currentTime: number; duration: number; assetPlayers: (HlsAssetPlayer | null)[]; playingIndex: number; scheduleItem: InterstitialScheduleEventItem | null; } + +export type HlsAssetPlayerConfig = Partial & + Required>; + export class HlsAssetPlayer { - public readonly hls: Hls; - public readonly interstitial: InterstitialEvent; + public hls: Hls | null; + public interstitial: InterstitialEvent; public readonly assetItem: InterstitialAssetItem; public tracks: Partial | null = null; private hasDetails: boolean = false; @@ -32,21 +37,13 @@ export class HlsAssetPlayer { constructor( HlsPlayerClass: typeof Hls, - userConfig: Partial, + userConfig: HlsAssetPlayerConfig, interstitial: InterstitialEvent, assetItem: InterstitialAssetItem, ) { const hls = (this.hls = new HlsPlayerClass(userConfig)); this.interstitial = interstitial; this.assetItem = assetItem; - let uri: string = assetItem.uri; - try { - uri = getInterstitialUrl(uri, hls.sessionId).href; - } catch (error) { - // Ignore error parsing ASSET_URI or adding _HLS_primary_id to it. The - // issue should surface as an INTERSTITIAL_ASSET_ERROR loading the asset. - } - hls.loadSource(uri); const detailsLoaded = () => { this.hasDetails = true; }; @@ -59,35 +56,71 @@ export class HlsAssetPlayer { const event = this.interstitial; if (event.playoutLimit) { media.addEventListener('timeupdate', this.checkPlayout); + if (this.appendInPlace) { + hls.on(Events.BUFFER_APPENDED, () => { + const bufferedEnd = this.bufferedEnd; + if (this.reachedPlayout(bufferedEnd)) { + this._bufferedEosTime = bufferedEnd; + hls.trigger(Events.BUFFERED_TO_END, undefined); + } + }); + } } }); } + get appendInPlace(): boolean { + return this.interstitial.appendInPlace; + } + + loadSource() { + const hls = this.hls; + if (!hls) { + return; + } + if (!hls.url) { + let uri: string = this.assetItem.uri; + try { + uri = getInterstitialUrl(uri, hls.config.primarySessionId || '').href; + } catch (error) { + // Ignore error parsing ASSET_URI or adding _HLS_primary_id to it. The + // issue should surface as an INTERSTITIAL_ASSET_ERROR loading the asset. + } + hls.loadSource(uri); + } else if (hls.levels.length && !(hls as any).started) { + hls.startLoad(-1, true); + } + } + bufferedInPlaceToEnd(media?: HTMLMediaElement | null) { - if (!this.interstitial.appendInPlace) { + if (!this.appendInPlace) { return false; } if (this.hls?.bufferedToEnd) { return true; } - if (!media || !this._bufferedEosTime) { + if (!media) { return false; } + const duration = Math.min(this._bufferedEosTime || Infinity, this.duration); const start = this.timelineOffset; const bufferInfo = BufferHelper.bufferInfo(media, start, 0); const bufferedEnd = this.getAssetTime(bufferInfo.end); - return bufferedEnd >= this._bufferedEosTime - 0.02; + return bufferedEnd >= duration - 0.02; } private checkPlayout = () => { - const interstitial = this.interstitial; - const playoutLimit = interstitial.playoutLimit; - const currentTime = this.currentTime; - if (this.startOffset + currentTime >= playoutLimit) { + if (this.reachedPlayout(this.currentTime) && this.hls) { this.hls.trigger(Events.PLAYOUT_LIMIT_REACHED, {}); } }; + private reachedPlayout(time: number): boolean { + const interstitial = this.interstitial; + const playoutLimit = interstitial.playoutLimit; + return this.startOffset + time >= playoutLimit; + } + get destroyed(): boolean { return !this.hls?.userConfig; } @@ -129,6 +162,13 @@ export class HlsAssetPlayer { if (!duration) { return 0; } + const playoutLimit = this.interstitial.playoutLimit; + if (playoutLimit) { + const assetPlayout = playoutLimit - this.startOffset; + if (assetPlayout > 0 && assetPlayout < duration) { + return assetPlayout; + } + } return duration; } @@ -152,7 +192,7 @@ export class HlsAssetPlayer { const timelineOffset = this.timelineOffset; if (value !== timelineOffset) { const diff = value - timelineOffset; - if (Math.abs(diff) > 1 / 90000) { + if (Math.abs(diff) > 1 / 90000 && this.hls) { if (this.hasDetails) { throw new Error( `Cannot set timelineOffset after playlists are loaded`, @@ -188,34 +228,48 @@ export class HlsAssetPlayer { destroy() { this.removeMediaListeners(); - this.hls.destroy(); - // @ts-ignore - this.hls = this.interstitial = null; + if (this.hls) { + this.hls.destroy(); + } + this.hls = null; // @ts-ignore this.tracks = this.mediaAttached = this.checkPlayout = null; } attachMedia(data: HTMLMediaElement | MediaAttachingData) { - this.hls.attachMedia(data); + this.loadSource(); + this.hls?.attachMedia(data); } detachMedia() { this.removeMediaListeners(); this.mediaAttached = null; - this.hls.detachMedia(); + this.hls?.detachMedia(); } resumeBuffering() { - this.hls.resumeBuffering(); + this.hls?.resumeBuffering(); } pauseBuffering() { - this.hls.pauseBuffering(); + this.hls?.pauseBuffering(); } transferMedia() { this.bufferSnapShot(); - return this.hls.transferMedia(); + return this.hls?.transferMedia() || null; + } + + resetDetails() { + const hls = this.hls; + if (hls && this.hasDetails) { + hls.stopLoad(); + const deleteDetails = (obj) => delete obj.details; + hls.levels.forEach(deleteDetails); + hls.allAudioTracks.forEach(deleteDetails); + hls.allSubtitleTracks.forEach(deleteDetails); + this.hasDetails = false; + } } on( @@ -223,7 +277,7 @@ export class HlsAssetPlayer { listener: HlsListeners[E], context?: Context, ) { - this.hls.on(event, listener); + this.hls?.on(event, listener); } once( @@ -231,7 +285,7 @@ export class HlsAssetPlayer { listener: HlsListeners[E], context?: Context, ) { - this.hls.once(event, listener); + this.hls?.once(event, listener); } off( @@ -239,10 +293,10 @@ export class HlsAssetPlayer { listener: HlsListeners[E], context?: Context, ) { - this.hls.off(event, listener); + this.hls?.off(event, listener); } toString(): string { - return `HlsAssetPlayer: ${eventAssetToString(this.assetItem)} ${this.hls?.sessionId} ${this.interstitial?.appendInPlace ? 'append-in-place' : ''}`; + return `HlsAssetPlayer: ${eventAssetToString(this.assetItem)} ${this.hls?.sessionId} ${this.appendInPlace ? 'append-in-place' : ''}`; } } diff --git a/src/controller/interstitials-controller.ts b/src/controller/interstitials-controller.ts index ff05896211b..594707dc87c 100644 --- a/src/controller/interstitials-controller.ts +++ b/src/controller/interstitials-controller.ts @@ -15,6 +15,7 @@ import { ALIGNED_END_THRESHOLD_SECONDS, eventAssetToString, generateAssetIdentifier, + getNextAssetIndex, type InterstitialAssetId, type InterstitialAssetItem, type InterstitialEvent, @@ -31,8 +32,10 @@ import { Logger } from '../utils/logger'; import { isCompatibleTrackChange } from '../utils/mediasource-helper'; import { getBasicSelectionOption } from '../utils/rendition-helper'; import { stringify } from '../utils/safe-json-stringify'; -import type { InterstitialPlayer } from './interstitial-player'; -import type { HlsConfig } from '../config'; +import type { + HlsAssetPlayerConfig, + InterstitialPlayer, +} from './interstitial-player'; import type Hls from '../hls'; import type { LevelDetails } from '../loader/level-details'; import type { SourceBufferName } from '../types/buffer'; @@ -83,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 @@ -114,7 +121,7 @@ export default class InterstitialsController private timelinePos: number = -1; // Schedule - private schedule: InterstitialsSchedule; + private schedule: InterstitialsSchedule | null; // Schedule playback and buffering state private playingItem: InterstitialScheduleItem | null = null; @@ -140,48 +147,51 @@ export default class InterstitialsController private registerListeners() { const hls = this.hls; - hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); - hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this); - hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); - hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); - hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this); - hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this); - hls.on(Events.AUDIO_TRACK_UPDATED, this.onAudioTrackUpdated, this); - hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this); - hls.on(Events.SUBTITLE_TRACK_UPDATED, this.onSubtitleTrackUpdated, this); - hls.on(Events.EVENT_CUE_ENTER, this.onInterstitialCueEnter, this); - hls.on(Events.ASSET_LIST_LOADED, this.onAssetListLoaded, this); - hls.on(Events.BUFFER_APPENDED, this.onBufferAppended, this); - hls.on(Events.BUFFER_FLUSHED, this.onBufferFlushed, this); - hls.on(Events.BUFFERED_TO_END, this.onBufferedToEnd, this); - hls.on(Events.MEDIA_ENDED, this.onMediaEnded, this); - hls.on(Events.ERROR, this.onError, this); - hls.on(Events.DESTROYING, this.onDestroying, this); + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (hls) { + hls.on(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); + hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this); + hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this); + hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this); + hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this); + hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this); + hls.on(Events.AUDIO_TRACK_UPDATED, this.onAudioTrackUpdated, this); + hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this); + hls.on(Events.SUBTITLE_TRACK_UPDATED, this.onSubtitleTrackUpdated, this); + hls.on(Events.EVENT_CUE_ENTER, this.onInterstitialCueEnter, this); + hls.on(Events.ASSET_LIST_LOADED, this.onAssetListLoaded, this); + hls.on(Events.BUFFER_APPENDED, this.onBufferAppended, this); + hls.on(Events.BUFFER_FLUSHED, this.onBufferFlushed, this); + hls.on(Events.BUFFERED_TO_END, this.onBufferedToEnd, this); + hls.on(Events.MEDIA_ENDED, this.onMediaEnded, this); + hls.on(Events.ERROR, this.onError, this); + hls.on(Events.DESTROYING, this.onDestroying, this); + } } private unregisterListeners() { const hls = this.hls; - if (!hls) { - return; + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (hls) { + hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); + hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this); + hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); + hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); + hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this); + hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this); + hls.off(Events.AUDIO_TRACK_UPDATED, this.onAudioTrackUpdated, this); + hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this); + hls.off(Events.SUBTITLE_TRACK_UPDATED, this.onSubtitleTrackUpdated, this); + hls.off(Events.EVENT_CUE_ENTER, this.onInterstitialCueEnter, this); + hls.off(Events.ASSET_LIST_LOADED, this.onAssetListLoaded, this); + hls.off(Events.BUFFER_CODECS, this.onBufferCodecs, this); + hls.off(Events.BUFFER_APPENDED, this.onBufferAppended, this); + hls.off(Events.BUFFER_FLUSHED, this.onBufferFlushed, this); + hls.off(Events.BUFFERED_TO_END, this.onBufferedToEnd, this); + hls.off(Events.MEDIA_ENDED, this.onMediaEnded, this); + hls.off(Events.ERROR, this.onError, this); + hls.off(Events.DESTROYING, this.onDestroying, this); } - hls.off(Events.MEDIA_ATTACHING, this.onMediaAttaching, this); - hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this); - hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this); - hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this); - hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this); - hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this); - hls.off(Events.AUDIO_TRACK_UPDATED, this.onAudioTrackUpdated, this); - hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this); - hls.off(Events.SUBTITLE_TRACK_UPDATED, this.onSubtitleTrackUpdated, this); - hls.off(Events.EVENT_CUE_ENTER, this.onInterstitialCueEnter, this); - hls.off(Events.ASSET_LIST_LOADED, this.onAssetListLoaded, this); - hls.off(Events.BUFFER_CODECS, this.onBufferCodecs, this); - hls.off(Events.BUFFER_APPENDED, this.onBufferAppended, this); - hls.off(Events.BUFFER_FLUSHED, this.onBufferFlushed, this); - hls.off(Events.BUFFERED_TO_END, this.onBufferedToEnd, this); - hls.off(Events.MEDIA_ENDED, this.onMediaEnded, this); - hls.off(Events.ERROR, this.onError, this); - hls.off(Events.DESTROYING, this.onDestroying, this); } startLoad() { @@ -205,6 +215,7 @@ export default class InterstitialsController destroy() { this.unregisterListeners(); this.stopLoad(); + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (this.assetListLoader) { this.assetListLoader.destroy(); } @@ -218,10 +229,11 @@ export default class InterstitialsController this.mediaSelection = this.requiredTracks = this.altSelection = + this.schedule = this.manager = null; // @ts-ignore - this.hls = this.HlsPlayerClass = this.schedule = this.log = null; + this.hls = this.HlsPlayerClass = this.log = null; // @ts-ignore this.assetListLoader = null; // @ts-ignore @@ -273,6 +285,7 @@ export default class InterstitialsController } private clearScheduleState() { + this.log(`clear schedule state`); this.playingItem = this.bufferingItem = this.waitingItem = @@ -300,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 = @@ -314,345 +328,366 @@ export default class InterstitialsController } public get interstitialsManager(): InterstitialsManager | null { - if (!this.manager) { - if (!this.hls) { - return null; - } - const c = this; - const effectiveBufferingItem = () => c.bufferingItem || c.waitingItem; - const getAssetPlayer = (asset: InterstitialAssetItem | null) => - asset ? c.getAssetPlayer(asset.identifier) : asset; - const getMappedTime = ( - item: InterstitialScheduleItem | null, - timelineType: TimelineType, - asset: InterstitialAssetItem | null, - controllerField: 'bufferedPos' | 'timelinePos', - assetPlayerField: 'bufferedEnd' | 'currentTime', - ) => { - if (item) { - let time = item[timelineType].start; - const interstitial = item.event; - if (interstitial) { - if ( - timelineType === 'playout' || - interstitial.timelineOccupancy !== TimelineOccupancy.Point - ) { - const assetPlayer = getAssetPlayer(asset); - if (assetPlayer?.interstitial === interstitial) { - time += - assetPlayer.assetItem.startOffset + - assetPlayer[assetPlayerField]; - } - } - } else { - const value = - controllerField === 'bufferedPos' - ? getBufferedEnd() - : c[controllerField]; - time += value - item.start; + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (!this.hls) { + return null; + } + if (this.manager) { + return this.manager; + } + const c = this; + const effectiveBufferingItem = () => c.bufferingItem || c.waitingItem; + const getAssetPlayer = (asset: InterstitialAssetItem | null) => + asset ? c.getAssetPlayer(asset.identifier) : asset; + const getMappedTime = ( + item: InterstitialScheduleItem | null, + timelineType: TimelineType, + asset: InterstitialAssetItem | null, + controllerField: 'bufferedPos' | 'timelinePos', + assetPlayerField: 'bufferedEnd' | 'currentTime', + ): number => { + if (item) { + let time = ( + item[timelineType] as { + start: number; + end: number; } - return time; - } - return 0; - }; - const findMappedTime = ( - primaryTime: number, - timelineType: TimelineType, - ): number => { - if ( - primaryTime !== 0 && - timelineType !== 'primary' && - c.schedule.length - ) { - const index = c.schedule.findItemIndexAtTime(primaryTime); - const item = c.schedule.items?.[index]; - if (item) { - const diff = item[timelineType].start - item.start; - return primaryTime + diff; + ).start; + const interstitial = item.event; + if (interstitial) { + if ( + timelineType === 'playout' || + interstitial.timelineOccupancy !== TimelineOccupancy.Point + ) { + const assetPlayer = getAssetPlayer(asset); + if (assetPlayer?.interstitial === interstitial) { + time += + assetPlayer.assetItem.startOffset + + assetPlayer[assetPlayerField]; + } } + } else { + const value = + controllerField === 'bufferedPos' + ? getBufferedEnd() + : c[controllerField]; + time += value - item.start; } - return primaryTime; - }; - const getBufferedEnd = (): number => { - const value = c.bufferedPos; - if (value === Number.MAX_VALUE) { - return getMappedDuration('primary'); + return time; + } + return 0; + }; + const findMappedTime = ( + primaryTime: number, + timelineType: TimelineType, + ): number => { + if ( + primaryTime !== 0 && + timelineType !== 'primary' && + c.schedule?.length + ) { + const index = c.schedule.findItemIndexAtTime(primaryTime); + const item = c.schedule.items?.[index]; + if (item) { + const diff = item[timelineType].start - item.start; + return primaryTime + diff; } - return Math.max(value, 0); - }; - const getMappedDuration = (timelineType: TimelineType): number => { - if (c.primaryDetails?.live) { - // return end of last event item or playlist - return c.primaryDetails.edge; + } + return primaryTime; + }; + const getBufferedEnd = (): number => { + const value = c.bufferedPos; + if (value === Number.MAX_VALUE) { + return getMappedDuration('primary'); + } + return Math.max(value, 0); + }; + const getMappedDuration = (timelineType: TimelineType): number => { + if (c.primaryDetails?.live) { + // return end of last event item or playlist + return c.primaryDetails.edge; + } + return c.schedule?.durations[timelineType] || 0; + }; + const seekTo = (time: number, timelineType: TimelineType) => { + const item = c.effectivePlayingItem; + if (item?.event?.restrictions.skip || !c.schedule) { + return; + } + c.log(`seek to ${time} "${timelineType}"`); + const playingItem = c.effectivePlayingItem; + const targetIndex = c.schedule.findItemIndexAtTime(time, timelineType); + const targetItem = c.schedule.items?.[targetIndex]; + const bufferingPlayer = c.getBufferingPlayer(); + const bufferingInterstitial = bufferingPlayer?.interstitial; + const appendInPlace = bufferingInterstitial?.appendInPlace; + const seekInItem = playingItem && c.itemsMatch(playingItem, targetItem); + if (playingItem && (appendInPlace || seekInItem)) { + // seek in asset player or primary media (appendInPlace) + const assetPlayer = getAssetPlayer(c.playingAsset); + const media = assetPlayer?.media || c.primaryMedia; + if (media) { + const currentTime = + timelineType === 'primary' + ? media.currentTime + : getMappedTime( + playingItem, + timelineType, + c.playingAsset, + 'timelinePos', + 'currentTime', + ); + + const diff = time - currentTime; + const seekToTime = + (appendInPlace ? currentTime : media.currentTime) + diff; + if ( + seekToTime >= 0 && + (!assetPlayer || + appendInPlace || + seekToTime <= assetPlayer.duration) + ) { + media.currentTime = seekToTime; + return; + } } - return c.schedule.durations[timelineType]; - }; - const seekTo = (time: number, timelineType: TimelineType) => { - const item = c.effectivePlayingItem; - if (item?.event?.restrictions.skip) { - return; + } + // seek out of item or asset + if (targetItem) { + let seekToTime = time; + if (timelineType !== 'primary') { + const primarySegmentStart = targetItem[timelineType].start; + const diff = time - primarySegmentStart; + seekToTime = targetItem.start + diff; } - c.log(`seek to ${time} "${timelineType}"`); - const playingItem = c.effectivePlayingItem; - const targetIndex = c.schedule.findItemIndexAtTime(time, timelineType); - const targetItem = c.schedule.items?.[targetIndex]; - const bufferingPlayer = c.getBufferingPlayer(); - const bufferingInterstitial = bufferingPlayer?.interstitial; - const appendInPlace = bufferingInterstitial?.appendInPlace; - const seekInItem = playingItem && c.itemsMatch(playingItem, targetItem); - if (playingItem && (appendInPlace || seekInItem)) { - // seek in asset player or primary media (appendInPlace) - const assetPlayer = getAssetPlayer(c.playingAsset); - const media = assetPlayer?.media || c.primaryMedia; + const targetIsPrimary = !c.isInterstitial(targetItem); + if ( + (!c.isInterstitial(playingItem) || playingItem.event.appendInPlace) && + (targetIsPrimary || targetItem.event.appendInPlace) + ) { + const media = + c.media || (appendInPlace ? bufferingPlayer?.media : null); if (media) { - const currentTime = - timelineType === 'primary' - ? media.currentTime - : getMappedTime( - playingItem, - timelineType, - c.playingAsset, - 'timelinePos', - 'currentTime', - ); - - const diff = time - currentTime; - const seekToTime = - (appendInPlace ? currentTime : media.currentTime) + diff; - if ( - seekToTime >= 0 && - (!assetPlayer || - appendInPlace || - seekToTime <= assetPlayer.duration) - ) { - media.currentTime = seekToTime; + media.currentTime = seekToTime; + } + } else if (playingItem) { + // check if an Interstitial between the current item and target item has an X-RESTRICT JUMP restriction + const playingIndex = c.findItemIndex(playingItem); + if (targetIndex > playingIndex) { + const jumpIndex = c.schedule.findJumpRestrictedIndex( + playingIndex + 1, + targetIndex, + ); + if (jumpIndex > playingIndex) { + c.setSchedulePosition(jumpIndex); return; } } - } - // seek out of item or asset - if (targetItem) { - let seekToTime = time; - if (timelineType !== 'primary') { - const primarySegmentStart = targetItem[timelineType].start; - const diff = time - primarySegmentStart; - seekToTime = targetItem.start + diff; - } - const targetIsPrimary = !c.isInterstitial(targetItem); - if ( - (!c.isInterstitial(playingItem) || - playingItem.event.appendInPlace) && - (targetIsPrimary || targetItem.event.appendInPlace) - ) { - const media = - c.media || (appendInPlace ? bufferingPlayer?.media : null); - if (media) { - media.currentTime = seekToTime; - } - } else if (playingItem) { - // check if an Interstitial between the current item and target item has an X-RESTRICT JUMP restriction - const playingIndex = c.findItemIndex(playingItem); - if (targetIndex > playingIndex) { - const jumpIndex = c.schedule.findJumpRestrictedIndex( - playingIndex + 1, - targetIndex, - ); - if (jumpIndex > playingIndex) { - c.setSchedulePosition(jumpIndex); - return; - } - } - let assetIndex = 0; - if (targetIsPrimary) { - c.timelinePos = seekToTime; - c.checkBuffer(); - } else { - const assetList = targetItem?.event?.assetList; - if (assetList) { - const eventTime = - time - (targetItem[timelineType] || targetItem).start; - for (let i = assetList.length; i--; ) { - const asset = assetList[i]; - if ( - asset.duration && - eventTime >= asset.startOffset && - eventTime < asset.startOffset + asset.duration - ) { - assetIndex = i; - break; - } - } + let assetIndex = 0; + if (targetIsPrimary) { + c.timelinePos = seekToTime; + c.checkBuffer(); + } else { + const assetList = targetItem.event.assetList; + const eventTime = + time - (targetItem[timelineType] || targetItem).start; + for (let i = assetList.length; i--; ) { + const asset = assetList[i]; + if ( + asset.duration && + eventTime >= asset.startOffset && + eventTime < asset.startOffset + asset.duration + ) { + assetIndex = i; + break; } } - c.setSchedulePosition(targetIndex, assetIndex); } + c.setSchedulePosition(targetIndex, assetIndex); } - }; - const getActiveInterstitial = () => { + } + }; + const getActiveInterstitial = () => { + const playingItem = c.effectivePlayingItem; + if (c.isInterstitial(playingItem)) { + return playingItem; + } + const bufferingItem = effectiveBufferingItem(); + if (c.isInterstitial(bufferingItem)) { + return bufferingItem; + } + return null; + }; + const interstitialPlayer: InterstitialPlayer = { + get bufferedEnd() { + const interstitialItem = effectiveBufferingItem(); + const bufferingItem = c.bufferingItem; + if (bufferingItem && bufferingItem === interstitialItem) { + return ( + getMappedTime( + bufferingItem, + 'playout', + c.bufferingAsset, + 'bufferedPos', + 'bufferedEnd', + ) - bufferingItem.playout.start || + c.bufferingAsset?.startOffset || + 0 + ); + } + return 0; + }, + get currentTime() { + const interstitialItem = getActiveInterstitial(); const playingItem = c.effectivePlayingItem; - if (c.isInterstitial(playingItem)) { - return playingItem; + if (playingItem && playingItem === interstitialItem) { + return ( + getMappedTime( + playingItem, + 'playout', + c.effectivePlayingAsset, + 'timelinePos', + 'currentTime', + ) - playingItem.playout.start + ); + } + return 0; + }, + set currentTime(time: number) { + const interstitialItem = getActiveInterstitial(); + const playingItem = c.effectivePlayingItem; + if (playingItem && playingItem === interstitialItem) { + seekTo(time + playingItem.playout.start, 'playout'); + } + }, + get duration() { + const interstitialItem = getActiveInterstitial(); + if (interstitialItem) { + return interstitialItem.playout.end - interstitialItem.playout.start; } - const bufferingItem = effectiveBufferingItem(); - if (c.isInterstitial(bufferingItem)) { - return bufferingItem; + return 0; + }, + get assetPlayers() { + const assetList = getActiveInterstitial()?.event.assetList; + if (assetList) { + return assetList.map((asset) => c.getAssetPlayer(asset.identifier)); + } + return []; + }, + get playingIndex() { + const interstitial = getActiveInterstitial()?.event; + if (interstitial && c.effectivePlayingAsset) { + return interstitial.findAssetIndex(c.effectivePlayingAsset); + } + return -1; + }, + get scheduleItem() { + return getActiveInterstitial(); + }, + }; + return (this.manager = { + get events() { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + return c.schedule?.events?.slice(0) || []; + }, + get schedule() { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + return c.schedule?.items?.slice(0) || []; + }, + get interstitialPlayer() { + if (getActiveInterstitial()) { + return interstitialPlayer; } return null; - }; - const interstitialPlayer: InterstitialPlayer = { + }, + get playerQueue() { + return c.playerQueue.slice(0); + }, + get bufferingAsset() { + return c.bufferingAsset; + }, + get bufferingItem() { + return effectiveBufferingItem(); + }, + get bufferingIndex() { + const item = effectiveBufferingItem(); + return c.findItemIndex(item); + }, + get playingAsset() { + return c.effectivePlayingAsset; + }, + get playingItem() { + return c.effectivePlayingItem; + }, + get playingIndex() { + const item = c.effectivePlayingItem; + return c.findItemIndex(item); + }, + primary: { + get bufferedEnd() { + return getBufferedEnd(); + }, get currentTime() { - const interstitialItem = getActiveInterstitial(); - const playingItem = c.effectivePlayingItem; - if (playingItem && playingItem === interstitialItem) { - return ( - getMappedTime( - playingItem, - 'playout', - c.effectivePlayingAsset, - 'timelinePos', - 'currentTime', - ) - playingItem.playout.start - ); - } - return 0; + const timelinePos = c.timelinePos; + return timelinePos > 0 ? timelinePos : 0; }, set currentTime(time: number) { - const interstitialItem = getActiveInterstitial(); - const playingItem = c.effectivePlayingItem; - if (playingItem && playingItem === interstitialItem) { - seekTo(time + playingItem.playout.start, 'playout'); - } + seekTo(time, 'primary'); }, get duration() { - const interstitialItem = getActiveInterstitial(); - if (interstitialItem) { - return ( - interstitialItem.playout.end - interstitialItem.playout.start - ); - } - return 0; - }, - get assetPlayers() { - const assetList = getActiveInterstitial()?.event.assetList; - if (assetList) { - return assetList.map((asset) => c.getAssetPlayer(asset.identifier)); - } - return []; - }, - get playingIndex() { - const interstitial = getActiveInterstitial()?.event; - if (interstitial && c.effectivePlayingAsset) { - return interstitial.findAssetIndex(c.effectivePlayingAsset); - } - return -1; - }, - get scheduleItem() { - return getActiveInterstitial(); - }, - }; - this.manager = { - get events() { - return c.schedule?.events?.slice(0) || []; - }, - get schedule() { - return c.schedule?.items?.slice(0) || []; - }, - get interstitialPlayer() { - if (getActiveInterstitial()) { - return interstitialPlayer; - } - return null; - }, - get playerQueue() { - return c.playerQueue.slice(0); - }, - get bufferingAsset() { - return c.bufferingAsset; - }, - get bufferingItem() { - return effectiveBufferingItem(); + return getMappedDuration('primary'); }, - get bufferingIndex() { - const item = effectiveBufferingItem(); - return c.findItemIndex(item); + get seekableStart() { + return c.primaryDetails?.fragmentStart || 0; }, - get playingAsset() { - return c.effectivePlayingAsset; + }, + integrated: { + get bufferedEnd() { + return getMappedTime( + effectiveBufferingItem(), + 'integrated', + c.bufferingAsset, + 'bufferedPos', + 'bufferedEnd', + ); }, - get playingItem() { - return c.effectivePlayingItem; + get currentTime() { + return getMappedTime( + c.effectivePlayingItem, + 'integrated', + c.effectivePlayingAsset, + 'timelinePos', + 'currentTime', + ); }, - get playingIndex() { - const item = c.effectivePlayingItem; - return c.findItemIndex(item); + set currentTime(time: number) { + seekTo(time, 'integrated'); }, - primary: { - get bufferedEnd() { - return getBufferedEnd(); - }, - get currentTime() { - const timelinePos = c.timelinePos; - return timelinePos > 0 ? timelinePos : 0; - }, - set currentTime(time: number) { - seekTo(time, 'primary'); - }, - get duration() { - return getMappedDuration('primary'); - }, - get seekableStart() { - return c.primaryDetails?.fragmentStart || 0; - }, + get duration() { + return getMappedDuration('integrated'); }, - integrated: { - get bufferedEnd() { - return getMappedTime( - effectiveBufferingItem(), - 'integrated', - c.bufferingAsset, - 'bufferedPos', - 'bufferedEnd', - ); - }, - get currentTime() { - return getMappedTime( - c.effectivePlayingItem, - 'integrated', - c.effectivePlayingAsset, - 'timelinePos', - 'currentTime', - ); - }, - set currentTime(time: number) { - seekTo(time, 'integrated'); - }, - get duration() { - return getMappedDuration('integrated'); - }, - get seekableStart() { - return findMappedTime( - c.primaryDetails?.fragmentStart || 0, - 'integrated', - ); - }, + get seekableStart() { + return findMappedTime( + c.primaryDetails?.fragmentStart || 0, + 'integrated', + ); }, - skip: () => { - const item = c.effectivePlayingItem; - const event = item?.event; - if (event && !event.restrictions.skip) { - const index = c.findItemIndex(item); - if (event.appendInPlace) { - const time = item.playout.start + item.event.duration; - seekTo(time + 0.001, 'playout'); - } else { - c.advanceAfterAssetEnded(event, index, Infinity); - } + }, + skip: () => { + const item = c.effectivePlayingItem; + const event = item?.event; + if (event && !event.restrictions.skip) { + const index = c.findItemIndex(item); + if (event.appendInPlace) { + const time = item.playout.start + item.event.duration; + seekTo(time + 0.001, 'playout'); + } else { + c.advanceAfterAssetEnded(event, index, Infinity); } - }, - }; - } - return this.manager; + } + }, + }); } // Schedule getters @@ -802,8 +837,9 @@ export default class InterstitialsController ) { const interstitial = queuedPlayer.interstitial; this.clearInterstitial(queuedPlayer.interstitial, null); - interstitial.appendInPlace = false; - if (interstitial.appendInPlace) { + interstitial.appendInPlace = false; // setter may be a no-op; + // `appendInPlace` getter may still return `true` after insterstitial streaming has begun in that mode. + if (interstitial.appendInPlace as boolean) { this.warn( `Could not change append strategy for queued assets ${interstitial}`, ); @@ -824,17 +860,17 @@ export default class InterstitialsController this.log( `${transferring ? 'transfering MediaSource' : 'attaching media'} to ${ isAssetPlayer ? player : 'Primary' - } from ${logFromSource}`, + } from ${logFromSource} (media.currentTime: ${media.currentTime})`, ); - if (dataToAttach === attachMediaSourceData) { + const schedule = this.schedule; + if (dataToAttach === attachMediaSourceData && schedule) { const isAssetAtEndOfSchedule = isAssetPlayer && - (player as HlsAssetPlayer).assetId === this.schedule.assetIdAtEnd; + (player as HlsAssetPlayer).assetId === schedule.assetIdAtEnd; // Prevent asset players from marking EoS on transferred MediaSource dataToAttach.overrides = { - duration: this.schedule.duration, + duration: schedule.duration, endOfStream: !isAssetPlayer || isAssetAtEndOfSchedule, - cueRemoval: !isAssetPlayer, }; } player.attachMedia(dataToAttach); @@ -850,7 +886,7 @@ export default class InterstitialsController private onSeeking = () => { const currentTime = this.currentTime; - if (currentTime === undefined || this.playbackDisabled) { + if (currentTime === undefined || this.playbackDisabled || !this.schedule) { return; } const diff = currentTime - this.timelinePos; @@ -874,7 +910,7 @@ export default class InterstitialsController currentTime - diff, ); if (resetCount) { - this.updateSchedule(); + this.updateSchedule(true); } } this.checkBuffer(); @@ -882,13 +918,19 @@ export default class InterstitialsController (backwardSeek && currentTime < playingItem.start) || currentTime >= playingItem.end ) { - const scheduleIndex = this.schedule.findItemIndexAtTime(this.timelinePos); + const playingIndex = this.findItemIndex(playingItem); + let scheduleIndex = this.schedule.findItemIndexAtTime(currentTime); + if (scheduleIndex === -1) { + scheduleIndex = playingIndex + (backwardSeek ? -1 : 1); + this.log( + `seeked ${backwardSeek ? 'back ' : ''}to position not covered by schedule ${currentTime} (resolving from ${playingIndex} to ${scheduleIndex})`, + ); + } if (!this.isInterstitial(playingItem) && this.media?.paused) { this.shouldPlay = false; } if (!backwardSeek) { // check if an Interstitial between the current item and target item has an X-RESTRICT JUMP restriction - const playingIndex = this.findItemIndex(playingItem); if (scheduleIndex > playingIndex) { const jumpIndex = this.schedule.findJumpRestrictedIndex( playingIndex + 1, @@ -909,6 +951,7 @@ export default class InterstitialsController // restart Interstitial at end if (this.playingLastItem && this.isInterstitial(playingItem)) { const restartAsset = playingItem.event.assetList[0]; + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (restartAsset) { this.endedItem = this.playingItem; this.playingItem = null; @@ -923,6 +966,10 @@ export default class InterstitialsController (backwardSeek && currentTime < start) || currentTime >= start + duration ) { + if (playingItem.event?.appendInPlace) { + this.clearInterstitial(playingItem.event, playingItem); + this.flushFrontBuffer(currentTime); + } this.setScheduleToAssetAtTime(currentTime, playingAsset); } }; @@ -972,7 +1019,7 @@ export default class InterstitialsController // Scheduling methods private checkStart() { const schedule = this.schedule; - const interstitialEvents = schedule.events; + const interstitialEvents = schedule?.events; if (!interstitialEvents || this.playbackDisabled || !this.media) { return; } @@ -985,6 +1032,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); @@ -1001,19 +1049,41 @@ export default class InterstitialsController } } + private advanceAssetBuffering( + item: InterstitialScheduleEventItem, + assetItem: InterstitialAssetItem, + ) { + const interstitial = item.event; + const assetListIndex = interstitial.findAssetIndex(assetItem); + const nextAssetIndex = getNextAssetIndex(interstitial, assetListIndex); + if (!interstitial.isAssetPastPlayoutLimit(nextAssetIndex)) { + this.bufferedToEvent(item, nextAssetIndex); + } else if (this.schedule) { + const nextItem = this.schedule.items?.[this.findItemIndex(item) + 1]; + if (nextItem) { + this.bufferedToItem(nextItem); + } + } + } + private advanceAfterAssetEnded( interstitial: InterstitialEvent, index: number, assetListIndex: number, ) { - const nextAssetIndex = assetListIndex + 1; - if ( - !interstitial.isAssetPastPlayoutLimit(nextAssetIndex) && - !interstitial.assetList[nextAssetIndex].error - ) { + const nextAssetIndex = getNextAssetIndex(interstitial, assetListIndex); + if (!interstitial.isAssetPastPlayoutLimit(nextAssetIndex)) { // Advance to next asset list item + if (interstitial.appendInPlace) { + const assetItem = interstitial.assetList[nextAssetIndex] as + | InterstitialAssetItem + | undefined; + if (assetItem) { + this.advanceInPlace(assetItem.timelineStart); + } + } this.setSchedulePosition(index, nextAssetIndex); - } else { + } else if (this.schedule) { // Advance to next schedule segment // check if we've reached the end of the program const scheduleItems = this.schedule.items; @@ -1026,8 +1096,12 @@ export default class InterstitialsController } const resumptionTime = interstitial.resumeTime; if (this.timelinePos < resumptionTime) { + this.log(timelineMessage('advanceAfterAssetEnded', resumptionTime)); this.timelinePos = resumptionTime; - this.checkBuffer(); + if (interstitial.appendInPlace) { + this.advanceInPlace(resumptionTime); + } + this.checkBuffer(this.bufferedPos < resumptionTime); } this.setSchedulePosition(nextIndex); } @@ -1039,24 +1113,29 @@ export default class InterstitialsController playingAsset: InterstitialAssetItem, ) { const schedule = this.schedule; + if (!schedule) { + return; + } const parentIdentifier = playingAsset.parentIdentifier; const interstitial = schedule.getEvent(parentIdentifier); if (interstitial) { const itemIndex = schedule.findEventIndex(parentIdentifier); const assetListIndex = schedule.findAssetIndex(interstitial, time); - this.setSchedulePosition(itemIndex, assetListIndex); + this.advanceAfterAssetEnded(interstitial, itemIndex, assetListIndex - 1); } } private setSchedulePosition(index: number, assetListIndex?: number) { - const scheduleItems = this.schedule.items; + const scheduleItems = this.schedule?.items; if (!scheduleItems || this.playbackDisabled) { return; } - this.log(`setSchedulePosition ${index}, ${assetListIndex}`); const scheduledItem = index >= 0 ? scheduleItems[index] : null; + this.log( + `setSchedulePosition ${index}, ${assetListIndex} (${scheduledItem ? segmentToString(scheduledItem) : scheduledItem}) pos: ${this.timelinePos}`, + ); // Cleanup current item / asset - const currentItem = this.playingItem; + const currentItem = this.waitingItem || this.playingItem; const playingLastItem = this.playingLastItem; if (this.isInterstitial(currentItem)) { const interstitial = currentItem.event; @@ -1068,22 +1147,38 @@ export default class InterstitialsController assetId && (!this.eventItemsMatch(currentItem, scheduledItem) || (assetListIndex !== undefined && - assetId !== interstitial.assetList?.[assetListIndex].identifier)) + assetId !== interstitial.assetList[assetListIndex].identifier)) ) { - const assetListIndex = interstitial.findAssetIndex(playingAsset); + const playingAssetListIndex = interstitial.findAssetIndex(playingAsset); this.log( - `INTERSTITIAL_ASSET_ENDED ${assetListIndex + 1}/${interstitial.assetList.length} ${eventAssetToString(playingAsset)}`, + `INTERSTITIAL_ASSET_ENDED ${playingAssetListIndex + 1}/${interstitial.assetList.length} ${eventAssetToString(playingAsset)}`, ); this.endedAsset = playingAsset; this.playingAsset = null; this.hls.trigger(Events.INTERSTITIAL_ASSET_ENDED, { asset: playingAsset, - assetListIndex, + assetListIndex: playingAssetListIndex, event: interstitial, schedule: scheduleItems.slice(0), scheduleIndex: index, player, }); + if (currentItem !== this.playingItem) { + // Schedule change occured on INTERSTITIAL_ASSET_ENDED + if ( + this.itemsMatch(currentItem, this.playingItem) && + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + !this.playingAsset // INTERSTITIAL_ASSET_ENDED side-effect + ) { + this.advanceAfterAssetEnded( + interstitial, + this.findItemIndex(this.playingItem), + playingAssetListIndex, + ); + } + // Navigation occured on INTERSTITIAL_ASSET_ENDED + return; + } this.retreiveMediaSource(assetId, scheduledItem); if (player.media && !this.detachedData?.mediaSource) { player.detachMedia(); @@ -1105,12 +1200,12 @@ export default class InterstitialsController if (interstitial.cue.once) { // Remove interstitial with CUE attribute value of ONCE after it has played this.updateSchedule(); - const items = this.schedule.items; - if (scheduledItem && items) { - const updatedIndex = this.schedule.findItemIndex(scheduledItem); + const updatedScheduleItems = this.schedule?.items; + if (scheduledItem && updatedScheduleItems) { + const updatedIndex = this.findItemIndex(scheduledItem); this.advanceSchedule( updatedIndex, - items, + updatedScheduleItems, assetListIndex, currentItem, playingLastItem, @@ -1135,16 +1230,18 @@ export default class InterstitialsController currentItem: InterstitialScheduleItem | null, playedLastItem: boolean, ) { - const scheduledItem = index >= 0 ? scheduleItems[index] : null; + const schedule = this.schedule; + if (!schedule) { + return; + } + const scheduledItem = scheduleItems[index] || null; const media = this.primaryMedia; // Cleanup out of range Interstitials const playerQueue = this.playerQueue; if (playerQueue.length) { playerQueue.forEach((player) => { const interstitial = player.interstitial; - const queuedIndex = this.schedule.findEventIndex( - interstitial.identifier, - ); + const queuedIndex = schedule.findEventIndex(interstitial.identifier); if (queuedIndex < index || queuedIndex > index + 1) { this.clearInterstitial(interstitial, scheduledItem); } @@ -1160,10 +1257,22 @@ export default class InterstitialsController const interstitial = scheduledItem.event; // find asset index if (assetListIndex === undefined) { - assetListIndex = this.schedule.findAssetIndex( + assetListIndex = schedule.findAssetIndex( interstitial, this.timelinePos, ); + const assetIndexCandidate = getNextAssetIndex( + interstitial, + assetListIndex - 1, + ); + if ( + interstitial.isAssetPastPlayoutLimit(assetIndexCandidate) || + (interstitial.appendInPlace && this.timelinePos === scheduledItem.end) + ) { + this.advanceAfterAssetEnded(interstitial, index, assetListIndex); + return; + } + assetListIndex = assetIndexCandidate; } // Ensure Interstitial is enqueued const waitingItem = this.waitingItem; @@ -1203,18 +1312,10 @@ export default class InterstitialsController this.playingItem = scheduledItem; // If asset-list is empty or missing asset index, advance to next item - const assetItem = interstitial.assetList[assetListIndex]; + const assetItem = interstitial.assetList[assetListIndex] as + | InterstitialAssetItem + | undefined; if (!assetItem) { - const nextItem = scheduleItems[index + 1]; - const media = this.media; - if ( - nextItem && - media && - !this.isInterstitial(nextItem) && - media.currentTime < nextItem.start - ) { - media.currentTime = this.timelinePos = nextItem.start; - } this.advanceAfterAssetEnded(interstitial, index, assetListIndex || 0); return; } @@ -1235,6 +1336,7 @@ export default class InterstitialsController assetItem, assetListIndex, ); + player.loadSource(); } if (!this.eventItemsMatch(scheduledItem, this.bufferingItem)) { if (interstitial.appendInPlace && this.isAssetBuffered(assetItem)) { @@ -1251,7 +1353,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); @@ -1263,7 +1365,7 @@ export default class InterstitialsController this.playingItem = currentItem; if (!currentItem.event.appendInPlace) { // Media must be re-attached to resume primary schedule if not sharing source - this.attachPrimary(this.schedule.durations.primary, null); + this.attachPrimary(schedule.durations.primary, null); } } } @@ -1273,7 +1375,7 @@ export default class InterstitialsController } private get primaryDetails(): LevelDetails | undefined { - return this.mediaSelection?.main?.details; + return this.mediaSelection?.main.details; } private get primaryLive(): boolean { @@ -1300,6 +1402,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); @@ -1309,11 +1412,11 @@ export default class InterstitialsController return; } - const scheduleItems = this.schedule.items; + const scheduleItems = this.schedule?.items; if (!scheduleItems) { return; } - this.log(`resumed ${segmentToString(scheduledItem)}`); + this.log(`INTERSTITIALS_PRIMARY_RESUMED ${segmentToString(scheduledItem)}`); this.hls.trigger(Events.INTERSTITIALS_PRIMARY_RESUMED, { schedule: scheduleItems.slice(0), scheduleIndex: index, @@ -1380,6 +1483,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); } @@ -1407,7 +1511,7 @@ export default class InterstitialsController // HLS.js event callbacks private onManifestLoading() { this.stopLoad(); - this.schedule.reset(); + this.schedule?.reset(); this.emptyPlayerQueue(); this.clearScheduleState(); this.shouldPlay = false; @@ -1423,7 +1527,7 @@ export default class InterstitialsController } private onLevelUpdated(event: Events.LEVEL_UPDATED, data: LevelUpdatedData) { - if (data.level === -1) { + if (data.level === -1 || !this.schedule) { // level was removed return; } @@ -1477,9 +1581,8 @@ export default class InterstitialsController ) { const audioOption = getBasicSelectionOption(data); this.playerQueue.forEach( - (player) => - player.hls.setAudioOption(data) || - player.hls.setAudioOption(audioOption), + ({ hls }) => + hls && (hls.setAudioOption(data) || hls.setAudioOption(audioOption)), ); } @@ -1489,9 +1592,10 @@ export default class InterstitialsController ) { const subtitleOption = getBasicSelectionOption(data); this.playerQueue.forEach( - (player) => - player.hls.setSubtitleOption(data) || - (data.id !== -1 && player.hls.setSubtitleOption(subtitleOption)), + ({ hls }) => + hls && + (hls.setSubtitleOption(data) || + (data.id !== -1 && hls.setSubtitleOption(subtitleOption))), ); } @@ -1526,6 +1630,9 @@ export default class InterstitialsController } private onBufferedToEnd(event: Events.BUFFERED_TO_END) { + if (!this.schedule) { + return; + } // Buffered to post-roll const interstitialEvents = this.schedule.events; if (this.bufferedPos < Number.MAX_VALUE && interstitialEvents) { @@ -1565,6 +1672,9 @@ export default class InterstitialsController previousItems: InterstitialScheduleItem[] | null, ) => { const schedule = this.schedule; + if (!schedule) { + return; + } const playingItem = this.playingItem; const interstitialEvents = schedule.events || []; const scheduleItems = schedule.items || []; @@ -1575,56 +1685,37 @@ export default class InterstitialsController const interstitialsUpdated = !!( interstitialEvents.length || removedIds.length ); - if (interstitialsUpdated) { + if (interstitialsUpdated || previousItems) { this.log( `INTERSTITIALS_UPDATED (${ interstitialEvents.length }): ${interstitialEvents} -Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, +Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timelinePos}`, ); } if (removedIds.length) { this.log(`Removed events ${removedIds}`); } - this.playerQueue.forEach((player) => { - if (player.interstitial.appendInPlace) { - const timelineStart = player.assetItem.timelineStart; - const diff = player.timelineOffset - timelineStart; - if (diff) { - try { - player.timelineOffset = timelineStart; - } catch (e) { - if (Math.abs(diff) > ALIGNED_END_THRESHOLD_SECONDS) { - this.warn( - `${e} ("${player.assetId}" ${player.timelineOffset}->${timelineStart})`, - ); - } - } - } - } - }); - // Update schedule item references // Do not replace Interstitial playingItem without a match - used for INTERSTITIAL_ASSET_ENDED and INTERSTITIAL_ENDED + let updatedPlayingItem: InterstitialScheduleItem | null = null; + let updatedBufferingItem: InterstitialScheduleItem | null = null; if (playingItem) { - const updatedPlayingItem = this.updateItem(playingItem, this.timelinePos); + updatedPlayingItem = this.updateItem(playingItem, this.timelinePos); if (this.itemsMatch(playingItem, updatedPlayingItem)) { this.playingItem = updatedPlayingItem; + } else { this.waitingItem = this.endedItem = null; } - } else { - // Clear waitingItem if it has been removed from the schedule - this.waitingItem = this.updateItem(this.waitingItem); - this.endedItem = this.updateItem(this.endedItem); } + // Clear waitingItem if it has been removed from the schedule + this.waitingItem = this.updateItem(this.waitingItem); + this.endedItem = this.updateItem(this.endedItem); // Do not replace Interstitial bufferingItem without a match - used for transfering media element or source const bufferingItem = this.bufferingItem; if (bufferingItem) { - const updatedBufferingItem = this.updateItem( - bufferingItem, - this.bufferedPos, - ); + updatedBufferingItem = this.updateItem(bufferingItem, this.bufferedPos); if (this.itemsMatch(bufferingItem, updatedBufferingItem)) { this.bufferingItem = updatedBufferingItem; } else if (bufferingItem.event) { @@ -1640,6 +1731,24 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, }); }); + this.playerQueue.forEach((player) => { + if (player.interstitial.appendInPlace) { + const timelineStart = player.assetItem.timelineStart; + const diff = player.timelineOffset - timelineStart; + if (diff) { + try { + player.timelineOffset = timelineStart; + } catch (e) { + if (Math.abs(diff) > ALIGNED_END_THRESHOLD_SECONDS) { + this.warn( + `${e} ("${player.assetId}" ${player.timelineOffset}->${timelineStart})`, + ); + } + } + } + } + }); + if (interstitialsUpdated || previousItems) { this.hls.trigger(Events.INTERSTITIALS_UPDATED, { events: interstitialEvents.slice(0), @@ -1659,7 +1768,14 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, return; } - // Check is buffered to new Interstitial event boundary + if (playingItem) { + this.trimInPlace(updatedPlayingItem, playingItem); + } + if (bufferingItem && updatedBufferingItem !== updatedPlayingItem) { + this.trimInPlace(updatedBufferingItem, bufferingItem); + } + + // Check if buffered to new Interstitial event boundary // (Live update publishes Interstitial with new segment) this.checkBuffer(); } @@ -1670,14 +1786,48 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, time?: number, ): T | null { // find item in this.schedule.items; - const items = this.schedule.items; + const items = this.schedule?.items; if (previousItem && items) { const index = this.findItemIndex(previousItem, time); - return (items[index] as T) || null; + return (items[index] as T | undefined) || null; } return null; } + private trimInPlace( + updatedItem: InterstitialScheduleItem | null, + itemBeforeUpdate: InterstitialScheduleItem, + ) { + if ( + this.isInterstitial(updatedItem) && + updatedItem.event.appendInPlace && + itemBeforeUpdate.end - updatedItem.end > 0.25 + ) { + updatedItem.event.assetList.forEach((asset, index) => { + if (updatedItem.event.isAssetPastPlayoutLimit(index)) { + this.clearAssetPlayer(asset.identifier, null); + } + }); + const flushStart = updatedItem.end + 0.25; + const bufferInfo = BufferHelper.bufferInfo( + this.primaryMedia, + flushStart, + 0, + ); + if ( + bufferInfo.end > flushStart || + (bufferInfo.nextStart || 0) > flushStart + ) { + this.log( + `trim buffered interstitial ${segmentToString(updatedItem)} (was ${segmentToString(itemBeforeUpdate)})`, + ); + const skipSeekToStartPosition = true; + this.attachPrimary(flushStart, null, skipSeekToStartPosition); + this.flushFrontBuffer(flushStart); + } + } + } + private itemsMatch( a: InterstitialScheduleItem, b: InterstitialScheduleItem | null | undefined, @@ -1703,20 +1853,20 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, item: InterstitialScheduleItem | null, time?: number, ): number { - return item ? this.schedule.findItemIndex(item, time) : -1; + return item && this.schedule ? this.schedule.findItemIndex(item, time) : -1; } - private updateSchedule() { + private updateSchedule(forceUpdate: boolean = false) { const mediaSelection = this.mediaSelection; if (!mediaSelection) { return; } - this.schedule.updateSchedule(mediaSelection, []); + this.schedule?.updateSchedule(mediaSelection, [], forceUpdate); } // Schedule buffer control private checkBuffer(starved?: boolean) { - const items = this.schedule.items; + const items = this.schedule?.items; if (!items) { return; } @@ -1740,7 +1890,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, ) { const schedule = this.schedule; const bufferingItem = this.bufferingItem; - if (this.bufferedPos > bufferEnd) { + if (this.bufferedPos > bufferEnd || !schedule) { return; } if (items.length === 1 && this.itemsMatch(items[0], bufferingItem)) { @@ -1764,12 +1914,22 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, ) { bufferEndIndex = nextToBufferIndex; } - if ( - nextToBufferIndex - playingIndex > 1 && - bufferingItem?.event?.appendInPlace === false - ) { - // do not advance buffering item past Interstitial that requires source reset - return; + if (this.isInterstitial(bufferingItem)) { + const interstitial = bufferingItem.event; + if ( + nextToBufferIndex - playingIndex > 1 && + interstitial.appendInPlace === false + ) { + // do not advance buffering item past Interstitial that requires source reset + return; + } + if ( + interstitial.assetList.length === 0 && + interstitial.assetListLoader + ) { + // do not advance buffering item past Interstitial loading asset-list + return; + } } this.bufferedPos = bufferEnd; if (bufferEndIndex > bufferingIndex && bufferEndIndex > playingIndex) { @@ -1821,7 +1981,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, const bufferingLast = this.bufferingItem; const schedule = this.schedule; - if (!this.itemsMatch(item, bufferingLast)) { + if (!this.itemsMatch(item, bufferingLast) && schedule) { const { items, events } = schedule; if (!items || !events) { return bufferingLast; @@ -1833,21 +1993,28 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, item.start, Math.min(item.end, this.timelinePos), ); + const timeRemaining = bufferingPlayer + ? bufferingPlayer.remaining + : bufferingLast + ? bufferingLast.end - this.timelinePos + : 0; + this.log( + `INTERSTITIALS_BUFFERED_TO_BOUNDARY ${segmentToString(item)}` + + (bufferingLast ? ` (${timeRemaining.toFixed(2)} remaining)` : ''), + ); if (!this.playbackDisabled) { - const timeRemaining = bufferingPlayer - ? bufferingPlayer.remaining - : bufferingLast - ? bufferingLast.end - this.timelinePos - : 0; - this.log( - `buffered to boundary ${segmentToString(item)}` + - (bufferingLast ? ` (${timeRemaining.toFixed(2)} remaining)` : ''), - ); if (isInterstitial) { + const bufferIndex = schedule.findAssetIndex( + item.event, + this.bufferedPos, + ); // primary fragment loading will exit early in base-stream-controller while `bufferingItem` is set to an Interstitial block - item.event.assetList.forEach((asset) => { + item.event.assetList.forEach((asset, i) => { const player = this.getAssetPlayer(asset.identifier); if (player) { + if (i === bufferIndex) { + player.loadSource(); + } player.resumeBuffering(); } }); @@ -1915,10 +2082,8 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, // Buffered to Interstitial boundary const player = this.preloadAssets(interstitial, assetListIndex); if (player?.interstitial.appendInPlace) { - // If we have a player and asset list info, start buffering - const assetItem = interstitial.assetList[assetListIndex]; const media = this.primaryMedia; - if (assetItem && media) { + if (media) { this.bufferAssetPlayer(player, media); } } @@ -1997,9 +2162,15 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, this.createAssetPlayer(interstitial, asset, i); } } - return this.getAssetPlayer( - interstitial.assetList[assetListIndex].identifier, - ); + const asset = interstitial.assetList[assetListIndex]; + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (asset) { + const player = this.getAssetPlayer(asset.identifier); + if (player) { + player.loadSource(); + } + return player; + } } return null; } @@ -2073,17 +2244,18 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, assetItem: InterstitialAssetItem, assetListIndex: number, ): HlsAssetPlayer { - this.log(`create HLSAssetPlayer for ${eventAssetToString(assetItem)}`); const primary = this.hls; const userConfig = primary.userConfig; let videoPreference = userConfig.videoPreference; const currentLevel = primary.loadLevelObj || primary.levels[primary.currentLevel]; + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (videoPreference || currentLevel) { videoPreference = Object.assign({}, videoPreference); if (currentLevel.videoCodec) { videoPreference.videoCodec = currentLevel.videoCodec; } + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (currentLevel.videoRange) { videoPreference.allowedVideoRanges = [currentLevel.videoRange]; } @@ -2101,8 +2273,9 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, } } const assetId = assetItem.identifier; - const playerConfig: Partial = { + const playerConfig: HlsAssetPlayerConfig = { ...userConfig, + maxMaxBufferLength: Math.min(180, primary.config.maxMaxBufferLength), autoStartLoad: true, startFragPrefetch: true, primarySessionId: primary.sessionId, @@ -2113,9 +2286,14 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, liveDurationInfinity: false, testBandwidth: false, videoPreference, - audioPreference: selectedAudio || userConfig.audioPreference, - subtitlePreference: selectedSubtitle || userConfig.subtitlePreference, + audioPreference: + (selectedAudio as MediaPlaylist | undefined) || + userConfig.audioPreference, + subtitlePreference: + (selectedSubtitle as MediaPlaylist | undefined) || + userConfig.subtitlePreference, }; + // TODO: limit maxMaxBufferLength in asset players to prevent QEE if (interstitial.appendInPlace) { interstitial.appendInPlaceStarted = true; if (assetItem.timelineStart) { @@ -2142,6 +2320,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, this.playerQueue.push(player); interstitial.assetList[assetListIndex] = assetItem; // Listen for LevelDetails and PTS change to update duration + let initialDuration = true; const updateAssetPlayerDetails = (details: LevelDetails) => { if (details.live) { const error = new Error( @@ -2153,10 +2332,12 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, details: ErrorDetails.INTERSTITIAL_ASSET_ITEM_ERROR, error, }; + const scheduleIndex = + this.schedule?.findEventIndex(interstitial.identifier) || -1; this.handleAssetItemError( errorData, interstitial, - this.schedule.findEventIndex(interstitial.identifier), + scheduleIndex, assetListIndex, error.message, ); @@ -2165,7 +2346,12 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, // Get time at end of last fragment const duration = details.edge - details.fragmentStart; const currentAssetDuration = assetItem.duration; - if (currentAssetDuration === null || duration > currentAssetDuration) { + if ( + initialDuration || + currentAssetDuration === null || + duration > currentAssetDuration + ) { + initialDuration = false; this.log( `Interstitial asset "${assetId}" duration change ${currentAssetDuration} > ${duration}`, ); @@ -2180,6 +2366,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, player.on(Events.LEVEL_PTS_UPDATED, (event, { details }) => updateAssetPlayerDetails(details), ); + player.on(Events.EVENT_CUE_ENTER, () => this.onInterstitialCueEnter()); const onBufferCodecs = ( event: Events.BUFFER_CODECS, data: BufferCodecsData, @@ -2202,36 +2389,23 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, const bufferedToEnd = () => { const inQueuPlayer = this.getAssetPlayer(assetId); this.log(`buffered to end of asset ${inQueuPlayer}`); - if (!inQueuPlayer) { + if (!inQueuPlayer || !this.schedule) { return; } // Preload at end of asset const scheduleIndex = this.schedule.findEventIndex( interstitial.identifier, ); - const assetListIndex = interstitial.findAssetIndex(assetItem); - const nextAssetIndex = assetListIndex + 1; const item = this.schedule.items?.[scheduleIndex]; if (this.isInterstitial(item)) { - if ( - assetListIndex !== -1 && - !interstitial.isAssetPastPlayoutLimit(nextAssetIndex) && - !interstitial.assetList[nextAssetIndex].error - ) { - this.bufferedToItem(item, nextAssetIndex); - } else { - const nextItem = this.schedule.items?.[scheduleIndex + 1]; - if (nextItem) { - this.bufferedToItem(nextItem); - } - } + this.advanceAssetBuffering(item, assetItem); } }; player.on(Events.BUFFERED_TO_END, bufferedToEnd); const endedWithAssetIndex = (assetIndex) => { return () => { const inQueuPlayer = this.getAssetPlayer(assetId); - if (!inQueuPlayer) { + if (!inQueuPlayer || !this.schedule) { return; } this.shouldPlay = true; @@ -2244,28 +2418,17 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, player.once(Events.MEDIA_ENDED, endedWithAssetIndex(assetListIndex)); player.once(Events.PLAYOUT_LIMIT_REACHED, endedWithAssetIndex(Infinity)); player.on(Events.ERROR, (event: Events.ERROR, data: ErrorData) => { + if (!this.schedule) { + return; + } const inQueuPlayer = this.getAssetPlayer(assetId); if (data.details === ErrorDetails.BUFFER_STALLED_ERROR) { - if (inQueuPlayer?.media) { - const assetCurrentTime = inQueuPlayer.currentTime; - const distanceFromEnd = inQueuPlayer.duration - assetCurrentTime; - if ( - assetCurrentTime && - interstitial.appendInPlace && - distanceFromEnd / inQueuPlayer.media.playbackRate < 0.5 - ) { - this.log( - `Advancing buffer past end of asset ${assetId} ${interstitial} at ${inQueuPlayer.media.currentTime}`, - ); - bufferedToEnd(); - } else { - this.warn( - `Stalled at ${assetCurrentTime} of ${assetCurrentTime + distanceFromEnd} in asset ${assetId} ${interstitial}`, - ); - this.onTimeupdate(); - this.checkBuffer(true); - } + if (inQueuPlayer?.appendInPlace) { + this.handleInPlaceStall(interstitial); + return; } + this.onTimeupdate(); + this.checkBuffer(true); return; } this.handleAssetItemError( @@ -2278,7 +2441,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, }); player.on(Events.DESTROYING, () => { const inQueuPlayer = this.getAssetPlayer(assetId); - if (!inQueuPlayer) { + if (!inQueuPlayer || !this.schedule) { return; } const error = new Error(`Asset player destroyed unexpectedly ${assetId}`); @@ -2296,7 +2459,9 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, error.message, ); }); - + this.log( + `INTERSTITIAL_ASSET_PLAYER_CREATED ${eventAssetToString(assetItem)}`, + ); this.hls.trigger(Events.INTERSTITIAL_ASSET_PLAYER_CREATED, { asset: assetItem, assetListIndex, @@ -2317,16 +2482,27 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, interstitial.reset(); } + private resetAssetPlayer(assetId: InterstitialAssetId) { + // Reset asset player so that it's timeline can be adjusted without reloading the MVP + const playerIndex = this.getAssetPlayerQueueIndex(assetId); + if (playerIndex !== -1) { + this.log(`reset asset player "${assetId}" after error`); + const player = this.playerQueue[playerIndex]; + this.transferMediaFromPlayer(player, null); + player.resetDetails(); + } + } + private clearAssetPlayer( assetId: InterstitialAssetId, toSegment: InterstitialScheduleItem | null, ) { const playerIndex = this.getAssetPlayerQueueIndex(assetId); if (playerIndex !== -1) { + const player = this.playerQueue[playerIndex]; this.log( - `clearAssetPlayer "${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(); @@ -2364,7 +2540,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, delete playingAsset.error; } this.log( - `INTERSTITIAL_ASSET_STARTED ${assetListIndex + 1}/${assetListLength} ${player}`, + `INTERSTITIAL_ASSET_STARTED ${assetListIndex + 1}/${assetListLength} ${eventAssetToString(assetItem)}`, ); this.hls.trigger(Events.INTERSTITIAL_ASSET_STARTED, { asset: assetItem, @@ -2381,12 +2557,16 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, } private bufferAssetPlayer(player: HlsAssetPlayer, media: HTMLMediaElement) { - const { interstitial, assetItem, assetId } = player; + if (!this.schedule) { + return; + } + const { interstitial, assetItem } = player; const scheduleIndex = this.schedule.findEventIndex(interstitial.identifier); const item = this.schedule.items?.[scheduleIndex]; if (!item) { return; } + player.loadSource(); this.setBufferingItem(item); this.bufferingAsset = assetItem; const bufferingPlayer = this.getBufferingPlayer(); @@ -2408,6 +2588,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, if (appendInPlaceNext && assetItem !== this.playingAsset) { // Do not buffer another item if tracks are unknown or incompatible if (!player.tracks) { + this.log(`Waiting for track info before buffering ${player}`); return; } if ( @@ -2415,7 +2596,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, !isCompatibleTrackChange(activeTracks, player.tracks) ) { const error = new Error( - `Asset "${assetId}" SourceBuffer tracks ('${Object.keys(player.tracks)}') are not compatible with primary content tracks ('${Object.keys(activeTracks)}')`, + `Asset ${eventAssetToString(assetItem)} SourceBuffer tracks ('${Object.keys(player.tracks)}') are not compatible with primary content tracks ('${Object.keys(activeTracks)}')`, ); const errorData: ErrorData = { fatal: true, @@ -2438,6 +2619,52 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, this.transferMediaTo(player, media); } + private handleInPlaceStall(interstitial: InterstitialEvent) { + const schedule = this.schedule; + const media = this.primaryMedia; + if (!schedule || !media) { + return; + } + const currentTime = media.currentTime; + const foundAssetIndex = schedule.findAssetIndex(interstitial, currentTime); + const stallingAsset = interstitial.assetList[foundAssetIndex] as + | InterstitialAssetItem + | undefined; + if (stallingAsset) { + const player = this.getAssetPlayer(stallingAsset.identifier); + if (player) { + const assetCurrentTime = + player.currentTime || currentTime - stallingAsset.timelineStart; + const distanceFromEnd = player.duration - assetCurrentTime; + this.warn( + `Stalled at ${assetCurrentTime} of ${assetCurrentTime + distanceFromEnd} in ${player} ${interstitial} (media.currentTime: ${currentTime})`, + ); + if ( + assetCurrentTime && + (distanceFromEnd / media.playbackRate < 0.5 || + player.bufferedInPlaceToEnd(media)) && + player.hls + ) { + const scheduleIndex = schedule.findEventIndex( + interstitial.identifier, + ); + this.advanceAfterAssetEnded( + interstitial, + scheduleIndex, + foundAssetIndex, + ); + } + } + } + } + + private advanceInPlace(time: number) { + const media = this.primaryMedia; + if (media && media.currentTime < time) { + media.currentTime = time; + } + } + private handleAssetItemError( data: ErrorData, interstitial: InterstitialEvent, @@ -2448,13 +2675,17 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, if (data.details === ErrorDetails.BUFFER_STALLED_ERROR) { return; } - - const assetItem = interstitial.assetList[assetListIndex] || null; - let player: HlsAssetPlayer | null = null; - if (assetItem) { - const playerIndex = this.getAssetPlayerQueueIndex(assetItem.identifier); - player = this.playerQueue[playerIndex] || null; + const assetItem = (interstitial.assetList[assetListIndex] || + null) as InterstitialAssetItem | null; + this.warn( + `INTERSTITIAL_ASSET_ERROR ${assetItem ? eventAssetToString(assetItem) : assetItem} ${data.error}`, + ); + if (!this.schedule) { + return; } + const assetId = assetItem?.identifier || ''; + const playerIndex = this.getAssetPlayerQueueIndex(assetId); + const player = this.playerQueue[playerIndex] || null; const items = this.schedule.items; const interstitialAssetError = Object.assign({}, data, { fatal: false, @@ -2466,29 +2697,40 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, scheduleIndex, player, }); - this.warn(`Asset item error: ${data.error}`); this.hls.trigger(Events.INTERSTITIAL_ASSET_ERROR, interstitialAssetError); if (!data.fatal) { return; } + const playingAsset = this.playingAsset; + const bufferingAsset = this.bufferingAsset; const error = new Error(errorMessage); if (assetItem) { - if (this.playingAsset !== assetItem) { - this.clearAssetPlayer(assetItem.identifier, null); - } + this.clearAssetPlayer(assetId, null); assetItem.error = error; } // If all assets in interstitial fail, mark the interstitial with an error if (!interstitial.assetList.some((asset) => !asset.error)) { interstitial.error = error; - } else if (interstitial.appendInPlace) { - // Skip entire interstitial since moving up subsequent assets is error prone - interstitial.error = error; + } else { + // Reset level details and reload/parse media playlists to align with updated schedule + for (let i = assetListIndex; i < interstitial.assetList.length; i++) { + this.resetAssetPlayer(interstitial.assetList[i].identifier); + } + } + this.updateSchedule(true); + if (interstitial.error) { + this.primaryFallback(interstitial); + } else if (playingAsset && playingAsset.identifier === assetId) { + this.advanceAfterAssetEnded(interstitial, scheduleIndex, assetListIndex); + } else if ( + bufferingAsset && + bufferingAsset.identifier === assetId && + this.isInterstitial(this.bufferingItem) + ) { + this.advanceAssetBuffering(this.bufferingItem, bufferingAsset); } - - this.primaryFallback(interstitial); } private primaryFallback(interstitial: InterstitialEvent) { @@ -2496,30 +2738,31 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, const flushStart = interstitial.timelineStart; const playingItem = this.effectivePlayingItem; // Update schedule now that interstitial/assets are flagged with `error` for fallback - this.updateSchedule(); if (playingItem) { this.log( `Fallback to primary from event "${interstitial.identifier}" start: ${ flushStart - } pos: ${this.timelinePos} playing: ${ - playingItem ? segmentToString(playingItem) : '' - } error: ${interstitial.error}`, + } pos: ${this.timelinePos} playing: ${segmentToString( + playingItem, + )} error: ${interstitial.error}`, ); - if (interstitial.appendInPlace) { - this.attachPrimary(flushStart, null); - this.flushFrontBuffer(flushStart); - } let timelinePos = this.timelinePos; if (timelinePos === -1) { timelinePos = this.hls.startPosition; } const newPlayingItem = this.updateItem(playingItem, timelinePos); - if (!this.itemsMatch(playingItem, newPlayingItem)) { - const scheduleIndex = this.schedule.findItemIndexAtTime(timelinePos); - this.setSchedulePosition(scheduleIndex); - } else { + if (this.itemsMatch(playingItem, newPlayingItem)) { this.clearInterstitial(interstitial, null); } + if (interstitial.appendInPlace) { + this.attachPrimary(flushStart, null); + this.flushFrontBuffer(flushStart); + } + if (!this.schedule) { + return; + } + const scheduleIndex = this.schedule.findItemIndexAtTime(timelinePos); + this.setSchedulePosition(scheduleIndex); } else { this.checkStart(); } @@ -2533,7 +2776,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, const interstitial = data.event; const interstitialId = interstitial.identifier; const assets = data.assetListResponse.ASSETS; - if (!this.schedule.hasEvent(interstitialId)) { + if (!this.schedule?.hasEvent(interstitialId)) { // Interstitial with id was removed return; } @@ -2577,6 +2820,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, interstitial.error = new Error( `Interstitial no longer within playback range ${this.timelinePos} ${interstitial}`, ); + this.updateSchedule(true); this.primaryFallback(interstitial); return; } @@ -2584,32 +2828,52 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))}`, this.setBufferingItem(item); } this.setSchedulePosition(scheduleIndex); - } else if ( - bufferingEvent?.identifier === interstitialId && - bufferingEvent.appendInPlace - ) { - // If buffering (but not playback) has reached this item transfer media-source + } else if (bufferingEvent?.identifier === interstitialId) { const assetItem = interstitial.assetList[0]; - const player = this.getAssetPlayer(assetItem.identifier); - const media = this.primaryMedia; - if (assetItem && player && media) { - this.bufferAssetPlayer(player, media); + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (assetItem) { + const player = this.getAssetPlayer(assetItem.identifier); + if (bufferingEvent.appendInPlace) { + // If buffering (but not playback) has reached this item transfer media-source + const media = this.primaryMedia; + if (player && media) { + this.bufferAssetPlayer(player, media); + } + } else if (player) { + player.loadSource(); + } } } } private onError(event: Events.ERROR, data: ErrorData) { + if (!this.schedule) { + return; + } switch (data.details) { case ErrorDetails.ASSET_LIST_PARSING_ERROR: case ErrorDetails.ASSET_LIST_LOAD_ERROR: case ErrorDetails.ASSET_LIST_LOAD_TIMEOUT: { const interstitial = data.interstitial; if (interstitial) { + this.updateSchedule(true); this.primaryFallback(interstitial); } break; } case ErrorDetails.BUFFER_STALLED_ERROR: { + const stallingItem = + this.endedItem || this.waitingItem || this.playingItem; + if ( + this.isInterstitial(stallingItem) && + stallingItem.event.appendInPlace + ) { + this.handleInPlaceStall(stallingItem.event); + return; + } + this.log( + `Primary player stall @${this.timelinePos} bufferedPos: ${this.bufferedPos}`, + ); this.onTimeupdate(); this.checkBuffer(true); break; diff --git a/src/controller/interstitials-schedule.ts b/src/controller/interstitials-schedule.ts index 4193355327d..5e6e41e9680 100644 --- a/src/controller/interstitials-schedule.ts +++ b/src/controller/interstitials-schedule.ts @@ -62,7 +62,7 @@ type ScheduleUpdateCallback = ( export class InterstitialsSchedule extends Logger { private onScheduleUpdate: ScheduleUpdateCallback; - private eventMap: Record = {}; + private eventMap: Record = {}; public events: InterstitialEvent[] | null = null; public items: InterstitialScheduleItem[] | null = null; public durations: InterstitialScheduleDurations = { @@ -217,7 +217,8 @@ export class InterstitialsSchedule extends Logger { if ( timelinePos === timelineStart || (timelinePos > timelineStart && - timelinePos < timelineStart + (asset.duration || 0)) + (timelinePos < timelineStart + (asset.duration || 0) || + i === length - 1)) ) { return i; } @@ -302,12 +303,14 @@ export class InterstitialsSchedule extends Logger { public updateSchedule( mediaSelection: MediaSelection, removedInterstitials: InterstitialEvent[] = [], + forceUpdate: boolean = false, ) { const events = this.events || []; if (events.length || removedInterstitials.length || this.length < 2) { const currentItems = this.items; const updatedItems = this.parseSchedule(events, mediaSelection); const updated = + forceUpdate || removedInterstitials.length || currentItems?.length !== updatedItems.length || updatedItems.some((item, i) => { @@ -326,7 +329,7 @@ export class InterstitialsSchedule extends Logger { } private parseDateRanges( - dateRanges: Record, + dateRanges: Record, baseData: BaseData, enableAppendInPlace: boolean, ): InterstitialEvent[] { @@ -334,7 +337,7 @@ export class InterstitialsSchedule extends Logger { const ids = Object.keys(dateRanges); for (let i = 0; i < ids.length; i++) { const id = ids[i]; - const dateRange = dateRanges[id]; + const dateRange = dateRanges[id]!; if (dateRange.isInterstitial) { let interstitial = this.eventMap[id]; if (interstitial) { @@ -377,7 +380,8 @@ export class InterstitialsSchedule extends Logger { interstitialEvents.forEach((interstitial, i) => { const preroll = interstitial.cue.pre; const postroll = interstitial.cue.post; - const previousEvent = interstitialEvents[i - 1] || null; + const previousEvent = + (interstitialEvents[i - 1] as InterstitialEvent | undefined) || null; const appendInPlace = interstitial.appendInPlace; const eventStart = postroll ? primaryDuration @@ -618,12 +622,6 @@ export class InterstitialsSchedule extends Logger { ); return false; } - if (!mediaSelection) { - this.log( - `"${interstitial.identifier}" resumption ${resumeTime} can not be aligned with media (none selected)`, - ); - return false; - } const playlists = Object.keys(mediaSelection); return !playlists.some((playlistType) => { const details = mediaSelection[playlistType].details; @@ -671,7 +669,8 @@ export class InterstitialsSchedule extends Logger { let sumDuration = 0; let hasUnknownDuration = false; let hasErrors = false; - interstitial.assetList.forEach((asset, i) => { + for (let i = 0; i < interstitial.assetList.length; i++) { + const asset = interstitial.assetList[i]; const timelineStart = eventStart + sumDuration; asset.startOffset = sumDuration; asset.timelineStart = timelineStart; @@ -679,7 +678,7 @@ export class InterstitialsSchedule extends Logger { hasErrors ||= !!asset.error; const duration = asset.error ? 0 : (asset.duration as number) || 0; sumDuration += duration; - }); + } // Use the sum of known durations when it is greater than the stated duration if (hasUnknownDuration && !hasErrors) { interstitial.duration = Math.max(sumDuration, interstitial.duration); diff --git a/src/controller/level-controller.ts b/src/controller/level-controller.ts index e227a7758e3..c2999e7eaf4 100644 --- a/src/controller/level-controller.ts +++ b/src/controller/level-controller.ts @@ -127,7 +127,7 @@ export default class LevelController extends BasePlaylistController { undefined; } - if (videoCodec?.indexOf('avc1') === 0) { + if (videoCodec) { videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec); } @@ -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 a62deea7870..2e3402bc5cf 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; @@ -345,7 +335,7 @@ export default class StreamController const backtrackSn = (this.backtrackFragment ?? frag).sn as number; const fragIdx = backtrackSn - levelDetails.startSN; const backtrackFrag = levelDetails.fragments[fragIdx - 1]; - if (backtrackFrag && frag.cc === backtrackFrag.cc) { + if ((backtrackFrag as any) && frag.cc === backtrackFrag.cc) { frag = backtrackFrag; this.fragmentTracker.removeFragment(backtrackFrag); } @@ -624,13 +614,13 @@ export default class StreamController // detect if we have different kind of audio codecs used amongst playlists let aac = false; let heaac = false; - data.levels.forEach((level) => { - const codec = level.audioCodec; + for (let i = 0; i < data.levels.length; i++) { + const codec = data.levels[i].audioCodec; if (codec) { aac = aac || codec.indexOf('mp4a.40.2') !== -1; heaac = heaac || codec.indexOf('mp4a.40.5') !== -1; } - }); + } this.audioCodecSwitch = aac && heaac && !changeTypeSupported(); if (this.audioCodecSwitch) { this.log( @@ -769,7 +759,37 @@ export default class StreamController 3, )}`, ); - media.currentTime = liveSyncPosition; + + if (this.config.liveSyncMode === 'buffered') { + const bufferInfo = BufferHelper.bufferInfo( + media, + liveSyncPosition, + 0, + ); + + if (!bufferInfo.buffered?.length) { + media.currentTime = liveSyncPosition; + return; + } + + const isLiveSyncInBuffer = bufferInfo.start <= currentTime; + + if (isLiveSyncInBuffer) { + media.currentTime = liveSyncPosition; + return; + } + + const { nextStart } = BufferHelper.bufferedInfo( + bufferInfo.buffered, + currentTime, + 0, + ); + if (nextStart) { + media.currentTime = nextStart; + } + } else { + media.currentTime = liveSyncPosition; + } } } } @@ -786,7 +806,7 @@ export default class StreamController return; } const currentLevel = levels[frag.level]; - if (!currentLevel) { + if (!currentLevel as any) { this.warn(`Level ${frag.level} not found on progress`); return; } @@ -877,7 +897,10 @@ export default class StreamController if (fromAltAudio) { this.fragmentTracker.removeAllFragments(); hls.once(Events.BUFFER_FLUSHED, () => { - this.hls?.trigger(Events.AUDIO_TRACK_SWITCHED, data); + if (!this.hls as any) { + return; + } + this.hls.trigger(Events.AUDIO_TRACK_SWITCHED, data); }); hls.trigger(Events.BUFFER_FLUSHING, { startOffset: 0, @@ -1023,14 +1046,23 @@ export default class StreamController if (data.parent !== 'main') { return; } - this.resetLoadingState(); + if (this.reduceLengthAndFlushBuffer(data)) { + this.resetLoadingState(); + } break; case ErrorDetails.BUFFER_FULL_ERROR: if (data.parent !== 'main') { return; } if (this.reduceLengthAndFlushBuffer(data)) { - this.flushMainBuffer(0, Number.POSITIVE_INFINITY); + const isAssetPlayer = + !this.config.interstitialsController && this.config.assetPlayerId; + if (isAssetPlayer) { + // Use currentTime in buffer estimate to prevent loading more until playback advances + this._hasEnoughToStart = true; + } else { + this.flushMainBuffer(0, Number.POSITIVE_INFINITY); + } } break; case ErrorDetails.INTERNAL_EXCEPTION: @@ -1119,9 +1151,10 @@ export default class StreamController this.config.maxFragLookUpTolerance, ); if ( - delta > 0 && - (delta < skipTolerance || - (this.loadingParts && delta < 2 * (details?.partTarget || 0))) + this.config.startOnSegmentBoundary || + (delta > 0 && + (delta < skipTolerance || + (this.loadingParts && delta < 2 * (details?.partTarget || 0)))) ) { this.log(`adjusting start position by ${delta} to match buffer start`); startPosition += delta; @@ -1152,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) { @@ -1200,27 +1241,42 @@ export default class StreamController this.state = State.PARSING; if (initSegment) { - if (initSegment?.tracks) { + const tracks = initSegment.tracks; + if (tracks) { const mapFragment = frag.initSegment || frag; - this._bufferInitSegment( - level, - initSegment.tracks, - mapFragment, - chunkMeta, - ); + if (this.unhandledEncryptionError(initSegment, frag)) { + return; + } + this._bufferInitSegment(level, tracks, mapFragment, chunkMeta); hls.trigger(Events.FRAG_PARSING_INIT_SEGMENT, { frag: mapFragment, id, - tracks: initSegment.tracks, + tracks, }); } - // This would be nice if Number.isFinite acted as a typeguard, but it doesn't. See: https://github.com/Microsoft/TypeScript/issues/10038 - const initPTS = initSegment.initPTS as number; + const baseTime = initSegment.initPTS as number; const timescale = initSegment.timescale as number; - if (Number.isFinite(initPTS)) { - this.initPTS[frag.cc] = { baseTime: initPTS, timescale }; - hls.trigger(Events.INIT_PTS_FOUND, { frag, id, initPTS, timescale }); + const initPTS = this.initPTS[frag.cc]; + if ( + Number.isFinite(baseTime) && + ((!initPTS as any) || + initPTS.baseTime !== baseTime || + initPTS.timescale !== timescale) + ) { + const trackId = initSegment.trackId as number; + this.initPTS[frag.cc] = { + baseTime, + timescale, + trackId, + }; + hls.trigger(Events.INIT_PTS_FOUND, { + frag, + id, + initPTS: baseTime, + timescale, + trackId, + }); } } @@ -1231,7 +1287,8 @@ export default class StreamController } const prevFrag = details.fragments[frag.sn - 1 - details.startSN]; const isFirstFragment = frag.sn === details.startSN; - const isFirstInDiscontinuity = !prevFrag || frag.cc > prevFrag.cc; + const isFirstInDiscontinuity = + (!prevFrag as any) || frag.cc > prevFrag.cc; if (remuxResult.independent !== false) { const { startPTS, endPTS, startDTS, endDTS } = video; if (part) { @@ -1334,7 +1391,7 @@ export default class StreamController this.bufferFragmentData(audio, frag, part, chunkMeta); } - if (details && id3?.samples?.length) { + if (details && id3?.samples.length) { const emittedID3: FragParsingMetadataData = { id, frag, @@ -1382,11 +1439,9 @@ export default class StreamController // include levelCodec in audio and video tracks const { audio, video, audiovideo } = tracks; if (audio) { - let audioCodec = pickMostCompleteCodecName( - audio.codec, - currentLevel.audioCodec, - ); - // Add level and profile to make up for passthrough-remuxer not being able to parse full codec + const levelCodec = currentLevel.audioCodec; + let audioCodec = pickMostCompleteCodecName(audio.codec, levelCodec); + // Add level and profile to make up for remuxer not being able to parse full codec // (logger warning "Unhandled audio codec...") if (audioCodec === 'mp4a') { audioCodec = 'mp4a.40.5'; @@ -1425,9 +1480,9 @@ export default class StreamController audioCodec = 'mp4a.40.2'; this.log(`Android: force audio codec to ${audioCodec}`); } - if (currentLevel.audioCodec && currentLevel.audioCodec !== audioCodec) { + if (levelCodec && levelCodec !== audioCodec) { this.log( - `Swapping manifest audio codec "${currentLevel.audioCodec}" for "${audioCodec}"`, + `Swapping manifest audio codec "${levelCodec}" for "${audioCodec}"`, ); } audio.levelCodec = audioCodec; @@ -1436,7 +1491,7 @@ export default class StreamController `Init audio buffer, container:${ audio.container }, codecs[selected/level/parsed]=[${audioCodec || ''}/${ - currentLevel.audioCodec || '' + levelCodec || '' }/${audio.codec}]`, ); delete tracks.audiovideo; @@ -1480,7 +1535,7 @@ export default class StreamController const trackTypes = Object.keys(tracks); if (trackTypes.length) { this.hls.trigger(Events.BUFFER_CODECS, tracks as BufferCodecsData); - if (!this.hls) { + if (!this.hls as any) { // Exit after fatal tracks error return; } diff --git a/src/controller/subtitle-stream-controller.ts b/src/controller/subtitle-stream-controller.ts index 157ac45da16..93d9ea9dcca 100644 --- a/src/controller/subtitle-stream-controller.ts +++ b/src/controller/subtitle-stream-controller.ts @@ -129,10 +129,12 @@ export class SubtitleStreamController data: SubtitleFragProcessed, ) { const { frag, success } = data; - if (isMediaFragment(frag)) { - this.fragPrevious = frag; + if (!this.fragContextChanged(frag)) { + if (isMediaFragment(frag)) { + this.fragPrevious = frag; + } + this.state = State.IDLE; } - this.state = State.IDLE; if (!success) { return; } @@ -295,8 +297,12 @@ export class SubtitleStreamController this.mediaBuffer = this.mediaBufferTimeRanges; let sliding = 0; if (newDetails.live || track.details?.live) { + if (newDetails.deltaUpdateFailed) { + return; + } const mainDetails = this.mainDetails; - if (newDetails.deltaUpdateFailed || !mainDetails) { + if (!mainDetails) { + this.startFragRequested = false; return; } const mainSlidingStartFragment = mainDetails.fragments[0]; @@ -321,6 +327,10 @@ export class SubtitleStreamController addSliding(newDetails, sliding); } } + // compute start position if we are aligned with the main playlist + if (mainDetails && !this.startFragRequested) { + this.setStartPosition(mainDetails, sliding); + } } track.details = newDetails; this.levelLastLoaded = track; 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 c730911f2fb..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'; @@ -36,7 +31,7 @@ import type { MediaPlaylist } from '../types/media-playlist'; import type { VTTCCs } from '../types/vtt'; import type { CaptionScreen } from '../utils/cea-608-parser'; import type { CuesInterface } from '../utils/cues'; -import type { RationalTimestamp } from '../utils/timescale-conversion'; +import type { TimestampOffset } from '../utils/timescale-conversion'; type TrackProperties = { label: string; @@ -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: RationalTimestamp[] = []; + 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); @@ -191,42 +185,31 @@ export class TimelineController implements ComponentAPI { // Triggered when an initial PTS is found; used for synchronisation of WebVTT. private onInitPtsFound( event: Events.INIT_PTS_FOUND, - { frag, id, initPTS, timescale }: InitPTSFoundData, + { frag, id, initPTS, timescale, trackId }: InitPTSFoundData, ) { const { unparsedVttFrags } = this; if (id === PlaylistLevelType.MAIN) { - this.initPTS[frag.cc] = { baseTime: initPTS, timescale }; + this.initPTS[frag.cc] = { baseTime: initPTS, timescale, trackId }; } // Due to asynchronous processing, initial PTS may arrive later than the first VTT fragments are loaded. // Parse any unparsed fragments upon receiving the initial PTS. if (unparsedVttFrags.length) { this.unparsedVttFrags = []; - unparsedVttFrags.forEach((frag) => { - this.onFragLoaded(Events.FRAG_LOADED, frag as FragLoadedData); - }); - } - } - - 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; + unparsedVttFrags.forEach((data) => { + if (this.initPTS[data.frag.cc]) { + this.onFragLoaded(Events.FRAG_LOADED, data as FragLoadedData); + } else { + this.hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, { + success: false, + frag: data.frag, + error: new Error( + 'Subtitle discontinuity domain does not match main', + ), + }); } - } + }); } - return null; } public createCaptionsTrack(trackName: string) { @@ -243,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, + ); } } @@ -280,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( @@ -312,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 = {}; } @@ -328,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) { @@ -342,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( @@ -503,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; @@ -521,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); @@ -597,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. @@ -635,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 @@ -711,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); + } + }); } } } @@ -758,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/demux/audio/base-audio-demuxer.ts b/src/demux/audio/base-audio-demuxer.ts index 5d9a382d281..4a980e11023 100644 --- a/src/demux/audio/base-audio-demuxer.ts +++ b/src/demux/audio/base-audio-demuxer.ts @@ -14,7 +14,10 @@ import { } from '../../types/demuxer'; import { appendUint8Array } from '../../utils/mp4-tools'; import { dummyTrack } from '../dummy-demuxed-track'; -import type { RationalTimestamp } from '../../utils/timescale-conversion'; +import type { + RationalTimestamp, + TimestampOffset, +} from '../../utils/timescale-conversion'; class BaseAudioDemuxer implements Demuxer { protected _audioTrack?: DemuxedAudioTrack; @@ -22,7 +25,7 @@ class BaseAudioDemuxer implements Demuxer { protected frameIndex: number = 0; protected cachedData: Uint8Array | null = null; protected basePTS: number | null = null; - protected initPTS: RationalTimestamp | null = null; + protected initPTS: TimestampOffset | null = null; protected lastPTS: number | null = null; resetInitSegment( @@ -42,7 +45,7 @@ class BaseAudioDemuxer implements Demuxer { }; } - resetTimeStamp(deaultTimestamp: RationalTimestamp | null) { + resetTimeStamp(deaultTimestamp: TimestampOffset | null) { this.initPTS = deaultTimestamp; this.resetContiguity(); } 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/demux/transmuxer-interface.ts b/src/demux/transmuxer-interface.ts index 3ae429dacce..50f79aa527b 100644 --- a/src/demux/transmuxer-interface.ts +++ b/src/demux/transmuxer-interface.ts @@ -21,7 +21,7 @@ import type Hls from '../hls'; import type { MediaFragment, Part } from '../loader/fragment'; import type { ErrorData, FragDecryptedData } from '../types/events'; import type { ChunkMetadata, TransmuxerResult } from '../types/transmuxer'; -import type { RationalTimestamp } from '../utils/timescale-conversion'; +import type { TimestampOffset } from '../utils/timescale-conversion'; let transmuxerInstanceCount: number = 0; @@ -193,7 +193,7 @@ export default class TransmuxerInterface { duration: number, accurateTimeOffset: boolean, chunkMeta: ChunkMetadata, - defaultInitPTS?: RationalTimestamp, + defaultInitPTS?: TimestampOffset, ) { chunkMeta.transmuxing.start = self.performance.now(); const { instanceNo, transmuxer } = this; diff --git a/src/demux/transmuxer-worker.ts b/src/demux/transmuxer-worker.ts index 361cc1b5cc9..53a9d131d7a 100644 --- a/src/demux/transmuxer-worker.ts +++ b/src/demux/transmuxer-worker.ts @@ -2,7 +2,7 @@ import { EventEmitter } from 'eventemitter3'; import Transmuxer, { isPromise } from '../demux/transmuxer'; import { ErrorDetails, ErrorTypes } from '../errors'; import { Events } from '../events'; -import { enableLogs, type ILogFunction, type ILogger } from '../utils/logger'; +import { enableLogs, type ILogger } from '../utils/logger'; import type { RemuxedTrack, RemuxerResult } from '../types/remuxer'; import type { ChunkMetadata, TransmuxerResult } from '../types/transmuxer'; @@ -196,7 +196,8 @@ function forwardMessage(event, data, instanceNo) { function forwardWorkerLogs(logger: ILogger, instanceNo: number) { for (const logFn in logger) { - const func: ILogFunction = (message?) => { + logger[logFn] = function () { + const message = Array.prototype.join.call(arguments, ' '); forwardMessage( 'workerLog', { @@ -206,7 +207,6 @@ function forwardWorkerLogs(logger: ILogger, instanceNo: number) { instanceNo, ); }; - logger[logFn] = func; } } diff --git a/src/demux/transmuxer.ts b/src/demux/transmuxer.ts index 45d8d8db863..136a5443482 100644 --- a/src/demux/transmuxer.ts +++ b/src/demux/transmuxer.ts @@ -21,7 +21,7 @@ import type { Remuxer } from '../types/remuxer'; import type { ChunkMetadata, TransmuxerResult } from '../types/transmuxer'; import type { TypeSupported } from '../utils/codecs'; import type { ILogger } from '../utils/logger'; -import type { RationalTimestamp } from '../utils/timescale-conversion'; +import type { TimestampOffset } from '../utils/timescale-conversion'; let now: () => number; // performance.now() not available on WebWorker, at least on Safari Desktop @@ -312,7 +312,7 @@ export default class Transmuxer { chunkMeta.transmuxing.executeEnd = now(); } - resetInitialTimestamp(defaultInitPts: RationalTimestamp | null) { + resetInitialTimestamp(defaultInitPts: TimestampOffset | null) { const { demuxer, remuxer } = this; if (!demuxer || !remuxer) { return; @@ -517,14 +517,14 @@ export class TransmuxConfig { public videoCodec?: string; public initSegmentData?: Uint8Array; public duration: number; - public defaultInitPts: RationalTimestamp | null; + public defaultInitPts: TimestampOffset | null; constructor( audioCodec: string | undefined, videoCodec: string | undefined, initSegmentData: Uint8Array | undefined, duration: number, - defaultInitPts?: RationalTimestamp, + defaultInitPts?: TimestampOffset, ) { this.audioCodec = audioCodec; this.videoCodec = videoCodec; diff --git a/src/demux/tsdemuxer.ts b/src/demux/tsdemuxer.ts index aa52b4881a9..863e0baadc1 100644 --- a/src/demux/tsdemuxer.ts +++ b/src/demux/tsdemuxer.ts @@ -191,6 +191,7 @@ class TSDemuxer implements Demuxer { this._audioTrack.segmentCodec = 'aac'; // flush any partial content + this.videoParser = null; this.aacOverFlow = null; this.remainderData = null; this.audioCodec = audioCodec; @@ -291,18 +292,7 @@ class TSDemuxer implements Demuxer { case videoPid: if (stt) { if (videoData && (pes = parsePES(videoData, this.logger))) { - if (this.videoParser === null) { - switch (videoTrack.segmentCodec) { - case 'avc': - this.videoParser = new AvcVideoParser(); - break; - case 'hevc': - if (__USE_M2TS_ADVANCED_CODECS__) { - this.videoParser = new HevcVideoParser(); - } - break; - } - } + this.readyVideoParser(videoTrack.segmentCodec); if (this.videoParser !== null) { this.videoParser.parsePES(videoTrack, textTrack, pes, false); } @@ -477,18 +467,7 @@ class TSDemuxer implements Demuxer { // try to parse last PES packets let pes: PES | null; if (videoData && (pes = parsePES(videoData, this.logger))) { - if (this.videoParser === null) { - switch (videoTrack.segmentCodec) { - case 'avc': - this.videoParser = new AvcVideoParser(); - break; - case 'hevc': - if (__USE_M2TS_ADVANCED_CODECS__) { - this.videoParser = new HevcVideoParser(); - } - break; - } - } + this.readyVideoParser(videoTrack.segmentCodec); if (this.videoParser !== null) { this.videoParser.parsePES( videoTrack as DemuxedVideoTrack, @@ -557,6 +536,16 @@ class TSDemuxer implements Demuxer { return this.decrypt(demuxResult, sampleAes); } + private readyVideoParser(codec: string | undefined) { + if (this.videoParser === null) { + if (codec === 'avc') { + this.videoParser = new AvcVideoParser(); + } else if (__USE_M2TS_ADVANCED_CODECS__ && codec === 'hevc') { + this.videoParser = new HevcVideoParser(); + } + } + } + private decrypt( demuxResult: DemuxerResult, sampleAes: SampleAesDecrypter, diff --git a/src/demux/video/hevc-video-parser.ts b/src/demux/video/hevc-video-parser.ts index 485ee85c6bd..61d00640a4c 100644 --- a/src/demux/video/hevc-video-parser.ts +++ b/src/demux/video/hevc-video-parser.ts @@ -516,10 +516,10 @@ class HevcVideoParser extends BaseVideoParser { eg.readBoolean(); // frame_field_info_present_flag default_display_window_flag = eg.readBoolean(); if (default_display_window_flag) { - pic_left_offset += eg.readUEG(); - pic_right_offset += eg.readUEG(); - pic_top_offset += eg.readUEG(); - pic_bottom_offset += eg.readUEG(); + eg.skipUEG(); + eg.skipUEG(); + eg.skipUEG(); + eg.skipUEG(); } const vui_timing_info_present_flag = eg.readBoolean(); if (vui_timing_info_present_flag) { @@ -604,7 +604,7 @@ class HevcVideoParser extends BaseVideoParser { let width = pic_width_in_luma_samples, height = pic_height_in_luma_samples; - if (conformance_window_flag || default_display_window_flag) { + if (conformance_window_flag) { let chroma_scale_w = 1, chroma_scale_h = 1; if (chroma_format_idc === 1) { diff --git a/src/hls.ts b/src/hls.ts index 55f89a7bcbf..cf61a67b01a 100644 --- a/src/hls.ts +++ b/src/hls.ts @@ -44,6 +44,7 @@ import type TransmuxerInterface from './demux/transmuxer-interface'; import type { HlsEventEmitter, HlsListeners } from './events'; import type FragmentLoader from './loader/fragment-loader'; import type { LevelDetails } from './loader/level-details'; +import type M3U8Parser from './loader/m3u8-parser'; import type TaskLoop from './task-loop'; import type { AttachMediaSourceData } from './types/buffer'; import type { @@ -59,8 +60,11 @@ import type { VideoSelectionOption, } from './types/media-playlist'; import type { BufferInfo, BufferTimeRange } from './utils/buffer-helper'; +import type Cues from './utils/cues'; import type EwmaBandWidthEstimator from './utils/ewma-bandwidth-estimator'; +import type FetchLoader from './utils/fetch-loader'; import type { MediaDecodingInfo } from './utils/mediacapabilities-helper'; +import type XhrLoader from './utils/xhr-loader'; /** * The `Hls` class is the core of the HLS.js library used to instantiate player instances. @@ -229,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, @@ -476,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); } /** @@ -486,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; } @@ -1280,6 +1288,10 @@ export type { TransmuxerInterface, InFlightData, State, + XhrLoader, + FetchLoader, + Cues, + M3U8Parser, }; export type { ABRControllerConfig, @@ -1326,6 +1338,7 @@ export type { } from './controller/error-controller'; export type { HlsAssetPlayer, + HlsAssetPlayerConfig, InterstitialPlayer, } from './controller/interstitial-player'; export type { PlayheadTimes } from './controller/interstitials-controller'; @@ -1524,4 +1537,7 @@ export type { KeySystems, KeySystemFormats, } from './utils/mediakeys-helper'; -export type { RationalTimestamp } from './utils/timescale-conversion'; +export type { + RationalTimestamp, + TimestampOffset, +} from './utils/timescale-conversion'; 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/fragment.ts b/src/loader/fragment.ts index 9431b039a7a..97e5e08cd95 100644 --- a/src/loader/fragment.ts +++ b/src/loader/fragment.ts @@ -177,7 +177,7 @@ export class Fragment extends BaseSegment { // levelkeys are the EXT-X-KEY tags that apply to this segment for decryption // core difference from the private field _decryptdata is the lack of the initialized IV // _decryptdata will set the IV for this segment based on the segment number in the fragment - public levelkeys?: { [key: string]: LevelKey }; + public levelkeys?: { [key: string]: LevelKey | undefined }; // A string representing the fragment type public readonly type: PlaylistLevelType; // A reference to the loader. Set while the fragment is loading, and removed afterwards. Used to abort fragment loading @@ -233,7 +233,7 @@ export class Fragment extends BaseSegment { return total; } } - if (this.byteRange) { + if (this.byteRange.length) { const start = this.byteRange[0]; const end = this.byteRange[1]; if (Number.isFinite(start) && Number.isFinite(end)) { @@ -270,9 +270,11 @@ export class Fragment extends BaseSegment { } else { const keyFormats = Object.keys(this.levelkeys); if (keyFormats.length === 1) { - return (this._decryptdata = this.levelkeys[ - keyFormats[0] - ].getDecryptData(this.sn)); + const levelKey = (this._decryptdata = + this.levelkeys[keyFormats[0]] || null); + if (levelKey) { + return levelKey.getDecryptData(this.sn); + } } else { // Multiple keys. key-loader to call Fragment.setKeyFormat based on selected key-system. } @@ -305,7 +307,7 @@ export class Fragment extends BaseSegment { } else if (this.levelkeys) { const keyFormats = Object.keys(this.levelkeys); const len = keyFormats.length; - if (len > 1 || (len === 1 && this.levelkeys[keyFormats[0]].encrypted)) { + if (len > 1 || (len === 1 && this.levelkeys[keyFormats[0]]?.encrypted)) { return true; } } 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/interstitial-event.ts b/src/loader/interstitial-event.ts index 84e7bc68ea9..7e488fa2665 100644 --- a/src/loader/interstitial-event.ts +++ b/src/loader/interstitial-event.ts @@ -118,14 +118,17 @@ export class InterstitialEvent { } public isAssetPastPlayoutLimit(assetIndex: number): boolean { - if (assetIndex >= this.assetList.length) { + if (assetIndex > 0 && assetIndex >= this.assetList.length) { return true; } const playoutLimit = this.playoutLimit; if (assetIndex <= 0 || isNaN(playoutLimit)) { return false; } - const assetOffset = this.assetList[assetIndex].startOffset; + if (playoutLimit === 0) { + return true; + } + const assetOffset = this.assetList[assetIndex]?.startOffset || 0; return assetOffset > playoutLimit; } @@ -313,6 +316,16 @@ export function getInterstitialUrl( return url; } +export function getNextAssetIndex( + interstitial: InterstitialEvent, + assetListIndex: number, +): number { + while (interstitial.assetList[++assetListIndex]?.error) { + /* no-op */ + } + return assetListIndex; +} + function eventToString(interstitial: InterstitialEvent): string { return `["${interstitial.identifier}" ${interstitial.cue.pre ? '
' : interstitial.cue.post ? '' : ''}${interstitial.timelineStart.toFixed(2)}-${interstitial.resumeTime.toFixed(2)}]`;
 }
diff --git a/src/loader/key-loader.ts b/src/loader/key-loader.ts
index 5042a944d52..c2be92e08e0 100644
--- a/src/loader/key-loader.ts
+++ b/src/loader/key-loader.ts
@@ -1,10 +1,16 @@
 import { LoadError } from './fragment-loader';
 import { ErrorDetails, ErrorTypes } from '../errors';
-import type { HlsConfig } from '../config';
+import { type Fragment, isMediaFragment } from '../loader/fragment';
+import { arrayToHex } from '../utils/hex';
+import { Logger } from '../utils/logger';
+import {
+  getKeySystemsForConfig,
+  keySystemFormatToKeySystemDomain,
+} 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 { Fragment } from '../loader/fragment';
 import type { ComponentAPI } from '../types/component-api';
 import type { KeyLoadedData } from '../types/events';
 import type {
@@ -16,6 +22,8 @@ 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';
 
 export interface KeyLoaderInfo {
@@ -24,18 +32,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;
@@ -46,34 +55,34 @@ 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(
     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({
@@ -83,32 +92,56 @@ export default class KeyLoader implements ComponentAPI {
       frag,
       response,
       error,
-      networkDetails,
+      networkDetails: networkDetails || null,
     });
   }
 
   loadClear(
     loadingFrag: Fragment,
     encryptedFragments: Fragment[],
-  ): void | Promise {
-    if (this.emeController && this.config.emeEnabled) {
-      // access key-system with nearest key on start (loaidng frag is unencrypted)
-      const { sn, cc } = loadingFrag;
-      for (let i = 0; i < encryptedFragments.length; i++) {
-        const frag = encryptedFragments[i];
-        if (
-          cc <= frag.cc &&
-          (sn === 'initSegment' || frag.sn === 'initSegment' || sn < frag.sn)
-        ) {
-          this.emeController
-            .selectKeySystemFormat(frag)
-            .then((keySystemFormat) => {
-              frag.setKeyFormat(keySystemFormat);
-            });
-          break;
+    startFragRequested: boolean,
+  ): null | Promise {
+    if (
+      this.emeController &&
+      this.config.emeEnabled &&
+      !this.emeController.getSelectedKeySystemFormats().length
+    ) {
+      // Access key-system with nearest key on start (loading frag is unencrypted)
+      if (encryptedFragments.length) {
+        for (let i = 0, l = encryptedFragments.length; i < l; i++) {
+          const frag = encryptedFragments[i];
+          // Loading at or before segment with EXT-X-KEY, or first frag loading and last EXT-X-KEY
+          if (
+            (loadingFrag.cc <= frag.cc &&
+              (!isMediaFragment(loadingFrag) ||
+                !isMediaFragment(frag) ||
+                loadingFrag.sn < frag.sn)) ||
+            (!startFragRequested && i == l - 1)
+          ) {
+            return this.emeController
+              .selectKeySystemFormat(frag)
+              .then((keySystemFormat) => {
+                if (!this.emeController) {
+                  return;
+                }
+                frag.setKeyFormat(keySystemFormat);
+                const keySystem =
+                  keySystemFormatToKeySystemDomain(keySystemFormat);
+                if (keySystem) {
+                  return this.emeController.getKeySystemAccess([keySystem]);
+                }
+              });
+          }
+        }
+      }
+      if (this.config.requireKeySystemAccessOnStart) {
+        const keySystemsInConfig = getKeySystemsForConfig(this.config);
+        if (keySystemsInConfig.length) {
+          return this.emeController.getKeySystemAccess(keySystemsInConfig);
         }
       }
     }
+    return null;
   }
 
   load(frag: Fragment): Promise {
@@ -157,22 +190,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 };
           });
       }
@@ -181,7 +215,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,
@@ -189,7 +227,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':
@@ -220,18 +257,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);
   }
@@ -267,10 +305,11 @@ export default class KeyLoader implements ComponentAPI {
           response: LoaderResponse,
           stats: LoaderStats,
           context: KeyLoaderContext,
-          networkDetails: any,
+          networkDetails: NullableNetworkDetails,
         ) => {
           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,
@@ -294,7 +333,7 @@ export default class KeyLoader implements ComponentAPI {
         onError: (
           response: { code: number; text: string },
           context: KeyLoaderContext,
-          networkDetails: any,
+          networkDetails: NullableNetworkDetails,
           stats: LoaderStats,
         ) => {
           this.resetLoader(context);
@@ -314,7 +353,7 @@ export default class KeyLoader implements ComponentAPI {
         onTimeout: (
           stats: LoaderStats,
           context: KeyLoaderContext,
-          networkDetails: any,
+          networkDetails: NullableNetworkDetails,
         ) => {
           this.resetLoader(context);
           reject(
@@ -330,7 +369,7 @@ export default class KeyLoader implements ComponentAPI {
         onAbort: (
           stats: LoaderStats,
           context: KeyLoaderContext,
-          networkDetails: any,
+          networkDetails: NullableNetworkDetails,
         ) => {
           this.resetLoader(context);
           reject(
@@ -355,9 +394,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 43ccef6bd40..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;
 
@@ -17,7 +19,7 @@ export class LevelDetails {
   public fragments: MediaFragment[];
   public fragmentHint?: MediaFragment;
   public partList: Part[] | null = null;
-  public dateRanges: Record;
+  public dateRanges: Record;
   public dateRangeTagCount: number = 0;
   public live: boolean = true;
   public requestScheduled: number = -1;
@@ -88,10 +90,21 @@ 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(
-        this.fragments[this.fragments.length - 1].programDateTime as number,
+        this.fragments[this.fragments.length - 1].programDateTime,
       );
     }
     return false;
@@ -126,14 +139,14 @@ export class LevelDetails {
   }
 
   get fragmentEnd(): number {
-    if (this.fragments?.length) {
+    if (this.fragments.length) {
       return this.fragments[this.fragments.length - 1].end;
     }
     return 0;
   }
 
   get fragmentStart(): number {
-    if (this.fragments?.length) {
+    if (this.fragments.length) {
       return this.fragments[0].start;
     }
     return 0;
diff --git a/src/loader/level-key.ts b/src/loader/level-key.ts
index 9cb5e73255d..aaccbcd4dfe 100644
--- a/src/loader/level-key.ts
+++ b/src/loader/level-key.ts
@@ -1,8 +1,10 @@
+import { arrayValuesMatch, optionalArrayValuesMatch } from '../utils/arrays';
 import { isFullSegmentEncryption } from '../utils/encryption-methods-util';
+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 } = {};
 
@@ -41,6 +43,7 @@ export class LevelKey implements DecryptData {
     format: string,
     formatversions: number[] = [1],
     iv: Uint8Array | null = null,
+    keyId?: string,
   ) {
     this.method = method;
     this.uri = uri;
@@ -50,6 +53,21 @@ export class LevelKey implements DecryptData {
     this.encrypted = method ? method !== 'NONE' : false;
     this.isCommonEncryption =
       this.encrypted && !isFullSegmentEncryption(method);
+    if (keyId?.startsWith('0x')) {
+      this.keyId = new Uint8Array(hexToArrayBuffer(keyId));
+    }
+  }
+
+  public matches(key: LevelKey): boolean {
+    return (
+      key.uri === this.uri &&
+      key.method === this.method &&
+      key.encrypted === this.encrypted &&
+      key.keyFormat === this.keyFormat &&
+      arrayValuesMatch(key.keyFormatVersions, this.keyFormatVersions) &&
+      optionalArrayValuesMatch(key.iv, this.iv) &&
+      optionalArrayValuesMatch(key.keyId, this.keyId)
+    );
   }
 
   public isSupported(): boolean {
@@ -68,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
             );
         }
       }
@@ -113,6 +128,10 @@ export class LevelKey implements DecryptData {
       return this;
     }
 
+    if (this.pssh && this.keyId) {
+      return this;
+    }
+
     // Initialize keyId if possible
     const keyBytes = convertDataUriToArrayBytes(this.uri);
     if (keyBytes) {
@@ -121,12 +140,17 @@ export class LevelKey implements DecryptData {
           // Setting `pssh` on this LevelKey/DecryptData allows HLS.js to generate a session using
           // the playlist-key before the "encrypted" event. (Comment out to only use "encrypted" path.)
           this.pssh = keyBytes;
-          // In case of widevine keyID is embedded in PSSH box. Read Key ID.
-          if (keyBytes.length >= 22) {
-            this.keyId = keyBytes.subarray(
-              keyBytes.length - 22,
-              keyBytes.length - 6,
-            );
+          // 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;
+          }
+          if (!this.keyId) {
+            const offset = keyBytes.length - 22;
+            this.keyId = keyBytes.subarray(offset, offset + 16);
           }
           break;
         case KeySystemFormats.PLAYREADY: {
diff --git a/src/loader/m3u8-parser.ts b/src/loader/m3u8-parser.ts
index 3a2f69bc7c6..a7765fdfc76 100644
--- a/src/loader/m3u8-parser.ts
+++ b/src/loader/m3u8-parser.ts
@@ -43,6 +43,8 @@ type ParsedMultivariantMediaOptions = {
   'CLOSED-CAPTIONS'?: MediaPlaylist[];
 };
 
+type LevelKeys = { [key: string]: LevelKey | undefined };
+
 const MASTER_PLAYLIST_REGEX =
   /#EXT-X-STREAM-INF:([^\r\n]*)(?:[\r\n](?:#[^\r\n]*)?)*([^\r\n]+)|#EXT-X-(SESSION-DATA|SESSION-KEY|DEFINE|CONTENT-STEERING|START):([^\r\n]*)[\r\n]+/g;
 const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
@@ -254,8 +256,9 @@ export default class M3U8Parser {
       const attrs = new AttrList(result[1], parsed) as MediaAttributes;
       const type = attrs.TYPE;
       if (type) {
-        const groups: (typeof groupsByType)[keyof typeof groupsByType] =
-          groupsByType[type];
+        const groups:
+          | (typeof groupsByType)[keyof typeof groupsByType]
+          | undefined = groupsByType[type];
         const medias: MediaPlaylist[] = results[type] || [];
         results[type] = medias;
         const lang = attrs.LANGUAGE;
@@ -328,7 +331,7 @@ export default class M3U8Parser {
     let frag: Fragment = new Fragment(type, base);
     let result: RegExpExecArray | RegExpMatchArray | null;
     let i: number;
-    let levelkeys: { [key: string]: LevelKey } | undefined;
+    let levelkeys: LevelKeys | undefined;
     let firstPdtIndex = -1;
     let createNextFrag = false;
     let nextByteRange: string | null = null;
@@ -351,7 +354,7 @@ export default class M3U8Parser {
         frag = new Fragment(type, base);
         // setup the next fragment for part loading
         frag.playlistOffset = totalduration;
-        frag.start = totalduration;
+        frag.setStart(totalduration);
         frag.sn = currentSN;
         frag.cc = discontinuityCounter;
         if (currentBitrate) {
@@ -383,7 +386,7 @@ export default class M3U8Parser {
         // url
         if (Number.isFinite(frag.duration)) {
           frag.playlistOffset = totalduration;
-          frag.start = totalduration;
+          frag.setStart(totalduration);
           if (levelkeys) {
             setFragLevelKeys(frag, levelkeys, level);
           }
@@ -414,7 +417,7 @@ export default class M3U8Parser {
           continue;
         }
         for (i = 1; i < result.length; i++) {
-          if (result[i] !== undefined) {
+          if ((result[i] as any) !== undefined) {
             break;
           }
         }
@@ -572,10 +575,14 @@ export default class M3U8Parser {
               if (!levelkeys) {
                 levelkeys = {};
               }
-              if (levelkeys[levelKey.keyFormat]) {
-                levelkeys = Object.assign({}, levelkeys);
+              const currentKey = levelkeys[levelKey.keyFormat];
+              // Ignore duplicate playlist KEY tags
+              if (!currentKey?.matches(levelKey)) {
+                if (currentKey) {
+                  levelkeys = Object.assign({}, levelkeys);
+                }
+                levelkeys[levelKey.keyFormat] = levelKey;
               }
-              levelkeys[levelKey.keyFormat] = levelKey;
             } else {
               logger.warn(`[Keys] Ignoring invalid EXT-X-KEY tag: "${value1}"`);
             }
@@ -716,9 +723,6 @@ export default class M3U8Parser {
       if (!level.live) {
         lastFragment.endList = true;
       }
-      if (firstFragment && level.startCC === undefined) {
-        level.startCC = firstFragment.cc;
-      }
       /**
        * Backfill any missing PDT values
        * "If the first EXT-X-PROGRAM-DATE-TIME tag in a Playlist appears after
@@ -734,9 +738,6 @@ export default class M3U8Parser {
           programDateTimes.unshift(firstFragment as MediaFragment);
         }
       }
-    } else {
-      level.endSN = 0;
-      level.startCC = 0;
     }
     if (level.fragmentHint) {
       totalduration += level.fragmentHint.duration;
@@ -757,15 +758,28 @@ 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) {
+    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;
   const dateRangeIds = Object.keys(details.dateRanges);
   for (let i = dateRangeIds.length; i--; ) {
-    const dateRange = details.dateRanges[dateRangeIds[i]];
+    const dateRange = details.dateRanges[dateRangeIds[i]]!;
     const startDateTime = dateRange.startDate.getTime();
     dateRange.tagAnchor = lastProgramDateTime.ref;
     for (let j = programDateTimeCount; j--; ) {
+      if (programDateTimes[j]?.sn < details.startSN) {
+        break;
+      }
       const fragIndex = findFragmentWithStartDate(
         details,
         startDateTime,
@@ -788,7 +802,7 @@ function findFragmentWithStartDate(
   index: number,
   endTime: number,
 ): number {
-  const pdtFragment = programDateTimes[index];
+  const pdtFragment = programDateTimes[index] as MediaFragment | undefined;
   if (pdtFragment) {
     // find matching range between PDT tags
     const pdtStart = pdtFragment.programDateTime as number;
@@ -798,6 +812,9 @@ function findFragmentWithStartDate(
       if (startDateTime <= pdtStart + durationBetweenPdt * 1000) {
         // map to fragment with date-time range
         const startIndex = programDateTimes[index].sn - details.startSN;
+        if (startIndex < 0) {
+          return -1;
+        }
         const fragments = details.fragments;
         if (fragments.length > programDateTimes.length) {
           const endSegment =
@@ -853,6 +870,7 @@ function parseKey(
     decryptkeyformat,
     keyFormatVersions,
     decryptiv,
+    keyAttrs.KEYID,
   );
 }
 
@@ -926,7 +944,7 @@ function setInitSegment(
   frag: Fragment,
   mapAttrs: AttrList,
   id: number,
-  levelkeys: { [key: string]: LevelKey } | undefined,
+  levelkeys: LevelKeys | undefined,
 ) {
   frag.relurl = mapAttrs.URI;
   if (mapAttrs.BYTERANGE) {
@@ -942,7 +960,7 @@ function setInitSegment(
 
 function setFragLevelKeys(
   frag: Fragment,
-  levelkeys: { [key: string]: LevelKey },
+  levelkeys: LevelKeys,
   level: LevelDetails,
 ) {
   frag.levelkeys = levelkeys;
@@ -952,7 +970,7 @@ function setFragLevelKeys(
       encryptedFragments[encryptedFragments.length - 1].levelkeys !==
         levelkeys) &&
     Object.keys(levelkeys).some(
-      (format) => levelkeys![format].isCommonEncryption,
+      (format) => levelkeys[format]!.isCommonEncryption,
     )
   ) {
     encryptedFragments.push(frag);
diff --git a/src/loader/playlist-loader.ts b/src/loader/playlist-loader.ts
index 168db972e9e..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;
@@ -711,7 +715,7 @@ class PlaylistLoader implements NetworkComponentAPI {
     }
     const error = levelDetails.playlistParsingError;
     if (error) {
-      this.hls.logger.warn(error);
+      this.hls.logger.warn(`${error} ${levelDetails.url}`);
       if (!hls.config.ignorePlaylistParsingErrors) {
         hls.trigger(Events.ERROR, {
           type: ErrorTypes.NETWORK_ERROR,
diff --git a/src/remux/mp4-remuxer.ts b/src/remux/mp4-remuxer.ts
index 3e761f585e0..9bd59545f98 100644
--- a/src/remux/mp4-remuxer.ts
+++ b/src/remux/mp4-remuxer.ts
@@ -3,6 +3,7 @@ import MP4 from './mp4-generator';
 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 type { HlsConfig } from '../config';
 import type { HlsEventEmitter } from '../events';
@@ -26,8 +27,10 @@ import type {
 } from '../types/remuxer';
 import type { TrackSet } from '../types/track';
 import type { TypeSupported } from '../utils/codecs';
-import type { ILogger } from '../utils/logger';
-import type { RationalTimestamp } from '../utils/timescale-conversion';
+import type {
+  RationalTimestamp,
+  TimestampOffset,
+} from '../utils/timescale-conversion';
 
 const MAX_SILENT_FRAME_DURATION = 10 * 1000; // 10 seconds
 const AAC_SAMPLES_PER_FRAME = 1024;
@@ -57,16 +60,15 @@ function createMp4Sample(
     },
   };
 }
-export default class MP4Remuxer implements Remuxer {
-  private readonly logger: ILogger;
+export default class MP4Remuxer extends Logger implements Remuxer {
   private readonly observer: HlsEventEmitter;
   private readonly config: HlsConfig;
   private readonly typeSupported: TypeSupported;
   private ISGenerated: boolean = false;
-  private _initPTS: RationalTimestamp | null = null;
-  private _initDTS: RationalTimestamp | null = null;
-  private nextAvcDts: number | null = null;
-  private nextAudioPts: number | null = null;
+  private _initPTS: TimestampOffset | null = null;
+  private _initDTS: TimestampOffset | null = null;
+  private nextVideoTs: number | null = null;
+  private nextAudioTs: number | null = null;
   private videoSampleDuration: number | null = null;
   private isAudioContiguous: boolean = false;
   private isVideoContiguous: boolean = false;
@@ -82,10 +84,10 @@ export default class MP4Remuxer implements Remuxer {
     typeSupported: TypeSupported,
     logger: ILogger,
   ) {
+    super('mp4-remuxer', logger);
     this.observer = observer;
     this.config = config;
     this.typeSupported = typeSupported;
-    this.logger = logger;
     this.ISGenerated = false;
 
     if (chromeVersion === null) {
@@ -104,19 +106,19 @@ export default class MP4Remuxer implements Remuxer {
     this.config = this.videoTrackConfig = this._initPTS = this._initDTS = null;
   }
 
-  resetTimeStamp(defaultTimeStamp: RationalTimestamp | null) {
-    this.logger.log('[mp4-remuxer]: initPTS & initDTS reset');
+  resetTimeStamp(defaultTimeStamp: TimestampOffset | null) {
+    this.log('initPTS & initDTS reset');
     this._initPTS = this._initDTS = defaultTimeStamp;
   }
 
   resetNextTimestamp() {
-    this.logger.log('[mp4-remuxer]: reset next timestamp');
+    this.log('reset next timestamp');
     this.isVideoContiguous = false;
     this.isAudioContiguous = false;
   }
 
   resetInitSegment() {
-    this.logger.log('[mp4-remuxer]: ISGenerated flag reset');
+    this.log('ISGenerated flag reset');
     this.ISGenerated = false;
     this.videoTrackConfig = undefined;
   }
@@ -140,7 +142,7 @@ export default class MP4Remuxer implements Remuxer {
       return pts;
     }, firstPts);
     if (rolloverDetected) {
-      this.logger.debug('PTS rollover detected');
+      this.debug('PTS rollover detected');
     }
     return startPTS;
   }
@@ -190,7 +192,7 @@ export default class MP4Remuxer implements Remuxer {
               videoTrack.pixelRatio?.[0] !== config.pixelRatio?.[0] ||
               videoTrack.pixelRatio?.[1] !== config.pixelRatio?.[1])) ||
           (!config && enoughVideoSamples) ||
-          (this.nextAudioPts === null && enoughAudioSamples)
+          (this.nextAudioTs === null && enoughAudioSamples)
         ) {
           this.resetInitSegment();
         }
@@ -213,8 +215,8 @@ export default class MP4Remuxer implements Remuxer {
         if (!isVideoContiguous && this.config.forceKeyFrameOnDiscontinuity) {
           independent = true;
           if (firstKeyFrameIndex > 0) {
-            this.logger.warn(
-              `[mp4-remuxer]: Dropped ${firstKeyFrameIndex} out of ${length} video samples due to a missing keyframe`,
+            this.warn(
+              `Dropped ${firstKeyFrameIndex} out of ${length} video samples due to a missing keyframe`,
             );
             const startPTS = this.getVideoStartPts(videoTrack.samples);
             videoTrack.samples = videoTrack.samples.slice(firstKeyFrameIndex);
@@ -224,9 +226,7 @@ export default class MP4Remuxer implements Remuxer {
               videoTrack.inputTimeScale;
             firstKeyFramePTS = videoTimeOffset;
           } else if (firstKeyFrameIndex === -1) {
-            this.logger.warn(
-              `[mp4-remuxer]: No keyframe found out of ${length} video samples`,
-            );
+            this.warn(`No keyframe found out of ${length} video samples`);
             independent = false;
           }
         }
@@ -250,9 +250,7 @@ export default class MP4Remuxer implements Remuxer {
         if (enoughAudioSamples) {
           // if initSegment was generated without audio samples, regenerate it again
           if (!audioTrack.samplerate) {
-            this.logger.warn(
-              '[mp4-remuxer]: regenerate InitSegment as audio detected',
-            );
+            this.warn('regenerate InitSegment as audio detected');
             initSegment = this.generateIS(
               audioTrack,
               videoTrack,
@@ -275,9 +273,7 @@ export default class MP4Remuxer implements Remuxer {
             const audioTrackLength = audio ? audio.endPTS - audio.startPTS : 0;
             // if initSegment was generated without video samples, regenerate it again
             if (!videoTrack.inputTimeScale) {
-              this.logger.warn(
-                '[mp4-remuxer]: regenerate InitSegment as video detected',
-              );
+              this.warn('regenerate InitSegment as video detected');
               initSegment = this.generateIS(
                 audioTrack,
                 videoTrack,
@@ -354,6 +350,7 @@ export default class MP4Remuxer implements Remuxer {
     let initPTS: number | undefined;
     let initDTS: number | undefined;
     let timescale: number | undefined;
+    let trackId: number = -1;
 
     if (computePTSDTS) {
       initPTS = initDTS = Infinity;
@@ -394,6 +391,7 @@ export default class MP4Remuxer implements Remuxer {
         },
       };
       if (computePTSDTS) {
+        trackId = audioTrack.id;
         timescale = audioTrack.inputTimeScale;
         if (!_initPTS || timescale !== _initPTS.timescale) {
           // remember first PTS of this demuxing context. for audio, PTS = DTS
@@ -420,6 +418,7 @@ export default class MP4Remuxer implements Remuxer {
         },
       };
       if (computePTSDTS) {
+        trackId = videoTrack.id;
         timescale = videoTrack.inputTimeScale;
         if (!_initPTS || timescale !== _initPTS.timescale) {
           const startPTS = this.getVideoStartPts(videoSamples);
@@ -443,13 +442,23 @@ export default class MP4Remuxer implements Remuxer {
     if (Object.keys(tracks).length) {
       this.ISGenerated = true;
       if (computePTSDTS) {
+        if (_initPTS) {
+          this.warn(
+            `Timestamps at playlist time: ${accurateTimeOffset ? '' : '~'}${timeOffset} ${initPTS! / timescale!} != initPTS: ${_initPTS.baseTime / _initPTS.timescale} (${_initPTS.baseTime}/${_initPTS.timescale}) trackId: ${_initPTS.trackId}`,
+          );
+        }
+        this.log(
+          `Found initPTS at playlist time: ${timeOffset} offset: ${initPTS! / timescale!} (${initPTS}/${timescale}) trackId: ${trackId}`,
+        );
         this._initPTS = {
           baseTime: initPTS as number,
           timescale: timescale as number,
+          trackId: trackId as number,
         };
         this._initDTS = {
           baseTime: initDTS as number,
           timescale: timescale as number,
+          trackId: trackId as number,
         };
       } else {
         initPTS = timescale = undefined;
@@ -459,6 +468,7 @@ export default class MP4Remuxer implements Remuxer {
         tracks,
         initPTS,
         timescale,
+        trackId,
       };
     }
   }
@@ -474,7 +484,8 @@ export default class MP4Remuxer implements Remuxer {
     const outputSamples: Array = [];
     const nbSamples = inputSamples.length;
     const initPTS = this._initPTS as RationalTimestamp;
-    let nextAvcDts = this.nextAvcDts;
+    const initTime = (initPTS.baseTime * timeScale) / initPTS.timescale;
+    let nextVideoTs = this.nextVideoTs;
     let offset = 8;
     let mp4SampleDuration = this.videoSampleDuration;
     let firstDTS;
@@ -484,31 +495,31 @@ export default class MP4Remuxer implements Remuxer {
     let sortSamples = false;
 
     // if parsed fragment is contiguous with last one, let's use last DTS value as reference
-    if (!contiguous || nextAvcDts === null) {
-      const pts = timeOffset * timeScale;
+    if (!contiguous || nextVideoTs === null) {
+      const pts = initTime + timeOffset * timeScale;
       const cts =
         inputSamples[0].pts -
         normalizePts(inputSamples[0].dts, inputSamples[0].pts);
       if (
         chromeVersion &&
-        nextAvcDts !== null &&
-        Math.abs(pts - cts - nextAvcDts) < 15000
+        nextVideoTs !== null &&
+        Math.abs(pts - cts - (nextVideoTs + initTime)) < 15000
       ) {
         // treat as contigous to adjust samples that would otherwise produce video buffer gaps in Chrome
         contiguous = true;
       } else {
         // if not contiguous, let's use target timeOffset
-        nextAvcDts = pts - cts;
+        nextVideoTs = pts - cts - initTime;
       }
     }
 
     // PTS is coded on 33bits, and can loop from -2^32 to 2^32
     // PTSNormalize will make PTS/DTS value monotonic, we use last known DTS value as reference value
-    const initTime = (initPTS.baseTime * timeScale) / initPTS.timescale;
+    const nextVideoPts = nextVideoTs + initTime;
     for (let i = 0; i < nbSamples; i++) {
       const sample = inputSamples[i];
-      sample.pts = normalizePts(sample.pts - initTime, nextAvcDts);
-      sample.dts = normalizePts(sample.dts - initTime, nextAvcDts);
+      sample.pts = normalizePts(sample.pts, nextVideoPts);
+      sample.dts = normalizePts(sample.dts, nextVideoPts);
       if (sample.dts < inputSamples[i > 0 ? i - 1 : i].dts) {
         sortSamples = true;
       }
@@ -537,12 +548,12 @@ export default class MP4Remuxer implements Remuxer {
     // if fragment are contiguous, detect hole/overlapping between fragments
     if (contiguous) {
       // check timestamp continuity across consecutive fragments (this is to remove inter-fragment gap/hole)
-      const delta = firstDTS - nextAvcDts;
+      const delta = firstDTS - nextVideoPts;
       const foundHole = delta > averageSampleDuration;
       const foundOverlap = delta < -1;
       if (foundHole || foundOverlap) {
         if (foundHole) {
-          this.logger.warn(
+          this.warn(
             `${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(
               delta,
               true,
@@ -551,7 +562,7 @@ export default class MP4Remuxer implements Remuxer {
             )}`,
           );
         } else {
-          this.logger.warn(
+          this.warn(
             `${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(
               -delta,
               true,
@@ -562,10 +573,10 @@ export default class MP4Remuxer implements Remuxer {
         }
         if (
           !foundOverlap ||
-          nextAvcDts >= inputSamples[0].pts ||
+          nextVideoPts >= inputSamples[0].pts ||
           chromeVersion
         ) {
-          firstDTS = nextAvcDts;
+          firstDTS = nextVideoPts;
           const firstPTS = inputSamples[0].pts - delta;
           if (foundHole) {
             inputSamples[0].dts = firstDTS;
@@ -594,7 +605,7 @@ export default class MP4Remuxer implements Remuxer {
               }
             }
           }
-          this.logger.log(
+          this.log(
             `Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(
               firstPTS,
               true,
@@ -696,7 +707,7 @@ export default class MP4Remuxer implements Remuxer {
           i > 0
             ? VideoSample.pts - inputSamples[i - 1].pts
             : averageSampleDuration;
-        if (config.stretchShortVideoTrack && this.nextAudioPts !== null) {
+        if (config.stretchShortVideoTrack && this.nextAudioTs !== null) {
           // In some cases, a segment's audio track duration may exceed the video track duration.
           // Since we've already remuxed audio, and we know how long the audio track is, we look to
           // see if the delta to the next segment is longer than maxBufferHole.
@@ -706,7 +717,7 @@ export default class MP4Remuxer implements Remuxer {
           const deltaToFrameEnd =
             (audioTrackLength
               ? minPTS + audioTrackLength * timeScale
-              : this.nextAudioPts) - VideoSample.pts;
+              : this.nextAudioTs + initTime) - VideoSample.pts;
           if (deltaToFrameEnd > gapTolerance) {
             // We subtract lastFrameDuration from deltaToFrameEnd to try to prevent any video
             // frame overlap. maxBufferHole should be >> lastFrameDuration anyway.
@@ -716,8 +727,8 @@ export default class MP4Remuxer implements Remuxer {
             } else {
               stretchedLastFrame = true;
             }
-            this.logger.log(
-              `[mp4-remuxer]: It is approximately ${
+            this.log(
+              `It is approximately ${
                 deltaToFrameEnd / 90
               } ms to the next segment; using duration ${
                 mp4SampleDuration / 90
@@ -765,7 +776,7 @@ export default class MP4Remuxer implements Remuxer {
           averageSampleDuration / maxDtsDelta < 0.025 &&
           outputSamples[0].cts === 0
         ) {
-          this.logger.warn(
+          this.warn(
             'Found irregular gaps in sample duration. Using PTS instead of DTS to determine MP4 sample duration.',
           );
           let dts = firstDTS;
@@ -791,7 +802,8 @@ export default class MP4Remuxer implements Remuxer {
       stretchedLastFrame || !mp4SampleDuration
         ? averageSampleDuration
         : mp4SampleDuration;
-    this.nextAvcDts = nextAvcDts = lastDTS + mp4SampleDuration;
+    const endDTS = lastDTS + mp4SampleDuration;
+    this.nextVideoTs = nextVideoTs = endDTS - initTime;
     this.videoSampleDuration = mp4SampleDuration;
     this.isVideoContiguous = true;
     const moof = MP4.moof(
@@ -805,10 +817,10 @@ export default class MP4Remuxer implements Remuxer {
     const data = {
       data1: moof,
       data2: mdat,
-      startPTS: minPTS / timeScale,
-      endPTS: (maxPTS + mp4SampleDuration) / timeScale,
-      startDTS: firstDTS / timeScale,
-      endDTS: (nextAvcDts as number) / timeScale,
+      startPTS: (minPTS - initTime) / timeScale,
+      endPTS: (maxPTS + mp4SampleDuration - initTime) / timeScale,
+      startDTS: (firstDTS - initTime) / timeScale,
+      endDTS: nextVideoTs / timeScale,
       type,
       hasAudio: false,
       hasVideo: true,
@@ -853,7 +865,7 @@ export default class MP4Remuxer implements Remuxer {
 
     let inputSamples: Array = track.samples;
     let offset: number = rawMPEG ? 0 : 8;
-    let nextAudioPts: number = this.nextAudioPts || -1;
+    let nextAudioTs: number = this.nextAudioTs || -1;
 
     // window.audioSamples ? window.audioSamples.push(inputSamples.map(s => s.pts)) : (window.audioSamples = [inputSamples.map(s => s.pts)]);
 
@@ -865,26 +877,26 @@ export default class MP4Remuxer implements Remuxer {
     // contiguous fragments are consecutive fragments from same quality level (same level, new SN = old SN + 1)
     // this helps ensuring audio continuity
     // and this also avoids audio glitches/cut when switching quality, or reporting wrong duration on first audio frame
-    const timeOffsetMpegTS = timeOffset * inputTimeScale;
     const initTime = (initPTS.baseTime * inputTimeScale) / initPTS.timescale;
+    const timeOffsetMpegTS = initTime + timeOffset * inputTimeScale;
     this.isAudioContiguous = contiguous =
       contiguous ||
       ((inputSamples.length &&
-        nextAudioPts > 0 &&
+        nextAudioTs > 0 &&
         ((accurateTimeOffset &&
-          Math.abs(timeOffsetMpegTS - nextAudioPts) < 9000) ||
+          Math.abs(timeOffsetMpegTS - (nextAudioTs + initTime)) < 9000) ||
           Math.abs(
-            normalizePts(inputSamples[0].pts - initTime, timeOffsetMpegTS) -
-              nextAudioPts,
+            normalizePts(inputSamples[0].pts, timeOffsetMpegTS) -
+              (nextAudioTs + initTime),
           ) <
             20 * inputSampleDuration)) as boolean);
 
     // compute normalized PTS
     inputSamples.forEach(function (sample) {
-      sample.pts = normalizePts(sample.pts - initTime, timeOffsetMpegTS);
+      sample.pts = normalizePts(sample.pts, timeOffsetMpegTS);
     });
 
-    if (!contiguous || nextAudioPts < 0) {
+    if (!contiguous || nextAudioTs < 0) {
       // 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
@@ -896,14 +908,14 @@ export default class MP4Remuxer implements Remuxer {
       }
 
       if (videoTimeOffset === 0) {
-        // Set the start to 0 to match video so that start gaps larger than inputSampleDuration are filled with silence
-        nextAudioPts = 0;
+        // Set the start to match video so that start gaps larger than inputSampleDuration are filled with silence
+        nextAudioTs = 0;
       } else if (accurateTimeOffset && !alignedWithVideo) {
         // When not seeking, not live, and LevelDetails.PTSKnown, use fragment start as predicted next audio PTS
-        nextAudioPts = Math.max(0, timeOffsetMpegTS);
+        nextAudioTs = Math.max(0, timeOffsetMpegTS - initTime);
       } else {
         // if frags are not contiguous and if we cant trust time offset, let's use first sample PTS as next audio PTS
-        nextAudioPts = inputSamples[0].pts;
+        nextAudioTs = inputSamples[0].pts - initTime;
       }
     }
 
@@ -915,7 +927,11 @@ export default class MP4Remuxer implements Remuxer {
 
     if (track.segmentCodec === 'aac') {
       const maxAudioFramesDrift = this.config.maxAudioFramesDrift;
-      for (let i = 0, nextPts = nextAudioPts; i < inputSamples.length; i++) {
+      for (
+        let i = 0, nextPts = nextAudioTs + initTime;
+        i < inputSamples.length;
+        i++
+      ) {
         // First, let's see how far off this frame is from where we expect it to be
         const sample = inputSamples[i];
         const pts = sample.pts;
@@ -928,14 +944,15 @@ export default class MP4Remuxer implements Remuxer {
           alignedWithVideo
         ) {
           if (i === 0) {
-            this.logger.warn(
+            this.warn(
               `Audio frame @ ${(pts / inputTimeScale).toFixed(
                 3,
-              )}s overlaps nextAudioPts by ${Math.round(
+              )}s overlaps marker by ${Math.round(
                 (1000 * delta) / inputTimeScale,
               )} ms.`,
             );
-            this.nextAudioPts = nextAudioPts = nextPts = pts;
+            this.nextAudioTs = nextAudioTs = pts - initTime;
+            nextPts = pts;
           }
         } // eslint-disable-line brace-style
 
@@ -953,35 +970,35 @@ export default class MP4Remuxer implements Remuxer {
           // Adjust nextPts so that silent samples are aligned with media pts. This will prevent media samples from
           // later being shifted if nextPts is based on timeOffset and delta is not a multiple of inputSampleDuration.
           nextPts = pts - missing * inputSampleDuration;
-          if (nextPts < 0) {
+          while (nextPts < 0 && missing && inputSampleDuration) {
             missing--;
             nextPts += inputSampleDuration;
           }
           if (i === 0) {
-            this.nextAudioPts = nextAudioPts = nextPts;
+            this.nextAudioTs = nextAudioTs = nextPts - initTime;
           }
-          this.logger.warn(
-            `[mp4-remuxer]: Injecting ${missing} audio frame @ ${(
-              nextPts / inputTimeScale
+          this.warn(
+            `Injecting ${missing} audio frames @ ${(
+              (nextPts - initTime) /
+              inputTimeScale
             ).toFixed(3)}s due to ${Math.round(
               (1000 * delta) / inputTimeScale,
             )} ms gap.`,
           );
           for (let j = 0; j < missing; j++) {
-            const newStamp = Math.max(nextPts as number, 0);
             let fillFrame = AAC.getSilentFrame(
               track.parsedCodec || track.manifestCodec || track.codec,
               track.channelCount,
             );
             if (!fillFrame) {
-              this.logger.log(
-                '[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.',
+              this.log(
+                'Unable to get silent frame for given audio codec; duplicating last frame instead.',
               );
               fillFrame = sample.unit.subarray();
             }
             inputSamples.splice(i, 0, {
               unit: fillFrame,
-              pts: newStamp,
+              pts: nextPts,
             });
             nextPts += inputSampleDuration;
             i++;
@@ -1011,7 +1028,7 @@ export default class MP4Remuxer implements Remuxer {
       } else {
         if (contiguous && track.segmentCodec === 'aac') {
           // set PTS/DTS to expected PTS/DTS
-          pts = nextAudioPts;
+          pts = nextAudioTs + initTime;
         }
         // remember first PTS of our audioSamples
         firstPTS = pts;
@@ -1060,8 +1077,8 @@ export default class MP4Remuxer implements Remuxer {
 
     // The next audio sample PTS should be equal to last sample PTS + duration
     const lastSample = outputSamples[outputSamples.length - 1];
-    this.nextAudioPts = nextAudioPts =
-      lastPTS! + scaleFactor * lastSample.duration;
+    nextAudioTs = (lastPTS as number) - initTime;
+    this.nextAudioTs = nextAudioTs + scaleFactor * lastSample.duration;
 
     // Set the track samples from inputSamples to outputSamples before remuxing
     const moof = rawMPEG
@@ -1074,8 +1091,8 @@ export default class MP4Remuxer implements Remuxer {
 
     // Clear the track samples. This also clears the samples array in the demuxer, since the reference is shared
     track.samples = [];
-    const start = firstPTS! / inputTimeScale;
-    const end = nextAudioPts / inputTimeScale;
+    const start = (firstPTS! - initTime) / inputTimeScale;
+    const end = nextAudioTs / inputTimeScale;
     const type: SourceBufferName = 'audio';
     const audioData = {
       data1: moof,
@@ -1130,8 +1147,8 @@ function findKeyframeIndex(samples: Array): number {
 export function flushTextTrackMetadataCueSamples(
   track: DemuxedMetadataTrack,
   timeOffset: number,
-  initPTS: RationalTimestamp,
-  initDTS: RationalTimestamp,
+  initPTS: TimestampOffset,
+  initDTS: TimestampOffset,
 ): RemuxedMetadata | undefined {
   const length = track.samples.length;
   if (!length) {
diff --git a/src/remux/passthrough-remuxer.ts b/src/remux/passthrough-remuxer.ts
index 6b3900ee84e..488b504254c 100644
--- a/src/remux/passthrough-remuxer.ts
+++ b/src/remux/passthrough-remuxer.ts
@@ -4,14 +4,9 @@ import {
 } from './mp4-remuxer';
 import { ElementaryStreamTypes } from '../loader/fragment';
 import { getCodecCompatibleName } from '../utils/codecs';
-import { type ILogger, logger } from '../utils/logger';
+import { type ILogger, Logger } from '../utils/logger';
 import { patchEncyptionData } from '../utils/mp4-tools';
-import {
-  getDuration,
-  getStartDTS,
-  offsetStartDTS,
-  parseInitSegment,
-} from '../utils/mp4-tools';
+import { getSampleData, parseInitSegment } from '../utils/mp4-tools';
 import type { HlsConfig } from '../config';
 import type { HlsEventEmitter } from '../events';
 import type { DecryptData } from '../loader/level-key';
@@ -29,18 +24,18 @@ import type {
 } from '../types/remuxer';
 import type { TrackSet } from '../types/track';
 import type { TypeSupported } from '../utils/codecs';
-import type { InitData, InitDataTrack } from '../utils/mp4-tools';
-import type { RationalTimestamp } from '../utils/timescale-conversion';
+import type { InitData, InitDataTrack, TrackTimes } from '../utils/mp4-tools';
+import type { TimestampOffset } from '../utils/timescale-conversion';
 
-class PassThroughRemuxer implements Remuxer {
-  private readonly logger: ILogger;
+class PassThroughRemuxer extends Logger implements Remuxer {
   private emitInitSegment: boolean = false;
   private audioCodec?: string;
   private videoCodec?: string;
   private initData?: InitData;
-  private initPTS: RationalTimestamp | null = null;
+  private initPTS: TimestampOffset | null = null;
   private initTracks?: TrackSet;
   private lastEndTime: number | null = null;
+  private isVideoContiguous: boolean = false;
 
   constructor(
     observer: HlsEventEmitter,
@@ -48,84 +43,111 @@ class PassThroughRemuxer implements Remuxer {
     typeSupported: TypeSupported,
     logger: ILogger,
   ) {
-    this.logger = logger;
+    super('passthrough-remuxer', logger);
   }
 
   public destroy() {}
 
-  public resetTimeStamp(defaultInitPTS: RationalTimestamp | null) {
-    this.initPTS = defaultInitPTS;
+  public resetTimeStamp(defaultInitPTS: TimestampOffset | null) {
     this.lastEndTime = null;
+    const initPTS = this.initPTS;
+    if (initPTS && defaultInitPTS) {
+      if (
+        initPTS.baseTime === defaultInitPTS.baseTime &&
+        initPTS.timescale === defaultInitPTS.timescale
+      ) {
+        return;
+      }
+    }
+    this.initPTS = defaultInitPTS;
   }
 
   public resetNextTimestamp() {
+    this.isVideoContiguous = false;
     this.lastEndTime = null;
   }
 
   public resetInitSegment(
-    initSegment: Uint8Array | undefined,
+    initSegment: Uint8Array | undefined,
     audioCodec: string | undefined,
     videoCodec: string | undefined,
     decryptdata: DecryptData | null,
   ) {
     this.audioCodec = audioCodec;
     this.videoCodec = videoCodec;
-    this.generateInitSegment(patchEncyptionData(initSegment, decryptdata));
+    this.generateInitSegment(initSegment, decryptdata);
     this.emitInitSegment = true;
   }
 
-  private generateInitSegment(initSegment: Uint8Array | undefined): void {
+  private generateInitSegment(
+    initSegment: Uint8Array | undefined,
+    decryptdata?: DecryptData | null,
+  ) {
     let { audioCodec, videoCodec } = this;
     if (!initSegment?.byteLength) {
       this.initTracks = undefined;
       this.initData = undefined;
       return;
     }
-    const initData = (this.initData = parseInitSegment(initSegment));
+    const { audio, video } = (this.initData = parseInitSegment(initSegment));
+
+    if (decryptdata) {
+      patchEncyptionData(initSegment, decryptdata);
+    } else {
+      const eitherTrack = audio || video;
+      if (eitherTrack?.encrypted) {
+        this.warn(
+          `Init segment with encrypted track with has no key ("${eitherTrack.codec}")!`,
+        );
+      }
+    }
 
     // Get codec from initSegment
-    if (initData.audio) {
+    if (audio) {
       audioCodec = getParsedTrackCodec(
-        initData.audio,
+        audio,
         ElementaryStreamTypes.AUDIO,
+        this,
       );
     }
 
-    if (initData.video) {
+    if (video) {
       videoCodec = getParsedTrackCodec(
-        initData.video,
+        video,
         ElementaryStreamTypes.VIDEO,
+        this,
       );
     }
 
     const tracks: TrackSet = {};
-    if (initData.audio && initData.video) {
+    if (audio && video) {
       tracks.audiovideo = {
         container: 'video/mp4',
         codec: audioCodec + ',' + videoCodec,
-        supplemental: initData.video.supplemental,
+        supplemental: video.supplemental,
+        encrypted: video.encrypted,
         initSegment,
         id: 'main',
       };
-    } else if (initData.audio) {
+    } else if (audio) {
       tracks.audio = {
         container: 'audio/mp4',
         codec: audioCodec,
+        encrypted: audio.encrypted,
         initSegment,
         id: 'audio',
       };
-    } else if (initData.video) {
+    } else if (video) {
       tracks.video = {
         container: 'video/mp4',
         codec: videoCodec,
-        supplemental: initData.video.supplemental,
+        supplemental: video.supplemental,
+        encrypted: video.encrypted,
         initSegment,
         id: 'main',
       };
     } else {
-      this.logger.warn(
-        '[passthrough-remuxer.ts]: initSegment does not contain moov or trak boxes.',
-      );
+      this.warn('initSegment does not contain moov or trak boxes.');
     }
     this.initTracks = tracks;
   }
@@ -157,13 +179,14 @@ class PassThroughRemuxer implements Remuxer {
     // The binary segment data is added to the videoTrack in the mp4demuxer. We don't check to see if the data is only
     // audio or video (or both); adding it to video was an arbitrary choice.
     const data = videoTrack.samples;
-    if (!data?.length) {
+    if (!data.length) {
       return result;
     }
 
     const initSegment: InitSegmentData = {
       initPTS: undefined,
-      timescale: 1,
+      timescale: undefined,
+      trackId: undefined,
     };
     let initData = this.initData;
     if (!initData?.length) {
@@ -172,46 +195,101 @@ class PassThroughRemuxer implements Remuxer {
     }
     if (!initData?.length) {
       // We can't remux if the initSegment could not be generated
-      this.logger.warn(
-        '[passthrough-remuxer.ts]: Failed to generate initSegment.',
-      );
+      this.warn('Failed to generate initSegment.');
       return result;
     }
     if (this.emitInitSegment) {
-      initSegment.tracks = this.initTracks as TrackSet;
+      initSegment.tracks = this.initTracks;
       this.emitInitSegment = false;
     }
 
-    const duration = getDuration(data, initData);
-    const startDTS = getStartDTS(initData, data);
-    const decodeTime = startDTS === null ? timeOffset : startDTS;
-    if (
-      (accurateTimeOffset || !initPTS) &&
-      (isInvalidInitPts(initPTS, decodeTime, timeOffset, duration) ||
-        initSegment.timescale !== initPTS.timescale)
-    ) {
-      initSegment.initPTS = decodeTime - timeOffset;
-      if (initPTS && initPTS.timescale === 1) {
-        this.logger.warn(
-          `Adjusting initPTS @${timeOffset} from ${initPTS.baseTime / initPTS.timescale} to ${initSegment.initPTS}`,
+    const trackSampleData = getSampleData(data, initData, this);
+    const audioSampleTimestamps = initData.audio
+      ? trackSampleData[initData.audio.id]
+      : null;
+    const videoSampleTimestamps = initData.video
+      ? trackSampleData[initData.video.id]
+      : null;
+
+    const videoStartTime = toStartEndOrDefault(videoSampleTimestamps, Infinity);
+    const audioStartTime = toStartEndOrDefault(audioSampleTimestamps, Infinity);
+    const videoEndTime = toStartEndOrDefault(videoSampleTimestamps, 0, true);
+    const audioEndTime = toStartEndOrDefault(audioSampleTimestamps, 0, true);
+
+    let decodeTime = timeOffset;
+    let duration = 0;
+
+    const syncOnAudio =
+      audioSampleTimestamps &&
+      (!videoSampleTimestamps ||
+        (!initPTS && audioStartTime < videoStartTime) ||
+        (initPTS && initPTS.trackId === initData.audio!.id));
+    const baseOffsetSamples = syncOnAudio
+      ? audioSampleTimestamps
+      : videoSampleTimestamps;
+
+    if (baseOffsetSamples) {
+      const timescale = baseOffsetSamples.timescale;
+      const baseTime = baseOffsetSamples.start - timeOffset * timescale;
+      const trackId = syncOnAudio ? initData.audio!.id : initData.video!.id;
+
+      decodeTime = baseOffsetSamples.start / timescale;
+      duration = syncOnAudio
+        ? audioEndTime - audioStartTime
+        : videoEndTime - videoStartTime;
+
+      if (
+        (accurateTimeOffset || !initPTS) &&
+        (isInvalidInitPts(initPTS, decodeTime, timeOffset, duration) ||
+          timescale !== initPTS.timescale)
+      ) {
+        if (initPTS) {
+          this.warn(
+            `Timestamps at playlist time: ${accurateTimeOffset ? '' : '~'}${timeOffset} ${baseTime / timescale} != initPTS: ${initPTS.baseTime / initPTS.timescale} (${initPTS.baseTime}/${initPTS.timescale}) trackId: ${initPTS.trackId}`,
+          );
+        }
+        this.log(
+          `Found initPTS at playlist time: ${timeOffset} offset: ${decodeTime - timeOffset} (${baseTime}/${timescale}) trackId: ${trackId}`,
         );
+        initPTS = null;
+        initSegment.initPTS = baseTime;
+        initSegment.timescale = timescale;
+        initSegment.trackId = trackId;
+      }
+    } else {
+      this.warn(
+        `No audio or video samples found for initPTS at playlist time: ${timeOffset}`,
+      );
+    }
+    if (!initPTS) {
+      if (
+        !initSegment.timescale ||
+        initSegment.trackId === undefined ||
+        initSegment.initPTS === undefined
+      ) {
+        this.warn('Could not set initPTS');
+        initSegment.initPTS = decodeTime;
+        initSegment.timescale = 1;
+        initSegment.trackId = -1;
       }
       this.initPTS = initPTS = {
         baseTime: initSegment.initPTS,
-        timescale: 1,
+        timescale: initSegment.timescale,
+        trackId: initSegment.trackId,
       };
+    } else {
+      initSegment.initPTS = initPTS.baseTime;
+      initSegment.timescale = initPTS.timescale;
+      initSegment.trackId = initPTS.trackId;
     }
 
-    const startTime = audioTrack
-      ? decodeTime - initPTS.baseTime / initPTS.timescale
-      : (lastEndTime as number);
+    const startTime = decodeTime - initPTS.baseTime / initPTS.timescale;
     const endTime = startTime + duration;
-    offsetStartDTS(initData, data, initPTS.baseTime / initPTS.timescale);
 
     if (duration > 0) {
       this.lastEndTime = endTime;
     } else {
-      this.logger.warn('Duration parsed from mp4 should be greater than zero');
+      this.warn('Duration parsed from mp4 should be greater than zero');
       this.resetNextTimestamp();
     }
 
@@ -227,6 +305,10 @@ class PassThroughRemuxer implements Remuxer {
       type += 'video';
     }
 
+    const encrypted =
+      (initData.audio ? initData.audio.encrypted : false) ||
+      (initData.video ? initData.video.encrypted : false);
+
     const track: RemuxedTrack = {
       data1: data,
       startPTS: startTime,
@@ -238,10 +320,40 @@ class PassThroughRemuxer implements Remuxer {
       hasVideo,
       nb: 1,
       dropped: 0,
+      encrypted,
     };
 
-    result.audio = track.type === 'audio' ? track : undefined;
-    result.video = track.type !== 'audio' ? track : undefined;
+    result.audio = hasAudio && !hasVideo ? track : undefined;
+    result.video = hasVideo ? track : undefined;
+    const videoSampleCount = videoSampleTimestamps?.sampleCount;
+    if (videoSampleCount) {
+      const firstKeyFrame = videoSampleTimestamps.keyFrameIndex;
+      const independent = firstKeyFrame !== -1;
+      track.nb = videoSampleCount;
+      track.dropped =
+        firstKeyFrame === 0 || this.isVideoContiguous
+          ? 0
+          : independent
+            ? firstKeyFrame
+            : videoSampleCount;
+      track.independent = independent;
+      track.firstKeyFrame = firstKeyFrame;
+      if (independent && videoSampleTimestamps.keyFrameStart) {
+        track.firstKeyFramePTS =
+          (videoSampleTimestamps.keyFrameStart - initPTS.baseTime) /
+          initPTS.timescale;
+      }
+      if (!this.isVideoContiguous) {
+        result.independent = independent;
+      }
+      this.isVideoContiguous ||= independent;
+      if (track.dropped) {
+        this.warn(
+          `fmp4 does not start with IDR: firstIDR ${firstKeyFrame}/${videoSampleCount} dropped: ${track.dropped} start: ${track.firstKeyFramePTS || 'NA'}`,
+        );
+      }
+    }
+
     result.initSegment = initSegment;
     result.id3 = flushTextTrackMetadataCueSamples(
       id3Track,
@@ -262,8 +374,19 @@ class PassThroughRemuxer implements Remuxer {
   }
 }
 
+function toStartEndOrDefault(
+  trackTimes: TrackTimes | null,
+  defaultValue: number,
+  end: boolean = false,
+): number {
+  return trackTimes?.start !== undefined
+    ? (trackTimes.start + (end ? trackTimes.duration : 0)) /
+        trackTimes.timescale
+    : defaultValue;
+}
+
 function isInvalidInitPts(
-  initPTS: RationalTimestamp | null,
+  initPTS: TimestampOffset | null,
   startDTS: number,
   timeOffset: number,
   duration: number,
@@ -280,8 +403,9 @@ function isInvalidInitPts(
 function getParsedTrackCodec(
   track: InitDataTrack,
   type: ElementaryStreamTypes.AUDIO | ElementaryStreamTypes.VIDEO,
+  logger: ILogger,
 ): string {
-  const parsedCodec = track?.codec;
+  const parsedCodec = track.codec;
   if (parsedCodec && parsedCodec.length > 4) {
     return parsedCodec;
   }
diff --git a/src/types/buffer.ts b/src/types/buffer.ts
index 396c739daf9..4dbc1af689b 100644
--- a/src/types/buffer.ts
+++ b/src/types/buffer.ts
@@ -21,6 +21,7 @@ export interface BaseTrack {
   container: string;
   codec?: string;
   supplemental?: string;
+  encrypted?: boolean;
   levelCodec?: string;
   pendingCodec?: string;
   metadata?: {
@@ -73,7 +74,6 @@ export type SourceBuffersTuple = [
 export type MediaOverrides = {
   duration?: number;
   endOfStream?: boolean;
-  cueRemoval?: boolean;
 };
 
 export interface BufferOperationQueues {
diff --git a/src/types/events.ts b/src/types/events.ts
index 4dc42da5f48..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,
@@ -89,8 +90,9 @@ export interface BufferAppendingData {
   frag: Fragment;
   part: Part | null;
   chunkMeta: ChunkMetadata;
+  offset?: number | undefined;
   parent: PlaylistLevelType;
-  data: Uint8Array;
+  data: Uint8Array;
 }
 
 export interface BufferAppendedData {
@@ -131,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;
@@ -207,7 +209,7 @@ export interface TrackLoadedData {
   details: LevelDetails;
   id: number;
   groupId: string;
-  networkDetails: any;
+  networkDetails: NullableNetworkDetails;
   stats: LoaderStats;
   deliveryDirectives: HlsUrlParameters | null;
   track: MediaPlaylist;
@@ -218,7 +220,7 @@ export interface LevelLoadedData {
   id: number;
   level: number;
   levelInfo: Level;
-  networkDetails: any;
+  networkDetails: NullableNetworkDetails;
   stats: LoaderStats;
   deliveryDirectives: HlsUrlParameters | null;
   withoutMultiVariant?: boolean;
@@ -318,13 +320,14 @@ export interface ErrorData {
   bytes?: number;
   chunkMeta?: ChunkMetadata;
   context?: PlaylistLoaderContext;
+  decryptdata?: LevelKey;
   event?: keyof HlsListeners | 'demuxerWorker';
   frag?: Fragment;
   part?: Part | null;
   level?: number | undefined;
   levelRetry?: boolean;
   loader?: Loader;
-  networkDetails?: any;
+  networkDetails?: NullableNetworkDetails;
   stalled?: { start: number };
   stats?: LoaderStats;
   mimeType?: string;
@@ -373,6 +376,7 @@ export interface InitPTSFoundData {
   frag: MediaFragment;
   initPTS: number;
   timescale: number;
+  trackId: number;
 }
 
 export interface FragLoadingData {
@@ -391,7 +395,7 @@ export interface FragLoadedData {
   frag: Fragment;
   part: Part | null;
   payload: ArrayBuffer;
-  networkDetails: unknown;
+  networkDetails: NullableNetworkDetails;
 }
 
 export interface PartsLoadedData {
@@ -471,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/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/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/src/types/remuxer.ts b/src/types/remuxer.ts
index 8478a1f80c2..c1e0a0259de 100644
--- a/src/types/remuxer.ts
+++ b/src/types/remuxer.ts
@@ -11,7 +11,7 @@ import type {
 import type { PlaylistLevelType } from './loader';
 import type { TrackSet } from './track';
 import type { DecryptData } from '../loader/level-key';
-import type { RationalTimestamp } from '../utils/timescale-conversion';
+import type { TimestampOffset } from '../utils/timescale-conversion';
 
 export interface Remuxer {
   remux(
@@ -30,14 +30,14 @@ export interface Remuxer {
     videoCodec: string | undefined,
     decryptdata: DecryptData | null,
   ): void;
-  resetTimeStamp(defaultInitPTS: RationalTimestamp | null): void;
+  resetTimeStamp(defaultInitPTS: TimestampOffset | null): void;
   resetNextTimestamp(): void;
   destroy(): void;
 }
 
 export interface RemuxedTrack {
-  data1: Uint8Array;
-  data2?: Uint8Array;
+  data1: Uint8Array;
+  data2?: Uint8Array;
   startPTS: number;
   endPTS: number;
   startDTS: number;
@@ -52,6 +52,7 @@ export interface RemuxedTrack {
   transferredData1?: ArrayBuffer;
   transferredData2?: ArrayBuffer;
   dropped?: number;
+  encrypted?: boolean;
 }
 
 export interface RemuxedMetadata {
@@ -99,4 +100,5 @@ export interface InitSegmentData {
   tracks?: TrackSet;
   initPTS: number | undefined;
   timescale: number | undefined;
+  trackId: number | undefined;
 }
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/codecs.ts b/src/utils/codecs.ts
index 6ae8cb06d06..2b7399d9f9c 100644
--- a/src/utils/codecs.ts
+++ b/src/utils/codecs.ts
@@ -232,7 +232,12 @@ export function pickMostCompleteCodecName(
     (parsedCodec.length > 4 ||
       ['ac-3', 'ec-3', 'alac', 'fLaC', 'Opus'].indexOf(parsedCodec) !== -1)
   ) {
-    return parsedCodec;
+    if (
+      isCodecSupportedAsType(parsedCodec, 'audio') ||
+      isCodecSupportedAsType(parsedCodec, 'video')
+    ) {
+      return parsedCodec;
+    }
   }
   if (levelCodec) {
     const levelCodecs = levelCodec.split(',');
@@ -250,19 +255,21 @@ export function pickMostCompleteCodecName(
   return levelCodec || parsedCodec;
 }
 
+function isCodecSupportedAsType(codec: string, type: CodecType): boolean {
+  return isCodecType(codec, type) && isCodecMediaSourceSupported(codec, type);
+}
+
 export function convertAVC1ToAVCOTI(videoCodecs: string): string {
   // Convert avc1 codec string from RFC-4281 to RFC-6381 for MediaSource.isTypeSupported
   // Examples: avc1.66.30 to avc1.42001e and avc1.77.30,avc1.66.30 to avc1.4d001e,avc1.42001e.
   const codecs = videoCodecs.split(',');
   for (let i = 0; i < codecs.length; i++) {
     const avcdata = codecs[i].split('.');
-    if (avcdata.length > 2) {
-      let result = avcdata.shift() + '.';
-      result += parseInt(avcdata.shift() as string).toString(16);
-      result += (
-        '000' + parseInt(avcdata.shift() as string).toString(16)
-      ).slice(-4);
-      codecs[i] = result;
+    // only convert codec strings starting with avc1 (Examples: avc1.64001f,dvh1.05.07)
+    if (avcdata.length > 2 && avcdata[0] === 'avc1') {
+      codecs[i] = `avc1.${parseInt(avcdata[1]).toString(16)}${(
+        '000' + parseInt(avcdata[2]).toString(16)
+      ).slice(-4)}`;
     }
   }
   return codecs.join(',');
diff --git a/src/utils/discontinuities.ts b/src/utils/discontinuities.ts
index 795365e05b9..a6abea9ef71 100644
--- a/src/utils/discontinuities.ts
+++ b/src/utils/discontinuities.ts
@@ -31,11 +31,10 @@ export function shouldAlignOnDiscontinuities(
 }
 
 function adjustFragmentStart(frag: Fragment, sliding: number) {
-  if (frag) {
-    const start = frag.start + sliding;
-    frag.start = frag.startPTS = start;
-    frag.endPTS = start + frag.duration;
-  }
+  const start = frag.start + sliding;
+  frag.startPTS = start;
+  frag.setStart(start);
+  frag.endPTS = start + frag.duration;
 }
 
 export function adjustSlidingStart(sliding: number, details: LevelDetails) {
@@ -68,13 +67,13 @@ export function alignStream(
     return;
   }
   alignDiscontinuities(details, switchDetails);
-  if (!details.alignedSliding && switchDetails) {
+  if (!details.alignedSliding) {
     // If the PTS wasn't figured out via discontinuity sequence that means there was no CC increase within the level.
     // Aligning via Program Date Time should therefore be reliable, since PDT should be the same within the same
     // discontinuity sequence.
     alignMediaPlaylistByPDT(details, switchDetails);
   }
-  if (!details.alignedSliding && switchDetails && !details.skippedSegments) {
+  if (!details.alignedSliding && !details.skippedSegments) {
     // Try to align on sn so that we pick a better start fragment.
     // Do not perform this on playlists with delta updates as this is only to align levels on switch
     // and adjustSliding only adjusts fragments after skippedSegments.
diff --git a/src/utils/error-helper.ts b/src/utils/error-helper.ts
index dc1eb18aa5d..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;
@@ -71,10 +74,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;
+}
diff --git a/src/utils/event-listener-helper.ts b/src/utils/event-listener-helper.ts
index 486524ae7b0..0dbc7a5c94a 100644
--- a/src/utils/event-listener-helper.ts
+++ b/src/utils/event-listener-helper.ts
@@ -1,5 +1,5 @@
 export function addEventListener(
-  el: HTMLElement,
+  el: EventTarget,
   type: string,
   listener: EventListenerOrEventListenerObject,
 ) {
@@ -8,7 +8,7 @@ export function addEventListener(
 }
 
 export function removeEventListener(
-  el: HTMLElement,
+  el: EventTarget,
   type: string,
   listener: EventListenerOrEventListenerObject,
 ) {
diff --git a/src/utils/hex.ts b/src/utils/hex.ts
index 703988cf601..2bc66b55132 100644
--- a/src/utils/hex.ts
+++ b/src/utils/hex.ts
@@ -2,19 +2,31 @@
  *  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(
+    str
+      .replace(/^0x/, '')
+      .replace(/([\da-fA-F]{2}) ?/g, '0x$1 ')
+      .replace(/ +$/, '')
+      .split(' '),
+  ).buffer;
+}
+
+const Hex = {
+  hexDump: arrayToHex,
 };
 
 export default Hex;
diff --git a/src/utils/imsc1-ttml-parser.ts b/src/utils/imsc1-ttml-parser.ts
index 764176fc10e..2a6dc5b6d9c 100644
--- a/src/utils/imsc1-ttml-parser.ts
+++ b/src/utils/imsc1-ttml-parser.ts
@@ -4,7 +4,7 @@ import { toTimescaleFromScale } from './timescale-conversion';
 import VTTCue from './vttcue';
 import { parseTimeStamp } from './vttparser';
 import { generateCueId } from './webvtt-parser';
-import type { RationalTimestamp } from './timescale-conversion';
+import type { TimestampOffset } from './timescale-conversion';
 
 export const IMSC1_CODEC = 'stpp.ttml.im1t';
 
@@ -24,7 +24,7 @@ const textAlignToLineAlign: Partial> = {
 
 export function parseIMSC1(
   payload: ArrayBuffer,
-  initPTS: RationalTimestamp,
+  initPTS: TimestampOffset,
   callBack: (cues: Array) => any,
   errorCallBack: (error: Error) => any,
 ) {
diff --git a/src/utils/level-helper.ts b/src/utils/level-helper.ts
index 12fd07ec6d9..467dac78a7a 100644
--- a/src/utils/level-helper.ts
+++ b/src/utils/level-helper.ts
@@ -2,10 +2,10 @@
  * Provides methods dealing with playlist sliding and drift
  */
 
-import { logger } from './logger';
 import { stringify } from './safe-json-stringify';
 import { DateRange } from '../loader/date-range';
 import { assignProgramDateTime, mapDateRanges } from '../loader/m3u8-parser';
+import type { ILogger } from './logger';
 import type { Fragment, MediaFragment, Part } from '../loader/fragment';
 import type { LevelDetails } from '../loader/level-details';
 import type { Level } from '../types/level';
@@ -67,6 +67,7 @@ export function updateFragPTSDTS(
   endPTS: number,
   startDTS: number,
   endDTS: number,
+  logger: ILogger,
 ): number {
   const parsedMediaDuration = endPTS - startPTS;
   if (parsedMediaDuration <= 0) {
@@ -81,7 +82,11 @@ export function updateFragPTSDTS(
   if (Number.isFinite(fragStartPts)) {
     // delta PTS between audio and video
     const deltaPTS = Math.abs(fragStartPts - startPTS);
-    if (!Number.isFinite(frag.deltaPTS as number)) {
+    if (details && deltaPTS > details.totalduration) {
+      logger.warn(
+        `media timestamps and playlist times differ by ${deltaPTS}s for level ${frag.level} ${details.url}`,
+      );
+    } else if (!Number.isFinite(frag.deltaPTS as number)) {
       frag.deltaPTS = deltaPTS;
     } else {
       frag.deltaPTS = Math.max(deltaPTS, frag.deltaPTS as number);
@@ -89,11 +94,14 @@ export function updateFragPTSDTS(
 
     maxStartPTS = Math.max(startPTS, fragStartPts);
     startPTS = Math.min(startPTS, fragStartPts);
-    startDTS = Math.min(startDTS, frag.startDTS as number);
+    startDTS =
+      frag.startDTS !== undefined
+        ? Math.min(startDTS, frag.startDTS)
+        : startDTS;
 
     minEndPTS = Math.min(endPTS, fragEndPts);
     endPTS = Math.max(endPTS, fragEndPts);
-    endDTS = Math.max(endDTS, frag.endDTS as number);
+    endDTS = frag.endDTS !== undefined ? Math.max(endDTS, frag.endDTS) : endDTS;
   }
 
   const drift = startPTS - frag.start;
@@ -142,6 +150,7 @@ export function updateFragPTSDTS(
 export function mergeDetails(
   oldDetails: LevelDetails,
   newDetails: LevelDetails,
+  logger: ILogger,
 ) {
   if (oldDetails === newDetails) {
     return;
@@ -168,14 +177,14 @@ export function mergeDetails(
     oldDetails,
     newDetails,
     (oldFrag, newFrag, newFragIndex, newFragments) => {
-      if (!newDetails.startCC && newFrag.cc !== oldFrag.cc) {
+      if (
+        (!newDetails.startCC || newDetails.skippedSegments) &&
+        newFrag.cc !== oldFrag.cc
+      ) {
         const ccOffset = oldFrag.cc - newFrag.cc;
         for (let i = newFragIndex; i < newFragments.length; i++) {
           newFragments[i].cc += ccOffset;
         }
-        newDetails.startCC =
-          getFragmentWithSN(oldDetails, newDetails.startSN - 1)?.cc ??
-          newFragments[0].cc;
         newDetails.endCC = newFragments[newFragments.length - 1].cc;
       }
       if (
@@ -223,7 +232,7 @@ export function mergeDetails(
   if (currentInitSegment) {
     fragmentsToCheck.forEach((frag) => {
       if (
-        frag &&
+        (frag as any) &&
         (!frag.initSegment ||
           frag.initSegment.relurl === currentInitSegment?.relurl)
       ) {
@@ -233,7 +242,7 @@ export function mergeDetails(
   }
 
   if (newDetails.skippedSegments) {
-    newDetails.deltaUpdateFailed = newFragments.some((frag) => !frag);
+    newDetails.deltaUpdateFailed = newFragments.some((frag) => !frag as any);
     if (newDetails.deltaUpdateFailed) {
       logger.warn(
         '[level-helper] Previous playlist missing segments skipped in delta playlist',
@@ -243,11 +252,11 @@ export function mergeDetails(
       }
       newDetails.startSN = newFragments[0].sn;
     } else {
-      newDetails.endCC = newFragments[newFragments.length - 1].cc;
       if (newDetails.canSkipDateRanges) {
         newDetails.dateRanges = mergeDateRanges(
           oldDetails.dateRanges,
           newDetails,
+          logger,
         );
       }
       const programDateTimes = oldDetails.fragments.filter(
@@ -266,6 +275,14 @@ export function mergeDetails(
       }
       mapDateRanges(programDateTimes, newDetails);
     }
+    newDetails.endCC = newFragments[newFragments.length - 1].cc;
+  }
+  if (!newDetails.startCC) {
+    const fragPriorToNewStart = getFragmentWithSN(
+      oldDetails,
+      newDetails.startSN - 1,
+    );
+    newDetails.startCC = fragPriorToNewStart?.cc ?? newFragments[0].cc;
   }
 
   // Merge parts
@@ -287,6 +304,7 @@ export function mergeDetails(
       PTSFrag.endPTS as number,
       PTSFrag.startDTS as number,
       PTSFrag.endDTS as number,
+      logger,
     );
   } else {
     // ensure that delta is within oldFragments range
@@ -321,9 +339,10 @@ export function mergeDetails(
 }
 
 function mergeDateRanges(
-  oldDateRanges: Record,
+  oldDateRanges: Record,
   newDetails: LevelDetails,
-): Record {
+  logger: ILogger,
+): Record {
   const { dateRanges: deltaDateRanges, recentlyRemovedDateranges } = newDetails;
   const dateRanges = Object.assign({}, oldDateRanges);
   if (recentlyRemovedDateranges) {
@@ -333,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;
 }
 
@@ -368,8 +385,8 @@ export function mapPartIntersection(
       const oldPart = oldParts[i];
       const newPart = newParts[i + delta];
       if (
-        oldPart &&
-        newPart &&
+        (oldPart as any) &&
+        (newPart as any) &&
         oldPart.index === newPart.index &&
         oldPart.fragment.sn === newPart.fragment.sn
       ) {
@@ -406,13 +423,15 @@ export function mapFragmentIntersection(
   for (let i = start; i <= end; i++) {
     const oldFrag = oldFrags[delta + i];
     let newFrag = newFrags[i];
-    if (skippedSegments && !newFrag && oldFrag) {
+    if (skippedSegments && (!newFrag as any) && (oldFrag as any)) {
       // Fill in skipped segments in delta playlist
       newFrag = newDetails.fragments[i] = oldFrag;
     }
-    if (oldFrag && newFrag) {
+    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,
@@ -530,8 +549,9 @@ export function getFragmentWithSN(
   if (!details) {
     return null;
   }
-  let fragment: MediaFragment | undefined =
-    details.fragments[sn - details.startSN];
+  let fragment = details.fragments[sn - details.startSN] as
+    | MediaFragment
+    | undefined;
   if (fragment) {
     return fragment;
   }
@@ -582,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/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/mediacapabilities-helper.ts b/src/utils/mediacapabilities-helper.ts
index d0aa9afd2e9..280a30c9561 100644
--- a/src/utils/mediacapabilities-helper.ts
+++ b/src/utils/mediacapabilities-helper.ts
@@ -16,18 +16,26 @@ export type MediaDecodingInfo = {
   error?: Error;
 };
 
-type BaseVideoConfiguration = Omit;
+// @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(
@@ -37,22 +45,11 @@ export function getUnsupportedResult(
   return {
     supported: false,
     configurations,
-    decodingInfoResults: [
-      {
-        supported: false,
-        smooth: false,
-        powerEfficient: false,
-      },
-    ],
+    decodingInfoResults: [unsupportedResult],
     error,
   };
 }
 
-export const SUPPORTED_INFO_CACHE: Record<
-  string,
-  Promise
-> = {};
-
 export function requiresMediaCapabilitiesDecodingInfo(
   level: Level,
   audioTracksByGroup: AudioTracksByGroup,
@@ -62,6 +59,7 @@ export function requiresMediaCapabilitiesDecodingInfo(
   audioPreference: AudioSelectionOption | undefined,
 ): boolean {
   // Only test support when configuration is exceeds minimum options
+  const videoCodecs = level.videoCodec;
   const audioGroups = level.audioCodec ? level.audioGroups : null;
   const audioCodecPreference = audioPreference?.audioCodec;
   const channelsPreference = audioPreference?.channels;
@@ -98,8 +96,10 @@ export function requiresMediaCapabilitiesDecodingInfo(
     }
   }
   return (
-    (level.videoCodec !== undefined &&
-      ((level.width > 1920 && level.height > 1088) ||
+    (videoCodecs !== undefined &&
+      // Force media capabilities check for HEVC to avoid failure on Windows
+      (videoCodecs.split(',').some((videoCodec) => isHEVC(videoCodec)) ||
+        (level.width > 1920 && level.height > 1088) ||
         (level.height > 1920 && level.width > 1088) ||
         level.frameRate > Math.max(currentFrameRate, 30) ||
         (level.videoRange !== 'SDR' &&
@@ -117,85 +117,59 @@ export function getMediaDecodingInfoPromise(
   level: Level,
   audioTracksByGroup: AudioTracksByGroup,
   mediaCapabilities: MediaCapabilities | undefined,
+  cache: Record<
+    string,
+    Promise | undefined
+  > = {},
 ): Promise {
   const videoCodecs = level.videoCodec;
-  const audioCodecs = level.audioCodec;
-  if ((!videoCodecs && !audioCodecs) || !mediaCapabilities) {
+  if ((!videoCodecs && !level.audioCodec) || !mediaCapabilities) {
     return Promise.resolve(SUPPORTED_INFO_DEFAULT);
   }
 
   const configurations: MediaDecodingConfiguration[] = [];
 
-  if (videoCodecs) {
-    const baseVideoConfiguration: BaseVideoConfiguration = {
-      width: level.width,
-      height: level.height,
-      bitrate: Math.ceil(Math.max(level.bitrate * 0.9, level.averageBitrate)),
-      // Assume a framerate of 30fps since MediaCapabilities will not accept Level default of 0.
-      framerate: level.frameRate || 30,
+  const videoDecodeList = makeVideoConfigurations(level);
+  const videoCount = videoDecodeList.length;
+  const audioDecodeList = makeAudioConfigurations(
+    level,
+    audioTracksByGroup,
+    videoCount > 0,
+  );
+  const audioCount = audioDecodeList.length;
+  for (let i = videoCount || 1 * audioCount || 1; i--; ) {
+    const configuration: MediaDecodingConfiguration = {
+      type: 'media-source',
     };
-    const videoRange = level.videoRange;
-    if (videoRange !== 'SDR') {
-      baseVideoConfiguration.transferFunction =
-        videoRange.toLowerCase() as TransferFunction;
+    if (videoCount) {
+      configuration.video = videoDecodeList[i % videoCount];
+    }
+    if (audioCount) {
+      configuration.audio = audioDecodeList[i % audioCount];
+      const audioBitrate = configuration.audio.bitrate;
+      if (configuration.video && audioBitrate) {
+        configuration.video.bitrate -= audioBitrate;
+      }
     }
-    const videoCodecsArray = videoCodecs.split(',');
+    configurations.push(configuration);
+  }
+
+  if (videoCodecs) {
     // Override Windows Firefox HEVC MediaCapabilities result (https://github.com/video-dev/hls.js/issues/7046)
     const ua = navigator.userAgent;
     if (
-      videoCodecsArray.some((videoCodec) => isHEVC(videoCodec)) &&
+      videoCodecs.split(',').some((videoCodec) => isHEVC(videoCodec)) &&
       userAgentHevcSupportIsInaccurate()
     ) {
       return Promise.resolve(
         getUnsupportedResult(
           new Error(
-            `Overriding Windows Firefox HEVC MediaCapabilities result based on user-agent sting: (${ua})`,
+            `Overriding Windows Firefox HEVC MediaCapabilities result based on user-agent string: (${ua})`,
           ),
           configurations,
         ),
       );
     }
-    configurations.push.apply(
-      configurations,
-      videoCodecsArray.map((videoCodec) => ({
-        type: 'media-source',
-        video: {
-          ...baseVideoConfiguration,
-          contentType: mimeTypeForCodec(
-            fillInMissingAV01Params(videoCodec),
-            'video',
-          ),
-        },
-      })),
-    );
-  }
-
-  if (audioCodecs && level.audioGroups) {
-    level.audioGroups.forEach((audioGroupId) => {
-      if (!audioGroupId) {
-        return;
-      }
-      audioTracksByGroup.groups[audioGroupId]?.tracks.forEach((audioTrack) => {
-        if (audioTrack.groupId === audioGroupId) {
-          const channels = audioTrack.channels || '';
-          const channelsNumber = parseFloat(channels);
-          if (Number.isFinite(channelsNumber) && channelsNumber > 2) {
-            configurations.push.apply(
-              configurations,
-              audioCodecs.split(',').map((audioCodec) => ({
-                type: 'media-source',
-                audio: {
-                  contentType: mimeTypeForCodec(audioCodec, 'audio'),
-                  channels: '' + channelsNumber,
-                  // spatialRendering:
-                  //   audioCodec === 'ec-3' && channels.indexOf('JOC'),
-                },
-              })),
-            );
-          }
-        }
-      });
-    });
   }
 
   return Promise.all(
@@ -203,9 +177,8 @@ export function getMediaDecodingInfoPromise(
       // Cache MediaCapabilities promises
       const decodingInfoKey = getMediaDecodingInfoKey(configuration);
       return (
-        SUPPORTED_INFO_CACHE[decodingInfoKey] ||
-        (SUPPORTED_INFO_CACHE[decodingInfoKey] =
-          mediaCapabilities.decodingInfo(configuration))
+        cache[decodingInfoKey] ||
+        (cache[decodingInfoKey] = mediaCapabilities.decodingInfo(configuration))
       );
     }),
   )
@@ -222,19 +195,107 @@ export function getMediaDecodingInfoPromise(
     }));
 }
 
+function makeVideoConfigurations(level: Level): VideoConfiguration[] {
+  const videoCodecs = level.videoCodec?.split(',');
+  const bitrate = getVariantDecodingBitrate(level);
+  const width = level.width || 640;
+  const height = level.height || 480;
+  // Assume a framerate of 30fps since MediaCapabilities will not accept Level default of 0.
+  const framerate = level.frameRate || 30;
+  const videoRange = level.videoRange.toLowerCase() as 'sdr' | 'pq' | 'hlg';
+  return videoCodecs
+    ? videoCodecs.map((videoCodec: string) => {
+        const videoConfiguration: VideoConfiguration = {
+          contentType: mimeTypeForCodec(
+            fillInMissingAV01Params(videoCodec),
+            'video',
+          ),
+          width,
+          height,
+          bitrate,
+          framerate,
+        };
+        if (videoRange !== 'sdr') {
+          videoConfiguration.transferFunction = videoRange as TransferFunction;
+        }
+        return videoConfiguration;
+      })
+    : [];
+}
+
+function makeAudioConfigurations(
+  level: Level,
+  audioTracksByGroup: AudioTracksByGroup,
+  hasVideo: boolean,
+): AudioConfiguration[] {
+  const audioCodecs = level.audioCodec?.split(',');
+  const combinedBitrate = getVariantDecodingBitrate(level);
+  if (audioCodecs && level.audioGroups) {
+    return level.audioGroups.reduce((configurations, audioGroupId) => {
+      const tracks = audioGroupId
+        ? audioTracksByGroup.groups[audioGroupId]?.tracks
+        : null;
+      if (tracks) {
+        return tracks.reduce((configs, audioTrack) => {
+          if (audioTrack.groupId === audioGroupId) {
+            const channelsNumber = parseFloat(audioTrack.channels || '');
+            audioCodecs.forEach((audioCodec) => {
+              const audioConfiguration: AudioConfiguration = {
+                contentType: mimeTypeForCodec(audioCodec, 'audio'),
+                bitrate: hasVideo
+                  ? estimatedAudioBitrate(audioCodec, combinedBitrate)
+                  : combinedBitrate,
+              };
+              if (channelsNumber) {
+                audioConfiguration.channels = '' + channelsNumber;
+              }
+              configs.push(audioConfiguration);
+            });
+          }
+          return configs;
+        }, configurations);
+      }
+      return configurations;
+    }, [] as AudioConfiguration[]);
+  }
+  return [];
+}
+
+function estimatedAudioBitrate(
+  audioCodec: string,
+  levelBitrate: number,
+): number {
+  if (levelBitrate <= 1) {
+    return 1;
+  }
+  let audioBitrate = 128000;
+  if (audioCodec === 'ec-3') {
+    audioBitrate = 768000;
+  } else if (audioCodec === 'ac-3') {
+    audioBitrate = 640000;
+  }
+  return Math.min(levelBitrate / 2, audioBitrate); // Don't exceed some % of level bitrate
+}
+
+function getVariantDecodingBitrate(level: Level): number {
+  return (
+    Math.ceil(Math.max(level.bitrate * 0.9, level.averageBitrate) / 1000) *
+      1000 || 1
+  );
+}
+
 function getMediaDecodingInfoKey(config: MediaDecodingConfiguration): string {
+  let key = '';
   const { audio, video } = config;
-  const mediaConfig = video || audio;
-  if (mediaConfig) {
-    const codec = getCodecsForMimeType(mediaConfig.contentType);
-    if (video) {
-      return `r${video.height}x${video.width}f${Math.ceil(video.framerate)}${
-        video.transferFunction || 'sd'
-      }_${codec}_${Math.ceil(video.bitrate / 1e5)}`;
-    }
-    if (audio) {
-      return `c${audio.channels}${audio.spatialRendering ? 's' : 'n'}_${codec}`;
-    }
+  if (video) {
+    const codec = getCodecsForMimeType(video.contentType);
+    key += `${codec}_r${video.height}x${video.width}f${Math.ceil(video.framerate)}${
+      video.transferFunction || 'sd'
+    }_${Math.ceil(video.bitrate / 1e5)}`;
+  }
+  if (audio) {
+    const codec = getCodecsForMimeType(audio.contentType);
+    key += `${video ? '_' : ''}${codec}_c${audio.channels}`;
   }
-  return '';
+  return key;
 }
diff --git a/src/utils/mediakeys-helper.ts b/src/utils/mediakeys-helper.ts
index b098f3ee47b..5d9f5e2f64e 100755
--- a/src/utils/mediakeys-helper.ts
+++ b/src/utils/mediakeys-helper.ts
@@ -167,17 +167,18 @@ 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',
+      ))
   );
 }
 
-export function parsePlayReadyWRM(keyBytes: Uint8Array) {
+export function parsePlayReadyWRM(keyBytes: Uint8Array) {
   const keyBytesUtf16 = new Uint16Array(
     keyBytes.buffer,
     keyBytes.byteOffset,
diff --git a/src/utils/mp4-tools.ts b/src/utils/mp4-tools.ts
index f7867cf2ed5..86d536f7047 100644
--- a/src/utils/mp4-tools.ts
+++ b/src/utils/mp4-tools.ts
@@ -1,10 +1,11 @@
 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';
 import type { DecryptData } from '../loader/level-key';
 import type { PassthroughTrack, UserdataSample } from '../types/demuxer';
+import type { ILogger } from '../utils/logger';
 
 const UINT32_MAX = Math.pow(2, 32) - 1;
 const push = [].push;
@@ -121,7 +122,7 @@ type SidxInfo = {
   references: any[];
 };
 
-export function parseSegmentIndex(sidx: Uint8Array): SidxInfo | null {
+function parseSegmentIndex(sidx: Uint8Array): SidxInfo | null {
   const references: any[] = [];
 
   const version = sidx[0];
@@ -223,6 +224,7 @@ export interface InitDataTrack {
   timescale: number;
   id: number;
   codec: string;
+  encrypted: boolean;
   supplemental: string | undefined;
 }
 
@@ -261,15 +263,15 @@ export function parseInitSegment(initSegment: Uint8Array): InitData {
   for (let i = 0; i < traks.length; i++) {
     const trak = traks[i];
     const tkhd = findBox(trak, ['tkhd'])[0];
-    if (tkhd) {
+    if (tkhd as any) {
       let version = tkhd[0];
       const trackId = readUint32(tkhd, version === 0 ? 12 : 20);
       const mdhd = findBox(trak, ['mdia', 'mdhd'])[0];
-      if (mdhd) {
+      if (mdhd as any) {
         version = mdhd[0];
         const timescale = readUint32(mdhd, version === 0 ? 12 : 20);
         const hdlr = findBox(trak, ['mdia', 'hdlr'])[0];
-        if (hdlr) {
+        if (hdlr as any) {
           const hdlrType = bin2str(hdlr.subarray(8, 12));
           const type: HdlrType | undefined = {
             soun: ElementaryStreamTypes.AUDIO as const,
@@ -283,7 +285,7 @@ export function parseInitSegment(initSegment: Uint8Array): InitData {
             result[trackId] = { timescale, type, stsd };
             result[type] = { timescale, id: trackId, ...stsd };
           } else {
-            // Add 'meta' and other track records required by `offsetStartDTS`
+            // Add 'meta' and other track records
             result[trackId] = {
               timescale,
               type: hdlrType as HdlrType,
@@ -323,11 +325,11 @@ function parseStsd(stsd: Uint8Array): StsdData {
     const sinfs = findBox(encBoxChildren, ['sinf']);
     sinfs.forEach((sinf) => {
       const schm = findBox(sinf, ['schm'])[0];
-      if (schm) {
+      if (schm as any) {
         const scheme = bin2str(schm.subarray(4, 8));
         if (scheme === 'cbcs' || scheme === 'cenc') {
           const frma = findBox(sinf, ['frma'])[0];
-          if (frma) {
+          if (frma as any) {
             // for encrypted content codec fourCC will be in frma
             codec = bin2str(frma);
           }
@@ -343,7 +345,7 @@ function parseStsd(stsd: Uint8Array): StsdData {
     case 'avc4': {
       // extract profile + compatibility + level out of avcC box
       const avcCBox = findBox(sampleEntriesEnd, ['avcC'])[0];
-      if (avcCBox && avcCBox.length > 3) {
+      if ((avcCBox as any) && avcCBox.length > 3) {
         codec +=
           '.' + toHex(avcCBox[1]) + toHex(avcCBox[2]) + toHex(avcCBox[3]);
         supplemental = parseSupplementalDoViCodec(
@@ -356,7 +358,7 @@ function parseStsd(stsd: Uint8Array): StsdData {
     case 'mp4a': {
       const codecBox = findBox(sampleEntries, [fourCC])[0];
       const esdsBox = findBox(codecBox.subarray(28), ['esds'])[0];
-      if (esdsBox && esdsBox.length > 7) {
+      if ((esdsBox as any) && esdsBox.length > 7) {
         let i = 4;
         // ES Descriptor tag
         if (esdsBox[i++] !== 0x03) {
@@ -401,7 +403,7 @@ function parseStsd(stsd: Uint8Array): StsdData {
     case 'hvc1':
     case 'hev1': {
       const hvcCBox = findBox(sampleEntriesEnd, ['hvcC'])[0];
-      if (hvcCBox && hvcCBox.length > 12) {
+      if ((hvcCBox as any) && hvcCBox.length > 12) {
         const profileByte = hvcCBox[1];
         const profileSpace = ['', 'A', 'B', 'C'][profileByte >> 6];
         const generalProfileIdc = profileByte & 0x1f;
@@ -439,7 +441,7 @@ function parseStsd(stsd: Uint8Array): StsdData {
     }
     case 'vp09': {
       const vpcCBox = findBox(sampleEntriesEnd, ['vpcC'])[0];
-      if (vpcCBox && vpcCBox.length > 6) {
+      if ((vpcCBox as any) && vpcCBox.length > 6) {
         const profile = vpcCBox[4];
         const level = vpcCBox[5];
         const bitDepth = (vpcCBox[6] >> 4) & 0x0f;
@@ -455,7 +457,7 @@ function parseStsd(stsd: Uint8Array): StsdData {
     }
     case 'av01': {
       const av1CBox = findBox(sampleEntriesEnd, ['av1C'])[0];
-      if (av1CBox && av1CBox.length > 2) {
+      if ((av1CBox as any) && av1CBox.length > 2) {
         const profile = av1CBox[1] >>> 5;
         const level = av1CBox[1] & 0x1f;
         const tierFlag = av1CBox[2] >>> 7 ? 'H' : 'M';
@@ -525,7 +527,7 @@ function parseSupplementalDoViCodec(
   const dvXCBox = dvvCResult.length
     ? dvvCResult[0]
     : findBox(sampleEntriesEnd, ['dvcC'])[0]; // used by DoVi Profiles up to 7 and 20
-  if (dvXCBox) {
+  if (dvXCBox as any) {
     const doViProfile = (dvXCBox[2] >> 1) & 0x7f;
     const doViLevel = ((dvXCBox[2] << 5) & 0x20) | ((dvXCBox[3] >> 3) & 0x1f);
     return (
@@ -563,11 +565,11 @@ function addLeadingZero(num: number): string {
 }
 
 export function patchEncyptionData(
-  initSegment: Uint8Array | undefined,
+  initSegment: Uint8Array | undefined,
   decryptdata: DecryptData | null,
-): Uint8Array | undefined {
+) {
   if (!initSegment || !decryptdata) {
-    return initSegment;
+    return;
   }
   const keyId = decryptdata.keyId;
   if (keyId && decryptdata.isCommonEncryption) {
@@ -589,12 +591,12 @@ export function patchEncyptionData(
           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);
+            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: ${Hex.hexDump(tencKeyId)} -> ${Hex.hexDump(
+                }>sinf>>tenc' box: ${arrayToHex(tencKeyId)} -> ${arrayToHex(
                   keyId,
                 )}`,
               );
@@ -605,13 +607,11 @@ export function patchEncyptionData(
       });
     });
   }
-
-  return initSegment;
 }
 
 export function parseSinf(sinf: Uint8Array): Uint8Array | null {
   const schm = findBox(sinf, ['schm'])[0];
-  if (schm) {
+  if (schm as any) {
     const scheme = bin2str(schm.subarray(4, 8));
     if (scheme === 'cbcs' || scheme === 'cenc') {
       return findBox(sinf, ['schi', 'tenc'])[0];
@@ -620,80 +620,6 @@ export function parseSinf(sinf: Uint8Array): Uint8Array | null {
   return null;
 }
 
-/**
- * Determine the base media decode start time, in seconds, for an MP4
- * fragment. If multiple fragments are specified, the earliest time is
- * returned.
- *
- * The base media decode time can be parsed from track fragment
- * metadata:
- * ```
- * moof > traf > tfdt.baseMediaDecodeTime
- * ```
- * It requires the timescale value from the mdhd to interpret.
- *
- * @param initData - a hash of track type to timescale values
- * @param fmp4 - the bytes of the mp4 fragment
- * @returns the earliest base media decode start time for the
- * fragment, in seconds
- */
-export function getStartDTS(
-  initData: InitData,
-  fmp4: Uint8Array,
-): number | null {
-  // we need info from two children of each track fragment box
-  return findBox(fmp4, ['moof', 'traf']).reduce(
-    (result: number | null, traf) => {
-      const tfdt = findBox(traf, ['tfdt'])[0];
-      const version = tfdt[0];
-      const start = findBox(traf, ['tfhd']).reduce(
-        (result: number | null, tfhd) => {
-          // get the track id from the tfhd
-          const id = readUint32(tfhd, 4);
-          const track = initData[id];
-          if (track) {
-            let baseTime = readUint32(tfdt, 4);
-            if (version === 1) {
-              // If value is too large, assume signed 64-bit. Negative track fragment decode times are invalid, but they exist in the wild.
-              // This prevents large values from being used for initPTS, which can cause playlist sync issues.
-              // https://github.com/video-dev/hls.js/issues/5303
-              if (baseTime === UINT32_MAX) {
-                logger.warn(
-                  `[mp4-demuxer]: Ignoring assumed invalid signed 64-bit track fragment decode time`,
-                );
-                return result;
-              }
-              baseTime *= UINT32_MAX + 1;
-              baseTime += readUint32(tfdt, 8);
-            }
-            // assume a 90kHz clock if no timescale was specified
-            const scale = track.timescale || 90e3;
-            // convert base time to seconds
-            const startTime = baseTime / scale;
-            if (
-              Number.isFinite(startTime) &&
-              (result === null || startTime < result)
-            ) {
-              return startTime;
-            }
-          }
-          return result;
-        },
-        null,
-      );
-      if (
-        start !== null &&
-        Number.isFinite(start) &&
-        (result === null || start < result)
-      ) {
-        return start;
-      }
-      return result;
-    },
-    null,
-  );
-}
-
 /*
   For Reference:
   aligned(8) class TrackFragmentHeaderBox
@@ -707,10 +633,22 @@ export function getStartDTS(
      unsigned int(32)  default_sample_flags
   }
  */
-export function getDuration(data: Uint8Array, initData: InitData) {
-  let rawDuration = 0;
-  let videoDuration = 0;
-  let audioDuration = 0;
+export type TrackTimes = {
+  duration: number;
+  keyFrameIndex?: number;
+  keyFrameStart?: number;
+  start: number;
+  sampleCount: number;
+  timescale: number;
+  type: HdlrType;
+};
+
+export function getSampleData(
+  data: Uint8Array,
+  initData: InitData,
+  logger: ILogger,
+): Record {
+  const tracks: Record = {};
   const trafs = findBox(data, ['moof', 'traf']);
   for (let i = 0; i < trafs.length; i++) {
     const traf = trafs[i];
@@ -725,41 +663,135 @@ export function getDuration(data: Uint8Array, initData: InitData) {
     if (!track) {
       continue;
     }
+    (tracks[id] as any) ||= {
+      start: NaN,
+      duration: 0,
+      sampleCount: 0,
+      timescale: track.timescale,
+      type: track.type,
+    };
+    const trackTimes: TrackTimes = tracks[id];
+    // get start DTS
+    const tfdt = findBox(traf, ['tfdt'])[0];
+
+    if (tfdt as any) {
+      const version = tfdt[0];
+      let baseTime = readUint32(tfdt, 4);
+      if (version === 1) {
+        // If value is too large, assume signed 64-bit. Negative track fragment decode times are invalid, but they exist in the wild.
+        // This prevents large values from being used for initPTS, which can cause playlist sync issues.
+        // https://github.com/video-dev/hls.js/issues/5303
+        if (baseTime === UINT32_MAX) {
+          logger.warn(
+            `[mp4-demuxer]: Ignoring assumed invalid signed 64-bit track fragment decode time`,
+          );
+        } else {
+          baseTime *= UINT32_MAX + 1;
+          baseTime += readUint32(tfdt, 8);
+        }
+      }
+      if (
+        Number.isFinite(baseTime) &&
+        (!Number.isFinite(trackTimes.start) || baseTime < trackTimes.start)
+      ) {
+        trackTimes.start = baseTime;
+      }
+    }
+
     const trackDefault = track.default;
     const tfhdFlags = readUint32(tfhd, 0) | trackDefault?.flags!;
-    let sampleDuration: number | undefined = trackDefault?.duration;
+    let defaultSampleDuration: number = trackDefault?.duration || 0;
     if (tfhdFlags & 0x000008) {
       // 0x000008 indicates the presence of the default_sample_duration field
       if (tfhdFlags & 0x000002) {
         // 0x000002 indicates the presence of the sample_description_index field, which precedes default_sample_duration
         // If present, the default_sample_duration exists at byte offset 12
-        sampleDuration = readUint32(tfhd, 12);
+        defaultSampleDuration = readUint32(tfhd, 12);
       } else {
         // Otherwise, the duration is at byte offset 8
-        sampleDuration = readUint32(tfhd, 8);
+        defaultSampleDuration = readUint32(tfhd, 8);
       }
     }
-    // assume a 90kHz clock if no timescale was specified
-    const timescale = track.timescale || 90e3;
     const truns = findBox(traf, ['trun']);
+    let sampleDTS = trackTimes.start || 0;
+    let rawDuration = 0;
+    let sampleDuration = defaultSampleDuration;
     for (let j = 0; j < truns.length; j++) {
-      rawDuration = computeRawDurationFromSamples(truns[j]);
-      if (!rawDuration && sampleDuration) {
-        const sampleCount = readUint32(truns[j], 4);
-        rawDuration = sampleDuration * sampleCount;
+      const trun = truns[j];
+      const sampleCount = readUint32(trun, 4);
+      const sampleIndex = trackTimes.sampleCount;
+      trackTimes.sampleCount += sampleCount;
+      // Get duration from samples
+      const dataOffsetPresent = trun[3] & 0x01;
+      const firstSampleFlagsPresent = trun[3] & 0x04;
+      const sampleDurationPresent = trun[2] & 0x01;
+      const sampleSizePresent = trun[2] & 0x02;
+      const sampleFlagsPresent = trun[2] & 0x04;
+      const sampleCompositionTimeOffsetPresent = trun[2] & 0x08;
+      let offset = 8;
+      let remaining = sampleCount;
+      if (dataOffsetPresent) {
+        offset += 4;
       }
-      if (track.type === ElementaryStreamTypes.VIDEO) {
-        videoDuration += rawDuration / timescale;
-      } else if (track.type === ElementaryStreamTypes.AUDIO) {
-        audioDuration += rawDuration / timescale;
+      if (firstSampleFlagsPresent && sampleCount) {
+        const isNonSyncSample = trun[offset + 1] & 0x01;
+        if (!isNonSyncSample && trackTimes.keyFrameIndex === undefined) {
+          trackTimes.keyFrameIndex = sampleIndex;
+        }
+        offset += 4;
+        if (sampleDurationPresent) {
+          sampleDuration = readUint32(trun, offset);
+          offset += 4;
+        } else {
+          sampleDuration = defaultSampleDuration;
+        }
+        if (sampleSizePresent) {
+          offset += 4;
+        }
+        if (sampleCompositionTimeOffsetPresent) {
+          offset += 4;
+        }
+        sampleDTS += sampleDuration;
+        rawDuration += sampleDuration;
+        remaining--;
+      }
+      while (remaining--) {
+        if (sampleDurationPresent) {
+          sampleDuration = readUint32(trun, offset);
+          offset += 4;
+        } else {
+          sampleDuration = defaultSampleDuration;
+        }
+        if (sampleSizePresent) {
+          offset += 4;
+        }
+        if (sampleFlagsPresent) {
+          const isNonSyncSample = trun[offset + 1] & 0x01;
+          if (!isNonSyncSample) {
+            if (trackTimes.keyFrameIndex === undefined) {
+              trackTimes.keyFrameIndex =
+                trackTimes.sampleCount - (remaining + 1);
+              trackTimes.keyFrameStart = sampleDTS;
+            }
+          }
+          offset += 4;
+        }
+        if (sampleCompositionTimeOffsetPresent) {
+          offset += 4;
+        }
+        sampleDTS += sampleDuration;
+        rawDuration += sampleDuration;
+      }
+      if (!rawDuration && defaultSampleDuration) {
+        rawDuration += defaultSampleDuration * sampleCount;
       }
     }
+    trackTimes.duration += rawDuration;
   }
-  if (videoDuration === 0 && audioDuration === 0) {
+  if (!Object.keys(tracks).some((trackId) => tracks[trackId].duration)) {
     // If duration samples are not available in the traf use sidx subsegment_duration
     let sidxMinStart = Infinity;
     let sidxMaxEnd = 0;
-    let sidxDuration = 0;
     const sidxs = findBox(data, ['sidx']);
     for (let i = 0; i < sidxs.length; i++) {
       const sidx = parseSegmentIndex(sidxs[i]);
@@ -776,122 +808,24 @@ export function getDuration(data: Uint8Array, initData: InitData) {
           sidxMaxEnd,
           subSegmentDuration + sidx.earliestPresentationTime / sidx.timescale,
         );
-        sidxDuration = sidxMaxEnd - sidxMinStart;
       }
     }
-    if (sidxDuration && Number.isFinite(sidxDuration)) {
-      return sidxDuration;
-    }
-  }
-  if (videoDuration) {
-    return videoDuration;
-  }
-  return audioDuration;
-}
-
-/*
-  For Reference:
-  aligned(8) class TrackRunBox
-           extends FullBox(‘trun’, version, tr_flags) {
-     unsigned int(32)  sample_count;
-     // the following are optional fields
-     signed int(32) data_offset;
-     unsigned int(32)  first_sample_flags;
-     // all fields in the following array are optional
-     {
-        unsigned int(32)  sample_duration;
-        unsigned int(32)  sample_size;
-        unsigned int(32)  sample_flags
-        if (version == 0)
-           { unsigned int(32)
-        else
-           { signed int(32)
-     }[ sample_count ]
-  }
- */
-export function computeRawDurationFromSamples(trun): number {
-  const flags = readUint32(trun, 0);
-  // Flags are at offset 0, non-optional sample_count is at offset 4. Therefore we start 8 bytes in.
-  // Each field is an int32, which is 4 bytes
-  let offset = 8;
-  // data-offset-present flag
-  if (flags & 0x000001) {
-    offset += 4;
-  }
-  // first-sample-flags-present flag
-  if (flags & 0x000004) {
-    offset += 4;
-  }
-
-  let duration = 0;
-  const sampleCount = readUint32(trun, 4);
-  for (let i = 0; i < sampleCount; i++) {
-    // sample-duration-present flag
-    if (flags & 0x000100) {
-      const sampleDuration = readUint32(trun, offset);
-      duration += sampleDuration;
-      offset += 4;
-    }
-    // sample-size-present flag
-    if (flags & 0x000200) {
-      offset += 4;
-    }
-    // sample-flags-present flag
-    if (flags & 0x000400) {
-      offset += 4;
-    }
-    // sample-composition-time-offsets-present flag
-    if (flags & 0x000800) {
-      offset += 4;
-    }
-  }
-  return duration;
-}
-
-// TODO: Remove `offsetStartDTS` in favor of using `timestampOffset` (issue #5715)
-export function offsetStartDTS(
-  initData: InitData,
-  fmp4: Uint8Array,
-  timeOffset: number,
-) {
-  findBox(fmp4, ['moof', 'traf']).forEach((traf) => {
-    findBox(traf, ['tfhd']).forEach((tfhd) => {
-      // get the track id from the tfhd
-      const id = readUint32(tfhd, 4);
-      const track = initData[id];
-      if (!track) {
-        return;
-      }
-      // assume a 90kHz clock if no timescale was specified
-      const timescale = track.timescale || 90e3;
-      // get the base media decode time from the tfdt
-      findBox(traf, ['tfdt']).forEach((tfdt) => {
-        const version = tfdt[0];
-        const offset = timeOffset * timescale;
-        if (offset) {
-          let baseMediaDecodeTime = readUint32(tfdt, 4);
-          if (version === 0) {
-            baseMediaDecodeTime -= offset;
-            baseMediaDecodeTime = Math.max(baseMediaDecodeTime, 0);
-            writeUint32(tfdt, 4, baseMediaDecodeTime);
-          } else {
-            baseMediaDecodeTime *= Math.pow(2, 32);
-            baseMediaDecodeTime += readUint32(tfdt, 8);
-            baseMediaDecodeTime -= offset;
-            baseMediaDecodeTime = Math.max(baseMediaDecodeTime, 0);
-            const upper = Math.floor(baseMediaDecodeTime / (UINT32_MAX + 1));
-            const lower = Math.floor(baseMediaDecodeTime % (UINT32_MAX + 1));
-            writeUint32(tfdt, 4, upper);
-            writeUint32(tfdt, 8, lower);
-          }
+    if (sidxMaxEnd && Number.isFinite(sidxMaxEnd)) {
+      Object.keys(tracks).forEach((trackId) => {
+        if (!tracks[trackId].duration) {
+          tracks[trackId].duration =
+            sidxMaxEnd * tracks[trackId].timescale - tracks[trackId].start;
         }
       });
-    });
-  });
+    }
+  }
+  return tracks;
 }
 
 // TODO: Check if the last moof+mdat pair is part of the valid range
-export function segmentValidRange(data: Uint8Array): SegmentedRange {
+export function segmentValidRange(
+  data: Uint8Array,
+): SegmentedRange {
   const segmentedRange: SegmentedRange = {
     valid: null,
     remainder: null,
@@ -910,8 +844,8 @@ export function segmentValidRange(data: Uint8Array): SegmentedRange {
 }
 
 export interface SegmentedRange {
-  valid: Uint8Array | null;
-  remainder: Uint8Array | null;
+  valid: Uint8Array | null;
+  remainder: Uint8Array | null;
 }
 
 export function appendUint8Array(data1: Uint8Array, data2: Uint8Array) {
@@ -958,7 +892,7 @@ export function parseSamples(
         return result / timescale;
       })[0];
 
-      if (baseTime !== undefined) {
+      if ((baseTime as any) !== undefined) {
         timeOffset = baseTime;
       }
 
@@ -1397,7 +1331,7 @@ export function mp4pssh(
     kidCount = new Uint8Array();
   }
   const dataSize = new Uint8Array(4);
-  if (data && data.byteLength > 0) {
+  if (data.byteLength > 0) {
     new DataView(dataSize.buffer).setUint32(0, data.byteLength, false);
   }
   return mp4Box(
@@ -1412,15 +1346,15 @@ export function mp4pssh(
     kidCount,
     kids,
     dataSize,
-    data || new Uint8Array(),
+    data,
   );
 }
 
 export type PsshData = {
   version: 0 | 1;
   systemId: KeySystemIds;
-  kids: null | Uint8Array[];
-  data: null | Uint8Array;
+  kids: null | Uint8Array[];
+  data: null | Uint8Array;
   offset: number;
   size: number;
 };
@@ -1448,7 +1382,7 @@ export function parseMultiPssh(
   return results;
 }
 
-function parsePssh(view: DataView): PsshData | PsshInvalidResult {
+function parsePssh(view: DataView): PsshData | PsshInvalidResult {
   const size = view.getUint32(0);
   const offset = view.byteOffset;
   const length = view.byteLength;
@@ -1467,29 +1401,41 @@ 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;
-  const dataSizeOrKidCount = view.getUint32(28);
-  let kids: null | Uint8Array[] = null;
-  let data: null | Uint8Array = null;
+
+  let kids: null | Uint8Array[] = null;
+  let data: null | Uint8Array = null;
+  let dataSizeOffset = 0;
+
   if (version === 0) {
-    if (size - 32 < dataSizeOrKidCount || dataSizeOrKidCount < 22) {
-      return { offset, size };
-    }
-    data = new Uint8Array(buffer, offset + 32, dataSizeOrKidCount);
-  } else if (version === 1) {
-    if (
-      !dataSizeOrKidCount ||
-      length < offset + 32 + dataSizeOrKidCount * 16 + 16
-    ) {
+    dataSizeOffset = 28;
+  } else {
+    const kidCounts = view.getUint32(28);
+    if (!kidCounts || length < 32 + kidCounts * 16) {
       return { offset, size };
     }
     kids = [];
-    for (let i = 0; i < dataSizeOrKidCount; i++) {
+    for (let i = 0; i < kidCounts; i++) {
       kids.push(new Uint8Array(buffer, offset + 32 + i * 16, 16));
     }
+    dataSizeOffset = 32 + kidCounts * 16;
+  }
+
+  if (!dataSizeOffset) {
+    return { offset, size };
+  }
+
+  const dataSizeOrKidCount = view.getUint32(dataSizeOffset);
+  if (size - 32 < dataSizeOrKidCount) {
+    return { offset, size };
   }
+  data = new Uint8Array(
+    buffer,
+    offset + dataSizeOffset + 4,
+    dataSizeOrKidCount,
+  );
   return {
     version,
     systemId,
diff --git a/src/utils/texttrack-utils.ts b/src/utils/texttrack-utils.ts
index a39ef3071dc..423e39d9c29 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
+  if (navigator.userAgent.includes('Chrome/')) {
+    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,27 +81,6 @@ export function addCueToTrack(track: TextTrack, cue: VTTCue) {
   }
 }
 
-export function clearCurrentCues(track: TextTrack, enterHandler?: () => 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,
@@ -151,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/src/utils/timescale-conversion.ts b/src/utils/timescale-conversion.ts
index f46aa531871..c2762a6ba32 100644
--- a/src/utils/timescale-conversion.ts
+++ b/src/utils/timescale-conversion.ts
@@ -5,6 +5,8 @@ export type RationalTimestamp = {
   timescale: number; // ticks per second
 };
 
+export type TimestampOffset = RationalTimestamp & { trackId: number };
+
 export function toTimescaleFromBase(
   baseTime: number,
   destScale: number,
diff --git a/src/utils/utf8-utils.ts b/src/utils/utf8-utils.ts
index 6468da6f2fe..113ee6f6ff0 100644
--- a/src/utils/utf8-utils.ts
+++ b/src/utils/utf8-utils.ts
@@ -11,7 +11,7 @@ export type Frame = DecodedFrame;
  * This library is free.  You can redistribute it and/or modify it.
  */
 
-export function strToUtf8array(str: string): Uint8Array {
+export function strToUtf8array(str: string) {
   return Uint8Array.from(unescape(encodeURIComponent(str)), (c) =>
     c.charCodeAt(0),
   );
diff --git a/src/utils/webvtt-parser.ts b/src/utils/webvtt-parser.ts
index 7e44e3da932..57a683ee518 100644
--- a/src/utils/webvtt-parser.ts
+++ b/src/utils/webvtt-parser.ts
@@ -3,7 +3,7 @@ import { hash } from './hash';
 import { toMpegTsClockFromTimescale } from './timescale-conversion';
 import { VTTParser } from './vttparser';
 import { normalizePts } from '../remux/mp4-remuxer';
-import type { RationalTimestamp } from './timescale-conversion';
+import type { TimestampOffset } from './timescale-conversion';
 import type { VTTCCs } from '../types/vtt';
 
 const LINEBREAKS = /\r\n|\n\r|\n|\r/g;
@@ -80,7 +80,7 @@ const calculateOffset = function (vttCCs: VTTCCs, cc, presentationTime) {
 
 export function parseWebVTT(
   vttByteArray: ArrayBuffer,
-  initPTS: RationalTimestamp | undefined,
+  initPTS: TimestampOffset | undefined,
   vttCCs: VTTCCs,
   cc: number,
   timeOffset: number,
diff --git a/tests/functional/auto/.eslintrc.js b/tests/functional/auto/.eslintrc.js
index bbfc9364f0d..6ba453647f3 100644
--- a/tests/functional/auto/.eslintrc.js
+++ b/tests/functional/auto/.eslintrc.js
@@ -1,10 +1,5 @@
 module.exports = {
-  env: {
-    node: true,
-    commonjs: true,
-    es6: false,
-    mocha: true,
-  },
+  env: { node: true, commonjs: true, es6: false, mocha: true },
   plugins: ['mocha', 'n'],
   globals: {
     // Test globals
diff --git a/tests/functional/auto/setup.js b/tests/functional/auto/setup.js
index 2d17b300b50..b0244d62a7f 100644
--- a/tests/functional/auto/setup.js
+++ b/tests/functional/auto/setup.js
@@ -13,9 +13,13 @@ const HlsjsLightBuild = !!process.env.HLSJS_LIGHT;
 const chai = require('chai');
 const expect = chai.expect;
 
+const UA = process.env.UA || 'chrome';
+const UA_VERSION = process.env.UA_VERSION || 'latest';
+const HLSJS_TEST_BASE = process.env.HLSJS_TEST_BASE;
+
 const browserConfig = {
-  version: 'latest',
-  name: 'chrome',
+  version: UA_VERSION,
+  name: UA,
 };
 
 /**
@@ -40,11 +44,6 @@ if (useSauce) {
     throw new Error('Missing sauce auth.');
   }
 
-  const UA_VERSION = process.env.UA_VERSION;
-  if (UA_VERSION) {
-    browserConfig.version = UA_VERSION;
-  }
-
   browserConfig.name = UA;
   browserConfig.platform = OS;
 }
@@ -60,11 +59,13 @@ if (browserConfig.platform) {
 }
 
 // Launch static server
-HttpServer.createServer({
-  showDir: false,
-  autoIndex: false,
-  root: './',
-}).listen(8000, useSauce ? '0.0.0.0' : '127.0.0.1');
+if (useSauce || !HLSJS_TEST_BASE) {
+  HttpServer.createServer({
+    showDir: false,
+    autoIndex: false,
+    root: './',
+  }).listen(8000, useSauce ? '0.0.0.0' : '127.0.0.1');
+}
 
 const wait = (ms) => new Promise((resolve) => global.setTimeout(resolve, ms));
 const stringifyResult = (result) =>
@@ -152,9 +153,7 @@ async function testSmoothSwitch(url, config) {
   const result = await browser.executeAsyncScript(
     function (url, config) {
       const callback = arguments[arguments.length - 1];
-      const startConfig = self.objectAssign(config, {
-        startLevel: 0,
-      });
+      const startConfig = self.objectAssign(config, { startLevel: 0 });
       self.startStream(url, startConfig, callback);
       self.hls.manualLevel = 0;
       const video = self.video;
@@ -163,11 +162,14 @@ async function testSmoothSwitch(url, config) {
           '[test] > ' + eventName + ' frag.level: ' + data.frag.level
         );
         const highestLevel = self.hls.levels.length - 1;
-        if (highestLevel === 0) {
+        const level = self.hls.levels[data.frag.level];
+        const fragmentCount = level.details.fragments.length;
+        if (highestLevel === 0 || fragmentCount === 1) {
           callback({
             highestLevel: highestLevel,
-            currentTimeDelta: 0,
-            message: 'No adaptive variants',
+            currentTimeDelta: 1, // pass the test by assigning currentTimeDelta > 0
+            message:
+              highestLevel === 0 ? 'No adaptive variants' : 'Only one segment',
             logs: self.logString,
           });
           return;
@@ -481,7 +483,18 @@ async function sauceDisconnect() {
   });
 }
 
-describe(`testing hls.js playback in the browser on "${browserDescription}"`, function () {
+function getPageURLComponents() {
+  return {
+    base:
+      useSauce || !HLSJS_TEST_BASE ? 'http://localhost:8000' : HLSJS_TEST_BASE,
+    path: '/tests/functional/auto/',
+    file: `index${HlsjsLightBuild ? '-light' : ''}.html`,
+  };
+}
+
+describe(`Testing hls.js playback in ${browserConfig.name} ${browserConfig.version} ${browserConfig.platform ? browserConfig.platform : ''}`, function () {
+  const failedUrls = {};
+
   before(async function () {
     // high timeout because sometimes getSession() takes a while
     this.timeout(100000);
@@ -504,8 +517,27 @@ describe(`testing hls.js playback in the browser on "${browserDescription}"`, fu
       };
     }
 
-    browser = new webdriver.Builder();
-    if (useSauce) {
+    if (!useSauce) {
+      // Configure webdriver for local testing
+      if (browserConfig.name === 'safari') {
+        browser = new webdriver.Builder()
+          .forBrowser(webdriver.Browser.SAFARI)
+          .build();
+      } else if (browserConfig.name === 'chrome') {
+        browser = new webdriver.Builder()
+          .forBrowser(webdriver.Browser.CHROME)
+          .setChromeOptions()
+          .build();
+      } else if (browserConfig.name === 'firefox') {
+        browser = new webdriver.Builder()
+          .forBrowser(webdriver.Browser.FIREFOX)
+          .build();
+      } else {
+        browser = new webdriver.Builder()
+          .withCapabilities(capabilities)
+          .build();
+      }
+    } else {
       if (process.env.SAUCE_TUNNEL_ID) {
         capabilities['sauce:options'] = {
           build: 'HLSJS-' + process.env.SAUCE_TUNNEL_ID,
@@ -524,13 +556,14 @@ describe(`testing hls.js playback in the browser on "${browserDescription}"`, fu
       capabilities['sauce:options'].public = 'public restricted';
       capabilities['sauce:options'].avoidProxy = true;
       capabilities['sauce:options']['record-screenshots'] = false;
-      browser = browser.usingServer(
-        `https://${process.env.SAUCE_USERNAME}:${process.env.SAUCE_ACCESS_KEY}@ondemand.us-west-1.saucelabs.com:443/wd/hub`
-      );
+      browser = new webdriver.Builder()
+        .usingServer(
+          `https://${process.env.SAUCE_USERNAME}:${process.env.SAUCE_ACCESS_KEY}@ondemand.us-west-1.saucelabs.com:443/wd/hub`
+        )
+        .withCapabilities(capabilities)
+        .build();
     }
 
-    browser = browser.withCapabilities(capabilities).build();
-
     const start = Date.now();
 
     try {
@@ -556,13 +589,14 @@ describe(`testing hls.js playback in the browser on "${browserDescription}"`, fu
   beforeEach(async function () {
     try {
       await retry(async () => {
-        const testPageExt = HlsjsLightBuild ? '-light' : '';
-        const testPageUrl = `http://localhost:8000/tests/functional/auto/index${testPageExt}.html`;
+        const page = getPageURLComponents();
+        const testPageUrl = `${page.base}${page.path}${page.file}`;
         if (printDebugLogs) {
           console.log(`Loading test page: ${testPageUrl}`);
         }
         try {
           await browser.get(testPageUrl);
+          await browser.manage().window().setRect(0, 0, 1200, 850);
         } catch (e) {
           throw new Error('failed to open test page');
         }
@@ -592,10 +626,24 @@ describe(`testing hls.js playback in the browser on "${browserDescription}"`, fu
   afterEach(async function () {
     const failed = this.currentTest.isFailed();
     if (printDebugLogs || failed) {
-      const logString = await browser.executeScript(
-        'return window.logString || "";'
-      );
-      console.log(logString);
+      const json = await browser.executeScript(function () {
+        return JSON.stringify({
+          url: self.hls ? self.hls.url : 'undefined',
+          logs: self.logString || '',
+        });
+      });
+      const data = JSON.parse(json);
+      const url = data.url;
+
+      const page = getPageURLComponents();
+      console.log(`${page.base}/hls.js/demo/?src=${encodeURIComponent(url)}
+
+============================= LOGS =======================================
+${data.logs}
+==========================================================================
+`);
+      failedUrls[url] = url in failedUrls ? failedUrls[url] + 1 : 1;
+
       if (failed && useSauce) {
         browser.executeScript('sauce:job-result=failed');
       }
@@ -612,6 +660,9 @@ describe(`testing hls.js playback in the browser on "${browserDescription}"`, fu
     console.log('Quitting browser...');
     await browser.quit();
     console.log('Browser quit.');
+    if (Object.keys(failedUrls).length > 0) {
+      console.log(JSON.stringify(failedUrls, null, 2));
+    }
     if (useSauce) {
       await sauceDisconnect();
     }
@@ -619,24 +670,17 @@ describe(`testing hls.js playback in the browser on "${browserDescription}"`, fu
 
   const entries = Object.entries(streams);
   if (HlsjsLightBuild) {
-    entries.length = 13;
+    entries.length = 10;
   }
 
-  const isSafari = browserConfig.name === 'safari';
-
   entries
     // eslint-disable-next-line no-unused-vars
     .filter(([name, stream]) => !stream.skipFunctionalTests)
     // eslint-disable-next-line no-unused-vars
-    .forEach(([name, stream]) => {
+    .forEach(([name, stream], index) => {
       const url = stream.url;
       const config = stream.config || {};
 
-      // Segment media is shorted than playlist duration resulting in overlapping appends on switch
-      // This appears to prevent playback in Safari which causes smoothswitch and VOD ended event tests to fail
-      const isStreamsWithOverlappingAppends =
-        name === 'arte' || name === 'oceansAES';
-
       config.preferManagedMediaSource = false;
       if (
         stream.skip_ua &&
@@ -652,47 +696,49 @@ describe(`testing hls.js playback in the browser on "${browserDescription}"`, fu
       ) {
         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 && (!isSafari || !isStreamsWithOverlappingAppends)) {
-        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 (!isSafari || !isStreamsWithOverlappingAppends) {
+        if (stream.abr) {
           it(
-            `should seek 3s from end and receive video ended event for ${stream.description} with 2 or less buffered ranges`,
+            `should "smooth switch" to highest level and still play after 2s`,
+            testSmoothSwitch.bind(null, url, config)
+          );
+        }
+
+        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));
         }
-        // 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));
-      }
+      });
     });
 });
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/mocks/data.js b/tests/mocks/data.ts
similarity index 52%
rename from tests/mocks/data.js
rename to tests/mocks/data.ts
index 71df3645198..dc26ddb2549 100644
--- a/tests/mocks/data.js
+++ b/tests/mocks/data.ts
@@ -1,10 +1,21 @@
 import { Fragment } from '../../src/loader/fragment';
 import { PlaylistLevelType } from '../../src/types/loader';
+import type { MediaFragment } from '../../src/loader/fragment';
 
-function fragment(options) {
+type MockMediaFragmentData = {
+  level: number;
+  programDateTime: number;
+  duration: number;
+  start: number;
+  sn: number;
+  cc: number;
+  deltaPTS?: number;
+} & Partial;
+
+export function fragment(options: MockMediaFragmentData): MediaFragment {
   const frag = new Fragment(PlaylistLevelType.MAIN, '');
-  Object.assign(frag, options);
-  return frag;
+  Object.assign(frag, { level: 0 }, options);
+  return frag as MediaFragment;
 }
 
 export const mockFragments = [
@@ -50,3 +61,30 @@ export const mockFragments = [
     cc: 1,
   }),
 ];
+
+export const mockFragmentsWithDiscos = mockFragments.concat([
+  fragment({
+    programDateTime: 1505502691523,
+    level: 2,
+    duration: 5.0,
+    start: 25.0,
+    sn: 5,
+    cc: 2,
+  }),
+  fragment({
+    programDateTime: 1505502696523,
+    level: 2,
+    duration: 5.0,
+    start: 30.0,
+    sn: 6,
+    cc: 2,
+  }),
+  fragment({
+    programDateTime: 1505502701523,
+    level: 2,
+    duration: 5.0,
+    start: 35.0,
+    sn: 7,
+    cc: 2,
+  }),
+]);
diff --git a/tests/test-streams.js b/tests/test-streams.js
index ba80d12a8cc..ec12f9ac926 100644
--- a/tests/test-streams.js
+++ b/tests/test-streams.js
@@ -159,11 +159,21 @@ module.exports = {
     description: 'Duplicate sequential PDT values',
     abr: false,
   },
-  pdtLargeGap: {
-    url: 'https://playertest.longtailvideo.com/adaptive/boxee/playlist.m3u8',
-    description: 'PDTs with large gaps following discontinuities',
-    abr: false,
-  },
+  pdtLargeGap: createTestStreamWithConfig(
+    {
+      url: 'https://playertest.longtailvideo.com/adaptive/boxee/playlist.m3u8',
+      description: 'PDTs with large gaps following discontinuities',
+      abr: false,
+    },
+    {
+      // gaps are introduced by missing audio samples in the TS segments
+      // silent audio insertion can only prepend missing back to the last appended time provided there is room
+      // The discontinuities and starting offset of the timestamps do not allow prepending earlier than the start of the disco
+      allowedBufferedRangesInSeekTest: 7,
+      // Ignore "should buffer up to maxBufferLength" result
+      avBufferOffset: 39,
+    },
+  ),
   pdtBadValues: {
     url: 'https://playertest.longtailvideo.com/adaptive/progdatime/playlist2.m3u8',
     description: 'PDTs with bad values',
diff --git a/tests/unit/controller/audio-stream-controller.ts b/tests/unit/controller/audio-stream-controller.ts
index 7dd91954a18..aae6d9f79f1 100644
--- a/tests/unit/controller/audio-stream-controller.ts
+++ b/tests/unit/controller/audio-stream-controller.ts
@@ -7,12 +7,13 @@ import { State } from '../../../src/controller/base-stream-controller';
 import { FragmentTracker } from '../../../src/controller/fragment-tracker';
 import { Events } from '../../../src/events';
 import Hls from '../../../src/hls';
+import { Fragment } from '../../../src/loader/fragment';
 import KeyLoader from '../../../src/loader/key-loader';
 import { LoadStats } from '../../../src/loader/load-stats';
 import { Level } from '../../../src/types/level';
+import { PlaylistLevelType } from '../../../src/types/loader';
 import { AttrList } from '../../../src/utils/attr-list';
 import { adjustSlidingStart } from '../../../src/utils/discontinuities';
-import type { Fragment } from '../../../src/loader/fragment';
 import type { LevelDetails } from '../../../src/loader/level-details';
 import type {
   AudioTrackLoadedData,
@@ -138,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,
@@ -180,19 +181,19 @@ describe('AudioStreamController', function () {
     const getPlaylistData = function (
       startSN: number,
       endSN: number,
-      type: 'audio' | 'main',
+      type: PlaylistLevelType,
       live: boolean,
     ) {
       const targetduration = 10;
       const fragments: Fragment[] = Array.from(new Array(endSN - startSN)).map(
-        (u, i) =>
-          ({
-            sn: i + startSN,
-            cc: Math.floor((i + startSN) / 10),
-            start: i * targetduration,
-            duration: targetduration,
-            type,
-          }) as unknown as Fragment,
+        (u, i) => {
+          const frag = new Fragment(type, '');
+          frag.sn = i + startSN;
+          frag.cc = Math.floor((i + startSN) / 10);
+          frag.setStart(i * targetduration);
+          frag.duration = targetduration;
+          return frag;
+        },
       );
       return {
         details: {
@@ -211,7 +212,7 @@ describe('AudioStreamController', function () {
           endSN,
         } as unknown as LevelDetails,
         id: 0,
-        networkDetails: {},
+        networkDetails: new Response('ok'),
         stats: new LoadStats(),
         deliveryDirectives: null,
       };
@@ -221,7 +222,12 @@ describe('AudioStreamController', function () {
       endSN: number,
       live: boolean = false,
     ): LevelLoadedData {
-      const data = getPlaylistData(startSN, endSN, 'main', live);
+      const data = getPlaylistData(
+        startSN,
+        endSN,
+        PlaylistLevelType.MAIN,
+        live,
+      );
       const levelData: LevelLoadedData = {
         ...data,
         level: 0,
@@ -234,7 +240,12 @@ describe('AudioStreamController', function () {
       endSN: number,
       live: boolean = false,
     ): AudioTrackLoadedData {
-      const data = getPlaylistData(startSN, endSN, 'audio', live);
+      const data = getPlaylistData(
+        startSN,
+        endSN,
+        PlaylistLevelType.AUDIO,
+        live,
+      );
       const audioTrackData: AudioTrackLoadedData = {
         ...data,
         groupId: 'audio',
diff --git a/tests/unit/controller/base-stream-controller.ts b/tests/unit/controller/base-stream-controller.ts
index a7ca4754086..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,
@@ -68,7 +68,7 @@ describe('BaseStreamController', function () {
       const frag = new Fragment(PlaylistLevelType.MAIN, '') as MediaFragment;
       frag.duration = 5;
       frag.sn = i;
-      frag.start = i * 5;
+      frag.setStart(i * 5);
       details.fragments.push(frag);
     }
     details.live = live;
diff --git a/tests/unit/controller/buffer-controller.ts b/tests/unit/controller/buffer-controller.ts
index 3d89bd8524e..86994802693 100644
--- a/tests/unit/controller/buffer-controller.ts
+++ b/tests/unit/controller/buffer-controller.ts
@@ -137,7 +137,6 @@ describe('BufferController', function () {
         .stub(bufferController, 'createSourceBuffers')
         .callsFake(() => {
           Object.keys(bufferController.tracks).forEach((type) => {
-            bufferController.tracks ||= {};
             bufferController.tracks[type] = {
               appendBuffer: () => {},
               remove: () => {},
diff --git a/tests/unit/controller/cap-level-controller.js b/tests/unit/controller/cap-level-controller.ts
similarity index 90%
rename from tests/unit/controller/cap-level-controller.js
rename to tests/unit/controller/cap-level-controller.ts
index b478d41ee05..401a51eee42 100644
--- a/tests/unit/controller/cap-level-controller.js
+++ b/tests/unit/controller/cap-level-controller.ts
@@ -1,30 +1,38 @@
+import chai from 'chai';
 import sinon from 'sinon';
-import Hls from '../../../src/hls';
+import sinonChai from 'sinon-chai';
 import CapLevelController from '../../../src/controller/cap-level-controller';
 import { Events } from '../../../src/events';
+import Hls from '../../../src/hls';
+import { Level } from '../../../src/types/level';
+import { parsedLevel } from '../utils/mock-level';
+
+chai.use(sinonChai);
+const expect = chai.expect;
 
-const levels = [
-  {
+const parsedLevels = [
+  parsedLevel({
     width: 360,
     height: 360,
-    bandwidth: 1000,
-  },
-  {
+    bitrate: 1000,
+  }),
+  parsedLevel({
     width: 540,
     height: 540,
-    bandwidth: 2000,
-  },
-  {
+    bitrate: 2000,
+  }),
+  parsedLevel({
     width: 540,
     height: 540,
-    bandwidth: 3000,
-  },
-  {
+    bitrate: 3000,
+  }),
+  parsedLevel({
     width: 720,
     height: 720,
-    bandwidth: 4000,
-  },
+    bitrate: 4000,
+  }),
 ];
+const levels = parsedLevels.map((parsedLevel) => new Level(parsedLevel));
 
 describe('CapLevelController', function () {
   describe('getMaxLevelByMediaSize', function () {
@@ -63,16 +71,6 @@ describe('CapLevelController', function () {
       const actual = CapLevelController.getMaxLevelByMediaSize([], 5000, 5000);
       expect(expected).to.equal(actual);
     });
-
-    it('Should return -1 if there levels is undefined', function () {
-      const expected = -1;
-      const actual = CapLevelController.getMaxLevelByMediaSize(
-        undefined,
-        5000,
-        5000,
-      );
-      expect(expected).to.equal(actual);
-    });
   });
 
   describe('getDimensions', function () {
@@ -100,7 +98,10 @@ describe('CapLevelController', function () {
       if (media.parentNode) {
         media.parentNode.removeChild(media);
       }
-      document.body.removeChild(document.querySelector('#test-fixture'));
+      const fixture = document.querySelector('#test-fixture');
+      if (fixture) {
+        document.body.removeChild(fixture);
+      }
       hls.destroy();
     });
 
@@ -126,7 +127,14 @@ describe('CapLevelController', function () {
     it('gets client bounds width and height when media element is in the DOM', function () {
       media.style.width = '1280px';
       media.style.height = '720px';
-      document.querySelector('#test-fixture').appendChild(media);
+
+      const fixture = document.querySelector('#test-fixture');
+      if (!fixture) {
+        expect(fixture).is.not.null;
+        return;
+      }
+      fixture.appendChild(media);
+
       const pixelRatio = capLevelController.contentScaleFactor;
       const bounds = capLevelController.getDimensions();
       expect(bounds.width).to.equal(1280);
@@ -150,11 +158,17 @@ describe('CapLevelController', function () {
 
       media.style.width = '1280px';
       media.style.height = '720px';
-      document.querySelector('#test-fixture').appendChild(media);
+
+      const fixture = document.querySelector('#test-fixture');
+      if (!fixture) {
+        expect(fixture).is.not.null;
+        return;
+      }
+      fixture.appendChild(media);
+
       capLevelController.onMediaAttaching(Events.MEDIA_ATTACHING, {
         media,
       });
-
       const pixelRatio = capLevelController.contentScaleFactor;
       bounds = capLevelController.getDimensions();
       expect(bounds.width).to.equal(1280);
diff --git a/tests/unit/controller/eme-controller.ts b/tests/unit/controller/eme-controller.ts
index 2564181b2db..52aa6688a7f 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', {});
     });
   }
@@ -158,9 +158,6 @@ describe('EMEController', function () {
     } as any);
 
     expect(emePromise).to.be.a('Promise');
-    if (!emePromise) {
-      return;
-    }
     return emePromise.finally(() => {
       expect(media.setMediaKeys).callCount(1);
       expect(reqMediaKsAccessSpy).callCount(1);
@@ -226,9 +223,6 @@ describe('EMEController', function () {
     } as any);
 
     expect(emePromise).to.be.a('Promise');
-    if (!emePromise) {
-      return;
-    }
     return emePromise.finally(() => {
       expect(reqMediaKsAccessSpy).callCount(1);
       const args = reqMediaKsAccessSpy.getCall(0)
@@ -379,58 +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');
-    if (
-      !emeController.keyIdToKeySessionPromise[
-        '00000000000000000000000000000000'
-      ]
-    ) {
-      return;
-    }
-    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 () {
@@ -482,30 +469,27 @@ 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[
         '00000000000000000000000000000000'
       ],
     ).to.be.a('Promise');
-    if (
-      !emeController.keyIdToKeySessionPromise[
-        '00000000000000000000000000000000'
-      ]
-    ) {
-      return;
-    }
     return emeController.keyIdToKeySessionPromise[
       '00000000000000000000000000000000'
     ]
@@ -565,30 +549,27 @@ 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[
         '00000000000000000000000000000000'
       ],
     ).to.be.a('Promise');
-    if (
-      !emeController.keyIdToKeySessionPromise[
-        '00000000000000000000000000000000'
-      ]
-    ) {
-      return;
-    }
     return emeController.keyIdToKeySessionPromise[
       '00000000000000000000000000000000'
     ]
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();
     });
diff --git a/tests/unit/controller/fragment-finders.js b/tests/unit/controller/fragment-finders.ts
similarity index 60%
rename from tests/unit/controller/fragment-finders.js
rename to tests/unit/controller/fragment-finders.ts
index a5b9c8cf7d1..58b2655df6b 100644
--- a/tests/unit/controller/fragment-finders.js
+++ b/tests/unit/controller/fragment-finders.ts
@@ -1,11 +1,28 @@
+import chai from 'chai';
+import sinon from 'sinon';
+import sinonChai from 'sinon-chai';
 import {
   findFragmentByPDT,
   findFragmentByPTS,
+  findNearestWithCC,
   fragmentWithinToleranceTest,
   pdtWithinToleranceTest,
 } from '../../../src/controller/fragment-finders';
-import { mockFragments } from '../../mocks/data';
+import { LevelDetails } from '../../../src/loader/level-details';
 import BinarySearch from '../../../src/utils/binary-search';
+import {
+  fragment,
+  mockFragments,
+  mockFragmentsWithDiscos,
+} from '../../mocks/data';
+import type { MediaFragment } from '../../../src/loader/fragment';
+
+chai.use(sinonChai);
+const expect = chai.expect;
+
+function makeMockFragment(options): MediaFragment {
+  return fragment(Object.assign({ start: 0, sn: 0, cc: 0, level: 0 }, options));
+}
 
 describe('Fragment finders', function () {
   const sandbox = sinon.createSandbox();
@@ -13,15 +30,14 @@ describe('Fragment finders', function () {
     sandbox.restore();
   });
 
-  const fragPrevious = {
+  const fragPrevious = makeMockFragment({
     programDateTime: 1505502671523,
-    endProgramDateTime: 1505502676523,
     duration: 5.0,
     level: 1,
     start: 10.0,
     sn: 2, // Fragment with PDT 1505502671523 in level 1 does not have the same sn as in level 2 where cc is 1
     cc: 0,
-  };
+  });
   const bufferEnd = fragPrevious.start + fragPrevious.duration;
 
   describe('findFragmentByPTS', function () {
@@ -32,7 +48,10 @@ describe('Fragment finders', function () {
     });
 
     it('finds a fragment with SN sequential to the previous fragment', function () {
-      const fragPreviousSameLevel = { ...fragPrevious, level: 2 };
+      const fragPreviousSameLevel = makeMockFragment({
+        ...fragPrevious,
+        level: 2,
+      });
       const actual = findFragmentByPTS(
         fragPreviousSameLevel,
         mockFragments,
@@ -67,22 +86,22 @@ describe('Fragment finders', function () {
       // See https://github.com/video-dev/hls.js/issues/2776
       const bufferEnd = 60.139636;
       const fragments = [
-        {
+        makeMockFragment({
           deltaPTS: 0.012346258503441732,
           cc: 2,
           duration: 5.017346258503444,
           start: 55.21705215419478,
           sn: 11,
           level: 0,
-        },
-        {
+        }),
+        makeMockFragment({
           deltaPTS: 0,
           cc: 2,
           duration: 0.033,
           start: 60.234398412698226,
           sn: 12,
           level: 0,
-        },
+        }),
       ];
       const fragPrevious = fragments[0];
       const actual = findFragmentByPTS(
@@ -111,40 +130,40 @@ describe('Fragment finders', function () {
     it('returns null when there is a gap in sn and start-end time', function () {
       const bufferEnd = 49;
       const fragments = [
-        {
+        makeMockFragment({
           deltaPTS: 0,
           cc: 0,
           duration: 5,
           start: 54,
           sn: 5,
           level: 0,
-        },
-        {
+        }),
+        makeMockFragment({
           deltaPTS: 0,
           cc: 0,
           duration: 5,
           start: 59,
           sn: 5,
           level: 0,
-        },
-        {
+        }),
+        makeMockFragment({
           deltaPTS: 0,
           cc: 0,
           duration: 5,
           start: 64,
           sn: 5,
           level: 0,
-        },
+        }),
       ];
       // sn is not contiguous, and there is a gap between start and end
-      const fragPrevious = {
+      const fragPrevious = makeMockFragment({
         deltaPTS: 0,
         cc: 0,
         duration: 5,
         start: 44,
         sn: 1,
         level: 0,
-      };
+      });
       const actual = findFragmentByPTS(
         fragPrevious,
         fragments,
@@ -158,72 +177,72 @@ describe('Fragment finders', function () {
   describe('fragmentWithinToleranceTest', function () {
     const tolerance = 0.25;
     it('returns 0 if the fragment range is equal to the end of the buffer', function () {
-      const frag = {
+      const frag = makeMockFragment({
         start: 5,
         duration: 5 - tolerance,
-      };
+      });
       const actual = fragmentWithinToleranceTest(5, tolerance, frag);
       expect(actual).to.equal(0);
     });
 
     it('returns 0 if the fragment range is greater than end of the buffer', function () {
-      const frag = {
+      const frag = makeMockFragment({
         start: 5,
         duration: 5,
-      };
+      });
       const actual = fragmentWithinToleranceTest(5, tolerance, frag);
       expect(actual).to.equal(0);
     });
 
     it('returns 1 if the fragment range is less than the end of the buffer', function () {
-      const frag = {
+      const frag = makeMockFragment({
         start: 0,
         duration: 5,
-      };
+      });
       const actual = fragmentWithinToleranceTest(5, tolerance, frag);
       expect(actual).to.equal(1);
     });
 
     it('returns -1 if the fragment range is greater than the end of the buffer', function () {
-      const frag = {
+      const frag = makeMockFragment({
         start: 6,
         duration: 5,
-      };
+      });
       const actual = fragmentWithinToleranceTest(5, tolerance, frag);
       expect(actual).to.equal(-1);
     });
 
     it('does not skip very small fragments at the start', function () {
-      const frag = {
+      const frag = makeMockFragment({
         start: 0.2,
         duration: 0.1,
         deltaPTS: 0.1,
-      };
+      });
       const actual = fragmentWithinToleranceTest(0, tolerance, frag);
       expect(actual).to.equal(0);
     });
     it('does not skip very small fragments', function () {
-      const frag = {
+      const frag = makeMockFragment({
         start: 5,
         duration: 0.1,
         deltaPTS: 0.1,
-      };
+      });
       const actual = fragmentWithinToleranceTest(5, tolerance, frag);
       expect(actual).to.equal(0);
     });
     it('does not skip very small fragments without deltaPTS', function () {
-      const frag = {
+      const frag = makeMockFragment({
         start: 5,
         duration: 0.1,
-      };
+      });
       const actual = fragmentWithinToleranceTest(5, tolerance, frag);
       expect(actual).to.equal(0);
     });
     it('does not skip fragments when searching near boundaries', function () {
-      const frag = {
+      const frag = makeMockFragment({
         start: 19.96916,
         duration: 9.98458,
-      };
+      });
       const actual = fragmentWithinToleranceTest(29, 0.25, frag);
       expect(actual).to.equal(0);
     });
@@ -233,7 +252,8 @@ describe('Fragment finders', function () {
     it('finds a fragment with endProgramDateTime greater than the reference PDT', function () {
       const foundFragment = findFragmentByPDT(
         mockFragments,
-        fragPrevious.endProgramDateTime + 1,
+        (fragPrevious.endProgramDateTime as number) + 1,
+        0,
       );
       expect(foundFragment).to.equal(mockFragments[2]);
     });
@@ -241,13 +261,16 @@ describe('Fragment finders', function () {
     it('returns null when the reference pdt is outside of the pdt range of the fragment array', function () {
       let foundFragment = findFragmentByPDT(
         mockFragments,
-        mockFragments[0].programDateTime - 1,
+        (mockFragments[0].programDateTime as number) - 1,
+        0,
       );
       expect(foundFragment).to.not.exist;
 
       foundFragment = findFragmentByPDT(
         mockFragments,
-        mockFragments[mockFragments.length - 1].endProgramDateTime + 1,
+        (mockFragments[mockFragments.length - 1].endProgramDateTime as number) +
+          1,
+        0,
       );
       expect(foundFragment).to.not.exist;
     });
@@ -256,6 +279,7 @@ describe('Fragment finders', function () {
       const foundFragment = findFragmentByPDT(
         mockFragments,
         mockFragments[0].programDateTime,
+        0,
       );
       const resultSN = foundFragment ? foundFragment.sn : -1;
       expect(foundFragment).to.equal(
@@ -268,6 +292,7 @@ describe('Fragment finders', function () {
       const foundFragment = findFragmentByPDT(
         mockFragments,
         mockFragments[mockFragments.length - 1].programDateTime,
+        0,
       );
       const resultSN = foundFragment ? foundFragment.sn : -1;
       expect(foundFragment).to.equal(
@@ -278,29 +303,29 @@ describe('Fragment finders', function () {
 
     it('is able to find a fragment if the PDT value is 0', function () {
       const fragments = [
-        {
+        makeMockFragment({
           programDateTime: 0,
-          endProgramDateTime: 1,
           duration: 0.001,
-        },
-        {
+        }),
+        makeMockFragment({
           programDateTime: 1,
-          endProgramDateTime: 2,
           duration: 0.001,
-        },
+        }),
       ];
-      const actual = findFragmentByPDT(fragments, 0);
+      const actual = findFragmentByPDT(fragments, 0, 0);
       expect(actual).to.equal(fragments[0]);
     });
 
     it('returns null when passed undefined arguments', function () {
-      expect(findFragmentByPDT(mockFragments)).to.not.exist;
-      expect(findFragmentByPDT(undefined, 9001)).to.not.exist;
-      expect(findFragmentByPDT()).to.not.exist;
+      expect(findFragmentByPDT(mockFragments, 0, 0)).to.not.exist;
+      expect(findFragmentByPDT(undefined as any, 9001, 0)).to.not.exist;
+      expect(
+        findFragmentByPDT(undefined as any, undefined as any, undefined as any),
+      ).to.not.exist;
     });
 
     it('returns null when passed an empty frag array', function () {
-      expect(findFragmentByPDT([], 9001)).to.not.exist;
+      expect(findFragmentByPDT([], 9001, 0)).to.not.exist;
     });
   });
 
@@ -308,44 +333,167 @@ describe('Fragment finders', function () {
     const tolerance = 0.25;
     const pdtBufferEnd = 1505502678523; // Fri Sep 15 2017 15:11:18 GMT-0400 (Eastern Daylight Time)
     it('returns true if the fragment range is equal to the end of the buffer', function () {
-      const frag = {
+      const frag = makeMockFragment({
         programDateTime: pdtBufferEnd,
-        endProgramDateTime: pdtBufferEnd + 5000 - tolerance * 1000,
-        duration: 5,
-      };
+        duration: 5 - tolerance,
+      });
       const actual = pdtWithinToleranceTest(pdtBufferEnd, tolerance, frag);
       expect(actual).to.be.true;
     });
 
     it('returns false if the fragment range is less than the end of the buffer', function () {
-      const frag = {
+      const frag = makeMockFragment({
         programDateTime: pdtBufferEnd - 10000,
-        endProgramDateTime: pdtBufferEnd - 5000,
         duration: 5,
-      };
+      });
       const actual = pdtWithinToleranceTest(pdtBufferEnd, tolerance, frag);
       expect(actual).to.be.false;
     });
 
     it('does not skip very small fragments', function () {
-      const frag = {
+      const frag = makeMockFragment({
         programDateTime: pdtBufferEnd + 200,
-        endProgramDateTime: pdtBufferEnd + 300,
         duration: 0.1,
         deltaPTS: 0.1,
-      };
+      });
       const actual = pdtWithinToleranceTest(pdtBufferEnd, tolerance, frag);
       expect(actual).to.be.true;
     });
 
     it('accounts for tolerance when checking the endProgramDateTime of the fragment', function () {
-      const frag = {
+      const frag = makeMockFragment({
         programDateTime: pdtBufferEnd,
-        endProgramDateTime: pdtBufferEnd + tolerance * 1000,
-        duration: 5,
-      };
+        duration: tolerance,
+      });
       const actual = pdtWithinToleranceTest(pdtBufferEnd, tolerance, frag);
       expect(actual).to.be.false;
     });
   });
+
+  describe('findNearestWithCC', function () {
+    const levelDetails = new LevelDetails('');
+    levelDetails.startCC = mockFragmentsWithDiscos[0].cc;
+    levelDetails.endCC =
+      mockFragmentsWithDiscos[mockFragmentsWithDiscos.length - 1].cc;
+    levelDetails.fragments = mockFragmentsWithDiscos;
+
+    const detailsToDomains = (levelDetails: LevelDetails) =>
+      levelDetails.fragments.reduce((a, f) => {
+        a[f.cc] ||= [f.start, f.end];
+        a[f.cc][1] = f.end;
+        return a;
+      }, {});
+    const expectToFindFragment = (
+      levelDetails: LevelDetails,
+      cc: number,
+      pos: number,
+      props: Partial,
+    ) => {
+      const frag = findNearestWithCC(levelDetails, cc, pos);
+      expect(frag).to.be.an('object');
+      Object.entries(props).forEach(([prop, val]) => {
+        expect(
+          frag,
+          `findNearestWithCC(${JSON.stringify(detailsToDomains(levelDetails))}, cc: ${cc}, pos: ${pos}) => frag.${prop}`,
+        )
+          .to.have.property(prop)
+          .which.equals(val);
+      });
+    };
+
+    it('returns a fragment with discontinuity sequence (cc) that starts at the given position', function () {
+      expectToFindFragment(levelDetails, 1, 10, {
+        start: 10,
+        sn: 2,
+        cc: 1,
+      });
+    });
+
+    it('returns a fragment with discontinuity sequence (cc) whose range intersects with the given position', function () {
+      expectToFindFragment(levelDetails, 0, 1, {
+        start: 0,
+        sn: 0,
+        cc: 0,
+      });
+      expectToFindFragment(levelDetails, 1, 17, {
+        start: 15,
+        sn: 3,
+        cc: 1,
+      });
+      expectToFindFragment(levelDetails, 2, 36, {
+        start: 35,
+        sn: 7,
+        cc: 2,
+      });
+    });
+
+    it('returns first fragment with discontinuity sequence (cc) when given a position before the domain', function () {
+      expectToFindFragment(levelDetails, 1, 0, {
+        start: 5,
+        sn: 1,
+        cc: 1,
+      });
+      expectToFindFragment(levelDetails, 2, 0, {
+        start: 25,
+        sn: 5,
+        cc: 2,
+      });
+      expectToFindFragment(levelDetails, 2, 19, {
+        start: 25,
+        sn: 5,
+        cc: 2,
+      });
+      expectToFindFragment(levelDetails, 2, 24, {
+        start: 25,
+        sn: 5,
+        cc: 2,
+      });
+    });
+
+    it('returns last fragment with discontinuity sequence (cc) when given a position past the domain', function () {
+      expectToFindFragment(levelDetails, 1, 25, {
+        start: 20,
+        sn: 4,
+        cc: 1,
+      });
+      expectToFindFragment(levelDetails, 1, 30, {
+        start: 20,
+        sn: 4,
+        cc: 1,
+      });
+      expectToFindFragment(levelDetails, 1, 33, {
+        start: 20,
+        sn: 4,
+        cc: 1,
+      });
+      expectToFindFragment(levelDetails, 2, 50, {
+        start: 35,
+        sn: 7,
+        cc: 2,
+      });
+    });
+
+    it('returns a fragment with discontinuity sequence (cc) at the end of the playlist', function () {
+      const lastFragment =
+        levelDetails.fragments[levelDetails.fragments.length - 1];
+      const { start, sn, cc } = lastFragment;
+      expectToFindFragment(levelDetails, cc, lastFragment.end, {
+        start,
+        sn,
+        cc,
+      });
+    });
+
+    it('returns null when LevelDetails does not include discontinuity sequence (cc)', function () {
+      const result1 = findNearestWithCC(levelDetails, 5, 0);
+      expect(result1).to.be.null;
+      const result2 = findNearestWithCC(levelDetails, -1, 0);
+      expect(result2).to.be.null;
+    });
+
+    it('returns null when LevelDetails is undefined', function () {
+      const result = findNearestWithCC(undefined, 0, 0);
+      expect(result).to.be.null;
+    });
+  });
 });
diff --git a/tests/unit/controller/fragment-tracker.ts b/tests/unit/controller/fragment-tracker.ts
index d245ef6fb6c..8c0fdf28e7c 100644
--- a/tests/unit/controller/fragment-tracker.ts
+++ b/tests/unit/controller/fragment-tracker.ts
@@ -608,7 +608,7 @@ function createBufferAppendedData(
     frag: new Fragment(PlaylistLevelType.MAIN, ''),
     part: null,
     parent: PlaylistLevelType.MAIN,
-    type: audio && video ? 'audiovideo' : video ? 'video' : 'audio',
+    type: audio ? 'audiovideo' : 'video',
     timeRanges: {
       video: createMockBuffer(video),
       audio: createMockBuffer(audio || video),
@@ -655,7 +655,7 @@ function createMockFragment(
 ): Fragment {
   const frag = new Fragment(data.type, '');
   Object.assign(frag, data);
-  frag.start = data.startPTS;
+  frag.setStart(data.startPTS);
   frag.duration = data.endPTS - data.startPTS;
   types.forEach((t) => {
     frag.setElementaryStreamInfo(
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 11b4fb19e9e..0e8e2a1cfce 100644
--- a/tests/unit/controller/interstitials-controller.ts
+++ b/tests/unit/controller/interstitials-controller.ts
@@ -36,10 +36,7 @@ class HLSTestPlayer extends Hls {
     hlsTestable.coreComponents.forEach((component) => component.destroy());
     hlsTestable.coreComponents.length = 0;
     hlsTestable.on(Events.MEDIA_ATTACHING, (t, data) => {
-      const media = data.media;
-      if (media) {
-        media.src = '';
-      }
+      data.media.src = '';
     });
     hlsTestable.on(Events.MEDIA_DETACHING, () => {
       const media = hlsTestable.media;
@@ -140,7 +137,7 @@ describe('InterstitialsController', function () {
       0,
       null,
     );
-    expect(details?.playlistParsingError).to.equal(null);
+    expect(details.playlistParsingError).to.equal(null);
     const attrs = new AttrList({});
     const level = new Level({
       name: '',
@@ -210,9 +207,6 @@ fileSequence4.ts
       expect(insterstitials.playingIndex).to.equal(0, 'playingIndex');
       expect(insterstitials.events).is.an('array').which.has.lengthOf(1);
       expect(schedule).is.an('array').which.has.lengthOf(2);
-      if (!insterstitials.events || !schedule) {
-        return;
-      }
       const interstitialEvent = insterstitials.events[0];
       expect(interstitialEvent.identifier).to.equal('0');
       expect(interstitialEvent.restrictions.jump).to.equal(true);
@@ -296,11 +290,8 @@ fileSequence4.ts
         .is.an('array')
         .which.has.lengthOf(
           7,
-          `Schedule items: ${schedule?.map((item) => `${item.start}-${item.end}`).join(', ')}`,
+          `Schedule items: ${schedule.map((item) => `${item.start}-${item.end}`).join(', ')}`,
         );
-      if (!events || !schedule) {
-        return;
-      }
       const eventAssertions = [
         {
           startTime: 0,
@@ -478,13 +469,10 @@ fileSequence3.ts
       const events = insterstitials.events;
       const schedule = insterstitials.schedule;
       expect(events).is.an('array').which.has.lengthOf(5);
-      const scheduleDebugString = `Schedule items: ${schedule?.map((item) => `[${item.event ? 'I' : 'P'}:${item.start}-${item.end}]`).join(', ')}`;
+      const scheduleDebugString = `Schedule items: ${schedule.map((item) => `[${item.event ? 'I' : 'P'}:${item.start}-${item.end}]`).join(', ')}`;
       expect(schedule)
         .is.an('array')
         .which.has.lengthOf(9, scheduleDebugString);
-      if (!events || !schedule) {
-        return;
-      }
       [
         'primary',
         '1',
@@ -498,12 +486,12 @@ fileSequence3.ts
       ].forEach((typeOrIdentifier, i) => {
         if (typeOrIdentifier === 'primary') {
           expect(
-            schedule?.[i],
+            schedule[i],
             `Expected to find a primary segment at index ${i}: ${scheduleDebugString}`,
           ).to.have.property('nextEvent');
         } else {
           expect(
-            schedule?.[i],
+            schedule[i],
             `Expected to find an Interstitial at index ${i}: ${scheduleDebugString}`,
           )
             .to.have.property('event')
@@ -725,9 +713,6 @@ fileSequence3.mp4
       expect(insterstitials.playingIndex).to.equal(0, 'playingIndex');
       expect(insterstitials.events).is.an('array').which.has.lengthOf(2);
       expect(schedule).is.an('array').which.has.lengthOf(4);
-      if (!insterstitials.events || !schedule) {
-        return;
-      }
       expect(insterstitials.events[0].identifier).to.equal('ad1');
       expect(insterstitials.events[1].identifier).to.equal('ad2');
       expect(insterstitials.events[0]).to.equal(schedule[0].event);
@@ -827,9 +812,6 @@ fileSequence3.mp4
       const schedule = insterstitials.schedule;
       expect(insterstitials.events).is.an('array').which.has.lengthOf(2);
       expect(schedule).is.an('array').which.has.lengthOf(2);
-      if (!insterstitials.events || !schedule) {
-        return;
-      }
       expect(insterstitials.events[0].identifier).to.equal('ad1');
       expect(insterstitials.events[1].identifier).to.equal('ad2');
       expect(insterstitials.events[0]).to.equal(schedule[0].event);
@@ -891,9 +873,6 @@ fileSequence4.ts
         const events = insterstitials.events;
         expect(events).is.an('array').which.has.lengthOf(1);
         expect(schedule).is.an('array').which.has.lengthOf(2);
-        if (!events || !schedule) {
-          return;
-        }
         expect(events[0]).to.deep.include({
           identifier: '0',
           timelineOccupancy: TimelineOccupancy.Point,
@@ -986,9 +965,6 @@ fileSequence4.ts
         const events = insterstitials.events;
         expect(events).is.an('array').which.has.lengthOf(4);
         expect(schedule).is.an('array').which.has.lengthOf(5);
-        if (!events || !schedule) {
-          return;
-        }
         eventAssertions.forEach((assertions, i) => {
           expect(events[i].identifier).to.equal('' + i);
           expect(events[i], `Interstitial Event "${i}"`).to.deep.include(
@@ -1118,9 +1094,6 @@ fileSequence5.mp4`;
       }
       expect(insterstitials.events).is.an('array').which.has.lengthOf(2);
       expect(insterstitials.schedule).is.an('array').which.has.lengthOf(4);
-      if (!insterstitials.events || !insterstitials.schedule) {
-        return;
-      }
       const callsWithPrerollBeforeAttach = getTriggerCalls();
       expect(callsWithPrerollBeforeAttach).to.deep.equal(
         [Events.LEVEL_UPDATED, Events.INTERSTITIALS_UPDATED],
@@ -1139,6 +1112,7 @@ fileSequence5.mp4`;
         Events.INTERSTITIAL_STARTED,
         Events.INTERSTITIAL_ASSET_STARTED,
         Events.MEDIA_DETACHING,
+        Events.MEDIA_DETACHED,
       ];
       expect(callsWithPrerollAfterAttach).to.deep.equal(
         expectedEvents,
@@ -1185,9 +1159,6 @@ fileSequence3.mp4
       }
       expect(insterstitials.events).is.an('array').which.has.lengthOf(1);
       expect(insterstitials.schedule).is.an('array').which.has.lengthOf(2);
-      if (!insterstitials.events || !insterstitials.schedule) {
-        return;
-      }
       const callsBeforeAttach = getTriggerCalls();
       expect(callsBeforeAttach).to.deep.equal(
         [
@@ -1212,7 +1183,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(
@@ -1255,9 +1226,6 @@ fileSequence3.mp4
       }
       expect(insterstitials.events).is.an('array').which.has.lengthOf(1);
       expect(insterstitials.schedule).is.an('array').which.has.lengthOf(2);
-      if (!insterstitials.events || !insterstitials.schedule) {
-        return;
-      }
       const callsBeforeAttach = getTriggerCalls();
       expect(callsBeforeAttach).to.deep.equal(
         [
@@ -1282,7 +1250,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(
@@ -1333,9 +1301,6 @@ fileSequence6.mp4`;
       }
       expect(insterstitials.events).is.an('array').which.has.lengthOf(1);
       expect(insterstitials.schedule).is.an('array').which.has.lengthOf(3);
-      if (!insterstitials.events || !insterstitials.schedule) {
-        return;
-      }
       const eventsBeforeAttach = getTriggerCalls();
       expect(eventsBeforeAttach).to.deep.equal(
         [Events.LEVEL_UPDATED, Events.INTERSTITIALS_UPDATED],
@@ -1373,7 +1338,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(
@@ -1382,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`,
       );
@@ -1463,9 +1429,6 @@ fileSequence6.mp4`;
       }
       expect(insterstitials.events).is.an('array').which.has.lengthOf(1);
       expect(insterstitials.schedule).is.an('array').which.has.lengthOf(3);
-      if (!insterstitials.events || !insterstitials.schedule) {
-        return;
-      }
       const eventsAfterPlaylist = getTriggerCalls();
       expect(eventsAfterPlaylist).to.deep.equal(
         [
@@ -1490,7 +1453,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(
@@ -1499,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`,
       );
@@ -1508,10 +1472,10 @@ fileSequence6.mp4`;
       insterstitials.skip();
       const eventsAfterSkip = getTriggerCalls();
       const expectedSkipEvents = [
-        Events.INTERSTITIALS_BUFFERED_TO_BOUNDARY,
         Events.INTERSTITIAL_ASSET_ENDED,
         Events.INTERSTITIAL_ENDED,
         Events.INTERSTITIALS_UPDATED, // removed Interstitial with CUE="ONCE"
+        Events.INTERSTITIALS_BUFFERED_TO_BOUNDARY,
         Events.MEDIA_ATTACHING,
         Events.INTERSTITIALS_PRIMARY_RESUMED,
       ];
@@ -1607,17 +1571,13 @@ fileSequence6.mp4
             duration: 15,
           });
         } else if (bufferingIndex === 2) {
-          // buffered to primary (end of interstitial)
+          // buffered to primary (interstitial ended)
           expectIm(primary, e).to.include({ currentTime: 40, bufferedEnd: 40 });
           expectIm(integrated, e).to.include({
             currentTime: 45,
             bufferedEnd: 45,
           });
-          expectIm(interstitialPlayer, e).to.include({
-            playingIndex: 2,
-            currentTime: 15,
-            duration: 15,
-          });
+          expectIm(interstitialPlayer, e).to.be.null;
         }
       });
       hls.on(Events.INTERSTITIAL_STARTED, (t) => {
@@ -1799,9 +1759,6 @@ fileSequence6.mp4
 
       expect(im.events).is.an('array').which.has.lengthOf(1);
       expect(im.schedule).is.an('array').which.has.lengthOf(3);
-      if (!im.events || !im.schedule) {
-        return;
-      }
       const eventsBeforeAttach = getTriggerCalls();
       expect(eventsBeforeAttach).to.deep.equal(
         [Events.LEVEL_UPDATED, Events.INTERSTITIALS_UPDATED],
@@ -1874,7 +1831,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(
@@ -1885,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`,
       );
@@ -1956,9 +1914,9 @@ fileSequence6.mp4
       });
       const eventsAfterLastAsset = getTriggerCalls();
       const expectedEndLastAssetEvents = [
-        Events.INTERSTITIALS_BUFFERED_TO_BOUNDARY,
         Events.INTERSTITIAL_ASSET_ENDED,
         Events.INTERSTITIAL_ENDED,
+        Events.INTERSTITIALS_BUFFERED_TO_BOUNDARY,
         Events.MEDIA_ATTACHING,
         Events.INTERSTITIALS_PRIMARY_RESUMED,
       ];
@@ -2008,12 +1966,10 @@ fileSequence6.mp4`;
       }
       expect(insterstitials.events).is.an('array').which.has.lengthOf(1);
       expect(insterstitials.schedule).is.an('array').which.has.lengthOf(3);
-      if (!insterstitials.events || !insterstitials.schedule) {
-        return;
-      }
 
       // Capture asset-list request
       const loadSpy = sandbox.spy(hls.config.loader.prototype, 'load');
+      const primaryId = hls.sessionId;
 
       // Attach media
       hls.trigger.resetHistory();
@@ -2032,7 +1988,7 @@ fileSequence6.mp4`;
         assetListUrl,
         '_HLS_primary_id and _HLS_start_offset match',
       ).to.equal(
-        `https://example.com/mid.json?_HLS_primary_id=${hls.sessionId}&_HLS_start_offset=10`,
+        `https://example.com/mid.json?_HLS_primary_id=${primaryId}&_HLS_start_offset=10`,
       );
       expect(eventsAfterAttach).to.deep.equal(
         expectedEvents,
@@ -2046,12 +2002,12 @@ fileSequence6.mp4`;
       hls.trigger.resetHistory();
       const interstitial = insterstitials.events[0];
       interstitial.assetListResponse = {
-        ASSETS: [{ URI: '', DURATION: '30' }],
+        ASSETS: [{ URI: 'https://example.com/midroll.m3u8', DURATION: '30' }],
       };
       hls.trigger(Events.ASSET_LIST_LOADED, {
         event: interstitial,
         assetListResponse: interstitial.assetListResponse,
-        networkDetails: {},
+        networkDetails: new Response('ok'),
       });
       const eventsAfterAssetListLoaded = getTriggerCalls();
       expect(eventsAfterAssetListLoaded).to.deep.equal(
@@ -2060,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`,
       );
@@ -2078,6 +2035,16 @@ fileSequence6.mp4`;
         insterstitials.interstitialPlayer?.scheduleItem?.event,
         `interstitialPlayer.scheduleItem`,
       ).to.include({ identifier: 'mid-live' });
+      expect(
+        insterstitials.interstitialPlayer?.assetPlayers,
+        `interstitialPlayer.assetPlayers[]`,
+      ).to.have.lengthOf(1);
+      expect(
+        insterstitials.interstitialPlayer?.assetPlayers[0]?.hls?.url,
+        `interstitialPlayer.assetPlayers[0].hls.url`,
+      ).to.equal(
+        `https://example.com/midroll.m3u8?_HLS_primary_id=${primaryId}`,
+      );
     });
   });
 });
diff --git a/tests/unit/controller/level-controller.ts b/tests/unit/controller/level-controller.ts
index 232f1928bc6..812d0e0580b 100755
--- a/tests/unit/controller/level-controller.ts
+++ b/tests/unit/controller/level-controller.ts
@@ -10,6 +10,7 @@ import { PlaylistLevelType } from '../../../src/types/loader';
 import { AttrList } from '../../../src/utils/attr-list';
 import { getMediaSource } from '../../../src/utils/mediasource-helper';
 import HlsMock from '../../mocks/hls.mock';
+import { parsedLevel } from '../utils/mock-level';
 import type { Fragment } from '../../../src/loader/fragment';
 import type { LevelDetails } from '../../../src/loader/level-details';
 import type { ParsedMultivariantPlaylist } from '../../../src/loader/m3u8-parser';
@@ -17,7 +18,6 @@ import type {
   ManifestLoadedData,
   ManifestParsedData,
 } from '../../../src/types/events';
-import type { LevelParsed } from '../../../src/types/level';
 import type { PlaylistLoaderContext } from '../../../src/types/loader';
 import type {
   MediaAttributes,
@@ -49,18 +49,6 @@ type LevelControllerTestable = Omit & {
   redundantFailover: (levelIndex: number) => void;
 };
 
-function parsedLevel(
-  options: Partial & { bitrate: number },
-): LevelParsed {
-  const level: LevelParsed = {
-    attrs: new AttrList({ BANDWIDTH: options.bitrate }),
-    bitrate: options.bitrate,
-    name: '',
-    url: `${options.bitrate}.m3u8`,
-  };
-  return Object.assign(level, options);
-}
-
 function mediaPlaylist(options: Partial): MediaPlaylist {
   const track: MediaPlaylist = {
     attrs: new AttrList({}) as MediaAttributes,
@@ -129,7 +117,7 @@ describe('LevelController', function () {
           name: '1080',
         }),
       ],
-      networkDetails: '',
+      networkDetails: new Response('ok'),
       sessionData: null,
       sessionKeys: null,
       contentSteering: null,
@@ -198,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',
       });
@@ -244,7 +232,7 @@ describe('LevelController', function () {
             name: '1080',
           }),
         ],
-        networkDetails: '',
+        networkDetails: new Response('ok'),
         subtitles: [],
         sessionData: null,
         sessionKeys: null,
@@ -329,7 +317,7 @@ http://bar.example.com/audio-only/prog_index.m3u8`,
             name: '144',
           }),
         ],
-        networkDetails: '',
+        networkDetails: new Response('ok'),
         subtitles: [],
         sessionData: null,
         sessionKeys: null,
@@ -373,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,
@@ -412,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,
@@ -450,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 1fb5914a988..cbc5ae1ab0e 100644
--- a/tests/unit/controller/level-helper.ts
+++ b/tests/unit/controller/level-helper.ts
@@ -19,6 +19,7 @@ import {
   mapPartIntersection,
   mergeDetails,
 } from '../../../src/utils/level-helper';
+import { logger } from '../../../src/utils/logger';
 import type { MediaFragment } from '../../../src/loader/fragment';
 import type {
   ComponentAPI,
@@ -48,7 +49,7 @@ const generatePlaylist = (sequenceNumbers, offset = 0, duration = 5) => {
   playlist.fragments = sequenceNumbers.map((n, i) => {
     const frag = new Fragment(PlaylistLevelType.MAIN, '');
     frag.sn = n;
-    frag.start = i * 5 + offset;
+    frag.setStart(i * 5 + offset);
     frag.duration = duration;
     return frag;
   });
@@ -67,7 +68,9 @@ const getIteratedSequence = (oldPlaylist, newPlaylist) => {
 };
 
 const getFragmentSequenceNumbers = (details: LevelDetails) =>
-  details.fragments.map((f) => `${f?.sn}-${f?.cc}`).join(',');
+  details.fragments
+    .map((f: MediaFragment | null) => `${f?.sn}-${f?.cc}`)
+    .join(',');
 
 describe('LevelHelper Tests', function () {
   let sandbox;
@@ -186,7 +189,7 @@ describe('LevelHelper Tests', function () {
     it('transfers start times where segments overlap, and extrapolates the start of any new segment', function () {
       const oldPlaylist = generatePlaylist([1, 2, 3, 4]); // start times: 0, 5, 10, 15
       const newPlaylist = generatePlaylist([2, 3, 4, 5]);
-      mergeDetails(oldPlaylist, newPlaylist);
+      mergeDetails(oldPlaylist, newPlaylist, logger);
       const actual = newPlaylist.fragments.map((f) => f.start);
       expect(actual).to.deep.equal([5, 10, 15, 20]);
       expect(newPlaylist.playlistParsingError).to.be.null;
@@ -195,7 +198,7 @@ describe('LevelHelper Tests', function () {
     it('applies expected sliding when there is no segment overlap', function () {
       const oldPlaylist = generatePlaylist([1, 2, 3]);
       const newPlaylist = generatePlaylist([5, 6, 7]);
-      mergeDetails(oldPlaylist, newPlaylist);
+      mergeDetails(oldPlaylist, newPlaylist, logger);
       const actual = newPlaylist.fragments.map((f) => f.start);
       expect(actual).to.deep.equal([20, 25, 30]);
       expect(newPlaylist.playlistParsingError).to.be.null;
@@ -204,10 +207,10 @@ describe('LevelHelper Tests', function () {
     it('matches start when the new playlist starts before the old', function () {
       const oldPlaylist = generatePlaylist([3, 4, 5]);
       oldPlaylist.fragments.forEach((f) => {
-        f.start += 10;
+        f.addStart(10);
       });
       const newPlaylist = generatePlaylist([1, 2, 3]);
-      mergeDetails(oldPlaylist, newPlaylist);
+      mergeDetails(oldPlaylist, newPlaylist, logger);
       const actual = newPlaylist.fragments.map((f) => f.start);
       expect(actual).to.deep.equal([10, 15, 20]);
       expect(newPlaylist.playlistParsingError).to.be.null;
@@ -221,7 +224,7 @@ describe('LevelHelper Tests', function () {
       // @ts-ignore
       newPlaylist.fragments.unshift(null, null, null, null, null, null, null);
       const merged = generatePlaylist([3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 10);
-      mergeDetails(oldPlaylist, newPlaylist);
+      mergeDetails(oldPlaylist, newPlaylist, logger);
       expect(newPlaylist.deltaUpdateFailed).to.equal(false);
       expect(newPlaylist.fragments.length).to.equal(merged.fragments.length);
       newPlaylist.fragments.forEach((frag, i) => {
@@ -244,7 +247,7 @@ expect: ${JSON.stringify(merged.fragments[i])}`,
       newPlaylist.fragments.unshift(null, null, null, null, null);
       // FIXME: An expected offset of 50 would be preferred, but there is nothing to sync playlist start with
       const merged = generatePlaylist([10, 11, 12], 0);
-      mergeDetails(oldPlaylist, newPlaylist);
+      mergeDetails(oldPlaylist, newPlaylist, logger);
       expect(newPlaylist.deltaUpdateFailed).to.equal(true);
       expect(newPlaylist.fragments.length).to.equal(3);
       newPlaylist.fragments.forEach((frag, i) => {
@@ -287,7 +290,7 @@ expect: ${JSON.stringify(merged.fragments[i])}`,
       newPlaylist.fragmentHint.sn = 5;
       newPlaylist.fragmentHint.initSegment = newInitSegment;
 
-      mergeDetails(oldPlaylist, newPlaylist);
+      mergeDetails(oldPlaylist, newPlaylist, logger);
 
       newPlaylist.fragments.forEach((frag, i) => {
         expect(
@@ -353,7 +356,7 @@ fileSequence11.ts
 #EXT-X-DATERANGE:ID="four",START-DATE="2024-02-29T12:02:04.000Z"`;
       const details = parseLevelPlaylist(playlist);
       const detailsUpdated = parseLevelPlaylist(playlistUpdate);
-      mergeDetails(details, detailsUpdated);
+      mergeDetails(details, detailsUpdated, logger);
       expect(details.hasProgramDateTime, 'details.hasProgramDateTime').to.be
         .true;
       expect(details.dateRanges, 'one')
@@ -374,12 +377,12 @@ fileSequence11.ts
         .which.equals(details.fragments[2].ref)
         .which.has.property('sn')
         .which.equals(5);
-      expect(details.dateRanges.one.startTime).to.equal(4);
-      expect(details.dateRanges.two.startTime).to.equal(10);
-      expect(details.dateRanges.three.startTime).to.equal(16);
-      expect(details.dateRanges.one.tagOrder, 'one.tagOrder').to.equal(0);
-      expect(details.dateRanges.two.tagOrder, 'two.tagOrder').to.equal(1);
-      expect(details.dateRanges.three.tagOrder, 'three.tagOrder').to.equal(2);
+      expect(details.dateRanges.one!.startTime).to.equal(4);
+      expect(details.dateRanges.two!.startTime).to.equal(10);
+      expect(details.dateRanges.three!.startTime).to.equal(16);
+      expect(details.dateRanges.one!.tagOrder, 'one.tagOrder').to.equal(0);
+      expect(details.dateRanges.two!.tagOrder, 'two.tagOrder').to.equal(1);
+      expect(details.dateRanges.three!.tagOrder, 'three.tagOrder').to.equal(2);
       expect(
         detailsUpdated.hasProgramDateTime,
         'detailsUpdated.hasProgramDateTime',
@@ -407,25 +410,26 @@ fileSequence11.ts
         .which.has.property('tagAnchor')
         .which.has.property('sn')
         .which.equals(5);
-      expect(detailsUpdated.dateRanges.one.startTime).to.equal(4);
-      expect(detailsUpdated.dateRanges.two.startTime).to.equal(10);
-      expect(detailsUpdated.dateRanges.three.startTime).to.equal(16);
-      expect(detailsUpdated.dateRanges.four.startTime).to.equal(76);
+      expect(detailsUpdated.dateRanges.one!.startTime).to.equal(4);
+      expect(detailsUpdated.dateRanges.two!.startTime).to.equal(10);
+      expect(detailsUpdated.dateRanges.three!.startTime).to.equal(16);
+      expect(detailsUpdated.dateRanges.four!.startTime).to.equal(76);
       expect(
-        detailsUpdated.dateRanges.one.tagOrder,
+        detailsUpdated.dateRanges.one!.tagOrder,
         'one.tagOrder updated',
       ).to.equal(0);
       expect(
-        detailsUpdated.dateRanges.two.tagOrder,
+        detailsUpdated.dateRanges.two!.tagOrder,
         'two.tagOrder updated',
       ).to.equal(1);
       expect(
-        detailsUpdated.dateRanges.three.tagOrder,
+        detailsUpdated.dateRanges.three!.tagOrder,
         'three.tagOrder updated',
       ).to.equal(2);
-      expect(detailsUpdated.dateRanges.four.tagOrder, 'four.tagOrder').to.equal(
-        3,
-      );
+      expect(
+        detailsUpdated.dateRanges.four!.tagOrder,
+        'four.tagOrder',
+      ).to.equal(3);
       expect(detailsUpdated.playlistParsingError).to.be.null;
     });
 
@@ -597,7 +601,7 @@ fileSequence12.ts`;
         updated: false,
       });
       // discontinuity sequence numbers (frag.cc) should be carried over
-      mergeDetails(details1, details2);
+      mergeDetails(details1, details2, logger);
       const mergedSequence1 = getFragmentSequenceNumbers(details2);
       expect(
         details2,
@@ -624,7 +628,7 @@ fileSequence12.ts`;
       });
 
       // discontinuity sequence numbers (frag.cc) should be carried over
-      mergeDetails(details2, details3);
+      mergeDetails(details2, details3, logger);
       const mergedSequence2 = getFragmentSequenceNumbers(details3);
       expect(
         details3,
@@ -651,7 +655,7 @@ fileSequence12.ts`;
       });
 
       // discontinuity sequence numbers (frag.cc) should be carried over
-      mergeDetails(details3, details4);
+      mergeDetails(details3, details4, logger);
       const mergedSequence3 = getFragmentSequenceNumbers(details4);
       expect(
         details4,
@@ -805,7 +809,7 @@ fileSequence8.m4s
         updated: true,
       });
       // discontinuity sequence numbers (frag.cc) should be carried over
-      mergeDetails(details1, details2);
+      mergeDetails(details1, details2, logger);
       const mergedSequence1 = getFragmentSequenceNumbers(details2);
       expect(
         details2,
@@ -818,6 +822,7 @@ fileSequence8.m4s
         startCC: 10, // w/o disco-sequence incremented
         endCC: 11,
       });
+      expect(details2.fragments[0]).to.include({ sn: 102, cc: 11 });
       expect(details2.fragmentHint).to.include({ sn: 109, cc: 12 });
       expect(mergedSequence1).to.equal(
         '102-11,103-11,104-11,105-11,106-11,107-11,108-11',
@@ -832,7 +837,7 @@ fileSequence8.m4s
         updated: true,
       });
       // discontinuity sequence numbers (frag.cc) should be carried over
-      mergeDetails(details1, details3);
+      mergeDetails(details1, details3, logger);
       const mergedSequence2 = getFragmentSequenceNumbers(details3);
       expect(
         details3,
@@ -845,13 +850,171 @@ fileSequence8.m4s
         startCC: 11, // w/ disco-sequence incremented
         endCC: 11,
       });
-      expect(details2.fragmentHint).to.include({ sn: 109, cc: 12 });
+      expect(details3.fragments[0]).to.include({ sn: 102, cc: 11 });
+      expect(details3.fragmentHint).to.include({ sn: 109, cc: 12 });
       expect(mergedSequence2).to.equal(
         '102-11,103-11,104-11,105-11,106-11,107-11,108-11',
       );
       expect(details3.playlistParsingError).to.be.null;
     });
 
+    it('handles delta Playlist updates with discontinuities and parts and no sequence change (missing PDT)', function () {
+      const playlist = `#EXTM3U
+#EXT-X-VERSION:10
+#EXT-X-INDEPENDENT-SEGMENTS
+#EXT-X-TARGETDURATION:3
+#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=3.126000,CAN-SKIP-UNTIL=20.0
+#EXT-X-PART-INF:PART-TARGET=1.000000
+#EXT-X-MEDIA-SEQUENCE:0
+#EXT-X-DISCONTINUITY-SEQUENCE:0
+#EXT-X-DATERANGE:ID="nmss-daterange",START-DATE="2025-05-22T07:59:38.809Z"
+
+#EXT-X-MAP:URI="720p_0_0.m4s"
+#EXTINF:2.500000,
+720p_0.m4v
+#EXTINF:2.500000,
+720p_1.m4v
+#EXTINF:2.500000,
+720p_2.m4v
+#EXTINF:2.184667,
+#EXT-X-DISCONTINUITY
+#EXT-X-MAP:URI="720p_0_1.m4s
+#EXTINF:2.500000,
+720p_3.m4v 
+#EXTINF:2.500000,
+720p_4.m4v
+#EXTINF:2.500000,
+720p_5.m4v
+#EXTINF:2.500000,
+720p_6.m4v
+#EXTINF:2.500000,
+720p_7.m4v
+#EXTINF:2.500000,
+720p_8.m4v
+#EXT-X-PART:DURATION=1.000000,URI="720p_9_0.m4v",INDEPENDENT=YES
+#EXT-X-PART:DURATION=1.000000,URI="720p_9_1.m4v"
+#EXT-X-PART:DURATION=0.500000,URI="720p_9_2.m4v"
+#EXTINF:2.500000,
+720p_9.m4v
+#EXT-X-PART:DURATION=1.000000,URI="720p_10_0.m4v",INDEPENDENT=YES
+#EXT-X-PART:DURATION=1.000000,URI="720p_10_1.m4v"
+#EXT-X-PART:DURATION=0.500000,URI="720p_10_2.m4v"
+#EXTINF:2.500000,
+720p_10.m4v
+#EXT-X-PART:DURATION=1.000000,URI="720p_11_0.m4v",INDEPENDENT=YES
+#EXT-X-PART:DURATION=1.000000,URI="720p_11_1.m4v"
+#EXT-X-PART:DURATION=0.500000,URI="720p_11_2.m4v"
+#EXTINF:2.500000,
+720p_11.m4v
+#EXT-X-PART:DURATION=1.000000,URI="720p_12_0.m4v",INDEPENDENT=YES
+#EXT-X-PART:DURATION=1.000000,URI="720p_12_0.m4v"
+#EXT-X-PRELOAD-HINT:TYPE=PART,URI="720p_12_2.m4v"`;
+      const deltaUpdate = `#EXTM3U
+#EXT-X-VERSION:10
+#EXT-X-INDEPENDENT-SEGMENTS
+#EXT-X-TARGETDURATION:3
+#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=3.126000,CAN-SKIP-UNTIL=20.0
+#EXT-X-PART-INF:PART-TARGET=1.000000
+#EXT-X-MEDIA-SEQUENCE:0
+#EXT-X-DISCONTINUITY-SEQUENCE:0
+#EXT-X-DATERANGE:ID="nmss-daterange",START-DATE="2025-05-22T07:59:38.809Z"
+#EXT-X-SKIP:SKIPPED-SEGMENTS=4
+
+#EXT-X-MAP:URI="720p_0_1.m4s"
+720p_4.m4v
+#EXTINF:2.500000,
+720p_5.m4v
+#EXTINF:2.500000,
+720p_6.m4v
+#EXTINF:2.500000,
+720p_7.m4v
+#EXTINF:2.500000,
+720p_8.m4v
+#EXT-X-PART:DURATION=1.000000,URI="720p_9_0.m4v",INDEPENDENT=YES
+#EXT-X-PART:DURATION=1.000000,URI="720p_9_1.m4v"
+#EXT-X-PART:DURATION=0.500000,URI="720p_9_2.m4v"
+#EXTINF:2.500000,
+720p_9.m4v
+#EXT-X-PART:DURATION=1.000000,URI="720p_10_0.m4v",INDEPENDENT=YES
+#EXT-X-PART:DURATION=1.000000,URI="720p_10_1.m4v"
+#EXT-X-PART:DURATION=0.500000,URI="720p_10_2.m4v"
+#EXTINF:2.500000,
+720p_10.m4v
+#EXT-X-PART:DURATION=1.000000,URI="720p_11_0.m4v",INDEPENDENT=YES
+#EXT-X-PART:DURATION=1.000000,URI="720p_11_1.m4v"
+#EXT-X-PART:DURATION=0.500000,URI="720p_11_2.m4v"
+#EXTINF:2.500000,
+720p_11.m4v
+#EXT-X-PART:DURATION=1.000000,URI="720p_12_0.m4v",INDEPENDENT=YES
+#EXT-X-PART:DURATION=1.000000,URI="720p_12_0.m4v"
+#EXT-X-PART:DURATION=1.000000,URI="720p_12_0.m4v"
+#EXTINF:2.500000,
+720p_12.m4v
+#EXT-X-PRELOAD-HINT:TYPE=PART,URI="720p_13_0.m4`;
+
+      const details1 = parseLevelPlaylist(playlist);
+      const details2 = parseLevelPlaylist(deltaUpdate);
+      expect(details1, 'details1').to.include({
+        live: true,
+        canSkipUntil: 20,
+        totalduration: 32,
+        startSN: 0,
+        endSN: 11,
+        fragmentStart: 0,
+        lastPartSn: 12,
+        lastPartIndex: 1,
+        startCC: 0,
+        endCC: 1,
+      });
+
+      expect(
+        details2,
+        'delta w/o disco seq incremented before merging',
+      ).to.include({
+        live: true,
+        skippedSegments: 4,
+        canSkipUntil: 20,
+        totalduration: 32,
+        startSN: 0,
+        endSN: 12,
+        lastPartSn: 12,
+        lastPartIndex: 2,
+        startCC: 0,
+        endCC: 0, // endCC of delta playlist will not be incremented until merged
+      });
+      expect(
+        details2.fragments,
+        'delta w/o disco seq incremented fragments',
+      ).to.have.lengthOf(13);
+
+      // This delta update does not increment discontinuity-sequence (discontinuity tag would appear before first segment)
+      details2.reloaded(details1);
+      expect(details2, 'delta w/o disco seq incremented reloaded').to.include({
+        misses: 0,
+        advanced: true,
+        updated: true,
+      });
+      // discontinuity sequence numbers (frag.cc) should be carried over
+      mergeDetails(details1, details2, logger);
+      const mergedSequence1 = getFragmentSequenceNumbers(details2);
+      expect(
+        details2,
+        `delta w/o disco seq incremented merged (${mergedSequence1})`,
+      ).to.include({
+        skippedSegments: 4,
+        deltaUpdateFailed: false,
+        startSN: 0,
+        endSN: 12,
+        startCC: 0,
+        endCC: 1, // incremented after merging
+      });
+      expect(details2.fragmentHint).to.include({ sn: 13, cc: 1 });
+      expect(mergedSequence1).to.equal(
+        '0-0,1-0,2-0,3-1,4-1,5-1,6-1,7-1,8-1,9-1,10-1,11-1,12-1',
+      );
+      expect(details2.playlistParsingError).to.be.null;
+    });
+
     it('handles delta Playlist updates with multiple skip tags and removed date ranges', function () {
       const playlist = `#EXTM3U
 #EXT-X-TARGETDURATION:6
@@ -930,15 +1093,15 @@ fileSequence17.ts
 fileSequence18.ts`;
       const details = parseLevelPlaylist(playlist);
       const detailsUpdated = parseLevelPlaylist(playlistUpdate);
-      mergeDetails(details, detailsUpdated);
+      mergeDetails(details, detailsUpdated, logger);
       expect(details.hasProgramDateTime, 'details.hasProgramDateTime').to.be
         .true;
       expect(
         Object.keys(details.dateRanges),
         'first playlist daterange ids',
       ).to.have.deep.equal(['d0', 'd1', 'd2', 'd3', 'd4']);
-      expect(details.dateRanges.d2.startTime).to.equal(2.94);
-      expect(details.dateRanges.d3.startTime).to.equal(3.94);
+      expect(details.dateRanges.d2!.startTime).to.equal(2.94);
+      expect(details.dateRanges.d3!.startTime).to.equal(3.94);
       expect(
         detailsUpdated.hasProgramDateTime,
         'detailsUpdated.hasProgramDateTime',
@@ -957,8 +1120,8 @@ fileSequence18.ts`;
         .which.equals(detailsUpdated.fragments[0].ref)
         .which.has.property('sn')
         .which.equals(3);
-      expect(detailsUpdated.dateRanges.d2.startTime).to.equal(2.94);
-      expect(detailsUpdated.dateRanges.d3.startTime).to.equal(3.94);
+      expect(detailsUpdated.dateRanges.d2!.startTime).to.equal(2.94);
+      expect(detailsUpdated.dateRanges.d3!.startTime).to.equal(3.94);
       // Multiple #EXT-X-SKIP tags are not allowed
       expect(detailsUpdated.playlistParsingError).to.include({
         message: `#EXT-X-SKIP must not appear more than once (#EXT-X-SKIP:SKIPPED-SEGMENTS=2,RECENTLY-REMOVED-DATERANGES="d0")`,
@@ -977,7 +1140,7 @@ fileSequence6.ts`;
       const details = parseLevelPlaylist(playlist);
       addSliding(details, 10);
       expect(details.fragmentStart).to.equal(10);
-      mergeDetails(details, details);
+      mergeDetails(details, details, logger);
       expect(details.fragmentStart).to.equal(10);
     });
 
@@ -1035,7 +1198,7 @@ video_32.m4s`;
         endCC: 3,
       });
 
-      mergeDetails(oldPlaylist, newPlaylist);
+      mergeDetails(oldPlaylist, newPlaylist, logger);
 
       expect(newPlaylist.playlistParsingError).to.be.null;
       expect(newPlaylist).to.include({
@@ -1113,7 +1276,7 @@ getMP4MediaFragment.mp4?FragmentNumber=91343852333398821516816632930230891347979
         endCC: 5,
       });
 
-      mergeDetails(oldPlaylist, newPlaylist);
+      mergeDetails(oldPlaylist, newPlaylist, logger);
 
       expect(newPlaylist.playlistParsingError).to.be.null;
       expect(newPlaylist).to.include({
@@ -1355,7 +1518,7 @@ audio_5441.m4s`;
         level: 0,
         id: 0,
         stats: new LoadStats(),
-        networkDetails: {},
+        networkDetails: new Response('ok'),
         deliveryDirectives: null,
       });
     }
@@ -1367,7 +1530,7 @@ audio_5441.m4s`;
         id: 0,
         groupId: '',
         stats: new LoadStats(),
-        networkDetails: {},
+        networkDetails: new Response('ok'),
         deliveryDirectives: null,
       });
     }
@@ -1383,8 +1546,8 @@ audio_5441.m4s`;
       expect(audioDetails1.totalduration).to.equal(16.019);
 
       // Seconds main and audio playlist responses
-      mergeDetails(mainDetails1, mainDetails2);
-      mergeDetails(audioDetails1, audioDetails2);
+      mergeDetails(mainDetails1, mainDetails2, logger);
+      mergeDetails(audioDetails1, audioDetails2, logger);
       expect(audioDetails2.alignedSliding).to.be.false;
       expect(mainDetails2.fragmentStart).to.equal(20.0825);
       expect(audioDetails2.fragmentStart).to.equal(16.0325);
@@ -1416,8 +1579,8 @@ audio_5441.m4s`;
       expect(audioDetails1.totalduration).to.equal(16.019);
 
       // Seconds main and audio playlist responses
-      mergeDetails(mainDetails1, mainDetails2);
-      mergeDetails(audioDetails1, audioDetails2);
+      mergeDetails(mainDetails1, mainDetails2, logger);
+      mergeDetails(audioDetails1, audioDetails2, logger);
       expect(audioDetails2.alignedSliding).to.be.false;
       expect(mainDetails2.fragmentStart).to.equal(20.0825);
       expect(audioDetails2.fragmentStart).to.equal(16.0325);
@@ -1467,7 +1630,7 @@ video_5431.m4s
 video_5432.m4s`;
       const details1 = parseLevelPlaylist(playlist1);
       const details2 = parseLevelPlaylist(playlist2);
-      mergeDetails(details1, details2);
+      mergeDetails(details1, details2, logger);
       expectPlaylistParsingError(
         details2,
         'discontinuity sequence mismatch (31!=32)',
@@ -1506,13 +1669,382 @@ video_5431.m4s
 video_5432.m4s`;
       const details1 = parseLevelPlaylist(playlist1);
       const details2 = parseLevelPlaylist(playlist2);
-      mergeDetails(details1, details2);
+      mergeDetails(details1, details2, logger);
       expectPlaylistParsingError(
         details2,
         'media sequence mismatch 5429: http://example.com/video_5430.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
+#EXT-X-MEDIA-SEQUENCE:1
+#EXT-X-TARGETDURATION:6
+#EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=9,CAN-SKIP-DATERANGES=YES
+#EXT-X-DATERANGE:ID="D0",START-DATE="2025-08-06T15:59:59Z",DURATION=1
+#EXT-X-DATERANGE:ID="D1",START-DATE="2025-08-06T16:00:00Z",DURATION=12,SCTE35-CMD=0x00000000
+#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
+#EXT-X-DATERANGE:ID="D2",START-DATE="2025-08-06T16:00:15Z",PLANNED-DURATION=12,SCTE35-OUT=0x00000000
+#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: 3,
+    });
+    expect(details1.dateRanges).to.have.keys(['D0', 'D1', 'D2']);
+    expect(details1.dateRanges.D0).to.include({
+      startTime: -1,
+      tagOrder: 0,
+    });
+    expect(details1.dateRanges.D0?.tagAnchor).to.include({
+      sn: 1,
+    });
+    expect(details1.dateRanges.D1).to.include({
+      startTime: 0,
+      tagOrder: 1,
+    });
+    expect(details1.dateRanges.D1?.tagAnchor).to.include({
+      sn: 1, // expected to change in details2 after `mergeDetails` when sn: 1 is removed
+    });
+    expect(details1.dateRanges.D2).to.include({
+      startTime: 15,
+      tagOrder: 2,
+    });
+    expect(details1.dateRanges.D2?.tagAnchor).to.include({
+      sn: 6,
+    });
+
+    // Merged delta playlist with EXT-X-PROGRAM-DATE-TIME removed and DateRange tagAnchors updated
+    mergeDetails(details1, details2, logger);
+
+    expect(details2).to.include({
+      startSN: 5,
+      endSN: 10,
+      totalduration: 24,
+      dateRangeTagCount: 1,
+    });
+    expect(details2.dateRanges).to.have.keys(['D1', 'D2', 'D3']);
+    expect(details2.dateRanges.D1).to.include({
+      startTime: 0,
+      tagOrder: 1,
+    });
+    expect(details2.dateRanges.D1?.tagAnchor).to.include({
+      sn: 6,
+    });
+    expect(details2.dateRanges.D2).to.include({
+      startTime: 15,
+      tagOrder: 2,
+    });
+    expect(details2.dateRanges.D2?.tagAnchor).to.include({
+      sn: 6,
+    });
+    expect(details2.dateRanges.D3).to.include({
+      startTime: 59.1,
+      tagOrder: 2, // Note: tagOrder: 3 is expected (following previous/skipped date-ranges), but not critical.
+    });
+    expect(details2.dateRanges.D3?.tagAnchor).to.include({
+      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(
diff --git a/tests/unit/controller/stream-controller.ts b/tests/unit/controller/stream-controller.ts
index e450395f30e..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({
@@ -210,7 +210,7 @@ describe('StreamController', function () {
     fragPrevious.programDateTime = 1505502671523;
     fragPrevious.duration = 5.0;
     fragPrevious.level = 1;
-    fragPrevious.start = 10.0;
+    fragPrevious.setStart(10.0);
     fragPrevious.sn = 2; // Fragment with PDT 1505502671523 in level 1 does not have the same sn as in level 2 where cc is 1
     fragPrevious.cc = 0;
 
@@ -274,7 +274,7 @@ describe('StreamController', function () {
         fragPrevious.programDateTime = 1505502681523;
         fragPrevious.duration = 5.0;
         fragPrevious.level = 1;
-        fragPrevious.start = 15.0;
+        fragPrevious.setStart(15.0);
         fragPrevious.sn = 3;
         streamController['fragPrevious'] = fragPrevious;
 
@@ -285,10 +285,8 @@ describe('StreamController', function () {
 
       describe('with program-date-time', function () {
         it('does PDT search, choosing fragment after level loaded', function () {
-          const foundFragment = streamController['getInitialLiveFragment'](
-            levelDetails,
-            mockFragments,
-          );
+          const foundFragment =
+            streamController['getInitialLiveFragment'](levelDetails);
           expect(foundFragment).to.equal(
             mockFragments[4],
             `Expected sn 4, found sn segment ${
@@ -323,10 +321,8 @@ describe('StreamController', function () {
 
         it('finds the next fragment to load based on the last fragment buffered', function () {
           fragPrevious.sn = 0;
-          let foundFragment = streamController['getInitialLiveFragment'](
-            levelDetails,
-            fragmentsWithoutPdt,
-          );
+          let foundFragment =
+            streamController['getInitialLiveFragment'](levelDetails);
           expect(foundFragment).to.equal(
             fragmentsWithoutPdt[1],
             `Expected sn 1, found sn segment ${
@@ -335,10 +331,8 @@ describe('StreamController', function () {
           );
 
           fragPrevious.sn = 3;
-          foundFragment = streamController['getInitialLiveFragment'](
-            levelDetails,
-            fragmentsWithoutPdt,
-          );
+          foundFragment =
+            streamController['getInitialLiveFragment'](levelDetails);
           expect(foundFragment).to.equal(
             fragmentsWithoutPdt[4],
             `Expected sn 4, found sn segment ${
@@ -350,10 +344,8 @@ describe('StreamController', function () {
         it('finds the first fragment to load when starting or re-syncing with a live stream', function () {
           streamController['fragPrevious'] = null;
 
-          const foundFragment = streamController['getInitialLiveFragment'](
-            levelDetails,
-            fragmentsWithoutPdt,
-          );
+          const foundFragment =
+            streamController['getInitialLiveFragment'](levelDetails);
           expect(foundFragment).to.equal(
             fragmentsWithoutPdt[2],
             `Expected sn 2, found sn segment ${
@@ -364,10 +356,8 @@ describe('StreamController', function () {
 
         it('finds the fragment with the same cc when there is no sn match', function () {
           fragPrevious.cc = 0;
-          const foundFragment = streamController['getInitialLiveFragment'](
-            levelDetails,
-            fragmentsWithoutPdt,
-          );
+          const foundFragment =
+            streamController['getInitialLiveFragment'](levelDetails);
           expect(foundFragment).to.equal(
             fragmentsWithoutPdt[0],
             `Expected sn 0, found sn segment ${
@@ -378,10 +368,8 @@ describe('StreamController', function () {
 
         it('returns null when there is no cc match with the previous segment', function () {
           fragPrevious.cc = 2;
-          const foundFragment = streamController['getInitialLiveFragment'](
-            levelDetails,
-            fragmentsWithoutPdt,
-          );
+          const foundFragment =
+            streamController['getInitialLiveFragment'](levelDetails);
           expect(foundFragment).to.equal(null);
         });
       });
@@ -487,7 +475,7 @@ describe('StreamController', function () {
       ) as MediaFragment;
       firstFrag.duration = 5.0;
       firstFrag.level = 1;
-      firstFrag.start = 0;
+      firstFrag.setStart(0);
       firstFrag.sn = 1;
       firstFrag.cc = 0;
       firstFrag.elementaryStreams.video = {
@@ -509,6 +497,42 @@ describe('StreamController', function () {
       expect(seekStub).to.have.been.calledOnce;
     });
 
+    it('should seek to start pos when media is buffered and seek to segment boundary', function () {
+      streamController['config'].startOnSegmentBoundary = true;
+      streamController['startPosition'] = 7;
+      streamController['media']?.buffered;
+      streamController['media'] = {
+        buffered: {
+          start() {
+            return 5;
+          },
+          length: 1,
+        },
+        currentTime: 0,
+      } as any as HTMLMediaElement;
+
+      streamController['seekToStartPos']();
+      expect(streamController['media']!.currentTime).to.equal(5);
+    });
+
+    it('should seek to start pos when media is buffered and not seek to segment boundary', function () {
+      streamController['config'].startOnSegmentBoundary = false;
+      streamController['startPosition'] = 7;
+      streamController['media']?.buffered;
+      streamController['media'] = {
+        buffered: {
+          start() {
+            return 5;
+          },
+          length: 1,
+        },
+        currentTime: 0,
+      } as any as HTMLMediaElement;
+
+      streamController['seekToStartPos']();
+      expect(streamController['media']!.currentTime).to.equal(7);
+    });
+
     describe('seekToStartPos', function () {
       it('should seek to startPosition when startPosition is not buffered & the media is not seeking', function () {
         streamController['startPosition'] = 5;
diff --git a/tests/unit/controller/subtitle-stream-controller.js b/tests/unit/controller/subtitle-stream-controller.ts
similarity index 95%
rename from tests/unit/controller/subtitle-stream-controller.js
rename to tests/unit/controller/subtitle-stream-controller.ts
index 406a78f973e..b70ae6371c8 100644
--- a/tests/unit/controller/subtitle-stream-controller.js
+++ b/tests/unit/controller/subtitle-stream-controller.ts
@@ -1,14 +1,18 @@
+import chai from 'chai';
 import sinon from 'sinon';
-
-import Hls from '../../../src/hls';
-import { Events } from '../../../src/events';
+import sinonChai from 'sinon-chai';
+import { State } from '../../../src/controller/base-stream-controller';
 import { FragmentTracker } from '../../../src/controller/fragment-tracker';
+import { SubtitleStreamController } from '../../../src/controller/subtitle-stream-controller';
+import { Events } from '../../../src/events';
+import Hls from '../../../src/hls';
 import { Fragment } from '../../../src/loader/fragment';
+import KeyLoader from '../../../src/loader/key-loader';
 import { PlaylistLevelType } from '../../../src/types/loader';
 import { AttrList } from '../../../src/utils/attr-list';
-import KeyLoader from '../../../src/loader/key-loader';
-import { State } from '../../../src/controller/base-stream-controller';
-import { SubtitleStreamController } from '../../../src/controller/subtitle-stream-controller';
+
+chai.use(sinonChai);
+const expect = chai.expect;
 
 const mediaMock = {
   currentTime: 0,
@@ -20,11 +24,11 @@ const tracksMock = [
   {
     id: 0,
     details: { url: '', fragments: [] },
-    attrs: new AttrList(),
+    attrs: new AttrList({}),
   },
   {
     id: 1,
-    attrs: new AttrList(),
+    attrs: new AttrList({}),
   },
 ];
 
@@ -38,7 +42,7 @@ describe('SubtitleStreamController', function () {
     hls = new Hls({});
     mediaMock.currentTime = 0;
     fragmentTracker = new FragmentTracker(hls);
-    keyLoader = new KeyLoader({});
+    keyLoader = new KeyLoader(hls.config, hls.logger);
 
     subtitleStreamController = new SubtitleStreamController(
       hls,
diff --git a/tests/unit/controller/subtitle-track-controller.ts b/tests/unit/controller/subtitle-track-controller.ts
index 3d08399f617..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 = {
@@ -494,7 +611,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 +644,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 +668,7 @@ describe('SubtitleTrackController', function () {
         groupId: 'default-text-group',
         details,
         stats: new LoadStats(),
-        networkDetails: {},
+        networkDetails: new Response('ok'),
         deliveryDirectives: null,
         track: {} as any,
       });
@@ -568,7 +685,7 @@ describe('SubtitleTrackController', function () {
         groupId: 'default-text-group',
         details,
         stats: new LoadStats(),
-        networkDetails: {},
+        networkDetails: new Response('ok'),
         deliveryDirectives: null,
         track: {} as any,
       });
@@ -586,7 +703,7 @@ describe('SubtitleTrackController', function () {
         groupId: 'default-text-group',
         details,
         stats: new LoadStats(),
-        networkDetails: {},
+        networkDetails: new Response('ok'),
         deliveryDirectives: null,
         track: {} as any,
       });
diff --git a/tests/unit/controller/timeline-controller.js b/tests/unit/controller/timeline-controller.js
deleted file mode 100644
index b6fcda03113..00000000000
--- a/tests/unit/controller/timeline-controller.js
+++ /dev/null
@@ -1,181 +0,0 @@
-import { TimelineController } from '../../../src/controller/timeline-controller';
-import Hls from '../../../src/hls';
-import { Events } from '../../../src/events';
-
-describe('TimelineController', function () {
-  let timelineController;
-  let hls;
-
-  beforeEach(function () {
-    hls = new Hls();
-    hls.config.enableWebVTT = true;
-    hls.config.renderNatively = true;
-    timelineController = new TimelineController(hls);
-    timelineController.media = document.createElement('video');
-  });
-
-  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');
-    });
-  });
-});
diff --git a/tests/unit/controller/timeline-controller.ts b/tests/unit/controller/timeline-controller.ts
new file mode 100644
index 00000000000..8b44f3889a0
--- /dev/null
+++ b/tests/unit/controller/timeline-controller.ts
@@ -0,0 +1,34 @@
+import chai from 'chai';
+import sinonChai from 'sinon-chai';
+import { TimelineController } from '../../../src/controller/timeline-controller';
+import { Events } from '../../../src/events';
+import Hls from '../../../src/hls';
+
+chai.use(sinonChai);
+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.renderTextTracksNatively = true;
+    timelineController = new TimelineController(hls);
+    timelineController.config.renderTextTracksNatively = true;
+    timelineController.media = videoElement;
+  });
+
+  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/hls.ts b/tests/unit/hls.ts
index ae64e5af31b..cf9779e5091 100644
--- a/tests/unit/hls.ts
+++ b/tests/unit/hls.ts
@@ -57,7 +57,7 @@ describe('Hls', function () {
       const hls = new Hls({ capLevelOnFPSDrop: true });
       expect(hls.media).to.equal(null);
       const media = document.createElement('video');
-      expect(media || null).to.not.equal(null);
+      expect(media).to.be.an('HTMLVideoElement');
       hls.attachMedia(media);
       expect(hls.media).to.equal(media);
       detachTest(hls, media, 6);
@@ -72,7 +72,7 @@ describe('Hls', function () {
       });
       expect(hls.media).to.equal(null);
       const media = document.createElement('video');
-      expect(media || null).to.not.equal(null);
+      expect(media).to.be.an('HTMLVideoElement');
       hls.attachMedia(media);
       expect(hls.media).to.equal(media);
       hls.trigger(Events.MEDIA_ATTACHED, { media });
diff --git a/tests/unit/loader/m3u8-parser.ts b/tests/unit/loader/m3u8-parser.ts
index e41bfc7d0ee..1549f308ce7 100644
--- a/tests/unit/loader/m3u8-parser.ts
+++ b/tests/unit/loader/m3u8-parser.ts
@@ -1996,10 +1996,10 @@ segment.m4s
       );
       expect(details.playlistParsingError).to.be.null;
       expect(details.dateRangeTagCount).to.equal(4);
-      expect(details.dateRanges.pre.isInterstitial).to.be.true;
-      expect(details.dateRanges.mid1.isInterstitial).to.be.true;
-      expect(details.dateRanges.mid2.isInterstitial).to.be.true;
-      expect(details.dateRanges.post.isInterstitial).to.be.true;
+      expect(details.dateRanges.pre!.isInterstitial).to.be.true;
+      expect(details.dateRanges.mid1!.isInterstitial).to.be.true;
+      expect(details.dateRanges.mid2!.isInterstitial).to.be.true;
+      expect(details.dateRanges.post!.isInterstitial).to.be.true;
       expect(details.dateRanges).to.have.property('pre').which.deep.includes({
         tagOrder: 0,
       });
@@ -2012,15 +2012,15 @@ segment.m4s
       expect(details.dateRanges).to.have.property('post').which.deep.includes({
         tagOrder: 3,
       });
-      expect(details.dateRanges.pre.cue.pre).to.be.true;
-      expect(details.dateRanges.mid1.cue.once).to.be.true;
-      expect(details.dateRanges.post.cue.post).to.be.true;
-      expect(details.dateRanges.post.cue.once).to.be.true;
+      expect(details.dateRanges.pre!.cue.pre).to.be.true;
+      expect(details.dateRanges.mid1!.cue.once).to.be.true;
+      expect(details.dateRanges.post!.cue.post).to.be.true;
+      expect(details.dateRanges.post!.cue.once).to.be.true;
       // DateRange start times are mapped to the primary timeline and not changed by CUE Interstitial DURATION
-      expect(details.dateRanges.pre.startTime).to.equal(-7200);
-      expect(details.dateRanges.mid1.startTime).to.equal(10);
-      expect(details.dateRanges.mid2.startTime).to.equal(25);
-      expect(details.dateRanges.post.startTime).to.equal(0);
+      expect(details.dateRanges.pre!.startTime).to.equal(-7200);
+      expect(details.dateRanges.mid1!.startTime).to.equal(10);
+      expect(details.dateRanges.mid2!.startTime).to.equal(25);
+      expect(details.dateRanges.post!.startTime).to.equal(0);
     });
 
     it('ensures DateRanges are mapped to a segment whose TimeRange covers the start date of the DATERANGE tag', function () {
@@ -2047,14 +2047,14 @@ segment.m4s
         null,
       );
       expect(details.playlistParsingError).to.be.null;
-      expect(details.dateRanges.sooner.isValid).to.equal(
+      expect(details.dateRanges.sooner!.isValid).to.equal(
         true,
         'is valid DateRange',
       );
-      expect(details.dateRanges.sooner.tagAnchor)
+      expect(details.dateRanges.sooner!.tagAnchor)
         .to.have.property('sn')
         .which.equals(2);
-      expect(details.dateRanges.sooner.startTime).to.equal(10);
+      expect(details.dateRanges.sooner!.startTime).to.equal(10);
     });
 
     it('ensures DateRanges that start before the program are mapped to the first PDT tag', function () {
@@ -2081,14 +2081,14 @@ segment.m4s
         null,
       );
       expect(details.playlistParsingError).to.be.null;
-      expect(details.dateRanges.earlier.isValid).to.equal(
+      expect(details.dateRanges.earlier!.isValid).to.equal(
         true,
         'is valid DateRange',
       );
-      expect(details.dateRanges.earlier.tagAnchor)
+      expect(details.dateRanges.earlier!.tagAnchor)
         .to.have.property('sn')
         .which.equals(1);
-      expect(details.dateRanges.earlier.startTime).to.equal(-10);
+      expect(details.dateRanges.earlier!.startTime).to.equal(-10);
     });
 
     it('adds PROGRAM-DATE-TIME and DATERANGE tag text to fragment[].tagList for backwards compatibility', function () {
@@ -2758,10 +2758,6 @@ a{$mvpVariable}.mp4
         TYPE: 'PART',
         URI: 'part-5.1.mp4',
       });
-      if (details.partList === null) {
-        expect(details.partList, 'partList').to.not.equal(null);
-        return;
-      }
       if (!details.renditionReports) {
         expect(details.renditionReports, 'renditionReports').to.not.be
           .undefined;
@@ -2913,7 +2909,7 @@ a{$bar}.mp4
         details,
         'Missing preceding EXT-X-DEFINE tag for Variable Reference: "bar"',
       );
-      expect(details.fragments?.[0].relurl).to.equal('a{$bar}.mp4');
+      expect(details.fragments[0].relurl).to.equal('a{$bar}.mp4');
     });
 
     it('fails to parse Media Playlist when variable reference precedes definition', function () {
diff --git a/tests/unit/utils/codecs.ts b/tests/unit/utils/codecs.ts
index 15e18e8dbe5..f55301919fb 100644
--- a/tests/unit/utils/codecs.ts
+++ b/tests/unit/utils/codecs.ts
@@ -25,6 +25,15 @@ describe('codecs', function () {
         'avc1.64001E,avc1.64001f',
       );
     });
+
+    it('only converts avc1 codec strings in mixed video codecs', function () {
+      expect(convertAVC1ToAVCOTI('avc1.66.30,dvh1.05.07')).to.equal(
+        'avc1.42001e,dvh1.05.07',
+      );
+      expect(convertAVC1ToAVCOTI('dvh1.05.07,avc1.77.30')).to.equal(
+        'dvh1.05.07,avc1.4d001e',
+      );
+    });
   });
 
   describe('fillInMissingAV01Params', function () {
diff --git a/tests/unit/utils/mediacapabilities-helper.ts b/tests/unit/utils/mediacapabilities-helper.ts
index dfd1d6c5cdc..c7518b24201 100644
--- a/tests/unit/utils/mediacapabilities-helper.ts
+++ b/tests/unit/utils/mediacapabilities-helper.ts
@@ -1,10 +1,16 @@
 import { expect } from 'chai';
 import { Level } from '../../../src/types/level';
 import { AttrList } from '../../../src/utils/attr-list';
-import {
-  getMediaDecodingInfoPromise,
-  SUPPORTED_INFO_CACHE,
-} from '../../../src/utils/mediacapabilities-helper';
+import { getMediaDecodingInfoPromise } from '../../../src/utils/mediacapabilities-helper';
+import type {
+  MediaAttributes,
+  MediaPlaylist,
+} from '../../../src/types/media-playlist';
+
+declare const navigator: {
+  prototype: Navigator;
+  mediaCapabilities?: MediaCapabilities;
+};
 
 describe('getMediaDecodingInfoPromise', function () {
   it('adds queries to cache', function () {
@@ -29,18 +35,112 @@ describe('getMediaDecodingInfoPromise', function () {
       hasAutoSelectAudio: false,
       groups: {},
     };
+    const SUPPORTED_INFO_CACHE = {};
     return getMediaDecodingInfoPromise(
       level,
       emptyAudioTracksByGroup,
       navigator.mediaCapabilities,
+      SUPPORTED_INFO_CACHE,
     ).then((mediaDecodingInfo) => {
       const cachedKeys = Object.keys(SUPPORTED_INFO_CACHE);
       expect(cachedKeys.length).to.be.gt(0);
       expect(cachedKeys, JSON.stringify(SUPPORTED_INFO_CACHE)).to.include(
-        'r1080x1920f30sd_hvc1.2.20000000.L93.B0_50',
+        'hvc1.2.20000000.L93.B0_r1080x1920f30sd_50',
       );
       expect(mediaDecodingInfo).to.have.property('supported');
       expect(mediaDecodingInfo).to.have.property('decodingInfoResults');
+      expect(mediaDecodingInfo)
+        .to.have.property('configurations')
+        .which.has.a.lengthOf(1);
+      expect(mediaDecodingInfo.configurations[0]).deep.equals({
+        type: 'media-source',
+        video: {
+          contentType: 'video/mp4;codecs=hvc1.2.20000000.L93.B0',
+          width: 1920,
+          height: 1080,
+          bitrate: 5000000,
+          framerate: 30,
+        },
+      });
+    });
+  });
+
+  it('combines audio and video into a single query', function () {
+    if (!navigator.mediaCapabilities) {
+      return;
+    }
+    const videoCodecs = ['hvc1.2.6.L93.B0'];
+    const audioCodecs = ['ec-3'];
+    const levelAttr = new AttrList(
+      `BANDWIDTH=5000000,CODECS="${videoCodecs.join(',')},${audioCodecs.join(',')}",FRAME-RATE=30,RESOLUTION=1920x1080,VIDEO-RANGE=PQ,AUDIO="ec3"`,
+    );
+    const level = new Level({
+      attrs: levelAttr,
+      bitrate: levelAttr.decimalInteger('BANDWIDTH'),
+      videoCodec: videoCodecs.join(','),
+      audioCodec: audioCodecs.join(','),
+      ...levelAttr.decimalResolution('RESOLUTION'),
+      name: '',
+      url: '',
+    });
+    const audioAttr = new AttrList(
+      `TYPE=AUDIO,LANGUAGE="en",NAME="English",AUTOSELECT=YES,DEFAULT=YES,CHANNELS="16/JOC",GROUP-ID="ec3",URI="a.m3u8"`,
+    ) as MediaAttributes;
+    const audioOption: MediaPlaylist = {
+      attrs: audioAttr,
+      groupId: audioAttr['GROUP-ID'] || '',
+      name: audioAttr.NAME || audioAttr.LANGUAGE || '',
+      default: audioAttr.bool('DEFAULT'),
+      autoselect: audioAttr.bool('AUTOSELECT'),
+      forced: audioAttr.bool('FORCED'),
+      channels: audioAttr.CHANNELS,
+      url: audioAttr.URI || '',
+      type: 'AUDIO',
+      id: 0,
+      bitrate: 0,
+      audioCodec: 'ec-3',
+    };
+
+    expect(level.codecSet).equals('hvc1,ec-3');
+
+    const audioTracksByGroup = {
+      hasDefaultAudio: true,
+      hasAutoSelectAudio: true,
+      groups: {
+        ec3: {
+          channels: { 16: 1 },
+          hasAutoSelect: true,
+          hasDefault: true,
+          tracks: [audioOption],
+        },
+      },
+    };
+    return getMediaDecodingInfoPromise(
+      level,
+      audioTracksByGroup,
+      navigator.mediaCapabilities,
+    ).then((mediaDecodingInfo) => {
+      expect(mediaDecodingInfo).to.have.property('supported');
+      expect(mediaDecodingInfo).to.have.property('decodingInfoResults');
+      expect(mediaDecodingInfo)
+        .to.have.property('configurations')
+        .which.has.a.lengthOf(1);
+      expect(mediaDecodingInfo.configurations[0]).deep.equals({
+        type: 'media-source',
+        video: {
+          contentType: 'video/mp4;codecs=hvc1.2.6.L93.B0',
+          width: 1920,
+          height: 1080,
+          bitrate: 4232000,
+          framerate: 30,
+          transferFunction: 'pq',
+        },
+        audio: {
+          contentType: 'audio/mp4;codecs=ec-3',
+          channels: '16',
+          bitrate: 768000,
+        },
+      });
     });
   });
 });
diff --git a/tests/unit/utils/mock-level.ts b/tests/unit/utils/mock-level.ts
new file mode 100644
index 00000000000..2b2fd421ef7
--- /dev/null
+++ b/tests/unit/utils/mock-level.ts
@@ -0,0 +1,15 @@
+import { AttrList } from '../../../src/utils/attr-list';
+import type { LevelParsed } from '../../../src/types/level';
+
+export function parsedLevel(
+  options: Partial & { bitrate: number },
+): LevelParsed {
+  const { bitrate, height } = options;
+  const level: LevelParsed = {
+    attrs: new AttrList({ BANDWIDTH: bitrate }),
+    bitrate,
+    name: `${height}-${bitrate}`,
+    url: `${bitrate}.m3u8`,
+  };
+  return Object.assign(level, options);
+}
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);
     });
   });
diff --git a/tools/mp4-inspect.js b/tools/mp4-inspect.js
index 4dad9167e6f..9672cc8654f 100644
--- a/tools/mp4-inspect.js
+++ b/tools/mp4-inspect.js
@@ -663,7 +663,8 @@ var mp4toJSON = function (data) {
 
   while (i < data.byteLength) {
     // parse box data
-    (size = view.getUint32(i)), (type = parseType(data.subarray(i + 4, i + 8)));
+    ((size = view.getUint32(i)),
+      (type = parseType(data.subarray(i + 4, i + 8))));
     end = size > 1 ? i + size : data.byteLength;
 
     // parse type-specific data