diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b6d0f5a..633aba1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,20 @@ on: branches: - '**' jobs: + lint: + name: Lint + timeout-minutes: 15 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Use Node.js + uses: actions/setup-node@v4 + with: + cache: npm + - name: Install dependencies + run: npm ci + - name: Lint + run: npm run lint test: strategy: matrix: diff --git a/.gitignore b/.gitignore index 8d06aaf..e06fabc 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,6 @@ node_modules # Mac DS_Store files .DS_Store + +# Optional eslint cache +.eslintcache diff --git a/.nvmrc b/.nvmrc index 6d80269..645ae0c 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -18.16.0 +20.15.0 \ No newline at end of file diff --git a/.releaserc.js b/.releaserc.js index d53ede6..a65c776 100644 --- a/.releaserc.js +++ b/.releaserc.js @@ -27,7 +27,7 @@ async function config() { // Get branch const branch = ref.split('/').pop(); console.log(`Running on branch: ${branch}`); - + // Set changelog file //const changelogFile = `./changelogs/CHANGELOG_${branch}.md`; const changelogFile = `./CHANGELOG.md`; @@ -107,7 +107,7 @@ async function loadTemplates() { function getReleaseComment() { const url = repositoryUrl + '/releases/tag/${nextRelease.gitTag}'; - let comment = '🎉 This change has been released in version [${nextRelease.version}](' + url + ')'; + const comment = '🎉 This change has been released in version [${nextRelease.version}](' + url + ')'; return comment; } diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..9aabbcb --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,38 @@ +import js from "@eslint/js"; + +export default [ + js.configs.recommended, + { + languageOptions: { + globals: { + __dirname: true, + beforeEach: true, + Buffer: true, + console: true, + describe: true, + fail: true, + expect: true, + global: true, + it: true, + jasmine: true, + process: true, + spyOn: true, + }, + }, + rules: { + "indent": ["error", 2], + "linebreak-style": ["error", "unix"], + "no-trailing-spaces": 2, + "eol-last": 2, + "space-in-parens": ["error", "never"], + "no-multiple-empty-lines": 1, + "prefer-const": "error", + "space-infix-ops": "error", + "no-useless-escape": "off", + "no-var": "error", + "no-unused-vars": "warn", + "no-undef": "warn", + "no-prototype-builtins": "off", + } + } +]; diff --git a/package-lock.json b/package-lock.json index e87f35d..df0c931 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "web-push": "3.6.7" }, "devDependencies": { + "@eslint/js": "9.6.0", "@semantic-release/changelog": "6.0.3", "@semantic-release/commit-analyzer": "13.0.0", "@semantic-release/git": "10.0.1", @@ -26,6 +27,7 @@ "@semantic-release/release-notes-generator": "14.0.1", "c8": "10.1.2", "codecov": "3.8.0", + "eslint": "9.6.0", "jasmine": "5.1.0", "jasmine-spec-reporter": "7.0.0", "semantic-release": "24.0.0" @@ -99,6 +101,127 @@ "node": ">=0.1.90" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.17.0.tgz", + "integrity": "sha512-A68TBu6/1mHHuc5YJL0U0VVeGNiklLAL6rRmhTCP2B5XjWLMnrX+HkO+IAXyHvks5cyyY1jjK5ITPQ1HGS2EVA==", + "dev": true, + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/eslintrc/node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.6.0.tgz", + "integrity": "sha512-D9B0/3vNg44ZeWbYMpBoXqNP4j6eQD5vNwIlGAuFRRzK/WtT/jvDQW3Bi9kkf3PMDMlM7Yi+73VLUsn5bJcl8A==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@fastify/busboy": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", @@ -302,21 +425,6 @@ "node": ">=10.0.0" } }, - "node_modules/@google-cloud/storage/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "optional": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@google-cloud/storage/node_modules/teeny-request": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", @@ -388,6 +496,32 @@ "node": ">=6" } }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", + "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1536,6 +1670,27 @@ "node": ">=6.5" } }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -1939,22 +2094,6 @@ "balanced-match": "^1.0.0" } }, - "node_modules/c8/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/c8/node_modules/foreground-child": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", @@ -1994,21 +2133,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/c8/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/c8/node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -2024,36 +2148,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/c8/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/c8/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/c8/node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -2634,6 +2728,12 @@ "node": ">=4.0.0" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -2962,6 +3062,196 @@ "node": ">=0.8.0" } }, + "node_modules/eslint": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.6.0.tgz", + "integrity": "sha512-ElQkdLMEEqQNM9Njff+2Y4q2afHk7JpkPvrd7Xh7xefwgQynqPxwf55J7di9+MEibWUGdNjFF9ITG9Pck5M84w==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/config-array": "^0.17.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.6.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.3.0", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.0.1", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.1.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/eslint-scope": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.1.tgz", + "integrity": "sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/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/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", + "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", + "dev": true, + "dependencies": { + "acorn": "^8.12.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -2975,6 +3265,48 @@ "node": ">=4" } }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "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/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/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", @@ -3096,6 +3428,12 @@ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, "node_modules/fast-xml-parser": { "version": "4.3.5", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.5.tgz", @@ -3153,6 +3491,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -3165,6 +3515,22 @@ "node": ">=8" } }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/find-up-simple": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.0.tgz", @@ -3230,6 +3596,25 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, "node_modules/forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -3477,6 +3862,18 @@ "node": ">= 6" } }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/globby": { "version": "14.0.2", "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", @@ -3915,6 +4312,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", @@ -4015,6 +4421,15 @@ "node": ">=8" } }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", @@ -4332,6 +4747,12 @@ "bignumber.js": "^9.0.0" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, "node_modules/json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", @@ -4354,6 +4775,12 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -4472,6 +4899,28 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/limiter": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", @@ -4520,6 +4969,21 @@ "node": ">=4" } }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -4567,6 +5031,12 @@ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", "dev": true }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "node_modules/lodash.uniqby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", @@ -4806,6 +5276,12 @@ "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -7578,6 +8054,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/p-each-series": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-3.0.0.tgz", @@ -7614,6 +8107,36 @@ "node": ">=8" } }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "devOptional": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-map": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.2.tgz", @@ -7906,6 +8429,15 @@ "node": ">=10" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/pretty-ms": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.0.0.tgz", @@ -9135,6 +9667,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -9232,6 +9770,18 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-fest": { "version": "4.21.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.21.0.tgz", @@ -9527,6 +10077,15 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", @@ -9775,18 +10334,104 @@ "regenerator-runtime": "^0.14.0" } }, - "@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "optional": true + }, + "@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^3.3.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true + } + } + }, + "@eslint-community/regexpp": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "dev": true + }, + "@eslint/config-array": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.17.0.tgz", + "integrity": "sha512-A68TBu6/1mHHuc5YJL0U0VVeGNiklLAL6rRmhTCP2B5XjWLMnrX+HkO+IAXyHvks5cyyY1jjK5ITPQ1HGS2EVA==", + "dev": true, + "requires": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + } + }, + "@eslint/eslintrc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + } + } + }, + "@eslint/js": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.6.0.tgz", + "integrity": "sha512-D9B0/3vNg44ZeWbYMpBoXqNP4j6eQD5vNwIlGAuFRRzK/WtT/jvDQW3Bi9kkf3PMDMlM7Yi+73VLUsn5bJcl8A==", "dev": true }, - "@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true, - "optional": true + "@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true }, "@fastify/busboy": { "version": "2.1.1", @@ -9959,15 +10604,6 @@ "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", "optional": true }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "optional": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, "teeny-request": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", @@ -10019,6 +10655,18 @@ "yargs": "^17.7.2" } }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, + "@humanwhocodes/retry": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", + "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", + "dev": true + }, "@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -10879,6 +11527,19 @@ "event-target-shim": "^5.0.0" } }, + "acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, "agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -11183,16 +11844,6 @@ "balanced-match": "^1.0.0" } }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, "foreground-child": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", @@ -11217,15 +11868,6 @@ "path-scurry": "^1.11.1" } }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, "minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -11235,24 +11877,6 @@ "brace-expansion": "^2.0.1" } }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, "signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -11673,6 +12297,12 @@ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -11915,12 +12545,177 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, + "eslint": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.6.0.tgz", + "integrity": "sha512-ElQkdLMEEqQNM9Njff+2Y4q2afHk7JpkPvrd7Xh7xefwgQynqPxwf55J7di9+MEibWUGdNjFF9ITG9Pck5M84w==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/config-array": "^0.17.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.6.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.3.0", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.0.1", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.1.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "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 + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "eslint-scope": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.1.tgz", + "integrity": "sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true + }, + "espree": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", + "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", + "dev": true, + "requires": { + "acorn": "^8.12.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.0.0" + } + }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, + "esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, "event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", @@ -12013,6 +12808,12 @@ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, "fast-xml-parser": { "version": "4.3.5", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.5.tgz", @@ -12048,6 +12849,15 @@ "is-unicode-supported": "^2.0.0" } }, + "file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "requires": { + "flat-cache": "^4.0.0" + } + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -12057,6 +12867,16 @@ "to-regex-range": "^5.0.1" } }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, "find-up-simple": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.0.tgz", @@ -12099,6 +12919,22 @@ } } }, + "flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "requires": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + } + }, + "flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -12295,6 +13131,12 @@ "is-glob": "^4.0.1" } }, + "globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true + }, "globby": { "version": "14.0.2", "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", @@ -12623,6 +13465,12 @@ "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", "dev": true }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, "indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", @@ -12693,6 +13541,12 @@ "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", "dev": true }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, "is-plain-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", @@ -12925,6 +13779,12 @@ "bignumber.js": "^9.0.0" } }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", @@ -12947,6 +13807,12 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -13043,6 +13909,25 @@ "safe-buffer": "^5.0.1" } }, + "keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "requires": { + "json-buffer": "3.0.1" + } + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, "limiter": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", @@ -13084,6 +13969,15 @@ } } }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -13130,6 +14024,12 @@ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", "dev": true }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "lodash.uniqby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", @@ -13309,6 +14209,12 @@ "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, "neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -15169,6 +16075,20 @@ "mimic-fn": "^2.1.0" } }, + "optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + } + }, "p-each-series": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-3.0.0.tgz", @@ -15190,6 +16110,24 @@ "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", "dev": true }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "devOptional": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, "p-map": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.2.tgz", @@ -15403,6 +16341,12 @@ "tunnel-agent": "^0.6.0" } }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, "pretty-ms": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.0.0.tgz", @@ -16280,6 +17224,12 @@ } } }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, "thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -16361,6 +17311,15 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, "type-fest": { "version": "4.21.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.21.0.tgz", @@ -16585,6 +17544,12 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, + "word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true + }, "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", diff --git a/package.json b/package.json index fa40955..de6063c 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,8 @@ "src/" ], "scripts": { + "lint": "eslint --cache ./", + "lint:fix": "eslint --fix --cache ./", "test": "TESTING=1 c8 ./node_modules/.bin/jasmine" }, "keywords": [ @@ -31,6 +33,7 @@ "web-push": "3.6.7" }, "devDependencies": { + "@eslint/js": "9.6.0", "@semantic-release/changelog": "6.0.3", "@semantic-release/commit-analyzer": "13.0.0", "@semantic-release/git": "10.0.1", @@ -39,6 +42,7 @@ "@semantic-release/release-notes-generator": "14.0.1", "c8": "10.1.2", "codecov": "3.8.0", + "eslint": "9.6.0", "jasmine": "5.1.0", "jasmine-spec-reporter": "7.0.0", "semantic-release": "24.0.0" diff --git a/spec/APNS.spec.js b/spec/APNS.spec.js index cd13c2a..70b3d54 100644 --- a/spec/APNS.spec.js +++ b/spec/APNS.spec.js @@ -5,20 +5,20 @@ import APNS from '../src/APNS.js'; describe('APNS', () => { it('can initialize with cert', (done) => { - let args = { + const args = { cert: '-----BEGIN CERTIFICATE-----fPEYJtQrEMXLC9JtFUJ6emXAWv2QdKu93QE+6o5htM+Eu/2oNFIEj2A71WUBu7kA-----END CERTIFICATE-----', - key: new Buffer('testKey'), + key: Buffer.from('testKey'), production: true, topic: 'topic' }; - let apns = new APNS(args); + const apns = new APNS(args); expect(apns.providers.length).toBe(1); - let apnsProvider = apns.providers[0]; + const apnsProvider = apns.providers[0]; expect(apnsProvider.index).toBe(0); expect(apnsProvider.topic).toBe(args.topic); // TODO: Remove this checking onec we inject APNS - let prodApnsOptions = apnsProvider.client.config; + const prodApnsOptions = apnsProvider.client.config; expect(prodApnsOptions.cert).toBe(args.cert); expect(prodApnsOptions.key).toBe(args.key); expect(prodApnsOptions.production).toBe(args.production); @@ -48,15 +48,15 @@ describe('APNS', () => { }); it('fails to initialize without a bundleID', (done) => { - expect(() => { + expect(() => { new APNS({ - key: new Buffer('key'), + key: Buffer.from('key'), production: true, bundle: 'hello' }); }).toThrow(); - expect(() => { + expect(() => { new APNS({ cert: 'pfx', production: true, @@ -64,9 +64,9 @@ describe('APNS', () => { }); }).toThrow(); - expect(() => { + expect(() => { new APNS({ - pfx: new Buffer(''), + pfx: Buffer.from(''), production: true, bundle: 'hello' }); @@ -74,27 +74,28 @@ describe('APNS', () => { done(); }); - it('can initialize with multiple certs', (done) => { - var args = [ + it('can initialize with multiple certs with bundleId', (done) => { + spyOn(log, 'warn').and.callFake(() => {}); + const args = [ { cert: '-----BEGIN CERTIFICATE-----fPEYJtQrEMXLC9JtFUJ6emXAWv2QdKu93QE+6o5htM+Eu/2oNFIEj2A71WUBu7kA-----END CERTIFICATE-----', - key: new Buffer('testKey'), + key: Buffer.from('testKey'), production: false, bundleId: 'bundleId' }, { cert: '-----BEGIN CERTIFICATE-----fPEYJtQrEMXLC9JtFUJ6emXAWv2QdKu93QE+6o5htM+Eu/2oNFIEj2A71WUBu7kA-----END CERTIFICATE-----', - key: new Buffer('testKey'), + key: Buffer.from('testKey'), production: true, bundleId: 'bundleIdAgain' } ] - var apns = new APNS(args); + const apns = new APNS(args); expect(apns.providers.length).toBe(2); - var devApnsConnection = apns.providers[1]; + const devApnsConnection = apns.providers[1]; expect(devApnsConnection.index).toBe(1); - var devApnsOptions = devApnsConnection.client.config; + const devApnsOptions = devApnsConnection.client.config; expect(devApnsOptions.cert).toBe(args[0].cert); expect(devApnsOptions.key).toBe(args[0].key); expect(devApnsOptions.production).toBe(args[0].production); @@ -102,11 +103,11 @@ describe('APNS', () => { expect(devApnsOptions.topic).toBe(args[0].bundleId); expect(devApnsConnection.topic).toBe(args[0].bundleId); - var prodApnsConnection = apns.providers[0]; + const prodApnsConnection = apns.providers[0]; expect(prodApnsConnection.index).toBe(0); // TODO: Remove this checking onec we inject APNS - var prodApnsOptions = prodApnsConnection.client.config; + const prodApnsOptions = prodApnsConnection.client.config; expect(prodApnsOptions.cert).toBe(args[1].cert); expect(prodApnsOptions.key).toBe(args[1].key); expect(prodApnsOptions.production).toBe(args[1].production); @@ -116,40 +117,40 @@ describe('APNS', () => { done(); }); - it('can initialize with multiple certs', (done) => { - let args = [ + it('can initialize with multiple certs with topic', (done) => { + const args = [ { cert: '-----BEGIN CERTIFICATE-----fPEYJtQrEMXLC9JtFUJ6emXAWv2QdKu93QE+6o5htM+Eu/2oNFIEj2A71WUBu7kA-----END CERTIFICATE-----', - key: new Buffer('testKey'), + key: Buffer.from('testKey'), production: false, topic: 'topic' }, { cert: '-----BEGIN CERTIFICATE-----fPEYJtQrEMXLC9JtFUJ6emXAWv2QdKu93QE+6o5htM+Eu/2oNFIEj2A71WUBu7kA-----END CERTIFICATE-----', - key: new Buffer('testKey'), + key: Buffer.from('testKey'), production: true, topic: 'topicAgain' } ]; - let apns = new APNS(args); + const apns = new APNS(args); expect(apns.providers.length).toBe(2); - let devApnsProvider = apns.providers[1]; + const devApnsProvider = apns.providers[1]; expect(devApnsProvider.index).toBe(1); expect(devApnsProvider.topic).toBe(args[0].topic); - let devApnsOptions = devApnsProvider.client.config; + const devApnsOptions = devApnsProvider.client.config; expect(devApnsOptions.cert).toBe(args[0].cert); expect(devApnsOptions.key).toBe(args[0].key); expect(devApnsOptions.production).toBe(args[0].production); - let prodApnsProvider = apns.providers[0]; + const prodApnsProvider = apns.providers[0]; expect(prodApnsProvider.index).toBe(0); expect(prodApnsProvider.topic).toBe(args[1].topic); // TODO: Remove this checking onec we inject APNS - let prodApnsOptions = prodApnsProvider.client.config; + const prodApnsOptions = prodApnsProvider.client.config; expect(prodApnsOptions.cert).toBe(args[1].cert); expect(prodApnsOptions.key).toBe(args[1].key); expect(prodApnsOptions.production).toBe(args[1].production); @@ -157,7 +158,7 @@ describe('APNS', () => { }); it('sets priority to 10 if not set explicitly', (done) => { - let data = { + const data = { 'alert': 'alert', 'title': 'title', 'badge': 100, @@ -169,14 +170,14 @@ describe('APNS', () => { 'key': 'value', 'keyAgain': 'valueAgain' }; - let notification = APNS._generateNotification(data, { }); + const notification = APNS._generateNotification(data, { }); expect(notification.priority).toEqual(10); done(); }); it('can generate APNS notification', (done) => { //Mock request data - let data = { + const data = { 'alert': 'alert', 'title': 'title', 'badge': 100, @@ -190,12 +191,12 @@ describe('APNS', () => { 'key': 'value', 'keyAgain': 'valueAgain' }; - let expirationTime = 1454571491354; - let collapseId = "collapseIdentifier"; + const expirationTime = 1454571491354; + const collapseId = "collapseIdentifier"; - let pushType = "alert"; - let priority = 5; - let notification = APNS._generateNotification(data, { expirationTime: expirationTime, collapseId: collapseId, pushType: pushType, priority: priority }); + const pushType = "alert"; + const priority = 5; + const notification = APNS._generateNotification(data, { expirationTime: expirationTime, collapseId: collapseId, pushType: pushType, priority: priority }); expect(notification.aps.alert).toEqual({ body: 'alert', title: 'title' }); expect(notification.aps.badge).toEqual(data.badge); @@ -219,7 +220,7 @@ describe('APNS', () => { it('can generate APNS notification with nested alert dictionary', (done) => { //Mock request data - let data = { + const data = { 'alert': { body: 'alert', title: 'title' }, 'badge': 100, 'sound': 'test', @@ -232,12 +233,12 @@ describe('APNS', () => { 'key': 'value', 'keyAgain': 'valueAgain' }; - let expirationTime = 1454571491354; - let collapseId = "collapseIdentifier"; + const expirationTime = 1454571491354; + const collapseId = "collapseIdentifier"; - let pushType = "alert"; - let priority = 5; - let notification = APNS._generateNotification(data, { expirationTime: expirationTime, collapseId: collapseId, pushType: pushType, priority: priority }); + const pushType = "alert"; + const priority = 5; + const notification = APNS._generateNotification(data, { expirationTime: expirationTime, collapseId: collapseId, pushType: pushType, priority: priority }); expect(notification.aps.alert).toEqual({ body: 'alert', title: 'title' }); expect(notification.aps.badge).toEqual(data.badge); @@ -261,7 +262,7 @@ describe('APNS', () => { it('sets push type to alert if not defined explicitly', (done) => { //Mock request data - let data = { + const data = { 'alert': 'alert', 'title': 'title', 'badge': 100, @@ -273,58 +274,58 @@ describe('APNS', () => { 'key': 'value', 'keyAgain': 'valueAgain' }; - let expirationTime = 1454571491354; - let collapseId = "collapseIdentifier"; + const expirationTime = 1454571491354; + const collapseId = "collapseIdentifier"; - let notification = APNS._generateNotification(data, { expirationTime: expirationTime, collapseId: collapseId }); + const notification = APNS._generateNotification(data, { expirationTime: expirationTime, collapseId: collapseId }); expect(notification.pushType).toEqual('alert'); done(); }); it('can generate APNS notification from raw data', (done) => { - //Mock request data - let data = { - 'aps': { - 'alert': { - "loc-key" : "GAME_PLAY_REQUEST_FORMAT", - "loc-args" : [ "Jenna", "Frank"] - }, - 'badge': 100, - 'sound': 'test', - 'thread-id': 'a-thread-id' + //Mock request data + const data = { + 'aps': { + 'alert': { + "loc-key" : "GAME_PLAY_REQUEST_FORMAT", + "loc-args" : [ "Jenna", "Frank"] }, - 'key': 'value', - 'keyAgain': 'valueAgain' - }; - let expirationTime = 1454571491354; - let collapseId = "collapseIdentifier"; - let pushType = "background"; - let priority = 5; - - let notification = APNS._generateNotification(data, { expirationTime: expirationTime, collapseId: collapseId, pushType: pushType, priority: priority }); - - expect(notification.expiry).toEqual(Math.round(expirationTime / 1000)); - expect(notification.collapseId).toEqual(collapseId); - expect(notification.pushType).toEqual(pushType); - expect(notification.priority).toEqual(priority); - - let stringifiedJSON = notification.compile(); - let jsonObject = JSON.parse(stringifiedJSON); - - expect(jsonObject.aps.alert).toEqual({ "loc-key" : "GAME_PLAY_REQUEST_FORMAT", "loc-args" : [ "Jenna", "Frank"] }); - expect(jsonObject.aps.badge).toEqual(100); - expect(jsonObject.aps.sound).toEqual('test'); - expect(jsonObject.aps['thread-id']).toEqual('a-thread-id'); - expect(jsonObject.key).toEqual('value'); - expect(jsonObject.keyAgain).toEqual('valueAgain'); - done(); - }); + 'badge': 100, + 'sound': 'test', + 'thread-id': 'a-thread-id' + }, + 'key': 'value', + 'keyAgain': 'valueAgain' + }; + const expirationTime = 1454571491354; + const collapseId = "collapseIdentifier"; + const pushType = "background"; + const priority = 5; + + const notification = APNS._generateNotification(data, { expirationTime: expirationTime, collapseId: collapseId, pushType: pushType, priority: priority }); + + expect(notification.expiry).toEqual(Math.round(expirationTime / 1000)); + expect(notification.collapseId).toEqual(collapseId); + expect(notification.pushType).toEqual(pushType); + expect(notification.priority).toEqual(priority); + + const stringifiedJSON = notification.compile(); + const jsonObject = JSON.parse(stringifiedJSON); + + expect(jsonObject.aps.alert).toEqual({ "loc-key" : "GAME_PLAY_REQUEST_FORMAT", "loc-args" : [ "Jenna", "Frank"] }); + expect(jsonObject.aps.badge).toEqual(100); + expect(jsonObject.aps.sound).toEqual('test'); + expect(jsonObject.aps['thread-id']).toEqual('a-thread-id'); + expect(jsonObject.key).toEqual('value'); + expect(jsonObject.keyAgain).toEqual('valueAgain'); + done(); + }); it('can choose providers for device with valid appIdentifier', (done) => { - let appIdentifier = 'topic'; + const appIdentifier = 'topic'; // Mock providers - let providers = [ + const providers = [ { topic: appIdentifier }, @@ -333,7 +334,7 @@ describe('APNS', () => { } ]; - let qualifiedProviders = APNS.prototype._chooseProviders.call({providers: providers}, appIdentifier); + const qualifiedProviders = APNS.prototype._chooseProviders.call({providers: providers}, appIdentifier); expect(qualifiedProviders).toEqual([{ topic: 'topic' }]); @@ -341,9 +342,9 @@ describe('APNS', () => { }); it('can choose providers for device with invalid appIdentifier', (done) => { - let appIdentifier = 'invalid'; + const appIdentifier = 'invalid'; // Mock providers - let providers = [ + const providers = [ { topic: 'bundleId' }, @@ -352,15 +353,15 @@ describe('APNS', () => { } ]; - let qualifiedProviders = APNS.prototype._chooseProviders.call({providers: providers}, appIdentifier); + const qualifiedProviders = APNS.prototype._chooseProviders.call({providers: providers}, appIdentifier); expect(qualifiedProviders).toEqual([]); done(); }); it('does log on invalid APNS notification', async () => { const args = { - cert: new Buffer('testCert'), - key: new Buffer('testKey'), + cert: Buffer.from('testCert'), + key: Buffer.from('testKey'), production: true, topic: 'topic' }; @@ -371,14 +372,14 @@ describe('APNS', () => { }); it('can send APNS notification', (done) => { - let args = { - cert: new Buffer('testCert'), - key: new Buffer('testKey'), + const args = { + cert: Buffer.from('testCert'), + key: Buffer.from('testKey'), production: true, topic: 'topic' }; - let apns = new APNS(args); - let provider = apns.providers[0]; + const apns = new APNS(args); + const provider = apns.providers[0]; spyOn(provider, 'send').and.callFake((notification, devices) => { return Promise.resolve({ sent: devices, @@ -386,10 +387,10 @@ describe('APNS', () => { }) }); // Mock data - let expirationTime = 1454571491354; - let collapseId = "collapseIdentifier"; - let pushType = "alert"; // or background - let data = { + const expirationTime = 1454571491354; + const collapseId = "collapseIdentifier"; + const pushType = "alert"; // or background + const data = { 'collapse_id': collapseId, 'push_type': pushType, 'expiration_time': expirationTime, @@ -399,7 +400,7 @@ describe('APNS', () => { } }; // Mock devices - let mockedDevices = [ + const mockedDevices = [ { deviceToken: '112233', appIdentifier: 'topic' @@ -417,29 +418,30 @@ describe('APNS', () => { appIdentifier: 'topic' } ]; - let promise = apns.send(data, mockedDevices); + apns.send(data, mockedDevices); + expect(provider.send).toHaveBeenCalled(); - let calledArgs = provider.send.calls.first().args; - let notification = calledArgs[0]; + const calledArgs = provider.send.calls.first().args; + const notification = calledArgs[0]; expect(notification.aps.alert).toEqual(data.data.alert); expect(notification.expiry).toEqual(Math.round(data['expiration_time'] / 1000)); expect(notification.collapseId).toEqual(collapseId); expect(notification.pushType).toEqual(pushType); expect(notification.priority).toEqual(data['priority']); - let apnDevices = calledArgs[1]; + const apnDevices = calledArgs[1]; expect(apnDevices.length).toEqual(4); done(); }); it('can send APNS notification headers in data', (done) => { - let args = { - cert: new Buffer('testCert'), - key: new Buffer('testKey'), + const args = { + cert: Buffer.from('testCert'), + key: Buffer.from('testKey'), production: true, topic: 'topic' }; - let apns = new APNS(args); - let provider = apns.providers[0]; + const apns = new APNS(args); + const provider = apns.providers[0]; spyOn(provider, 'send').and.callFake((notification, devices) => { return Promise.resolve({ sent: devices, @@ -447,10 +449,10 @@ describe('APNS', () => { }) }); // Mock data - let expirationTime = 1454571491354; - let collapseId = "collapseIdentifier"; - let pushType = "alert"; // or background - let data = { + const expirationTime = 1454571491354; + const collapseId = "collapseIdentifier"; + const pushType = "alert"; // or background + const data = { 'expiration_time': expirationTime, 'data': { 'alert': 'alert', @@ -460,7 +462,7 @@ describe('APNS', () => { } }; // Mock devices - let mockedDevices = [ + const mockedDevices = [ { deviceToken: '112233', appIdentifier: 'topic' @@ -478,42 +480,42 @@ describe('APNS', () => { appIdentifier: 'topic' } ]; - let promise = apns.send(data, mockedDevices); + apns.send(data, mockedDevices); expect(provider.send).toHaveBeenCalled(); - let calledArgs = provider.send.calls.first().args; - let notification = calledArgs[0]; + const calledArgs = provider.send.calls.first().args; + const notification = calledArgs[0]; expect(notification.aps.alert).toEqual(data.data.alert); expect(notification.expiry).toEqual(Math.round(data['expiration_time'] / 1000)); expect(notification.collapseId).toEqual(collapseId); expect(notification.pushType).toEqual(pushType); expect(notification.priority).toEqual(6); - let apnDevices = calledArgs[1]; + const apnDevices = calledArgs[1]; expect(apnDevices.length).toEqual(4); done(); }); it('can send APNS notification to multiple bundles', (done) => { - let args = [{ - cert: new Buffer('testCert'), - key: new Buffer('testKey'), + const args = [{ + cert: Buffer.from('testCert'), + key: Buffer.from('testKey'), production: true, topic: 'topic' }, { - cert: new Buffer('testCert'), - key: new Buffer('testKey'), + cert: Buffer.from('testCert'), + key: Buffer.from('testKey'), production: false, topic: 'topic.dev' }]; - let apns = new APNS(args); - let provider = apns.providers[0]; + const apns = new APNS(args); + const provider = apns.providers[0]; spyOn(provider, 'send').and.callFake((notification, devices) => { return Promise.resolve({ sent: devices, failed: [] }) }); - let providerDev = apns.providers[1]; + const providerDev = apns.providers[1]; spyOn(providerDev, 'send').and.callFake((notification, devices) => { return Promise.resolve({ sent: devices, @@ -522,10 +524,10 @@ describe('APNS', () => { }); apns.providers = [provider, providerDev]; // Mock data - let expirationTime = 1454571491354; - let pushType = "alert"; // or background - let collapseId = "collapseIdentifier"; - let data = { + const expirationTime = 1454571491354; + const pushType = "alert"; // or background + const collapseId = "collapseIdentifier"; + const data = { 'collapse_id': collapseId, 'push_type': pushType, 'expiration_time': expirationTime, @@ -534,7 +536,7 @@ describe('APNS', () => { } }; // Mock devices - let mockedDevices = [ + const mockedDevices = [ { deviceToken: '112233', appIdentifier: 'topic' @@ -556,8 +558,7 @@ describe('APNS', () => { appIdentifier: 'topic.dev' } ]; - - let promise = apns.send(data, mockedDevices); + apns.send(data, mockedDevices); expect(provider.send).toHaveBeenCalled(); let calledArgs = provider.send.calls.first().args; @@ -581,45 +582,47 @@ describe('APNS', () => { done(); }); - it('reports proper error when no conn is available', (done) => { - var args = [{ + it('reports proper error when no conn is available', (done) => { + spyOn(log, 'warn').and.callFake(() => {}); + const args = [{ cert: '-----BEGIN CERTIFICATE-----fPEYJtQrEMXLC9JtFUJ6emXAWv2QdKu93QE+6o5htM+Eu/2oNFIEj2A71WUBu7kA-----END CERTIFICATE-----', - key: new Buffer('testKey'), + key: Buffer.from('testKey'), production: true, bundleId: 'bundleId' }]; - var data = { + const data = { 'data': { 'alert': 'alert' } } - var devices = [ + const devices = [ { deviceToken: '112233', appIdentifier: 'invalidBundleId' }, ] - var apns = new APNS(args); - apns.send(data, devices).then((results) => { + const apns = new APNS(args); + apns.send(data, devices).then((results) => { expect(results.length).toBe(1); - let result = results[0]; + const result = results[0]; expect(result.transmitted).toBe(false); expect(result.response.error).toBe('No Provider found'); done(); - }, (err) => { + }, () => { fail('should not fail'); done(); }) }); - it('properly parses errors', (done) => { + it('properly parses errors', (done) => { + spyOn(log, 'error').and.callFake(() => {}); APNS._handlePushFailure({ device: 'abcd', status: -1, response: { reason: 'Something wrong happend' } - }).then((result) => { + }).then((result) => { expect(result.transmitted).toBe(false); expect(result.device.deviceToken).toBe('abcd'); expect(result.device.deviceType).toBe('ios'); @@ -628,10 +631,11 @@ describe('APNS', () => { }) }); - it('properly parses errors again', (done) => { + it('properly parses errors again', (done) => { + spyOn(log, 'error').and.callFake(() => {}); APNS._handlePushFailure({ device: 'abcd', - }).then((result) => { + }).then((result) => { expect(result.transmitted).toBe(false); expect(result.device.deviceToken).toBe('abcd'); expect(result.device.deviceType).toBe('ios'); diff --git a/spec/EXPO.spec.js b/spec/EXPO.spec.js index 8318c5a..b47d398 100644 --- a/spec/EXPO.spec.js +++ b/spec/EXPO.spec.js @@ -107,7 +107,7 @@ describe('EXPO', () => { const response = await expo.send(data, devices); expect(Array.isArray(response)).toBe(true); expect(response.length).toEqual(devices.length); - response.forEach((res, index) => { + response.forEach((res, index) => { expect(res.transmitted).toEqual(false); expect(res.device.deviceToken).toEqual(devices[index].deviceToken); }); diff --git a/spec/FCM.spec.js b/spec/FCM.spec.js index a18e12c..ee4e3ec 100644 --- a/spec/FCM.spec.js +++ b/spec/FCM.spec.js @@ -48,8 +48,6 @@ describe('FCM', () => { const pushId = 'pushId'; const timeStamp = 1454538822113; - const timeStampISOStr = new Date(timeStamp).toISOString(); - const payload = FCM.generateFCMPayload( requestData, pushId, @@ -69,9 +67,9 @@ describe('FCM', () => { it('can slice devices', () => { // Mock devices - var devices = [makeDevice(1), makeDevice(2), makeDevice(3), makeDevice(4)]; + const devices = [makeDevice(1), makeDevice(2), makeDevice(3), makeDevice(4)]; - var chunkDevices = FCM.sliceDevices(devices, 3); + const chunkDevices = FCM.sliceDevices(devices, 3); expect(chunkDevices).toEqual([ [makeDevice(1), makeDevice(2), makeDevice(3)], [makeDevice(4)], @@ -260,12 +258,12 @@ describe('FCM', () => { // To maintain backwards compatibility with APNS payload format // See corresponding test with same test label in APNS.spec.js - let expirationTime = 1454571491354; - let collapseId = 'collapseIdentifier'; - let pushType = 'alert'; - let priority = 5; + const expirationTime = 1454571491354; + const collapseId = 'collapseIdentifier'; + const pushType = 'alert'; + const priority = 5; - let data = { + const data = { expiration_time: expirationTime, collapse_id: collapseId, push_type: pushType, @@ -286,8 +284,6 @@ describe('FCM', () => { const pushId = 'pushId'; const timeStamp = 1454538822113; - const timeStampISOStr = new Date(timeStamp).toISOString(); - const payload = FCM.generateFCMPayload( data, pushId, @@ -330,7 +326,7 @@ describe('FCM', () => { }); it('sets push type to alert if not defined explicitly', () => { - let data = { + const data = { alert: 'alert', title: 'title', badge: 100, @@ -360,11 +356,11 @@ describe('FCM', () => { }); it('can generate APNS notification from raw data', () => { - let expirationTime = 1454571491354; - let collapseId = 'collapseIdentifier'; - let pushType = 'background'; - let priority = 5; - let data = { + const expirationTime = 1454571491354; + const collapseId = 'collapseIdentifier'; + const pushType = 'background'; + const priority = 5; + const data = { expiration_time: expirationTime, collapse_id: collapseId, push_type: pushType, @@ -416,11 +412,11 @@ describe('FCM', () => { // See 'can send APNS notification headers in data' in APNS.spec.js // Not mocking sends currently, only payload generation - let expirationTime = 1454571491354; - let collapseId = 'collapseIdentifier'; - let pushType = 'alert'; // or background + const expirationTime = 1454571491354; + const collapseId = 'collapseIdentifier'; + const pushType = 'alert'; // or background - let data = { + const data = { expiration_time: expirationTime, data: { alert: { body: 'alert', title: 'title' }, diff --git a/spec/GCM.spec.js b/spec/GCM.spec.js index a9bf3ce..be0a19e 100644 --- a/spec/GCM.spec.js +++ b/spec/GCM.spec.js @@ -12,7 +12,7 @@ function mockSender(gcm) { {"error":"InvalidRegistration"}, {"error":"InvalidRegistration"}] }*/ - let tokens = options.registrationTokens; + const tokens = options.registrationTokens; const response = { multicast_id: 7680139367771848000, success: tokens.length, @@ -20,12 +20,12 @@ function mockSender(gcm) { cannonical_ids: 0, results: tokens.map((token, index) => { return { - message_id: 7680139367771848000+''+index, + message_id: 7680139367771848000 + '' + index, registration_id: token } }) } - process.nextTick(() => { + process.nextTick(() => { cb(null, response); }); }); @@ -33,16 +33,16 @@ function mockSender(gcm) { describe('GCM', () => { it('can initialize', (done) => { - var args = { + const args = { apiKey: 'apiKey' }; - var gcm = new GCM(args); + const gcm = new GCM(args); expect(gcm.sender.key).toBe(args.apiKey); done(); }); it('can throw on initializing with invalid args', (done) => { - var args = 123 + let args = 123 expect(function() { new GCM(args); }).toThrow(); @@ -68,7 +68,7 @@ describe('GCM', () => { it('can generate GCM Payload without expiration time', (done) => { //Mock request data - var requestData = { + const requestData = { data: { 'alert': 'alert' }, @@ -77,26 +77,26 @@ describe('GCM', () => { 'body': 'I am a body' } }; - var pushId = 'pushId'; - var timeStamp = 1454538822113; - var timeStampISOStr = new Date(timeStamp).toISOString(); + const pushId = 'pushId'; + const timeStamp = 1454538822113; + const timeStampISOStr = new Date(timeStamp).toISOString(); - var payload = GCM.generateGCMPayload(requestData, pushId, timeStamp); + const payload = GCM.generateGCMPayload(requestData, pushId, timeStamp); expect(payload.priority).toEqual('high'); expect(payload.timeToLive).toEqual(undefined); - var dataFromPayload = payload.data; + const dataFromPayload = payload.data; expect(dataFromPayload.time).toEqual(timeStampISOStr); expect(payload.notification).toEqual(requestData.notification); expect(dataFromPayload['push_id']).toEqual(pushId); - var dataFromUser = dataFromPayload.data; + const dataFromUser = dataFromPayload.data; expect(dataFromUser).toEqual(requestData.data); done(); }); it('can generate GCM Payload with valid expiration time', (done) => { //Mock request data - var requestData = { + const requestData = { data: { 'alert': 'alert' }, @@ -105,27 +105,27 @@ describe('GCM', () => { 'body': 'I am a body' } }; - var pushId = 'pushId'; - var timeStamp = 1454538822113; - var timeStampISOStr = new Date(timeStamp).toISOString(); - var expirationTime = 1454538922113 + const pushId = 'pushId'; + const timeStamp = 1454538822113; + const timeStampISOStr = new Date(timeStamp).toISOString(); + const expirationTime = 1454538922113 - var payload = GCM.generateGCMPayload(requestData, pushId, timeStamp, expirationTime); + const payload = GCM.generateGCMPayload(requestData, pushId, timeStamp, expirationTime); expect(payload.priority).toEqual('high'); expect(payload.timeToLive).toEqual(Math.floor((expirationTime - timeStamp) / 1000)); - var dataFromPayload = payload.data; + const dataFromPayload = payload.data; expect(dataFromPayload.time).toEqual(timeStampISOStr); expect(payload.notification).toEqual(requestData.notification); expect(dataFromPayload['push_id']).toEqual(pushId); - var dataFromUser = dataFromPayload.data; + const dataFromUser = dataFromPayload.data; expect(dataFromUser).toEqual(requestData.data); done(); }); it('can generate GCM Payload with too early expiration time', (done) => { //Mock request data - var requestData = { + const requestData = { data: { 'alert': 'alert' }, @@ -134,27 +134,27 @@ describe('GCM', () => { 'body': 'I am a body' } }; - var pushId = 'pushId'; - var timeStamp = 1454538822113; - var timeStampISOStr = new Date(timeStamp).toISOString(); - var expirationTime = 1454538822112; + const pushId = 'pushId'; + const timeStamp = 1454538822113; + const timeStampISOStr = new Date(timeStamp).toISOString(); + const expirationTime = 1454538822112; - var payload = GCM.generateGCMPayload(requestData, pushId, timeStamp, expirationTime); + const payload = GCM.generateGCMPayload(requestData, pushId, timeStamp, expirationTime); expect(payload.priority).toEqual('high'); expect(payload.timeToLive).toEqual(0); - var dataFromPayload = payload.data; + const dataFromPayload = payload.data; expect(dataFromPayload.time).toEqual(timeStampISOStr); expect(payload.notification).toEqual(requestData.notification); expect(dataFromPayload['push_id']).toEqual(pushId); - var dataFromUser = dataFromPayload.data; + const dataFromUser = dataFromPayload.data; expect(dataFromUser).toEqual(requestData.data); done(); }); it('can generate GCM Payload with too late expiration time', (done) => { //Mock request data - var requestData = { + const requestData = { data: { 'alert': 'alert' }, @@ -163,44 +163,44 @@ describe('GCM', () => { 'body': 'I am a body' } }; - var pushId = 'pushId'; - var timeStamp = 1454538822113; - var timeStampISOStr = new Date(timeStamp).toISOString(); - var expirationTime = 2454538822113; + const pushId = 'pushId'; + const timeStamp = 1454538822113; + const timeStampISOStr = new Date(timeStamp).toISOString(); + const expirationTime = 2454538822113; - var payload = GCM.generateGCMPayload(requestData, pushId, timeStamp, expirationTime); + const payload = GCM.generateGCMPayload(requestData, pushId, timeStamp, expirationTime); expect(payload.priority).toEqual('high'); // Four week in second expect(payload.timeToLive).toEqual(4 * 7 * 24 * 60 * 60); - var dataFromPayload = payload.data; + const dataFromPayload = payload.data; expect(dataFromPayload.time).toEqual(timeStampISOStr); expect(payload.notification).toEqual(requestData.notification); expect(dataFromPayload['push_id']).toEqual(pushId); - var dataFromUser = dataFromPayload.data; + const dataFromUser = dataFromPayload.data; expect(dataFromUser).toEqual(requestData.data); done(); }); it('can send GCM request', (done) => { - var gcm = new GCM({ + const gcm = new GCM({ apiKey: 'apiKey' }); // Mock gcm sender - var sender = { + const sender = { send: jasmine.createSpy('send') }; gcm.sender = sender; // Mock data - var expirationTime = 2454538822113; - var data = { + const expirationTime = 2454538822113; + const data = { 'expiration_time': expirationTime, 'data': { 'alert': 'alert' } } // Mock devices - var devices = [ + const devices = [ { deviceToken: 'token' } @@ -208,7 +208,7 @@ describe('GCM', () => { gcm.send(data, devices); expect(sender.send).toHaveBeenCalled(); - var args = sender.send.calls.first().args; + const args = sender.send.calls.first().args; // It is too hard to verify message of gcm library, we just verify tokens and retry times expect(args[1].registrationTokens).toEqual(['token']); expect(args[2]).toEqual(5); @@ -216,19 +216,19 @@ describe('GCM', () => { }); it('can send GCM request', (done) => { - var gcm = new GCM({ + const gcm = new GCM({ apiKey: 'apiKey' }); // Mock data - var expirationTime = 2454538822113; - var data = { + const expirationTime = 2454538822113; + const data = { 'expiration_time': expirationTime, 'data': { 'alert': 'alert' } } // Mock devices - var devices = [ + const devices = [ { deviceToken: 'token' }, @@ -243,11 +243,11 @@ describe('GCM', () => { } ]; mockSender(gcm); - gcm.send(data, devices).then((response) => { + gcm.send(data, devices).then((response) => { expect(Array.isArray(response)).toBe(true); expect(response.length).toEqual(devices.length); expect(response.length).toEqual(4); - response.forEach((res, index) => { + response.forEach((res, index) => { expect(res.transmitted).toEqual(true); expect(res.device).toEqual(devices[index]); }) @@ -256,21 +256,22 @@ describe('GCM', () => { }); it('can send GCM request with slices', (done) => { - let originalMax = GCM.GCMRegistrationTokensMax; + spyOn(log, 'error').and.callFake(() => {}); + const originalMax = GCM.GCMRegistrationTokensMax; GCM.GCMRegistrationTokensMax = 2; - var gcm = new GCM({ + const gcm = new GCM({ apiKey: 'apiKey' }); // Mock data - var expirationTime = 2454538822113; - var data = { + const expirationTime = 2454538822113; + const data = { 'expiration_time': expirationTime, 'data': { 'alert': 'alert' } } // Mock devices - var devices = [ + const devices = [ { deviceToken: 'token' }, @@ -297,17 +298,17 @@ describe('GCM', () => { } ]; spyOn(gcm, 'send').and.callThrough(); - gcm.send(data, devices).then((response) => { + gcm.send(data, devices).then((response) => { expect(Array.isArray(response)).toBe(true); expect(response.length).toEqual(devices.length); expect(response.length).toEqual(8); - response.forEach((res, index) => { + response.forEach((res, index) => { expect(res.transmitted).toEqual(false); expect(res.device).toEqual(devices[index]); }); // 1 original call // 4 calls (1 per slice of 2) - expect(gcm.send.calls.count()).toBe(1+4); + expect(gcm.send.calls.count()).toBe(1 + 4); GCM.GCMRegistrationTokensMax = originalMax; done(); }) @@ -315,9 +316,9 @@ describe('GCM', () => { it('can slice devices', (done) => { // Mock devices - var devices = [makeDevice(1), makeDevice(2), makeDevice(3), makeDevice(4)]; + const devices = [makeDevice(1), makeDevice(2), makeDevice(3), makeDevice(4)]; - var chunkDevices = GCM.sliceDevices(devices, 3); + const chunkDevices = GCM.sliceDevices(devices, 3); expect(chunkDevices).toEqual([ [makeDevice(1), makeDevice(2), makeDevice(3)], [makeDevice(4)] diff --git a/spec/MockAPNProvider.js b/spec/MockAPNProvider.js index 229b9da..12d0c4c 100644 --- a/spec/MockAPNProvider.js +++ b/spec/MockAPNProvider.js @@ -1,16 +1,16 @@ import EventEmitter from 'events'; const MockAPNProvider = function (args) { - let emitter = new EventEmitter(); + const emitter = new EventEmitter(); emitter.options = args; emitter.send = function(push, devices) { if (!Array.isArray(devices)) { devices = [devices]; } - let sent = []; - let failed = []; + const sent = []; + const failed = []; - devices.forEach((device) => { + devices.forEach((device) => { if (args.shouldFailTransmissions) { if (args.errorBuilder) { failed.push() diff --git a/spec/ParsePushAdapter.spec.js b/spec/ParsePushAdapter.spec.js index 10a5e9c..4cbbb81 100644 --- a/spec/ParsePushAdapter.spec.js +++ b/spec/ParsePushAdapter.spec.js @@ -1,7 +1,8 @@ import { join } from 'path'; +import log from 'npmlog'; import apn from '@parse/node-apn'; import ParsePushAdapterPackage, { ParsePushAdapter as _ParsePushAdapter, APNS as _APNS, GCM as _GCM, WEB as _WEB, EXPO as _EXPO, utils } from '../src/index.js'; -var ParsePushAdapter = _ParsePushAdapter; +const ParsePushAdapter = _ParsePushAdapter; import { randomString } from '../src/PushAdapterUtils.js'; import MockAPNProvider from './MockAPNProvider.js'; import APNS from '../src/APNS.js'; @@ -12,7 +13,7 @@ import EXPO from '../src/EXPO.js'; describe('ParsePushAdapter', () => { - beforeEach(() => { + beforeEach(() => { spyOn(apn, 'Provider').and.callFake(MockAPNProvider); }); @@ -28,7 +29,7 @@ describe('ParsePushAdapter', () => { it('can be initialized', (done) => { // Make mock config - var pushConfig = { + const pushConfig = { web: { vapidDetails: { subject: 'test@example.com', @@ -59,24 +60,24 @@ describe('ParsePushAdapter', () => { ] }; - var parsePushAdapter = new ParsePushAdapter(pushConfig); + const parsePushAdapter = new ParsePushAdapter(pushConfig); // Check ios - var iosSender = parsePushAdapter.senderMap['ios']; + const iosSender = parsePushAdapter.senderMap['ios']; expect(iosSender instanceof APNS).toBe(true); // Check android - var androidSender = parsePushAdapter.senderMap['android']; + const androidSender = parsePushAdapter.senderMap['android']; expect(androidSender instanceof GCM).toBe(true); // Check web - var webSender = parsePushAdapter.senderMap['web']; + const webSender = parsePushAdapter.senderMap['web']; expect(webSender instanceof WEB).toBe(true); // Check expo - var expoSender = parsePushAdapter.senderMap['expo']; + const expoSender = parsePushAdapter.senderMap['expo']; expect(expoSender instanceof EXPO).toBe(true); done(); }); it("can be initialized with FCM for android and ios", (done) => { - var pushConfig = { + const pushConfig = { android: { firebaseServiceAccount: join(__dirname, '..', 'spec', 'support', 'fakeServiceAccount.json') }, @@ -85,16 +86,16 @@ describe('ParsePushAdapter', () => { }, }; - var parsePushAdapter = new ParsePushAdapter(pushConfig); - var iosSender = parsePushAdapter.senderMap["ios"]; + const parsePushAdapter = new ParsePushAdapter(pushConfig); + const iosSender = parsePushAdapter.senderMap["ios"]; expect(iosSender instanceof FCM).toBe(true); - var androidSender = parsePushAdapter.senderMap["android"]; + const androidSender = parsePushAdapter.senderMap["android"]; expect(androidSender instanceof FCM).toBe(true); done(); }); it("can be initialized with FCM for android and APNS for apple", (done) => { - var pushConfig = { + const pushConfig = { android: { firebaseServiceAccount: join(__dirname, '..', 'spec', 'support', 'fakeServiceAccount.json') }, @@ -114,16 +115,16 @@ describe('ParsePushAdapter', () => { ], }; - var parsePushAdapter = new ParsePushAdapter(pushConfig); - var iosSender = parsePushAdapter.senderMap["ios"]; + const parsePushAdapter = new ParsePushAdapter(pushConfig); + const iosSender = parsePushAdapter.senderMap["ios"]; expect(iosSender instanceof APNS).toBe(true); - var androidSender = parsePushAdapter.senderMap["android"]; + const androidSender = parsePushAdapter.senderMap["android"]; expect(androidSender instanceof FCM).toBe(true); done(); }); it("can be initialized with FCM for apple and GCM for android", (done) => { - var pushConfig = { + const pushConfig = { android: { senderId: "senderId", apiKey: "apiKey", @@ -133,17 +134,17 @@ describe('ParsePushAdapter', () => { }, }; - var parsePushAdapter = new ParsePushAdapter(pushConfig); - var iosSender = parsePushAdapter.senderMap["ios"]; + const parsePushAdapter = new ParsePushAdapter(pushConfig); + const iosSender = parsePushAdapter.senderMap["ios"]; expect(iosSender instanceof FCM).toBe(true); - var androidSender = parsePushAdapter.senderMap["android"]; + const androidSender = parsePushAdapter.senderMap["android"]; expect(androidSender instanceof GCM).toBe(true); done(); }); it('can throw on initializing with unsupported push type', (done) => { // Make mock config - var pushConfig = { + const pushConfig = { win: { senderId: 'senderId', apiKey: 'apiKey' @@ -157,7 +158,7 @@ describe('ParsePushAdapter', () => { }); it('can get valid push types', (done) => { - var parsePushAdapter = new ParsePushAdapter(); + const parsePushAdapter = new ParsePushAdapter(); expect(parsePushAdapter.getValidPushTypes()).toEqual(['ios', 'osx', 'tvos', 'android', 'fcm', 'web', 'expo']); done(); @@ -165,8 +166,8 @@ describe('ParsePushAdapter', () => { it('can classify installation', (done) => { // Mock installations - var validPushTypes = ['ios', 'osx', 'tvos', 'android', 'fcm', 'web', 'expo']; - var installations = [ + const validPushTypes = ['ios', 'osx', 'tvos', 'android', 'fcm', 'web', 'expo']; + const installations = [ { deviceType: 'android', deviceToken: 'androidToken' @@ -202,7 +203,7 @@ describe('ParsePushAdapter', () => { } ]; - var deviceMap = ParsePushAdapter.classifyInstallations(installations, validPushTypes); + const deviceMap = ParsePushAdapter.classifyInstallations(installations, validPushTypes); expect(deviceMap['android']).toEqual([makeDevice('androidToken', 'android')]); expect(deviceMap['ios']).toEqual([makeDevice('iosToken', 'ios')]); expect(deviceMap['osx']).toEqual([makeDevice('osxToken', 'osx')]); @@ -215,24 +216,24 @@ describe('ParsePushAdapter', () => { it('can send push notifications', (done) => { - var parsePushAdapter = new ParsePushAdapter(); + const parsePushAdapter = new ParsePushAdapter(); // Mock senders - var androidSender = { + const androidSender = { send: jasmine.createSpy('send') }; - var iosSender = { + const iosSender = { send: jasmine.createSpy('send') }; - var osxSender = { + const osxSender = { send: jasmine.createSpy('send') } - var webSender = { + const webSender = { send: jasmine.createSpy('send') } - var expoSender = { + const expoSender = { send: jasmine.createSpy('send') } - var senderMap = { + const senderMap = { osx: osxSender, ios: iosSender, android: androidSender, @@ -241,7 +242,7 @@ describe('ParsePushAdapter', () => { }; parsePushAdapter.senderMap = senderMap; // Mock installations - var installations = [ + const installations = [ { deviceType: 'android', deviceToken: 'androidToken' @@ -272,12 +273,12 @@ describe('ParsePushAdapter', () => { deviceToken: 'expoToken' } ]; - var data = {}; + const data = {}; parsePushAdapter.send(data, installations); // Check android sender expect(androidSender.send).toHaveBeenCalled(); - var args = androidSender.send.calls.first().args; + let args = androidSender.send.calls.first().args; expect(args[0]).toEqual(data); expect(args[1]).toEqual([ makeDevice('androidToken', 'android') @@ -314,21 +315,21 @@ describe('ParsePushAdapter', () => { }); it('can send push notifications by pushType and failback by deviceType', (done) => { - var parsePushAdapter = new ParsePushAdapter(); + const parsePushAdapter = new ParsePushAdapter(); // Mock senders - var androidSender = { + const androidSender = { send: jasmine.createSpy('send') }; - var iosSender = { + const iosSender = { send: jasmine.createSpy('send') }; - var senderMap = { + const senderMap = { ios: iosSender, android: androidSender }; parsePushAdapter.senderMap = senderMap; // Mock installations - var installations = [ + const installations = [ { deviceType: 'android', deviceToken: 'androidToken' @@ -370,12 +371,12 @@ describe('ParsePushAdapter', () => { deviceToken: undefined } ]; - var data = {}; + const data = {}; parsePushAdapter.send(data, installations); // Check android sender expect(androidSender.send).toHaveBeenCalled(); - var args = androidSender.send.calls.first().args; + let args = androidSender.send.calls.first().args; expect(args[0]).toEqual(data); expect(args[1]).toEqual([ makeDevice('androidToken', 'android'), @@ -395,7 +396,8 @@ describe('ParsePushAdapter', () => { }); it('reports properly results', (done) => { - var pushConfig = { + spyOn(log, 'error').and.callFake(() => {}); + const pushConfig = { web: { vapidDetails: { subject: 'test@example.com', @@ -427,7 +429,7 @@ describe('ParsePushAdapter', () => { } ] }; - var installations = [ + const installations = [ { deviceType: 'android', deviceToken: 'androidToken' @@ -471,7 +473,7 @@ describe('ParsePushAdapter', () => { } ]; - var parsePushAdapter = new ParsePushAdapter(pushConfig); + const parsePushAdapter = new ParsePushAdapter(pushConfig); parsePushAdapter.send({ data: { alert: 'some' } }, installations).then((results) => { expect(Array.isArray(results)).toBe(true); @@ -496,14 +498,16 @@ describe('ParsePushAdapter', () => { } }) done(); - }).catch((err) => { + }).catch(() => { fail('Should not fail'); done(); }) }); - it('reports properly failures when all transmissions have failed', (done) => { - var pushConfig = { + it('reports properly failures when all transmissions have failed', (done) => { + spyOn(log, 'error').and.callFake(() => {}); + spyOn(log, 'warn').and.callFake(() => {}); + const pushConfig = { ios: [ { cert: 'cert.cer', @@ -514,7 +518,7 @@ describe('ParsePushAdapter', () => { } ] }; - var installations = [ + const installations = [ { deviceType: 'ios', deviceToken: '0d72a1baa92a2febd9a254cbd6584f750c70b2350af5fc9052d1d12584b738e6', @@ -522,8 +526,8 @@ describe('ParsePushAdapter', () => { } ]; - var parsePushAdapter = new ParsePushAdapter(pushConfig); - parsePushAdapter.send({data: {alert: 'some'}}, installations).then((results) => { + const parsePushAdapter = new ParsePushAdapter(pushConfig); + parsePushAdapter.send({data: {alert: 'some'}}, installations).then((results) => { expect(Array.isArray(results)).toBe(true); // 2x iOS, 1x android, 1x osx, 1x tvos @@ -540,7 +544,7 @@ describe('ParsePushAdapter', () => { expect(result.transmitted).toBe(false); expect(typeof result.response.error).toBe('string'); done(); - }).catch((err) => { + }).catch(() => { fail('Should not fail'); done(); }) @@ -548,7 +552,8 @@ describe('ParsePushAdapter', () => { // Xited till we can retry on other connections it('reports properly select connection', (done) => { - var pushConfig = { + spyOn(log, 'warn').and.callFake(() => {}); + const pushConfig = { ios: [ { cert: 'cert.cer', @@ -565,7 +570,7 @@ describe('ParsePushAdapter', () => { } ] }; - var installations = [ + const installations = [ { deviceType: 'ios', deviceToken: '0d72a1baa92a2febd9a254cbd6584f750c70b2350af5fc9052d1d12584b738e6', @@ -573,8 +578,8 @@ describe('ParsePushAdapter', () => { } ]; - var parsePushAdapter = new ParsePushAdapter(pushConfig); - parsePushAdapter.send({data: {alert: 'some'}}, installations).then((results) => { + const parsePushAdapter = new ParsePushAdapter(pushConfig); + parsePushAdapter.send({data: {alert: 'some'}}, installations).then((results) => { expect(Array.isArray(results)).toBe(true); // 1x iOS @@ -590,33 +595,33 @@ describe('ParsePushAdapter', () => { expect(typeof device.deviceToken).toBe('string'); expect(result.transmitted).toBe(true); done(); - }).catch((err) => { + }).catch(() => { fail('Should not fail'); done(); }) }); it('properly marks not transmitter when sender is missing', (done) => { - var pushConfig = { + const pushConfig = { android: { senderId: 'senderId', apiKey: 'apiKey' } }; - var installations = [{ - deviceType: 'ios', - deviceToken: '0d72a1baa92a2febd9a254cbd6584f750c70b2350af5fc9052d1d12584b738e6', - appIdentifier: 'invalidiosbundleId' - }, - { - deviceType: 'ios', - deviceToken: 'ff3943ed0b2090c47e5d6f07d8f202a10427941d7897fda5a6b18c6d9fd07d48', - appIdentifier: 'invalidiosbundleId' - }] - var parsePushAdapter = new ParsePushAdapter(pushConfig); - parsePushAdapter.send({data: {alert: 'some'}}, installations).then((results) => { + const installations = [{ + deviceType: 'ios', + deviceToken: '0d72a1baa92a2febd9a254cbd6584f750c70b2350af5fc9052d1d12584b738e6', + appIdentifier: 'invalidiosbundleId' + }, + { + deviceType: 'ios', + deviceToken: 'ff3943ed0b2090c47e5d6f07d8f202a10427941d7897fda5a6b18c6d9fd07d48', + appIdentifier: 'invalidiosbundleId' + }] + const parsePushAdapter = new ParsePushAdapter(pushConfig); + parsePushAdapter.send({data: {alert: 'some'}}, installations).then((results) => { expect(results.length).toBe(2); - results.forEach((result) => { + results.forEach((result) => { expect(result.transmitted).toBe(false); expect(typeof result.device).toBe('object'); expect(typeof result.device.deviceType).toBe('string'); diff --git a/spec/WEB.spec.js b/spec/WEB.spec.js index 93cd5d4..1eb2a7e 100644 --- a/spec/WEB.spec.js +++ b/spec/WEB.spec.js @@ -34,7 +34,7 @@ function mockSender() { } function mockWebPush(success) { - return spyOn(webpush, 'sendNotification').and.callFake((deviceToken, payload, options) => { + return spyOn(webpush, 'sendNotification').and.callFake(() => { if (success) { return Promise.resolve({ statusCode: 201 }); } @@ -70,7 +70,7 @@ describe('WEB', () => { return Promise.resolve({ sent: 1, failed: 0, - results: [{ result: 201 }], + results: [{ result: 201 }], }); }); const data = { data: { alert: 'alert' } }; @@ -98,7 +98,7 @@ describe('WEB', () => { return Promise.resolve({ sent: 0, failed: 1, - results: [{ error: 'push subscription has unsubscribed or expired.' }], + results: [{ error: 'push subscription has unsubscribed or expired.' }], }); }); const data = { data: { alert: 'alert' } }; @@ -132,7 +132,7 @@ describe('WEB', () => { const response = await web.send(data, devices); expect(Array.isArray(response)).toBe(true); expect(response.length).toEqual(devices.length); - response.forEach((res, index) => { + response.forEach((res, index) => { expect(res.transmitted).toEqual(true); expect(res.device).toEqual(devices[index]); }); @@ -154,7 +154,7 @@ describe('WEB', () => { const response = await web.send(data, devices); expect(Array.isArray(response)).toBe(true); expect(response.length).toEqual(devices.length); - response.forEach((res, index) => { + response.forEach((res, index) => { expect(res.transmitted).toEqual(false); expect(res.device).toEqual(devices[index]); }); diff --git a/src/APNS.js b/src/APNS.js index c78fec1..b68f2f6 100644 --- a/src/APNS.js +++ b/src/APNS.js @@ -39,7 +39,7 @@ export class APNS { } // Create Provider from each arg-object - for (let apnsArgs of apnsArgsList) { + for (const apnsArgs of apnsArgsList) { // rewrite bundleId to topic for backward-compatibility if (apnsArgs.bundleId) { @@ -47,7 +47,7 @@ export class APNS { apnsArgs.topic = apnsArgs.bundleId } - let provider = APNS._createProvider(apnsArgs); + const provider = APNS._createProvider(apnsArgs); this.providers.push(provider); } @@ -70,42 +70,42 @@ export class APNS { * @returns {Object} A promise which is resolved immediately */ send(data, allDevices) { - let coreData = data && data.data; + const coreData = data && data.data; if (!coreData || !allDevices || !Array.isArray(allDevices)) { log.warn(LOG_PREFIX, 'invalid push payload'); return; } - let expirationTime = data['expiration_time'] || coreData['expiration_time']; - let collapseId = data['collapse_id'] || coreData['collapse_id']; - let pushType = data['push_type'] || coreData['push_type']; - let priority = data['priority'] || coreData['priority']; + const expirationTime = data['expiration_time'] || coreData['expiration_time']; + const collapseId = data['collapse_id'] || coreData['collapse_id']; + const pushType = data['push_type'] || coreData['push_type']; + const priority = data['priority'] || coreData['priority']; let allPromises = []; - let devicesPerAppIdentifier = {}; + const devicesPerAppIdentifier = {}; // Start by clustering the devices per appIdentifier allDevices.forEach(device => { - let appIdentifier = device.appIdentifier; + const appIdentifier = device.appIdentifier; devicesPerAppIdentifier[appIdentifier] = devicesPerAppIdentifier[appIdentifier] || []; devicesPerAppIdentifier[appIdentifier].push(device); }); - for (let key in devicesPerAppIdentifier) { - let devices = devicesPerAppIdentifier[key]; - let appIdentifier = devices[0].appIdentifier; - let providers = this._chooseProviders(appIdentifier); + for (const key in devicesPerAppIdentifier) { + const devices = devicesPerAppIdentifier[key]; + const appIdentifier = devices[0].appIdentifier; + const providers = this._chooseProviders(appIdentifier); // No Providers found if (!providers || providers.length === 0) { - let errorPromises = devices.map(device => APNS._createErrorPromise(device.deviceToken, 'No Provider found')); + const errorPromises = devices.map(device => APNS._createErrorPromise(device.deviceToken, 'No Provider found')); allPromises = allPromises.concat(errorPromises); continue; } - let headers = { expirationTime: expirationTime, topic: appIdentifier, collapseId: collapseId, pushType: pushType, priority: priority } - let notification = APNS._generateNotification(coreData, headers); + const headers = { expirationTime: expirationTime, topic: appIdentifier, collapseId: collapseId, pushType: pushType, priority: priority } + const notification = APNS._generateNotification(coreData, headers); const deviceIds = devices.map(device => device.deviceToken); - let promise = this.sendThroughProvider(notification, deviceIds, providers); + const promise = this.sendThroughProvider(notification, deviceIds, providers); allPromises.push(promise.then(this._handlePromise.bind(this))); } @@ -117,25 +117,25 @@ export class APNS { sendThroughProvider(notification, devices, providers) { return providers[0] - .send(notification, devices) - .then((response) => { - if (response.failed + .send(notification, devices) + .then((response) => { + if (response.failed && response.failed.length > 0 && providers && providers.length > 1) { - let devices = response.failed.map((failure) => { return failure.device; }); - // Reset the failures as we'll try next connection - response.failed = []; - return this.sendThroughProvider(notification, - devices, - providers.slice(1, providers.length)).then((retryResponse) => { - response.failed = response.failed.concat(retryResponse.failed); - response.sent = response.sent.concat(retryResponse.sent); - return response; - }); - } else { + const devices = response.failed.map((failure) => { return failure.device; }); + // Reset the failures as we'll try next connection + response.failed = []; + return this.sendThroughProvider(notification, + devices, + providers.slice(1, providers.length)).then((retryResponse) => { + response.failed = response.failed.concat(retryResponse.failed); + response.sent = response.sent.concat(retryResponse.sent); return response; - } - }); + }); + } else { + return response; + } + }); } static _validateAPNArgs(apnsArgs) { @@ -154,7 +154,7 @@ export class APNS { throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, 'topic is mssing for %j', apnsArgs); } - let provider = new apn.Provider(apnsArgs); + const provider = new apn.Provider(apnsArgs); // Sets the topic on this provider provider.topic = apnsArgs.topic; @@ -176,48 +176,46 @@ export class APNS { * @returns {Object} A apns Notification */ static _generateNotification(coreData, headers) { - let notification = new apn.Notification(); - let payload = {}; - for (let key in coreData) { + const notification = new apn.Notification(); + const payload = {}; + for (const key in coreData) { switch (key) { - case 'aps': - notification.aps = coreData.aps; - break; - case 'alert': - notification.setAlert(coreData.alert); - break; - case 'title': - notification.setTitle(coreData.title); + case 'aps': + notification.aps = coreData.aps; + break; + case 'alert': + notification.setAlert(coreData.alert); + break; + case 'title': + notification.setTitle(coreData.title); + break; + case 'badge': + notification.setBadge(coreData.badge); + break; + case 'sound': + notification.setSound(coreData.sound); + break; + case 'content-available': + notification.setContentAvailable(coreData['content-available'] === 1); + break; + case 'mutable-content': + notification.setMutableContent(coreData['mutable-content'] === 1); + break; + case 'targetContentIdentifier': + notification.setTargetContentIdentifier(coreData.targetContentIdentifier); + break; + case 'interruptionLevel': + notification.setInterruptionLevel(coreData.interruptionLevel); + break; + case 'category': + notification.setCategory(coreData.category); + break; + case 'threadId': + notification.setThreadId(coreData.threadId); + break; + default: + payload[key] = coreData[key]; break; - case 'badge': - notification.setBadge(coreData.badge); - break; - case 'sound': - notification.setSound(coreData.sound); - break; - case 'content-available': - let isAvailable = coreData['content-available'] === 1; - notification.setContentAvailable(isAvailable); - break; - case 'mutable-content': - let isMutable = coreData['mutable-content'] === 1; - notification.setMutableContent(isMutable); - break; - case 'targetContentIdentifier': - notification.setTargetContentIdentifier(coreData.targetContentIdentifier); - break; - case 'interruptionLevel': - notification.setInterruptionLevel(coreData.interruptionLevel); - break; - case 'category': - notification.setCategory(coreData.category); - break; - case 'threadId': - notification.setThreadId(coreData.threadId); - break; - default: - payload[key] = coreData[key]; - break; } } @@ -251,7 +249,7 @@ export class APNS { }*/ // Otherwise we try to match the appIdentifier with topic on provider - let qualifiedProviders = this.providers.filter((provider) => appIdentifier === provider.topic); + const qualifiedProviders = this.providers.filter((provider) => appIdentifier === provider.topic); if (qualifiedProviders.length > 0) { return qualifiedProviders; @@ -263,7 +261,7 @@ export class APNS { } _handlePromise(response) { - let promises = []; + const promises = []; response.sent.forEach((token) => { log.verbose(LOG_PREFIX, 'APNS transmitted to %s', token.device); promises.push(APNS._createSuccesfullPromise(token.device)); @@ -282,8 +280,8 @@ export class APNS { log.error(LOG_PREFIX, 'APNS error transmitting to device %s with status %s and reason %s', failure.device, failure.status, failure.response.reason); return APNS._createErrorPromise(failure.device, failure.response.reason); } else { - log.error(LOG_PREFIX, 'APNS error transmitting to device with unkown error'); - return APNS._createErrorPromise(failure.device, 'Unkown status'); + log.error(LOG_PREFIX, 'APNS error transmitting to device with unkown error'); + return APNS._createErrorPromise(failure.device, 'Unkown status'); } } diff --git a/src/EXPO.js b/src/EXPO.js index 34a5fa6..621f7f4 100644 --- a/src/EXPO.js +++ b/src/EXPO.js @@ -7,19 +7,19 @@ import { Expo } from 'expo-server-sdk'; const LOG_PREFIX = 'parse-server-push-adapter EXPO'; function expoResultToParseResponse(result) { - if (result.status === 'ok') { - return result; - } else { - // ParseServer looks for "error", and supports ceratin codes like 'NotRegistered' for - // cleanup. Expo returns slighyly different ones so changing to match what is expected - // This can be taken out if the responsibility gets moved to the adapter itself. - const error = result.message === 'DeviceNotRegistered' ? - 'NotRegistered' : result.message; - return { - error, - ...result - } + if (result.status === 'ok') { + return result; + } else { + // ParseServer looks for "error", and supports ceratin codes like 'NotRegistered' for + // cleanup. Expo returns slighyly different ones so changing to match what is expected + // This can be taken out if the responsibility gets moved to the adapter itself. + const error = result.message === 'DeviceNotRegistered' ? + 'NotRegistered' : result.message; + return { + error, + ...result } + } } export class EXPO { @@ -60,7 +60,7 @@ export class EXPO { const resolvers = []; const promises = deviceTokens.map(() => new Promise(resolve => resolvers.push(resolve))); - let length = deviceTokens.length; + const length = deviceTokens.length; log.verbose(LOG_PREFIX, `sending to ${length} ${length > 1 ? 'devices' : 'device'}`); diff --git a/src/FCM.js b/src/FCM.js index 7dd3a71..400e020 100644 --- a/src/FCM.js +++ b/src/FCM.js @@ -142,14 +142,14 @@ function _APNSToFCMPayload(requestData) { coreData = requestData.data; } - let expirationTime = + const expirationTime = requestData['expiration_time'] || coreData['expiration_time']; - let collapseId = requestData['collapse_id'] || coreData['collapse_id']; - let pushType = requestData['push_type'] || coreData['push_type']; - let priority = requestData['priority'] || coreData['priority']; + const collapseId = requestData['collapse_id'] || coreData['collapse_id']; + const pushType = requestData['push_type'] || coreData['push_type']; + const priority = requestData['priority'] || coreData['priority']; - let apnsPayload = { apns: { payload: { aps: {} } } }; - let headers = {}; + const apnsPayload = { apns: { payload: { aps: {} } } }; + const headers = {}; // Set to alert by default if not set explicitly headers['apns-push-type'] = 'alert'; @@ -172,70 +172,70 @@ function _APNSToFCMPayload(requestData) { apnsPayload.apns.headers = headers; } - for (let key in coreData) { + for (const key in coreData) { switch (key) { - case 'aps': - apnsPayload['apns']['payload']['aps'] = coreData.aps; - break; - case 'alert': - if (typeof coreData.alert == 'object') { - // When we receive a dictionary, use as is to remain - // compatible with how the APNS.js + node-apn work - apnsPayload['apns']['payload']['aps']['alert'] = coreData.alert; - } else { - // When we receive a value, prepare `alert` dictionary - // and set its `body` property - apnsPayload['apns']['payload']['aps']['alert'] = {}; - apnsPayload['apns']['payload']['aps']['alert']['body'] = coreData.alert; - } - break; - case 'title': - // Ensure the alert object exists before trying to assign the title - // title always goes into the nested `alert` dictionary - if (!apnsPayload['apns']['payload']['aps'].hasOwnProperty('alert')) { - apnsPayload['apns']['payload']['aps']['alert'] = {}; - } - apnsPayload['apns']['payload']['aps']['alert']['title'] = coreData.title; - break; - case 'badge': - apnsPayload['apns']['payload']['aps']['badge'] = coreData.badge; - break; - case 'sound': - apnsPayload['apns']['payload']['aps']['sound'] = coreData.sound; - break; - case 'content-available': - apnsPayload['apns']['payload']['aps']['content-available'] = + case 'aps': + apnsPayload['apns']['payload']['aps'] = coreData.aps; + break; + case 'alert': + if (typeof coreData.alert == 'object') { + // When we receive a dictionary, use as is to remain + // compatible with how the APNS.js + node-apn work + apnsPayload['apns']['payload']['aps']['alert'] = coreData.alert; + } else { + // When we receive a value, prepare `alert` dictionary + // and set its `body` property + apnsPayload['apns']['payload']['aps']['alert'] = {}; + apnsPayload['apns']['payload']['aps']['alert']['body'] = coreData.alert; + } + break; + case 'title': + // Ensure the alert object exists before trying to assign the title + // title always goes into the nested `alert` dictionary + if (!apnsPayload['apns']['payload']['aps'].hasOwnProperty('alert')) { + apnsPayload['apns']['payload']['aps']['alert'] = {}; + } + apnsPayload['apns']['payload']['aps']['alert']['title'] = coreData.title; + break; + case 'badge': + apnsPayload['apns']['payload']['aps']['badge'] = coreData.badge; + break; + case 'sound': + apnsPayload['apns']['payload']['aps']['sound'] = coreData.sound; + break; + case 'content-available': + apnsPayload['apns']['payload']['aps']['content-available'] = coreData['content-available']; - break; - case 'mutable-content': - apnsPayload['apns']['payload']['aps']['mutable-content'] = + break; + case 'mutable-content': + apnsPayload['apns']['payload']['aps']['mutable-content'] = coreData['mutable-content']; - break; - case 'targetContentIdentifier': - apnsPayload['apns']['payload']['aps']['target-content-id'] = + break; + case 'targetContentIdentifier': + apnsPayload['apns']['payload']['aps']['target-content-id'] = coreData.targetContentIdentifier; - break; - case 'interruptionLevel': - apnsPayload['apns']['payload']['aps']['interruption-level'] = + break; + case 'interruptionLevel': + apnsPayload['apns']['payload']['aps']['interruption-level'] = coreData.interruptionLevel; - break; - case 'category': - apnsPayload['apns']['payload']['aps']['category'] = coreData.category; - break; - case 'threadId': - apnsPayload['apns']['payload']['aps']['thread-id'] = coreData.threadId; - break; - case 'expiration_time': // Exclude header-related fields as these are set above - break; - case 'collapse_id': - break; - case 'push_type': - break; - case 'priority': - break; - default: - apnsPayload['apns']['payload'][key] = coreData[key]; // Custom keys should be outside aps - break; + break; + case 'category': + apnsPayload['apns']['payload']['aps']['category'] = coreData.category; + break; + case 'threadId': + apnsPayload['apns']['payload']['aps']['thread-id'] = coreData.threadId; + break; + case 'expiration_time': // Exclude header-related fields as these are set above + break; + case 'collapse_id': + break; + case 'push_type': + break; + case 'priority': + break; + default: + apnsPayload['apns']['payload'][key] = coreData[key]; // Custom keys should be outside aps + break; } } return apnsPayload; diff --git a/src/GCM.js b/src/GCM.js index 7f6c822..aa7345a 100644 --- a/src/GCM.js +++ b/src/GCM.js @@ -12,7 +12,7 @@ const GCMRegistrationTokensMax = 1000; export default function GCM(args) { if (typeof args !== 'object' || !args.apiKey) { throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, - 'GCM Configuration is invalid'); + 'GCM Configuration is invalid'); } this.sender = new gcm.Sender(args.apiKey, args.requestOptions); } @@ -30,23 +30,23 @@ GCM.prototype.send = function(data, devices) { log.warn(LOG_PREFIX, 'invalid push payload'); return; } - let pushId = randomString(10); + const pushId = randomString(10); // Make a new array - devices=devices.slice(0); - let timestamp = Date.now(); + devices = devices.slice(0); + const timestamp = Date.now(); // For android, we can only have 1000 recepients per send, so we need to slice devices to // chunk if necessary - let slices = sliceDevices(devices, GCM.GCMRegistrationTokensMax); + const slices = sliceDevices(devices, GCM.GCMRegistrationTokensMax); if (slices.length > 1) { log.verbose(LOG_PREFIX, `the number of devices exceeds ${GCMRegistrationTokensMax}`); // Make 1 send per slice - let promises = slices.reduce((memo, slice) => { - let promise = this.send(data, slice, timestamp); + const promises = slices.reduce((memo, slice) => { + const promise = this.send(data, slice, timestamp); memo.push(promise); return memo; }, []) - return Promise.all(promises).then((results) => { - let allResults = results.reduce((memo, result) => { + return Promise.all(promises).then((results) => { + const allResults = results.reduce((memo, result) => { return memo.concat(result); }, []); return Promise.resolve(allResults); @@ -63,23 +63,23 @@ GCM.prototype.send = function(data, devices) { } // Generate gcm payload // PushId is not a formal field of GCM, but Parse Android SDK uses this field to deduplicate push notifications - let gcmPayload = generateGCMPayload(data, pushId, timestamp, expirationTime); + const gcmPayload = generateGCMPayload(data, pushId, timestamp, expirationTime); // Make and send gcm request - let message = new gcm.Message(gcmPayload); + const message = new gcm.Message(gcmPayload); // Build a device map - let devicesMap = devices.reduce((memo, device) => { + const devicesMap = devices.reduce((memo, device) => { memo[device.deviceToken] = device; return memo; }, {}); - let deviceTokens = Object.keys(devicesMap); + const deviceTokens = Object.keys(devicesMap); const resolvers = []; - const promises = deviceTokens.map(() => new Promise(resolve => resolvers.push(resolve))); - let registrationTokens = deviceTokens; - let length = registrationTokens.length; - log.verbose(LOG_PREFIX, `sending to ${length} ${length > 1 ? 'devices' : 'device'}`); + const promises = deviceTokens.map(() => new Promise(resolve => resolvers.push(resolve))); + const registrationTokens = deviceTokens; + const length = registrationTokens.length; + log.verbose(LOG_PREFIX, `sending to ${length} ${length > 1 ? 'devices' : 'device'}`); this.sender.send(message, { registrationTokens: registrationTokens }, 5, (error, response) => { // example response: /* @@ -97,13 +97,13 @@ GCM.prototype.send = function(data, devices) { } else { log.verbose(LOG_PREFIX, `GCM Response: %s`, JSON.stringify(response, null, 4)); } - let { results, multicast_id } = response || {}; + const { results, multicast_id } = response || {}; registrationTokens.forEach((token, index) => { - let resolve = resolvers[index]; - let result = results ? results[index] : undefined; - let device = devicesMap[token]; + const resolve = resolvers[index]; + const result = results ? results[index] : undefined; + const device = devicesMap[token]; device.deviceType = 'android'; - let resolution = { + const resolution = { device, multicast_id, response: error || result, @@ -128,7 +128,7 @@ GCM.prototype.send = function(data, devices) { * @returns {Object} A promise which is resolved after we get results from gcm */ function generateGCMPayload(requestData, pushId, timeStamp, expirationTime) { - let payload = { + const payload = { priority: 'high' }; payload.data = { @@ -144,7 +144,7 @@ function generateGCMPayload(requestData, pushId, timeStamp, expirationTime) { }); if (expirationTime) { - // The timeStamp and expiration is in milliseconds but gcm requires second + // The timeStamp and expiration is in milliseconds but gcm requires second let timeToLive = Math.floor((expirationTime - timeStamp) / 1000); if (timeToLive < 0) { timeToLive = 0; @@ -164,7 +164,7 @@ function generateGCMPayload(requestData, pushId, timeStamp, expirationTime) { * @returns {Array} An array which contaisn several arries of devices with fixed chunk size */ function sliceDevices(devices, chunkSize) { - let chunkDevices = []; + const chunkDevices = []; while (devices.length > 0) { chunkDevices.push(devices.splice(0, chunkSize)); } diff --git a/src/ParsePushAdapter.js b/src/ParsePushAdapter.js index 975d76e..0f2eace 100644 --- a/src/ParsePushAdapter.js +++ b/src/ParsePushAdapter.js @@ -21,38 +21,38 @@ export default class ParsePushAdapter { this.feature = { immediatePush: true }; - let pushTypes = Object.keys(pushConfig); + const pushTypes = Object.keys(pushConfig); - for (let pushType of pushTypes) { + for (const pushType of pushTypes) { // adapter may be passed as part of the parse-server initialization if (this.validPushTypes.indexOf(pushType) < 0 && pushType != 'adapter') { throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, - 'Push to ' + pushType + ' is not supported'); + 'Push to ' + pushType + ' is not supported'); } switch (pushType) { - case 'ios': - case 'tvos': - case 'osx': - if (pushConfig[pushType].hasOwnProperty('firebaseServiceAccount')) { - this.senderMap[pushType] = new FCM(pushConfig[pushType], 'apple'); - } else { - this.senderMap[pushType] = new APNS(pushConfig[pushType]); - } - break; - case 'web': - this.senderMap[pushType] = new WEB(pushConfig[pushType]); - break; - case 'expo': - this.senderMap[pushType] = new EXPO(pushConfig[pushType]); - break; - case 'android': - case 'fcm': - if (pushConfig[pushType].hasOwnProperty('firebaseServiceAccount')) { - this.senderMap[pushType] = new FCM(pushConfig[pushType], 'android'); - } else { - this.senderMap[pushType] = new GCM(pushConfig[pushType]); - } - break; + case 'ios': + case 'tvos': + case 'osx': + if (pushConfig[pushType].hasOwnProperty('firebaseServiceAccount')) { + this.senderMap[pushType] = new FCM(pushConfig[pushType], 'apple'); + } else { + this.senderMap[pushType] = new APNS(pushConfig[pushType]); + } + break; + case 'web': + this.senderMap[pushType] = new WEB(pushConfig[pushType]); + break; + case 'expo': + this.senderMap[pushType] = new EXPO(pushConfig[pushType]); + break; + case 'android': + case 'fcm': + if (pushConfig[pushType].hasOwnProperty('firebaseServiceAccount')) { + this.senderMap[pushType] = new FCM(pushConfig[pushType], 'android'); + } else { + this.senderMap[pushType] = new GCM(pushConfig[pushType]); + } + break; } } } @@ -66,16 +66,16 @@ export default class ParsePushAdapter { } send(data, installations) { - let deviceMap = classifyInstallations(installations, this.validPushTypes); - let sendPromises = []; - for (let pushType in deviceMap) { - let sender = this.senderMap[pushType]; - let devices = deviceMap[pushType]; + const deviceMap = classifyInstallations(installations, this.validPushTypes); + const sendPromises = []; + for (const pushType in deviceMap) { + const sender = this.senderMap[pushType]; + const devices = deviceMap[pushType]; if(Array.isArray(devices) && devices.length > 0) { if (!sender) { log.verbose(LOG_PREFIX, `Can not find sender for push type ${pushType}, ${data}`) - let results = devices.map((device) => { + const results = devices.map((device) => { return Promise.resolve({ device, transmitted: false, @@ -88,7 +88,7 @@ export default class ParsePushAdapter { } } } - return Promise.all(sendPromises).then((promises) => { + return Promise.all(sendPromises).then((promises) => { // flatten all return [].concat.apply([], promises); }) diff --git a/src/PushAdapterUtils.js b/src/PushAdapterUtils.js index 9abb1e8..02fabda 100644 --- a/src/PushAdapterUtils.js +++ b/src/PushAdapterUtils.js @@ -8,16 +8,16 @@ import { randomBytes } from 'crypto'; */ export function classifyInstallations(installations, validPushTypes) { // Init deviceTokenMap, create a empty array for each valid pushType - let deviceMap = {}; - for (let validPushType of validPushTypes) { + const deviceMap = {}; + for (const validPushType of validPushTypes) { deviceMap[validPushType] = []; } - for (let installation of installations) { + for (const installation of installations) { // No deviceToken, ignore if (!installation.deviceToken) { continue; } - let devices = deviceMap[installation.pushType] || deviceMap[installation.deviceType] || null; + const devices = deviceMap[installation.pushType] || deviceMap[installation.deviceType] || null; if (Array.isArray(devices)) { devices.push({ deviceToken: installation.deviceToken, @@ -33,11 +33,11 @@ export function randomString(size) { if (size === 0) { throw new Error('Zero-length randomString is useless.'); } - let chars = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' + + const chars = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' + 'abcdefghijklmnopqrstuvwxyz' + '0123456789'); let objectId = ''; - let bytes = randomBytes(size); + const bytes = randomBytes(size); for (let i = 0; i < bytes.length; ++i) { objectId += chars[bytes.readUInt8(i) % chars.length]; } diff --git a/src/WEB.js b/src/WEB.js index 503438e..4bf9c5f 100644 --- a/src/WEB.js +++ b/src/WEB.js @@ -9,7 +9,7 @@ const LOG_PREFIX = 'parse-server-push-adapter WEB'; export class WEB { /** * Create a new WEB push adapter. - * + * * @param {Object} args https://github.com/web-push-libs/web-push#api-reference */ constructor(args) { @@ -39,9 +39,9 @@ export class WEB { const deviceTokens = Object.keys(devicesMap); const resolvers = []; - const promises = deviceTokens.map(() => new Promise(resolve => resolvers.push(resolve))); - let length = deviceTokens.length; - log.verbose(LOG_PREFIX, `sending to ${length} ${length > 1 ? 'devices' : 'device'}`); + const promises = deviceTokens.map(() => new Promise(resolve => resolvers.push(resolve))); + const length = deviceTokens.length; + log.verbose(LOG_PREFIX, `sending to ${length} ${length > 1 ? 'devices' : 'device'}`); const response = await WEB.sendNotifications(coreData, deviceTokens, this.options); const { results, sent, failed } = response;