From 9585d9a3863d43607a33722887bf374145a10694 Mon Sep 17 00:00:00 2001 From: Heemanshu Suri Date: Tue, 30 Apr 2024 21:14:24 +0000 Subject: [PATCH] (WIP) Adding Header Validation tool and github workflow --- .github/workflows/build.yml | 27 + Makefile | 24 + header-validation/.gitignore | 2 + header-validation/index.html | 65 + header-validation/package-lock.json | 1582 +++++++++++++++++++++ header-validation/package.json | 18 + header-validation/src/base.js | 361 +++++ header-validation/src/index.js | 82 ++ header-validation/src/source_tests.js | 1469 +++++++++++++++++++ header-validation/src/trigger_tests.js | 61 + header-validation/src/validate_source.js | 87 ++ header-validation/src/validate_test.js | 46 + header-validation/src/validate_trigger.js | 41 + header-validation/style.css | 45 + header-validation/webpack.config.js | 9 + 15 files changed, 3919 insertions(+) create mode 100644 .github/workflows/build.yml create mode 100644 Makefile create mode 100644 header-validation/.gitignore create mode 100644 header-validation/index.html create mode 100644 header-validation/package-lock.json create mode 100644 header-validation/package.json create mode 100644 header-validation/src/base.js create mode 100644 header-validation/src/index.js create mode 100644 header-validation/src/source_tests.js create mode 100644 header-validation/src/trigger_tests.js create mode 100644 header-validation/src/validate_source.js create mode 100644 header-validation/src/validate_test.js create mode 100644 header-validation/src/validate_trigger.js create mode 100644 header-validation/style.css create mode 100644 header-validation/webpack.config.js diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..05836c0 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,27 @@ +name: Build +on: + pull_request: + branches: + - main + paths: + - 'Makefile' + push: + branches: + - main + paths: + - 'Makefile' + - 'header-validation/**' +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Build + run: make + - name: Deploy + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./out diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5255a43 --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +SHELL = /bin/bash +OUT_DIR ?= out + +.PHONY: all clean validator +all: validator + +validator: $(OUT_DIR)/validate-headers.html $(OUT_DIR)/validate-headers.js + +$(OUT_DIR)/validate-headers.html: header-validation/index.html $(OUT_DIR) + @ cp $< $@ + +$(OUT_DIR)/validate-headers.js: header-validation/dist/main.js $(OUT_DIR) + @ cp $< $@ + +$(OUT_DIR): + @ mkdir -p $@ + +header-validation/dist/main.js: header-validation/package.json header-validation/src/*.js + @ npm ci --prefix ./header-validation + @ npm run build --prefix ./header-validation + +clean: + @ rm -rf $(OUT_DIR) + diff --git a/header-validation/.gitignore b/header-validation/.gitignore new file mode 100644 index 0000000..de4d1f0 --- /dev/null +++ b/header-validation/.gitignore @@ -0,0 +1,2 @@ +dist +node_modules diff --git a/header-validation/index.html b/header-validation/index.html new file mode 100644 index 0000000..fa53982 --- /dev/null +++ b/header-validation/index.html @@ -0,0 +1,65 @@ + + +Attribution Reporting Header Validation + + + + + + +

Attribution Reporting Header Validation

+
+ + +
+ Value + +
+ + +
+ + + + \ No newline at end of file diff --git a/header-validation/package-lock.json b/header-validation/package-lock.json new file mode 100644 index 0000000..c294eb5 --- /dev/null +++ b/header-validation/package-lock.json @@ -0,0 +1,1582 @@ +{ + "name": "header-validation", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "header-validation", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "js-loader": "^0.1.1", + "webpack": "^5.89.0", + "webpack-cli": "^5.1.4" + } + }, + "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.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "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.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@types/eslint": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.1.tgz", + "integrity": "sha512-18PLWRzhy9glDQp3+wOgfLYRWlhgX0azxgJ63rdpoUHyrC9z0f5CkFburjQx4uD7ZCruw85ZtMt6K+L+R8fLJQ==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.10.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.7.tgz", + "integrity": "sha512-fRbIKb8C/Y2lXxB5eVMj4IU7xpdox0Lh8bUPEdtLysaylsml1hOOx1+STloRs/B9nf7C6kPRmmg/V7aQW7usNg==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "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.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", + "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.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", + "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.5", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", + "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/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "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/browserslist": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", + "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", + "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": { + "caniuse-lite": "^1.0.30001565", + "electron-to-chromium": "^1.4.601", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "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/call-bind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001576", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001576.tgz", + "integrity": "sha512-ff5BdakGe2P3SQsMsiqmt1Lc8221NR1VzHj5jXN5vBny9A6fpze94HiVV/n7XRosOlsShJcvMv5mdnpjOGCEgg==", + "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/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/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/connect": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/connect/-/connect-0.5.8.tgz", + "integrity": "sha512-N/eXBtibhXlXU/M3mYaIaTAg9OPx0560l48h+kxbuY7FQ4+6ByrJLTZe6q//Kw7wBTy1d4eQGW3fqBDMLoK6/g==", + "deprecated": "connect 0.x series is deprecated", + "dev": true, + "dependencies": { + "qs": ">= 0.0.1" + }, + "engines": { + "node": ">= 0.2.5" + } + }, + "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/define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.624", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.624.tgz", + "integrity": "sha512-w9niWuheXjz23vezH3w90n9KKcHe0UkhTfJ+rXJkuGGogHyQbQ7KS1x0a8ER4LbI3ljFS/gqxKh1TidNXDMHOg==", + "dev": true + }, + "node_modules/enhanced-resolve": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/envinfo": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.0.tgz", + "integrity": "sha512-G9/6xF1FPbIw0TtalAMaVPpiq2aDEuKLXM314jPVAO9r2fo2a4BLqMNkmRS7O/xPPZ+COAhGIz3ETvHEV3eUcg==", + "dev": true, + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", + "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==", + "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/eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==", + "dev": true, + "engines": { + "node": "> 0.1.90" + } + }, + "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/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/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-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/has-property-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/js-loader": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/js-loader/-/js-loader-0.1.1.tgz", + "integrity": "sha512-3QBYVT2BkoyzBVYjPBiJ1w0Bpclhww+SH9OdObgODb1KxjtYyjb4MH3kwGqZVuGBIQK2lmPJkFL2ohouMQzQUA==", + "dev": true, + "dependencies": { + "connect": "=0.5.8", + "temp": "=0.2.0", + "uglify-js": "=0.0.4", + "vows": "=0.5.6" + }, + "bin": { + "jsloader": "bin/jsloader" + }, + "engines": { + "node": "*" + } + }, + "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/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/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.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/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.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.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/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "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/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/set-function-length": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", + "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.1", + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-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": "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/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/temp": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.2.0.tgz", + "integrity": "sha512-YDBo1L4ZLi2+8u7rcY8asynPBwrXuNQ97sdemNwv/v8OJLCj/duAiuGYMtFIMUNKZG4zO1O5oQD7u+Y0c2O6ZA==", + "dev": true, + "engines": [ + "node >=0.1.90" + ] + }, + "node_modules/terser": { + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.26.0.tgz", + "integrity": "sha512-dytTGoE2oHgbNV9nTzgBEPaqAWvcJNl66VZ0BkJqlvp71IjO8CxdBx/ykCNb47cLnCmCvRZ6ZR0tLkqvZCdVBQ==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "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.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.20", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" + }, + "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/uglify-js": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-0.0.4.tgz", + "integrity": "sha512-cGiAIcNECVjqL3wNiWiBctBJr94T6WS+q0c/H/I+pGsAQ/+8ZKbJxw9GJiaL+ZYbGDU275Shz+h2Gq4S2Oc35Q==", + "dev": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": "*" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "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/vows": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/vows/-/vows-0.5.6.tgz", + "integrity": "sha512-IGLEetjz/nPKHYPlA3LTGMxj6nbfsyZ3CwFTR6hzE6zRLLY7yu5ya4v9ymY7cit9wnogDv6Cj12nU/myCeDwgQ==", + "dev": true, + "dependencies": { + "eyes": ">=0.1.6" + }, + "bin": { + "vows": "bin/vows" + }, + "engines": { + "node": ">=0.2.6" + } + }, + "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.89.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", + "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==", + "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.9.0", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.15.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.2.0", + "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.4", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", + "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", + "dev": true, + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", + "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.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "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 + } + } +} diff --git a/header-validation/package.json b/header-validation/package.json new file mode 100644 index 0000000..06b27ef --- /dev/null +++ b/header-validation/package.json @@ -0,0 +1,18 @@ +{ + "name": "header-validation", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "build": "webpack", + "test": "node --test ./src/validate_test.js" + }, + "type": "commonjs", + "author": "", + "license": "ISC", + "devDependencies": { + "js-loader": "^0.1.1", + "webpack": "^5.89.0", + "webpack-cli": "^5.1.4" + } +} diff --git a/header-validation/src/base.js b/header-validation/src/base.js new file mode 100644 index 0000000..8e65a30 --- /dev/null +++ b/header-validation/src/base.js @@ -0,0 +1,361 @@ +const uint64Regex = /^[0-9]+$/; +const int64Regex = /^-?[0-9]+$/; + +class State { + constructor() { + this.path = []; + this.errors = []; + this.warnings = []; + } + + error(msg) { + this.errors.push({ + path: [...this.path], + msg, + formattedError: `${msg}: \`${[...this.path]}\``, + }); + } + + warning(msg) { + this.warnings.push({ + path: [...this.path], + msg, + formattedWarning: `${msg}: \`${[...this.path]}\``, + }); + } + + scope(scope, fn) { + this.path.push(scope); + fn(); + this.path.pop(); + } + + result() { + return {errors: this.errors, warnings: this.warnings}; + } + + validate(obj, checks) { + Object.entries(checks).forEach(([key, check]) => { + this.scope(key, () => check(this, key, obj)); + }); + } +} + +function getDataType(value) { + if (value === null) { + return 'null'; + } + if (typeof value === 'boolean') { + return 'boolean' + } + if (typeof value === 'number') { + return 'number' + } + if (typeof value === 'string') { + return 'string' + } + if (Array.isArray(value)) { + return 'array' + } + if (value !== null && typeof value === 'object' && value.constructor === Object) { + return 'object' + } + return '' +} + + +function required(fn = () => {}) { + return (state, key, object) => { + if (key in object) { + fn(state, object[key]); + return; + } + state.error('Missing required field'); + return; + } +} + +function exclude(fn = () => {}) { + return (state, key, object) => { + if (!(key in object)) { + fn(state, object[key]); + return; + } + state.error('Field should be excluded'); + return; + } +} + +function optional(fn = () => {}) { + return (state, key, object) => { + if (key in object) { + fn(state, object[key]); + return; + } + } +} + +function string(fn = () => {}) { + return (state, value) => { + if (getDataType(value) == 'string') { + fn(state, value); + return; + } + state.error('Must be a string'); + } +} + +function optString(fn = () => {}) { + return (state, value) => { + if (getDataType(value) == 'string') { + fn(state, value); + return; + } else if (getDataType(value) != 'null') { + fn(state, String(value)); + return; + } + state.error('Must be a string or able to cast to string'); + } +} + +function optStringFallback(fn = () => {}) { + return (state, value) => { + if (getDataType(value) == 'string') { + fn(state, value); + } else if (getDataType(value) != 'null') { + fn(state, String(value)); + } else { + fn(state, ""); + } + } +} + +function object(fn = () => {}) { + return (state, value) => { + if (getDataType(value) == 'object') { + fn(state, value); + return; + } + state.error('Must be an object'); + } +} + +function objectWithFlags(fn = () => {}, flags) { + return (state, value) => { + if (getDataType(value) == 'object') { + fn(state, value, flags); + return; + } + state.error('Must be an object'); + } +} + +function array(fn = () => {}) { + return (state, value) => { + if (getDataType(value) == 'array') { + fn(state, value); + return; + } + state.error('Must be an array'); + } +} + +function boolean(fn = () => {}) { + return (state, value) => { + if (getDataType(value) == 'boolean') { + fn(state, value); + return; + } + state.error('Must be a boolean'); + } +} + +function optBoolean(fn = () => {}) { + return (state, value) => { + if (getDataType(value) == 'boolean') { + fn(state, value); + return; + } else if (getDataType(value) == 'string') { + if (value.toLowerCase() == 'true') { + fn(state, true); + return; + } else if (value.toLowerCase() == 'false') { + fn(state, false); + return; + } + } + state.error('Must be a boolean'); + } +} + +function optBooleanFallback(fn = () => {}) { + return (state, value) => { + if (getDataType(value) == 'boolean') { + fn(state, value); + return; + } else if (getDataType(value) == 'string') { + if (value == 'true') { + fn(state, true); + return; + } else if (value == 'false') { + fn(state, false); + return; + } + } + fn(state, false); + return; + } +} + +function isValidFilterData(state, value, flags) { + if (flags["feature-ara-parsing-alignment-v1"] && 'source_type' in value) { + state.error('filter: source_type is not allowed'); + return; + } + if (Object.keys(value).length > flags["max_attribution_filters"]) { + state.error('exceeded max attribution filters'); + return; + } + for (key in value) { + let keyByteSize = new Blob([key]).size; + if (keyByteSize > flags["max_bytes_per_attribution_filter_string"]) { + state.error('exceeded max bytes per attribution filter string'); + return; + } + if (key === "_lookback_window" && flags["feature-lookback-window-filter"]) { + state.error('filter: _lookback_window is not allowed'); + return; + } + if (key.startsWith("_")) { + state.error('filter can not start with underscore'); + return; + } + + filterValues = value[key]; + if (!Array.isArray(filterValues)) { + state.error('filter value must be an array'); + return; + } + if(filterValues.length > flags["max_values_per_attribution_filter"]) { + state.error('exceeded max values per attribution filter'); + return; + } + for(let i = 0; i < filterValues.length; i++) { + let filterValue = filterValues[i]; + if (getDataType(filterValue) != 'string') { + state.error('filter values must be strings'); + return; + } + let valueByteSize = new Blob([filterValue]).size; + if (valueByteSize > flags["max_bytes_per_attribution_filter_string"]) { + state.error('exceeded max bytes per attribution filter value string'); + return; + } + } + } +} + +const uint64 = string((state, value) => { + if (!uint64Regex.test(value)) { + state.error(`Must be a uint64 (must match ${uint64Regex})`); + return; + } + + const max = 2n ** 64n - 1n; + if (BigInt(value) > max) { + state.error('Must fit in an unsigned 64-bit integer') + } +}); + +const optUint64 = optString((state, value) => { + if (!uint64Regex.test(value)) { + state.error(`Must be a uint64 (must match ${uint64Regex})`); + return; + } + + const max = 2n ** 64n - 1n; + if (BigInt(value) > max) { + state.error('Must fit in an unsigned 64-bit integer') + } +}); + +const int64 = string((state, value) => { + if (!int64Regex.test(value)) { + state.error(`Must be an int64 (must match ${int64Regex})`); + return; + } + + const max = 2n ** (64n - 1n) - 1n; + const min = (-2n) ** (64n - 1n); + value = BigInt(value); + if (value < min || value > max) { + state.error('Must fit in a signed 64-bit integer') + } +}); + +const optInt64 = optString((state, value) => { + if (!int64Regex.test(value)) { + state.error(`Must be an int64 (must match ${int64Regex})`); + return; + } + + const max = 2n ** (64n - 1n) - 1n; + const min = (-2n) ** (64n - 1n); + value = BigInt(value); + if (value < min || value > max) { + state.error('Must fit in a signed 64-bit integer') + } +}); + +const isValidUri = optString((state, value) => { + try { + let url = new URL(value); + } catch (err) { + state.error('URI is not valid'); + } +}); + +const isValidAppDestinationHost = optString((state, value) => { + let url; + try { + url = new URL(value); + } catch (err) { + return + } + + if (url.protocol != 'android-app:') { + state.error('app URI host is invalid'); + } +}); + +const isValidTriggerMatchingData = optString((state, value) => { + if(value.toLowerCase() != 'modulus' && value.toLowerCase() != 'exact') { + state.error("value must be 'exact' or 'modulus' (case-insensitive)"); + return; + } +}); + + +module.exports = { + State, + boolean, + optBoolean, + optBooleanFallback, + uint64, + optUint64, + int64, + optInt64, + optString, + optStringFallback, + object, + objectWithFlags, + array, + required, + optional, + exclude, + getDataType, + isValidUri, + isValidAppDestinationHost, + isValidFilterData, + isValidTriggerMatchingData +}; diff --git a/header-validation/src/index.js b/header-validation/src/index.js new file mode 100644 index 0000000..0c7a974 --- /dev/null +++ b/header-validation/src/index.js @@ -0,0 +1,82 @@ +const {validateSource} = require('./validate_source') +const {validateTrigger} = require('./validate_trigger') + +// Initialize page elements +const validationForm = document.getElementById('validation-form') +const inputTextbox = document.getElementById('input-text') +const headerOptions = validationForm.elements.namedItem('header') +const sourceTypeOptionsContainer = document.getElementById('source-type-options') +const sourceTypeOptions = validationForm.elements.namedItem('source-type') +const registrationOptions = validationForm.elements.namedItem('registration-type') +const featureAraParsingAlignmentV1 = validationForm.elements.namedItem('feature-ara-parsing-alignment-v1') +const featureLookbackWindowFilter = validationForm.elements.namedItem('feature-lookback-window-filter') +const featureTriggerDataMatching = validationForm.elements.namedItem('feature-trigger-data-matching') +const copyButton = document.getElementById('linkify') +const errorList = document.getElementById('errors') +const warningList = document.getElementById('warnings') +const noteList = document.getElementById('notes') +const successDiv = document.getElementById('success') + +// Add listeners +validationForm.addEventListener('input', validate) +copyButton.addEventListener('click', copyLink) + + +// Define functions +async function copyLink() { + const url = new URL(location.toString()) + url.search = '' + url.searchParams.set('header', headerOptions.value) + url.searchParams.set('json', inputTextbox.value) + if (url.searchParams.get('header') === 'source') { + url.searchParams.set('source-type', sourceTypeOptions.value) + } + url.searchParams.set('feature-ara-parsing-alignment-v1', featureAraParsingAlignmentV1.checked.toString()) + url.searchParams.set('feature-lookback-window-filter', featureLookbackWindowFilter.checked.toString()) + url.searchParams.set('feature-trigger-data-matching', featureTriggerDataMatching.checked.toString()) + await navigator.clipboard.writeText(url.toString()) +} + +function populateUIList(element, items) { + element.replaceChildren([]) + items.forEach(function (item) { + let listItem = document.createElement('li') + if (typeof item === "string") { + listItem.textContent = item + } else { + listItem.textContent = item.msg + ': ' + item.path + } + element.appendChild(listItem) + }) +} + +function validate() { + // Enable/Disable source type option based on header type + sourceTypeOptionsContainer.disabled = (headerOptions.value != 'source') + + // Fetch Flag Values + let flagValues = {} + flagValues['feature-ara-parsing-alignment-v1'] = featureAraParsingAlignmentV1.checked + flagValues['feature-lookback-window-filter'] = featureLookbackWindowFilter.checked + flagValues['feature-trigger-data-matching'] = featureTriggerDataMatching.checked + flagValues['max_attribution_filters'] = 50 + flagValues['max_bytes_per_attribution_filter_string'] = 25 + flagValues['max_values_per_attribution_filter'] = 50; + + // Validate input + let result; + if (sourceTypeOptionsContainer.disabled) { + result = validateTrigger(inputTextbox.value, registrationOptions.value, flagValues) + } else { + result = validateSource(inputTextbox.value, registrationOptions.value, flagValues) + } + + // Show results + successDiv.textContent = (result.errors.length === 0 && result.warnings.length === 0) ? 'The header is valid.' : '' + populateUIList(errorList, result.errors) + populateUIList(warningList, result.warnings) + populateUIList(noteList, []) +} + +// Load initial state of page +validate() \ No newline at end of file diff --git a/header-validation/src/source_tests.js b/header-validation/src/source_tests.js new file mode 100644 index 0000000..35e1dc7 --- /dev/null +++ b/header-validation/src/source_tests.js @@ -0,0 +1,1469 @@ +const sourceTestCases = [ + { + name: "App Destination Present | Valid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\"}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Null) App Destination | Invalid", + type: "app", + flags: {}, + json: "{\"destination\":null}", + result: { + valid: false, + errors: ["Must be a string or able to cast to string: `destination`"], + warnings: [] + } + }, + { + name: "App Destination Missing Scheme | Valid", + type: "app", + flags: {}, + json: "{\"destination\":\"com.myapps\"}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "App Destination Invalid Host | Invalid", + type: "app", + flags: {}, + json: "{\"destination\":\"http://com.myapps\"}", + result: { + valid: false, + errors: ["app URI host is invalid: `destination`"], + warnings: [] + } + }, + { + name: "Web Destination Present | Valid", + type: "app", + flags: {}, + json: "{\"web_destination\":\"https://web-destination.test\"}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "App and Web Destination Missing | Invalid", + type: "app", + flags: {}, + json: "{\"source_event_id\":\"1234\"}", + result: { + valid: false, + errors: ["At least one field must be present: `destination or web_destination`"], + warnings: [] + } + }, + { + name: "(String) Source Event ID + v1 Alignment| Valid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"source_event_id\":\"1234\"}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Non-String) Source Event ID + v1 Alignment | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"source_event_id\":1234}", + result: { + valid: false, + errors: ["Must be a string: `source_event_id`"], + warnings: [] + } + }, + { + name: "(Negative) Source Event ID + v1 Alignment | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"source_event_id\":\"-1234\"}", + result: { + valid: false, + errors: ["Must be a uint64 (must match /^[0-9]+$/): `source_event_id`"], + warnings: [] + } + }, + { + name: "(Non-Numeric) Source Event ID + v1 Alignment | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"source_event_id\":\"true\"}", + result: { + valid: false, + errors: ["Must be a uint64 (must match /^[0-9]+$/): `source_event_id`"], + warnings: [] + } + }, + { + name: "(String) Source Event ID | Valid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"source_event_id\":\"1234\"}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Non-String) Source Event ID | Valid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"source_event_id\":1234}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Null) Source Event ID | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"source_event_id\":null}", + result: { + valid: false, + errors: ["Must be a string or able to cast to string: `source_event_id`"], + warnings: [] + } + }, + { + name: "(Negative) Source Event ID | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"source_event_id\":\"-1234\"}", + result: { + valid: false, + errors: ["Must be a uint64 (must match /^[0-9]+$/): `source_event_id`"], + warnings: [] + } + }, + { + name: "(Non-Numeric) Source Event ID | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"source_event_id\":\"true\"}", + result: { + valid: false, + errors: ["Must be a uint64 (must match /^[0-9]+$/): `source_event_id`"], + warnings: [] + } + }, + { + name: "(String) Expiry + v1 Alignment| Valid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"expiry\":\"100\"}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Non-String) Expiry + v1 Alignment | Valid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"expiry\":100}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Null) Expiry + v1 Alignment | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"expiry\":null}", + result: { + valid: false, + errors: ["Must be a string or able to cast to string: `expiry`"], + warnings: [] + } + }, + { + name: "(Negative) Expiry + v1 Alignment | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"expiry\":\"-1234\"}", + result: { + valid: false, + errors: ["Must be a uint64 (must match /^[0-9]+$/): `expiry`"], + warnings: [] + } + }, + { + name: "(Non-Numeric) Expiry + v1 Alignment | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"expiry\":\"true\"}", + result: { + valid: false, + errors: ["Must be a uint64 (must match /^[0-9]+$/): `expiry`"], + warnings: [] + } + }, + { + name: "(String) Expiry | Valid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"expiry\":\"100\"}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Non-String) Expiry | Valid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"expiry\":100}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Null) Expiry | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"expiry\":null}", + result: { + valid: false, + errors: ["Must be a string or able to cast to string: `expiry`"], + warnings: [] + } + }, + { + name: "(Negative) Expiry | Valid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"expiry\":\"-1234\"}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Non-Numeric) Expiry | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"expiry\":\"true\"}", + result: { + valid: false, + errors: ["Must be an int64 (must match /^-?[0-9]+$/): `expiry`"], + warnings: [] + } + }, + { + name: "(String) Event Report Window + v1 Alignment| Valid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"event_report_window\":\"2000\"}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Non-String) Event Report Window + v1 Alignment | Valid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"event_report_window\":2000}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Null) Event Report Window + v1 Alignment | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"event_report_window\":null}", + result: { + valid: false, + errors: ["Must be a string or able to cast to string: `event_report_window`"], + warnings: [] + } + }, + { + name: "(Negative) Event Report Window + v1 Alignment | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"event_report_window\":\"-2000\"}", + result: { + valid: false, + errors: ["Must be a uint64 (must match /^[0-9]+$/): `event_report_window`"], + warnings: [] + } + }, + { + name: "(Non-Numeric) Event Report Window + v1 Alignment | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"event_report_window\":\"true\"}", + result: { + valid: false, + errors: ["Must be a uint64 (must match /^[0-9]+$/): `event_report_window`"], + warnings: [] + } + }, + { + name: "(String) Event Report Window | Valid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"event_report_window\":\"2000\"}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Non-String) Event Report Window | Valid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"event_report_window\":2000}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Null) Event Report Window | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"event_report_window\":null}", + result: { + valid: false, + errors: ["Must be a string or able to cast to string: `event_report_window`"], + warnings: [] + } + }, + { + name: "(Negative) Event Report Window | Valid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"event_report_window\":\"-2000\"}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Non-Numeric) Event Report Window | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"event_report_window\":\"true\"}", + result: { + valid: false, + errors: ["Must be an int64 (must match /^-?[0-9]+$/): `event_report_window`"], + warnings: [] + } + }, + { + name: "(String) Aggregatable Report Window + v1 Alignment| Valid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"aggregatable_report_window\":\"2000\"}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Non-String) Aggregatable Report Window + v1 Alignment | Valid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"aggregatable_report_window\":2000}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Null) Aggregatable Report Window + v1 Alignment | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"aggregatable_report_window\":null}", + result: { + valid: false, + errors: ["Must be a string or able to cast to string: `aggregatable_report_window`"], + warnings: [] + } + }, + { + name: "(Negative) Aggregatable Report Window + v1 Alignment | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"aggregatable_report_window\":\"-2000\"}", + result: { + valid: false, + errors: ["Must be a uint64 (must match /^[0-9]+$/): `aggregatable_report_window`"], + warnings: [] + } + }, + { + name: "(Non-Numeric) Aggregatable Report Window + v1 Alignment | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"aggregatable_report_window\":\"true\"}", + result: { + valid: false, + errors: ["Must be a uint64 (must match /^[0-9]+$/): `aggregatable_report_window`"], + warnings: [] + } + }, + { + name: "(String) Aggregatable Report Window | Valid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"aggregatable_report_window\":\"2000\"}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Non-String) Aggregatable Report Window | Valid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"aggregatable_report_window\":2000}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Null) Aggregatable Report Window | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"aggregatable_report_window\":null}", + result: { + valid: false, + errors: ["Must be a string or able to cast to string: `aggregatable_report_window`"], + warnings: [] + } + }, + { + name: "(Negative) Aggregatable Report Window | Valid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"aggregatable_report_window\":\"-2000\"}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Non-Numeric) Aggregatable Report Window | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"aggregatable_report_window\":\"true\"}", + result: { + valid: false, + errors: ["Must be an int64 (must match /^-?[0-9]+$/): `aggregatable_report_window`"], + warnings: [] + } + }, + { + name: "(String) Priority + v1 Alignment| Valid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"priority\":\"1\"}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Non-String) Priority + v1 Alignment | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"priority\":1}", + result: { + valid: false, + errors: ["Must be a string: `priority`"], + warnings: [] + } + }, + { + name: "(Negative) Priority + v1 Alignment | Valid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"priority\":\"-1\"}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Non-Numeric) Priority + v1 Alignment | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"priority\":\"true\"}", + result: { + valid: false, + errors: ["Must be an int64 (must match /^-?[0-9]+$/): `priority`"], + warnings: [] + } + }, + { + name: "(String) Priority | Valid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"priority\":\"1\"}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Non-String) Priority | Valid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"priority\":1}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Null) Priority | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"priority\":null}", + result: { + valid: false, + errors: ["Must be a string or able to cast to string: `priority`"], + warnings: [] + } + }, + { + name: "(Negative) Priority | Valid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"priority\":\"-1\"}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Non-Numeric) Priority | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"priority\":\"true\"}", + result: { + valid: false, + errors: ["Must be an int64 (must match /^-?[0-9]+$/): `priority`"], + warnings: [] + } + }, + { + name: "(Boolean) Debug Reporting | Valid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"debug_reporting\":true}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(String) Debug Reporting | Valid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"debug_reporting\":\"true\"}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Non-Boolean) Debug Reporting | Valid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"debug_reporting\":99}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Null) Debug Reporting | Valid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"debug_reporting\":null}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(String) Debug Key + v1 Alignment| Valid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"debug_key\":\"1000\"}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Non-String) Debug Key + v1 Alignment | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"debug_key\":1000}", + result: { + valid: false, + errors: ["Must be a string: `debug_key`"], + warnings: [] + } + }, + { + name: "(Negative) Debug Key + v1 Alignment | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"debug_key\":\"-1000\"}", + result: { + valid: false, + errors: ["Must be a uint64 (must match /^[0-9]+$/): `debug_key`"], + warnings: [] + } + }, + { + name: "(Non-Numeric) Debug Key + v1 Alignment | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"debug_key\":\"true\"}", + result: { + valid: false, + errors: ["Must be a uint64 (must match /^[0-9]+$/): `debug_key`"], + warnings: [] + } + }, + { + name: "(String) Debug Key | Valid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"debug_key\":\"1000\"}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Non-String) Debug Key | Valid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"debug_key\":1000}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Null) Debug Key | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"debug_key\":null}", + result: { + valid: false, + errors: ["Must be a string or able to cast to string: `debug_key`"], + warnings: [] + } + }, + { + name: "(Negative) Debug Key | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"debug_key\":\"-1000\"}", + result: { + valid: false, + errors: ["Must be a uint64 (must match /^[0-9]+$/): `debug_key`"], + warnings: [] + } + }, + { + name: "(Non-Numeric) Debug Key | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"debug_key\":\"true\"}", + result: { + valid: false, + errors: ["Must be a uint64 (must match /^[0-9]+$/): `debug_key`"], + warnings: [] + } + }, + { + name: "(String) Install Attribution Window | Valid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"install_attribution_window\":\"86300\"}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Non-String) Install Attribution Window | Valid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"install_attribution_window\":86300}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Null) Install Attribution Window | Invalid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"install_attribution_window\":null}", + result: { + valid: false, + errors: ["Must be a string or able to cast to string: `install_attribution_window`"], + warnings: [] + } + }, + { + name: "(Negative) Install Attribution Window | Valid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"install_attribution_window\":\"-86300\"}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Non-Numeric) Install Attribution Window | Invalid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"install_attribution_window\":\"true\"}", + result: { + valid: false, + errors: ["Must be an int64 (must match /^-?[0-9]+$/): `install_attribution_window`"], + warnings: [] + } + }, + { + name: "(String) Post Install Exclusivity Window | Valid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"post_install_exclusivity_window\":\"987654\"}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Non-String) Post Install Exclusivity Window | Valid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"post_install_exclusivity_window\":987654}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Null) Post Install Exclusivity Window | Invalid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"post_install_exclusivity_window\":null}", + result: { + valid: false, + errors: ["Must be a string or able to cast to string: `post_install_exclusivity_window`"], + warnings: [] + } + }, + { + name: "(Negative) Post Install Exclusivity Window | Valid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"post_install_exclusivity_window\":\"-987654\"}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Non-Numeric) Post Install Exclusivity Window | Invalid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"post_install_exclusivity_window\":\"true\"}", + result: { + valid: false, + errors: ["Must be an int64 (must match /^-?[0-9]+$/): `post_install_exclusivity_window`"], + warnings: [] + } + + }, + { + name: "(Object) Filter Data | Valid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"filter_data\":{}}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Non-Object) Filter Data | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"filter_data\":[\"A\"]}", + result: { + valid: false, + errors: ["Must be an object: `filter_data`"], + warnings: [] + } + }, + { + name: "('source_type' Filter is Present) Filter Data + v1 Alignment | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"filter_data\":{\"source_type\":[\"A\"]}}", + result: { + valid: false, + errors: ["filter: source_type is not allowed: `filter_data`"], + warnings: [] + } + }, + { + name: "('source_type' Filter is Present) Filter Data | Valid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"filter_data\":{\"source_type\":[\"A\"]}}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Number of Filters Limit Exceeded) Filter Data | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true, + "max_attribution_filters": 2, + }, + json: "{\"destination\":\"android-app://com.myapps\", \"filter_data\":{\"filter_1\":[\"A\"], \"filter_2\":[\"B\"], \"filter_3\":[\"C\"]}}", + result: { + valid: false, + errors: ["exceeded max attribution filters: `filter_data`"], + warnings: [] + } + }, + { + name: "(Filter String Byte Size Limit Exceeded) Filter Data | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true, + "max_attribution_filters": 2, + "max_bytes_per_attribution_filter_string": 25 + }, + json: "{\"destination\":\"android-app://com.myapps\", \"filter_data\":{\"ABCDEFGHIJKLMNOPQRSTUVWXYZ\":[\"A\"]}}", + result: { + valid: false, + errors: ["exceeded max bytes per attribution filter string: `filter_data`"], + warnings: [] + } + }, + { + name: "('_lookback_window' Filter is Present) Filter Data + Lookback Window Filter | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true, + "max_attribution_filters": 2, + "max_bytes_per_attribution_filter_string": 25, + "feature-lookback-window-filter": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"filter_data\":{\"_lookback_window\":[\"A\"]}}", + result: { + valid: false, + errors: ["filter: _lookback_window is not allowed: `filter_data`"], + warnings: [] + } + }, + { + name: "('_lookback_window' Filter is Present) Filter Data | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true, + "max_attribution_filters": 2, + "max_bytes_per_attribution_filter_string": 25, + "feature-lookback-window-filter": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"filter_data\":{\"_lookback_window\":[\"A\"]}}", + result: { + valid: false, + errors: ["filter can not start with underscore: `filter_data`"], + warnings: [] + } + }, + { + name: "(Filter String Starts with Underscore) Filter Data | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true, + "max_attribution_filters": 2, + "max_bytes_per_attribution_filter_string": 25, + "feature-lookback-window-filter": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"filter_data\":{\"filter_1\":[\"A\"], \"_filter_2\":[\"B\"]}}", + result: { + valid: false, + errors: ["filter can not start with underscore: `filter_data`"], + warnings: [] + } + }, + { + name: "(Non-Array Filter Value) Filter Data | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true, + "max_attribution_filters": 2, + "max_bytes_per_attribution_filter_string": 25, + "feature-lookback-window-filter": false + }, + json: "{\"destination\":\"android-app://com.myapps\", \"filter_data\":{\"filter_1\":{\"name\": \"A\"}}}", + result: { + valid: false, + errors: ["filter value must be an array: `filter_data`"], + warnings: [] + } + }, + { + name: "(Number of Values Per Filter Limit Exceeded) Filter Data | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true, + "max_attribution_filters": 2, + "max_bytes_per_attribution_filter_string": 25, + "feature-lookback-window-filter": false, + "max_values_per_attribution_filter": 2 + }, + json: "{\"destination\":\"android-app://com.myapps\", \"filter_data\":{\"filter_1\":[\"A\", \"B\", \"C\"]}}", + result: { + valid: false, + errors: ["exceeded max values per attribution filter: `filter_data`"], + warnings: [] + } + }, + { + name: "(Non-String Filter Value) Filter Data | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true, + "max_attribution_filters": 2, + "max_bytes_per_attribution_filter_string": 25, + "feature-lookback-window-filter": false, + "max_values_per_attribution_filter": 2 + }, + json: "{\"destination\":\"android-app://com.myapps\", \"filter_data\":{\"filter_1\":[\"A\", 2]}}", + result: { + valid: false, + errors: ["filter values must be strings: `filter_data`"], + warnings: [] + } + }, + { + name: "(Filter Value String Byte Size Limit Exceeded) Filter Data | Invalid", + type: "app", + flags: { + "feature-ara-parsing-alignment-v1": true, + "max_attribution_filters": 2, + "max_bytes_per_attribution_filter_string": 25, + "feature-lookback-window-filter": false, + "max_values_per_attribution_filter": 2 + }, + json: "{\"destination\":\"android-app://com.myapps\", \"filter_data\":{\"filter_1\":[\"A\", \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"]}}", + result: { + valid: false, + errors: ["exceeded max bytes per attribution filter value string: `filter_data`"], + warnings: [] + } + }, + { + name: "(String) Debug Ad ID | Valid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"debug_ad_id\":\"11756\"}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Non-String) Debug Ad ID | Valid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"debug_ad_id\":11756}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Null) Debug Ad ID | Valid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"debug_ad_id\":null}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(String) Debug Join Key | Valid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"debug_join_key\":\"66784\"}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Non-String) Debug Join Key | Valid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"debug_join_key\":66784}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Null) Debug Join Key | Valid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"debug_join_key\":null}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(String - Enum Value) Trigger Data Matching | Valid", + type: "app", + flags: { + "feature-trigger-data-matching": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"trigger_data_matching\":\"MODuLus\"}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(String - Unknown Value) Trigger Data Matching | Invalid", + type: "app", + flags: { + "feature-trigger-data-matching": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"trigger_data_matching\":\"invalid\"}", + result: { + valid: false, + errors: ["value must be 'exact' or 'modulus' (case-insensitive): `trigger_data_matching`"], + warnings: [] + } + }, + { + name: "(Non-String) Trigger Data Matching | Invalid", + type: "app", + flags: { + "feature-trigger-data-matching": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"trigger_data_matching\":true}", + result: { + valid: false, + errors: ["value must be 'exact' or 'modulus' (case-insensitive): `trigger_data_matching`"], + warnings: [] + } + }, + { + name: "(Null) Trigger Data Matching | Invalid", + type: "app", + flags: { + "feature-trigger-data-matching": true + }, + json: "{\"destination\":\"android-app://com.myapps\", \"trigger_data_matching\":null}", + result: { + valid: false, + errors: ["Must be a string or able to cast to string: `trigger_data_matching`"], + warnings: [] + } + }, + { + name: "(Boolean) Coarse Event Report Destinations | Valid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"coarse_event_report_destinations\":true}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(String) Coarse Event Report Destinations | Valid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"coarse_event_report_destinations\":\"truE\"}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Non-Boolean) Coarse Event Report Destinations | Invalid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"coarse_event_report_destinations\":99}", + result: { + valid: false, + errors: ["Must be a boolean: `coarse_event_report_destinations`"], + warnings: [] + } + }, + { + name: "(Null) Coarse Event Report Destinations | Invalid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"coarse_event_report_destinations\":null}", + result: { + valid: false, + errors: ["Must be a boolean: `coarse_event_report_destinations`"], + warnings: [] + } + }, + { + name: "(String) Shared Debug Keys | Valid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"shared_debug_keys\":\"100\"}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Non-String) Shared Debug Keys | Valid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"shared_debug_keys\":100}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Null) Shared Debug Keys | Invalid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"shared_debug_keys\":null}", + result: { + valid: false, + errors: ["Must be a string or able to cast to string: `shared_debug_keys`"], + warnings: [] + } + }, + { + name: "(Negative) Shared Debug Keys | Invalid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"shared_debug_keys\":\"-1234\"}", + result: { + valid: false, + errors: ["Must be a uint64 (must match /^[0-9]+$/): `shared_debug_keys`"], + warnings: [] + } + }, + { + name: "(Non-Numeric) Shared Debug Keys | Invalid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"shared_debug_keys\":\"true\"}", + result: { + valid: false, + errors: ["Must be a uint64 (must match /^[0-9]+$/): `shared_debug_keys`"], + warnings: [] + } + }, + { + name: "(Boolean) Drop Source If Installed | Valid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"drop_source_if_installed\":true}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(String) Drop Source If Installed | Valid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"drop_source_if_installed\":\"truE\"}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Non-Boolean) Drop Source If Installed | Invalid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"drop_source_if_installed\":99}", + result: { + valid: false, + errors: ["Must be a boolean: `drop_source_if_installed`"], + warnings: [] + } + }, + { + name: "(Null) Drop Source If Installed | Invalid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"drop_source_if_installed\":null}", + result: { + valid: false, + errors: ["Must be a boolean: `drop_source_if_installed`"], + warnings: [] + } + }, + { + name: "(Array) Shared Aggregation Keys | Invalid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"shared_aggregation_keys\":[]}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Non-Array) Shared Aggregation Keys | Invalid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"shared_aggregation_keys\":{}}", + result: { + valid: false, + errors: ["Must be an array: `shared_aggregation_keys`"], + warnings: [] + } + }, + { + name: "(Array) Shared Filter Data Keys | Invalid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"shared_filter_data_keys\":[]}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Non-Array) Shared Filter Data Keys | Invalid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\", \"shared_filter_data_keys\":{}}", + result: { + valid: false, + errors: ["Must be an array: `shared_filter_data_keys`"], + warnings: [] + } + } +] + +module.exports = { + sourceTestCases + }; \ No newline at end of file diff --git a/header-validation/src/trigger_tests.js b/header-validation/src/trigger_tests.js new file mode 100644 index 0000000..b120e7f --- /dev/null +++ b/header-validation/src/trigger_tests.js @@ -0,0 +1,61 @@ +const triggerTestCases = [ + { + name: "App Destination Present | Valid", + type: "app", + flags: {}, + json: "{\"destination\":\"android-app://com.myapps\"}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "(Null) App Destination | Invalid", + type: "app", + flags: {}, + json: "{\"destination\":null}", + result: { + valid: false, + errors: ["Must be a string or able to cast to string: `destination`"], + warnings: [] + } + }, + { + name: "Web Destination Present | Valid", + type: "web", + flags: {}, + json: "{\"destination\":\"https://web-destination.test\"}", + result: { + valid: true, + errors: [], + warnings: [] + } + }, + { + name: "Web Destination Invalid Path | Invalid", + type: "web", + flags: {}, + json: "{\"destination\":\"web-destination.test\"}", + result: { + valid: false, + errors: ["URI is not valid: `destination`"], + warnings: [] + } + }, + { + name: "Web Destination Missing Host | Invalid", + type: "web", + flags: {}, + json: "{\"destination\":\"/web-destination\"}", + result: { + valid: false, + errors: ["URI is not valid: `destination`"], + warnings: [] + } + } +] + +module.exports = { + triggerTestCases +}; \ No newline at end of file diff --git a/header-validation/src/validate_source.js b/header-validation/src/validate_source.js new file mode 100644 index 0000000..4df383c --- /dev/null +++ b/header-validation/src/validate_source.js @@ -0,0 +1,87 @@ +const { + State, + boolean, + optBoolean, + optBooleanFallback, + uint64, + optUint64, + int64, + optInt64, + optString, + optStringFallback, + object, + objectWithFlags, + array, + required, + optional, + exclude, + getDataType, + isValidAppDestinationHost, + isValidFilterData, + isValidTriggerMatchingData +} = require('./base'); + +function validateSource(source, registrationType, flagValues) { + try { + sourceJSON = JSON.parse(source) + } catch (err) { + return {errors: [err instanceof Error ? err.toString() : 'unknown error'], warnings: []}; + } + + const state = new State(); + let jsonSpec = {}; + + // destination check + if (!('destination' in sourceJSON) && !('web_destination' in sourceJSON)) { + return {errors: [{ + path: ["destination or web_destination"], + msg: "At least one field must be present", + formattedError: "At least one field must be present: `destination or web_destination`"}], warnings: []}; + } + jsonSpec["destination"] = optional(isValidAppDestinationHost); + jsonSpec["web_destination"] = optional(); + + // define fields that are dependent on the feature-ara-parsing-alignment-v1 flag + if (flagValues['feature-ara-parsing-alignment-v1']) { + jsonSpec["source_event_id"] = optional(uint64); + jsonSpec["expiry"] = optional(optUint64); + jsonSpec["event_report_window"] = optional(optUint64); + jsonSpec["aggregatable_report_window"] = optional(optUint64); + jsonSpec["priority"] = optional(int64); + jsonSpec["debug_key"] = optional(uint64); + } else { + jsonSpec["source_event_id"] = optional(optUint64); + jsonSpec["expiry"] = optional(optInt64); + jsonSpec["event_report_window"] = optional(optInt64); + jsonSpec["aggregatable_report_window"] = optional(optInt64); + jsonSpec["priority"] = optional(optInt64); + jsonSpec["debug_key"] = optional(optUint64); + } + + // define fields that are dependent on the feature-trigger-data-matching flag + if (flagValues['feature-trigger-data-matching']) { + jsonSpec["trigger_data_matching"] = optional(isValidTriggerMatchingData) + } + + // define fields that have no dependencies + jsonSpec["debug_reporting"] = optional(optBooleanFallback()) + jsonSpec["install_attribution_window"] = optional(optInt64) + jsonSpec["post_install_exclusivity_window"] = optional(optInt64) + jsonSpec["debug_ad_id"] = optional(optStringFallback()) + jsonSpec["debug_join_key"] = optional(optStringFallback()) + jsonSpec["coarse_event_report_destinations"] = optional(optBoolean()) + jsonSpec["shared_debug_keys"] = optional(optUint64); + jsonSpec["drop_source_if_installed"] = optional(optBoolean()) + jsonSpec["shared_aggregation_keys"] = optional(array()) + jsonSpec["shared_filter_data_keys"] = optional(array()) + + // define filter data spec + jsonSpec["filter_data"] = optional(objectWithFlags(isValidFilterData, flagValues)) + + state.validate(sourceJSON, jsonSpec, flagValues); + return state.result(); +} + +module.exports = { + validateSource +}; \ No newline at end of file diff --git a/header-validation/src/validate_test.js b/header-validation/src/validate_test.js new file mode 100644 index 0000000..f191845 --- /dev/null +++ b/header-validation/src/validate_test.js @@ -0,0 +1,46 @@ +const test = require('node:test') +const assert = require('assert') +const {validateSource} = require('./validate_source') +const {sourceTestCases} = require('./source_tests') +const {validateTrigger} = require('./validate_trigger') +const {triggerTestCases} = require('./trigger_tests') + +test('Source Header Validation Tests', async (t) => { + for (testCase of sourceTestCases) { + await t.test(testCase.name, (t) => { + // Setup + type = testCase.type + flags = testCase.flags + json = testCase.json + + // Test + result = validateSource(json, type, flags) + + // Assert + isValid = (result.errors.length === 0 && result.warnings.length === 0) + assert.equal(/* actual */ isValid, /* expected */ testCase.result.valid) + assert.deepEqual(/* actual */ result.errors.map(error => error.formattedError), /* expected */ testCase.result.errors) + assert.deepEqual(/* actual */ result.warnings, /* expected */ testCase.result.warnings) + }) + } +}) + +test('Trigger Header Validation Tests', async (t) => { + for (testCase of triggerTestCases) { + await t.test(testCase.name, (t) => { + // Setup + type = testCase.type + flags = testCase.flags + json = testCase.json + + // Test + result = validateTrigger(json, type, flags) + + // Assert + isValid = (result.errors.length === 0 && result.warnings.length === 0) + assert.equal(/* actual */ isValid, /* expected */ testCase.result.valid) + assert.deepEqual(/* actual */ result.errors.map(error => error.formattedError), /* expected */ testCase.result.errors) + assert.deepEqual(/* actual */ result.warnings, /* expected */ testCase.result.warnings) + }) + } +}) \ No newline at end of file diff --git a/header-validation/src/validate_trigger.js b/header-validation/src/validate_trigger.js new file mode 100644 index 0000000..0d752ce --- /dev/null +++ b/header-validation/src/validate_trigger.js @@ -0,0 +1,41 @@ +const { + State, + boolean, + optBoolean, + optBooleanFallback, + uint64, + optUint64, + int64, + isValidUri, + optInt64, + string, + optString, + optStringFallback, + object, + objectWithFlags, + array, + required, + optional, + exclude, + getDataType, +} = require('./base'); + +function validateTrigger(trigger, registrationType, flagValues) { + try { + triggerJSON = JSON.parse(trigger) + } catch (err) { + return {errors: [err instanceof Error ? err.toString() : 'unknown error'], warnings: []}; + } + + const state = new State(); + let jsonSpec = {}; + + jsonSpec["destination"] = optional(isValidUri); + + state.validate(triggerJSON, jsonSpec, flagValues); + return state.result(); +} + +module.exports = { + validateTrigger +}; \ No newline at end of file diff --git a/header-validation/style.css b/header-validation/style.css new file mode 100644 index 0000000..bd77850 --- /dev/null +++ b/header-validation/style.css @@ -0,0 +1,45 @@ +* { + box-sizing: border-box; + } + + html { + height: 100vh; + margin: 0; + padding: 0; + } + + body { + margin: 0; + padding: 1em; + } + + body, + form, + textarea, + #output { + height: 100%; + width: 100%; + } + + body, + form, + #right { + display: flex; + } + + body, + #right { + flex-direction: column; + } + + #header { + flex: none; + } + + #input { + flex: 1; + } + + textarea { + resize: none; + } \ No newline at end of file diff --git a/header-validation/webpack.config.js b/header-validation/webpack.config.js new file mode 100644 index 0000000..9c84a1f --- /dev/null +++ b/header-validation/webpack.config.js @@ -0,0 +1,9 @@ +const path = require('path') + +module.exports = { + entry: './src/index.js', + output: { + filename: 'main.js', + path: path.resolve(__dirname, 'dist'), + }, +}