diff --git a/.circleci/config.yml b/.circleci/config.yml index 1da5feec0e..6f74862d56 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -55,6 +55,7 @@ commands: command: | npm run build npm run test + process_test_results: # CircleCI can not handle NaN values so we replace them with 0 steps: - run: @@ -97,13 +98,32 @@ commands: name: Run functional tests (<> / <>) <> command: node test/functional/runTests.cjs --selenium=remote --reporters=junit --app=remote --browsers=<> --protocol=<> --groupname="<>" + build_samples: + # parameters: + # samples: + # default: "network-interceptor" + # type: string + steps: + - run: + name: Install dependencies and build samples + command: | + for sample in "${SAMPLES}"; do + echo "Build sample $sample" + cd samples/$sample + npm install + npm run build + done + jobs: build-and-unit-test: + environment: + SAMPLES: "network-interceptor" executor: dashjs-executor steps: - checkout - dependencies_setup - build_unit_test_steps + - build_samples merge-build-and-unit-test: executor: dashjs-executor diff --git a/index.d.ts b/index.d.ts index cdf3e65d66..d10a4fc0e5 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,3 +1,5 @@ +import * as CommonMediaLibrary from '@svta/common-media-library'; + export = dashjs; export as namespace dashjs; @@ -1483,6 +1485,14 @@ declare namespace dashjs { setProtectionData(value: ProtectionDataSet): void; + addRequestInterceptor(interceptor: CommonMediaLibrary.RequestInterceptor): void; + + removeRequestInterceptor(interceptor: CommonMediaLibrary.RequestInterceptor): void; + + addResponseInterceptor(interceptor: CommonMediaLibrary.ResponseInterceptor): void; + + removeResponseInterceptor(interceptor: CommonMediaLibrary.ResponseInterceptor): void; + registerLicenseRequestFilter(filter: RequestFilter): void; registerLicenseResponseFilter(filter: ResponseFilter): void; @@ -3962,12 +3972,6 @@ declare namespace dashjs { areEqual(obj1: object, obj2: object): boolean; } - export interface RequestModifier { - modifyRequestURL(url: string): string; - - modifyRequestHeader(request: Request): Request; - } - export interface SupervisorTools { checkParameterType(parameter: any, type: string): void; diff --git a/package-lock.json b/package-lock.json index b9c7b960ac..dc48093a48 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "5.0.0", "license": "BSD-3-Clause", "dependencies": { - "@svta/common-media-library": "^0.4.5", + "@svta/common-media-library": "^0.5.0", "bcp-47-match": "^2.0.3", "bcp-47-normalize": "^2.3.0", "codem-isoboxer": "0.3.9", @@ -2018,9 +2018,9 @@ "dev": true }, "node_modules/@svta/common-media-library": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/@svta/common-media-library/-/common-media-library-0.4.5.tgz", - "integrity": "sha512-H7fcIcfuWJfmGdaZDgIHUQwOniPnz3jZKc4vujfzx6c9pDaarm7T/TCuqHWJYVPLicQ2PH1W8a0RjpNEXON97Q==" + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@svta/common-media-library/-/common-media-library-0.5.0.tgz", + "integrity": "sha512-up4bpVbbeYxkbEt2zbjmEBO1o/VpvdhHg4181JtnCvJfn27yjl+RmrXNVYPMeK6kIqK5kceEh8E3vIRhUmHtpg==" }, "node_modules/@theintern/common": { "version": "0.3.0", diff --git a/package.json b/package.json index 387c0936fd..3b244c0496 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "yargs": "16.0.3" }, "dependencies": { - "@svta/common-media-library": "^0.4.5", + "@svta/common-media-library": "^0.5.0", "bcp-47-match": "^2.0.3", "bcp-47-normalize": "^2.3.0", "codem-isoboxer": "0.3.9", diff --git a/samples/advanced/extend.html b/samples/advanced/extend.html deleted file mode 100644 index e8e86e93e5..0000000000 --- a/samples/advanced/extend.html +++ /dev/null @@ -1,104 +0,0 @@ - - - - - Extending dash.js - - - - - - - - - - - - - -
-
-
- -
-
-
-
-

Extending dash.js

-

This sample shows how to use dash.js extend mechanism to add custom HTTP headers and modify URL's of the requests done by the player.

-
-
-
- -
-
-
-
-
-
-
-
- © DASH-IF -
-
-
- - - - - - diff --git a/samples/advanced/modify-segment-response.html b/samples/advanced/modify-segment-response.html deleted file mode 100644 index 8a95b74bfd..0000000000 --- a/samples/advanced/modify-segment-response.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - - Segment response modification - dash.js - - - - - - - - - - - - - -
-
-
- -
-
-
-
-

Extending dash.js

-

This sample shows how to use dash.js to modify the segment before the data is appended - to the Sourcebuffer.

-
-
-
- -
-
-
-
-
-
-
-
- © DASH-IF -
-
-
- - - - - - diff --git a/samples/network-interceptor/index.html b/samples/network-interceptor/index.html new file mode 100644 index 0000000000..f14b952319 --- /dev/null +++ b/samples/network-interceptor/index.html @@ -0,0 +1,130 @@ + + + + + + Dash.js Network Interceptor sample + + + + + + + + + + + + + + + +
+
+
+ +
+
+
+
+

Network interceptors

+

This sample shows how to apply network request and response interceptor based on CommonMediaLibray API.

