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
+
+
+
+
+
\ 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'),
+ },
+}