Improve performance in production setup (#15594)

* build cached content files for mobile during gulp build

* load already cached content files during startup

* add option for mongoose to define minPoolSize

* cache client index.html for 10 minutes. Improves initial load times

* add option to auth to use lean version of user doc

* add a way to produce a heapdump from the command line

* fix lint
This commit is contained in:
Phillip Thelen
2026-01-26 18:45:44 +01:00
committed by GitHub
parent 3d93390a7a
commit cdf8556fd6
9 changed files with 474 additions and 141 deletions

View File

@@ -6,9 +6,21 @@ gulp.task('cache:content', done => {
// Requiring at runtime because these files access `common`
// code which in production works only if transpiled so after
// gulp build:babel:common has run
const { CONTENT_CACHE_PATH, getLocalizedContentResponse } = require('../website/server/libs/content'); // eslint-disable-line global-require
const {
CONTENT_CACHE_PATH,
getLocalizedContentResponse,
IOS_FILTER,
ANDROID_FILTER,
buildFilterObject,
hashForFilter,
} = require('../website/server/libs/content'); // eslint-disable-line global-require
const { langCodes } = require('../website/server/libs/i18n'); // eslint-disable-line global-require
const iosHash = hashForFilter(IOS_FILTER);
const iosFilterObj = buildFilterObject(IOS_FILTER);
const androidHash = hashForFilter(ANDROID_FILTER);
const androidFilterObj = buildFilterObject(ANDROID_FILTER);
try {
// create the cache folder (if it doesn't exist)
try {
@@ -26,6 +38,18 @@ gulp.task('cache:content', done => {
getLocalizedContentResponse(langCode),
'utf8',
);
fs.writeFileSync(
`${CONTENT_CACHE_PATH}${langCode}${iosHash}.json`,
getLocalizedContentResponse(langCode, iosFilterObj),
'utf8',
);
fs.writeFileSync(
`${CONTENT_CACHE_PATH}${langCode}${androidHash}.json`,
getLocalizedContentResponse(langCode, androidFilterObj),
'utf8',
);
});
done();
} catch (err) {

511
package-lock.json generated
View File

@@ -45,6 +45,7 @@
"gulp-imagemin": "^7.1.0",
"gulp.spritesmith": "^6.13.0",
"habitica-markdown": "^3.0.0",
"heapdump": "^0.3.15",
"helmet": "^4.6.0",
"in-app-purchase": "^1.11.3",
"js2xmlparser": "^5.0.0",
@@ -58,6 +59,7 @@
"moment-recur": "git://github.com/HabitRPG/moment-recur.git#d3e8e6da0806f13b74dd2e4d7d9053e6a63db119",
"mongoose": "^8.9.5",
"morgan": "^1.10.1",
"nan": "^2.25.0",
"nconf": "^0.12.1",
"node-gcm": "^1.0.5",
"on-headers": "^1.1.0",
@@ -1769,6 +1771,246 @@
"node": ">=10.0.0"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.17.tgz",
"integrity": "sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz",
"integrity": "sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.17.tgz",
"integrity": "sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz",
"integrity": "sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz",
"integrity": "sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz",
"integrity": "sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz",
"integrity": "sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz",
"integrity": "sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==",
"cpu": [
"arm"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz",
"integrity": "sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz",
"integrity": "sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==",
"cpu": [
"ia32"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz",
"integrity": "sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==",
"cpu": [
"loong64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz",
"integrity": "sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==",
"cpu": [
"mips64el"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz",
"integrity": "sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==",
"cpu": [
"ppc64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz",
"integrity": "sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==",
"cpu": [
"riscv64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz",
"integrity": "sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==",
"cpu": [
"s390x"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz",
@@ -1784,6 +2026,102 @@
"node": ">=12"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz",
"integrity": "sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz",
"integrity": "sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz",
"integrity": "sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz",
"integrity": "sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz",
"integrity": "sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==",
"cpu": [
"ia32"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.16.17",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz",
"integrity": "sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -5935,7 +6273,7 @@
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"dev": true,
"devOptional": true,
"dependencies": {
"file-uri-to-path": "1.0.0"
}
@@ -10258,7 +10596,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
"dev": true
"devOptional": true
},
"node_modules/filename-reserved-regex": {
"version": "2.0.0",
@@ -10790,6 +11128,20 @@
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -11344,6 +11696,25 @@
"node": ">=0.10.0"
}
},
"node_modules/glob-watcher/node_modules/fsevents": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
"deprecated": "Upgrade to fsevents v2 to mitigate potential security issues",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"dependencies": {
"bindings": "^1.5.0",
"nan": "^2.12.1"
},
"engines": {
"node": ">= 4.0"
}
},
"node_modules/glob-watcher/node_modules/glob-parent": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
@@ -12450,6 +12821,19 @@
"he": "bin/he"
}
},
"node_modules/heapdump": {
"version": "0.3.15",
"resolved": "https://registry.npmjs.org/heapdump/-/heapdump-0.3.15.tgz",
"integrity": "sha512-n8aSFscI9r3gfhOcAECAtXFaQ1uy4QSke6bnaL+iymYZ/dWs9cqDqHM+rALfsHUwukUbxsdlECZ0pKmJdQ/4OA==",
"hasInstallScript": true,
"license": "ISC",
"dependencies": {
"nan": "^2.13.2"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/helmet": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/helmet/-/helmet-4.6.0.tgz",
@@ -15337,22 +15721,6 @@
"url": "https://opencollective.com/mongoose"
}
},
"node_modules/mongoose/node_modules/kerberos": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/kerberos/-/kerberos-2.2.1.tgz",
"integrity": "sha512-Vlyv1tjAPb0y2VIJ03dKkUjsneGIBuTkH24uGRx6/DrKpFlVuGPmct3m5aEotljVUlw7PAGWABwR5aNeW7y8Zw==",
"hasInstallScript": true,
"license": "Apache-2.0",
"optional": true,
"peer": true,
"dependencies": {
"node-addon-api": "^6.1.0",
"prebuild-install": "^7.1.2"
},
"engines": {
"node": ">=12.9.0"
}
},
"node_modules/mongoose/node_modules/mongodb": {
"version": "6.12.0",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.12.0.tgz",
@@ -15404,105 +15772,6 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/mongoose/node_modules/napi-build-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
"integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/mongoose/node_modules/node-abi": {
"version": "3.74.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.74.0.tgz",
"integrity": "sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"semver": "^7.3.5"
},
"engines": {
"node": ">=10"
}
},
"node_modules/mongoose/node_modules/node-addon-api": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz",
"integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==",
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/mongoose/node_modules/prebuild-install": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
"integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"detect-libc": "^2.0.0",
"expand-template": "^2.0.3",
"github-from-package": "0.0.0",
"minimist": "^1.2.3",
"mkdirp-classic": "^0.5.3",
"napi-build-utils": "^2.0.0",
"node-abi": "^3.3.0",
"pump": "^3.0.0",
"rc": "^1.2.7",
"simple-get": "^4.0.0",
"tar-fs": "^2.0.0",
"tunnel-agent": "^0.6.0"
},
"bin": {
"prebuild-install": "bin.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/mongoose/node_modules/semver": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
"license": "ISC",
"optional": true,
"peer": true,
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/mongoose/node_modules/simple-get": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"decompress-response": "^6.0.0",
"once": "^1.3.1",
"simple-concat": "^1.0.0"
}
},
"node_modules/monk": {
"version": "7.3.4",
"resolved": "https://registry.npmjs.org/monk/-/monk-7.3.4.tgz",
@@ -15684,10 +15953,10 @@
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="
},
"node_modules/nan": {
"version": "2.18.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz",
"integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==",
"dev": true
"version": "2.25.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.25.0.tgz",
"integrity": "sha512-0M90Ag7Xn5KMLLZ7zliPWP3rT90P6PN+IzVFS0VqmnPktBk3700xUVv8Ikm9EUaUE5SDWdp/BIxdENzVznpm1g==",
"license": "MIT"
},
"node_modules/nanomatch": {
"version": "1.2.13",

View File

@@ -40,6 +40,7 @@
"gulp-imagemin": "^7.1.0",
"gulp.spritesmith": "^6.13.0",
"habitica-markdown": "^3.0.0",
"heapdump": "^0.3.15",
"helmet": "^4.6.0",
"in-app-purchase": "^1.11.3",
"js2xmlparser": "^5.0.0",
@@ -53,6 +54,7 @@
"moment-recur": "git://github.com/HabitRPG/moment-recur.git#d3e8e6da0806f13b74dd2e4d7d9053e6a63db119",
"mongoose": "^8.9.5",
"morgan": "^1.10.1",
"nan": "^2.25.0",
"nconf": "^0.12.1",
"node-gcm": "^1.0.5",
"on-headers": "^1.1.0",

View File

@@ -1,21 +1,12 @@
import nconf from 'nconf';
import { langCodes } from '../../libs/i18n';
import { serveContent } from '../../libs/content';
import { serveContent, ANDROID_FILTER, IOS_FILTER } from '../../libs/content';
import { authWithHeaders } from '../../middlewares/auth';
const IS_PROD = nconf.get('IS_PROD');
const api = {};
const MOBILE_FILTER = ['achievements', 'questSeriesAchievements', 'animalColorAchievements', 'animalSetAchievements',
'stableAchievements', 'bundles', 'loginIncentives', 'pets', 'premiumPets', 'specialPets', 'questPets',
'wackyPets', 'mounts', 'premiumMounts,specialMounts,questMounts', 'events', 'dropEggs', 'questEggs', 'dropHatchingPotions',
'premiumHatchingPotions', 'wackyHatchingPotions', 'backgroundsFlat', 'questsByLevel', 'gear.tree', 'tasksByCategory',
'userDefaults', 'timeTravelStable', 'gearTypes', 'cardTypes'];
const ANDROID_FILTER = [...MOBILE_FILTER, 'appearances.background'].join(',');
const IOS_FILTER = [...MOBILE_FILTER, 'backgrounds'].join(',');
/**
* @api {get} /api/v3/content Get all available content objects
* @apiDescription Does not require authentication.

View File

@@ -8,6 +8,7 @@ if (process.env.NODE_ENV !== 'production') {
}
const cluster = require('cluster');
const heapdump = require('heapdump');
const setupNconf = require('./libs/setupNconf').default;
@@ -17,6 +18,13 @@ setupNconf();
// Initialize @google-cloud/trace-agent
require('./libs/gcpTraceAgent');
process.on('SIGUSR2', () => {
const filename = `/tmp/heapdump-${Date.now()}.heapsnapshot`;
heapdump.writeSnapshot(filename);
// eslint-disable-next-line no-console
console.log('Heap snapshot written to', filename);
});
const logger = require('./libs/logger').default;
const { ENABLE_CLUSTER, CORES } = require('./libs/config');

View File

@@ -1,5 +1,7 @@
const ROOT = `${__dirname}/../../../`;
const TEN_MINUTES = 1000 * 60 * 10;
export function serveClient (expressRes) { // eslint-disable-line import/prefer-default-export
return expressRes.sendFile('./website/client/dist/index.html', { root: ROOT });
return expressRes.sendFile('./website/client/dist/index.html', { root: ROOT, maxAge: TEN_MINUTES });
}

View File

@@ -9,6 +9,15 @@ import packageInfo from '../../../package.json';
export const CONTENT_CACHE_PATH = path.join(__dirname, '/../../../content_cache/');
const SWITCHOVER_TIME = nconf.get('CONTENT_SWITCHOVER_TIME_OFFSET') || 0;
const MOBILE_FILTER = ['achievements', 'questSeriesAchievements', 'animalColorAchievements', 'animalSetAchievements',
'stableAchievements', 'bundles', 'loginIncentives', 'pets', 'premiumPets', 'specialPets', 'questPets',
'wackyPets', 'mounts', 'premiumMounts,specialMounts,questMounts', 'events', 'dropEggs', 'questEggs', 'dropHatchingPotions',
'premiumHatchingPotions', 'wackyHatchingPotions', 'backgroundsFlat', 'questsByLevel', 'gear.tree', 'tasksByCategory',
'userDefaults', 'timeTravelStable', 'gearTypes', 'cardTypes'];
export const ANDROID_FILTER = [...MOBILE_FILTER, 'appearances.background'].join(',');
export const IOS_FILTER = [...MOBILE_FILTER, 'backgrounds'].join(',');
function getDay (date) {
if (date === undefined) {
return 0;
@@ -30,6 +39,22 @@ let CACHED_HASHES = [
];
// Load existing cached hashes
try {
const files = fs.readdirSync(CONTENT_CACHE_PATH);
files.forEach(file => {
if (file.endsWith('.json')) {
const fileName = file.substring(0, file.length - 5);
CACHED_HASHES.push(fileName);
}
});
if (CACHED_HASHES.length > 0) {
CACHED_DATE = new Date();
}
} catch (err) {
// Folder does not exist yet
}
function walkContent (obj, lang, removedKeys = {}) {
_.each(obj, (item, key, source) => {
if (key in removedKeys && removedKeys[key] === true) {
@@ -72,8 +97,7 @@ export function hashForFilter (filter) {
return String(hash);
}
export function serveContent (res, language, filter, isProd) {
// Build usable filter object
export function buildFilterObject (filter) {
const filterObj = {};
filter.split(',').forEach(item => {
if (item.includes('.')) {
@@ -86,7 +110,12 @@ export function serveContent (res, language, filter, isProd) {
filterObj[item.trim()] = true;
}
});
return filterObj;
}
export function serveContent (res, language, filter, isProd) {
// Build usable filter object
const filterObj = buildFilterObject(filter);
if (isProd) {
const today = new Date();
if (CACHED_DATE && (getDay(today) !== getDay(CACHED_DATE)
@@ -95,22 +124,23 @@ export function serveContent (res, language, filter, isProd) {
CACHED_HASHES = [];
CACHED_DATE = undefined;
}
const filterHash = language + hashForFilter(filter);
if (CACHED_HASHES.includes(filterHash)) {
const cachedName = language + hashForFilter(filter);
if (CACHED_HASHES.includes(cachedName)) {
// Content is already cached, so just send it.
res.sendFile(`${CONTENT_CACHE_PATH}${filterHash}.json`);
res.sendFile(`${CONTENT_CACHE_PATH}${cachedName}.json`);
} else {
console.log(`Caching content for language ${language} with filter ${filter}`);
// Content is not cached, so cache it and send it.
res.set({
'Content-Type': 'application/json',
});
const jsonResString = getLocalizedContentResponse(language, filterObj);
fs.writeFileSync(
`${CONTENT_CACHE_PATH}${filterHash}.json`,
`${CONTENT_CACHE_PATH}${cachedName}.json`,
jsonResString,
'utf8',
);
CACHED_HASHES.push(filterHash);
CACHED_HASHES.push(cachedName);
CACHED_DATE = new Date();
res.status(200).send(jsonResString);
}

View File

@@ -8,11 +8,13 @@ import {
const IS_PROD = nconf.get('IS_PROD');
const MAINTENANCE_MODE = nconf.get('MAINTENANCE_MODE');
const MIN_POOL_SIZE = nconf.get('MONGODB_MIN_POOL_SIZE');
const POOL_SIZE = nconf.get('MONGODB_POOL_SIZE');
const SOCKET_TIMEOUT = nconf.get('MONGODB_SOCKET_TIMEOUT');
const mongooseOptions = getDefaultConnectionOptions();
if (MIN_POOL_SIZE) mongooseOptions.minPoolSize = Number(MIN_POOL_SIZE);
if (POOL_SIZE) mongooseOptions.maxPoolSize = Number(POOL_SIZE);
if (SOCKET_TIMEOUT) mongooseOptions.socketTimeoutMS = Number(SOCKET_TIMEOUT);

View File

@@ -65,6 +65,7 @@ export function authWithHeaders (options = {}) {
const apiToken = req.header('x-api-key');
const client = req.header('x-client');
const optional = options.optional || false;
const leanUser = options.leanUser || false;
if (ENFORCE_CLIENT_HEADER && !client) {
return next(new BadRequest(res.t('missingClientHeader')));
@@ -83,7 +84,11 @@ export function authWithHeaders (options = {}) {
fields = `${fields} apiToken`;
}
const findPromise = fields ? User.findOne(userQuery).select(fields) : User.findOne(userQuery);
let findPromise = fields ? User.findOne(userQuery).select(fields) : User.findOne(userQuery);
if (leanUser) {
findPromise = findPromise.lean();
}
return findPromise
.exec()