+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ © DASH-IF +
+
+
+ + + + + + + \ No newline at end of file diff --git a/samples/network-interceptor/package-lock.json b/samples/network-interceptor/package-lock.json new file mode 100644 index 0000000000..74d7d3594f --- /dev/null +++ b/samples/network-interceptor/package-lock.json @@ -0,0 +1,2739 @@ +{ + "name": "network-interceptor", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "devDependencies": { + "@svta/common-media-library": "^0.5.0", + "@types/node": "^18.14.0", + "source-map-loader": "^4.0.1", + "ts-loader": "^9.4.2", + "typescript": "^4.9.3", + "webpack": "^5.82.1", + "webpack-cli": "^5.1.1" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", + "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@svta/common-media-library": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@svta/common-media-library/-/common-media-library-0.5.0.tgz", + "integrity": "sha512-up4bpVbbeYxkbEt2zbjmEBO1o/VpvdhHg4181JtnCvJfn27yjl+RmrXNVYPMeK6kIqK5kceEh8E3vIRhUmHtpg==", + "dev": true + }, + "node_modules/@types/eslint": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.37.0.tgz", + "integrity": "sha512-Piet7dG2JBuDIfohBngQ3rCt7MgO9xCO4xIMKxBThCq5PNRB91IjlJ10eJVwfoNtvTErmxLzwBZ7rHZtbOMmFQ==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.16.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.12.tgz", + "integrity": "sha512-tIRrjbY9C277MOfP8M3zjMIhtMlUJ6YVqkGgLjz+74jVsdf4/UjC6Hku4+1N0BS0qyC0JAS6tJLUk9H6JUKviQ==", + "dev": true + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.0.tgz", + "integrity": "sha512-K/vuv72vpfSEZoo5KIU0a2FsEoYdW0DUMtMpB5X3LlUwshetMZRZRxB7sCsVji/lFaSxtQQ3aM9O4eMolXkU9w==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.1.tgz", + "integrity": "sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.4.tgz", + "integrity": "sha512-0xRgjgDLdz6G7+vvDLlaRpFatJaJ69uTalZLRSMX5B3VUrDmXcrVA3+6fXXQgmYz7bY9AAgs348XQdmtLsK41A==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-assertions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001488", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001488.tgz", + "integrity": "sha512-NORIQuuL4xGpIy6iCCQGN4iFjlBXtfKWIenlUuyZJumLRIindLb7wXM+GO8erEhb7vXfcnf4BAg2PrSDN5TNLQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.397", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.397.tgz", + "integrity": "sha512-jwnPxhh350Q/aMatQia31KAIQdhEsYS0fFZ0BQQlN9tfvOEwShu6ZNwI4kL/xBabjcB/nTy6lSt17kNIluJZ8Q==", + "dev": true + }, + "node_modules/enhanced-resolve": { + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.14.0.tgz", + "integrity": "sha512-+DCows0XNwLDcUhbFJPdlQEVnT2zXlCv7hPxemTz86/O+B/hCQ+mb7ydkPKiflpVraqLPCAfu7lDy+hBXueojw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/envinfo": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", + "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", + "dev": true, + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz", + "integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/is-core-module": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/merge-stream": { + "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 + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", + "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", + "dev": true + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dev": true, + "dependencies": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/schema-utils": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz", + "integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-loader": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-4.0.1.tgz", + "integrity": "sha512-oqXpzDIByKONVY8g1NUPOTQhe0UTU5bWUl32GSkqK2LjJj0HmwTMVKxcUip0RgAYhY1mqgOxjbQM48a0mmeNfA==", + "dev": true, + "dependencies": { + "abab": "^2.0.6", + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.72.1" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.17.4", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.17.4.tgz", + "integrity": "sha512-jcEKZw6UPrgugz/0Tuk/PVyLAPfMBJf5clnGueo45wTweoV8yh7Q7PEkhkJ5uuUbC7zAxEcG3tqNr1bstkQ8nw==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.8.tgz", + "integrity": "sha512-WiHL3ElchZMsK27P8uIUh4604IgJyAW47LVXGbEoB21DbQcZ+OuMpGjVYnEUaqcWM6dO8uS2qUbA7LSCWqvsbg==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.17", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.16.8" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-loader": { + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.2.tgz", + "integrity": "sha512-OmlC4WVmFv5I0PpaxYb+qGeGOdm5giHU7HwDDUjw59emP2UYMHy9fFSDcYgSNoH8sXcj4hGCSEhlDZ9ULeDraA==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack": { + "version": "5.82.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.82.1.tgz", + "integrity": "sha512-C6uiGQJ+Gt4RyHXXYt+v9f+SN1v83x68URwgxNQ98cvH8kxiuywWGP4XeNZ1paOzZ63aY3cTciCEQJNFUljlLw==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.14.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.1.2", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.7", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.1.tgz", + "integrity": "sha512-OLJwVMoXnXYH2ncNGU8gxVpUtm3ybvdioiTvHgUyBuyMLKiVvWy+QObzBsMtp5pH7qQoEuWgeEUQ/sU3ZJFzAw==", + "dev": true, + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.1.0", + "@webpack-cli/info": "^2.0.1", + "@webpack-cli/serve": "^2.0.4", + "colorette": "^2.0.14", + "commander": "^10.0.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/webpack-merge": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", + "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + }, + "dependencies": { + "@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true + }, + "@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true + }, + "@jridgewell/source-map": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", + "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "@svta/common-media-library": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@svta/common-media-library/-/common-media-library-0.5.0.tgz", + "integrity": "sha512-up4bpVbbeYxkbEt2zbjmEBO1o/VpvdhHg4181JtnCvJfn27yjl+RmrXNVYPMeK6kIqK5kceEh8E3vIRhUmHtpg==", + "dev": true + }, + "@types/eslint": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.37.0.tgz", + "integrity": "sha512-Piet7dG2JBuDIfohBngQ3rCt7MgO9xCO4xIMKxBThCq5PNRB91IjlJ10eJVwfoNtvTErmxLzwBZ7rHZtbOMmFQ==", + "dev": true, + "requires": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dev": true, + "requires": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "@types/estree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "dev": true + }, + "@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "@types/node": { + "version": "18.16.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.12.tgz", + "integrity": "sha512-tIRrjbY9C277MOfP8M3zjMIhtMlUJ6YVqkGgLjz+74jVsdf4/UjC6Hku4+1N0BS0qyC0JAS6tJLUk9H6JUKviQ==", + "dev": true + }, + "@webassemblyjs/ast": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "dev": true, + "requires": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", + "dev": true + }, + "@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "requires": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "@webpack-cli/configtest": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.0.tgz", + "integrity": "sha512-K/vuv72vpfSEZoo5KIU0a2FsEoYdW0DUMtMpB5X3LlUwshetMZRZRxB7sCsVji/lFaSxtQQ3aM9O4eMolXkU9w==", + "dev": true, + "requires": {} + }, + "@webpack-cli/info": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.1.tgz", + "integrity": "sha512-fE1UEWTwsAxRhrJNikE7v4EotYflkEhBL7EbajfkPlf6E37/2QshOy/D48Mw8G5XMFlQtS6YV42vtbG9zBpIQA==", + "dev": true, + "requires": {} + }, + "@webpack-cli/serve": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.4.tgz", + "integrity": "sha512-0xRgjgDLdz6G7+vvDLlaRpFatJaJ69uTalZLRSMX5B3VUrDmXcrVA3+6fXXQgmYz7bY9AAgs348XQdmtLsK41A==", + "dev": true, + "requires": {} + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, + "acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true + }, + "acorn-import-assertions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "dev": true, + "requires": {} + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "requires": {} + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001488", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001488.tgz", + "integrity": "sha512-NORIQuuL4xGpIy6iCCQGN4iFjlBXtfKWIenlUuyZJumLRIindLb7wXM+GO8erEhb7vXfcnf4BAg2PrSDN5TNLQ==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true + }, + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "electron-to-chromium": { + "version": "1.4.397", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.397.tgz", + "integrity": "sha512-jwnPxhh350Q/aMatQia31KAIQdhEsYS0fFZ0BQQlN9tfvOEwShu6ZNwI4kL/xBabjcB/nTy6lSt17kNIluJZ8Q==", + "dev": true + }, + "enhanced-resolve": { + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.14.0.tgz", + "integrity": "sha512-+DCows0XNwLDcUhbFJPdlQEVnT2zXlCv7hPxemTz86/O+B/hCQ+mb7ydkPKiflpVraqLPCAfu7lDy+hBXueojw==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + } + }, + "envinfo": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", + "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", + "dev": true + }, + "es-module-lexer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz", + "integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==", + "dev": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true + }, + "is-core-module": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true + }, + "jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "merge-stream": { + "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 + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "requires": { + "mime-db": "1.52.0" + } + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node-releases": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", + "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", + "dev": true + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "requires": { + "resolve": "^1.20.0" + } + }, + "resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dev": true, + "requires": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "schema-utils": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz", + "integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, + "semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, + "source-map-loader": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-4.0.1.tgz", + "integrity": "sha512-oqXpzDIByKONVY8g1NUPOTQhe0UTU5bWUl32GSkqK2LjJj0HmwTMVKxcUip0RgAYhY1mqgOxjbQM48a0mmeNfA==", + "dev": true, + "requires": { + "abab": "^2.0.6", + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.2" + } + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true + }, + "terser": { + "version": "5.17.4", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.17.4.tgz", + "integrity": "sha512-jcEKZw6UPrgugz/0Tuk/PVyLAPfMBJf5clnGueo45wTweoV8yh7Q7PEkhkJ5uuUbC7zAxEcG3tqNr1bstkQ8nw==", + "dev": true, + "requires": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + } + }, + "terser-webpack-plugin": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.8.tgz", + "integrity": "sha512-WiHL3ElchZMsK27P8uIUh4604IgJyAW47LVXGbEoB21DbQcZ+OuMpGjVYnEUaqcWM6dO8uS2qUbA7LSCWqvsbg==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.17", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.16.8" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "ts-loader": { + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.2.tgz", + "integrity": "sha512-OmlC4WVmFv5I0PpaxYb+qGeGOdm5giHU7HwDDUjw59emP2UYMHy9fFSDcYgSNoH8sXcj4hGCSEhlDZ9ULeDraA==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4" + } + }, + "typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true + }, + "update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "requires": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + } + }, + "webpack": { + "version": "5.82.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.82.1.tgz", + "integrity": "sha512-C6uiGQJ+Gt4RyHXXYt+v9f+SN1v83x68URwgxNQ98cvH8kxiuywWGP4XeNZ1paOzZ63aY3cTciCEQJNFUljlLw==", + "dev": true, + "requires": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.14.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.1.2", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.7", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + } + }, + "webpack-cli": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.1.tgz", + "integrity": "sha512-OLJwVMoXnXYH2ncNGU8gxVpUtm3ybvdioiTvHgUyBuyMLKiVvWy+QObzBsMtp5pH7qQoEuWgeEUQ/sU3ZJFzAw==", + "dev": true, + "requires": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.1.0", + "@webpack-cli/info": "^2.0.1", + "@webpack-cli/serve": "^2.0.4", + "colorette": "^2.0.14", + "commander": "^10.0.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + }, + "dependencies": { + "commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true + } + } + }, + "webpack-merge": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.8.0.tgz", + "integrity": "sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==", + "dev": true, + "requires": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + } + }, + "webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } +} diff --git a/samples/network-interceptor/package.json b/samples/network-interceptor/package.json new file mode 100644 index 0000000000..ba871ae343 --- /dev/null +++ b/samples/network-interceptor/package.json @@ -0,0 +1,14 @@ +{ + "scripts": { + "build": "webpack --mode development" + }, + "devDependencies": { + "@svta/common-media-library": "^0.5.0", + "@types/node": "^18.14.0", + "source-map-loader": "^4.0.1", + "ts-loader": "^9.4.2", + "typescript": "^4.9.3", + "webpack": "^5.82.1", + "webpack-cli": "^5.1.1" + } +} diff --git a/samples/network-interceptor/src/App.ts b/samples/network-interceptor/src/App.ts new file mode 100644 index 0000000000..27f214cd33 --- /dev/null +++ b/samples/network-interceptor/src/App.ts @@ -0,0 +1,60 @@ +import { CommonMediaRequest, CommonMediaResponse, RequestInterceptor, ResponseInterceptor } from '@svta/common-media-library/request' +import { CmcdObjectType } from '@svta/common-media-library/cmcd' +import * as dashjs from 'dashjs' + +declare global { + interface Window { + player?: dashjs.MediaPlayerClass + } +} + +export class App { + + player: dashjs.MediaPlayerClass | null = null + + mpd: string = 'https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd' + + constructor() { + } + + public init(): void { + this.player = dashjs.MediaPlayer().create() + this.player.initialize(document.querySelector('video') as HTMLMediaElement, this.mpd, true) + + // Add request plugin to override request url for video segment requests only + this.addRequestInterceptor() + + // Add response plugin to add response header + this.addResponseInterceptor() + + window.player = this.player + } + + private addRequestInterceptor() { + if (!this.player) { + return + } + const interceptor: RequestInterceptor = (request: CommonMediaRequest) => { + if (request.cmcd?.ot === CmcdObjectType.VIDEO) { + request.url += (request.url.includes('?') ? '&' : '?') + 'request-interceptor=true' + console.log(request.url) + } + return Promise.resolve(request) + } + this.player.addRequestInterceptor(interceptor) + } + + private addResponseInterceptor() { + if (!this.player) { + return + } + const interceptor: ResponseInterceptor = (response: CommonMediaResponse) => { + if (!response.headers) { + response.headers = {} + } + response.headers['response-interceptor'] = 'true' + return Promise.resolve(response) + } + this.player.addResponseInterceptor(interceptor) + } +} \ No newline at end of file diff --git a/samples/network-interceptor/tsconfig.json b/samples/network-interceptor/tsconfig.json new file mode 100644 index 0000000000..5271d5cc94 --- /dev/null +++ b/samples/network-interceptor/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "node", + "isolatedModules": true, + "noEmit": false, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + + "paths": { + "dashjs": ["../../index.d.ts"] + }, + }, + "include": ["src"] +} diff --git a/samples/network-interceptor/webpack.config.js b/samples/network-interceptor/webpack.config.js new file mode 100644 index 0000000000..322037cdb1 --- /dev/null +++ b/samples/network-interceptor/webpack.config.js @@ -0,0 +1,37 @@ +const path = require('path'); + +module.exports = { + entry: './src/App.ts', + externals: { + dashjs: 'dashjs' + }, + module: { + rules: [ + { + test: /\.js$/, + enforce: "pre", + use: ["source-map-loader"], + exclude: /node_modules/, + }, + { + test: /\.ts?$/, + use: 'ts-loader', + exclude: /node_modules/, + }, + ], + }, + resolve: { + extensions: ['.tsx', '.ts', '.js'], + }, + output: { + library: 'app', + filename: 'app.js', + path: path.resolve(__dirname, 'dist'), + }, + devtool: 'eval-source-map', + devServer: { + static: path.join(__dirname, "dist"), + compress: true, + port: 4000, + }, +}; \ No newline at end of file diff --git a/samples/samples.json b/samples/samples.json index 4348825863..49dba6830d 100644 --- a/samples/samples.json +++ b/samples/samples.json @@ -652,10 +652,10 @@ ] }, { - "title": "Extending Dash.js", - "description": "This sample shows how to use dash.js extend mechanism to add custom HTTP headers and modify URL's of the requests done by the player.", - "href": "advanced/extend.html", - "image": "lib/img/bbb-4.jpg", + "title": "Network interceptor", + "description": "This sample shows how to apply network request and response interceptor based on CommonMediaLibray API.", + "href": "network-interceptor/index.html", + "image": "lib/img/bbb-1.jpg", "labels": [ "VoD", "Video", diff --git a/src/dash/SegmentBaseLoader.js b/src/dash/SegmentBaseLoader.js index 18322b33bf..adb3103b97 100644 --- a/src/dash/SegmentBaseLoader.js +++ b/src/dash/SegmentBaseLoader.js @@ -42,7 +42,6 @@ function SegmentBaseLoader() { logger, errHandler, boxParser, - requestModifier, dashMetrics, mediaPlayerModel, urlLoader, @@ -60,7 +59,6 @@ function SegmentBaseLoader() { errHandler: errHandler, dashMetrics: dashMetrics, mediaPlayerModel: mediaPlayerModel, - requestModifier: requestModifier, boxParser: boxParser, errors: errors, urlUtils: urlUtils, @@ -94,10 +92,6 @@ function SegmentBaseLoader() { logger = config.debug.getLogger(instance); } - if (config.requestModifier) { - requestModifier = config.requestModifier; - } - if (config.errors) { errors = config.errors; } diff --git a/src/dash/WebmSegmentBaseLoader.js b/src/dash/WebmSegmentBaseLoader.js index 6b0bf41b01..80f7f99c8f 100644 --- a/src/dash/WebmSegmentBaseLoader.js +++ b/src/dash/WebmSegmentBaseLoader.js @@ -14,7 +14,6 @@ function WebmSegmentBaseLoader() { logger, WebM, errHandler, - requestModifier, dashMetrics, mediaPlayerModel, urlLoader, @@ -92,7 +91,6 @@ function WebmSegmentBaseLoader() { errHandler: errHandler, dashMetrics: dashMetrics, mediaPlayerModel: mediaPlayerModel, - requestModifier: requestModifier, errors: errors }); } @@ -107,7 +105,6 @@ function WebmSegmentBaseLoader() { errHandler = config.errHandler; errors = config.errors; logger = config.debug.getLogger(instance); - requestModifier = config.requestModifier; } function parseCues(ab) { diff --git a/src/dash/controllers/ContentSteeringController.js b/src/dash/controllers/ContentSteeringController.js index d61df8d843..8f4967f520 100644 --- a/src/dash/controllers/ContentSteeringController.js +++ b/src/dash/controllers/ContentSteeringController.js @@ -64,7 +64,6 @@ function ContentSteeringController() { dashMetrics, mediaPlayerModel, manifestModel, - requestModifier, serviceDescriptionController, throughputController, eventBus, @@ -90,9 +89,6 @@ function ContentSteeringController() { if (config.mediaPlayerModel) { mediaPlayerModel = config.mediaPlayerModel; } - if (config.requestModifier) { - requestModifier = config.requestModifier; - } if (config.manifestModel) { manifestModel = config.manifestModel; } @@ -115,7 +111,6 @@ function ContentSteeringController() { errHandler, dashMetrics, mediaPlayerModel, - requestModifier, errors: Errors }); eventBus.on(MediaPlayerEvents.FRAGMENT_LOADING_STARTED, _onFragmentLoadingStarted, instance); diff --git a/src/dash/controllers/SegmentBaseController.js b/src/dash/controllers/SegmentBaseController.js index 58d5c9d15e..fa4f0730e6 100644 --- a/src/dash/controllers/SegmentBaseController.js +++ b/src/dash/controllers/SegmentBaseController.js @@ -45,7 +45,6 @@ function SegmentBaseController(config) { const baseURLController = config.baseURLController; const debug = config.debug; const boxParser = config.boxParser; - const requestModifier = config.requestModifier; const errors = config.errors; let instance, @@ -65,8 +64,7 @@ function SegmentBaseController(config) { events: events, errors: errors, debug: debug, - boxParser: boxParser, - requestModifier: requestModifier + boxParser: boxParser }); webmSegmentBaseLoader.setConfig({ @@ -77,8 +75,7 @@ function SegmentBaseController(config) { eventBus: eventBus, events: events, errors: errors, - debug: debug, - requestModifier: requestModifier + debug: debug }); } diff --git a/src/offline/OfflineStreamProcessor.js b/src/offline/OfflineStreamProcessor.js index f36e08d912..d19c883b16 100644 --- a/src/offline/OfflineStreamProcessor.js +++ b/src/offline/OfflineStreamProcessor.js @@ -33,7 +33,6 @@ import RepresentationController from '../dash/controllers/RepresentationControll import FragmentModel from '../streaming/models/FragmentModel.js'; import FragmentLoader from '../streaming/FragmentLoader.js'; import URLUtils from '../streaming/utils/URLUtils.js'; -import RequestModifier from '../streaming/utils/RequestModifier.js'; import SegmentsController from '../dash/controllers/SegmentsController.js'; function OfflineStreamProcessor(config) { @@ -102,7 +101,6 @@ function OfflineStreamProcessor(config) { eventBus: eventBus, events: events, debug: debug, - requestModifier: RequestModifier(context).getInstance(), dashConstants: dashConstants, constants: constants, segmentsController: segmentsController, @@ -130,7 +128,6 @@ function OfflineStreamProcessor(config) { dashMetrics: dashMetrics, mediaPlayerModel: mediaPlayerModel, errHandler: errHandler, - requestModifier: RequestModifier(context).getInstance(), settings: settings, eventBus: eventBus, events: events, diff --git a/src/streaming/FragmentLoader.js b/src/streaming/FragmentLoader.js index f531888099..537958e62c 100644 --- a/src/streaming/FragmentLoader.js +++ b/src/streaming/FragmentLoader.js @@ -53,7 +53,6 @@ function FragmentLoader(config) { errors: errors, dashMetrics: config.dashMetrics, mediaPlayerModel: config.mediaPlayerModel, - requestModifier: config.requestModifier, urlUtils: urlUtils, constants: Constants, boxParser: config.boxParser, diff --git a/src/streaming/ManifestLoader.js b/src/streaming/ManifestLoader.js index 82141a7fe1..7c68358b9a 100644 --- a/src/streaming/ManifestLoader.js +++ b/src/streaming/ManifestLoader.js @@ -68,7 +68,6 @@ function ManifestLoader(config) { errHandler: config.errHandler, dashMetrics: config.dashMetrics, mediaPlayerModel: config.mediaPlayerModel, - requestModifier: config.requestModifier, urlUtils: urlUtils, constants: Constants, dashConstants: DashConstants, @@ -80,7 +79,6 @@ function ManifestLoader(config) { errHandler: errHandler, dashMetrics: config.dashMetrics, mediaPlayerModel: config.mediaPlayerModel, - requestModifier: config.requestModifier, settings: config.settings }); diff --git a/src/streaming/MediaPlayer.js b/src/streaming/MediaPlayer.js index e8948c5c64..57bea9c0e3 100644 --- a/src/streaming/MediaPlayer.js +++ b/src/streaming/MediaPlayer.js @@ -44,7 +44,6 @@ import ManifestLoader from './ManifestLoader.js'; import ErrorHandler from './utils/ErrorHandler.js'; import Capabilities from './utils/Capabilities.js'; import CapabilitiesFilter from './utils/CapabilitiesFilter.js'; -import RequestModifier from './utils/RequestModifier.js'; import URIFragmentModel from './models/URIFragmentModel.js'; import ManifestModel from './models/ManifestModel.js'; import MediaPlayerModel from './models/MediaPlayerModel.js'; @@ -389,7 +388,6 @@ function MediaPlayer() { eventBus: eventBus, debug: debug, boxParser: BoxParser(context).getInstance(), - requestModifier: RequestModifier(context).getInstance(), errors: Errors }); } @@ -416,8 +414,7 @@ function MediaPlayer() { manifestModel, serviceDescriptionController, throughputController, - eventBus, - requestModifier: RequestModifier(context).getInstance() + eventBus }) restoreDefaultUTCTimingSources(); @@ -1697,6 +1694,50 @@ function MediaPlayer() { } + /** + * Adds a request interceptor. This enables application to monitor, manipulate, overwrite any request parameter and/or request data. + * The provided callback function shall return a promise with updated request that shall be resolved once the process of the request is completed. + * The interceptors are applied in the order they are added. + * @param {function} interceptor - the request interceptor callback + * @memberof module:MediaPlayer + * @instance + */ + function addRequestInterceptor(interceptor) { + customParametersModel.addRequestInterceptor(interceptor); + } + + /** + * Removes a request interceptor. + * @param {function} interceptor - the request interceptor callback + * @memberof module:MediaPlayer + * @instance + */ + function removeRequestInterceptor(interceptor) { + customParametersModel.removeRequestInterceptor(interceptor); + } + + /** + * Adds a response interceptor. This enables application to monitor, manipulate, overwrite the response data + * The provided callback function shall return a promise with updated response that shall be resolved once the process of the response is completed. + * The interceptors are applied in the order they are added. + * @param {function} interceptor - the response interceptor + * @memberof module:MediaPlayer + * @instance + */ + function addResponseInterceptor(interceptor) { + customParametersModel.addResponseInterceptor(interceptor); + } + + /** + * Removes a response interceptor. + * @param {function} interceptor - the request interceptor + * @memberof module:MediaPlayer + * @instance + */ + function removeResponseInterceptor(interceptor) { + customParametersModel.removeResponseInterceptor(interceptor); + } + /** * Registers a license request filter. This enables application to manipulate/overwrite any request parameter and/or request data. * The provided callback function shall return a promise that shall be resolved once the filter process is completed. @@ -2283,7 +2324,6 @@ function MediaPlayer() { errHandler: errHandler, dashMetrics: dashMetrics, mediaPlayerModel: mediaPlayerModel, - requestModifier: RequestModifier(context).getInstance(), mssHandler: mssHandler, settings: settings }); @@ -2512,6 +2552,8 @@ function MediaPlayer() { instance = { addABRCustomRule, + addRequestInterceptor, + addResponseInterceptor, addUTCTimingSource, attachProtectionController, attachSource, @@ -2579,6 +2621,8 @@ function MediaPlayer() { registerLicenseResponseFilter, removeABRCustomRule, removeAllABRCustomRule, + removeRequestInterceptor, + removeResponseInterceptor, removeUTCTimingSource, reset, resetCustomInitialTrackSelectionFunction, diff --git a/src/streaming/StreamProcessor.js b/src/streaming/StreamProcessor.js index 69c0450840..382f23bff0 100644 --- a/src/streaming/StreamProcessor.js +++ b/src/streaming/StreamProcessor.js @@ -45,7 +45,6 @@ import DashHandler from '../dash/DashHandler.js'; import Errors from '../core/errors/Errors.js'; import DashJSError from './vo/DashJSError.js'; import Debug from '../core/Debug.js'; -import RequestModifier from './utils/RequestModifier.js'; import URLUtils from '../streaming/utils/URLUtils.js'; import {PlayListTrace} from './vo/metrics/PlayList.js'; import SegmentsController from '../dash/controllers/SegmentsController.js'; @@ -140,7 +139,6 @@ function StreamProcessor(config) { eventBus, errors: Errors, debug: Debug(context).getInstance(), - requestModifier: RequestModifier(context).getInstance(), dashConstants: DashConstants, constants: Constants, urlUtils: URLUtils(context).getInstance() diff --git a/src/streaming/XlinkLoader.js b/src/streaming/XlinkLoader.js index 5500c9dcdf..0251984de2 100644 --- a/src/streaming/XlinkLoader.js +++ b/src/streaming/XlinkLoader.js @@ -49,7 +49,6 @@ function XlinkLoader(config) { errHandler: config.errHandler, dashMetrics: config.dashMetrics, mediaPlayerModel: config.mediaPlayerModel, - requestModifier: config.requestModifier, errors: Errors }); diff --git a/src/streaming/controllers/FragmentController.js b/src/streaming/controllers/FragmentController.js index c07fa2e671..b5911a235d 100644 --- a/src/streaming/controllers/FragmentController.js +++ b/src/streaming/controllers/FragmentController.js @@ -32,14 +32,12 @@ import Constants from '../constants/Constants.js'; import DataChunk from '../vo/DataChunk.js'; import FragmentModel from '../models/FragmentModel.js'; import FragmentLoader from '../FragmentLoader.js'; -import RequestModifier from '../utils/RequestModifier.js'; import EventBus from '../../core/EventBus.js'; import Events from '../../core/events/Events.js'; import MediaPlayerEvents from '../MediaPlayerEvents.js'; import Errors from '../../core/errors/Errors.js'; import FactoryMaker from '../../core/FactoryMaker.js'; import Debug from '../../core/Debug.js'; -import SegmentResponseModifier from '../utils/SegmentResponseModifier.js'; function FragmentController(config) { @@ -51,7 +49,6 @@ function FragmentController(config) { const mediaPlayerModel = config.mediaPlayerModel; const dashMetrics = config.dashMetrics; const debug = Debug(context).getInstance(); - const segmentResponseModifier = SegmentResponseModifier(context).getInstance(); const streamInfo = config.streamInfo; let instance, @@ -80,7 +77,6 @@ function FragmentController(config) { dashMetrics: dashMetrics, mediaPlayerModel: mediaPlayerModel, errHandler: errHandler, - requestModifier: RequestModifier(context).getInstance(), settings: config.settings, boxParser: config.boxParser, eventBus: eventBus, @@ -152,26 +148,10 @@ function FragmentController(config) { return; } const chunk = createDataChunk(bytes, request, streamInfo.id, e.type !== Events.FRAGMENT_LOADING_PROGRESS); - segmentResponseModifier.modifyResponseAsync(chunk) - .then((modifiedChunk) => { - eventBus.trigger(isInit ? Events.INIT_FRAGMENT_LOADED : Events.MEDIA_FRAGMENT_LOADED, - { - chunk: modifiedChunk, - request: request - }, - { streamId: strInfo.id, mediaType: request.mediaType } - ); - }) - .catch((e) => { - logger.error(e); - eventBus.trigger(isInit ? Events.INIT_FRAGMENT_LOADED : Events.MEDIA_FRAGMENT_LOADED, - { - chunk: chunk, - request: request - }, - { streamId: strInfo.id, mediaType: request.mediaType } - ); - }) + eventBus.trigger(isInit ? Events.INIT_FRAGMENT_LOADED : Events.MEDIA_FRAGMENT_LOADED, + { chunk, request }, + { streamId: strInfo.id, mediaType: request.mediaType } + ); } instance = { diff --git a/src/streaming/controllers/XlinkController.js b/src/streaming/controllers/XlinkController.js index 3e74abafa0..5d92ce9e63 100644 --- a/src/streaming/controllers/XlinkController.js +++ b/src/streaming/controllers/XlinkController.js @@ -58,7 +58,6 @@ function XlinkController(config) { errHandler: config.errHandler, dashMetrics: config.dashMetrics, mediaPlayerModel: config.mediaPlayerModel, - requestModifier: config.requestModifier, settings: config.settings }); } diff --git a/src/streaming/models/CmcdModel.js b/src/streaming/models/CmcdModel.js index 065260e481..ccd4ae6514 100644 --- a/src/streaming/models/CmcdModel.js +++ b/src/streaming/models/CmcdModel.js @@ -131,7 +131,7 @@ function CmcdModel() { function getQueryParameter(request) { try { if (settings.get().streaming.cmcd && settings.get().streaming.cmcd.enabled) { - const cmcdData = _getCmcdData(request); + const cmcdData = getCmcdData(request); const filteredCmcdData = _applyWhitelist(cmcdData); const finalPayloadString = encodeCmcd(filteredCmcdData); @@ -172,7 +172,7 @@ function CmcdModel() { function getHeaderParameters(request) { try { if (settings.get().streaming.cmcd && settings.get().streaming.cmcd.enabled) { - const cmcdData = _getCmcdData(request); + const cmcdData = getCmcdData(request); const filteredCmcdData = _applyWhitelist(cmcdData); const headers = toCmcdHeaders(filteredCmcdData) @@ -191,7 +191,7 @@ function CmcdModel() { } } - function _getCmcdData(request) { + function getCmcdData(request) { try { let cmcdData = null; if (request.type === HTTPRequest.MPD_TYPE) { @@ -540,6 +540,7 @@ function CmcdModel() { } instance = { + getCmcdData, getQueryParameter, getHeaderParameters, setConfig, diff --git a/src/streaming/models/CmsdModel.js b/src/streaming/models/CmsdModel.js index fee38c4676..2343afc574 100644 --- a/src/streaming/models/CmsdModel.js +++ b/src/streaming/models/CmsdModel.js @@ -192,28 +192,22 @@ function CmsdModel() { function parseResponseHeaders(responseHeaders, mediaType) { let staticParams = null; let dynamicParams = null; - const headers = responseHeaders.split('\r\n'); - // Ge in reverse order in order to consider only last CMSD-Dynamic header - for (let i = headers.length - 1; i >= 0; i--) { - const header = headers[i]; - let m = header.match(/^(?[^:]*):\s*(?.*)$/); - if (m && m.groups) { - // Note: in modern browsers, the header names are returned in all lower case - let key = m.groups.key.toLowerCase(), - value = m.groups.value; - switch (key) { - case CMSD_STATIC_RESPONSE_FIELD_NAME: - staticParams = _parseCMSDStatic(value); - eventBus.trigger(Events.CMSD_STATIC_HEADER, staticParams); - break; - case CMSD_DYNAMIC_RESPONSE_FIELD_NAME: - if (!dynamicParams) { - dynamicParams = _parseCMSDDynamic(value); - } - break; - default: - break; - } + + // Note: responseHeaders as a record of key:value + for (const key in responseHeaders) { + const value = responseHeaders[key]; + switch (key) { + case CMSD_STATIC_RESPONSE_FIELD_NAME: + staticParams = _parseCMSDStatic(value); + eventBus.trigger(Events.CMSD_STATIC_HEADER, staticParams); + break; + case CMSD_DYNAMIC_RESPONSE_FIELD_NAME: + if (!dynamicParams) { + dynamicParams = _parseCMSDDynamic(value); + } + break; + default: + break; } } diff --git a/src/streaming/models/CustomParametersModel.js b/src/streaming/models/CustomParametersModel.js index 33095a4b1f..fd30ff8cdc 100644 --- a/src/streaming/models/CustomParametersModel.js +++ b/src/streaming/models/CustomParametersModel.js @@ -41,6 +41,8 @@ function CustomParametersModel() { let instance, utcTimingSources, xhrWithCredentials, + requestInterceptors, + responseInterceptors, licenseRequestFilters, licenseResponseFilters, customCapabilitiesFilters, @@ -58,6 +60,8 @@ function CustomParametersModel() { } function _resetInitialSettings() { + requestInterceptors = []; + responseInterceptors = []; licenseRequestFilters = []; licenseResponseFilters = []; customCapabilitiesFilters = []; @@ -272,6 +276,57 @@ function CustomParametersModel() { return customAbrRules; } + /** + * Adds a request interceptor. This enables application to monitor, manipulate, overwrite any request parameter and/or request data. + * The provided callback function shall return a promise with updated request that shall be resolved once the process of the request is completed. + * The interceptors are applied in the order they are added. + * @param {function} interceptor - the request interceptor callback + */ + function addRequestInterceptor(interceptor) { + requestInterceptors.push(interceptor); + } + + /** + * Adds a response interceptor. This enables application to monitor, manipulate, overwrite the response data + * The provided callback function shall return a promise with updated response that shall be resolved once the process of the response is completed. + * The interceptors are applied in the order they are added. + * @param {function} interceptor - the response interceptor callback + */ + function addResponseInterceptor(interceptor) { + responseInterceptors.push(interceptor); + } + + /** + * Unregisters a request interceptor. + * @param {function} interceptor - the request interceptor callback + */ + function removeRequestInterceptor(interceptor) { + _unregisterFilter(requestInterceptors, interceptor); + } + + /** + * Unregisters a response interceptor. + * @param {function} interceptor - the request interceptor callback + */ + function removeResponseInterceptor(interceptor) { + _unregisterFilter(responseInterceptors, interceptor); + } + + /** + * Returns all request interceptors + * @return {array} + */ + function getRequestInterceptors() { + return requestInterceptors; + } + + /** + * Returns all response interceptors + * @return {array} + */ + function getResponseInterceptors() { + return responseInterceptors; + } /** * Add a UTC timing source at the top of the list @@ -357,6 +412,12 @@ function CustomParametersModel() { removeAllAbrCustomRule, removeAbrCustomRule, getAbrCustomRules, + addRequestInterceptor, + addResponseInterceptor, + removeRequestInterceptor, + removeResponseInterceptor, + getRequestInterceptors, + getResponseInterceptors, addUTCTimingSource, removeUTCTimingSource, getUTCTimingSources, diff --git a/src/streaming/models/MetricsModel.js b/src/streaming/models/MetricsModel.js index 1d8b5a2229..b4cccd7af8 100644 --- a/src/streaming/models/MetricsModel.js +++ b/src/streaming/models/MetricsModel.js @@ -184,7 +184,11 @@ function MetricsModel(config) { vo._stream = request.mediaType; vo._mediaduration = request.duration; vo._quality = request.quality; - vo._responseHeaders = response.headers; + // For backward compatibility, convert response headers into string representation + vo._responseHeaders = ''; + for (const key in response.headers) { + vo._responseHeaders += key + ': ' + response.headers[key] + '\r\n'; + } vo._serviceLocation = request.serviceLocation || null; vo._fileLoaderType = request.fileLoaderType; vo._resourceTimingValues = request.resourceTimingValues; diff --git a/src/streaming/net/FetchLoader.js b/src/streaming/net/FetchLoader.js index 972903c6c8..2b37926f08 100644 --- a/src/streaming/net/FetchLoader.js +++ b/src/streaming/net/FetchLoader.js @@ -32,7 +32,6 @@ import FactoryMaker from '../../core/FactoryMaker.js'; import Settings from '../../core/Settings.js'; import Constants from '../constants/Constants.js'; -import {modifyRequest} from '../utils/RequestModifier.js'; import AastLowLatencyThroughputModel from '../models/AastLowLatencyThroughputModel.js'; /** @@ -45,67 +44,45 @@ function FetchLoader() { const context = this.context; const aastLowLatencyThroughputModel = AastLowLatencyThroughputModel(context).getInstance(); const settings = Settings(context).getInstance(); - let instance, dashMetrics, requestModifier, boxParser; + let instance, dashMetrics, boxParser; function setConfig(cfg) { dashMetrics = cfg.dashMetrics; - requestModifier = cfg.requestModifier; boxParser = cfg.boxParser } - function load(httpRequest) { - if (requestModifier && requestModifier.modifyRequest) { - modifyRequest(httpRequest, requestModifier) - .then(() => _request(httpRequest)); - } else { - _request(httpRequest); - } - } - - function _request(httpLoaderRequest) { + /** + * Load request + * @param {CommonMediaLibrary.request.CommonMediaRequest} httpRequest + * @param {CommonMediaLibrary.request.CommonMediaResponse} httpResponse + */ + function load(httpRequest, httpResponse) { // Variables will be used in the callback functions - const requestStartTime = new Date(); - const request = httpLoaderRequest.request; + const fragmentRequest = httpRequest.customData.request; const headers = new Headers(); - if (request.range) { - headers.append('Range', 'bytes=' + request.range); - } - - if (httpLoaderRequest.headers) { - for (let header in httpLoaderRequest.headers) { - let value = httpLoaderRequest.headers[header]; + if (httpRequest.headers) { + for (let header in httpRequest.headers) { + let value = httpRequest.headers[header]; if (value) { headers.append(header, value); } } } - if (!request.startDate) { - request.startDate = requestStartTime; - } - - if (requestModifier && requestModifier.modifyRequestHeader) { - requestModifier.modifyRequestHeader({ - setRequestHeader: function (header, value) { - headers.append(header, value); - } - }, { - url: httpLoaderRequest.url - }); - } - let abortController; if (typeof window.AbortController === 'function') { abortController = new AbortController(); /*jshint ignore:line*/ - httpLoaderRequest.abortController = abortController; - abortController.signal.onabort = httpLoaderRequest.onabort; + httpRequest.customData.abortController = abortController; + abortController.signal.onabort = httpRequest.customData.onabort; } + httpRequest.customData.abort = abort.bind(httpRequest); + const reqOptions = { - method: httpLoaderRequest.method, + method: httpRequest.method, headers: headers, - credentials: httpLoaderRequest.withCredentials ? 'include' : undefined, + credentials: httpRequest.credentials, signal: abortController ? abortController.signal : undefined }; @@ -115,7 +92,7 @@ function FetchLoader() { new Promise((resolve) => { if (calculationMode === Constants.LOW_LATENCY_DOWNLOAD_TIME_CALCULATION_MODE.AAST && aastLowLatencyThroughputModel) { - throughputCapacityDelayMS = aastLowLatencyThroughputModel.getThroughputCapacityDelayMS(request, dashMetrics.getCurrentBufferLevel(request.mediaType) * 1000); + throughputCapacityDelayMS = aastLowLatencyThroughputModel.getThroughputCapacityDelayMS(fragmentRequest, dashMetrics.getCurrentBufferLevel(fragmentRequest.mediaType) * 1000); if (throughputCapacityDelayMS) { // safely delay the "fetch" call a bit to be able to measure the throughput capacity of the line. // this will lead to first few chunks downloaded at max network speed @@ -127,24 +104,21 @@ function FetchLoader() { .then(() => { let markBeforeFetch = performance.now(); - fetch(httpLoaderRequest.url, reqOptions) + fetch(httpRequest.url, reqOptions) .then((response) => { - if (!httpLoaderRequest.response) { - httpLoaderRequest.response = {}; - } - httpLoaderRequest.response.status = response.status; - httpLoaderRequest.response.statusText = response.statusText; - httpLoaderRequest.response.responseURL = response.url; + httpResponse.status = response.status; + httpResponse.statusText = response.statusText; + httpResponse.url = response.url; if (!response.ok) { - httpLoaderRequest.onerror(); + httpRequest.customData.onerror(); } - let responseHeaders = ''; + const responseHeaders = {}; for (const key of response.headers.keys()) { - responseHeaders += key + ': ' + response.headers.get(key) + '\r\n'; + responseHeaders[key] = response.headers.get(key); } - httpLoaderRequest.response.responseHeaders = responseHeaders; + httpResponse.headers = responseHeaders; const totalBytes = parseInt(response.headers.get('Content-Length'), 10); let bytesReceived = 0; @@ -153,9 +127,9 @@ function FetchLoader() { let offset = 0; if (calculationMode === Constants.LOW_LATENCY_DOWNLOAD_TIME_CALCULATION_MODE.AAST && aastLowLatencyThroughputModel) { - _aastProcessResponse(markBeforeFetch, request, requestTime, throughputCapacityDelayMS, responseHeaders, httpLoaderRequest, response) + _aastProcessResponse(markBeforeFetch, httpRequest, requestTime, throughputCapacityDelayMS, responseHeaders, response); } else { - httpLoaderRequest.reader = response.body.getReader(); + httpRequest.customData.reader = response.body.getReader(); } let downloadedData = []; @@ -179,7 +153,7 @@ function FetchLoader() { _handleDataReceived(value) } - _read(httpLoaderRequest, _processResult); + _read(httpRequest, httpResponse, _processResult); }; /** @@ -203,7 +177,7 @@ function FetchLoader() { calculatedTime = calculateDownloadedTime(downloadedData, bytesReceived); } - httpLoaderRequest.progress({ + httpRequest.customData.onprogress({ loaded: bytesReceived, total: isNaN(totalBytes) ? bytesReceived : totalBytes, lengthComputable: true, @@ -211,10 +185,10 @@ function FetchLoader() { }); } - httpLoaderRequest.response.response = receivedData.buffer; + httpResponse.data = receivedData.buffer; } - httpLoaderRequest.onload(); - httpLoaderRequest.onloadend(); + httpRequest.customData.onload(); + httpRequest.customData.onloadend(); } /** @@ -273,7 +247,7 @@ function FetchLoader() { // Announce progress but don't track traces. Throughput measures are quite unstable // when they are based in small amount of data - httpLoaderRequest.progress({ + httpRequest.customData.onprogress({ data: data.buffer, lengthComputable: false, noTrace: true @@ -285,7 +259,7 @@ function FetchLoader() { // Call progress, so it generates traces that will be later used to know when the first byte // were received if (!signaledFirstByte) { - httpLoaderRequest.progress({ + httpRequest.customData.onprogress({ lengthComputable: false, noTrace: true }); @@ -294,24 +268,25 @@ function FetchLoader() { } } - _read(httpLoaderRequest, _processResult); + _read(httpRequest, httpResponse, _processResult); }) .catch(function (e) { - if (httpLoaderRequest.onerror) { - httpLoaderRequest.onerror(e); + if (httpRequest.customData.onerror) { + httpRequest.customData.onerror(e); } }); }); } - function _aastProcessResponse(markBeforeFetch, request, requestTime, throughputCapacityDelayMS, responseHeaders, httpLoaderRequest, response) { + function _aastProcessResponse(markBeforeFetch, httpRequest, requestTime, throughputCapacityDelayMS, responseHeaders, response) { let markA = markBeforeFetch; let markB = 0; function fetchMeassurement(stream) { const reader = stream.getReader(); const measurement = []; + const fragmentRequest = httpRequest.customData.request; reader.read() .then(function processFetch(args) { @@ -327,7 +302,7 @@ function FetchLoader() { chunkDownloadDurationMS, chunkBytes, kbps: Math.round(8 * chunkBytes / (chunkDownloadDurationMS / 1000)), - bufferLevel: dashMetrics.getCurrentBufferLevel(request.mediaType) + bufferLevel: dashMetrics.getCurrentBufferLevel(fragmentRequest.mediaType) }); } @@ -336,13 +311,13 @@ function FetchLoader() { const fetchDuration = markB - markBeforeFetch; const bytesAllChunks = measurement.reduce((prev, curr) => prev + curr.chunkBytes, 0); - aastLowLatencyThroughputModel.addMeasurement(request, fetchDuration, measurement, requestTime, throughputCapacityDelayMS, responseHeaders); + aastLowLatencyThroughputModel.addMeasurement(fragmentRequest, fetchDuration, measurement, requestTime, throughputCapacityDelayMS, responseHeaders); - httpLoaderRequest.progress({ + httpRequest.progress({ loaded: bytesAllChunks, total: bytesAllChunks, lengthComputable: true, - time: aastLowLatencyThroughputModel.getEstimatedDownloadDurationMS(request) + time: aastLowLatencyThroughputModel.getEstimatedDownloadDurationMS(fragmentRequest) }); return; } @@ -355,7 +330,7 @@ function FetchLoader() { // https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/tee const [forMeasure, forConsumer] = response.body.tee(); fetchMeassurement(forMeasure); - httpLoaderRequest.reader = forConsumer.getReader(); + httpRequest.customData.reader = forConsumer.getReader(); } /** @@ -364,13 +339,13 @@ function FetchLoader() { * @param processResult * @private */ - function _read(httpRequest, processResult) { - httpRequest.reader.read() + function _read(httpRequest, httpResponse, processResult) { + httpRequest.customData.reader.read() .then(processResult) .catch(function (e) { - if (httpRequest.onerror && httpRequest.response.status === 200) { + if (httpRequest.customData.onerror && httpResponse.status === 200) { // Error, but response code is 200, trigger error - httpRequest.onerror(e); + httpRequest.customData.onerror(e); } }); } @@ -399,15 +374,16 @@ function FetchLoader() { * Use the AbortController to abort a request * @param request */ - function abort(request) { - if (request.abortController) { + function abort() { + // this = httpRequest (CommonMediaRequest) + if (this.customData.abortController) { // For firefox and edge - request.abortController.abort(); - } else if (request.reader) { + this.customData.abortController.abort(); + } else if (this.customData.reader) { // For Chrome try { - request.reader.cancel(); - request.onabort(); + this.customData.reader.cancel(); + this.onabort(); } catch (e) { // throw exceptions (TypeError) when reader was previously closed, // for example, because a network issue diff --git a/src/streaming/net/HTTPLoader.js b/src/streaming/net/HTTPLoader.js index 23ad45f2b1..6348032572 100644 --- a/src/streaming/net/HTTPLoader.js +++ b/src/streaming/net/HTTPLoader.js @@ -42,7 +42,6 @@ import Events from '../../core/events/Events.js'; import Settings from '../../core/Settings.js'; import Constants from '../constants/Constants.js'; import CustomParametersModel from '../models/CustomParametersModel.js'; -import HttpLoaderRequest from '../vo/HttpLoaderRequest.js'; /** * @module HTTPLoader @@ -58,7 +57,6 @@ function HTTPLoader(cfg) { const errHandler = cfg.errHandler; const dashMetrics = cfg.dashMetrics; const mediaPlayerModel = cfg.mediaPlayerModel; - const requestModifier = cfg.requestModifier; const boxParser = cfg.boxParser; const errors = cfg.errors; const requestTimeout = cfg.requestTimeout || 0; @@ -106,11 +104,12 @@ function HTTPLoader(cfg) { function load(config) { if (config.request) { const retryAttempts = mediaPlayerModel.getRetryAttemptsForType(config.request.type); - _internalLoad(config, retryAttempts); + return _internalLoad(config, retryAttempts); } else { if (config.error) { config.error(config.request, 'error'); } + return Promise.resolve(); } } @@ -140,7 +139,7 @@ function HTTPLoader(cfg) { * Fired when a request has started to load data. * @param event */ - const _progress = function (event) { + const _onprogress = function (event) { const currentTime = new Date(); // If we did not transfer all data yet and this is the first time we are getting a progress event we use this time as firstByteDate. @@ -152,13 +151,16 @@ function HTTPLoader(cfg) { if (!event.lengthComputable || (event.lengthComputable && event.total !== event.loaded)) { requestObject.firstByteDate = currentTime; + httpResponse.resourceTiming.responseStart = currentTime.getTime(); } } // lengthComputable indicating if the resource concerned by the ProgressEvent has a length that can be calculated. If not, the ProgressEvent.total property has no significant value. if (event.lengthComputable) { - requestObject.bytesLoaded = event.loaded; - requestObject.bytesTotal = event.total; + requestObject.bytesLoaded = httpResponse.length = event.loaded; + requestObject.bytesTotal = httpResponse.resourceTiming.encodedBodySize = event.total; + httpResponse.length = event.total; + httpResponse.resourceTiming.encodedBodySize = event.loaded; } if (!event.noTrace) { @@ -182,8 +184,7 @@ function HTTPLoader(cfg) { progressTimeout = setTimeout(function () { // No more progress => abort request and treat as an error logger.warn('Abort request ' + httpRequest.url + ' due to progress timeout'); - httpRequest.response.onabort = null; - httpRequest.loader.abort(httpRequest); + loader.abort(httpRequest); _onloadend(); }, settings.get().streaming.fragmentRequestProgressTimeout); } @@ -193,32 +194,55 @@ function HTTPLoader(cfg) { } }; + const _oncomplete = function() { + // Update request timing info + requestObject.startDate = requestStartTime; + requestObject.endDate = new Date(); + requestObject.firstByteDate = requestObject.firstByteDate || requestStartTime; + httpResponse.resourceTiming.responseEnd = requestObject.endDate.getTime(); + + // If enabled the ResourceTimingApi we add the corresponding information to the request object. + // These values are more accurate and can be used by the ThroughputController later + _addResourceTimingValues(httpRequest, httpResponse); + } + /** * Fired when an XMLHttpRequest transaction completes. * This includes status codes such as 404. We handle errors in the _onError function. */ const _onload = function () { - if (httpRequest.response.status >= 200 && httpRequest.response.status <= 299) { - _handleLoaded(true, requestObject, httpRequest, traces, requestStartTime, fileLoaderType); + _oncomplete(); - if (config.success) { - config.success(httpRequest.response.response, httpRequest.response.statusText, httpRequest.response.responseURL); - } + _applyResponseInterceptors(httpResponse).then((_httpResponse) => { + httpResponse = _httpResponse; - if (config.complete) { - config.complete(requestObject, httpRequest.response.statusText); + _addHttpRequestMetric(httpRequest, httpResponse, traces); + + if (requestObject.type === HTTPRequest.MPD_TYPE) { + dashMetrics.addManifestUpdate(requestObject); + eventBus.trigger(Events.MANIFEST_LOADING_FINISHED, { requestObject }); } - } else { - _onerror(); - } + if (httpResponse.status >= 200 && httpResponse.status <= 299) { + if (config.success) { + config.success(httpResponse.data, httpResponse.statusText, httpResponse.url); + } + + if (config.complete) { + config.complete(requestObject, httpResponse.statusText); + } + } else { + _onerror(); + } + }); }; /** * Fired when a request has been aborted, for example because the program called XMLHttpRequest.abort(). */ const _onabort = function () { - _addHttpRequestMetric(requestObject, requestStartTime, fileLoaderType, httpRequest, true, traces); + _oncomplete(); + _addHttpRequestMetric(httpRequest, httpResponse, traces); if (progressTimeout) { clearTimeout(progressTimeout); progressTimeout = null; @@ -233,8 +257,9 @@ function HTTPLoader(cfg) { * @param event */ const _ontimeout = function (event) { - let timeoutMessage; + _oncomplete(); + let timeoutMessage; // We know how much we already downloaded by looking at the timeout event if (event.lengthComputable) { let percentageComplete = (event.loaded / event.total) * 100; @@ -243,7 +268,7 @@ function HTTPLoader(cfg) { timeoutMessage = 'Request timeout: non-computable download size'; } logger.warn(timeoutMessage); - _addHttpRequestMetric(requestObject, requestStartTime, fileLoaderType, httpRequest, true, traces); + _addHttpRequestMetric(httpRequest, httpResponse, traces); _retriggerRequest(); }; @@ -251,24 +276,39 @@ function HTTPLoader(cfg) { * Fired when the request encountered an error. */ const _onerror = function () { - _handleLoaded(false, requestObject, httpRequest, traces, requestStartTime, fileLoaderType); - // If we get a 404 to a media segment we should check the client clock again and perform a UTC sync in the background. try { - if (httpRequest.response.status === 404 && settings.get().streaming.utcSynchronization.enableBackgroundSyncAfterSegmentDownloadError && requestObject.type === HTTPRequest.MEDIA_SEGMENT_TYPE) { + if (httpResponse.status === 404 && settings.get().streaming.utcSynchronization.enableBackgroundSyncAfterSegmentDownloadError && requestObject.type === HTTPRequest.MEDIA_SEGMENT_TYPE) { // Only trigger a sync if the loading failed for the first time const initialNumberOfAttempts = mediaPlayerModel.getRetryAttemptsForType(HTTPRequest.MEDIA_SEGMENT_TYPE); if (initialNumberOfAttempts === remainingAttempts) { eventBus.trigger(Events.ATTEMPT_BACKGROUND_SYNC); } } - } catch (e) { - - } + } catch (e) {} _retriggerRequest(); } + const _loadRequest = function(loader, httpRequest, httpResponse) { + return new Promise((resolve) => { + _applyRequestInterceptors(httpRequest).then((_httpRequest) => { + httpRequest = _httpRequest; + + httpRequest.customData.onload = _onload; + httpRequest.customData.onloadend = _onloadend; + httpRequest.customData.onerror = _onloadend; + httpRequest.customData.onprogress = _onprogress; + httpRequest.customData.onabort = _onabort; + httpRequest.customData.ontimeout = _ontimeout; + + httpResponse.resourceTiming.startTime = Date.now(); + loader.load(httpRequest, httpResponse); + resolve(); + }); + }); + } + /** * Retriggers the request in case we did not exceed the number of retry attempts * @private @@ -293,15 +333,15 @@ function HTTPLoader(cfg) { errHandler.error(new DashJSError(downloadErrorToRequestTypeMap[requestObject.type], requestObject.url + ' is not available', { request: requestObject, - response: httpRequest.response + response: httpResponse })); if (config.error) { - config.error(requestObject, 'error', httpRequest.response.statusText, httpRequest.response); + config.error(requestObject, 'error', httpResponse.statusText, httpResponse); } if (config.complete) { - config.complete(requestObject, httpRequest.response.statusText); + config.complete(requestObject, httpResponse.statusText); } } } @@ -309,9 +349,11 @@ function HTTPLoader(cfg) { // Main code after inline functions const requestObject = config.request; const traces = []; - let firstProgress, requestStartTime, lastTraceTime, lastTraceReceivedCount, fileLoaderType, httpRequest, - progressTimeout; + let firstProgress, requestStartTime, lastTraceTime, lastTraceReceivedCount, progressTimeout; + let httpRequest; // CommonMediaLibrary.request.CommonMediaRequest + let httpResponse; // CommonMediaLibrary.request.CommonMediaResponse + requestObject.bytesLoaded = NaN; requestObject.bytesTotal = NaN; requestObject.firstByteDate = null; @@ -319,46 +361,55 @@ function HTTPLoader(cfg) { requestStartTime = new Date(); lastTraceTime = requestStartTime; lastTraceReceivedCount = 0; - fileLoaderType = ''; progressTimeout = null; - if (!requestModifier || !dashMetrics || !errHandler) { + if (!dashMetrics || !errHandler) { throw new Error('config object is not correct or missing'); } const loaderInformation = _getLoader(requestObject); const loader = loaderInformation.loader; - fileLoaderType = loaderInformation.fileLoaderType; - const modifiedRequestParams = _getModifiedRequestHeaderAndUrl(requestObject); - requestObject.url = modifiedRequestParams.url; - const method = requestObject.checkExistenceOnly ? HTTPRequest.HEAD : HTTPRequest.GET; + requestObject.fileLoaderType = loaderInformation.fileLoaderType; + + requestObject.headers = {}; + _updateRequestUrlAndHeaders(requestObject); + if (requestObject.range) { + requestObject.headers['Range'] = 'bytes=' + requestObject.range; + } const withCredentials = customParametersModel.getXHRWithCredentialsForType(requestObject.type); - httpRequest = new HttpLoaderRequest({ - url: modifiedRequestParams.url, - method, - withCredentials, - request: requestObject, - onload: _onload, - onloadend: _onloadend, - onerror: _onerror, - progress: _progress, - onabort: _onabort, - ontimeout: _ontimeout, - loader, + httpRequest = /* CommonMediaRequest */{ + url: requestObject.url, + method: HTTPRequest.GET, + responseType: requestObject.responseType, + headers: requestObject.headers, + credentials: withCredentials ? 'include' : 'omit', timeout: requestTimeout, - headers: modifiedRequestParams.headers - }); + cmcd: cmcdModel.getCmcdData(requestObject), + customData: { request: requestObject } + }; + + // Init response (CommonMediaLibrary.request.CommoneMediaResponse) + httpResponse = { + request: httpRequest, + resourceTiming: { + startTime: Date.now(), + encodedBodySize: 0 + } + }; // Adds the ability to delay single fragment loading time to control buffer. let now = new Date().getTime(); if (isNaN(requestObject.delayLoadingTime) || now >= requestObject.delayLoadingTime) { // no delay - just send httpRequests.push(httpRequest); - loader.load(httpRequest); + return _loadRequest(loader, httpRequest, httpResponse); } else { // delay - let delayedRequest = { httpRequest: httpRequest }; + let delayedRequest = { + httpRequest, + httpResponse + }; delayedRequests.push(delayedRequest); delayedRequest.delayTimeout = setTimeout(function () { if (delayedRequests.indexOf(delayedRequest) === -1) { @@ -370,54 +421,42 @@ function HTTPLoader(cfg) { requestStartTime = new Date(); lastTraceTime = requestStartTime; httpRequests.push(delayedRequest.httpRequest); - loader.load(delayedRequest.httpRequest); + _loadRequest(loader, delayedRequest.httpRequest, delayedRequest.httpResponse); } catch (e) { delayedRequest.httpRequest.onerror(); } }, (requestObject.delayLoadingTime - now)); - } - } - /** - * Function to be called after the request has been loaded. Either successfully or unsuccesfully - * @param {boolean} success - * @param {object} requestObject - * @param {object} httpRequest - * @param {array} traces - * @param {date} requestStartTime - * @param {string} fileLoaderType - * @private - */ - function _handleLoaded(success, requestObject, httpRequest, traces, requestStartTime, fileLoaderType) { - // If enabled the ResourceTimingApi we add the corresponding information to the request object. - // These values are more accurate and can be used by the ThroughputController later - if (settings.get().streaming.abr.throughput.useResourceTimingApi) { - _addResourceTimingValues(requestObject); + return Promise.resolve(); } + } - if (!requestObject.checkExistenceOnly) { - _addHttpRequestMetric(requestObject, requestStartTime, fileLoaderType, httpRequest, success, traces); + function _applyRequestInterceptors(httpRequest) { + const interceptors = customParametersModel.getRequestInterceptors(); + if (!interceptors) return Promise.resolve(httpRequest); - if (requestObject.type === HTTPRequest.MPD_TYPE) { - dashMetrics.addManifestUpdate(requestObject); - eventBus.trigger(Events.MANIFEST_LOADING_FINISHED, { requestObject }); - } - } + return interceptors.reduce((prev, next) => { + return prev.then((request) => { + return next(request); + }); + }, Promise.resolve(httpRequest)); } - function _addHttpRequestMetric(requestObject, requestStartTime, fileLoaderType, httpRequest, success, traces) { - requestObject.startDate = requestStartTime; - requestObject.endDate = new Date(); - requestObject.firstByteDate = requestObject.firstByteDate || requestStartTime; - requestObject.fileLoaderType = fileLoaderType; + function _applyResponseInterceptors(response) { + const interceptors = customParametersModel.getResponseInterceptors(); + if (!interceptors) return Promise.resolve(response); - const responseUrl = httpRequest.response ? httpRequest.response.responseURL : null; - const responseStatus = httpRequest.response ? httpRequest.response.status : null; - const responseHeaders = httpRequest.response && httpRequest.response.getAllResponseHeaders ? httpRequest.response.getAllResponseHeaders() : - httpRequest.response ? httpRequest.response.responseHeaders : null; + return interceptors.reduce((prev, next) => { + return prev.then(resp => { + return next(resp); + }); + }, Promise.resolve(response)); + } - const cmsd = responseHeaders && settings.get().streaming.cmsd && settings.get().streaming.cmsd.enabled ? cmsdModel.parseResponseHeaders(responseHeaders, requestObject.mediaType) : null; - dashMetrics.addHttpRequest(requestObject, responseUrl, responseStatus, responseHeaders, success ? traces : null, cmsd); + function _addHttpRequestMetric(httpRequest, httpResponse, traces) { + const requestObject = httpRequest.customData.request; + const cmsd = settings.get().streaming.cmsd && settings.get().streaming.cmsd.enabled ? cmsdModel.parseResponseHeaders(httpResponse.headers, requestObject.mediaType) : null; + dashMetrics.addHttpRequest(requestObject, httpResponse.url, httpResponse.status, httpResponse.headers, traces, cmsd); } /** @@ -425,9 +464,12 @@ function HTTPLoader(cfg) { * @param requestObject * @private */ - function _addResourceTimingValues(requestObject) { + function _addResourceTimingValues(httpRequest, httpResponse) { + if (!settings.get().streaming.abr.throughput.useResourceTimingApi) { + return; + } // Check performance support. We do not support range requests, needs to figure out how to find the right resource here. - if (typeof performance === 'undefined' || requestObject.range) { + if (typeof performance === 'undefined' || httpRequest.range) { return; } @@ -441,14 +483,23 @@ function HTTPLoader(cfg) { let i = 0; let resource = null; while (i < resources.length) { - if (resources[i].name === requestObject.url) { + if (resources[i].name === httpRequest.url) { resource = resources[i]; break; } i += 1; } - requestObject.resourceTimingValues = resource; + httpRequest.customData.request.resourceTimingValues = resource; + + // Use Resource Timing info when available for CommonMediaResponse + if (resource) { + httpResponse.resourceTiming.startTime = resource.startTime; + httpResponse.resourceTiming.encodedBodySize = resource.startTime; + httpResponse.resourceTiming.responseStart = resource.startTime; + httpResponse.resourceTiming.responseEnd = resource.responseEnd; + httpResponse.resourceTiming.duration = resource.duration; + } } /** @@ -466,7 +517,6 @@ function HTTPLoader(cfg) { fetchLoader = FetchLoader(context).create(); fetchLoader.setConfig({ dashMetrics, - requestModifier, boxParser }); } @@ -474,9 +524,7 @@ function HTTPLoader(cfg) { fileLoaderType = Constants.FILE_LOADER_TYPES.FETCH; } else { if (!xhrLoader) { - xhrLoader = XHRLoader(context).create({ - requestModifier - }); + xhrLoader = XHRLoader(context).create(); } loader = xhrLoader; fileLoaderType = Constants.FILE_LOADER_TYPES.XHR; @@ -486,24 +534,19 @@ function HTTPLoader(cfg) { } /** - * Modifies the request headers and the request url. Uses the requestModifier and the CMCDModel + * Updates the request url and headers according to CMCD and content steering (pathway cloning) * @param request - * @return {{headers: null, url}} * @private */ - function _getModifiedRequestHeaderAndUrl(request) { - let url; - let headers = null; - - url = requestModifier.modifyRequestURL ? requestModifier.modifyRequestURL(request.url) : request.url; + function _updateRequestUrlAndHeaders(request) { if (settings.get().streaming.cmcd && settings.get().streaming.cmcd.enabled) { const cmcdMode = settings.get().streaming.cmcd.mode; if (cmcdMode === Constants.CMCD_MODE_QUERY) { const additionalQueryParameter = _getAdditionalQueryParameter(request); - url = Utils.addAditionalQueryParameterToUrl(url, additionalQueryParameter); + request.url = Utils.addAditionalQueryParameterToUrl(request.url, additionalQueryParameter); } else if (cmcdMode === Constants.CMCD_MODE_HEADER) { - headers = cmcdModel.getHeaderParameters(request); + request.headers = Object.assign(request.headers, cmcdModel.getHeaderParameters(request)); } } @@ -515,12 +558,7 @@ function HTTPLoader(cfg) { value: request.queryParams[key] } }) - url = Utils.addAditionalQueryParameterToUrl(url, queryParams); - } - - return { - url, - headers + request.url = Utils.addAditionalQueryParameterToUrl(request.url, queryParams); } } @@ -563,17 +601,23 @@ function HTTPLoader(cfg) { delayedRequests.forEach(x => clearTimeout(x.delayTimeout)); delayedRequests = []; - httpRequests.forEach(x => { + httpRequests.forEach(req => { + const reqData = req.customData + if (!reqData) { + return + } // MSS patch: ignore FragmentInfo requests - if (x.request.type === HTTPRequest.MSS_FRAGMENT_INFO_SEGMENT_TYPE) { + if (reqData.request && reqData.request.type === HTTPRequest.MSS_FRAGMENT_INFO_SEGMENT_TYPE) { return; } // abort will trigger onloadend which we don't want // when deliberately aborting inflight requests - // set them to undefined so they are not called - x.onloadend = x.onerror = x.onprogress = undefined; - x.loader.abort(x); + reqData.onloadend = reqData.onerror = reqData.onprogress = undefined; + if (reqData.abort) { + reqData.abort(); + } }); httpRequests = []; } diff --git a/src/streaming/net/URLLoader.js b/src/streaming/net/URLLoader.js index 18a50f12c4..11a915e1ae 100644 --- a/src/streaming/net/URLLoader.js +++ b/src/streaming/net/URLLoader.js @@ -54,7 +54,6 @@ function URLLoader(cfg) { loader = loaderFactory(context).create({ errHandler: cfg.errHandler, mediaPlayerModel: cfg.mediaPlayerModel, - requestModifier: cfg.requestModifier, dashMetrics: cfg.dashMetrics, boxParser: cfg.boxParser ? cfg.boxParser : null, constants: cfg.constants ? cfg.constants : null, diff --git a/src/streaming/net/XHRLoader.js b/src/streaming/net/XHRLoader.js index 2501c131d1..2f0928ecab 100644 --- a/src/streaming/net/XHRLoader.js +++ b/src/streaming/net/XHRLoader.js @@ -29,90 +29,76 @@ * POSSIBILITY OF SUCH DAMAGE. */ import FactoryMaker from '../../core/FactoryMaker.js'; -import { modifyRequest } from '../utils/RequestModifier.js'; +import Utils from '../../core/Utils.js'; /** * @module XHRLoader * @ignore * @description Manages download of resources via HTTP. - * @param {Object} cfg - dependencies from parent */ -function XHRLoader(cfg) { - - cfg = cfg || {}; - const requestModifier = cfg.requestModifier; +function XHRLoader() { let instance; - - function load(httpLoaderRequest) { - if (requestModifier && requestModifier.modifyRequest) { - modifyRequest(httpLoaderRequest, requestModifier) - .then(() => _request(httpLoaderRequest)); - } - else { - _request(httpLoaderRequest); + let xhr; + + /** + * Load request + * @param {CommonMediaLibrary.request.CommonMediaRequest} httpRequest + * @param {CommonMediaLibrary.request.CommonMediaResponse} httpResponse + */ + function load(httpRequest, httpResponse) { + xhr = new XMLHttpRequest(); + xhr.open(httpRequest.method, httpRequest.url, true); + + if (httpRequest.responseType) { + xhr.responseType = httpRequest.responseType; } - } - - function _request(httpLoaderRequest) { - // Variables will be used in the callback functions - const requestStartTime = new Date(); - const request = httpLoaderRequest.request; - let xhr = new XMLHttpRequest(); - xhr.open(httpLoaderRequest.method, httpLoaderRequest.url, true); - - if (request.responseType) { - xhr.responseType = request.responseType; - } - - if (request.range) { - xhr.setRequestHeader('Range', 'bytes=' + request.range); - } - - if (!request.startDate) { - request.startDate = requestStartTime; - } - - if (requestModifier && requestModifier.modifyRequestHeader) { - xhr = requestModifier.modifyRequestHeader(xhr, { - url: httpLoaderRequest.url - }); - } - - if (httpLoaderRequest.headers) { - for (let header in httpLoaderRequest.headers) { - let value = httpLoaderRequest.headers[header]; + if (httpRequest.headers) { + for (let header in httpRequest.headers) { + let value = httpRequest.headers[header]; if (value) { xhr.setRequestHeader(header, value); } } } - xhr.withCredentials = httpLoaderRequest.withCredentials; + xhr.withCredentials = httpRequest.credentials === 'include'; + xhr.timeout = httpRequest.timeout; - xhr.onload = httpLoaderRequest.onload; - xhr.onloadend = httpLoaderRequest.onloadend; - xhr.onerror = httpLoaderRequest.onerror; - xhr.onprogress = httpLoaderRequest.progress; - xhr.onabort = httpLoaderRequest.onabort; - xhr.ontimeout = httpLoaderRequest.ontimeout; - xhr.timeout = httpLoaderRequest.timeout; + xhr.onload = function(e) { + httpResponse.url = this.responseURL; + httpResponse.status = this.status; + httpResponse.statusText = this.statusText; + httpResponse.headers = Utils.parseHttpHeaders(this.getAllResponseHeaders()); + httpResponse.data = this.response; + httpRequest.customData.onload(e); + } + xhr.onloadend = httpRequest.customData.onloadend; + xhr.onerror = httpRequest.customData.onerror; + xhr.onprogress = httpRequest.customData.onprogress; + xhr.onabort = httpRequest.customData.onabort; + xhr.ontimeout = httpRequest.customData.ontimeout; xhr.send(); - httpLoaderRequest.response = xhr; + httpRequest.customData.abort = abort.bind(this); + return true; + } + + function abort() { + xhr.onloadend = xhr.onerror = xhr.onprogress = null; // Ignore events from aborted requests. + xhr.abort(); } - function abort(request) { - const x = request.response; - x.onloadend = x.onerror = x.onprogress = undefined; //Ignore events from aborted requests. - x.abort(); + function getXhr() { + return xhr } instance = { load, - abort + abort, + getXhr }; return instance; diff --git a/src/streaming/rules/abr/BolaRule.js b/src/streaming/rules/abr/BolaRule.js index 887f324a87..8cdbd805fe 100644 --- a/src/streaming/rules/abr/BolaRule.js +++ b/src/streaming/rules/abr/BolaRule.js @@ -418,7 +418,7 @@ function BolaRule(config) { * @private */ function _onFragmentLoadingAbandoned(e) { - if (e) { + if (e && bolaStateDict[e.streamId]) { const bolaState = bolaStateDict[e.streamId][e.mediaType]; if (bolaState && bolaState.state !== BOLA_STATE_ONE_BITRATE) { // deflate placeholderBuffer - note that we want to be conservative when abandoning diff --git a/src/streaming/utils/RequestModifier.js b/src/streaming/utils/RequestModifier.js deleted file mode 100644 index 5381d8120e..0000000000 --- a/src/streaming/utils/RequestModifier.js +++ /dev/null @@ -1,71 +0,0 @@ -/** - * The copyright in this software is being made available under the BSD License, - * included below. This software may be subject to other third party and contributor - * rights, including patent rights, and no such rights are granted under this license. - * - * Copyright (c) 2013, Dash Industry Forum. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * * Neither the name of Dash Industry Forum nor the names of its - * contributors may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -import FactoryMaker from '../../core/FactoryMaker.js'; - -export function modifyRequest(httpRequest, requestModifier) { - const request = { - url: httpRequest.url, - method: httpRequest.method, - headers: Object.assign({}, httpRequest.headers), - credentials: httpRequest.withCredentials ? 'include' : undefined, - }; - - return Promise.resolve(requestModifier.modifyRequest(request)) - .then(() => - Object.assign(httpRequest, request, { withCredentials: request.credentials === 'include' }) - ); -} - -function RequestModifier() { - - let instance; - - function modifyRequestURL(url) { - return url; - } - - // eslint-disable-next-line no-unused-vars - function modifyRequestHeader(request, { url }) { - return request; - } - - instance = { - modifyRequest: null, - modifyRequestURL: modifyRequestURL, - modifyRequestHeader: modifyRequestHeader - }; - - return instance; -} - -RequestModifier.__dashjs_factory_name = 'RequestModifier'; -export default FactoryMaker.getSingletonFactory(RequestModifier); diff --git a/src/streaming/utils/SegmentResponseModifier.js b/src/streaming/utils/SegmentResponseModifier.js deleted file mode 100644 index 3ce63fbdc0..0000000000 --- a/src/streaming/utils/SegmentResponseModifier.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * The copyright in this software is being made available under the BSD License, - * included below. This software may be subject to other third party and contributor - * rights, including patent rights, and no such rights are granted under this license. - * - * Copyright (c) 2013, Dash Industry Forum. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation and/or - * other materials provided with the distribution. - * * Neither the name of Dash Industry Forum nor the names of its - * contributors may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -import FactoryMaker from '../../core/FactoryMaker.js'; - -function SegmentResponseModifier() { - - let instance; - - function modifyResponseAsync(chunk) { - return Promise.resolve(chunk); - } - - instance = { - modifyResponseAsync - }; - - return instance; -} - -SegmentResponseModifier.__dashjs_factory_name = 'SegmentResponseModifier'; -export default FactoryMaker.getSingletonFactory(SegmentResponseModifier); diff --git a/test/unit/dash.SegmentBaseLoader.js b/test/unit/dash.SegmentBaseLoader.js index db6f5feff5..9866720de8 100644 --- a/test/unit/dash.SegmentBaseLoader.js +++ b/test/unit/dash.SegmentBaseLoader.js @@ -2,7 +2,6 @@ import SegmentBaseLoader from '../../src/dash/SegmentBaseLoader.js'; import EventBus from '../../src/core/EventBus.js'; import Events from '../../src/core/events/Events.js'; import Errors from '../../src/core/errors/Errors.js'; -import RequestModifier from '../../src/streaming/utils/RequestModifier.js'; import ErrorHandlerMock from './mocks/ErrorHandlerMock.js'; import MediaPlayerModelMock from './mocks/MediaPlayerModelMock.js'; import DashMetricsMock from './mocks/DashMetricsMock.js'; @@ -27,8 +26,7 @@ describe('SegmentBaseLoader', function () { debug: new DebugMock(), eventBus: eventBus, events: Events, - errors: Errors, - requestModifier: RequestModifier(context).getInstance() + errors: Errors }); segmentBaseLoader.initialize(); }); diff --git a/test/unit/dash.WebSegmentBaseLoader.js b/test/unit/dash.WebSegmentBaseLoader.js index 8396293f04..2dbd336bfa 100644 --- a/test/unit/dash.WebSegmentBaseLoader.js +++ b/test/unit/dash.WebSegmentBaseLoader.js @@ -3,7 +3,6 @@ import Constants from '../../src/streaming/constants/Constants.js'; import EventBus from '../../src/core/EventBus.js'; import Events from '../../src/core/events/Events.js'; import Errors from '../../src/core/errors/Errors.js'; -import RequestModifier from '../../src/streaming/utils/RequestModifier.js'; import ErrorHandlerMock from './mocks/ErrorHandlerMock.js'; import MediaPlayerModelMock from './mocks/MediaPlayerModelMock.js'; import DashMetricsMock from './mocks/DashMetricsMock.js'; @@ -57,8 +56,7 @@ describe('WebmSegmentBaseLoader', function () { debug: new DebugMock(), eventBus: eventBus, events: Events, - errors: Errors, - requestModifier: RequestModifier(context).getInstance() + errors: Errors }); webmSegmentBaseLoader.initialize(); }); @@ -76,7 +74,7 @@ describe('WebmSegmentBaseLoader', function () { .catch((e) => { done(e); }); - self.requests[0].respond(200); + setTimeout(() => self.requests[0].respond(200), 1) }); it('should trigger SEGMENTS_LOADED event with an error when loadSegments function is called without representation parameter', function (done) { @@ -93,7 +91,7 @@ describe('WebmSegmentBaseLoader', function () { done(e); }); - self.requests[0].respond(200); + setTimeout(() => self.requests[0].respond(200), 1) }); }); }); diff --git a/test/unit/streaming.net.HTTPLoader.js b/test/unit/streaming.net.HTTPLoader.js index 4999d9a129..f7fc997d4c 100644 --- a/test/unit/streaming.net.HTTPLoader.js +++ b/test/unit/streaming.net.HTTPLoader.js @@ -1,5 +1,4 @@ import HTTPLoader from '../../src/streaming/net/HTTPLoader.js'; -import RequestModifier from '../../src/streaming/utils/RequestModifier.js'; import Errors from '../../src/core/errors/Errors.js'; import ErrorHandler from '../../src/streaming/utils/ErrorHandler.js'; import DashMetrics from '../../src/dash/DashMetrics.js'; @@ -14,7 +13,6 @@ const context = {}; let errHandler; let dashMetrics; -let requestModifier; let mediaPlayerModelMock; let httpLoader; let settings = Settings(context).getInstance(); @@ -27,7 +25,6 @@ describe('HTTPLoader', function () { mediaPlayerModelMock = new MediaPlayerModelMock(); errHandler = ErrorHandler(context).getInstance(); dashMetrics = DashMetrics(context).getInstance(); - requestModifier = RequestModifier(context).getInstance(); }); beforeEach(function () { @@ -61,7 +58,6 @@ describe('HTTPLoader', function () { httpLoader = HTTPLoader(context).create({ errHandler: errHandler, dashMetrics: dashMetrics, - requestModifier: requestModifier, mediaPlayerModel: mediaPlayerModelMock, errors: Errors }); @@ -72,9 +68,10 @@ describe('HTTPLoader', function () { type: HTTPRequest.MEDIA_SEGMENT_TYPE, availabilityTimeComplete: false }, success: callbackSucceeded, complete: callbackCompleted, error: callbackError + }).then(() => { + expect(self.requests.length).to.equal(1); + self.requests[0].respond(200); }); - expect(self.requests.length).to.equal(1); - self.requests[0].respond(200); }); it('should use XHRLoader and call success and complete callback when load is called successfully', () => { @@ -86,7 +83,6 @@ describe('HTTPLoader', function () { httpLoader = HTTPLoader(context).create({ errHandler: errHandler, dashMetrics: dashMetrics, - requestModifier: requestModifier, mediaPlayerModel: mediaPlayerModelMock, errors: Errors }); @@ -96,12 +92,13 @@ describe('HTTPLoader', function () { success: callbackSucceeded, complete: callbackCompleted, error: callbackError + }).then(() => { + expect(self.requests.length).to.equal(1); + self.requests[0].respond(200); + sinon.assert.calledOnce(callbackSucceeded); + sinon.assert.calledOnce(callbackCompleted); + expect(callbackSucceeded.calledBefore(callbackCompleted)).to.be.true; // jshint ignore:line }); - expect(self.requests.length).to.equal(1); - self.requests[0].respond(200); - sinon.assert.calledOnce(callbackSucceeded); - sinon.assert.calledOnce(callbackCompleted); - expect(callbackSucceeded.calledBefore(callbackCompleted)).to.be.true; // jshint ignore:line }); it('should use XHRLoader and call error and complete callback when load is called with error', () => { @@ -113,7 +110,6 @@ describe('HTTPLoader', function () { httpLoader = HTTPLoader(context).create({ errHandler: errHandler, dashMetrics: dashMetrics, - requestModifier: requestModifier, mediaPlayerModel: mediaPlayerModelMock, errors: Errors }); @@ -123,13 +119,14 @@ describe('HTTPLoader', function () { success: callbackSucceeded, complete: callbackCompleted, error: callbackError + }).then(() => { + expect(self.requests.length).to.equal(1); + setTimeout(() => self.requests[0].respond(404), 1); + sinon.assert.calledOnce(callbackError); + sinon.assert.calledOnce(callbackCompleted); + sinon.assert.notCalled(callbackSucceeded); + expect(callbackError.calledBefore(callbackCompleted)).to.be.true; // jshint ignore:line }); - expect(self.requests.length).to.equal(1); - self.requests[0].respond(404); - sinon.assert.calledOnce(callbackError); - sinon.assert.calledOnce(callbackCompleted); - sinon.assert.notCalled(callbackSucceeded); - expect(callbackError.calledBefore(callbackCompleted)).to.be.true; // jshint ignore:line }); it('should use XHRLoader if it is not a MEDIA_SEGMENT_TYPE request even if availabilityTimeComplete is set to false and it is an arraybuffer request', () => { @@ -141,7 +138,6 @@ describe('HTTPLoader', function () { httpLoader = HTTPLoader(context).create({ errHandler: errHandler, dashMetrics: dashMetrics, - requestModifier: requestModifier, mediaPlayerModel: mediaPlayerModelMock, errors: Errors }); @@ -152,9 +148,10 @@ describe('HTTPLoader', function () { type: HTTPRequest.INIT_SEGMENT_TYPE, availabilityTimeComplete: false }, success: callbackSucceeded, complete: callbackCompleted, error: callbackError + }).then(() => { + expect(self.requests.length).to.equal(1); + self.requests[0].respond(200); }); - expect(self.requests.length).to.equal(1); - self.requests[0].respond(200); }); }); diff --git a/test/unit/streaming.net.XHRLoader.js b/test/unit/streaming.net.XHRLoader.js index be2529bee2..05063f0c81 100644 --- a/test/unit/streaming.net.XHRLoader.js +++ b/test/unit/streaming.net.XHRLoader.js @@ -1,20 +1,14 @@ import XHRLoader from '../../src/streaming/net/XHRLoader.js'; -import RequestModifier from '../../src/streaming/utils/RequestModifier.js'; import {expect} from 'chai'; import sinon from 'sinon'; const context = {}; -let requestModifier; let xhrLoader; describe('XHRLoader', function () { - beforeEach(function () { - requestModifier = RequestModifier(context).getInstance(); - }); - beforeEach(function () { window.XMLHttpRequest = sinon.useFakeXMLHttpRequest(); @@ -38,17 +32,17 @@ describe('XHRLoader', function () { const callbackError = sinon.spy(); const callbackAbort = sinon.spy(); - xhrLoader = XHRLoader(context).create({ - requestModifier: requestModifier - }); + xhrLoader = XHRLoader(context).create({}); const request = { request: {}, - onload: callbackSucceeded, - onloadend: callbackCompleted, - onerror: callbackError, - onabort: callbackAbort + customData: { + onload: callbackSucceeded, + onloadend: callbackCompleted, + onerror: callbackError, + onabort: callbackAbort + } }; - xhrLoader.load(request); + xhrLoader.load(request, {}); expect(self.requests.length).to.equal(1); self.requests[0].respond(200); sinon.assert.notCalled(callbackError); @@ -64,17 +58,16 @@ describe('XHRLoader', function () { const callbackCompleted = sinon.spy(); const callbackError = sinon.spy(); const callbackAbort = sinon.spy(); - xhrLoader = XHRLoader(context).create({ - requestModifier: requestModifier - }); + xhrLoader = XHRLoader(context).create({}); const request = { - request: {}, - onload: callbackSucceeded, - onloadend: callbackCompleted, - onerror: callbackError, - onabort: callbackAbort + customData: { + onload: callbackSucceeded, + onloadend: callbackCompleted, + onerror: callbackError, + onabort: callbackAbort + } }; - xhrLoader.load(request); + xhrLoader.load(request, {}); expect(self.requests.length).to.equal(1); self.requests[0].respond(404); sinon.assert.notCalled(callbackError); @@ -90,19 +83,18 @@ describe('XHRLoader', function () { const callbackError = sinon.spy(); const callbackAbort = sinon.spy(); - xhrLoader = XHRLoader(context).create({ - requestModifier: requestModifier - }); + xhrLoader = XHRLoader(context).create({}); const request = { - request: {}, - onload: callbackSucceeded, - onloadend: callbackCompleted, - onerror: callbackError, - onabort: callbackAbort + customData: { + onload: callbackSucceeded, + onloadend: callbackCompleted, + onerror: callbackError, + onabort: callbackAbort + } }; - xhrLoader.load(request); - xhrLoader.abort(request); + xhrLoader.load(request, {}); + xhrLoader.abort(); sinon.assert.notCalled(callbackError); sinon.assert.notCalled(callbackSucceeded); @@ -117,17 +109,16 @@ describe('XHRLoader', function () { const callbackCompleted = sinon.spy(); const callbackError = sinon.spy(); const callbackAbort = sinon.spy(); - xhrLoader = XHRLoader(context).create({ - requestModifier: requestModifier - }); + xhrLoader = XHRLoader(context).create({}); const request = { - request: {}, - onload: callbackSucceeded, - onloadend: callbackCompleted, - onerror: callbackError, - onabort: callbackAbort + customData: { + onload: callbackSucceeded, + onloadend: callbackCompleted, + onerror: callbackError, + onabort: callbackAbort + } }; - xhrLoader.load(request); + xhrLoader.load(request, {}); expect(self.requests.length).to.equal(1); self.requests[0].error(); sinon.assert.calledOnce(callbackError); @@ -138,14 +129,13 @@ describe('XHRLoader', function () { }); it('should set timeout on the sending XHR request', () => { - xhrLoader = XHRLoader(context).create({ - requestModifier: requestModifier - }); + xhrLoader = XHRLoader(context).create({}); const request = { - request: {}, - timeout: 100 + timeout: 100, + customData: { + } }; - xhrLoader.load(request); - expect(request.response.timeout).to.be.equal(100); + xhrLoader.load(request, {}); + expect(xhrLoader.getXhr().timeout).to.be.equal(100); }); });