diff --git a/assist-server/Makefile b/assist-server/Makefile new file mode 100644 index 000000000..5cfc5aae5 --- /dev/null +++ b/assist-server/Makefile @@ -0,0 +1,24 @@ +ee ?= "false" # true to build ee +arch ?= "amd64" # default amd64 +docker_runtime ?= "docker" # default docker runtime +docker_repo ?= "public.ecr.aws/p1t3u8a3" +docker_build_args ?= $(if $(filter depot,$(docker_runtime)),"--push","") + +.PHONY: help +help: ## Prints help for targets with comments + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-25s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +##@ Docker + +.PHONY: build +build: ## Build the backend. ee=true for ee build. + @DOCKER_BUILD_ARGS=$(docker_build_args) DOCKER_REPO=$(docker_repo) ARCH=$(arch) DOCKER_RUNTIME=$(docker_runtime) bash build.sh $(ee) + +##@ Local Dev + +.PHONY: scan +scan: ## Scan the backend + @echo scanning foss + @trivy fs -q . + @echo scanning ee + @trivy fs -q ../ee/assist-server/ diff --git a/backend/Makefile b/backend/Makefile index 1e445ad76..cdac74142 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -1,7 +1,10 @@ -ee ?= "false" # true to build ee +distro ?= foss # ee to build ee app ?= "" # app name, default all arch ?= "amd64" # default amd64 +docker_repo ?= "public.ecr.aws/p1t3u8a3" docker_runtime ?= "docker" # default docker runtime +image_tag ?= "" # image tag to build. Default is git sha short +docker_build_args ?= $(if $(filter depot,$(docker_runtime)),"--push","") .PHONY: help help: ## Prints help for targets with comments @@ -11,7 +14,7 @@ help: ## Prints help for targets with comments .PHONY: build build: ## Build the backend. ee=true for ee build. app=app name for only one app. Default build all apps. - ARCH=$(arch) DOCKER_RUNTIME=$(docker_runtime) bash build.sh $(ee) $(app) + IMAGE_TAG=$(image_tag) DOCKER_BUILD_ARGS=$(docker_build_args) DOCKER_REPO=$(docker_repo) ARCH=$(arch) DOCKER_RUNTIME=$(docker_runtime) bash build.sh $(distro) $(app) ##@ Local Dev diff --git a/backend/pkg/logger/logger.go b/backend/pkg/logger/logger.go index ddb6f9fc1..6a050b796 100644 --- a/backend/pkg/logger/logger.go +++ b/backend/pkg/logger/logger.go @@ -26,7 +26,11 @@ func New() Logger { encoderConfig := zap.NewProductionEncoderConfig() encoderConfig.EncodeTime = zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05.000") jsonEncoder := zapcore.NewJSONEncoder(encoderConfig) - core := zapcore.NewCore(jsonEncoder, zapcore.AddSync(os.Stdout), zap.InfoLevel) + logLevel := zap.InfoLevel + if os.Getenv("DEBUG") == "true" { + logLevel = zap.DebugLevel + } + core := zapcore.NewCore(jsonEncoder, zapcore.AddSync(os.Stdout), logLevel) baseLogger := zap.New(core, zap.AddCaller()) logger := baseLogger.WithOptions(zap.AddCallerSkip(1)) customLogger := &loggerImpl{l: logger} diff --git a/backend/pkg/server/api/middleware.go b/backend/pkg/server/api/middleware.go index 423e7e0d9..aad305393 100644 --- a/backend/pkg/server/api/middleware.go +++ b/backend/pkg/server/api/middleware.go @@ -13,7 +13,7 @@ func (e *routerImpl) health(w http.ResponseWriter, r *http.Request) { func (e *routerImpl) healthMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/" { + if r.URL.Path == "/" || r.URL.Path == "/health" { w.WriteHeader(http.StatusOK) return } diff --git a/ee/assist-server/.gitignore b/ee/assist-server/.gitignore deleted file mode 100644 index e0fd21e8f..000000000 --- a/ee/assist-server/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -.idea -node_modules -npm-debug.log -.cache -*.mmdb \ No newline at end of file diff --git a/ee/assist-server/Dockerfile b/ee/assist-server/Dockerfile deleted file mode 100644 index f30f5dc1d..000000000 --- a/ee/assist-server/Dockerfile +++ /dev/null @@ -1,24 +0,0 @@ -ARG ARCH=amd64 - -FROM --platform=linux/$ARCH node:23-alpine -LABEL Maintainer="Zavorotynskiy Alexander " -RUN apk add --no-cache tini git libc6-compat -ARG envarg -ENV ENTERPRISE_BUILD=${envarg} \ - MAXMINDDB_FILE=/home/openreplay/geoip.mmdb \ - PRIVATE_ENDPOINTS=false \ - LISTEN_PORT=9001 \ - ERROR=1 \ - NODE_ENV=production -WORKDIR /work -COPY package.json . -COPY package-lock.json . -RUN npm install -COPY . . - -RUN adduser -u 1001 openreplay -D -USER 1001 -ADD --chown=1001 https://static.openreplay.com/geoip/GeoLite2-City.mmdb $MAXMINDDB_FILE - -ENTRYPOINT ["/sbin/tini", "--"] -CMD npm start diff --git a/ee/assist-server/app/cache.js b/ee/assist-server/app/cache.js deleted file mode 100644 index 8a07e08dc..000000000 --- a/ee/assist-server/app/cache.js +++ /dev/null @@ -1,109 +0,0 @@ -const {logger} = require('./logger'); -const {createClient} = require("redis"); -const crypto = require("crypto"); - -let redisClient; -const REDIS_URL = (process.env.REDIS_URL || "localhost:6379").replace(/((^\w+:|^)\/\/|^)/, 'redis://'); -redisClient = createClient({url: REDIS_URL}); -redisClient.on("error", (error) => logger.error(`Redis cache error : ${error}`)); -void redisClient.connect(); - -function generateNodeID() { - const buffer = crypto.randomBytes(8); - return "node_"+buffer.readBigUInt64BE(0).toString(); -} - -const PING_INTERVAL = parseInt(process.env.PING_INTERVAL_SECONDS) || 25; -const CACHE_REFRESH_INTERVAL = parseInt(process.env.CACHE_REFRESH_INTERVAL_SECONDS) || 10; -const pingInterval = PING_INTERVAL + PING_INTERVAL/2; -const cacheRefreshInterval = CACHE_REFRESH_INTERVAL + CACHE_REFRESH_INTERVAL/2; -const cacheRefreshIntervalMs = CACHE_REFRESH_INTERVAL * 1000; -let lastCacheUpdateTime = 0; -let cacheRefresher = null; -const nodeID = process.env.HOSTNAME || generateNodeID(); - -const addSessionToCache = async function (sessionID, sessionData) { - try { - await redisClient.set(`active_sessions:${sessionID}`, JSON.stringify(sessionData), 'EX', pingInterval); - logger.debug(`Session ${sessionID} stored in Redis`); - } catch (error) { - logger.error(error); - } -} - -const renewSession = async function (sessionID){ - try { - await redisClient.expire(`active_sessions:${sessionID}`, pingInterval); - logger.debug(`Session ${sessionID} renewed in Redis`); - } catch (error) { - logger.error(error); - } -} - -const getSessionFromCache = async function (sessionID) { - try { - const sessionData = await redisClient.get(`active_sessions:${sessionID}`); - if (sessionData) { - logger.debug(`Session ${sessionID} retrieved from Redis`); - return JSON.parse(sessionData); - } - return null; - } catch (error) { - logger.error(error); - return null; - } -} - -const removeSessionFromCache = async function (sessionID) { - try { - await redisClient.del(`active_sessions:${sessionID}`); - logger.debug(`Session ${sessionID} removed from Redis`); - } catch (error) { - logger.error(error); - } -} - -const setNodeSessions = async function (nodeID, sessionIDs) { - try { - await redisClient.set(`node:${nodeID}:sessions`, JSON.stringify(sessionIDs), 'EX', cacheRefreshInterval); - logger.debug(`Node ${nodeID} sessions stored in Redis`); - } catch (error) { - logger.error(error); - } -} - -function startCacheRefresher(io) { - if (cacheRefresher) clearInterval(cacheRefresher); - - cacheRefresher = setInterval(async () => { - const now = Date.now(); - if (now - lastCacheUpdateTime < cacheRefreshIntervalMs) { - return; - } - logger.debug('Background refresh triggered'); - try { - const startTime = performance.now(); - const sessionIDs = new Set(); - const result = await io.fetchSockets(); - result.forEach((socket) => { - if (socket.handshake.query.sessId) { - sessionIDs.add(socket.handshake.query.sessId); - } - }) - await setNodeSessions(nodeID, Array.from(sessionIDs)); - lastCacheUpdateTime = now; - const duration = performance.now() - startTime; - logger.info(`Background refresh complete: ${duration}ms, ${result.length} sockets`); - } catch (error) { - logger.error(`Background refresh error: ${error}`); - } - }, cacheRefreshIntervalMs / 2); -} - -module.exports = { - addSessionToCache, - renewSession, - getSessionFromCache, - removeSessionFromCache, - startCacheRefresher, -} \ No newline at end of file diff --git a/ee/assist-server/package-lock.json b/ee/assist-server/package-lock.json deleted file mode 100644 index 8d8f17e93..000000000 --- a/ee/assist-server/package-lock.json +++ /dev/null @@ -1,1761 +0,0 @@ -{ - "name": "assist-server", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "assist-server", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "@fastify/deepmerge": "^3.0.0", - "@maxmind/geoip2-node": "^6.0.0", - "express": "^4.21.2", - "jsonwebtoken": "^9.0.2", - "redis": "^4.7.0", - "socket.io": "^4.8.1", - "socket.io-client": "^4.8.1", - "ua-parser-js": "^2.0.3", - "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.51.0", - "winston": "^3.17.0" - } - }, - "node_modules/@colors/colors": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", - "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/@dabh/diagnostics": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", - "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", - "dependencies": { - "colorspace": "1.1.x", - "enabled": "2.0.x", - "kuler": "^2.0.0" - } - }, - "node_modules/@fastify/deepmerge": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@fastify/deepmerge/-/deepmerge-3.0.0.tgz", - "integrity": "sha512-VW7srTEkCzbfqoRBn+k7s/nP9WVFmKSfeHBbMZLLrOYDQoShHWuEi5V6Jgz1Q0fm3pMYsiC+EVMvhZyI3hKzkw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ] - }, - "node_modules/@maxmind/geoip2-node": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@maxmind/geoip2-node/-/geoip2-node-6.0.0.tgz", - "integrity": "sha512-b1zcdxRm13HDgNfHnRpET7E3r1NlSK+VpQhN9dKn8aUUH71Ug6rNmgASYh7WrHDZLDqSXMHryfs5Aw0ufMtBYw==", - "dependencies": { - "maxmind": "^4.2.0" - } - }, - "node_modules/@redis/bloom": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", - "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, - "node_modules/@redis/client": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.0.tgz", - "integrity": "sha512-aR0uffYI700OEEH4gYnitAnv3vzVGXCFvYfdpu/CJKvk4pHfLPEy/JSZyrpQ+15WhXe1yJRXLtfQ84s4mEXnPg==", - "dependencies": { - "cluster-key-slot": "1.1.2", - "generic-pool": "3.9.0", - "yallist": "4.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@redis/graph": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz", - "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==", - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, - "node_modules/@redis/json": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.7.tgz", - "integrity": "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==", - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, - "node_modules/@redis/search": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.2.0.tgz", - "integrity": "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==", - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, - "node_modules/@redis/time-series": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.1.0.tgz", - "integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==", - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, - "node_modules/@socket.io/component-emitter": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", - "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" - }, - "node_modules/@types/cors": { - "version": "2.8.17", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", - "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/node": { - "version": "22.13.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.14.tgz", - "integrity": "sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w==", - "dependencies": { - "undici-types": "~6.20.0" - } - }, - "node_modules/@types/node-fetch": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", - "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", - "dependencies": { - "@types/node": "*", - "form-data": "^4.0.0" - } - }, - "node_modules/@types/triple-beam": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", - "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/base64id": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", - "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", - "engines": { - "node": "^4.5.0 || >= 5.9" - } - }, - "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/cluster-key-slot": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", - "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/color": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", - "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", - "dependencies": { - "color-convert": "^1.9.3", - "color-string": "^1.6.0" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "node_modules/colorspace": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", - "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", - "dependencies": { - "color": "^3.1.3", - "text-hex": "1.0.x" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-europe-js": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/detect-europe-js/-/detect-europe-js-0.1.2.tgz", - "integrity": "sha512-lgdERlL3u0aUdHocoouzT10d9I89VVhk0qNRmll7mXdGfJT1/wqZ2ZLA4oJAjeACPY5fT1wsbq2AT+GkuInsow==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/faisalman" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - } - ] - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "node_modules/enabled": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", - "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/engine.io": { - "version": "6.6.4", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", - "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", - "dependencies": { - "@types/cors": "^2.8.12", - "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.7.2", - "cors": "~2.8.5", - "debug": "~4.3.1", - "engine.io-parser": "~5.2.1", - "ws": "~8.17.1" - }, - "engines": { - "node": ">=10.2.0" - } - }, - "node_modules/engine.io-client": { - "version": "6.6.3", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", - "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1", - "engine.io-parser": "~5.2.1", - "ws": "~8.17.1", - "xmlhttprequest-ssl": "~2.1.1" - } - }, - "node_modules/engine.io-client/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/engine.io-client/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/engine.io-parser": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", - "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/engine.io/node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/engine.io/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/engine.io/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/fecha": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", - "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" - }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/fn.name": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", - "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" - }, - "node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/generic-pool": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", - "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", - "engines": { - "node": ">= 4" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" - }, - "node_modules/is-standalone-pwa": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-standalone-pwa/-/is-standalone-pwa-0.1.1.tgz", - "integrity": "sha512-9Cbovsa52vNQCjdXOzeQq5CnCbAcRk05aU62K20WO372NrTv0NxibLFCK6lQ4/iZEFdEA3p3t2VNOn8AJ53F5g==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/faisalman" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - } - ] - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jsonwebtoken": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", - "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", - "dependencies": { - "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=12", - "npm": ">=6" - } - }, - "node_modules/jsonwebtoken/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", - "dependencies": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "dependencies": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/kuler": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", - "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" - }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" - }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" - }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" - }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" - }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" - }, - "node_modules/logform": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", - "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", - "dependencies": { - "@colors/colors": "1.6.0", - "@types/triple-beam": "^1.3.2", - "fecha": "^4.2.0", - "ms": "^2.1.1", - "safe-stable-stringify": "^2.3.1", - "triple-beam": "^1.3.0" - }, - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/logform/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/maxmind": { - "version": "4.3.24", - "resolved": "https://registry.npmjs.org/maxmind/-/maxmind-4.3.24.tgz", - "integrity": "sha512-dexrLcjfS2xDGOvdV8XcfQYmyQVpGidMwEG2ld19lXlsB+i+lXRWPzQi81HfwRXR4hxzFr5gT0oAIFyqAAb/Ww==", - "dependencies": { - "mmdb-lib": "2.1.1", - "tiny-lru": "11.2.11" - }, - "engines": { - "node": ">=12", - "npm": ">=6" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mmdb-lib": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/mmdb-lib/-/mmdb-lib-2.1.1.tgz", - "integrity": "sha512-yx8H/1H5AfnufiLnzzPqPf4yr/dKU9IFT1rPVwSkrKWHsQEeVVd6+X+L0nUbXhlEFTu3y/7hu38CFmEVgzvyeg==", - "engines": { - "node": ">=10", - "npm": ">=6" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/one-time": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", - "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", - "dependencies": { - "fn.name": "1.x.x" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/redis": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/redis/-/redis-4.7.0.tgz", - "integrity": "sha512-zvmkHEAdGMn+hMRXuMBtu4Vo5P6rHQjLoHftu+lBqq8ZTA3RCVC/WzD790bkKKiNFp7d5/9PcSD19fJyyRvOdQ==", - "dependencies": { - "@redis/bloom": "1.2.0", - "@redis/client": "1.6.0", - "@redis/graph": "1.1.1", - "@redis/json": "1.0.7", - "@redis/search": "1.2.0", - "@redis/time-series": "1.1.0" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safe-stable-stringify": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", - "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/socket.io": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", - "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", - "dependencies": { - "accepts": "~1.3.4", - "base64id": "~2.0.0", - "cors": "~2.8.5", - "debug": "~4.3.2", - "engine.io": "~6.6.0", - "socket.io-adapter": "~2.5.2", - "socket.io-parser": "~4.2.4" - }, - "engines": { - "node": ">=10.2.0" - } - }, - "node_modules/socket.io-adapter": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", - "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", - "dependencies": { - "debug": "~4.3.4", - "ws": "~8.17.1" - } - }, - "node_modules/socket.io-adapter/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socket.io-adapter/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/socket.io-client": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", - "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.2", - "engine.io-client": "~6.6.1", - "socket.io-parser": "~4.2.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/socket.io-client/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socket.io-client/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/socket.io-parser": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", - "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/socket.io-parser/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socket.io-parser/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/socket.io/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socket.io/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", - "engines": { - "node": "*" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/text-hex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", - "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" - }, - "node_modules/tiny-lru": { - "version": "11.2.11", - "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-11.2.11.tgz", - "integrity": "sha512-27BIW0dIWTYYoWNnqSmoNMKe5WIbkXsc0xaCQHd3/3xT2XMuMJrzHdrO9QBFR14emBz1Bu0dOAs2sCBBrvgPQA==", - "engines": { - "node": ">=12" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/triple-beam": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", - "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ua-is-frozen": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ua-is-frozen/-/ua-is-frozen-0.1.2.tgz", - "integrity": "sha512-RwKDW2p3iyWn4UbaxpP2+VxwqXh0jpvdxsYpZ5j/MLLiQOfbsV5shpgQiw93+KMYQPcteeMQ289MaAFzs3G9pw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/faisalman" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - } - ] - }, - "node_modules/ua-parser-js": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-2.0.3.tgz", - "integrity": "sha512-LZyXZdNttONW8LjzEH3Z8+6TE7RfrEiJqDKyh0R11p/kxvrV2o9DrT2FGZO+KVNs3k+drcIQ6C3En6wLnzJGpw==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - }, - { - "type": "github", - "url": "https://github.com/sponsors/faisalman" - } - ], - "dependencies": { - "@types/node-fetch": "^2.6.12", - "detect-europe-js": "^0.1.2", - "is-standalone-pwa": "^0.1.1", - "node-fetch": "^2.7.0", - "ua-is-frozen": "^0.1.2" - }, - "bin": { - "ua-parser-js": "script/cli.js" - }, - "engines": { - "node": "*" - } - }, - "node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==" - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uWebSockets.js": { - "version": "20.51.0", - "resolved": "git+ssh://git@github.com/uNetworking/uWebSockets.js.git#6609a88ffa9a16ac5158046761356ce03250a0df", - "license": "Apache-2.0" - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/winston": { - "version": "3.17.0", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz", - "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", - "dependencies": { - "@colors/colors": "^1.6.0", - "@dabh/diagnostics": "^2.0.2", - "async": "^3.2.3", - "is-stream": "^2.0.0", - "logform": "^2.7.0", - "one-time": "^1.0.0", - "readable-stream": "^3.4.0", - "safe-stable-stringify": "^2.3.1", - "stack-trace": "0.0.x", - "triple-beam": "^1.3.0", - "winston-transport": "^4.9.0" - }, - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/winston-transport": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", - "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", - "dependencies": { - "logform": "^2.7.0", - "readable-stream": "^3.6.2", - "triple-beam": "^1.3.0" - }, - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xmlhttprequest-ssl": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", - "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - } - } -} diff --git a/ee/assist-server/package.json b/ee/assist-server/package.json deleted file mode 100644 index 50c1fbf95..000000000 --- a/ee/assist-server/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "assist-server", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "keywords": [], - "author": "", - "license": "ISC", - "dependencies": { - "@fastify/deepmerge": "^3.0.0", - "@maxmind/geoip2-node": "^6.0.0", - "express": "^4.21.2", - "jsonwebtoken": "^9.0.2", - "redis": "^4.7.0", - "socket.io": "^4.8.1", - "socket.io-client": "^4.8.1", - "ua-parser-js": "^2.0.3", - "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.51.0", - "winston": "^3.17.0" - } -} diff --git a/ee/assist-server/server.js b/ee/assist-server/server.js deleted file mode 100644 index c143ab0d4..000000000 --- a/ee/assist-server/server.js +++ /dev/null @@ -1,67 +0,0 @@ -const { App } = require('uWebSockets.js'); -const { Server } = require('socket.io'); -const { logger } = require("./app/logger"); -const { authorizer } = require("./app/assist"); -const { onConnect, setSocketIOServer } = require("./app/socket"); -const { startCacheRefresher } = require("./app/cache"); - -const app = App(); -const pingInterval = parseInt(process.env.PING_INTERVAL) || 25000; - -const getCompressionConfig = function () { - // WS: The theoretical overhead per socket is 19KB (11KB for compressor and 8KB for decompressor) - let perMessageDeflate = false; - if (process.env.COMPRESSION === "true") { - logger.info(`WS compression: enabled`); - perMessageDeflate = { - zlibDeflateOptions: { - windowBits: 10, - memLevel: 1 - }, - zlibInflateOptions: { - windowBits: 10 - } - } - } else { - logger.info(`WS compression: disabled`); - } - return { - perMessageDeflate: perMessageDeflate, - clientNoContextTakeover: true - }; -} - -const io = new Server({ - maxHttpBufferSize: (parseFloat(process.env.maxHttpBufferSize) || 5) * 1e6, - pingInterval: pingInterval, // Will use it for cache invalidation - cors: { - origin: "*", // Allow connections from any origin (for development) - methods: ["GET", "POST"], - credentials: true - }, - path: '/socket', - ...getCompressionConfig() -}); - -io.use(async (socket, next) => await authorizer.check(socket, next)); -io.on('connection', (socket) => onConnect(socket)); -io.attachApp(app); -io.engine.on("headers", (headers) => { - headers["x-host-id"] = process.env.HOSTNAME || "unknown"; -}); -setSocketIOServer(io); - -const HOST = process.env.LISTEN_HOST || '0.0.0.0'; -const PORT = parseInt(process.env.PORT) || 9001; -app.listen(PORT, (token) => { - if (token) { - console.log(`Server running at http://${HOST}:${PORT}`); - } else { - console.log(`Failed to listen on port ${PORT}`); - } -}); -startCacheRefresher(io); - -process.on('uncaughtException', err => { - logger.error(`Uncaught Exception: ${err}`); -}); \ No newline at end of file diff --git a/ee/assist/.gitignore b/ee/assist/.gitignore index 085b1a57e..e0fd21e8f 100644 --- a/ee/assist/.gitignore +++ b/ee/assist/.gitignore @@ -2,20 +2,4 @@ node_modules npm-debug.log .cache -test.html -build.sh - - - -servers/peerjs-server.js -servers/sourcemaps-handler.js -servers/sourcemaps-server.js -/utils/geoIP.js -/utils/health.js -/utils/HeapSnapshot.js -/utils/helper.js -/utils/assistHelper.js -/utils/httpHandlers.js -/utils/socketHandlers.js -.local *.mmdb \ No newline at end of file diff --git a/ee/assist/Dockerfile b/ee/assist/Dockerfile index b80869cfa..f30f5dc1d 100644 --- a/ee/assist/Dockerfile +++ b/ee/assist/Dockerfile @@ -1,11 +1,8 @@ -#ARCH can be amd64 or arm64 ARG ARCH=amd64 FROM --platform=linux/$ARCH node:23-alpine -LABEL Maintainer="KRAIEM Taha Yassine" +LABEL Maintainer="Zavorotynskiy Alexander " RUN apk add --no-cache tini git libc6-compat -# && ln -s /lib/libc.musl-x86_64.so.1 /lib/ld-linux-x86-64.so.2 - ARG envarg ENV ENTERPRISE_BUILD=${envarg} \ MAXMINDDB_FILE=/home/openreplay/geoip.mmdb \ diff --git a/ee/assist-server/app/assist.js b/ee/assist/app/assist.js similarity index 98% rename from ee/assist-server/app/assist.js rename to ee/assist/app/assist.js index a394f4908..3745e590d 100644 --- a/ee/assist-server/app/assist.js +++ b/ee/assist/app/assist.js @@ -32,16 +32,13 @@ EVENTS_DEFINITION.emit = { const BASE_sessionInfo = { "pageTitle": "Page", "active": false, - "live": true, "sessionID": "0", "metadata": {}, "userID": "", "userUUID": "", "projectKey": "", - "revID": "", "timestamp": 0, "trackerVersion": "", - "isSnippet": true, "userOs": "", "userBrowser": "", "userBrowserVersion": "", @@ -49,8 +46,7 @@ const BASE_sessionInfo = { "userDeviceType": "", "userCountry": "", "userState": "", - "userCity": "", - "projectId": 0 + "userCity": "" }; const extractPeerId = (peerId) => { diff --git a/ee/assist/app/cache.js b/ee/assist/app/cache.js new file mode 100644 index 000000000..efe3c7181 --- /dev/null +++ b/ee/assist/app/cache.js @@ -0,0 +1,180 @@ +const {logger} = require('./logger'); +const Redis = require("ioredis"); +const crypto = require("crypto"); +const { Mutex } = require("async-mutex"); + +const REDIS_URL = process.env.REDIS_URL || "localhost:6379"; +const redisClient = new Redis(REDIS_URL); +redisClient.on("error", (error) => { + logger.error(`Redis cache error : ${error}`); +}); + +function generateNodeID() { + const buffer = crypto.randomBytes(8); + return "node_"+buffer.readBigUInt64BE(0).toString(); +} + +const batchSize = parseInt(process.env.REDIS_BATCH_SIZE) || 1000; +const PING_INTERVAL = parseInt(process.env.PING_INTERVAL_SECONDS) || 25; +const CACHE_REFRESH_INTERVAL = parseInt(process.env.CACHE_REFRESH_INTERVAL_SECONDS) || 5; +const pingInterval = Math.floor(PING_INTERVAL + PING_INTERVAL/2); +const cacheRefreshInterval = Math.floor(CACHE_REFRESH_INTERVAL * 4); +const cacheRefreshIntervalMs = CACHE_REFRESH_INTERVAL * 1000; +let lastCacheUpdateTime = 0; +let cacheRefresher = null; +const nodeID = process.env.HOSTNAME || generateNodeID(); + +const mutex = new Mutex(); +const localCache = { + addedSessions: new Set(), + updatedSessions: new Set(), + refreshedSessions: new Set(), + deletedSessions: new Set() +}; + +const addSession = async function (sessionID) { + await mutex.runExclusive(() => { + localCache.addedSessions.add(sessionID); + }); +} + +const updateSession = async function (sessionID) { + await mutex.runExclusive(() => { + localCache.addedSessions.add(sessionID); // to update the session's cache + localCache.updatedSessions.add(sessionID); // to add sessionID to the list of recently updated sessions + }); +} + +const renewSession = async function (sessionID) { + await mutex.runExclusive(() => { + localCache.refreshedSessions.add(sessionID); + }) +} + +const removeSession = async function (sessionID) { + await mutex.runExclusive(() => { + localCache.deletedSessions.add(sessionID); + }); +} + +const updateNodeCache = async function (io) { + logger.debug('Background refresh triggered'); + try { + const startTime = performance.now(); + let currStepTs = performance.now(); + const sessionIDs = new Set(); + const result = await io.fetchSockets(); + let toAdd = new Map(); + let toUpdate = []; + let toRenew = []; + let toDelete = []; + await mutex.runExclusive(() => { + result.forEach((socket) => { + if (socket.handshake.query.sessId) { + const sessID = socket.handshake.query.sessId; + if (sessionIDs.has(sessID)) { + return; + } + sessionIDs.add(sessID); + if (localCache.addedSessions.has(sessID)) { + toAdd.set(sessID, socket.handshake.query.sessionInfo); + } + } + }); + toUpdate = [...localCache.updatedSessions]; + toRenew = [...localCache.refreshedSessions]; + toDelete = [...localCache.deletedSessions]; + // Clear the local cache + localCache.addedSessions.clear(); + localCache.updatedSessions.clear(); + localCache.refreshedSessions.clear(); + localCache.deletedSessions.clear(); + }) + + // insert new sessions in pipeline + const toAddArray = Array.from(toAdd.keys()); + for (let i = 0; i < toAddArray.length; i += batchSize) { + const batch = toAddArray.slice(i, i + batchSize); + const pipeline = redisClient.pipeline(); + for (const sessionID of batch) { + pipeline.set(`assist:online_sessions:${sessionID}`, JSON.stringify(toAdd.get(sessionID)), 'EX', pingInterval); + } + await pipeline.exec(); + } + logger.info(`step 1 (toAdd) complete: ${(performance.now() - currStepTs).toFixed(2)}ms, ${toAddArray.length} sockets`); + currStepTs = performance.now(); + + // renew sessions in pipeline + for (let i = 0; i < toRenew.length; i += batchSize) { + const batch = toRenew.slice(i, i + batchSize); + const pipeline = redisClient.pipeline(); + for (const sessionID of batch) { + pipeline.expire(`assist:online_sessions:${sessionID}`, pingInterval); + } + await pipeline.exec(); + } + logger.info(`step 2 (toRenew) complete: ${(performance.now() - currStepTs).toFixed(2)}ms, ${toRenew.length} sockets`); + currStepTs = performance.now(); + + // delete sessions in pipeline + for (let i = 0; i < toDelete.length; i += batchSize) { + const batch = toDelete.slice(i, i + batchSize); + const pipeline = redisClient.pipeline(); + for (const sessionID of batch) { + pipeline.del(`assist:online_sessions:${sessionID}`); + } + await pipeline.exec(); + } + logger.info(`step 3 (toDelete) complete: ${(performance.now() - currStepTs).toFixed(2)}ms, ${toDelete.length} sockets`); + currStepTs = performance.now(); + + // add recently updated sessions + if (toUpdate.length > 0) { + await redisClient.sadd('assist:updated_sessions', toUpdate); + } + // store the node sessions + await redisClient.set(`assist:nodes:${nodeID}:sessions`, JSON.stringify(Array.from(sessionIDs)), 'EX', cacheRefreshInterval); + logger.info(`step 4 (full list + updated) complete: ${(performance.now() - currStepTs).toFixed(2)}ms, ${toUpdate.length} sockets`); + + const duration = performance.now() - startTime; + logger.info(`Background refresh complete: ${duration.toFixed(2)}ms, ${result.length} sockets`); + } catch (error) { + logger.error(`Background refresh error: ${error}`); + } +} + +let isFlushing = false; + +function startCacheRefresher(io) { + if (cacheRefresher) clearInterval(cacheRefresher); + + cacheRefresher = setInterval(async () => { + if (isFlushing) { + logger.warn("Skipping tick: flush in progress"); + return; + } + + const now = Date.now(); + if (now - lastCacheUpdateTime < cacheRefreshIntervalMs) { + return; + } + + isFlushing = true; + try { + await updateNodeCache(io); + lastCacheUpdateTime = Date.now(); + } catch (err) { + logger.error(`Tick error: ${err}`); + } finally { + isFlushing = false; + } + }, cacheRefreshIntervalMs / 2); +} + +module.exports = { + addSession, + updateSession, + renewSession, + removeSession, + startCacheRefresher, +} \ No newline at end of file diff --git a/ee/assist-server/app/geoIP.js b/ee/assist/app/geoIP.js similarity index 100% rename from ee/assist-server/app/geoIP.js rename to ee/assist/app/geoIP.js diff --git a/ee/assist-server/app/logger.js b/ee/assist/app/logger.js similarity index 100% rename from ee/assist-server/app/logger.js rename to ee/assist/app/logger.js diff --git a/ee/assist-server/app/socket.js b/ee/assist/app/socket.js similarity index 93% rename from ee/assist-server/app/socket.js rename to ee/assist/app/socket.js index 28005a2fc..f2bb882ae 100644 --- a/ee/assist-server/app/socket.js +++ b/ee/assist/app/socket.js @@ -6,9 +6,10 @@ const { errorHandler } = require("./assist"); const { - addSessionToCache, + addSession, + updateSession, renewSession, - removeSessionFromCache + removeSession } = require('./cache'); const { logger @@ -83,8 +84,11 @@ async function getRoomData(roomID) { async function onConnect(socket) { logger.debug(`A new client:${socket.id}, Query:${JSON.stringify(socket.handshake.query)}`); // Drop unknown socket.io connections - if (socket.handshake.query.identity === undefined || socket.handshake.query.peerId === undefined || socket.handshake.query.sessionInfo === undefined) { - logger.debug(`something is undefined, refusing connexion`); + if (socket.handshake.query.identity === undefined || socket.handshake.query.peerId === undefined) { + logger.debug(`no identity or peerId, refusing connexion`); + return socket.disconnect(); + } else if (socket.handshake.query.identity === IDENTITIES.session && socket.handshake.query.sessionInfo === undefined) { + logger.debug(`sessionInfo is undefined, refusing connexion`); return socket.disconnect(); } processPeerInfo(socket); @@ -122,7 +126,7 @@ async function onConnect(socket) { // Add session to cache if (socket.handshake.query.identity === IDENTITIES.session) { - await addSessionToCache(socket.handshake.query.sessId, socket.handshake.query.sessionInfo); + await addSession(socket.handshake.query.sessId, socket.handshake.query.sessionInfo); } if (socket.handshake.query.identity === IDENTITIES.agent) { @@ -167,7 +171,7 @@ async function onDisconnect(socket) { let {tabsCount, agentsCount, tabIDs, agentIDs} = await getRoomData(socket.handshake.query.roomId); if (tabsCount <= 0) { - await removeSessionFromCache(socket.handshake.query.sessId); + await removeSession(socket.handshake.query.sessId); } if (tabsCount === -1 && agentsCount === -1) { @@ -195,7 +199,7 @@ async function onUpdateEvent(socket, ...args) { socket.handshake.query.sessionInfo = deepMerge(socket.handshake.query.sessionInfo, args[0]?.data, {tabId: args[0]?.meta?.tabId}); // update session cache - await addSessionToCache(socket.handshake.query.sessId, socket.handshake.query.sessionInfo); + await updateSession(socket.handshake.query.sessId, socket.handshake.query.sessionInfo); // Update sessionInfo for all agents in the room const connected_sockets = await fetchSockets(socket.handshake.query.roomId); diff --git a/ee/assist/clean-dev.sh b/ee/assist/clean-dev.sh deleted file mode 100755 index 868545257..000000000 --- a/ee/assist/clean-dev.sh +++ /dev/null @@ -1,14 +0,0 @@ -rm -rf ./utils/assistHelper.js -rm -rf ./utils/geoIP.js -rm -rf ./utils/health.js -rm -rf ./utils/HeapSnapshot.js -rm -rf ./utils/helper.js -rm -rf ./utils/httpHandlers.js -rm -rf ./utils/logger.js -rm -rf ./utils/metrics.js -rm -rf ./utils/socketHandlers.js - -rm -rf servers/peerjs-server.js -rm -rf servers/sourcemaps-handler.js -rm -rf servers/sourcemaps-server.js -rm -rf build.sh \ No newline at end of file diff --git a/ee/assist/package-lock.json b/ee/assist/package-lock.json index b7ea390b0..d1618b261 100644 --- a/ee/assist/package-lock.json +++ b/ee/assist/package-lock.json @@ -1,25 +1,26 @@ { "name": "assist-server", - "version": "v1.22.0-ee", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "assist-server", - "version": "v1.12.0-ee", - "license": "Elastic License 2.0 (ELv2)", + "version": "1.0.0", + "license": "ISC", "dependencies": { - "@fastify/deepmerge": "^2.0.1", - "@maxmind/geoip2-node": "^4.2.0", - "@socket.io/redis-adapter": "^8.2.1", - "express": "^4.21.1", + "@fastify/deepmerge": "^3.0.0", + "@maxmind/geoip2-node": "^6.0.0", + "async-mutex": "^0.5.0", + "express": "^4.21.2", + "ioredis": "^5.6.1", "jsonwebtoken": "^9.0.2", - "prom-client": "^15.0.0", - "redis": "^4.6.10", - "socket.io": "^4.8.0", - "ua-parser-js": "^1.0.37", + "redis": "^4.7.0", + "socket.io": "^4.8.1", + "socket.io-client": "^4.8.1", + "ua-parser-js": "^2.0.3", "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.51.0", - "winston": "^3.13.0" + "winston": "^3.17.0" } }, "node_modules/@colors/colors": { @@ -41,9 +42,9 @@ } }, "node_modules/@fastify/deepmerge": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@fastify/deepmerge/-/deepmerge-2.0.1.tgz", - "integrity": "sha512-hx+wJQr9Ph1hY/dyzY0SxqjumMyqZDlIF6oe71dpRKDHUg7dFQfjG94qqwQ274XRjmUrwKiYadex8XplNHx3CA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@fastify/deepmerge/-/deepmerge-3.0.0.tgz", + "integrity": "sha512-VW7srTEkCzbfqoRBn+k7s/nP9WVFmKSfeHBbMZLLrOYDQoShHWuEi5V6Jgz1Q0fm3pMYsiC+EVMvhZyI3hKzkw==", "funding": [ { "type": "github", @@ -55,21 +56,17 @@ } ] }, - "node_modules/@maxmind/geoip2-node": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@maxmind/geoip2-node/-/geoip2-node-4.2.0.tgz", - "integrity": "sha512-TIyKmvdIjDtRRRZTM2tgm8vHoKH1Sxr7+r7g2bB+5+Uvho7M9/+gXMZalQMpqMtm2hKFq27qoHpMpqq82ycxQA==", - "dependencies": { - "ip6addr": "^0.2.5", - "maxmind": "^4.2.0" - } + "node_modules/@ioredis/commands": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", + "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==" }, - "node_modules/@opentelemetry/api": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", - "engines": { - "node": ">=8.0.0" + "node_modules/@maxmind/geoip2-node": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@maxmind/geoip2-node/-/geoip2-node-6.0.0.tgz", + "integrity": "sha512-b1zcdxRm13HDgNfHnRpET7E3r1NlSK+VpQhN9dKn8aUUH71Ug6rNmgASYh7WrHDZLDqSXMHryfs5Aw0ufMtBYw==", + "dependencies": { + "maxmind": "^4.2.0" } }, "node_modules/@redis/bloom": { @@ -130,27 +127,6 @@ "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" }, - "node_modules/@socket.io/redis-adapter": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@socket.io/redis-adapter/-/redis-adapter-8.3.0.tgz", - "integrity": "sha512-ly0cra+48hDmChxmIpnESKrc94LjRL80TEmZVscuQ/WWkRP81nNj8W8cCGMqbI4L6NCuAaPRSzZF1a9GlAxxnA==", - "dependencies": { - "debug": "~4.3.1", - "notepack.io": "~3.0.1", - "uid2": "1.0.0" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "socket.io-adapter": "^2.5.4" - } - }, - "node_modules/@types/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" - }, "node_modules/@types/cors": { "version": "2.8.17", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", @@ -160,11 +136,20 @@ } }, "node_modules/@types/node": { - "version": "22.7.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.6.tgz", - "integrity": "sha512-/d7Rnj0/ExXDMcioS78/kf1lMzYk4BZV8MZGTBKzTGZ6/406ukkbYlIsZmMPhcR5KlkunDHQLrtAVmSq7r+mSw==", + "version": "22.13.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.14.tgz", + "integrity": "sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w==", "dependencies": { - "undici-types": "~6.19.2" + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" } }, "node_modules/@types/triple-beam": { @@ -189,19 +174,24 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "engines": { - "node": ">=0.8" - } - }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" }, + "node_modules/async-mutex": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", + "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/base64id": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", @@ -210,11 +200,6 @@ "node": "^4.5.0 || >= 5.9" } }, - "node_modules/bintrees": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz", - "integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==" - }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -238,19 +223,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -264,16 +236,25 @@ "node": ">= 0.8" } }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dependencies": { - "es-define-property": "^1.0.0", "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { "node": ">= 0.4" @@ -330,6 +311,17 @@ "text-hex": "1.0.x" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -362,11 +354,6 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" - }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -380,35 +367,27 @@ } }, "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "ms": "2.0.0" } }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.4.0" + } + }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "engines": { + "node": ">=0.10" } }, "node_modules/depd": { @@ -428,6 +407,38 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-europe-js": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/detect-europe-js/-/detect-europe-js-0.1.2.tgz", + "integrity": "sha512-lgdERlL3u0aUdHocoouzT10d9I89VVhk0qNRmll7mXdGfJT1/wqZ2ZLA4oJAjeACPY5fT1wsbq2AT+GkuInsow==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + } + ] + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -455,11 +466,10 @@ } }, "node_modules/engine.io": { - "version": "6.6.2", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", - "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==", + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", + "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", "dependencies": { - "@types/cookie": "^0.4.1", "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", "accepts": "~1.3.4", @@ -474,6 +484,39 @@ "node": ">=10.2.0" } }, + "node_modules/engine.io-client": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", + "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-client/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/engine.io-parser": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", @@ -490,13 +533,31 @@ "node": ">= 0.6" } }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dependencies": { - "get-intrinsic": "^1.2.4" + "ms": "^2.1.3" }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "engines": { "node": ">= 0.4" } @@ -509,6 +570,31 @@ "node": ">= 0.4" } }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -567,27 +653,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "engines": [ - "node >=0.6.0" - ] - }, "node_modules/fecha": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", @@ -610,24 +675,25 @@ "node": ">= 0.8" } }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/fn.name": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -661,15 +727,20 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -678,32 +749,22 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gopd": { + "node_modules/get-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "dependencies": { - "get-intrinsic": "^1.1.3" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">= 0.4" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "engines": { "node": ">= 0.4" }, @@ -712,9 +773,23 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, "engines": { "node": ">= 0.4" }, @@ -764,15 +839,50 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "node_modules/ip6addr": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/ip6addr/-/ip6addr-0.2.5.tgz", - "integrity": "sha512-9RGGSB6Zc9Ox5DpDGFnJdIeF0AsqXzdH+FspCfPPaU/L/4tI6P+5lIoFUFm9JXs9IrJv1boqAaNCQmoDADTSKQ==", + "node_modules/ioredis": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.1.tgz", + "integrity": "sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA==", "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^2.0.2" + "@ioredis/commands": "^1.1.1", + "cluster-key-slot": "^1.1.0", + "debug": "^4.3.4", + "denque": "^2.1.0", + "lodash.defaults": "^4.2.0", + "lodash.isarguments": "^3.1.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0", + "standard-as-callback": "^2.1.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ioredis" } }, + "node_modules/ioredis/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/ioredis/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -786,6 +896,25 @@ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" }, + "node_modules/is-standalone-pwa": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-standalone-pwa/-/is-standalone-pwa-0.1.1.tgz", + "integrity": "sha512-9Cbovsa52vNQCjdXOzeQq5CnCbAcRk05aU62K20WO372NrTv0NxibLFCK6lQ4/iZEFdEA3p3t2VNOn8AJ53F5g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + } + ] + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -797,11 +926,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" - }, "node_modules/jsonwebtoken": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", @@ -823,19 +947,10 @@ "npm": ">=6" } }, - "node_modules/jsprim": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", - "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", - "engines": [ - "node >=0.6.0" - ], - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - } + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/jwa": { "version": "1.4.1", @@ -861,11 +976,21 @@ "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==" + }, "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", @@ -897,9 +1022,9 @@ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" }, "node_modules/logform": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.1.tgz", - "integrity": "sha512-CdaO738xRapbKIMVn2m4F6KTj4j7ooJ8POVnebSgKo3KBz5axNXRAL7ZdRjIV6NOr2Uf4vjtRkxrFETOioCqSA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", "dependencies": { "@colors/colors": "1.6.0", "@types/triple-beam": "^1.3.2", @@ -912,10 +1037,23 @@ "node": ">= 12.0.0" } }, + "node_modules/logform/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/maxmind": { - "version": "4.3.21", - "resolved": "https://registry.npmjs.org/maxmind/-/maxmind-4.3.21.tgz", - "integrity": "sha512-orda4yI01roTa4pP5Jf43u5HPOoEIGaLudTn9cdgCPyZDPipTJdbz1boZQR9QE96hvwNDrJwt1ak9vHIE6iZSQ==", + "version": "4.3.24", + "resolved": "https://registry.npmjs.org/maxmind/-/maxmind-4.3.24.tgz", + "integrity": "sha512-dexrLcjfS2xDGOvdV8XcfQYmyQVpGidMwEG2ld19lXlsB+i+lXRWPzQi81HfwRXR4hxzFr5gT0oAIFyqAAb/Ww==", "dependencies": { "mmdb-lib": "2.1.1", "tiny-lru": "11.2.11" @@ -989,9 +1127,9 @@ } }, "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/negotiator": { "version": "0.6.3", @@ -1001,10 +1139,24 @@ "node": ">= 0.6" } }, - "node_modules/notepack.io": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/notepack.io/-/notepack.io-3.0.1.tgz", - "integrity": "sha512-TKC/8zH5pXIAMVQio2TvVDTtPRX+DJPHDqjRbxogtFiByHyzKmy96RA0JtCQJ+WouyyL4A10xomQzgbUT+1jCg==" + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } }, "node_modules/object-assign": { "version": "4.1.1", @@ -1015,9 +1167,9 @@ } }, "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "engines": { "node": ">= 0.4" }, @@ -1057,18 +1209,6 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" }, - "node_modules/prom-client": { - "version": "15.1.3", - "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-15.1.3.tgz", - "integrity": "sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==", - "dependencies": { - "@opentelemetry/api": "^1.4.0", - "tdigest": "^0.1.1" - }, - "engines": { - "node": "^16 || ^18 || >=20" - } - }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -1143,6 +1283,25 @@ "@redis/time-series": "1.1.0" } }, + "node_modules/redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", + "engines": { + "node": ">=4" + } + }, + "node_modules/redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", + "dependencies": { + "redis-errors": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1176,9 +1335,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "bin": { "semver": "bin/semver.js" }, @@ -1209,19 +1368,6 @@ "node": ">= 0.8.0" } }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, "node_modules/send/node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -1230,6 +1376,11 @@ "node": ">= 0.8" } }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/serve-static": { "version": "1.16.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", @@ -1244,36 +1395,71 @@ "node": ">= 0.8.0" } }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dependencies": { - "call-bind": "^1.0.7", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -1291,9 +1477,9 @@ } }, "node_modules/socket.io": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.0.tgz", - "integrity": "sha512-8U6BEgGjQOfGz3HHTYaC/L1GaxDCJ/KM0XTkJly0EhZ5U/du9uNEZy4ZgYzEzIqlx2CMm25CrCqr1ck899eLNA==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", @@ -1316,6 +1502,62 @@ "ws": "~8.17.1" } }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-adapter/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-client/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/socket.io-parser": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", @@ -1328,6 +1570,48 @@ "node": ">=10.0.0" } }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -1336,6 +1620,11 @@ "node": "*" } }, + "node_modules/standard-as-callback": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==" + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -1352,14 +1641,6 @@ "safe-buffer": "~5.2.0" } }, - "node_modules/tdigest": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz", - "integrity": "sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==", - "dependencies": { - "bintrees": "1.0.2" - } - }, "node_modules/text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", @@ -1381,6 +1662,11 @@ "node": ">=0.6" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/triple-beam": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", @@ -1389,6 +1675,11 @@ "node": ">= 14.0.0" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -1401,10 +1692,29 @@ "node": ">= 0.6" } }, + "node_modules/ua-is-frozen": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ua-is-frozen/-/ua-is-frozen-0.1.2.tgz", + "integrity": "sha512-RwKDW2p3iyWn4UbaxpP2+VxwqXh0jpvdxsYpZ5j/MLLiQOfbsV5shpgQiw93+KMYQPcteeMQ289MaAFzs3G9pw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + } + ] + }, "node_modules/ua-parser-js": { - "version": "1.0.38", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz", - "integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-2.0.3.tgz", + "integrity": "sha512-LZyXZdNttONW8LjzEH3Z8+6TE7RfrEiJqDKyh0R11p/kxvrV2o9DrT2FGZO+KVNs3k+drcIQ6C3En6wLnzJGpw==", "funding": [ { "type": "opencollective", @@ -1419,22 +1729,24 @@ "url": "https://github.com/sponsors/faisalman" } ], + "dependencies": { + "@types/node-fetch": "^2.6.12", + "detect-europe-js": "^0.1.2", + "is-standalone-pwa": "^0.1.1", + "node-fetch": "^2.7.0", + "ua-is-frozen": "^0.1.2" + }, + "bin": { + "ua-parser-js": "script/cli.js" + }, "engines": { "node": "*" } }, - "node_modules/uid2": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/uid2/-/uid2-1.0.0.tgz", - "integrity": "sha512-+I6aJUv63YAcY9n4mQreLUt0d4lvwkkopDNmpomkAUz0fAkEMV9pRWxN0EjhW1YfRhcuyHg2v3mwddCDW1+LFQ==", - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==" }, "node_modules/unpipe": { "version": "1.0.0", @@ -1470,46 +1782,47 @@ "node": ">= 0.8" } }, - "node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "engines": [ - "node >=0.6.0" - ], + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" } }, "node_modules/winston": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.14.2.tgz", - "integrity": "sha512-CO8cdpBB2yqzEf8v895L+GNKYJiEq8eKlHU38af3snQBQ+sdAIUepjMSguOIJC7ICbzm0ZI+Af2If4vIJrtmOg==", + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz", + "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.2", "async": "^3.2.3", "is-stream": "^2.0.0", - "logform": "^2.6.0", + "logform": "^2.7.0", "one-time": "^1.0.0", "readable-stream": "^3.4.0", "safe-stable-stringify": "^2.3.1", "stack-trace": "0.0.x", "triple-beam": "^1.3.0", - "winston-transport": "^4.7.0" + "winston-transport": "^4.9.0" }, "engines": { "node": ">= 12.0.0" } }, "node_modules/winston-transport": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.7.1.tgz", - "integrity": "sha512-wQCXXVgfv/wUPOfb2x0ruxzwkcZfxcktz6JIMUaPLmcNhO4bZTwA/WtDWK74xV3F2dKu8YadrFv0qhwYjVEwhA==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", "dependencies": { - "logform": "^2.6.1", + "logform": "^2.7.0", "readable-stream": "^3.6.2", "triple-beam": "^1.3.0" }, @@ -1537,6 +1850,14 @@ } } }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/ee/assist/package.json b/ee/assist/package.json index 04b257f80..2a6efdfd7 100644 --- a/ee/assist/package.json +++ b/ee/assist/package.json @@ -1,33 +1,26 @@ { "name": "assist-server", - "version": "v1.22.0-ee", - "description": "assist server to get live sessions & sourcemaps reader to get stack trace", - "main": "peerjs-server.js", + "version": "1.0.0", + "description": "", + "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "start": "node server.js" + "test": "echo \"Error: no test specified\" && exit 1" }, - "repository": { - "type": "git", - "url": "git+https://github.com/openreplay/openreplay.git" - }, - "author": "KRAIEM Taha Yassine ", - "license": "Elastic License 2.0 (ELv2)", - "bugs": { - "url": "https://github.com/openreplay/openreplay/issues" - }, - "homepage": "https://github.com/openreplay/openreplay#readme", + "keywords": [], + "author": "", + "license": "ISC", "dependencies": { - "@fastify/deepmerge": "^2.0.1", - "@maxmind/geoip2-node": "^4.2.0", - "@socket.io/redis-adapter": "^8.2.1", - "express": "^4.21.1", + "@fastify/deepmerge": "^3.0.0", + "@maxmind/geoip2-node": "^6.0.0", + "async-mutex": "^0.5.0", + "express": "^4.21.2", + "ioredis": "^5.6.1", "jsonwebtoken": "^9.0.2", - "prom-client": "^15.0.0", - "redis": "^4.6.10", - "socket.io": "^4.8.0", - "ua-parser-js": "^1.0.37", + "redis": "^4.7.0", + "socket.io": "^4.8.1", + "socket.io-client": "^4.8.1", + "ua-parser-js": "^2.0.3", "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.51.0", - "winston": "^3.13.0" + "winston": "^3.17.0" } } diff --git a/ee/assist/prepare-dev.sh b/ee/assist/prepare-dev.sh deleted file mode 100755 index 8da98eac3..000000000 --- a/ee/assist/prepare-dev.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -rsync -avr --exclude=".*" --exclude="node_modules" --ignore-existing ../../assist/* ./ \ No newline at end of file diff --git a/ee/assist/run-dev.sh b/ee/assist/run-dev.sh deleted file mode 100755 index 00e8d5a4b..000000000 --- a/ee/assist/run-dev.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -set -a -source .env -set +a - -npm start \ No newline at end of file diff --git a/ee/assist/server.js b/ee/assist/server.js index 2a6ec5bee..a5ec880e3 100644 --- a/ee/assist/server.js +++ b/ee/assist/server.js @@ -1,119 +1,64 @@ -const dumps = require('./utils/HeapSnapshot'); -const {request_logger} = require('./utils/helper'); -const express = require('express'); -const health = require("./utils/health"); -const assert = require('assert').strict; -const register = require('./utils/metrics').register; -let socket; -if (process.env.redis === "true") { - socket = require("./servers/websocket-cluster"); -} else { - socket = require("./servers/websocket"); -} -const {logger} = require('./utils/logger'); +const { App } = require('uWebSockets.js'); +const { Server } = require('socket.io'); +const { logger } = require("./app/logger"); +const { authorizer } = require("./app/assist"); +const { onConnect, setSocketIOServer } = require("./app/socket"); +const { startCacheRefresher } = require("./app/cache"); -health.healthApp.get('/metrics', async (req, res) => { - try { - res.set('Content-Type', register.contentType); - res.end(await register.metrics()); - } catch (ex) { - res.status(500).end(ex); +const app = App(); +const pingInterval = parseInt(process.env.PING_INTERVAL) || 25000; + +const getCompressionConfig = function () { + // WS: The theoretical overhead per socket is 19KB (11KB for compressor and 8KB for decompressor) + let perMessageDeflate = false; + if (process.env.COMPRESSION === "true") { + logger.info(`WS compression: enabled`); + perMessageDeflate = { + zlibDeflateOptions: { + windowBits: 10, + memLevel: 1 + }, + zlibInflateOptions: { + windowBits: 10 + } + } + } else { + logger.info(`WS compression: disabled`); } + return { + perMessageDeflate: perMessageDeflate, + clientNoContextTakeover: true + }; +} + +const io = new Server({ + maxHttpBufferSize: (parseFloat(process.env.maxHttpBufferSize) || 5) * 1e6, + pingInterval: pingInterval, // Will use it for cache invalidation + cors: { + origin: "*", // Allow connections from any origin (for development) + methods: ["GET", "POST"], + credentials: true + }, + path: '/socket', + ...getCompressionConfig() }); +io.use(async (socket, next) => await authorizer.check(socket, next)); +io.on('connection', (socket) => onConnect(socket)); +io.attachApp(app); +setSocketIOServer(io); + const HOST = process.env.LISTEN_HOST || '0.0.0.0'; -const PORT = process.env.LISTEN_PORT || 9001; -assert.ok(process.env.ASSIST_KEY, 'The "ASSIST_KEY" environment variable is required'); -const P_KEY = process.env.ASSIST_KEY; -const PREFIX = process.env.PREFIX || process.env.prefix || `/assist`; - -const heapdump = process.env.heapdump === "1"; - -if (process.env.uws !== "true") { - let wsapp = express(); - wsapp.use(express.json()); - wsapp.use(express.urlencoded({extended: true})); - wsapp.use(request_logger("[wsapp]")); - wsapp.get(['/', PREFIX, `${PREFIX}/`, `${PREFIX}/${P_KEY}`, `${PREFIX}/${P_KEY}/`], (req, res) => { - res.statusCode = 200; - res.end("ok!"); - } - ); - heapdump && wsapp.use(`${PREFIX}/${P_KEY}/heapdump`, dumps.router); - wsapp.use(`${PREFIX}/${P_KEY}`, socket.wsRouter); - - wsapp.enable('trust proxy'); - const wsserver = wsapp.listen(PORT, HOST, () => { - logger.info(`WS App listening on http://${HOST}:${PORT}`); - health.healthApp.listen(health.PORT, HOST, health.listen_cb); - }); - - socket.start(wsserver); - module.exports = {wsserver}; -} else { - logger.info("Using uWebSocket"); - const {App} = require("uWebSockets.js"); - - - const uapp = new App(); - - const healthFn = (res, req) => { - res.writeStatus('200 OK').end('ok!'); +const PORT = parseInt(process.env.PORT) || 9001; +app.listen(PORT, (token) => { + if (token) { + console.log(`Server running at http://${HOST}:${PORT}`); + } else { + console.log(`Failed to listen on port ${PORT}`); } - uapp.get('/', healthFn); - uapp.get(PREFIX, healthFn); - uapp.get(`${PREFIX}/`, healthFn); - uapp.get(`${PREFIX}/${P_KEY}`, healthFn); - uapp.get(`${PREFIX}/${P_KEY}/`, healthFn); +}); +startCacheRefresher(io); - - /* Either onAborted or simply finished request */ - const onAbortedOrFinishedResponse = function (res) { - - if (res.id === -1) { - logger.debug("ERROR! onAbortedOrFinishedResponse called twice for the same res!"); - } else { - logger.debug('Stream was closed'); - } - - /* Mark this response already accounted for */ - res.id = -1; - } - - const uWrapper = function (fn) { - return (res, req) => { - res.id = 1; - res.aborted = false; - req.startTs = performance.now(); // track request's start timestamp - req.method = req.getMethod(); - res.onAborted(() => { - res.aborted = true; - onAbortedOrFinishedResponse(res); - }); - return fn(req, res); - } - } - - uapp.get(`${PREFIX}/${P_KEY}/sockets-list/:projectKey/autocomplete`, uWrapper(socket.handlers.autocomplete)); - uapp.get(`${PREFIX}/${P_KEY}/sockets-list/:projectKey/:sessionId`, uWrapper(socket.handlers.socketsListByProject)); - uapp.get(`${PREFIX}/${P_KEY}/sockets-live/:projectKey/autocomplete`, uWrapper(socket.handlers.autocomplete)); - uapp.get(`${PREFIX}/${P_KEY}/sockets-live/:projectKey`, uWrapper(socket.handlers.socketsLiveByProject)); - uapp.post(`${PREFIX}/${P_KEY}/sockets-live/:projectKey`, uWrapper(socket.handlers.socketsLiveByProject)); - uapp.get(`${PREFIX}/${P_KEY}/sockets-live/:projectKey/:sessionId`, uWrapper(socket.handlers.socketsLiveBySession)); - - socket.start(uapp); - - uapp.listen(HOST, PORT, (token) => { - if (!token) { - logger.error("port already in use"); - } - logger.info(`WS App listening on http://${HOST}:${PORT}`); - health.healthApp.listen(health.PORT, HOST, health.listen_cb); - }); - - - process.on('uncaughtException', err => { - logger.error(`Uncaught Exception: ${err}`); - }); - module.exports = {uapp}; -} \ No newline at end of file +process.on('uncaughtException', err => { + logger.error(`Uncaught Exception: ${err}`); +}); \ No newline at end of file diff --git a/ee/assist/servers/websocket-cluster.js b/ee/assist/servers/websocket-cluster.js deleted file mode 100644 index 6967bd518..000000000 --- a/ee/assist/servers/websocket-cluster.js +++ /dev/null @@ -1,64 +0,0 @@ -const express = require('express'); -const { - socketConnexionTimeout, - authorizer -} = require('../utils/assistHelper'); -const { - createSocketIOServer -} = require('../utils/wsServer'); -const { - onConnect -} = require('../utils/socketHandlers'); -const { - socketsListByProject, - socketsLiveByProject, - socketsLiveBySession, - autocomplete -} = require('../utils/httpHandlers'); -const {logger} = require('../utils/logger'); - -const {createAdapter} = require("@socket.io/redis-adapter"); -const {createClient} = require("redis"); -const REDIS_URL = (process.env.REDIS_URL || "localhost:6379").replace(/((^\w+:|^)\/\/|^)/, 'redis://'); -const pubClient = createClient({url: REDIS_URL}); -const subClient = pubClient.duplicate(); -logger.info(`Using Redis: ${REDIS_URL}`); - -const wsRouter = express.Router(); -wsRouter.get(`/sockets-list/:projectKey/autocomplete`, autocomplete); // autocomplete -wsRouter.get(`/sockets-list/:projectKey/:sessionId`, socketsListByProject); // is_live -wsRouter.get(`/sockets-live/:projectKey/autocomplete`, autocomplete); // not using -wsRouter.get(`/sockets-live/:projectKey`, socketsLiveByProject); -wsRouter.post(`/sockets-live/:projectKey`, socketsLiveByProject); // assist search -wsRouter.get(`/sockets-live/:projectKey/:sessionId`, socketsLiveBySession); // session_exists, get_live_session_by_id - -let io; -module.exports = { - wsRouter, - start: (server, prefix) => { - io = createSocketIOServer(server, prefix); - io.use(async (socket, next) => await authorizer.check(socket, next)); - io.on('connection', (socket) => onConnect(socket)); - - logger.info("WS server started"); - - socketConnexionTimeout(io); - - Promise.all([pubClient.connect(), subClient.connect()]) - .then(() => { - io.adapter(createAdapter(pubClient, subClient, - {requestsTimeout: process.env.REDIS_REQUESTS_TIMEOUT || 5000})); - logger.info("> redis connected."); - }) - .catch((err) => { - logger.error(`redis connection error: ${err}`); - process.exit(2); - }); - }, - handlers: { - socketsListByProject, - socketsLiveByProject, - socketsLiveBySession, - autocomplete - } -}; \ No newline at end of file diff --git a/ee/assist/servers/websocket.js b/ee/assist/servers/websocket.js deleted file mode 100644 index efbeaae34..000000000 --- a/ee/assist/servers/websocket.js +++ /dev/null @@ -1,45 +0,0 @@ -const express = require('express'); -const { - socketConnexionTimeout, - authorizer -} = require('../utils/assistHelper'); -const { - createSocketIOServer -} = require('../utils/wsServer'); -const { - onConnect -} = require('../utils/socketHandlers'); -const { - socketsListByProject, - socketsLiveByProject, - socketsLiveBySession, - autocomplete -} = require('../utils/httpHandlers'); -const {logger} = require('../utils/logger'); - -const wsRouter = express.Router(); -wsRouter.get(`/sockets-list/:projectKey/autocomplete`, autocomplete); // autocomplete -wsRouter.get(`/sockets-list/:projectKey/:sessionId`, socketsListByProject); // is_live -wsRouter.get(`/sockets-live/:projectKey/autocomplete`, autocomplete); // not using -wsRouter.get(`/sockets-live/:projectKey`, socketsLiveByProject); -wsRouter.post(`/sockets-live/:projectKey`, socketsLiveByProject); // assist search -wsRouter.get(`/sockets-live/:projectKey/:sessionId`, socketsLiveBySession); // session_exists, get_live_session_by_id - -let io; -module.exports = { - wsRouter, - start: (server, prefix) => { - io = createSocketIOServer(server, prefix); - io.use(async (socket, next) => await authorizer.check(socket, next)); - io.on('connection', (socket) => onConnect(socket)); - - logger.info("WS server started"); - socketConnexionTimeout(io); - }, - handlers: { - socketsListByProject, - socketsLiveByProject, - socketsLiveBySession, - autocomplete - } -}; diff --git a/ee/assist/utils/extractors.js b/ee/assist/utils/extractors.js deleted file mode 100644 index 8a19cf2e6..000000000 --- a/ee/assist/utils/extractors.js +++ /dev/null @@ -1,13 +0,0 @@ -const { - extractProjectKeyFromRequest, - extractSessionIdFromRequest, - extractPayloadFromRequest, - getAvailableRooms -} = require('../utils/helper-ee'); - -module.exports = { - extractProjectKeyFromRequest, - extractSessionIdFromRequest, - extractPayloadFromRequest, - getAvailableRooms -} \ No newline at end of file diff --git a/ee/assist/utils/helper-ee.js b/ee/assist/utils/helper-ee.js deleted file mode 100644 index 4abe98b2a..000000000 --- a/ee/assist/utils/helper-ee.js +++ /dev/null @@ -1,126 +0,0 @@ -const uWS = require("uWebSockets.js"); -const helper = require('./helper'); -const {logger} = require('./logger'); - -const getBodyFromUWSResponse = async function (res) { - return new Promise(((resolve, reject) => { - let buffer; - res.onData((ab, isLast) => { - let chunk = Buffer.from(ab); - if (buffer) { - buffer = Buffer.concat([buffer, chunk]); - } else { - buffer = Buffer.concat([chunk]); - } - if (isLast) { - let json; - try { - json = JSON.parse(buffer); - } catch (e) { - console.error(e); - json = {}; - } - resolve(json); - } - }); - })); -} -const extractProjectKeyFromRequest = function (req) { - if (process.env.uws === "true") { - if (req.getParameter(0)) { - logger.debug(`[WS]where projectKey=${req.getParameter(0)}`); - return req.getParameter(0); - } - } else { - return helper.extractProjectKeyFromRequest(req); - } - return undefined; -} -const extractSessionIdFromRequest = function (req) { - if (process.env.uws === "true") { - if (req.getParameter(1)) { - logger.debug(`[WS]where projectKey=${req.getParameter(1)}`); - return req.getParameter(1); - } - } else { - return helper.extractSessionIdFromRequest(req); - } - return undefined; -} -const extractPayloadFromRequest = async function (req, res) { - let filters = { - "query": {}, - "filter": {} - }; - if (process.env.uws === "true") { - if (req.getQuery("q")) { - logger.debug(`[WS]where q=${req.getQuery("q")}`); - filters.query.value = req.getQuery("q"); - } - if (req.getQuery("key")) { - logger.debug(`[WS]where key=${req.getQuery("key")}`); - filters.query.key = req.getQuery("key"); - } - if (req.getQuery("userId")) { - logger.debug(`[WS]where userId=${req.getQuery("userId")}`); - filters.filter.userID = [req.getQuery("userId")]; - } - if (!filters.query.value) { - let body = {}; - if (req.getMethod() !== 'get') { - body = await getBodyFromUWSResponse(res); - } - filters = { - ...filters, - "sort": { - "key": body.sort && body.sort.key ? body.sort.key : undefined, - "order": body.sort && body.sort.order === "DESC" - }, - "pagination": { - "limit": body.pagination && body.pagination.limit ? body.pagination.limit : undefined, - "page": body.pagination && body.pagination.page ? body.pagination.page : undefined - } - } - filters.filter = {...filters.filter, ...(body.filter || {})}; - } - } else { - return helper.extractPayloadFromRequest(req); - } - filters.filter = helper.objectToObjectOfArrays(filters.filter); - filters.filter = helper.transformFilters(filters.filter); - logger.debug("payload/filters:" + JSON.stringify(filters)) - return Object.keys(filters).length > 0 ? filters : undefined; -} -const getAvailableRooms = async function (io) { - if (process.env.redis === "true") { - return io.of('/').adapter.allRooms(); - } else { - return helper.getAvailableRooms(io); - } -} -const getCompressionConfig = function () { - if (process.env.uws !== "true") { - return helper.getCompressionConfig(); - } else { - // uWS: The theoretical overhead per socket is 32KB (8KB for compressor and for 24KB decompressor) - if (process.env.COMPRESSION === "true") { - console.log(`uWS compression: enabled`); - return { - compression: uWS.DEDICATED_COMPRESSOR_8KB, - decompression: uWS.DEDICATED_DECOMPRESSOR_1KB - }; - } else { - console.log(`uWS compression: disabled`); - return {}; - } - } - -} - -module.exports = { - extractProjectKeyFromRequest, - extractSessionIdFromRequest, - extractPayloadFromRequest, - getCompressionConfig, - getAvailableRooms -}; \ No newline at end of file diff --git a/ee/assist/utils/stats.js b/ee/assist/utils/stats.js deleted file mode 100644 index 39dae5e84..000000000 --- a/ee/assist/utils/stats.js +++ /dev/null @@ -1,234 +0,0 @@ -const statsHost = process.env.STATS_HOST || 'http://assist-stats-openreplay.app.svc.cluster.local:8000/events'; -const authToken = process.env.STATS_AUTH_TOKEN || ''; -const {logger} = require('./logger'); - -class InMemoryCache { - constructor() { - this.cache = new Map(); - } - - set(key, value) { - this.cache.set(key, value); - } - - get(key) { - return this.cache.get(key); - } - - delete(key) { - this.cache.delete(key); - } - - clear() { - this.cache.clear(); - } -} - -const cache = new InMemoryCache(); - -async function postData(payload) { - let headers = { - 'Content-Type': 'application/json' - }; - if (authToken && authToken.trim() !== '') { - headers['Authorization'] = 'Bearer ' + authToken; - } - const options = { - method: 'POST', - body: JSON.stringify(payload), - headers: headers, - } - - try { - const response = await fetch(statsHost, options) - const jsonResponse = await response.json(); - logger.debug('JSON response', JSON.stringify(jsonResponse, null, 4)) - } catch(err) { - logger.debug('ERROR', err); - } -} - -function startAssist(socket, agentID) { - const tsNow = +new Date(); - const eventID = `${socket.handshake.query.sessId}_${agentID}_assist_${tsNow}`; - void postData({ - "project_id": socket.handshake.query.projectId, - "session_id": socket.handshake.query.sessId, - "agent_id": agentID, - "event_id": eventID, - "event_type": "assist", - "event_state": "start", - "timestamp": tsNow, - }); - // Save uniq eventID to cache - cache.set(`${socket.handshake.query.sessId}_${agentID}_assist`, eventID); - // Debug log - logger.debug(`assist_started, agentID: ${agentID}, sessID: ${socket.handshake.query.sessId}, projID: ${socket.handshake.query.projectId}, time: ${tsNow}`); -} - -function endAssist(socket, agentID) { - const eventID = cache.get(`${socket.handshake.query.sessId}_${agentID}_assist`); - if (eventID === undefined) { - logger.debug(`have to skip assist_ended, no eventID in the cache, agentID: ${socket.handshake.query.agentID}, sessID: ${socket.handshake.query.sessId}, projID: ${socket.handshake.query.projectId}`); - return - } - void postData({ - "project_id": socket.handshake.query.projectId, - "session_id": socket.handshake.query.sessId, - "agent_id": agentID, - "event_id": eventID, - "event_type": "assist", - "event_state": "end", - "timestamp": +new Date(), - }) - // Remove eventID from cache - cache.delete(`${socket.handshake.query.sessId}_${agentID}_assist`); - // Debug logs - logger.debug(`assist_ended, agentID: ${socket.handshake.query.agentID}, sessID: ${socket.handshake.query.sessId}, projID: ${socket.handshake.query.projectId}`); -} - -function startCall(socket, agentID) { - const tsNow = +new Date(); - const eventID = `${socket.handshake.query.sessId}_${agentID}_call_${tsNow}`; - void postData({ - "project_id": socket.handshake.query.projectId, - "session_id": socket.handshake.query.sessId, - "agent_id": agentID, - "event_id": eventID, - "event_type": "call", - "event_state": "start", - "timestamp": tsNow, - }); - // Save uniq eventID to cache - cache.set(`${socket.handshake.query.sessId}_call`, eventID); - // Debug logs - logger.debug(`s_call_started, agentID: ${agentID}, sessID: ${socket.handshake.query.sessId}, projID: ${socket.handshake.query.projectId}, time: ${tsNow}`); -} - -function endCall(socket, agentID) { - const tsNow = +new Date(); - const eventID = cache.get(`${socket.handshake.query.sessId}_call`); - if (eventID === undefined) { - logger.debug(`have to skip s_call_ended, no eventID in the cache, agentID: ${agentID}, sessID: ${socket.handshake.query.sessId}, projID: ${socket.handshake.query.projectId}, time: ${tsNow}`); - return - } - void postData({ - "project_id": socket.handshake.query.projectId, - "session_id": socket.handshake.query.sessId, - "agent_id": agentID, - "event_id": eventID, - "event_type": "call", - "event_state": "end", - "timestamp": tsNow, - }); - cache.delete(`${socket.handshake.query.sessId}_call`) - // Debug logs - logger.debug(`s_call_ended, agentID: ${agentID}, sessID: ${socket.handshake.query.sessId}, projID: ${socket.handshake.query.projectId}, time: ${tsNow}`); -} - -function startControl(socket, agentID) { - const tsNow = +new Date(); - const eventID = `${socket.handshake.query.sessId}_${agentID}_control_${tsNow}`; - void postData({ - "project_id": socket.handshake.query.projectId, - "session_id": socket.handshake.query.sessId, - "agent_id": agentID, - "event_id": eventID, - "event_type": "control", - "event_state": "start", - "timestamp": tsNow, - }); - cache.set(`${socket.handshake.query.sessId}_control`, eventID) - // Debug logs - logger.debug(`s_control_started, agentID: ${agentID}, sessID: ${socket.handshake.query.sessId}, projID: ${socket.handshake.query.projectId}, time: ${+new Date()}`); -} - -function endControl(socket, agentID) { - const tsNow = +new Date(); - const eventID = cache.get(`${socket.handshake.query.sessId}_control`); - if (eventID === undefined) { - logger.debug(`have to skip s_control_ended, no eventID in the cache, agentID: ${agentID}, sessID: ${socket.handshake.query.sessId}, projID: ${socket.handshake.query.projectId}, time: ${tsNow}`); - return - } - void postData({ - "project_id": socket.handshake.query.projectId, - "session_id": socket.handshake.query.sessId, - "agent_id": agentID, - "event_id": eventID, - "event_type": "control", - "event_state": "end", - "timestamp": tsNow, - }); - cache.delete(`${socket.handshake.query.sessId}_control`) - // Debug logs - logger.debug(`s_control_ended, agentID: ${agentID}, sessID: ${socket.handshake.query.sessId}, projID: ${socket.handshake.query.projectId}, time: ${+new Date()}`); -} - -function startRecord(socket, agentID) { - const tsNow = +new Date(); - const eventID = `${socket.handshake.query.sessId}_${agentID}_record_${tsNow}`; - void postData({ - "project_id": socket.handshake.query.projectId, - "session_id": socket.handshake.query.sessId, - "agent_id": agentID, - "event_id": eventID, - "event_type": "record", - "event_state": "start", - "timestamp": tsNow, - }); - cache.set(`${socket.handshake.query.sessId}_record`, eventID) - // Debug logs - logger.debug(`s_recording_started, agentID: ${agentID}, sessID: ${socket.handshake.query.sessId}, projID: ${socket.handshake.query.projectId}, time: ${+new Date()}`); -} - -function endRecord(socket, agentID) { - const tsNow = +new Date(); - const eventID = cache.get(`${socket.sessId}_record`); - void postData({ - "project_id": socket.handshake.query.projectId, - "session_id": socket.handshake.query.sessId, - "agent_id": agentID, - "event_id": eventID, - "event_type": "record", - "event_state": "end", - "timestamp": tsNow, - }); - cache.delete(`${socket.handshake.query.sessId}_record`) - // Debug logs - logger.debug(`s_recording_ended, agentID: ${agentID}, sessID: ${socket.handshake.query.sessId}, projID: ${socket.handshake.query.projectId}, time: ${+new Date()}`); -} - -function handleEvent(eventName, socket, agentID) { - switch (eventName) { - case "s_call_started": { - startCall(socket, agentID); - break; - } - case "s_call_ended": { - endCall(socket, agentID); - break; - } - case "s_control_started": { - startControl(socket, agentID) - break; - } - case "s_control_ended": { - endControl(socket, agentID) - break; - } - case "s_recording_started": { - startRecord(socket, agentID); - break; - } - case "s_recording_ended": { - endRecord(socket, agentID); - break; - } - } -} - -module.exports = { - startAssist, - endAssist, - handleEvent, -} \ No newline at end of file diff --git a/ee/assist/utils/wsServer.js b/ee/assist/utils/wsServer.js deleted file mode 100644 index e774eb8bf..000000000 --- a/ee/assist/utils/wsServer.js +++ /dev/null @@ -1,107 +0,0 @@ -const _io = require("socket.io"); -const {getCompressionConfig} = require("./helper"); -const {logger} = require('./logger'); - -let io; -const getServer = function () {return io;} - -const useRedis = process.env.redis === "true"; -let inMemorySocketsCache = []; -let lastCacheUpdateTime = 0; -const CACHE_REFRESH_INTERVAL = parseInt(process.env.cacheRefreshInterval) || 5000; - -const doFetchAllSockets = async function () { - if (useRedis) { - const now = Date.now(); - logger.info(`Using in-memory cache (age: ${now - lastCacheUpdateTime}ms)`); - return inMemorySocketsCache; - } else { - try { - return await io.fetchSockets(); - } catch (error) { - logger.error('Error fetching sockets:', error); - return []; - } - } -} - -// Background refresher that runs independently of requests -let cacheRefresher = null; -function startCacheRefresher() { - if (cacheRefresher) clearInterval(cacheRefresher); - - cacheRefresher = setInterval(async () => { - const now = Date.now(); - // Only refresh if cache is stale - if (now - lastCacheUpdateTime >= CACHE_REFRESH_INTERVAL) { - logger.debug('Background refresh triggered'); - try { - const startTime = performance.now(); - const result = await io.fetchSockets(); - inMemorySocketsCache = result; - lastCacheUpdateTime = now; - const duration = performance.now() - startTime; - logger.info(`Background refresh complete: ${duration}ms, ${result.length} sockets`); - } catch (error) { - logger.error(`Background refresh error: ${error}`); - } - } - }, CACHE_REFRESH_INTERVAL / 2); -} - -const processSocketsList = function (sockets) { - let res = [] - for (let socket of sockets) { - let {handshake} = socket; - res.push({handshake}); - } - return res -} - -const fetchSockets = async function (roomID) { - if (!io) { - return []; - } - if (!roomID) { - return await doFetchAllSockets(); - } - return await io.in(roomID).fetchSockets(); -} - -const createSocketIOServer = function (server, prefix) { - if (io) { - return io; - } - if (process.env.uws !== "true") { - io = _io(server, { - maxHttpBufferSize: (parseFloat(process.env.maxHttpBufferSize) || 5) * 1e6, - cors: { - origin: "*", - methods: ["GET", "POST", "PUT"], - credentials: true - }, - path: (prefix ? prefix : '') + '/socket', - ...getCompressionConfig() - }); - } else { - io = new _io.Server({ - maxHttpBufferSize: (parseFloat(process.env.maxHttpBufferSize) || 5) * 1e6, - cors: { - origin: "*", - methods: ["GET", "POST", "PUT"], - credentials: true - }, - path: (prefix ? prefix : '') + '/socket', - ...getCompressionConfig() - }); - io.attachApp(server); - } - startCacheRefresher(); - return io; -} - -module.exports = { - createSocketIOServer, - getServer, - fetchSockets, -} \ No newline at end of file diff --git a/ee/backend/cmd/assist-api/main.go b/ee/backend/cmd/assist-api/main.go new file mode 100644 index 000000000..a73b249d7 --- /dev/null +++ b/ee/backend/cmd/assist-api/main.go @@ -0,0 +1,57 @@ +package main + +import ( + "context" + + assistConfig "openreplay/backend/internal/config/assist" + "openreplay/backend/pkg/assist" + "openreplay/backend/pkg/db/postgres/pool" + "openreplay/backend/pkg/db/redis" + "openreplay/backend/pkg/logger" + "openreplay/backend/pkg/metrics" + databaseMetrics "openreplay/backend/pkg/metrics/database" + "openreplay/backend/pkg/metrics/web" + "openreplay/backend/pkg/server" + "openreplay/backend/pkg/server/api" +) + +func main() { + ctx := context.Background() + log := logger.New() + cfg := assistConfig.New(log) + // Observability + webMetrics := web.New("assist") + dbMetric := databaseMetrics.New("assist") + metrics.New(log, append(webMetrics.List(), dbMetric.List()...)) + + if cfg.AssistKey == "" { + log.Fatal(ctx, "assist key is not set") + } + + pgConn, err := pool.New(dbMetric, cfg.Postgres.String()) + if err != nil { + log.Fatal(ctx, "can't init postgres connection: %s", err) + } + defer pgConn.Close() + + redisClient, err := redis.New(&cfg.Redis) + if err != nil { + log.Fatal(ctx, "can't init redis connection: %s", err) + } + defer redisClient.Close() + + prefix := api.NoPrefix + builder, err := assist.NewServiceBuilder(log, cfg, webMetrics, dbMetric, pgConn, redisClient) + if err != nil { + log.Fatal(ctx, "can't init services: %s", err) + } + + router, err := api.NewRouter(&cfg.HTTP, log) + if err != nil { + log.Fatal(ctx, "failed while creating router: %s", err) + } + router.AddHandlers(prefix, builder.AssistAPI) + router.AddMiddlewares(builder.RateLimiter.Middleware) + + server.Run(ctx, log, &cfg.HTTP, router) +} diff --git a/ee/backend/internal/config/assist/config.go b/ee/backend/internal/config/assist/config.go new file mode 100644 index 000000000..0cd7f4ee4 --- /dev/null +++ b/ee/backend/internal/config/assist/config.go @@ -0,0 +1,30 @@ +package assist + +import ( + "time" + + "openreplay/backend/internal/config/common" + "openreplay/backend/internal/config/configurator" + "openreplay/backend/internal/config/redis" + "openreplay/backend/pkg/env" + "openreplay/backend/pkg/logger" +) + +type Config struct { + common.Config + common.Postgres + redis.Redis + common.HTTP + ProjectExpiration time.Duration `env:"PROJECT_EXPIRATION,default=10m"` + AssistKey string `env:"ASSIST_KEY"` + CacheTTL time.Duration `env:"REDIS_CACHE_TTL,default=5s"` + BatchSize int `env:"REDIS_BATCH_SIZE,default=1000"` + ScanSize int64 `env:"REDIS_SCAN_SIZE,default=1000"` + WorkerID uint16 +} + +func New(log logger.Logger) *Config { + cfg := &Config{WorkerID: env.WorkerID()} + configurator.Process(log, cfg) + return cfg +} diff --git a/ee/backend/pkg/assist/api/handlers.go b/ee/backend/pkg/assist/api/handlers.go new file mode 100644 index 000000000..c26a2f541 --- /dev/null +++ b/ee/backend/pkg/assist/api/handlers.go @@ -0,0 +1,207 @@ +package api + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "time" + + "github.com/gorilla/mux" + + assistAPI "openreplay/backend/internal/config/assist" + "openreplay/backend/pkg/assist/service" + "openreplay/backend/pkg/logger" + "openreplay/backend/pkg/server/api" + "openreplay/backend/pkg/sessionmanager" +) + +type handlersImpl struct { + cfg *assistAPI.Config + log logger.Logger + responser *api.Responser + jsonSizeLimit int64 + assist service.Assist +} + +func NewHandlers(log logger.Logger, cfg *assistAPI.Config, responser *api.Responser, assist service.Assist) (api.Handlers, error) { + return &handlersImpl{ + cfg: cfg, + log: log, + responser: responser, + jsonSizeLimit: cfg.JsonSizeLimit, + assist: assist, + }, nil +} + +func (e *handlersImpl) GetAll() []*api.Description { + keyPrefix := "/assist" + if e.cfg.AssistKey != "" { + keyPrefix = fmt.Sprintf("/assist/%s", e.cfg.AssistKey) + } + return []*api.Description{ + {keyPrefix + "/sockets-list/{projectKey}/autocomplete", e.autocomplete, "GET"}, // event search with live=true + {keyPrefix + "/sockets-list/{projectKey}/{sessionId}", e.socketsListByProject, "GET"}, // is_live for getReplay call + {keyPrefix + "/sockets-live/{projectKey}", e.socketsLiveByProject, "POST"}, // handler /{projectId}/assist/sessions for co-browser + {keyPrefix + "/sockets-live/{projectKey}/{sessionId}", e.socketsLiveBySession, "GET"}, // for get_live_session (with data) and for session_exists + {"/v1/ping", e.ping, "GET"}, + } +} + +func (e *handlersImpl) ping(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) +} + +func getProjectKey(r *http.Request) (string, error) { + vars := mux.Vars(r) + key := vars["projectKey"] + if key == "" { + return "", fmt.Errorf("empty project key") + } + return key, nil +} + +func getSessionID(r *http.Request) (string, error) { + vars := mux.Vars(r) + key := vars["sessionId"] + if key == "" { + return "", fmt.Errorf("empty session ID") + } + return key, nil +} + +func getQuery(r *http.Request) (*service.Query, error) { + params := r.URL.Query() + q := &service.Query{ + Key: params.Get("key"), + Value: params.Get("q"), + } + if q.Key == "" || q.Value == "" { + return nil, fmt.Errorf("empty key or value") + } + return q, nil +} + +func (e *handlersImpl) autocomplete(w http.ResponseWriter, r *http.Request) { + startTime := time.Now() + bodySize := 0 + + projectKey, err := getProjectKey(r) + if err != nil { + e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize) + return + } + query, err := getQuery(r) + if err != nil { + e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize) + return + } + + resp, err := e.assist.Autocomplete(projectKey, query) + if err != nil { + e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusInternalServerError, err, startTime, r.URL.Path, bodySize) + return + } + response := map[string]interface{}{ + "data": resp, + } + e.responser.ResponseWithJSON(e.log, r.Context(), w, response, startTime, r.URL.Path, bodySize) +} + +func (e *handlersImpl) socketsListByProject(w http.ResponseWriter, r *http.Request) { + startTime := time.Now() + bodySize := 0 + + projectKey, err := getProjectKey(r) + if err != nil { + e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize) + return + } + sessionID, err := getSessionID(r) + if err != nil { + e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize) + return + } + + resp, err := e.assist.GetByID(projectKey, sessionID) + if err != nil { + if errors.Is(err, sessionmanager.ErrSessionNotFound) { + e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusNotFound, err, startTime, r.URL.Path, bodySize) + } else if errors.Is(err, sessionmanager.ErrSessionNotBelongToProject) { + e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusForbidden, err, startTime, r.URL.Path, bodySize) + } else { + e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusInternalServerError, err, startTime, r.URL.Path, bodySize) + } + return + } + response := map[string]interface{}{ + "data": resp, + } + e.responser.ResponseWithJSON(e.log, r.Context(), w, response, startTime, r.URL.Path, bodySize) +} + +func (e *handlersImpl) socketsLiveByProject(w http.ResponseWriter, r *http.Request) { + startTime := time.Now() + bodySize := 0 + + projectKey, err := getProjectKey(r) + if err != nil { + e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize) + return + } + bodyBytes, err := api.ReadBody(e.log, w, r, e.jsonSizeLimit) + if err != nil { + e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusRequestEntityTooLarge, err, startTime, r.URL.Path, bodySize) + return + } + e.log.Debug(context.Background(), "bodyBytes: %s", bodyBytes) + bodySize = len(bodyBytes) + req := &service.Request{} + if err := json.Unmarshal(bodyBytes, req); err != nil { + e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize) + return + } + + resp, err := e.assist.GetAll(projectKey, req) + if err != nil { + e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusInternalServerError, err, startTime, r.URL.Path, bodySize) + return + } + response := map[string]interface{}{ + "data": resp, + } + e.responser.ResponseWithJSON(e.log, r.Context(), w, response, startTime, r.URL.Path, bodySize) +} + +func (e *handlersImpl) socketsLiveBySession(w http.ResponseWriter, r *http.Request) { + startTime := time.Now() + bodySize := 0 + + projectKey, err := getProjectKey(r) + if err != nil { + e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize) + return + } + sessionID, err := getSessionID(r) + if err != nil { + e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize) + return + } + + resp, err := e.assist.GetByID(projectKey, sessionID) + if err != nil { + if errors.Is(err, sessionmanager.ErrSessionNotFound) { + e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusNotFound, err, startTime, r.URL.Path, bodySize) + } else if errors.Is(err, sessionmanager.ErrSessionNotBelongToProject) { + e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusForbidden, err, startTime, r.URL.Path, bodySize) + } else { + e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusInternalServerError, err, startTime, r.URL.Path, bodySize) + } + return + } + response := map[string]interface{}{ + "data": resp, + } + e.responser.ResponseWithJSON(e.log, r.Context(), w, response, startTime, r.URL.Path, bodySize) +} diff --git a/ee/backend/pkg/assist/builder.go b/ee/backend/pkg/assist/builder.go new file mode 100644 index 000000000..154d5adb0 --- /dev/null +++ b/ee/backend/pkg/assist/builder.go @@ -0,0 +1,42 @@ +package assist + +import ( + "time" + + "openreplay/backend/internal/config/assist" + assistAPI "openreplay/backend/pkg/assist/api" + "openreplay/backend/pkg/assist/service" + "openreplay/backend/pkg/db/postgres/pool" + "openreplay/backend/pkg/db/redis" + "openreplay/backend/pkg/logger" + "openreplay/backend/pkg/metrics/database" + "openreplay/backend/pkg/metrics/web" + "openreplay/backend/pkg/projects" + "openreplay/backend/pkg/server/api" + "openreplay/backend/pkg/server/limiter" + "openreplay/backend/pkg/sessionmanager" +) + +type ServicesBuilder struct { + RateLimiter *limiter.UserRateLimiter + AssistAPI api.Handlers +} + +func NewServiceBuilder(log logger.Logger, cfg *assist.Config, webMetrics web.Web, dbMetrics database.Database, pgconn pool.Pool, redis *redis.Client) (*ServicesBuilder, error) { + projectsManager := projects.New(log, pgconn, redis, dbMetrics) + sessManager, err := sessionmanager.New(log, cfg, redis.Redis) + if err != nil { + return nil, err + } + sessManager.Start() + assistManager := service.NewAssist(log, pgconn, projectsManager, sessManager) + responser := api.NewResponser(webMetrics) + handlers, err := assistAPI.NewHandlers(log, cfg, responser, assistManager) + if err != nil { + return nil, err + } + return &ServicesBuilder{ + RateLimiter: limiter.NewUserRateLimiter(10, 30, 1*time.Minute, 5*time.Minute), + AssistAPI: handlers, + }, nil +} diff --git a/ee/backend/pkg/assist/service/assist.go b/ee/backend/pkg/assist/service/assist.go new file mode 100644 index 000000000..684574ae8 --- /dev/null +++ b/ee/backend/pkg/assist/service/assist.go @@ -0,0 +1,119 @@ +package service + +import ( + "fmt" + "strconv" + + "openreplay/backend/pkg/db/postgres/pool" + "openreplay/backend/pkg/logger" + "openreplay/backend/pkg/projects" + "openreplay/backend/pkg/sessionmanager" +) + +type assistImpl struct { + log logger.Logger + pgconn pool.Pool + projects projects.Projects + sessions sessionmanager.SessionManager +} + +type Assist interface { + Autocomplete(projectKey string, query *Query) (interface{}, error) + IsLive(projectKey, sessionID string) (bool, error) + GetAll(projectKey string, filters *Request) (interface{}, error) + GetByID(projectKey, sessionID string) (interface{}, error) +} + +func NewAssist(log logger.Logger, pgconn pool.Pool, projects projects.Projects, sessions sessionmanager.SessionManager) Assist { + return &assistImpl{ + log: log, + pgconn: pgconn, + projects: projects, + sessions: sessions, + } +} + +func (a *assistImpl) Autocomplete(projectKey string, query *Query) (interface{}, error) { + switch { + case projectKey == "": + return nil, fmt.Errorf("project key is required") + case query == nil: + return nil, fmt.Errorf("query is required") + case query.Key == "": + return nil, fmt.Errorf("query key is required") + case query.Value == "": + return nil, fmt.Errorf("query value is required") + } + project, err := a.projects.GetProjectByKey(projectKey) + if err != nil { + return nil, fmt.Errorf("failed to get project by key: %s", err) + } + return a.sessions.Autocomplete(strconv.Itoa(int(project.ProjectID)), sessionmanager.FilterType(query.Key), query.Value) +} + +func (a *assistImpl) IsLive(projectKey, sessionID string) (bool, error) { + switch { + case projectKey == "": + return false, fmt.Errorf("project key is required") + case sessionID == "": + return false, fmt.Errorf("session ID is required") + } + project, err := a.projects.GetProjectByKey(projectKey) + if err != nil { + return false, fmt.Errorf("failed to get project by key: %s", err) + } + sess, err := a.sessions.GetByID(strconv.Itoa(int(project.ProjectID)), sessionID) + if err != nil { + return false, fmt.Errorf("failed to get session by ID: %s", err) + } + return sess != nil, nil +} + +func (a *assistImpl) GetAll(projectKey string, request *Request) (interface{}, error) { + switch { + case projectKey == "": + return nil, fmt.Errorf("project key is required") + case request == nil: + return nil, fmt.Errorf("filters are required") + } + project, err := a.projects.GetProjectByKey(projectKey) + if err != nil { + return nil, fmt.Errorf("failed to get project by key: %s", err) + } + order := sessionmanager.Asc + if request.Sort.Order == "DESC" { + order = sessionmanager.Desc + } + filters := make([]*sessionmanager.Filter, 0, len(request.Filters)) + for name, f := range request.Filters { + filters = append(filters, &sessionmanager.Filter{ + Type: sessionmanager.FilterType(name), + Value: f.Value, + Operator: f.Operator == "is", + }) + } + sessions, total, counter, err := a.sessions.GetAll(strconv.Itoa(int(project.ProjectID)), filters, order, request.Pagination.Page, request.Pagination.Limit) + if err != nil { + return nil, fmt.Errorf("failed to get sessions: %s", err) + } + resp := map[string]interface{}{ + "total": total, + "counter": counter, + "sessions": sessions, + } + return resp, nil +} + +func (a *assistImpl) GetByID(projectKey, sessionID string) (interface{}, error) { + switch { + case projectKey == "": + return nil, fmt.Errorf("project key is required") + case sessionID == "": + return nil, fmt.Errorf("session ID is required") + } + project, err := a.projects.GetProjectByKey(projectKey) + if err != nil { + return nil, fmt.Errorf("failed to get project by key: %s", err) + } + return a.sessions.GetByID(strconv.Itoa(int(project.ProjectID)), sessionID) +} diff --git a/ee/backend/pkg/assist/service/model.go b/ee/backend/pkg/assist/service/model.go new file mode 100644 index 000000000..dfd1b017b --- /dev/null +++ b/ee/backend/pkg/assist/service/model.go @@ -0,0 +1,27 @@ +package service + +type Query struct { + Key string + Value string +} + +type Filter struct { + Value []string `json:"values"` + Operator string `json:"operator"` // is|contains +} + +type Pagination struct { + Limit int `json:"limit"` + Page int `json:"page"` +} + +type Sort struct { + Key string `json:"key"` // useless + Order string `json:"order"` // [ASC|DESC] +} + +type Request struct { + Filters map[string]Filter `json:"filter"` + Pagination Pagination `json:"pagination"` + Sort Sort `json:"sort"` +} diff --git a/ee/backend/pkg/sessionmanager/manager.go b/ee/backend/pkg/sessionmanager/manager.go new file mode 100644 index 000000000..477c23034 --- /dev/null +++ b/ee/backend/pkg/sessionmanager/manager.go @@ -0,0 +1,589 @@ +package sessionmanager + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "sort" + "strings" + "sync" + "time" + + "github.com/go-redis/redis" + + "openreplay/backend/internal/config/assist" + "openreplay/backend/pkg/logger" +) + +const ( + NodeKeyPattern = "assist:nodes:*" + ActiveSessionPrefix = "assist:online_sessions:" + RecentlyUpdatedSessions = "assist:updated_sessions" +) + +type SessionData struct { + Timestamp uint64 `json:"timestamp"` + ProjectID string `json:"projectID"` + SessionID string `json:"sessionID"` + UserID *string `json:"userID"` + UserUUID *string `json:"userUUID"` + UserOS *string `json:"userOs"` + UserBrowser *string `json:"userBrowser"` + UserDevice *string `json:"userDevice"` + UserPlatform *string `json:"userDeviceType"` // is + UserCountry *string `json:"userCountry"` // is + UserState *string `json:"userState"` // is + UserCity *string `json:"userCity"` // is + Metadata *map[string]string `json:"metadata"` // contains + IsActive bool `json:"active"` + Raw interface{} +} + +type SessionManager interface { + Start() + Stop() + GetByID(projectID, sessionID string) (interface{}, error) + GetAll(projectID string, filters []*Filter, sort SortOrder, page, limit int) ([]interface{}, int, map[string]map[string]int, error) + Autocomplete(projectID string, key FilterType, value string) ([]interface{}, error) +} + +type sessionManagerImpl struct { + ctx context.Context + log logger.Logger + client *redis.Client + ticker *time.Ticker + wg *sync.WaitGroup + stopChan chan struct{} + mutex *sync.RWMutex + cache map[string]*SessionData + sorted []*SessionData + batchSize int + scanSize int64 +} + +func New(log logger.Logger, cfg *assist.Config, redis *redis.Client) (SessionManager, error) { + switch { + case cfg == nil: + return nil, fmt.Errorf("config is required") + case log == nil: + return nil, fmt.Errorf("logger is required") + case redis == nil: + return nil, fmt.Errorf("redis client is required") + } + sm := &sessionManagerImpl{ + ctx: context.Background(), + log: log, + client: redis, + ticker: time.NewTicker(cfg.CacheTTL), + wg: &sync.WaitGroup{}, + stopChan: make(chan struct{}), + mutex: &sync.RWMutex{}, + cache: make(map[string]*SessionData), + sorted: make([]*SessionData, 0), + batchSize: cfg.BatchSize, + scanSize: cfg.ScanSize, + } + return sm, nil +} + +func (sm *sessionManagerImpl) Start() { + sm.log.Debug(sm.ctx, "Starting session manager...") + + go func() { + sm.loadSessions() + for { + select { + case <-sm.ticker.C: + sm.updateSessions() + case <-sm.stopChan: + sm.log.Debug(sm.ctx, "Stopping session manager...") + return + } + } + }() +} + +func (sm *sessionManagerImpl) Stop() { + close(sm.stopChan) + sm.ticker.Stop() + sm.wg.Wait() + if err := sm.client.Close(); err != nil { + sm.log.Debug(sm.ctx, "Error closing Redis connection: %v", err) + } + sm.log.Debug(sm.ctx, "Session manager stopped") +} + +func (sm *sessionManagerImpl) getNodeIDs() ([]string, error) { + var nodeIDs = make([]string, 0, 16) // Let's assume we have at most 16 nodes + var cursor uint64 = 0 + + for { + keys, nextCursor, err := sm.client.Scan(cursor, NodeKeyPattern, 100).Result() + if err != nil { + return nil, fmt.Errorf("scan failed: %v", err) + } + for _, key := range keys { + nodeIDs = append(nodeIDs, key) + } + cursor = nextCursor + if cursor == 0 { + break + } + } + return nodeIDs, nil +} + +func (sm *sessionManagerImpl) getAllNodeSessions(nodeIDs []string) map[string]struct{} { + allSessionIDs := make(map[string]struct{}) + var mu sync.Mutex + var wg sync.WaitGroup + + for _, nodeID := range nodeIDs { + wg.Add(1) + go func(id string) { + defer wg.Done() + + sessionListJSON, err := sm.client.Get(id).Result() + if err != nil { + if errors.Is(err, redis.Nil) { + return + } + sm.log.Debug(sm.ctx, "Error getting session list for node %s: %v", id, err) + return + } + + var sessionList []string + if err = json.Unmarshal([]byte(sessionListJSON), &sessionList); err != nil { + sm.log.Debug(sm.ctx, "Error unmarshalling session list for node %s: %v", id, err) + return + } + + mu.Lock() + for _, sessionID := range sessionList { + allSessionIDs[sessionID] = struct{}{} + } + mu.Unlock() + }(nodeID) + } + wg.Wait() + return allSessionIDs +} + +func (sm *sessionManagerImpl) getOnlineSessionIDs() (map[string]struct{}, error) { + nodeIDs, err := sm.getNodeIDs() + if err != nil { + sm.log.Debug(sm.ctx, "Error getting node IDs: %v", err) + return nil, err + } + sm.log.Debug(sm.ctx, "Found %d nodes", len(nodeIDs)) + + allSessionIDs := sm.getAllNodeSessions(nodeIDs) + sm.log.Debug(sm.ctx, "Collected %d unique session IDs", len(allSessionIDs)) + return allSessionIDs, nil +} + +func (sm *sessionManagerImpl) getSessionData(sessionIDs []string) map[string]*SessionData { + sessionData := make(map[string]*SessionData, len(sessionIDs)) + + for i := 0; i < len(sessionIDs); i += sm.batchSize { + end := i + sm.batchSize + if end > len(sessionIDs) { + end = len(sessionIDs) + } + batch := sessionIDs[i:end] + + keys := make([]string, len(batch)) + for j, id := range batch { + keys[j] = ActiveSessionPrefix + id + } + + results, err := sm.client.MGet(keys...).Result() + if err != nil { + sm.log.Debug(sm.ctx, "Error in MGET operation: %v", err) + continue // TODO: Handle the error + } + + for j, result := range results { + if result == nil { + continue + } + + strVal, ok := result.(string) + if !ok { + sm.log.Debug(sm.ctx, "Unexpected type for session data: %T", result) + continue + } + + var data SessionData + if err := json.Unmarshal([]byte(strVal), &data); err != nil { + sm.log.Debug(sm.ctx, "Error unmarshalling session data: %v", err) + continue + } + raw := make(map[string]interface{}) + if err := json.Unmarshal([]byte(strVal), &raw); err != nil { + sm.log.Debug(sm.ctx, "Error unmarshalling raw session data: %v", err) + continue + } + data.Raw = raw + sessionData[batch[j]] = &data + } + sm.log.Debug(sm.ctx, "Collected %d new sessions", len(results)) + } + + sm.wg.Wait() + return sessionData +} + +func (sm *sessionManagerImpl) updateCache(sessionsToAdd map[string]*SessionData, sessionsToRemove []string) { + sm.mutex.Lock() + defer sm.mutex.Unlock() + + if sessionsToRemove != nil { + for _, sessID := range sessionsToRemove { + delete(sm.cache, sessID) + } + } + if sessionsToAdd == nil { + return + } + for sessID, session := range sessionsToAdd { + sm.cache[sessID] = session + } + + sessionList := make([]*SessionData, 0, len(sm.cache)) + for _, s := range sm.cache { + sessionList = append(sessionList, s) + } + sort.Slice(sessionList, func(i, j int) bool { + return sessionList[i].Timestamp < sessionList[j].Timestamp + }) + sm.sorted = sessionList +} + +func (sm *sessionManagerImpl) loadSessions() { + startTime := time.Now() + sm.log.Debug(sm.ctx, "Starting session processing cycle") + + sessIDs, err := sm.getOnlineSessionIDs() + if err != nil { + sm.log.Debug(sm.ctx, "Error getting online session IDs: %v", err) + return + } + if len(sessIDs) == 0 { + sm.log.Debug(sm.ctx, "No sessions found for nodes") + return + } + allSessionIDsList := make([]string, 0, len(sessIDs)) + for sessionID := range sessIDs { + allSessionIDsList = append(allSessionIDsList, sessionID) + } + sessionMap := sm.getSessionData(allSessionIDsList) + sm.updateCache(sessionMap, nil) + + duration := time.Since(startTime) + sm.log.Debug(sm.ctx, "Session processing cycle completed in %v. Processed %d sessions", duration, len(sm.cache)) +} + +func (sm *sessionManagerImpl) getAllRecentlyUpdatedSessions() (map[string]struct{}, error) { + var ( + cursor uint64 + allIDs = make(map[string]struct{}) + batchIDs []string + err error + ) + + for { + batchIDs, cursor, err = sm.client.SScan(RecentlyUpdatedSessions, cursor, "*", sm.scanSize).Result() + if err != nil { + sm.log.Debug(sm.ctx, "Error scanning updated session IDs: %v", err) + return nil, err + } + for _, id := range batchIDs { + allIDs[id] = struct{}{} + } + if cursor == 0 { + break + } + } + + if len(allIDs) == 0 { + sm.log.Debug(sm.ctx, "No updated session IDs found") + return allIDs, nil + } + + var sessionIDsSlice []interface{} + for id := range allIDs { + sessionIDsSlice = append(sessionIDsSlice, id) + } + removed := sm.client.SRem(RecentlyUpdatedSessions, sessionIDsSlice...).Val() + sm.log.Debug(sm.ctx, "Fetched and removed %d session IDs from updated_session_set", removed) + + return allIDs, nil +} + +func (sm *sessionManagerImpl) updateSessions() { + startTime := time.Now() + sm.log.Debug(sm.ctx, "Starting session processing cycle") + + sessIDs, err := sm.getOnlineSessionIDs() + if err != nil { + sm.log.Debug(sm.ctx, "Error getting online session IDs: %v", err) + return + } + + updatedSessIDs, err := sm.getAllRecentlyUpdatedSessions() + if err != nil { + sm.log.Debug(sm.ctx, "Error getting recently updated sessions: %v", err) + return + } + + sm.mutex.RLock() + toAdd := make([]string, 0, len(updatedSessIDs)) + if updatedSessIDs == nil { + updatedSessIDs = make(map[string]struct{}) + } + for sessID, _ := range sessIDs { + if _, exists := sm.cache[sessID]; !exists { + updatedSessIDs[sessID] = struct{}{} // Add to updated sessions if not in cache + } + } + for sessID, _ := range updatedSessIDs { + toAdd = append(toAdd, sessID) + } + + toRemove := make([]string, 0, len(sessIDs)/16) + for sessID, _ := range sm.cache { + if _, exists := sessIDs[sessID]; !exists { + toRemove = append(toRemove, sessID) + } + } + sm.mutex.RUnlock() + + // Load full session data from Redis + newCache := sm.getSessionData(toAdd) + sm.updateCache(newCache, toRemove) + + duration := time.Since(startTime) + sm.log.Debug(sm.ctx, "Session processing cycle completed in %v. Processed %d sessions", duration, len(sm.cache)) +} + +var ErrSessionNotFound = errors.New("session not found") +var ErrSessionNotBelongToProject = errors.New("session does not belong to the project") + +func (sm *sessionManagerImpl) GetByID(projectID, sessionID string) (interface{}, error) { + if sessionID == "" { + return nil, fmt.Errorf("session ID is required") + } + + sm.mutex.RLock() + defer sm.mutex.RUnlock() + + sessionData, exists := sm.cache[sessionID] + if !exists { + return nil, ErrSessionNotFound + } + if sessionData.ProjectID != projectID { + return nil, ErrSessionNotBelongToProject + } + return sessionData.Raw, nil +} + +func (sm *sessionManagerImpl) GetAll(projectID string, filters []*Filter, sortOrder SortOrder, page, limit int) ([]interface{}, int, map[string]map[string]int, error) { + if projectID == "" { + return nil, 0, nil, fmt.Errorf("project ID is required") + } + + counter := make(map[string]map[string]int) + for _, filter := range filters { + if _, ok := counter[string(filter.Type)]; !ok { + counter[string(filter.Type)] = make(map[string]int) + } + for _, value := range filter.Value { + counter[string(filter.Type)][value] = 0 + } + } + + if page < 1 || limit < 1 { + page, limit = 1, 10 + } + start := (page - 1) * limit + end := start + limit + + result := make([]interface{}, 0, limit) + totalMatches := 0 + + doFiltering := func(session *SessionData) { + if session.ProjectID != projectID { + return // TODO: keep sessions separate by projectID + } + if matchesFilters(session, filters, counter) { + if totalMatches >= start && totalMatches < end { + result = append(result, session.Raw) + } + totalMatches++ + } + } + + sm.mutex.RLock() + defer sm.mutex.RUnlock() + + if sortOrder == Asc { + for _, session := range sm.sorted { + doFiltering(session) + } + } else { + for i := len(sm.sorted) - 1; i >= 0; i-- { + doFiltering(sm.sorted[i]) + } + } + return result, totalMatches, counter, nil +} + +func matchesFilters(session *SessionData, filters []*Filter, counter map[string]map[string]int) bool { + if filters == nil || len(filters) == 0 { + return true + } + matchedFilters := make(map[string][]string, len(filters)) + for _, filter := range filters { + name := string(filter.Type) + if _, ok := matchedFilters[name]; !ok { + matchedFilters[name] = make([]string, 0, len(filter.Value)) + } + var value string + + switch filter.Type { + case UserID: + if session.UserID != nil { + value = *session.UserID + } + case UserAnonymousID: + if session.UserUUID != nil { + value = *session.UserUUID + } + case UserOS: + if session.UserOS != nil { + value = *session.UserOS + } + case UserBrowser: + if session.UserBrowser != nil { + value = *session.UserBrowser + } + case UserDevice: + if session.UserDevice != nil { + value = *session.UserDevice + } + case UserPlatform: + if session.UserPlatform != nil { + value = *session.UserPlatform + } + case UserCountry: + if session.UserCountry != nil { + value = *session.UserCountry + } + case UserState: + if session.UserState != nil { + value = *session.UserState + } + case UserCity: + if session.UserCity != nil { + value = *session.UserCity + } + case IsActive: + if session.IsActive { + value = "true" + } else { + value = "false" + } + default: + if val, ok := (*session.Metadata)[name]; ok { + value = val + } + } + + matched := false + for _, filterValue := range filter.Value { + if filter.Operator == Is && value != filterValue { + continue + } else if filter.Operator == Contains && !strings.Contains(strings.ToLower(value), strings.ToLower(filterValue)) { + continue + } + matched = true + matchedFilters[name] = append(matchedFilters[name], value) + } + if !matched { + return false + } + } + for values, filter := range matchedFilters { + for _, value := range filter { + counter[values][value]++ + } + } + return true +} + +func (sm *sessionManagerImpl) Autocomplete(projectID string, key FilterType, value string) ([]interface{}, error) { + matches := make(map[string]struct{}) // To ensure uniqueness + lowerValue := strings.ToLower(value) + + sm.mutex.RLock() + defer sm.mutex.RUnlock() + + for _, session := range sm.sorted { + if session.ProjectID != projectID { + continue + } + + var fieldValue string + switch key { + case UserID: + if session.UserID != nil { + fieldValue = *session.UserID + } + case UserAnonymousID: + if session.UserUUID != nil { + fieldValue = *session.UserUUID + } + case UserOS: + if session.UserOS != nil { + fieldValue = *session.UserOS + } + case UserBrowser: + if session.UserBrowser != nil { + fieldValue = *session.UserBrowser + } + case UserDevice: + if session.UserDevice != nil { + fieldValue = *session.UserDevice + } + case UserState: + if session.UserState != nil { + fieldValue = *session.UserState + } + case UserCity: + if session.UserCity != nil { + fieldValue = *session.UserCity + } + default: + if v, ok := (*session.Metadata)[string(key)]; ok { + fieldValue = v + } + } + + if fieldValue != "" && strings.Contains(strings.ToLower(fieldValue), lowerValue) { + matches[fieldValue] = struct{}{} + } + } + + results := make([]interface{}, 0, len(matches)) + keyName := strings.ToUpper(string(key)) + type pair struct { + Type string `json:"type"` + Value string `json:"value"` + } + for k := range matches { + results = append(results, pair{Type: keyName, Value: k}) + } + return results, nil +} diff --git a/ee/backend/pkg/sessionmanager/model.go b/ee/backend/pkg/sessionmanager/model.go new file mode 100644 index 000000000..64e9d7875 --- /dev/null +++ b/ee/backend/pkg/sessionmanager/model.go @@ -0,0 +1,37 @@ +package sessionmanager + +type SortOrder bool + +const ( + Asc SortOrder = true + Desc SortOrder = false +) + +type FilterType string + +const ( + UserID FilterType = "userId" + UserAnonymousID = "userAnonymousId" + UserOS = "userOs" + UserBrowser = "userBrowser" + UserDevice = "userDevice" + UserPlatform = "platform" + UserCountry = "userCountry" + UserState = "userState" + UserCity = "userCity" + IsActive = "active" +) + +type FilterOperator bool + +const ( + Is FilterOperator = true + Contains FilterOperator = false +) + +type Filter struct { + Type FilterType + Value []string + Operator FilterOperator + Source string // for metadata only +} diff --git a/scripts/helmcharts/openreplay/charts/assist-api/.helmignore b/scripts/helmcharts/openreplay/charts/assist-api/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/scripts/helmcharts/openreplay/charts/assist-api/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/scripts/helmcharts/openreplay/charts/assist-api/Chart.yaml b/scripts/helmcharts/openreplay/charts/assist-api/Chart.yaml new file mode 100644 index 000000000..498f81dd6 --- /dev/null +++ b/scripts/helmcharts/openreplay/charts/assist-api/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: assist-api +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rassist-apiing +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.1 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +AppVersion: "v1.22.0" diff --git a/scripts/helmcharts/openreplay/charts/assist-api/templates/NOTES.txt b/scripts/helmcharts/openreplay/charts/assist-api/templates/NOTES.txt new file mode 100644 index 000000000..f5106914d --- /dev/null +++ b/scripts/helmcharts/openreplay/charts/assist-api/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "assist-api.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "assist-api.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "assist-api.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "assist-api.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/scripts/helmcharts/openreplay/charts/assist-api/templates/_helpers.tpl b/scripts/helmcharts/openreplay/charts/assist-api/templates/_helpers.tpl new file mode 100644 index 000000000..568007bc7 --- /dev/null +++ b/scripts/helmcharts/openreplay/charts/assist-api/templates/_helpers.tpl @@ -0,0 +1,65 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "assist-api.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "assist-api.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "assist-api.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "assist-api.labels" -}} +helm.sh/chart: {{ include "assist-api.chart" . }} +{{ include "assist-api.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- if .Values.global.appLabels }} +{{- .Values.global.appLabels | toYaml | nindent 0}} +{{- end}} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "assist-api.selectorLabels" -}} +app.kubernetes.io/name: {{ include "assist-api.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "assist-api.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "assist-api.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/scripts/helmcharts/openreplay/charts/assist-api/templates/deployment.yaml b/scripts/helmcharts/openreplay/charts/assist-api/templates/deployment.yaml new file mode 100644 index 000000000..e8540295b --- /dev/null +++ b/scripts/helmcharts/openreplay/charts/assist-api/templates/deployment.yaml @@ -0,0 +1,103 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "assist-api.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "assist-api.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "assist-api.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "assist-api.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "assist-api.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + shareProcessNamespace: true + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + {{- if .Values.global.enterpriseEditionLicense }} + image: "{{ tpl .Values.image.repository . }}:{{ .Values.image.tag | default .Chart.AppVersion }}-ee" + {{- else }} + image: "{{ tpl .Values.image.repository . }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + {{- end }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- if .Values.healthCheck}} + {{- .Values.healthCheck | toYaml | nindent 10}} + {{- end}} + env: + - name: LICENSE_KEY + value: '{{ .Values.global.enterpriseEditionLicense }}' + - name: KAFKA_SERVERS + value: '{{ .Values.global.kafka.kafkaHost }}:{{ .Values.global.kafka.kafkaPort }}' + - name: KAFKA_USE_SSL + value: '{{ .Values.global.kafka.kafkaUseSsl }}' + - name: ASSIST_KEY + value: {{ .Values.global.assistKey }} + - name: pg_password + {{- if .Values.global.postgresql.existingSecret }} + valueFrom: + secretKeyRef: + name: {{ .Values.global.postgresql.existingSecret }} + key: postgresql-postgres-password + {{- else }} + value: '{{ .Values.global.postgresql.postgresqlPassword }}' + {{- end}} + - name: POSTGRES_STRING + value: 'postgres://{{ .Values.global.postgresql.postgresqlUser }}:$(pg_password)@{{ .Values.global.postgresql.postgresqlHost }}:{{ .Values.global.postgresql.postgresqlPort }}/{{ .Values.global.postgresql.postgresqlDatabase }}' + {{- include "openreplay.env.redis_string" .Values.global.redis | nindent 12 }} + {{- range $key, $val := .Values.global.env }} + - name: {{ $key }} + value: '{{ $val }}' + {{- end }} + {{- range $key, $val := .Values.env }} + - name: {{ $key }} + value: '{{ $val }}' + {{- end}} + ports: + {{- range $key, $val := .Values.service.ports }} + - name: {{ $key }} + containerPort: {{ $val }} + protocol: TCP + {{- end }} + volumeMounts: + {{- include "openreplay.volume.redis_ca_certificate.mount" .Values.global.redis | nindent 12 }} + {{- with .Values.persistence.mounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumes: + {{- with .Values.persistence.volumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- include "openreplay.volume.redis_ca_certificate" .Values.global.redis | nindent 8 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/scripts/helmcharts/openreplay/charts/assist-api/templates/hpa.yaml b/scripts/helmcharts/openreplay/charts/assist-api/templates/hpa.yaml new file mode 100644 index 000000000..04f9e7961 --- /dev/null +++ b/scripts/helmcharts/openreplay/charts/assist-api/templates/hpa.yaml @@ -0,0 +1,33 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "assist-api.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "assist-api.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "assist-api.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/scripts/helmcharts/openreplay/charts/assist-api/templates/ingress.yaml b/scripts/helmcharts/openreplay/charts/assist-api/templates/ingress.yaml new file mode 100644 index 000000000..6341db4f2 --- /dev/null +++ b/scripts/helmcharts/openreplay/charts/assist-api/templates/ingress.yaml @@ -0,0 +1,36 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "assist-api.fullname" . -}} +{{- $svcPort := .Values.service.ports.http -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $fullName }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "assist-api.labels" . | nindent 4 }} + annotations: + nginx.ingress.kubernetes.io/rewrite-target: /$1 + nginx.ingress.kubernetes.io/upstream-hash-by: $http_x_forwarded_for + {{- with .Values.ingress.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + ingressClassName: "{{ tpl .Values.ingress.className . }}" + tls: + - hosts: + - {{ .Values.global.domainName }} + {{- if .Values.ingress.tls.secretName}} + secretName: {{ .Values.ingress.tls.secretName }} + {{- end }} + rules: + - host: {{ .Values.global.domainName }} + http: + paths: + - path: /api-assist/(.*) + pathType: Prefix + backend: + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} +{{- end }} diff --git a/scripts/helmcharts/openreplay/charts/assist-api/templates/service.yaml b/scripts/helmcharts/openreplay/charts/assist-api/templates/service.yaml new file mode 100644 index 000000000..6838d1702 --- /dev/null +++ b/scripts/helmcharts/openreplay/charts/assist-api/templates/service.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "assist-api.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "assist-api.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + {{- range $key, $val := .Values.service.ports }} + - port: {{ $val }} + targetPort: {{ $key }} + protocol: TCP + name: {{ $key }} + {{- end}} + selector: + {{- include "assist-api.selectorLabels" . | nindent 4 }} diff --git a/scripts/helmcharts/openreplay/charts/assist-api/templates/serviceMonitor.yaml b/scripts/helmcharts/openreplay/charts/assist-api/templates/serviceMonitor.yaml new file mode 100644 index 000000000..c57c21606 --- /dev/null +++ b/scripts/helmcharts/openreplay/charts/assist-api/templates/serviceMonitor.yaml @@ -0,0 +1,18 @@ +{{- if and ( .Capabilities.APIVersions.Has "monitoring.coreos.com/v1" ) ( .Values.serviceMonitor.enabled ) }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "assist-api.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "assist-api.labels" . | nindent 4 }} + {{- if .Values.serviceMonitor.additionalLabels }} + {{- toYaml .Values.serviceMonitor.additionalLabels | nindent 4 }} + {{- end }} +spec: + endpoints: + {{- .Values.serviceMonitor.scrapeConfigs | toYaml | nindent 4 }} + selector: + matchLabels: + {{- include "assist-api.selectorLabels" . | nindent 6 }} +{{- end }} diff --git a/scripts/helmcharts/openreplay/charts/assist-api/templates/serviceaccount.yaml b/scripts/helmcharts/openreplay/charts/assist-api/templates/serviceaccount.yaml new file mode 100644 index 000000000..06db808ac --- /dev/null +++ b/scripts/helmcharts/openreplay/charts/assist-api/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "assist-api.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "assist-api.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/scripts/helmcharts/openreplay/charts/assist-api/templates/tests/test-connection.yaml b/scripts/helmcharts/openreplay/charts/assist-api/templates/tests/test-connection.yaml new file mode 100644 index 000000000..57e38f294 --- /dev/null +++ b/scripts/helmcharts/openreplay/charts/assist-api/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "assist-api.fullname" . }}-test-connection" + labels: + {{- include "assist-api.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "assist-api.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/scripts/helmcharts/openreplay/charts/assist-api/values.yaml b/scripts/helmcharts/openreplay/charts/assist-api/values.yaml new file mode 100644 index 000000000..f5bf928e0 --- /dev/null +++ b/scripts/helmcharts/openreplay/charts/assist-api/values.yaml @@ -0,0 +1,119 @@ +# Default values for openreplay. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + repository: "{{ .Values.global.openReplayContainerRegistry }}/assist-api" + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "" + +imagePullSecrets: [] +nameOverride: "assist-api" +fullnameOverride: "assist-api-openreplay" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} + +securityContext: + runAsUser: 1001 + runAsGroup: 1001 +podSecurityContext: + runAsUser: 1001 + runAsGroup: 1001 + fsGroup: 1001 + fsGroupChangePolicy: "OnRootMismatch" + # podSecurityContext: {} + # fsGroup: 2000 + + # securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +#service: +# type: ClusterIP +# port: 9000 + +serviceMonitor: + enabled: false + additionalLabels: + release: observability + scrapeConfigs: + - port: metrics + honorLabels: true + interval: 15s + path: /metrics + scheme: http + scrapeTimeout: 10s + +service: + type: ClusterIP + ports: + http: 8080 + metrics: 8888 + +ingress: + enabled: false + className: "{{ .Values.global.ingress.controller.ingressClassResource.name }}" + annotations: + nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" + nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + tls: + secretName: openreplay-ssl + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m +# memory: 128Mi + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 5 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +env: + CLEAR_SOCKET_TIME: 720 + + +nodeSelector: {} + +tolerations: [] + +affinity: {} + + +persistence: {} + # # Spec of spec.template.spec.containers[*].volumeMounts + # mounts: + # - name: kafka-ssl + # mountPath: /opt/kafka/ssl + # # Spec of spec.template.spec.volumes + # volumes: + # - name: kafka-ssl + # secret: +# secretName: kafka-ssl diff --git a/scripts/helmcharts/openreplay/charts/assist/values.yaml b/scripts/helmcharts/openreplay/charts/assist/values.yaml index a3a14eff5..426b1bb72 100644 --- a/scripts/helmcharts/openreplay/charts/assist/values.yaml +++ b/scripts/helmcharts/openreplay/charts/assist/values.yaml @@ -110,7 +110,6 @@ autoscaling: env: debug: 0 - uws: false redis: false CLEAR_SOCKET_TIME: 720