diff --git a/package-lock.json b/package-lock.json index 37897efa27..3ba72c4b9f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4703,17 +4703,17 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.9.1.tgz", - "integrity": "sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -4733,9 +4733,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dependencies": { "type-fest": "^0.20.2" }, @@ -4747,9 +4747,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.51.0.tgz", - "integrity": "sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -6074,18 +6074,23 @@ "integrity": "sha512-aKmlCO57XFZ26wso4rJsW4oTUnrgTFw2jh3io7CAtO9w4UltBNwRXvXIVzzyfkaaLRo3nluP/19msA8vDUUuKw==" }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", - "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { "node": ">=10.10.0" } }, + "node_modules/@humanwhocodes/config-array/node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==" + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -6101,7 +6106,8 @@ "node_modules/@humanwhocodes/object-schema": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true }, "node_modules/@icons/material": { "version": "0.2.4", @@ -12621,9 +12627,9 @@ } }, "node_modules/@types/json-schema": { - "version": "7.0.13", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz", - "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==" + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, "node_modules/@types/json5": { "version": "0.0.29", @@ -12971,9 +12977,9 @@ "dev": true }, "node_modules/@types/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==" + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==" }, "node_modules/@types/send": { "version": "0.17.1", @@ -13185,15 +13191,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.5.tgz", - "integrity": "sha512-bIZVSGx2UME/lmhLcjdVc7ePBwn7CLqKarUBL4me1C5feOd663liTGjMBGVcGr+BhnSLeP4SgwdvNnnkbIdkCw==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.7.5", - "@typescript-eslint/types": "6.7.5", - "@typescript-eslint/typescript-estree": "6.7.5", - "@typescript-eslint/visitor-keys": "6.7.5", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4" }, "engines": { @@ -13236,13 +13242,13 @@ "dev": true }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.5.tgz", - "integrity": "sha512-GAlk3eQIwWOJeb9F7MKQ6Jbah/vx1zETSDw8likab/eFcqkjSD7BI75SDAeC5N2L0MmConMoPvTsmkrg71+B1A==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.5", - "@typescript-eslint/visitor-keys": "6.7.5" + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -13253,9 +13259,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.5.tgz", - "integrity": "sha512-WboQBlOXtdj1tDFPyIthpKrUb+kZf2VroLZhxKa/VlwLlLyqv/PwUNgL30BlTVZV1Wu4Asu2mMYPqarSO4L5ZQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -13266,16 +13272,17 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.5.tgz", - "integrity": "sha512-NhJiJ4KdtwBIxrKl0BqG1Ur+uw7FiOnOThcYx9DpOGJ/Abc9z2xNzLeirCG02Ig3vkvrc2qFLmYSSsaITbKjlg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.5", - "@typescript-eslint/visitor-keys": "6.7.5", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", + "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" }, @@ -13301,6 +13308,15 @@ "node": ">=8" } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/@typescript-eslint/typescript-estree/node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -13350,6 +13366,21 @@ "node": ">=10" } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/typescript-estree/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -13357,9 +13388,9 @@ "dev": true }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -13387,12 +13418,12 @@ "dev": true }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.5.tgz", - "integrity": "sha512-3MaWdDZtLlsexZzDSdQWsFQ9l9nL8B80Z4fImSpyllFC/KLqWQRdEcB+gGGO+N3Q2uL40EsG66wZLsohPxNXvg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.5", + "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -13418,8 +13449,7 @@ "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" }, "node_modules/@vitest/expect": { "version": "1.3.1", @@ -20265,17 +20295,18 @@ } }, "node_modules/eslint": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.51.0.tgz", - "integrity": "sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.51.0", - "@humanwhocodes/config-array": "^0.11.11", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -38736,12 +38767,12 @@ } }, "node_modules/ts-api-utils": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", - "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", "dev": true, "engines": { - "node": ">=16.13.0" + "node": ">=16" }, "peerDependencies": { "typescript": ">=4.2.0" @@ -44107,8 +44138,8 @@ "@types/recurly__recurly-js": "^4.22.0", "@types/sinon-chai": "^3.2.8", "@types/uuid": "^9.0.8", - "@typescript-eslint/eslint-plugin": "^6.7.4", - "@typescript-eslint/parser": "^6.7.4", + "@typescript-eslint/eslint-plugin": "^7.8.0", + "@typescript-eslint/parser": "^7.8.0", "@uppy/core": "^3.8.0", "@uppy/dashboard": "^3.7.1", "@uppy/drag-drop": "^3.0.3", @@ -44446,33 +44477,61 @@ "dev": true }, "services/web/node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.4.tgz", - "integrity": "sha512-DAbgDXwtX+pDkAHwiGhqP3zWUGpW49B7eqmgpPtg+BKJXwdct79ut9+ifqOFPJGClGKSHXn2PTBatCnldJRUoA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.8.0.tgz", + "integrity": "sha512-gFTT+ezJmkwutUPmB0skOj3GZJtlEGnlssems4AjkVweUPGj7jRwwqg0Hhg7++kPGJqKtTYx+R05Ftww372aIg==", "dev": true, "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.7.4", - "@typescript-eslint/type-utils": "6.7.4", - "@typescript-eslint/utils": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.8.0", + "@typescript-eslint/type-utils": "7.8.0", + "@typescript-eslint/utils": "7.8.0", + "@typescript-eslint/visitor-keys": "7.8.0", "debug": "^4.3.4", "graphemer": "^1.4.0", - "ignore": "^5.2.4", + "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "services/web/node_modules/@typescript-eslint/parser": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.8.0.tgz", + "integrity": "sha512-KgKQly1pv0l4ltcftP59uQZCi4HUYswCLbTqVZEJu7uLX8CTLyswqMLqLN+2QFz4jCptqWVV4SB7vdxcH2+0kQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "7.8.0", + "@typescript-eslint/types": "7.8.0", + "@typescript-eslint/typescript-estree": "7.8.0", + "@typescript-eslint/visitor-keys": "7.8.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -44481,16 +44540,16 @@ } }, "services/web/node_modules/@typescript-eslint/scope-manager": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.4.tgz", - "integrity": "sha512-SdGqSLUPTXAXi7c3Ob7peAGVnmMoGzZ361VswK2Mqf8UOYcODiYvs8rs5ILqEdfvX1lE7wEZbLyELCW+Yrql1A==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.8.0.tgz", + "integrity": "sha512-viEmZ1LmwsGcnr85gIq+FCYI7nO90DVbE37/ll51hjv9aG+YZMb4WDE2fyWpUR4O/UrhGRpYXK/XajcGTk2B8g==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4" + "@typescript-eslint/types": "7.8.0", + "@typescript-eslint/visitor-keys": "7.8.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -44498,25 +44557,25 @@ } }, "services/web/node_modules/@typescript-eslint/type-utils": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.7.4.tgz", - "integrity": "sha512-n+g3zi1QzpcAdHFP9KQF+rEFxMb2KxtnJGID3teA/nxKHOVi3ylKovaqEzGBbVY2pBttU6z85gp0D00ufLzViQ==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.8.0.tgz", + "integrity": "sha512-H70R3AefQDQpz9mGv13Uhi121FNMh+WEaRqcXTX09YEDky21km4dV1ZXJIp8QjXc4ZaVkXVdohvWDzbnbHDS+A==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.7.4", - "@typescript-eslint/utils": "6.7.4", + "@typescript-eslint/typescript-estree": "7.8.0", + "@typescript-eslint/utils": "7.8.0", "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -44525,12 +44584,12 @@ } }, "services/web/node_modules/@typescript-eslint/types": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.4.tgz", - "integrity": "sha512-o9XWK2FLW6eSS/0r/tgjAGsYasLAnOWg7hvZ/dGYSSNjCh+49k5ocPN8OmG5aZcSJ8pclSOyVKP2x03Sj+RrCA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.8.0.tgz", + "integrity": "sha512-wf0peJ+ZGlcH+2ZS23aJbOv+ztjeeP8uQ9GgwMJGVLx/Nj9CJt17GWgWWoSmoRVKAX2X+7fzEnAjxdvK2gqCLw==", "dev": true, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -44538,21 +44597,22 @@ } }, "services/web/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.4.tgz", - "integrity": "sha512-ty8b5qHKatlNYd9vmpHooQz3Vki3gG+3PchmtsA4TgrZBKWHNjWfkQid7K7xQogBqqc7/BhGazxMD5vr6Ha+iQ==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.8.0.tgz", + "integrity": "sha512-5pfUCOwK5yjPaJQNy44prjCwtr981dO8Qo9J9PwYXZ0MosgAbfEMB008dJ5sNo3+/BN6ytBPuSvXUg9SAqB0dg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4", + "@typescript-eslint/types": "7.8.0", + "@typescript-eslint/visitor-keys": "7.8.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -44584,42 +44644,57 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "services/web/node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "services/web/node_modules/@typescript-eslint/utils": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.7.4.tgz", - "integrity": "sha512-PRQAs+HUn85Qdk+khAxsVV+oULy3VkbH3hQ8hxLRJXWBEd7iI+GbQxH5SEUSH7kbEoTp6oT1bOwyga24ELALTA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.8.0.tgz", + "integrity": "sha512-L0yFqOCflVqXxiZyXrDr80lnahQfSOfc9ELAAZ75sqicqp2i36kEZZGuUymHNFoYOqxRT05up760b4iGsl02nQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.7.4", - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/typescript-estree": "6.7.4", - "semver": "^7.5.4" + "@types/json-schema": "^7.0.15", + "@types/semver": "^7.5.8", + "@typescript-eslint/scope-manager": "7.8.0", + "@typescript-eslint/types": "7.8.0", + "@typescript-eslint/typescript-estree": "7.8.0", + "semver": "^7.6.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" } }, "services/web/node_modules/@typescript-eslint/visitor-keys": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.4.tgz", - "integrity": "sha512-pOW37DUhlTZbvph50x5zZCkFn3xzwkGtNoJHzIM3svpiSkJzwOYr/kVBaXmf+RAQiUDs1AHEZVNPg6UJCJpwRA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.8.0.tgz", + "integrity": "sha512-q4/gibTNBQNA0lGyYQCmWRS5D15n8rXh4QjK3KV+MBPlTYHpfBUT3D3PaPR/HeNiI9W6R7FvlkcGhNyAoP+caA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.7.4", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "7.8.0", + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -45366,9 +45441,9 @@ } }, "services/web/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -48372,14 +48447,14 @@ } }, "@eslint-community/regexpp": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.9.1.tgz", - "integrity": "sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==" + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==" }, "@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -48393,9 +48468,9 @@ }, "dependencies": { "globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "requires": { "type-fest": "^0.20.2" } @@ -48403,9 +48478,9 @@ } }, "@eslint/js": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.51.0.tgz", - "integrity": "sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==" + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==" }, "@fal-works/esbuild-plugin-global-externals": { "version": "2.1.2", @@ -49450,13 +49525,20 @@ } }, "@humanwhocodes/config-array": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", - "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "requires": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" + }, + "dependencies": { + "@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==" + } } }, "@humanwhocodes/module-importer": { @@ -49467,7 +49549,8 @@ "@humanwhocodes/object-schema": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true }, "@icons/material": { "version": "0.2.4", @@ -52480,8 +52563,8 @@ "@types/recurly__recurly-js": "^4.22.0", "@types/sinon-chai": "^3.2.8", "@types/uuid": "^9.0.8", - "@typescript-eslint/eslint-plugin": "^6.7.4", - "@typescript-eslint/parser": "^6.7.4", + "@typescript-eslint/eslint-plugin": "^7.8.0", + "@typescript-eslint/parser": "^7.8.0", "@uppy/core": "^3.8.0", "@uppy/dashboard": "^3.7.1", "@uppy/drag-drop": "^3.0.3", @@ -52858,65 +52941,79 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.4.tgz", - "integrity": "sha512-DAbgDXwtX+pDkAHwiGhqP3zWUGpW49B7eqmgpPtg+BKJXwdct79ut9+ifqOFPJGClGKSHXn2PTBatCnldJRUoA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.8.0.tgz", + "integrity": "sha512-gFTT+ezJmkwutUPmB0skOj3GZJtlEGnlssems4AjkVweUPGj7jRwwqg0Hhg7++kPGJqKtTYx+R05Ftww372aIg==", "dev": true, "requires": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.7.4", - "@typescript-eslint/type-utils": "6.7.4", - "@typescript-eslint/utils": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.8.0", + "@typescript-eslint/type-utils": "7.8.0", + "@typescript-eslint/utils": "7.8.0", + "@typescript-eslint/visitor-keys": "7.8.0", "debug": "^4.3.4", "graphemer": "^1.4.0", - "ignore": "^5.2.4", + "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + } + }, + "@typescript-eslint/parser": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.8.0.tgz", + "integrity": "sha512-KgKQly1pv0l4ltcftP59uQZCi4HUYswCLbTqVZEJu7uLX8CTLyswqMLqLN+2QFz4jCptqWVV4SB7vdxcH2+0kQ==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "7.8.0", + "@typescript-eslint/types": "7.8.0", + "@typescript-eslint/typescript-estree": "7.8.0", + "@typescript-eslint/visitor-keys": "7.8.0", + "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.4.tgz", - "integrity": "sha512-SdGqSLUPTXAXi7c3Ob7peAGVnmMoGzZ361VswK2Mqf8UOYcODiYvs8rs5ILqEdfvX1lE7wEZbLyELCW+Yrql1A==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.8.0.tgz", + "integrity": "sha512-viEmZ1LmwsGcnr85gIq+FCYI7nO90DVbE37/ll51hjv9aG+YZMb4WDE2fyWpUR4O/UrhGRpYXK/XajcGTk2B8g==", "dev": true, "requires": { - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4" + "@typescript-eslint/types": "7.8.0", + "@typescript-eslint/visitor-keys": "7.8.0" } }, "@typescript-eslint/type-utils": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.7.4.tgz", - "integrity": "sha512-n+g3zi1QzpcAdHFP9KQF+rEFxMb2KxtnJGID3teA/nxKHOVi3ylKovaqEzGBbVY2pBttU6z85gp0D00ufLzViQ==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.8.0.tgz", + "integrity": "sha512-H70R3AefQDQpz9mGv13Uhi121FNMh+WEaRqcXTX09YEDky21km4dV1ZXJIp8QjXc4ZaVkXVdohvWDzbnbHDS+A==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "6.7.4", - "@typescript-eslint/utils": "6.7.4", + "@typescript-eslint/typescript-estree": "7.8.0", + "@typescript-eslint/utils": "7.8.0", "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" } }, "@typescript-eslint/types": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.4.tgz", - "integrity": "sha512-o9XWK2FLW6eSS/0r/tgjAGsYasLAnOWg7hvZ/dGYSSNjCh+49k5ocPN8OmG5aZcSJ8pclSOyVKP2x03Sj+RrCA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.8.0.tgz", + "integrity": "sha512-wf0peJ+ZGlcH+2ZS23aJbOv+ztjeeP8uQ9GgwMJGVLx/Nj9CJt17GWgWWoSmoRVKAX2X+7fzEnAjxdvK2gqCLw==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.4.tgz", - "integrity": "sha512-ty8b5qHKatlNYd9vmpHooQz3Vki3gG+3PchmtsA4TgrZBKWHNjWfkQid7K7xQogBqqc7/BhGazxMD5vr6Ha+iQ==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.8.0.tgz", + "integrity": "sha512-5pfUCOwK5yjPaJQNy44prjCwtr981dO8Qo9J9PwYXZ0MosgAbfEMB008dJ5sNo3+/BN6ytBPuSvXUg9SAqB0dg==", "dev": true, "requires": { - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/visitor-keys": "6.7.4", + "@typescript-eslint/types": "7.8.0", + "@typescript-eslint/visitor-keys": "7.8.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "dependencies": { "globby": { @@ -52932,32 +53029,41 @@ "merge2": "^1.4.1", "slash": "^3.0.0" } + }, + "minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } } } }, "@typescript-eslint/utils": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.7.4.tgz", - "integrity": "sha512-PRQAs+HUn85Qdk+khAxsVV+oULy3VkbH3hQ8hxLRJXWBEd7iI+GbQxH5SEUSH7kbEoTp6oT1bOwyga24ELALTA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.8.0.tgz", + "integrity": "sha512-L0yFqOCflVqXxiZyXrDr80lnahQfSOfc9ELAAZ75sqicqp2i36kEZZGuUymHNFoYOqxRT05up760b4iGsl02nQ==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.7.4", - "@typescript-eslint/types": "6.7.4", - "@typescript-eslint/typescript-estree": "6.7.4", - "semver": "^7.5.4" + "@types/json-schema": "^7.0.15", + "@types/semver": "^7.5.8", + "@typescript-eslint/scope-manager": "7.8.0", + "@typescript-eslint/types": "7.8.0", + "@typescript-eslint/typescript-estree": "7.8.0", + "semver": "^7.6.0" } }, "@typescript-eslint/visitor-keys": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.4.tgz", - "integrity": "sha512-pOW37DUhlTZbvph50x5zZCkFn3xzwkGtNoJHzIM3svpiSkJzwOYr/kVBaXmf+RAQiUDs1AHEZVNPg6UJCJpwRA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.8.0.tgz", + "integrity": "sha512-q4/gibTNBQNA0lGyYQCmWRS5D15n8rXh4QjK3KV+MBPlTYHpfBUT3D3PaPR/HeNiI9W6R7FvlkcGhNyAoP+caA==", "dev": true, "requires": { - "@typescript-eslint/types": "6.7.4", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "7.8.0", + "eslint-visitor-keys": "^3.4.3" } }, "@uppy/companion-client": { @@ -53517,9 +53623,9 @@ } }, "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -56928,9 +57034,9 @@ } }, "@types/json-schema": { - "version": "7.0.13", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz", - "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==" + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, "@types/json5": { "version": "0.0.29", @@ -57278,9 +57384,9 @@ "dev": true }, "@types/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==" + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==" }, "@types/send": { "version": "0.17.1", @@ -57491,15 +57597,15 @@ } }, "@typescript-eslint/parser": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.5.tgz", - "integrity": "sha512-bIZVSGx2UME/lmhLcjdVc7ePBwn7CLqKarUBL4me1C5feOd663liTGjMBGVcGr+BhnSLeP4SgwdvNnnkbIdkCw==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "6.7.5", - "@typescript-eslint/types": "6.7.5", - "@typescript-eslint/typescript-estree": "6.7.5", - "@typescript-eslint/visitor-keys": "6.7.5", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4" }, "dependencies": { @@ -57521,32 +57627,33 @@ } }, "@typescript-eslint/scope-manager": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.7.5.tgz", - "integrity": "sha512-GAlk3eQIwWOJeb9F7MKQ6Jbah/vx1zETSDw8likab/eFcqkjSD7BI75SDAeC5N2L0MmConMoPvTsmkrg71+B1A==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", "dev": true, "requires": { - "@typescript-eslint/types": "6.7.5", - "@typescript-eslint/visitor-keys": "6.7.5" + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" } }, "@typescript-eslint/types": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.7.5.tgz", - "integrity": "sha512-WboQBlOXtdj1tDFPyIthpKrUb+kZf2VroLZhxKa/VlwLlLyqv/PwUNgL30BlTVZV1Wu4Asu2mMYPqarSO4L5ZQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.5.tgz", - "integrity": "sha512-NhJiJ4KdtwBIxrKl0BqG1Ur+uw7FiOnOThcYx9DpOGJ/Abc9z2xNzLeirCG02Ig3vkvrc2qFLmYSSsaITbKjlg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", "dev": true, "requires": { - "@typescript-eslint/types": "6.7.5", - "@typescript-eslint/visitor-keys": "6.7.5", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", + "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" }, @@ -57557,6 +57664,15 @@ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -57589,6 +57705,15 @@ "yallist": "^4.0.0" } }, + "minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -57596,9 +57721,9 @@ "dev": true }, "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -57619,12 +57744,12 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "6.7.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.5.tgz", - "integrity": "sha512-3MaWdDZtLlsexZzDSdQWsFQ9l9nL8B80Z4fImSpyllFC/KLqWQRdEcB+gGGO+N3Q2uL40EsG66wZLsohPxNXvg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", "dev": true, "requires": { - "@typescript-eslint/types": "6.7.5", + "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" }, "dependencies": { @@ -57639,8 +57764,7 @@ "@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" }, "@vitest/expect": { "version": "1.3.1", @@ -62922,17 +63046,18 @@ } }, "eslint": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.51.0.tgz", - "integrity": "sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.51.0", - "@humanwhocodes/config-array": "^0.11.11", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -77782,9 +77907,9 @@ } }, "ts-api-utils": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", - "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", "dev": true, "requires": {} }, diff --git a/services/web/.eslintrc b/services/web/.eslintrc deleted file mode 100644 index fbeea4bd3d..0000000000 --- a/services/web/.eslintrc +++ /dev/null @@ -1,370 +0,0 @@ -{ - "root": true, - "parser": "@typescript-eslint/parser", - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended", - "standard", - "prettier" - ], - "plugins": ["@overleaf"], - "env": { - "es2020": true - }, - "settings": { - // Tell eslint-plugin-react to detect which version of React we are using - "react": { - "version": "detect" - } - }, - "rules": { - "no-constant-binary-expression": "error", - - // do not allow importing of implicit dependencies. - "import/no-extraneous-dependencies": "error", - - // disable some TypeScript rules - "@typescript-eslint/no-var-requires": "off", - "@typescript-eslint/no-unused-vars": "off", - "@typescript-eslint/no-empty-function": "off", - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-this-alias": "off", - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/ban-ts-comment": "off" - }, - "overrides": [ - // NOTE: changing paths may require updating them in the Makefile too. - { - // Node - "files": [ - "**/app/src/**/*.js", - "app.js", - "i18next-scanner.config.js", - "scripts/**/*.js", - "webpack.config*.js" - ], - "env": { - "node": true - } - }, - { - // Test specific rules - "files": ["**/test/**/*.*"], - "plugins": [ - "mocha", - "chai-expect", - "chai-friendly" - ], - "env": { - "mocha": true - }, - "rules": { - // mocha-specific rules - "mocha/handle-done-callback": "error", - "mocha/no-exclusive-tests": "error", - "mocha/no-global-tests": "error", - "mocha/no-identical-title": "error", - "mocha/no-nested-tests": "error", - "mocha/no-pending-tests": "error", - "mocha/no-skipped-tests": "error", - "mocha/no-mocha-arrows": "error", - - - // Swap the no-unused-expressions rule with a more chai-friendly one - "no-unused-expressions": "off", - "chai-friendly/no-unused-expressions": "error", - - // chai-specific rules - "chai-expect/missing-assertion": "error", - "chai-expect/terminating-properties": "error", - - // prefer-arrow-callback applies to all callbacks, not just ones in mocha tests. - // we don't enforce this at the top-level - just in tests to manage `this` scope - // based on mocha's context mechanism - "mocha/prefer-arrow-callback": "error" - } - }, - { - // Backend specific rules - "files": ["**/app/src/**/*.js", "app.js"], - "rules": { - // do not allow importing of implicit dependencies. - "import/no-extraneous-dependencies": ["error", { - // do not allow importing of devDependencies. - "devDependencies": false - }], - "no-restricted-syntax": [ - "error", - // do not allow node-fetch in backend code - { - "selector": "CallExpression[callee.name='require'] > .arguments[value='node-fetch']", - "message": "Requiring node-fetch is not allowed in production services, please use fetch-utils." - }, - // mongoose populate must set fields to populate - { - "selector": "CallExpression[callee.property.name='populate'][arguments.length<2]", - "message": "Populate without a second argument returns the whole document. Use populate('field',['prop1','prop2']) instead" - }, - // Require `new` when constructing ObjectId (For mongo + mongoose upgrade) - { - "selector": "CallExpression[callee.name='ObjectId'], CallExpression[callee.property.name='ObjectId']", - "message": "Construct ObjectId with `new ObjectId()` instead of `ObjectId()`" - }, - // Require `new` when mapping a list of ids to a list of ObjectId (For mongo + mongoose upgrade) - { - "selector": "CallExpression[callee.property.name='map'] Identifier[name='ObjectId']:first-child, CallExpression[callee.property.name='map'] MemberExpression[property.name='ObjectId']:first-child", - "message": "Don't map ObjectId directly. Use `id => new ObjectId(id)` instead" - } - ] - } - }, - { - // Backend tests and scripts specific rules - "files": ["**/test/**/*.*", "**/scripts/*.*"], - "rules": { - "no-restricted-syntax": [ - "error", - // Require `new` when constructing ObjectId (For mongo + mongoose upgrade) - { - "selector": "CallExpression[callee.name='ObjectId'], CallExpression[callee.property.name='ObjectId']", - "message": "Construct ObjectId with `new ObjectId()` instead of `ObjectId()`" - }, - // Require `new` when mapping a list of ids to a list of ObjectId (For mongo + mongoose upgrade) - { - "selector": "CallExpression[callee.property.name='map'] Identifier[name='ObjectId']:first-child, CallExpression[callee.property.name='map'] MemberExpression[property.name='ObjectId']:first-child", - "message": "Don't map ObjectId directly. Use `id => new ObjectId(id)` instead" - }, - // Catch incorrect usage of `await db.collection.find()` - { - "selector": "AwaitExpression > CallExpression > MemberExpression[property.name='find'][object.object.name='db']", - "message": "Mongo find returns a cursor not a promise, use `for await (const result of cursor)` or `.toArray()` instead." - } - ] - } - }, - { - // Cypress specific rules - "files": ["cypress/**/*.{js,jsx,ts,tsx}", "**/test/frontend/**/*.spec.{js,jsx,ts,tsx}"], - "extends": [ - "plugin:cypress/recommended" - ] - }, - { - // Frontend specific rules - "files": ["**/frontend/js/**/*.{js,jsx,ts,tsx}", "**/frontend/stories/**/*.{js,jsx,ts,tsx}", "**/*.stories.{js,jsx,ts,tsx}", "**/test/frontend/**/*.{js,jsx,ts,tsx}", "**/test/frontend/components/**/*.spec.{js,jsx,ts,tsx}"], - "env": { - "browser": true - }, - "parserOptions": { - "sourceType": "module" - }, - "plugins": [ - "jsx-a11y" - ], - "extends": [ - "plugin:react/recommended", - "plugin:react-hooks/recommended", - "plugin:jsx-a11y/recommended", - "standard-jsx", - "prettier" - ], - "globals": { - "__webpack_public_path__": true, - "$": true, - "angular": true, - "ga": true, - // Injected in layout.pug - "user_id": true, - "ExposedSettings": true - }, - "rules": { - // TODO: remove once https://github.com/standard/eslint-config-standard-react/issues/68 (support eslint@8) is fixed. - // START: inline standard-react rules - // "react/jsx-no-bind": ["error", { - // "allowArrowFunctions": true, - // "allowBind": false, - // "ignoreRefs": true - // },], - "react/no-did-update-set-state": "error", - "react/no-unused-prop-types": "error", - "react/prop-types": "error", - // "react/react-in-jsx-scope": "error", - // END: inline standard-react rules - - "react/no-unknown-property": ["error", { - "ignore": ["dnd-container", "dropdown-toggle"] - }], - - "react/jsx-no-target-blank": ["error", { - "allowReferrer": true - }], - // Prevent usage of legacy string refs - "react/no-string-refs": "error", - - // Prevent curly braces around strings (as they're unnecessary) - "react/jsx-curly-brace-presence": ["error", { - "props": "never", - "children": "never" - }], - - // Don't import React for JSX; the JSX runtime is added by a Babel plugin - "react/react-in-jsx-scope": "off", - "react/jsx-uses-react": "off", - - // Allow functions as JSX props - "react/jsx-no-bind": "off", // TODO: fix occurrences and re-enable this - - // Fix conflict between prettier & standard by overriding to prefer - // double quotes - "jsx-quotes": ["error", "prefer-double"], - - // Override weird behaviour of jsx-a11y label-has-for (says labels must be - // nested *and* have for/id attributes) - "jsx-a11y/label-has-for": [ - "error", - { - "required": { - "some": [ - "nesting", - "id" - ] - } - } - ], - - // Require .jsx or .tsx file extension when using JSX - "react/jsx-filename-extension": ["error", { - "extensions": [".jsx", ".tsx"] - }], - "no-restricted-syntax": [ - "error", - // Begin: Make sure angular can withstand minification - { - "selector": "CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > :function[params.length > 0]", - "message": "Wrap the function in an array with the parameter names, to withstand minifcation. E.g. App.controller('MyController', ['param1', function(param1) {}]" - }, - { - "selector": "CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > ArrayExpression > ArrowFunctionExpression", - "message": "Use standard function syntax instead of arrow function syntax in angular components. E.g. function(param1) {}" - }, - { - "selector": "CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > ArrowFunctionExpression", - "message": "Use standard function syntax instead of arrow function syntax in angular components. E.g. function(param1) {}" - }, - { - "selector": "CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > ArrayExpression > :not(:function, Identifier):last-child", - "message": "Last element of the array must be a function. E.g ['param1', function(param1) {}]" - }, - { - "selector": "CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > ArrayExpression[elements.length=0]", - "message": "Array must not be empty. Add parameters and a function. E.g ['param1', function(param1) {}]" - }, - // End: Make sure angular can withstand minification - // prohibit direct calls to methods of window.localStorage - { - "selector": "CallExpression[callee.object.object.name='window'][callee.object.property.name='localStorage']", - "message": "Modify location via customLocalStorage instead of calling window.localStorage methods directly" - } - ] - } - }, - { - // React component specific rules - // - "files": ["**/frontend/js/**/components/**/*.{js,jsx,ts,tsx}", "**/frontend/js/**/hooks/**/*.{js,jsx,ts,tsx}"], - "rules": { - "@overleaf/no-unnecessary-trans": "error", - "@overleaf/should-unescape-trans": "error", - - // https://astexplorer.net/ - "no-restricted-syntax": [ - "error", - // prohibit direct calls to methods of window.location - { - "selector": "CallExpression[callee.object.object.name='window'][callee.object.property.name='location']", - "message": "Modify location via useLocation instead of calling window.location methods directly" - }, - // prohibit assignment to window.location - { - "selector": "AssignmentExpression[left.object.name='window'][left.property.name='location']", - "message": "Modify location via useLocation instead of calling window.location methods directly" - }, - // prohibit assignment to window.location.href - { - "selector": "AssignmentExpression[left.object.object.name='window'][left.object.property.name='location'][left.property.name='href']", - "message": "Modify location via useLocation instead of calling window.location methods directly" - }, - // prohibit using lookbehinds due to incidents with Safari simply crashing when the script is parsed - { - "selector": "Literal[regex.pattern=/\\(\\?<[!=]/]", - "message": "Lookbehind is not supported in older Safari versions." - }, - // prohibit direct calls to methods of window.localStorage - // NOTE: this rule is also defined for all frontend files, but those rules are overriden by the React component-specific config - { - "selector": "CallExpression[callee.object.object.name='window'][callee.object.property.name='localStorage']", - "message": "Modify location via customLocalStorage instead of calling window.localStorage methods directly" - } - ] - } - }, - // React + TypeScript-specific rules - { - "files": ["**/*.tsx"], - "rules": { - "react/prop-types": "off", - "no-undef": "off" - } - }, - // TypeScript-specific rules - { - "files": ["**/*.ts"], - "rules": { - "no-undef": "off" - } - }, - { - "files": ["scripts/ukamf/*.js"], - "rules": { - // Do not allow importing of any dependencies unless specified in either - // - web/package.json - // - web/scripts/ukamf/package.json - "import/no-extraneous-dependencies": ["error", {"packageDir": [".", "scripts/ukamf"]}] - } - }, - { - "files": ["scripts/learn/checkSanitize/*.js"], - "rules": { - // The checkSanitize script is used in the dev-env only. - "import/no-extraneous-dependencies": ["error", { - "devDependencies": true, - "packageDir": [".", "../../"] - }] - } - }, - { - "files": [ - // Backend: Use @overleaf/logger - // Docs: https://manual.dev-overleaf.com/development/code/logging/#structured-logging - "**/app/**/*.{js,cjs,mjs}", "app.js", "modules/*/*.js", - // Frontend: Prefer debugConsole over bare console - // Docs: https://manual.dev-overleaf.com/development/code/logging/#frontend - "**/frontend/**/*.{js,jsx,ts,tsx}", - // Tests - "**/test/**/*.{js,cjs,mjs,jsx,ts,tsx}" - ], - "excludedFiles": [ - // Allow console logs in scripts - "**/scripts/**/*.js", - // Allow console logs in stories - "**/stories/**/*.{js,jsx,ts,tsx}", - // Workers do not have access to the search params for enabling ?debug=true. - // self.location.url is the URL of the worker script. - "*.worker.{js,ts}" - ], - "rules": { - "no-console": "error" - } - } - ] -} diff --git a/services/web/.eslintrc.js b/services/web/.eslintrc.js new file mode 100644 index 0000000000..9bc1656822 --- /dev/null +++ b/services/web/.eslintrc.js @@ -0,0 +1,432 @@ +module.exports = { + root: true, + parser: '@typescript-eslint/parser', + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'standard', + 'prettier', + ], + plugins: ['@overleaf'], + env: { + es2020: true, + }, + settings: { + // Tell eslint-plugin-react to detect which version of React we are using + react: { + version: 'detect', + }, + }, + rules: { + 'no-constant-binary-expression': 'error', + + // do not allow importing of implicit dependencies. + 'import/no-extraneous-dependencies': 'error', + + // disable some TypeScript rules + '@typescript-eslint/no-var-requires': 'off', + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/no-empty-function': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-this-alias': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/ban-ts-comment': 'off', + }, + overrides: [ + // NOTE: changing paths may require updating them in the Makefile too. + { + // Node + files: [ + '**/app/src/**/*.js', + 'app.js', + 'i18next-scanner.config.js', + 'scripts/**/*.js', + 'webpack.config*.js', + ], + env: { + node: true, + }, + }, + { + // Test specific rules + files: ['**/test/**/*.*'], + plugins: ['mocha', 'chai-expect', 'chai-friendly'], + env: { + mocha: true, + }, + rules: { + // mocha-specific rules + 'mocha/handle-done-callback': 'error', + 'mocha/no-exclusive-tests': 'error', + 'mocha/no-global-tests': 'error', + 'mocha/no-identical-title': 'error', + 'mocha/no-nested-tests': 'error', + 'mocha/no-pending-tests': 'error', + 'mocha/no-skipped-tests': 'error', + 'mocha/no-mocha-arrows': 'error', + + // Swap the no-unused-expressions rule with a more chai-friendly one + 'no-unused-expressions': 'off', + 'chai-friendly/no-unused-expressions': 'error', + + // chai-specific rules + 'chai-expect/missing-assertion': 'error', + 'chai-expect/terminating-properties': 'error', + + // prefer-arrow-callback applies to all callbacks, not just ones in mocha tests. + // we don't enforce this at the top-level - just in tests to manage `this` scope + // based on mocha's context mechanism + 'mocha/prefer-arrow-callback': 'error', + }, + }, + { + // Backend specific rules + files: ['**/app/src/**/*.js', 'app.js'], + parserOptions: { + tsconfigRootDir: __dirname, + project: './tsconfig.backend.json', + }, + rules: { + // do not allow importing of implicit dependencies. + 'import/no-extraneous-dependencies': [ + 'error', + { + // do not allow importing of devDependencies. + devDependencies: false, + }, + ], + 'no-restricted-syntax': [ + 'error', + // do not allow node-fetch in backend code + { + selector: + "CallExpression[callee.name='require'] > .arguments[value='node-fetch']", + message: + 'Requiring node-fetch is not allowed in production services, please use fetch-utils.', + }, + // mongoose populate must set fields to populate + { + selector: + "CallExpression[callee.property.name='populate'][arguments.length<2]", + message: + "Populate without a second argument returns the whole document. Use populate('field',['prop1','prop2']) instead", + }, + // Require `new` when constructing ObjectId (For mongo + mongoose upgrade) + { + selector: + "CallExpression[callee.name='ObjectId'], CallExpression[callee.property.name='ObjectId']", + message: + 'Construct ObjectId with `new ObjectId()` instead of `ObjectId()`', + }, + // Require `new` when mapping a list of ids to a list of ObjectId (For mongo + mongoose upgrade) + { + selector: + "CallExpression[callee.property.name='map'] Identifier[name='ObjectId']:first-child, CallExpression[callee.property.name='map'] MemberExpression[property.name='ObjectId']:first-child", + message: + "Don't map ObjectId directly. Use `id => new ObjectId(id)` instead", + }, + ], + '@typescript-eslint/no-floating-promises': 'error', + }, + }, + { + // Backend tests and scripts specific rules + files: ['**/test/**/*.*', '**/scripts/*.*'], + rules: { + 'no-restricted-syntax': [ + 'error', + // Require `new` when constructing ObjectId (For mongo + mongoose upgrade) + { + selector: + "CallExpression[callee.name='ObjectId'], CallExpression[callee.property.name='ObjectId']", + message: + 'Construct ObjectId with `new ObjectId()` instead of `ObjectId()`', + }, + // Require `new` when mapping a list of ids to a list of ObjectId (For mongo + mongoose upgrade) + { + selector: + "CallExpression[callee.property.name='map'] Identifier[name='ObjectId']:first-child, CallExpression[callee.property.name='map'] MemberExpression[property.name='ObjectId']:first-child", + message: + "Don't map ObjectId directly. Use `id => new ObjectId(id)` instead", + }, + // Catch incorrect usage of `await db.collection.find()` + { + selector: + "AwaitExpression > CallExpression > MemberExpression[property.name='find'][object.object.name='db']", + message: + 'Mongo find returns a cursor not a promise, use `for await (const result of cursor)` or `.toArray()` instead.', + }, + ], + }, + }, + { + // Cypress specific rules + files: [ + 'cypress/**/*.{js,jsx,ts,tsx}', + '**/test/frontend/**/*.spec.{js,jsx,ts,tsx}', + ], + extends: ['plugin:cypress/recommended'], + }, + { + // Frontend specific rules + files: [ + '**/frontend/js/**/*.{js,jsx,ts,tsx}', + '**/frontend/stories/**/*.{js,jsx,ts,tsx}', + '**/*.stories.{js,jsx,ts,tsx}', + '**/test/frontend/**/*.{js,jsx,ts,tsx}', + '**/test/frontend/components/**/*.spec.{js,jsx,ts,tsx}', + ], + env: { + browser: true, + }, + parserOptions: { + sourceType: 'module', + }, + plugins: ['jsx-a11y'], + extends: [ + 'plugin:react/recommended', + 'plugin:react-hooks/recommended', + 'plugin:jsx-a11y/recommended', + 'standard-jsx', + 'prettier', + ], + globals: { + __webpack_public_path__: true, + $: true, + angular: true, + ga: true, + // Injected in layout.pug + user_id: true, + ExposedSettings: true, + }, + rules: { + // TODO: remove once https://github.com/standard/eslint-config-standard-react/issues/68 (support eslint@8) is fixed. + // START: inline standard-react rules + // "react/jsx-no-bind": ["error", { + // "allowArrowFunctions": true, + // "allowBind": false, + // "ignoreRefs": true + // },], + 'react/no-did-update-set-state': 'error', + 'react/no-unused-prop-types': 'error', + 'react/prop-types': 'error', + // "react/react-in-jsx-scope": "error", + // END: inline standard-react rules + + 'react/no-unknown-property': [ + 'error', + { + ignore: ['dnd-container', 'dropdown-toggle'], + }, + ], + + 'react/jsx-no-target-blank': [ + 'error', + { + allowReferrer: true, + }, + ], + // Prevent usage of legacy string refs + 'react/no-string-refs': 'error', + + // Prevent curly braces around strings (as they're unnecessary) + 'react/jsx-curly-brace-presence': [ + 'error', + { + props: 'never', + children: 'never', + }, + ], + + // Don't import React for JSX; the JSX runtime is added by a Babel plugin + 'react/react-in-jsx-scope': 'off', + 'react/jsx-uses-react': 'off', + + // Allow functions as JSX props + 'react/jsx-no-bind': 'off', // TODO: fix occurrences and re-enable this + + // Fix conflict between prettier & standard by overriding to prefer + // double quotes + 'jsx-quotes': ['error', 'prefer-double'], + + // Override weird behaviour of jsx-a11y label-has-for (says labels must be + // nested *and* have for/id attributes) + 'jsx-a11y/label-has-for': [ + 'error', + { + required: { + some: ['nesting', 'id'], + }, + }, + ], + + // Require .jsx or .tsx file extension when using JSX + 'react/jsx-filename-extension': [ + 'error', + { + extensions: ['.jsx', '.tsx'], + }, + ], + 'no-restricted-syntax': [ + 'error', + // Begin: Make sure angular can withstand minification + { + selector: + "CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > :function[params.length > 0]", + message: + "Wrap the function in an array with the parameter names, to withstand minifcation. E.g. App.controller('MyController', ['param1', function(param1) {}]", + }, + { + selector: + "CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > ArrayExpression > ArrowFunctionExpression", + message: + 'Use standard function syntax instead of arrow function syntax in angular components. E.g. function(param1) {}', + }, + { + selector: + "CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > ArrowFunctionExpression", + message: + 'Use standard function syntax instead of arrow function syntax in angular components. E.g. function(param1) {}', + }, + { + selector: + "CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > ArrayExpression > :not(:function, Identifier):last-child", + message: + "Last element of the array must be a function. E.g ['param1', function(param1) {}]", + }, + { + selector: + "CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > ArrayExpression[elements.length=0]", + message: + "Array must not be empty. Add parameters and a function. E.g ['param1', function(param1) {}]", + }, + // End: Make sure angular can withstand minification + // prohibit direct calls to methods of window.localStorage + { + selector: + "CallExpression[callee.object.object.name='window'][callee.object.property.name='localStorage']", + message: + 'Modify location via customLocalStorage instead of calling window.localStorage methods directly', + }, + ], + }, + }, + { + // React component specific rules + // + files: [ + '**/frontend/js/**/components/**/*.{js,jsx,ts,tsx}', + '**/frontend/js/**/hooks/**/*.{js,jsx,ts,tsx}', + ], + rules: { + '@overleaf/no-unnecessary-trans': 'error', + '@overleaf/should-unescape-trans': 'error', + + // https://astexplorer.net/ + 'no-restricted-syntax': [ + 'error', + // prohibit direct calls to methods of window.location + { + selector: + "CallExpression[callee.object.object.name='window'][callee.object.property.name='location']", + message: + 'Modify location via useLocation instead of calling window.location methods directly', + }, + // prohibit assignment to window.location + { + selector: + "AssignmentExpression[left.object.name='window'][left.property.name='location']", + message: + 'Modify location via useLocation instead of calling window.location methods directly', + }, + // prohibit assignment to window.location.href + { + selector: + "AssignmentExpression[left.object.object.name='window'][left.object.property.name='location'][left.property.name='href']", + message: + 'Modify location via useLocation instead of calling window.location methods directly', + }, + // prohibit using lookbehinds due to incidents with Safari simply crashing when the script is parsed + { + selector: 'Literal[regex.pattern=/\\(\\?<[!=]/]', + message: 'Lookbehind is not supported in older Safari versions.', + }, + // prohibit direct calls to methods of window.localStorage + // NOTE: this rule is also defined for all frontend files, but those rules are overriden by the React component-specific config + { + selector: + "CallExpression[callee.object.object.name='window'][callee.object.property.name='localStorage']", + message: + 'Modify location via customLocalStorage instead of calling window.localStorage methods directly', + }, + ], + }, + }, + // React + TypeScript-specific rules + { + files: ['**/*.tsx'], + rules: { + 'react/prop-types': 'off', + 'no-undef': 'off', + }, + }, + // TypeScript-specific rules + { + files: ['**/*.ts'], + rules: { + 'no-undef': 'off', + }, + }, + { + files: ['scripts/ukamf/*.js'], + rules: { + // Do not allow importing of any dependencies unless specified in either + // - web/package.json + // - web/scripts/ukamf/package.json + 'import/no-extraneous-dependencies': [ + 'error', + { packageDir: ['.', 'scripts/ukamf'] }, + ], + }, + }, + { + files: ['scripts/learn/checkSanitize/*.js'], + rules: { + // The checkSanitize script is used in the dev-env only. + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: true, + packageDir: ['.', '../../'], + }, + ], + }, + }, + { + files: [ + // Backend: Use @overleaf/logger + // Docs: https://manual.dev-overleaf.com/development/code/logging/#structured-logging + '**/app/**/*.{js,cjs,mjs}', + 'app.js', + 'modules/*/*.js', + // Frontend: Prefer debugConsole over bare console + // Docs: https://manual.dev-overleaf.com/development/code/logging/#frontend + '**/frontend/**/*.{js,jsx,ts,tsx}', + // Tests + '**/test/**/*.{js,cjs,mjs,jsx,ts,tsx}', + ], + excludedFiles: [ + // Allow console logs in scripts + '**/scripts/**/*.js', + // Allow console logs in stories + '**/stories/**/*.{js,jsx,ts,tsx}', + // Workers do not have access to the search params for enabling ?debug=true. + // self.location.url is the URL of the worker script. + '*.worker.{js,ts}', + ], + rules: { + 'no-console': 'error', + }, + }, + ], +} diff --git a/services/web/app/src/Features/Analytics/AnalyticsManager.js b/services/web/app/src/Features/Analytics/AnalyticsManager.js index cc3a6741f4..67ad4598a9 100644 --- a/services/web/app/src/Features/Analytics/AnalyticsManager.js +++ b/services/web/app/src/Features/Analytics/AnalyticsManager.js @@ -57,6 +57,15 @@ async function recordEventForUser(userId, event, segmentation) { } } +function recordEventForUserInBackground(userId, event, segmentation) { + recordEventForUser(userId, event, segmentation).catch(err => { + logger.warn( + { err, userId, event, segmentation }, + 'failed to record event for user' + ) + }) +} + function recordEventForSession(session, event, segmentation) { const { analyticsId, userId } = getIdsFromSession(session) if (!analyticsId) { @@ -88,6 +97,15 @@ async function setUserPropertyForUser(userId, propertyName, propertyValue) { } } +function setUserPropertyForUserInBackground(userId, property, value) { + setUserPropertyForUser(userId, property, value).catch(err => { + logger.warn( + { err, userId, property, value }, + 'failed to set user property for user' + ) + }) +} + async function setUserPropertyForAnalyticsId( analyticsId, propertyName, @@ -115,6 +133,16 @@ async function setUserPropertyForSession(session, propertyName, propertyValue) { } } +function setUserPropertyForSessionInBackground(session, property, value) { + setUserPropertyForSession(session, property, value).catch(err => { + const { analyticsId, userId } = getIdsFromSession(session) + logger.warn( + { err, analyticsId, userId, property, value }, + 'failed to set user property for session' + ) + }) +} + function updateEditingSession(userId, projectId, countryCode, segmentation) { if (!userId) { return @@ -310,8 +338,11 @@ module.exports = { identifyUser, recordEventForSession, recordEventForUser, + recordEventForUserInBackground, setUserPropertyForUser, + setUserPropertyForUserInBackground, setUserPropertyForSession, + setUserPropertyForSessionInBackground, setUserPropertyForAnalyticsId, updateEditingSession, getIdsFromSession, diff --git a/services/web/app/src/Features/Analytics/AnalyticsRegistrationSourceHelper.js b/services/web/app/src/Features/Analytics/AnalyticsRegistrationSourceHelper.js index ed79e1f17d..6166f0859e 100644 --- a/services/web/app/src/Features/Analytics/AnalyticsRegistrationSourceHelper.js +++ b/services/web/app/src/Features/Analytics/AnalyticsRegistrationSourceHelper.js @@ -31,25 +31,25 @@ function addUserProperties(userId, session) { } if (session.required_login_from_product_medium) { - AnalyticsManager.setUserPropertyForUser( + AnalyticsManager.setUserPropertyForUserInBackground( userId, `registered-from-product-medium`, session.required_login_from_product_medium ) if (session.required_login_from_product_source) { - AnalyticsManager.setUserPropertyForUser( + AnalyticsManager.setUserPropertyForUserInBackground( userId, `registered-from-product-source`, session.required_login_from_product_source ) } } else if (session.referal_id) { - AnalyticsManager.setUserPropertyForUser( + AnalyticsManager.setUserPropertyForUserInBackground( userId, `registered-from-bonus-scheme`, true ) - AnalyticsManager.setUserPropertyForUser( + AnalyticsManager.setUserPropertyForUserInBackground( userId, `registered-from-product-medium`, 'bonus-scheme' @@ -58,7 +58,7 @@ function addUserProperties(userId, session) { if (session.inbound) { if (session.inbound.referrer && session.inbound.referrer.medium) { - AnalyticsManager.setUserPropertyForUser( + AnalyticsManager.setUserPropertyForUserInBackground( userId, `registered-from-referrer-medium`, `${session.inbound.referrer.medium @@ -66,7 +66,7 @@ function addUserProperties(userId, session) { .toUpperCase()}${session.inbound.referrer.medium.slice(1)}` ) if (session.inbound.referrer.source) { - AnalyticsManager.setUserPropertyForUser( + AnalyticsManager.setUserPropertyForUserInBackground( userId, `registered-from-referrer-source`, session.inbound.referrer.source @@ -77,7 +77,7 @@ function addUserProperties(userId, session) { if (session.inbound.utm) { for (const utmKey of RequestHelper.UTM_KEYS) { if (session.inbound.utm[utmKey]) { - AnalyticsManager.setUserPropertyForUser( + AnalyticsManager.setUserPropertyForUserInBackground( userId, `registered-from-${utmKey.replace('_', '-')}`, session.inbound.utm[utmKey] diff --git a/services/web/app/src/Features/Analytics/AnalyticsUTMTrackingMiddleware.js b/services/web/app/src/Features/Analytics/AnalyticsUTMTrackingMiddleware.js index f4515ff935..d03a43fef5 100644 --- a/services/web/app/src/Features/Analytics/AnalyticsUTMTrackingMiddleware.js +++ b/services/web/app/src/Features/Analytics/AnalyticsUTMTrackingMiddleware.js @@ -27,7 +27,7 @@ function recordUTMTags() { };${utmValues.utm_campaign || 'N/A'};${ utmValues.utm_content || utmValues.utm_term || 'N/A' }` - AnalyticsManager.setUserPropertyForSession( + AnalyticsManager.setUserPropertyForSessionInBackground( req.session, 'utm-tags', propertyValue diff --git a/services/web/app/src/Features/Authentication/AuthenticationController.js b/services/web/app/src/Features/Authentication/AuthenticationController.js index 201862cd12..e1e412e495 100644 --- a/services/web/app/src/Features/Authentication/AuthenticationController.js +++ b/services/web/app/src/Features/Authentication/AuthenticationController.js @@ -646,7 +646,7 @@ function _loginAsyncHandlers(req, user, anonymousAnalyticsId, isNewUser) { LoginRateLimiter.recordSuccessfulLogin(user.email, () => {}) AuthenticationController._recordSuccessfulLogin(user._id, () => {}) AuthenticationController.ipMatchCheck(req, user) - Analytics.recordEventForUser(user._id, 'user-logged-in', { + Analytics.recordEventForUserInBackground(user._id, 'user-logged-in', { source: req.session.saml ? 'saml' : req.user_info?.auth_provider || 'email-password', diff --git a/services/web/app/src/Features/BetaProgram/BetaProgramHandler.js b/services/web/app/src/Features/BetaProgram/BetaProgramHandler.js index d558de37df..68ee193023 100644 --- a/services/web/app/src/Features/BetaProgram/BetaProgramHandler.js +++ b/services/web/app/src/Features/BetaProgram/BetaProgramHandler.js @@ -6,7 +6,11 @@ const AnalyticsManager = require('../Analytics/AnalyticsManager') async function optIn(userId) { await UserUpdater.promises.updateUser(userId, { $set: { betaProgram: true } }) metrics.inc('beta-program.opt-in') - AnalyticsManager.setUserPropertyForUser(userId, 'beta-program', true) + AnalyticsManager.setUserPropertyForUserInBackground( + userId, + 'beta-program', + true + ) } async function optOut(userId) { @@ -14,7 +18,11 @@ async function optOut(userId) { $set: { betaProgram: false }, }) metrics.inc('beta-program.opt-out') - AnalyticsManager.setUserPropertyForUser(userId, 'beta-program', false) + AnalyticsManager.setUserPropertyForUserInBackground( + userId, + 'beta-program', + false + ) } module.exports = { diff --git a/services/web/app/src/Features/Collaborators/CollaboratorsInviteController.js b/services/web/app/src/Features/Collaborators/CollaboratorsInviteController.js index 17b3169b52..f1b04d5df1 100644 --- a/services/web/app/src/Features/Collaborators/CollaboratorsInviteController.js +++ b/services/web/app/src/Features/Collaborators/CollaboratorsInviteController.js @@ -348,7 +348,7 @@ const CollaboratorsInviteController = { 'project:membership:changed', { invites: true, members: true } ) - AnalyticsManager.recordEventForUser( + AnalyticsManager.recordEventForUserInBackground( currentUser._id, 'project-invite-accept', { diff --git a/services/web/app/src/Features/Collaborators/CollaboratorsInviteHandler.js b/services/web/app/src/Features/Collaborators/CollaboratorsInviteHandler.js index 70b153a958..65f160a4f5 100644 --- a/services/web/app/src/Features/Collaborators/CollaboratorsInviteHandler.js +++ b/services/web/app/src/Features/Collaborators/CollaboratorsInviteHandler.js @@ -158,7 +158,7 @@ const CollaboratorsInviteHandler = { }, async acceptInvite(invite, projectId, user) { - CollaboratorsHandler.promises.addUserIdToProject( + await CollaboratorsHandler.promises.addUserIdToProject( projectId, invite.sendingUserId, user._id, diff --git a/services/web/app/src/Features/Collaborators/OwnershipTransferHandler.js b/services/web/app/src/Features/Collaborators/OwnershipTransferHandler.js index 3ac29c44d5..e4064db88f 100644 --- a/services/web/app/src/Features/Collaborators/OwnershipTransferHandler.js +++ b/services/web/app/src/Features/Collaborators/OwnershipTransferHandler.js @@ -38,7 +38,7 @@ async function transferOwnership(projectId, newOwnerId, options = {}) { } // Track the change of ownership in BigQuery. - AnalyticsManager.recordEventForUser( + AnalyticsManager.recordEventForUserInBackground( previousOwnerId, 'project-ownership-transfer', { projectId, newOwnerId } diff --git a/services/web/app/src/Features/Email/EmailHandler.js b/services/web/app/src/Features/Email/EmailHandler.js index 8e0b52e098..33b17136f5 100644 --- a/services/web/app/src/Features/Email/EmailHandler.js +++ b/services/web/app/src/Features/Email/EmailHandler.js @@ -1,5 +1,6 @@ const { callbackify } = require('util') const Settings = require('@overleaf/settings') +const logger = require('@overleaf/logger') const EmailBuilder = require('./EmailBuilder') const EmailSender = require('./EmailSender') const Queues = require('../../infrastructure/Queues') @@ -30,5 +31,7 @@ function sendDeferredEmail(emailType, opts, delay) { 'deferred-emails', { data: { emailType, opts } }, delay - ) + ).catch(err => { + logger.warn({ err, emailType, opts }, 'failed to queue deferred email') + }) } diff --git a/services/web/app/src/Features/LinkedFiles/LinkedFilesController.js b/services/web/app/src/Features/LinkedFiles/LinkedFilesController.js index e809c02bec..1ea71176a3 100644 --- a/services/web/app/src/Features/LinkedFiles/LinkedFilesController.js +++ b/services/web/app/src/Features/LinkedFiles/LinkedFilesController.js @@ -94,9 +94,13 @@ module.exports = LinkedFilesController = { return LinkedFilesController.handleError(err, req, res, next) } if (name.endsWith('.bib')) { - AnalyticsManager.recordEventForUser(userId, 'linked-bib-file', { - integration: provider, - }) + AnalyticsManager.recordEventForUserInBackground( + userId, + 'linked-bib-file', + { + integration: provider, + } + ) } return res.json({ new_file_id: newFileId }) } diff --git a/services/web/app/src/Features/Project/ProjectController.js b/services/web/app/src/Features/Project/ProjectController.js index ac94fe7bac..485eedaef7 100644 --- a/services/web/app/src/Features/Project/ProjectController.js +++ b/services/web/app/src/Features/Project/ProjectController.js @@ -435,12 +435,16 @@ const ProjectController = { SplitTestSessionHandler.sessionMaintenance(req, null, () => {}) cb(null, defaultSettingsForAnonymousUser(userId)) } else { + // Ignore spurious floating promises warning until we promisify + // eslint-disable-next-line @typescript-eslint/no-floating-promises User.updateOne( { _id: new ObjectId(userId) }, { $set: { lastActive: new Date() } }, {}, () => {} ) + // Ignore spurious floating promises warning until we promisify + // eslint-disable-next-line @typescript-eslint/no-floating-promises User.findById( userId, 'email first_name last_name referal_id signUpDate featureSwitches features featuresEpoch refProviders alphaProgram betaProgram isAdmin ace labsProgram completedTutorials writefull', @@ -693,9 +697,13 @@ const ProjectController = { metrics.inc(metricName) if (userId) { - AnalyticsManager.recordEventForUser(userId, 'project-opened', { - projectId: project._id, - }) + AnalyticsManager.recordEventForUserInBackground( + userId, + 'project-opened', + { + projectId: project._id, + } + ) } // should not be used in place of split tests query param overrides (?my-split-test-name=my-variant) diff --git a/services/web/app/src/Features/Project/ProjectCreationHandler.js b/services/web/app/src/Features/Project/ProjectCreationHandler.js index 5dda23eeb9..a604a67e03 100644 --- a/services/web/app/src/Features/Project/ProjectCreationHandler.js +++ b/services/web/app/src/Features/Project/ProjectCreationHandler.js @@ -55,13 +55,13 @@ async function createBlankProject( Object.assign(segmentation, attributes.segmentation) segmentation.projectId = project._id if (isImport) { - AnalyticsManager.recordEventForUser( + AnalyticsManager.recordEventForUserInBackground( ownerId, 'project-imported', segmentation ) } else { - AnalyticsManager.recordEventForUser( + AnalyticsManager.recordEventForUserInBackground( ownerId, 'project-created', segmentation @@ -72,7 +72,7 @@ async function createBlankProject( async function createProjectFromSnippet(ownerId, projectName, docLines) { const project = await _createBlankProject(ownerId, projectName) - AnalyticsManager.recordEventForUser(ownerId, 'project-created', { + AnalyticsManager.recordEventForUserInBackground(ownerId, 'project-created', { projectId: project._id, }) await _createRootDoc(project, ownerId, docLines) @@ -85,7 +85,7 @@ async function createBasicProject(ownerId, projectName) { const docLines = await _buildTemplate('mainbasic.tex', ownerId, projectName) await _createRootDoc(project, ownerId, docLines) - AnalyticsManager.recordEventForUser(ownerId, 'project-created', { + AnalyticsManager.recordEventForUserInBackground(ownerId, 'project-created', { projectId: project._id, }) @@ -97,7 +97,7 @@ async function createExampleProject(ownerId, projectName) { await _addExampleProjectFiles(ownerId, projectName, project) - AnalyticsManager.recordEventForUser(ownerId, 'project-created', { + AnalyticsManager.recordEventForUserInBackground(ownerId, 'project-created', { projectId: project._id, }) diff --git a/services/web/app/src/Features/Project/ProjectEntityUpdateHandler.js b/services/web/app/src/Features/Project/ProjectEntityUpdateHandler.js index a332397b11..506b7e13e6 100644 --- a/services/web/app/src/Features/Project/ProjectEntityUpdateHandler.js +++ b/services/web/app/src/Features/Project/ProjectEntityUpdateHandler.js @@ -245,6 +245,8 @@ const ProjectEntityUpdateHandler = { return callback(err) } if (ProjectEntityUpdateHandler.isPathValidForRootDoc(docPath)) { + // Ignore spurious floating promises warning until we promisify + // eslint-disable-next-line @typescript-eslint/no-floating-promises Project.updateOne( { _id: projectId }, { rootDoc_id: newRootDocID }, @@ -264,6 +266,8 @@ const ProjectEntityUpdateHandler = { unsetRootDoc(projectId, callback) { logger.debug({ projectId }, 'removing root doc') + // Ignore spurious floating promises warning until we promisify + // eslint-disable-next-line @typescript-eslint/no-floating-promises Project.updateOne( { _id: projectId }, { $unset: { rootDoc_id: true } }, diff --git a/services/web/app/src/Features/SplitTests/SplitTestHandler.js b/services/web/app/src/Features/SplitTests/SplitTestHandler.js index 963c8f970b..3c4e345c8e 100644 --- a/services/web/app/src/Features/SplitTests/SplitTestHandler.js +++ b/services/web/app/src/Features/SplitTests/SplitTestHandler.js @@ -314,7 +314,19 @@ async function _getAssignment( if (sync === true) { await _recordAssignment(assignmentData) } else { - _recordAssignment(assignmentData) + _recordAssignment(assignmentData).catch(err => { + logger.warn( + { + err, + userId, + splitTestName, + phase, + versionNumber, + variantName: selectedVariantName, + }, + 'failed to record split test assignment' + ) + }) } } // otherwise this is an anonymous user, we store assignments in session to persist them on registration @@ -329,11 +341,23 @@ async function _getAssignment( }) } + const effectiveAnalyticsId = user?.analyticsId || analyticsId || userId AnalyticsManager.setUserPropertyForAnalyticsId( - user?.analyticsId || analyticsId || userId, + effectiveAnalyticsId, `split-test-${splitTestName}-${versionNumber}`, selectedVariantName - ) + ).catch(err => { + logger.warn( + { + err, + analyticsId: effectiveAnalyticsId, + splitTest: splitTestName, + versionNumber, + variant: selectedVariantName, + }, + 'failed to set user property for analytics id' + ) + }) } return _makeAssignment(splitTest, selectedVariantName, currentVersion) } diff --git a/services/web/app/src/Features/Subscription/FeaturesUpdater.js b/services/web/app/src/Features/Subscription/FeaturesUpdater.js index 725b176885..06653086ed 100644 --- a/services/web/app/src/Features/Subscription/FeaturesUpdater.js +++ b/services/web/app/src/Features/Subscription/FeaturesUpdater.js @@ -43,7 +43,7 @@ async function refreshFeatures(userId, reason) { logger.debug({ userId, features }, 'updating user features') const matchedFeatureSet = FeaturesHelper.getMatchedFeatureSet(features) - AnalyticsManager.setUserPropertyForUser( + AnalyticsManager.setUserPropertyForUserInBackground( userId, 'feature-set', matchedFeatureSet diff --git a/services/web/app/src/Features/Subscription/RecurlyEventHandler.js b/services/web/app/src/Features/Subscription/RecurlyEventHandler.js index 9aa4851b1d..d933bffc1c 100644 --- a/services/web/app/src/Features/Subscription/RecurlyEventHandler.js +++ b/services/web/app/src/Features/Subscription/RecurlyEventHandler.js @@ -51,19 +51,27 @@ async function sendRecurlyAnalyticsEvent(event, eventData) { async function _sendSubscriptionStartedEvent(userId, eventData) { const { planCode, quantity, state, isTrial, subscriptionId } = _getSubscriptionData(eventData) - AnalyticsManager.recordEventForUser(userId, 'subscription-started', { - plan_code: planCode, - quantity, - is_trial: isTrial, - subscriptionId, - }) - AnalyticsManager.setUserPropertyForUser( + AnalyticsManager.recordEventForUserInBackground( + userId, + 'subscription-started', + { + plan_code: planCode, + quantity, + is_trial: isTrial, + subscriptionId, + } + ) + AnalyticsManager.setUserPropertyForUserInBackground( userId, 'subscription-plan-code', planCode ) - AnalyticsManager.setUserPropertyForUser(userId, 'subscription-state', state) - AnalyticsManager.setUserPropertyForUser( + AnalyticsManager.setUserPropertyForUserInBackground( + userId, + 'subscription-state', + state + ) + AnalyticsManager.setUserPropertyForUserInBackground( userId, 'subscription-is-trial', isTrial @@ -77,19 +85,27 @@ async function _sendSubscriptionStartedEvent(userId, eventData) { async function _sendSubscriptionUpdatedEvent(userId, eventData) { const { planCode, quantity, state, isTrial, subscriptionId } = _getSubscriptionData(eventData) - AnalyticsManager.recordEventForUser(userId, 'subscription-updated', { - plan_code: planCode, - quantity, - is_trial: isTrial, - subscriptionId, - }) - AnalyticsManager.setUserPropertyForUser( + AnalyticsManager.recordEventForUserInBackground( + userId, + 'subscription-updated', + { + plan_code: planCode, + quantity, + is_trial: isTrial, + subscriptionId, + } + ) + AnalyticsManager.setUserPropertyForUserInBackground( userId, 'subscription-plan-code', planCode ) - AnalyticsManager.setUserPropertyForUser(userId, 'subscription-state', state) - AnalyticsManager.setUserPropertyForUser( + AnalyticsManager.setUserPropertyForUserInBackground( + userId, + 'subscription-state', + state + ) + AnalyticsManager.setUserPropertyForUserInBackground( userId, 'subscription-is-trial', isTrial @@ -99,14 +115,22 @@ async function _sendSubscriptionUpdatedEvent(userId, eventData) { async function _sendSubscriptionCancelledEvent(userId, eventData) { const { planCode, quantity, state, isTrial, subscriptionId } = _getSubscriptionData(eventData) - AnalyticsManager.recordEventForUser(userId, 'subscription-cancelled', { - plan_code: planCode, - quantity, - is_trial: isTrial, - subscriptionId, - }) - AnalyticsManager.setUserPropertyForUser(userId, 'subscription-state', state) - AnalyticsManager.setUserPropertyForUser( + AnalyticsManager.recordEventForUserInBackground( + userId, + 'subscription-cancelled', + { + plan_code: planCode, + quantity, + is_trial: isTrial, + subscriptionId, + } + ) + AnalyticsManager.setUserPropertyForUserInBackground( + userId, + 'subscription-state', + state + ) + AnalyticsManager.setUserPropertyForUserInBackground( userId, 'subscription-is-trial', isTrial @@ -116,19 +140,27 @@ async function _sendSubscriptionCancelledEvent(userId, eventData) { async function _sendSubscriptionExpiredEvent(userId, eventData) { const { planCode, quantity, state, isTrial, subscriptionId } = _getSubscriptionData(eventData) - AnalyticsManager.recordEventForUser(userId, 'subscription-expired', { - plan_code: planCode, - quantity, - is_trial: isTrial, - subscriptionId, - }) - AnalyticsManager.setUserPropertyForUser( + AnalyticsManager.recordEventForUserInBackground( + userId, + 'subscription-expired', + { + plan_code: planCode, + quantity, + is_trial: isTrial, + subscriptionId, + } + ) + AnalyticsManager.setUserPropertyForUserInBackground( userId, 'subscription-plan-code', planCode ) - AnalyticsManager.setUserPropertyForUser(userId, 'subscription-state', state) - AnalyticsManager.setUserPropertyForUser( + AnalyticsManager.setUserPropertyForUserInBackground( + userId, + 'subscription-state', + state + ) + AnalyticsManager.setUserPropertyForUserInBackground( userId, 'subscription-is-trial', isTrial @@ -138,19 +170,27 @@ async function _sendSubscriptionExpiredEvent(userId, eventData) { async function _sendSubscriptionRenewedEvent(userId, eventData) { const { planCode, quantity, state, isTrial, subscriptionId } = _getSubscriptionData(eventData) - AnalyticsManager.recordEventForUser(userId, 'subscription-renewed', { - plan_code: planCode, - quantity, - is_trial: isTrial, - subscriptionId, - }) - AnalyticsManager.setUserPropertyForUser( + AnalyticsManager.recordEventForUserInBackground( + userId, + 'subscription-renewed', + { + plan_code: planCode, + quantity, + is_trial: isTrial, + subscriptionId, + } + ) + AnalyticsManager.setUserPropertyForUserInBackground( userId, 'subscription-plan-code', planCode ) - AnalyticsManager.setUserPropertyForUser(userId, 'subscription-state', state) - AnalyticsManager.setUserPropertyForUser( + AnalyticsManager.setUserPropertyForUserInBackground( + userId, + 'subscription-state', + state + ) + AnalyticsManager.setUserPropertyForUserInBackground( userId, 'subscription-is-trial', isTrial @@ -160,18 +200,26 @@ async function _sendSubscriptionRenewedEvent(userId, eventData) { async function _sendSubscriptionReactivatedEvent(userId, eventData) { const { planCode, quantity, state, isTrial, subscriptionId } = _getSubscriptionData(eventData) - AnalyticsManager.recordEventForUser(userId, 'subscription-reactivated', { - plan_code: planCode, - quantity, - subscriptionId, - }) - AnalyticsManager.setUserPropertyForUser( + AnalyticsManager.recordEventForUserInBackground( + userId, + 'subscription-reactivated', + { + plan_code: planCode, + quantity, + subscriptionId, + } + ) + AnalyticsManager.setUserPropertyForUserInBackground( userId, 'subscription-plan-code', planCode ) - AnalyticsManager.setUserPropertyForUser(userId, 'subscription-state', state) - AnalyticsManager.setUserPropertyForUser( + AnalyticsManager.setUserPropertyForUserInBackground( + userId, + 'subscription-state', + state + ) + AnalyticsManager.setUserPropertyForUserInBackground( userId, 'subscription-is-trial', isTrial @@ -195,7 +243,7 @@ async function _sendInvoicePaidEvent(userId, eventData) { subscriptionIds[`subscriptionId${idx + 1}`] = e } }) - AnalyticsManager.recordEventForUser( + AnalyticsManager.recordEventForUserInBackground( userId, 'subscription-invoice-collected', { @@ -208,7 +256,7 @@ async function _sendInvoicePaidEvent(userId, eventData) { ...subscriptionIds, } ) - AnalyticsManager.setUserPropertyForUser( + AnalyticsManager.setUserPropertyForUserInBackground( userId, 'subscription-is-trial', false diff --git a/services/web/app/src/Features/Subscription/SubscriptionUpdater.js b/services/web/app/src/Features/Subscription/SubscriptionUpdater.js index 297fd0c4ee..f64e0f8cdc 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionUpdater.js +++ b/services/web/app/src/Features/Subscription/SubscriptionUpdater.js @@ -355,7 +355,7 @@ async function _sendUserGroupPlanCodeUserProperty(userId) { bestFeatures = plan.features } } - AnalyticsManager.setUserPropertyForUser( + AnalyticsManager.setUserPropertyForUserInBackground( userId, 'group-subscription-plan-code', bestPlanCode @@ -376,7 +376,7 @@ async function _sendSubscriptionEvent(userId, subscriptionId, event) { if (!subscription || !subscription.groupPlan) { return } - AnalyticsManager.recordEventForUser(userId, event, { + AnalyticsManager.recordEventForUserInBackground(userId, event, { groupId: subscription._id.toString(), subscriptionId: subscription.recurlySubscription_id, }) @@ -397,7 +397,7 @@ async function _sendSubscriptionEventForAllMembers(subscriptionId, event) { const userIds = (subscription.member_ids || []).filter(Boolean) for (const userId of userIds) { if (userId) { - AnalyticsManager.recordEventForUser(userId, event, { + AnalyticsManager.recordEventForUserInBackground(userId, event, { groupId: subscription._id.toString(), subscriptionId: subscription.recurlySubscription_id, }) diff --git a/services/web/app/src/Features/SystemMessages/SystemMessageManager.js b/services/web/app/src/Features/SystemMessages/SystemMessageManager.js index 81df2bec5f..2eed16716a 100644 --- a/services/web/app/src/Features/SystemMessages/SystemMessageManager.js +++ b/services/web/app/src/Features/SystemMessages/SystemMessageManager.js @@ -3,6 +3,7 @@ const { addRequiredCleanupHandlerBeforeDrainingConnections, } = require('../../infrastructure/GracefulShutdown') const { callbackifyAll } = require('@overleaf/promise-utils') +const logger = require('@overleaf/logger') const SystemMessageManager = { getMessages() { @@ -22,9 +23,14 @@ const SystemMessageManager = { await message.save() }, - async refreshCache() { - const messages = await this.getMessagesFromDB() - this._cachedMessages = messages + refreshCache() { + this.getMessagesFromDB() + .then(messages => { + this._cachedMessages = messages + }) + .catch(err => { + logger.warn({ err }, 'failed to refresh system messages cache') + }) }, } diff --git a/services/web/app/src/Features/TokenAccess/TokenAccessHandler.js b/services/web/app/src/Features/TokenAccess/TokenAccessHandler.js index b154ebd212..d94d2b4368 100644 --- a/services/web/app/src/Features/TokenAccess/TokenAccessHandler.js +++ b/services/web/app/src/Features/TokenAccess/TokenAccessHandler.js @@ -154,7 +154,7 @@ const TokenAccessHandler = { async addReadOnlyUserToProject(userId, projectId) { userId = new ObjectId(userId.toString()) projectId = new ObjectId(projectId.toString()) - Analytics.recordEventForUser(userId, 'project-joined', { + Analytics.recordEventForUserInBackground(userId, 'project-joined', { mode: 'read-only', }) @@ -171,7 +171,7 @@ const TokenAccessHandler = { async addReadAndWriteUserToProject(userId, projectId) { userId = new ObjectId(userId.toString()) projectId = new ObjectId(projectId.toString()) - Analytics.recordEventForUser(userId, 'project-joined', { + Analytics.recordEventForUserInBackground(userId, 'project-joined', { mode: 'read-write', }) diff --git a/services/web/app/src/Features/User/ThirdPartyIdentityManager.js b/services/web/app/src/Features/User/ThirdPartyIdentityManager.js index 0c98bd6bb9..63622dc31e 100644 --- a/services/web/app/src/Features/User/ThirdPartyIdentityManager.js +++ b/services/web/app/src/Features/User/ThirdPartyIdentityManager.js @@ -165,21 +165,21 @@ function _getUserQuery(providerId, externalUserId) { return query } -async function _sendSecurityAlert(accountLinked, providerId, user, userId) { +function _sendSecurityAlert(accountLinked, providerId, user, userId) { const providerName = oauthProviders[providerId].name const emailOptions = EmailOptionsHelper.linkOrUnlink( accountLinked, providerName, user.email ) - try { - await EmailHandler.promises.sendEmail('securityAlert', emailOptions) - } catch (error) { - logger.error( - { err: error, userId }, - `could not send security alert email when ${emailOptions.action.toLowerCase()}` - ) - } + EmailHandler.promises + .sendEmail('securityAlert', emailOptions) + .catch(error => { + logger.error( + { err: error, userId }, + `could not send security alert email when ${emailOptions.action.toLowerCase()}` + ) + }) } function _thirdPartyIdentifierUpdate( diff --git a/services/web/app/src/Features/User/UserCreator.js b/services/web/app/src/Features/User/UserCreator.js index ebee80cd8a..808e4323aa 100644 --- a/services/web/app/src/Features/User/UserCreator.js +++ b/services/web/app/src/Features/User/UserCreator.js @@ -43,7 +43,11 @@ async function recordRegistrationEvent(user) { if (user.thirdPartyIdentifiers && user.thirdPartyIdentifiers.length > 0) { segmentation.provider = user.thirdPartyIdentifiers[0].providerId } - Analytics.recordEventForUser(user._id, 'user-registered', segmentation) + Analytics.recordEventForUserInBackground( + user._id, + 'user-registered', + segmentation + ) } catch (err) { logger.warn({ err }, 'there was an error recording `user-registered` event') } diff --git a/services/web/app/src/Features/User/UserEmailsController.js b/services/web/app/src/Features/User/UserEmailsController.js index 4d3a5afe17..076c0f500f 100644 --- a/services/web/app/src/Features/User/UserEmailsController.js +++ b/services/web/app/src/Features/User/UserEmailsController.js @@ -319,11 +319,15 @@ async function checkSecondaryEmailConfirmationCode(req, res) { delete req.session.pendingSecondaryEmail - AnalyticsManager.recordEventForUser(user._id, 'email-verified', { - provider: 'email', - verification_type: 'token', - isPrimary: false, - }) + AnalyticsManager.recordEventForUserInBackground( + user._id, + 'email-verified', + { + provider: 'email', + verification_type: 'token', + isPrimary: false, + } + ) const redirectUrl = AuthenticationController.getRedirectFromSession(req) || '/project' @@ -427,7 +431,7 @@ async function primaryEmailCheckPage(req, res) { return res.redirect('/project') } - AnalyticsManager.recordEventForUser( + AnalyticsManager.recordEventForUserInBackground( userId, 'primary-email-check-page-displayed' ) @@ -439,7 +443,10 @@ async function primaryEmailCheck(req, res) { await UserUpdater.promises.updateUser(userId, { $set: { lastPrimaryEmailCheck: new Date() }, }) - AnalyticsManager.recordEventForUser(userId, 'primary-email-check-done') + AnalyticsManager.recordEventForUserInBackground( + userId, + 'primary-email-check-done' + ) AsyncFormHelper.redirect(req, res, '/project') } @@ -610,7 +617,7 @@ const UserEmailsController = { ) } const isPrimary = user?.email === userData.email - AnalyticsManager.recordEventForUser( + AnalyticsManager.recordEventForUserInBackground( userData.userId, 'email-verified', { diff --git a/services/web/app/src/Features/User/UserRegistrationHandler.js b/services/web/app/src/Features/User/UserRegistrationHandler.js index c45c19b189..2802fdc81c 100644 --- a/services/web/app/src/Features/User/UserRegistrationHandler.js +++ b/services/web/app/src/Features/User/UserRegistrationHandler.js @@ -113,14 +113,14 @@ const UserRegistrationHandler = { const setNewPasswordUrl = `${settings.siteUrl}/user/activate?token=${token}&user_id=${user._id}` - try { - await EmailHandler.promises.sendEmail('registered', { + EmailHandler.promises + .sendEmail('registered', { to: user.email, setNewPasswordUrl, }) - } catch (error) { - logger.warn({ err: error }, 'failed to send activation email') - } + .catch(error => { + logger.warn({ err: error }, 'failed to send activation email') + }) return { user, setNewPasswordUrl } }, diff --git a/services/web/app/src/Features/User/UserUpdater.js b/services/web/app/src/Features/User/UserUpdater.js index e9613f6811..7f38154edd 100644 --- a/services/web/app/src/Features/User/UserUpdater.js +++ b/services/web/app/src/Features/User/UserUpdater.js @@ -75,7 +75,10 @@ async function addEmailAddress(userId, newEmail, affiliationOptions, auditLog) { await UserGetter.promises.ensureUniqueEmailAddress(newEmail) - AnalyticsManager.recordEventForUser(userId, 'secondary-email-added') + AnalyticsManager.recordEventForUserInBackground( + userId, + 'secondary-email-added' + ) await UserAuditLogHandler.promises.addEntry( userId, @@ -201,7 +204,10 @@ async function setDefaultEmailAddress( throw new Error('email update error') } - AnalyticsManager.recordEventForUser(userId, 'primary-email-address-updated') + AnalyticsManager.recordEventForUserInBackground( + userId, + 'primary-email-address-updated' + ) if (sendSecurityAlert) { // no need to wait, errors are logged and not passed back diff --git a/services/web/app/src/infrastructure/Translations.js b/services/web/app/src/infrastructure/Translations.js index aab680bde7..9c2cc79ba7 100644 --- a/services/web/app/src/infrastructure/Translations.js +++ b/services/web/app/src/infrastructure/Translations.js @@ -76,6 +76,9 @@ i18n supportedLngs: availableLanguageCodes, fallbackLng: fallbackLanguageCode, }) + .catch(err => { + logger.error({ err }, 'failed to initialize i18next library') + }) // Make custom language detector for Accept-Language header const headerLangDetector = new middleware.LanguageDetector(i18n.services, { diff --git a/services/web/modules/launchpad/app/src/LaunchpadController.js b/services/web/modules/launchpad/app/src/LaunchpadController.js index b050fedb5e..a3b8a5b639 100644 --- a/services/web/modules/launchpad/app/src/LaunchpadController.js +++ b/services/web/modules/launchpad/app/src/LaunchpadController.js @@ -169,6 +169,8 @@ module.exports = LaunchpadController = { return next(err) } + // Ignore spurious floating promises warning until we promisify + // eslint-disable-next-line @typescript-eslint/no-floating-promises User.updateOne( { _id: user._id }, { @@ -245,6 +247,8 @@ module.exports = LaunchpadController = { } logger.debug({ userId: user._id }, 'making user an admin') + // Ignore spurious floating promises warning until we promisify + // eslint-disable-next-line @typescript-eslint/no-floating-promises User.updateOne( { _id: user._id }, { diff --git a/services/web/package.json b/services/web/package.json index fc14592b0e..3e6a976820 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -245,8 +245,8 @@ "@types/recurly__recurly-js": "^4.22.0", "@types/sinon-chai": "^3.2.8", "@types/uuid": "^9.0.8", - "@typescript-eslint/eslint-plugin": "^6.7.4", - "@typescript-eslint/parser": "^6.7.4", + "@typescript-eslint/eslint-plugin": "^7.8.0", + "@typescript-eslint/parser": "^7.8.0", "@uppy/core": "^3.8.0", "@uppy/dashboard": "^3.7.1", "@uppy/drag-drop": "^3.0.3", diff --git a/services/web/scripts/refresh_features.js b/services/web/scripts/refresh_features.js index 3c25846bdb..8e7b5cbb4d 100644 --- a/services/web/scripts/refresh_features.js +++ b/services/web/scripts/refresh_features.js @@ -81,7 +81,7 @@ const checkAndUpdateUser = (user, callback) => } const matchedFeatureSet = FeaturesHelper.getMatchedFeatureSet(freshFeatures) - AnalyticsManager.setUserPropertyForUser( + AnalyticsManager.setUserPropertyForUserInBackground( user._id, 'feature-set', matchedFeatureSet diff --git a/services/web/test/unit/src/Analytics/AnalyticsUTMTrackingMiddlewareTests.js b/services/web/test/unit/src/Analytics/AnalyticsUTMTrackingMiddlewareTests.js index 9c76b4e4ad..9c7b31bc98 100644 --- a/services/web/test/unit/src/Analytics/AnalyticsUTMTrackingMiddlewareTests.js +++ b/services/web/test/unit/src/Analytics/AnalyticsUTMTrackingMiddlewareTests.js @@ -29,7 +29,7 @@ describe('AnalyticsUTMTrackingMiddleware', function () { requires: { './AnalyticsManager': (this.AnalyticsManager = { recordEventForSession: sinon.stub().resolves(), - setUserPropertyForSession: sinon.stub().resolves(), + setUserPropertyForSessionInBackground: sinon.stub(), }), '@overleaf/settings': { siteUrl: 'https://www.overleaf.com', @@ -56,7 +56,9 @@ describe('AnalyticsUTMTrackingMiddleware', function () { it('no event or user property is recorded', function () { sinon.assert.notCalled(this.AnalyticsManager.recordEventForSession) - sinon.assert.notCalled(this.AnalyticsManager.setUserPropertyForSession) + sinon.assert.notCalled( + this.AnalyticsManager.setUserPropertyForSessionInBackground + ) }) }) @@ -101,7 +103,7 @@ describe('AnalyticsUTMTrackingMiddleware', function () { it('utm-tags user property is set for session', function () { sinon.assert.calledWith( - this.AnalyticsManager.setUserPropertyForSession, + this.AnalyticsManager.setUserPropertyForSessionInBackground, this.req.session, 'utm-tags', 'Organic;Facebook;Some Campaign;foo-bar' @@ -146,7 +148,7 @@ describe('AnalyticsUTMTrackingMiddleware', function () { it('utm-tags user property is set for session', function () { sinon.assert.calledWith( - this.AnalyticsManager.setUserPropertyForSession, + this.AnalyticsManager.setUserPropertyForSessionInBackground, this.req.session, 'utm-tags', 'N/A;Facebook;Some Campaign;foo' @@ -190,7 +192,7 @@ describe('AnalyticsUTMTrackingMiddleware', function () { it('utm-tags user property is set for session', function () { sinon.assert.calledWith( - this.AnalyticsManager.setUserPropertyForSession, + this.AnalyticsManager.setUserPropertyForSessionInBackground, this.req.session, 'utm-tags', 'N/A;Facebook;Some Campaign;N/A' diff --git a/services/web/test/unit/src/Authentication/AuthenticationControllerTests.js b/services/web/test/unit/src/Authentication/AuthenticationControllerTests.js index 2dbf94a629..6792740e25 100644 --- a/services/web/test/unit/src/Authentication/AuthenticationControllerTests.js +++ b/services/web/test/unit/src/Authentication/AuthenticationControllerTests.js @@ -88,7 +88,7 @@ describe('AuthenticationController', function () { setupLoginData: sinon.stub(), }), '../Analytics/AnalyticsManager': (this.AnalyticsManager = { - recordEventForUser: sinon.stub(), + recordEventForUserInBackground: sinon.stub(), identifyUser: sinon.stub(), getIdsFromSession: sinon.stub().returns({ userId: this.user._id }), }), @@ -1476,7 +1476,7 @@ describe('AuthenticationController', function () { it('should track the login event', function () { sinon.assert.calledWith( - this.AnalyticsManager.recordEventForUser, + this.AnalyticsManager.recordEventForUserInBackground, this.user._id, 'user-logged-in' ) diff --git a/services/web/test/unit/src/BetaProgram/BetaProgramHandlerTests.js b/services/web/test/unit/src/BetaProgram/BetaProgramHandlerTests.js index 26d2c658fd..5f8f9be3c9 100644 --- a/services/web/test/unit/src/BetaProgram/BetaProgramHandlerTests.js +++ b/services/web/test/unit/src/BetaProgram/BetaProgramHandlerTests.js @@ -28,7 +28,7 @@ describe('BetaProgramHandler', function () { }, }), '../Analytics/AnalyticsManager': (this.AnalyticsManager = { - setUserPropertyForUser: sinon.stub().resolves(), + setUserPropertyForUserInBackground: sinon.stub(), }), }, }) @@ -54,7 +54,7 @@ describe('BetaProgramHandler', function () { this.call(err => { expect(err).to.not.exist sinon.assert.calledWith( - this.AnalyticsManager.setUserPropertyForUser, + this.AnalyticsManager.setUserPropertyForUserInBackground, this.user_id, 'beta-program', true @@ -105,7 +105,7 @@ describe('BetaProgramHandler', function () { this.call(err => { expect(err).to.not.exist sinon.assert.calledWith( - this.AnalyticsManager.setUserPropertyForUser, + this.AnalyticsManager.setUserPropertyForUserInBackground, this.user_id, 'beta-program', false diff --git a/services/web/test/unit/src/Collaborators/CollaboratorsInviteControllerTests.js b/services/web/test/unit/src/Collaborators/CollaboratorsInviteControllerTests.js index 785cd85340..4108e8ecf5 100644 --- a/services/web/test/unit/src/Collaborators/CollaboratorsInviteControllerTests.js +++ b/services/web/test/unit/src/Collaborators/CollaboratorsInviteControllerTests.js @@ -33,7 +33,7 @@ describe('CollaboratorsInviteController', function () { getSessionUser: sinon.stub().returns(this.currentUser), } - this.AnalyticsManger = { recordEventForUser: sinon.stub() } + this.AnalyticsManger = { recordEventForUserInBackground: sinon.stub() } this.rateLimiter = { consume: sinon.stub().resolves(), diff --git a/services/web/test/unit/src/Collaborators/OwnershipTransferHandlerTests.js b/services/web/test/unit/src/Collaborators/OwnershipTransferHandlerTests.js index 934496a42e..112aa564ca 100644 --- a/services/web/test/unit/src/Collaborators/OwnershipTransferHandlerTests.js +++ b/services/web/test/unit/src/Collaborators/OwnershipTransferHandlerTests.js @@ -74,7 +74,8 @@ describe('OwnershipTransferHandler', function () { '../Email/EmailHandler': this.EmailHandler, './CollaboratorsHandler': this.CollaboratorsHandler, '../Analytics/AnalyticsManager': { - recordEventForUser: (this.recordEventForUser = sinon.stub()), + recordEventForUserInBackground: (this.recordEventForUserInBackground = + sinon.stub()), }, }, }) @@ -214,7 +215,7 @@ describe('OwnershipTransferHandler', function () { this.collaborator._id, { sessionUserId } ) - expect(this.recordEventForUser).to.have.been.calledWith( + expect(this.recordEventForUserInBackground).to.have.been.calledWith( this.user._id, 'project-ownership-transfer', { diff --git a/services/web/test/unit/src/Email/EmailHandlerTests.js b/services/web/test/unit/src/Email/EmailHandlerTests.js index b52ac65d1e..fdd2404161 100644 --- a/services/web/test/unit/src/Email/EmailHandlerTests.js +++ b/services/web/test/unit/src/Email/EmailHandlerTests.js @@ -21,7 +21,7 @@ describe('EmailHandler', function () { }, } this.Queues = { - createScheduledJob: sinon.stub(), + createScheduledJob: sinon.stub().resolves(), } this.EmailHandler = SandboxedModule.require(MODULE_PATH, { requires: { diff --git a/services/web/test/unit/src/Project/ProjectControllerTests.js b/services/web/test/unit/src/Project/ProjectControllerTests.js index eba513384d..0afd9dd5e7 100644 --- a/services/web/test/unit/src/Project/ProjectControllerTests.js +++ b/services/web/test/unit/src/Project/ProjectControllerTests.js @@ -189,7 +189,9 @@ describe('ProjectController', function () { this.BrandVariationsHandler, '../ThirdPartyDataStore/TpdsProjectFlusher': this.TpdsProjectFlusher, '../../models/Project': {}, - '../Analytics/AnalyticsManager': { recordEventForUser: () => {} }, + '../Analytics/AnalyticsManager': { + recordEventForUserInBackground: () => {}, + }, '../Subscription/SubscriptionViewModelBuilder': this.SubscriptionViewModelBuilder, '../Spelling/SpellingHandler': { diff --git a/services/web/test/unit/src/SplitTests/SplitTestHandlerTests.js b/services/web/test/unit/src/SplitTests/SplitTestHandlerTests.js index cdf3bf5d82..b7458e9c26 100644 --- a/services/web/test/unit/src/SplitTests/SplitTestHandlerTests.js +++ b/services/web/test/unit/src/SplitTests/SplitTestHandlerTests.js @@ -48,7 +48,7 @@ describe('SplitTestHandler', function () { } this.AnalyticsManager = { getIdsFromSession: sinon.stub(), - setUserPropertyForAnalyticsId: sinon.stub(), + setUserPropertyForAnalyticsId: sinon.stub().resolves(), } this.LocalsHelper = { setSplitTestVariant: sinon.stub(), diff --git a/services/web/test/unit/src/Subscription/FeaturesUpdaterTests.js b/services/web/test/unit/src/Subscription/FeaturesUpdaterTests.js index 8cb497d7bc..3c97b30bf9 100644 --- a/services/web/test/unit/src/Subscription/FeaturesUpdaterTests.js +++ b/services/web/test/unit/src/Subscription/FeaturesUpdaterTests.js @@ -93,7 +93,7 @@ describe('FeaturesUpdater', function () { .resolves(this.user) this.AnalyticsManager = { - setUserPropertyForUser: sinon.stub(), + setUserPropertyForUserInBackground: sinon.stub(), } this.Modules = { promises: { hooks: { fire: sinon.stub().resolves() } }, @@ -141,7 +141,7 @@ describe('FeaturesUpdater', function () { it('should send the corresponding feature set user property', function () { expect( - this.AnalyticsManager.setUserPropertyForUser + this.AnalyticsManager.setUserPropertyForUserInBackground ).to.have.been.calledWith(this.user._id, 'feature-set', 'all') }) }) @@ -159,7 +159,7 @@ describe('FeaturesUpdater', function () { it('should send mixed feature set user property', function () { sinon.assert.calledWith( - this.AnalyticsManager.setUserPropertyForUser, + this.AnalyticsManager.setUserPropertyForUserInBackground, this.user._id, 'feature-set', 'mixed' diff --git a/services/web/test/unit/src/Subscription/RecurlyEventHandlerTests.js b/services/web/test/unit/src/Subscription/RecurlyEventHandlerTests.js index 872d8f2128..c88ae46745 100644 --- a/services/web/test/unit/src/Subscription/RecurlyEventHandlerTests.js +++ b/services/web/test/unit/src/Subscription/RecurlyEventHandlerTests.js @@ -31,8 +31,8 @@ describe('RecurlyEventHandler', function () { sendTrialOnboardingEmail: sinon.stub(), }), '../Analytics/AnalyticsManager': (this.AnalyticsManager = { - recordEventForUser: sinon.stub(), - setUserPropertyForUser: sinon.stub(), + recordEventForUserInBackground: sinon.stub(), + setUserPropertyForUserInBackground: sinon.stub(), }), '../SplitTests/SplitTestHandler': (this.SplitTestHandler = { promises: { @@ -51,7 +51,7 @@ describe('RecurlyEventHandler', function () { this.eventData ) sinon.assert.calledWith( - this.AnalyticsManager.recordEventForUser, + this.AnalyticsManager.recordEventForUserInBackground, this.userId, 'subscription-started', { @@ -62,19 +62,19 @@ describe('RecurlyEventHandler', function () { } ) sinon.assert.calledWith( - this.AnalyticsManager.setUserPropertyForUser, + this.AnalyticsManager.setUserPropertyForUserInBackground, this.userId, 'subscription-plan-code', this.planCode ) sinon.assert.calledWith( - this.AnalyticsManager.setUserPropertyForUser, + this.AnalyticsManager.setUserPropertyForUserInBackground, this.userId, 'subscription-state', 'active' ) sinon.assert.calledWith( - this.AnalyticsManager.setUserPropertyForUser, + this.AnalyticsManager.setUserPropertyForUserInBackground, this.userId, 'subscription-is-trial', true @@ -104,7 +104,7 @@ describe('RecurlyEventHandler', function () { this.eventData ) sinon.assert.calledWith( - this.AnalyticsManager.recordEventForUser, + this.AnalyticsManager.recordEventForUserInBackground, this.userId, 'subscription-started', { @@ -115,13 +115,13 @@ describe('RecurlyEventHandler', function () { } ) sinon.assert.calledWith( - this.AnalyticsManager.setUserPropertyForUser, + this.AnalyticsManager.setUserPropertyForUserInBackground, this.userId, 'subscription-state', 'active' ) sinon.assert.calledWith( - this.AnalyticsManager.setUserPropertyForUser, + this.AnalyticsManager.setUserPropertyForUserInBackground, this.userId, 'subscription-is-trial', false @@ -136,7 +136,7 @@ describe('RecurlyEventHandler', function () { this.eventData ) sinon.assert.calledWith( - this.AnalyticsManager.recordEventForUser, + this.AnalyticsManager.recordEventForUserInBackground, this.userId, 'subscription-updated', { @@ -147,19 +147,19 @@ describe('RecurlyEventHandler', function () { } ) sinon.assert.calledWith( - this.AnalyticsManager.setUserPropertyForUser, + this.AnalyticsManager.setUserPropertyForUserInBackground, this.userId, 'subscription-plan-code', this.planCode ) sinon.assert.calledWith( - this.AnalyticsManager.setUserPropertyForUser, + this.AnalyticsManager.setUserPropertyForUserInBackground, this.userId, 'subscription-state', 'active' ) sinon.assert.calledWith( - this.AnalyticsManager.setUserPropertyForUser, + this.AnalyticsManager.setUserPropertyForUserInBackground, this.userId, 'subscription-is-trial', true @@ -173,7 +173,7 @@ describe('RecurlyEventHandler', function () { this.eventData ) sinon.assert.calledWith( - this.AnalyticsManager.recordEventForUser, + this.AnalyticsManager.recordEventForUserInBackground, this.userId, 'subscription-cancelled', { @@ -184,13 +184,13 @@ describe('RecurlyEventHandler', function () { } ) sinon.assert.calledWith( - this.AnalyticsManager.setUserPropertyForUser, + this.AnalyticsManager.setUserPropertyForUserInBackground, this.userId, 'subscription-state', 'cancelled' ) sinon.assert.calledWith( - this.AnalyticsManager.setUserPropertyForUser, + this.AnalyticsManager.setUserPropertyForUserInBackground, this.userId, 'subscription-is-trial', true @@ -204,7 +204,7 @@ describe('RecurlyEventHandler', function () { this.eventData ) sinon.assert.calledWith( - this.AnalyticsManager.recordEventForUser, + this.AnalyticsManager.recordEventForUserInBackground, this.userId, 'subscription-expired', { @@ -215,19 +215,19 @@ describe('RecurlyEventHandler', function () { } ) sinon.assert.calledWith( - this.AnalyticsManager.setUserPropertyForUser, + this.AnalyticsManager.setUserPropertyForUserInBackground, this.userId, 'subscription-plan-code', this.planCode ) sinon.assert.calledWith( - this.AnalyticsManager.setUserPropertyForUser, + this.AnalyticsManager.setUserPropertyForUserInBackground, this.userId, 'subscription-state', 'expired' ) sinon.assert.calledWith( - this.AnalyticsManager.setUserPropertyForUser, + this.AnalyticsManager.setUserPropertyForUserInBackground, this.userId, 'subscription-is-trial', true @@ -240,7 +240,7 @@ describe('RecurlyEventHandler', function () { this.eventData ) sinon.assert.calledWith( - this.AnalyticsManager.recordEventForUser, + this.AnalyticsManager.recordEventForUserInBackground, this.userId, 'subscription-renewed', { @@ -258,7 +258,7 @@ describe('RecurlyEventHandler', function () { this.eventData ) sinon.assert.calledWith( - this.AnalyticsManager.recordEventForUser, + this.AnalyticsManager.recordEventForUserInBackground, this.userId, 'subscription-reactivated', { @@ -292,7 +292,7 @@ describe('RecurlyEventHandler', function () { } ) sinon.assert.calledWith( - this.AnalyticsManager.recordEventForUser, + this.AnalyticsManager.recordEventForUserInBackground, this.userId, 'subscription-invoice-collected', { @@ -321,7 +321,7 @@ describe('RecurlyEventHandler', function () { }, } ) - sinon.assert.notCalled(this.AnalyticsManager.recordEventForUser) + sinon.assert.notCalled(this.AnalyticsManager.recordEventForUserInBackground) }) it('with closed_invoice_notification', function () { @@ -338,7 +338,7 @@ describe('RecurlyEventHandler', function () { } ) sinon.assert.calledWith( - this.AnalyticsManager.recordEventForUser, + this.AnalyticsManager.recordEventForUserInBackground, this.userId, 'subscription-invoice-collected' ) @@ -357,7 +357,7 @@ describe('RecurlyEventHandler', function () { }, } ) - sinon.assert.notCalled(this.AnalyticsManager.recordEventForUser) + sinon.assert.notCalled(this.AnalyticsManager.recordEventForUserInBackground) }) it('nothing is called with invalid account code', function () { @@ -367,9 +367,15 @@ describe('RecurlyEventHandler', function () { 'new_subscription_notification', this.eventData ) - sinon.assert.notCalled(this.AnalyticsManager.recordEventForUser) - sinon.assert.notCalled(this.AnalyticsManager.setUserPropertyForUser) - sinon.assert.notCalled(this.AnalyticsManager.setUserPropertyForUser) - sinon.assert.notCalled(this.AnalyticsManager.setUserPropertyForUser) + sinon.assert.notCalled(this.AnalyticsManager.recordEventForUserInBackground) + sinon.assert.notCalled( + this.AnalyticsManager.setUserPropertyForUserInBackground + ) + sinon.assert.notCalled( + this.AnalyticsManager.setUserPropertyForUserInBackground + ) + sinon.assert.notCalled( + this.AnalyticsManager.setUserPropertyForUserInBackground + ) }) }) diff --git a/services/web/test/unit/src/Subscription/SubscriptionUpdaterTests.js b/services/web/test/unit/src/Subscription/SubscriptionUpdaterTests.js index 0ad660e19f..834d0a5d21 100644 --- a/services/web/test/unit/src/Subscription/SubscriptionUpdaterTests.js +++ b/services/web/test/unit/src/Subscription/SubscriptionUpdaterTests.js @@ -146,8 +146,8 @@ describe('SubscriptionUpdater', function () { } this.AnalyticsManager = { - recordEventForUser: sinon.stub().resolves(), - setUserPropertyForUser: sinon.stub(), + recordEventForUserInBackground: sinon.stub().resolves(), + setUserPropertyForUserInBackground: sinon.stub(), } this.Features = { @@ -448,7 +448,7 @@ describe('SubscriptionUpdater', function () { .calledWith(searchOps, insertOperation) .should.equal(true) sinon.assert.calledWith( - this.AnalyticsManager.recordEventForUser, + this.AnalyticsManager.recordEventForUserInBackground, this.otherUserId, 'group-subscription-joined', { @@ -477,7 +477,7 @@ describe('SubscriptionUpdater', function () { this.otherUserId ) sinon.assert.calledWith( - this.AnalyticsManager.setUserPropertyForUser, + this.AnalyticsManager.setUserPropertyForUserInBackground, this.otherUserId, 'group-subscription-plan-code', 'group_subscription' @@ -493,7 +493,7 @@ describe('SubscriptionUpdater', function () { this.otherUserId ) sinon.assert.calledWith( - this.AnalyticsManager.setUserPropertyForUser, + this.AnalyticsManager.setUserPropertyForUserInBackground, this.otherUserId, 'group-subscription-plan-code', 'better_group_subscription' @@ -509,7 +509,7 @@ describe('SubscriptionUpdater', function () { this.otherUserId ) sinon.assert.calledWith( - this.AnalyticsManager.setUserPropertyForUser, + this.AnalyticsManager.setUserPropertyForUserInBackground, this.otherUserId, 'group-subscription-plan-code', 'better_group_subscription' @@ -567,7 +567,7 @@ describe('SubscriptionUpdater', function () { this.otherUserId ) sinon.assert.calledWith( - this.AnalyticsManager.recordEventForUser, + this.AnalyticsManager.recordEventForUserInBackground, this.otherUserId, 'group-subscription-left', { @@ -583,7 +583,7 @@ describe('SubscriptionUpdater', function () { this.otherUserId ) sinon.assert.calledWith( - this.AnalyticsManager.setUserPropertyForUser, + this.AnalyticsManager.setUserPropertyForUserInBackground, this.otherUserId, 'group-subscription-plan-code', null @@ -635,7 +635,7 @@ describe('SubscriptionUpdater', function () { this.otherUserId ) sinon.assert.calledWith( - this.AnalyticsManager.setUserPropertyForUser, + this.AnalyticsManager.setUserPropertyForUserInBackground, this.otherUserId, 'group-subscription-plan-code', null @@ -682,7 +682,7 @@ describe('SubscriptionUpdater', function () { this.otherUserId ) sinon.assert.calledWith( - this.AnalyticsManager.recordEventForUser, + this.AnalyticsManager.recordEventForUserInBackground, this.otherUserId, 'group-subscription-left', { @@ -691,7 +691,7 @@ describe('SubscriptionUpdater', function () { } ) sinon.assert.calledWith( - this.AnalyticsManager.recordEventForUser, + this.AnalyticsManager.recordEventForUserInBackground, this.otherUserId, 'group-subscription-left', { diff --git a/services/web/test/unit/src/TokenAccess/TokenAccessHandlerTests.js b/services/web/test/unit/src/TokenAccess/TokenAccessHandlerTests.js index a0138220c0..2b6ad52c5c 100644 --- a/services/web/test/unit/src/TokenAccess/TokenAccessHandlerTests.js +++ b/services/web/test/unit/src/TokenAccess/TokenAccessHandlerTests.js @@ -32,7 +32,7 @@ describe('TokenAccessHandler', function () { }), crypto: (this.Crypto = require('crypto')), '../Analytics/AnalyticsManager': (this.Analytics = { - recordEventForUser: sinon.stub(), + recordEventForUserInBackground: sinon.stub(), }), }, }) @@ -127,7 +127,7 @@ describe('TokenAccessHandler', function () { 'tokenAccessReadOnly_refs' ) sinon.assert.calledWith( - this.Analytics.recordEventForUser, + this.Analytics.recordEventForUserInBackground, this.userId, 'project-joined', { mode: 'read-only' } @@ -175,7 +175,7 @@ describe('TokenAccessHandler', function () { 'tokenAccessReadAndWrite_refs' ) sinon.assert.calledWith( - this.Analytics.recordEventForUser, + this.Analytics.recordEventForUserInBackground, this.userId, 'project-joined', { mode: 'read-write' } diff --git a/services/web/test/unit/src/User/ThirdPartyIdentityManagerTests.js b/services/web/test/unit/src/User/ThirdPartyIdentityManagerTests.js index de67251c8b..d944262518 100644 --- a/services/web/test/unit/src/User/ThirdPartyIdentityManagerTests.js +++ b/services/web/test/unit/src/User/ThirdPartyIdentityManagerTests.js @@ -144,7 +144,7 @@ describe('ThirdPartyIdentityManager', function () { describe('EmailHandler', function () { beforeEach(function () { - this.EmailHandler.promises.sendEmail.throws(anError) + this.EmailHandler.promises.sendEmail.rejects(anError) }) it('should log but not return the error', async function () { await expect( @@ -219,7 +219,7 @@ describe('ThirdPartyIdentityManager', function () { describe('EmailHandler', function () { beforeEach(function () { - this.EmailHandler.promises.sendEmail.throws(anError) + this.EmailHandler.promises.sendEmail.rejects(anError) }) it('should log but not return the error', async function () { await expect( diff --git a/services/web/test/unit/src/User/UserCreatorTests.js b/services/web/test/unit/src/User/UserCreatorTests.js index ddbd48da64..f9075ed121 100644 --- a/services/web/test/unit/src/User/UserCreatorTests.js +++ b/services/web/test/unit/src/User/UserCreatorTests.js @@ -43,7 +43,7 @@ describe('UserCreator', function () { }, }), '../Analytics/AnalyticsManager': (this.Analytics = { - recordEventForUser: sinon.stub(), + recordEventForUserInBackground: sinon.stub(), setUserPropertyForUser: sinon.stub(), }), '../SplitTests/SplitTestHandler': (this.SplitTestHandler = { @@ -278,7 +278,7 @@ describe('UserCreator', function () { }) assert.equal(user.email, this.email) sinon.assert.calledWith( - this.Analytics.recordEventForUser, + this.Analytics.recordEventForUserInBackground, user._id, 'user-registered' ) diff --git a/services/web/test/unit/src/User/UserEmailsControllerTests.js b/services/web/test/unit/src/User/UserEmailsControllerTests.js index 5dcaa5aad2..4376dbc69e 100644 --- a/services/web/test/unit/src/User/UserEmailsControllerTests.js +++ b/services/web/test/unit/src/User/UserEmailsControllerTests.js @@ -57,7 +57,7 @@ describe('UserEmailsController', function () { } this.HttpErrorHandler = { conflict: sinon.stub() } this.AnalyticsManager = { - recordEventForUser: sinon.stub(), + recordEventForUserInBackground: sinon.stub(), } this.UserAuditLogHandler = { addEntry: sinon.stub().yields(), diff --git a/services/web/test/unit/src/User/UserRegistrationHandlerTests.js b/services/web/test/unit/src/User/UserRegistrationHandlerTests.js index 4f94f001da..6cbb92af15 100644 --- a/services/web/test/unit/src/User/UserRegistrationHandlerTests.js +++ b/services/web/test/unit/src/User/UserRegistrationHandlerTests.js @@ -40,7 +40,7 @@ describe('UserRegistrationHandler', function () { subscribe: sinon.stub(), } this.EmailHandler = { - promises: { sendEmail: sinon.stub() }, + promises: { sendEmail: sinon.stub().resolves() }, } this.OneTimeTokenHandler = { promises: { getNewToken: sinon.stub() } } this.handler = SandboxedModule.require(modulePath, { diff --git a/services/web/test/unit/src/User/UserUpdaterTests.js b/services/web/test/unit/src/User/UserUpdaterTests.js index 7aed708c39..79eacdee27 100644 --- a/services/web/test/unit/src/User/UserUpdaterTests.js +++ b/services/web/test/unit/src/User/UserUpdaterTests.js @@ -65,7 +65,7 @@ describe('UserUpdater', function () { }, } this.AnalyticsManager = { - recordEventForUser: sinon.stub(), + recordEventForUserInBackground: sinon.stub(), } this.InstitutionsAPI = { promises: {