diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..da9a0d123 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,25 @@ +# Directories to exclude +.circleci +.idea +.platform +.vscode +_reference +client +redis/dockerdata +hasura +node_modules + +# Files to exclude +.ebignore +.editorconfig +.eslintrc.json +.gitignore +.prettierrc.js +Dockerfile +README.MD +bodyshop_translations.babel +docker-compose.yml +ecosystem.config.js + +# Optional: Exclude logs and temporary files +*.log diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..dab748c1b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,44 @@ +# Use Amazon Linux 2023 as the base image +FROM amazonlinux:2023 + +# Install Git and Node.js (Amazon Linux 2023 uses the DNF package manager) +RUN dnf install -y git \ + && curl -sL https://rpm.nodesource.com/setup_20.x | bash - \ + && dnf install -y nodejs \ + && dnf clean all + + +# Install dependencies required by node-canvas +RUN dnf install -y \ + gcc \ + gcc-c++ \ + cairo-devel \ + pango-devel \ + libjpeg-turbo-devel \ + giflib-devel \ + libpng-devel \ + make \ + python3 \ + python3-pip \ + && dnf clean all + +# Set the working directory +WORKDIR /app + +# Copy package.json and package-lock.json +COPY package*.json ./ + +# Install Nodemon +RUN npm install -g nodemon + +# Install dependencies +RUN npm install --omit=dev + +# Copy the rest of your application code +COPY . . + +# Expose the port your app runs on (adjust if necessary) +EXPOSE 4000 + +# Start the application +CMD ["nodemon", "--legacy-watch", "server.js"] diff --git a/_reference/dockerreadme.md b/_reference/dockerreadme.md new file mode 100644 index 000000000..b65c6b23d --- /dev/null +++ b/_reference/dockerreadme.md @@ -0,0 +1,182 @@ +# Setting up External Networking and Static IP for WSL2 using Hyper-V + +This guide will walk you through the steps to configure your WSL2 (Windows Subsystem for Linux) instance to use an external Hyper-V virtual switch, enabling it to connect directly to your local network. Additionally, you'll learn how to assign a static IP address to your WSL2 instance. + +## Prerequisites + +1. **Windows 10/11** with **WSL2** installed. +2. **Hyper-V** enabled on your system. If not, follow these steps to enable it: + - Open PowerShell as Administrator and run: + ```powershell + dism.exe /Online /Enable-Feature /All /FeatureName:Microsoft-Hyper-V + ``` + - Restart your computer. + +3. A basic understanding of networking and WSL2 configuration. + +--- + +## Step 1: Create an External Hyper-V Switch + +1. **Open Hyper-V Manager**: + - Press `Windows Key + X`, select `Hyper-V Manager`. + +2. **Create a Virtual Switch**: + - In the right-hand pane, click `Virtual Switch Manager`. + - Choose `External` and click `Create Virtual Switch`. + - Select your external network adapter (this is usually your Ethernet or Wi-Fi adapter). + - Give the switch a name (e.g., `WSL External Switch`), then click `Apply` and `OK`. + +--- + +## Step 2: Configure WSL2 to Use the External Hyper-V Switch + +Now that you've created the external virtual switch, follow these steps to configure your WSL2 instance to use this switch. + +1. **Set WSL2 to Use the External Switch**: + - By default, WSL2 uses NAT to connect to your local network. You need to configure WSL2 to use the external Hyper-V switch instead. + +2. **Check WSL2 Networking**: + - Inside WSL, run: + ```bash + ip a + ``` + - You should see an IP address in the range of your local network (e.g., `192.168.x.x`). + +--- + +## Step 3: Configure a Static IP Address for WSL2 + +Once WSL2 is connected to the external network, you can assign a static IP address to your WSL2 instance. + +1. **Open WSL2** and Edit the Network Configuration: + - Depending on your Linux distribution, the file paths may vary, but typically for Ubuntu-based systems: + ```bash + sudo nano /etc/netplan/01-netcfg.yaml + ``` + - If this file doesn’t exist, create a new file or use the correct configuration file path. + +2. **Configure Static IP**: + - Add or update the following configuration: + ```yaml + network: + version: 2 + renderer: networkd + ethernets: + eth0: + dhcp4: no + addresses: + - 192.168.1.100/24 # Choose an IP address in your network range + gateway4: 192.168.1.1 # Your router's IP address + nameservers: + addresses: + - 8.8.8.8 + - 8.8.4.4 + ``` + - Adjust the values according to your local network settings: + - `addresses`: This is the static IP you want to assign. + - `gateway4`: This should be the IP address of your router. + - `nameservers`: These are DNS servers, you can use Google's public DNS or any other DNS provider. + +3. **Apply the Changes**: + - Run the following command to apply the network configuration: + ```bash + sudo netplan apply + ``` + +4. **Verify the Static IP**: + - Check if the static IP is correctly set by running: + ```bash + ip a + ``` + - You should see the static IP you configured (e.g., `192.168.1.100`) on the appropriate network interface (usually `eth0`). + +--- + +## Step 4: Restart WSL2 to Apply Changes + +To ensure the changes are fully applied, restart WSL2: + +1. Open PowerShell or Command Prompt and run: + ```powershell + wsl --shutdown +2. Then, start your WSL2 instance again. + +## Step 5: Verify Connectivity + +1. Check Internet and Local Network Connectivity: + - Run a ping command from within WSL to verify that it can reach the internet: ```ping 8.8.8.8``` +2. Test Access from other Devices: + - If you're running services inside WSL (e.g., a web server), ensure they are accessible from other devices on your local network using the static IP address you configured (e.g., `http://192.168.1.100:4000`). + + + +# Configuring `vm.overcommit_memory` in sysctl for WSL2 + +To prevent memory overcommitment issues and optimize performance, you can configure the `vm.overcommit_memory` setting in WSL2. This is particularly useful when running Redis or other memory-intensive services inside WSL2, as it helps control how the Linux kernel handles memory allocation. + +### 1. **Open the sysctl Configuration File**: +To set the `vm.overcommit_memory` value, you'll need to edit the sysctl configuration file. Inside your WSL2 instance, run the following command to open the `sysctl.conf` file for editing: +```bash + sudo nano /etc/sysctl.conf +``` +### 2. Add the Overcommit Memory Setting: +Add the following line at the end of the file to allow memory overcommitment: +```bash +vm.overcommit_memory = 1 +``` + +This setting tells the Linux kernel to always allow memory allocation, regardless of how much memory is available, which can prevent out-of-memory errors when running certain applications. + +### 3. Apply the Changes: +After editing the file, save it and then apply the new sysctl configuration by running: + +```bash +sudo sysctl -p +``` + +# Install Docker and Docker Compose in WSL2 +- https://docs.docker.com/desktop/wsl/ + +# Docker Commands + +## General `docker-compose` Commands: +1. Bring up the services, force a rebuild of all services, and do not use the cache: `docker-compose up --build --no-cache` +2. Start Containers in Detached Mode: This will run the containers in the background (detached mode): `docker-compose up -d` +3. Stop and Remove Containers: Stops and removes the containers gracefully: `docker-compose down` +4. Stop containers without removing them: `docker-compose stop` +5. Remove Containers, Volumes, and Networks: `docker-compose down --volumes` +6. Force rebuild of containers: `docker-compose build --no-cache` +7. View running Containers: `docker-compose ps` +8. View a specific containers logs: `docker-compose logs ` +9. Scale services (multiple instances of a service): `docker-compose up --scale = -d` + +## Volume Management Commands +1. List Docker volumes: `docker volume ls` +2. Remove Unused volumes `docker volume prune` +3. Remove specific volumes `docker volume rm ` +4. Inspect a volume: `docker volume inspect ` + +## Container Image Management Commands: +1. List running containers: `docker ps` +2. List all containers: `docker os -a` +3. Remove Stopped containers: `docker container prune` +4. Remove a specific container: `docker container rm ` +5. Remove a specific image: `docker rmi :` +6. Remove all unused images: `docker image prune -a` + +## Network Management Commands: +1. List networks: `docker network ls` +2. Inspect a specific network: `docker network inspect ` +3. Remove a specific network: `docker network rm ` +4. Remove unused networks: `docker network prune` + +## Debugging and maintenance: +1. Enter a Running container: `docker exec -it /bin/bash` (could also be `/bin/sh` or for example `redis-cli` on a redis node) +2. View container resource usage: `docker stats` +3. Check Disk space used by Docker: `docker system df` +4. Remove all unused Data (Nuclear option): `docker system prune` + +## Specific examples +1. To simulate a Clean state, one should run `docker system prune` followed by `docker volume prune -a` +2. You can run `docker-compose up` without the `-d` option, and you will get what is identical to the experience you were used to, this includes being able to control-c and bring the entire stack down diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..4e8ecf9d4 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,111 @@ +############################# +# Ports Exposed +# 4000 - Imex Node API +# 3333 - SocketIO Admin-UI +# 3334 - Redis-Insights +############################# + +services: + redis-node-1: + build: + context: ./redis + container_name: redis-node-1 + hostname: redis-node-1 + restart: always + networks: + - redis-cluster-net + volumes: + - redis-node-1-data:/data + - redis-lock:/redis-lock + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 10 + + redis-node-2: + build: + context: ./redis + container_name: redis-node-2 + hostname: redis-node-2 + restart: always + networks: + - redis-cluster-net + volumes: + - redis-node-2-data:/data + - redis-lock:/redis-lock + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 10 + + redis-node-3: + build: + context: ./redis + container_name: redis-node-3 + hostname: redis-node-3 + restart: always + networks: + - redis-cluster-net + volumes: + - redis-node-3-data:/data + - redis-lock:/redis-lock + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 10 + + node-app: + build: + context: . + container_name: node-app + hostname: imex-api + networks: + - redis-cluster-net + env_file: + - .env.development + depends_on: + redis-node-1: + condition: service_healthy + redis-node-2: + condition: service_healthy + redis-node-3: + condition: service_healthy + ports: + - "4000:4000" + volumes: + - .:/app + - /app/node_modules + + socketio-admin-ui: + image: maitrungduc1410/socket.io-admin-ui + container_name: socketio-admin-ui + networks: + - redis-cluster-net + ports: + - "3333:80" + + redis-insight: + image: redislabs/redisinsight:latest + container_name: redis-insight + hostname: redis-insight + restart: always + ports: + - "3334:5540" + networks: + - redis-cluster-net + volumes: + - redis-insight-data:/db + +networks: + redis-cluster-net: + driver: bridge + +volumes: + redis-node-1-data: + redis-node-2-data: + redis-node-3-data: + redis-lock: + redis-insight-data: diff --git a/package-lock.json b/package-lock.json index be917a0e0..822bb487e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.2.0", "license": "UNLICENSED", "dependencies": { + "@aws-sdk/client-elasticache": "^3.665.0", "@aws-sdk/client-secrets-manager": "^3.654.0", "@aws-sdk/client-ses": "^3.654.0", "@aws-sdk/credential-provider-node": "^3.654.0", @@ -36,6 +37,7 @@ "graylog2": "^0.2.1", "inline-css": "^4.0.2", "intuit-oauth": "^4.1.2", + "ioredis": "^5.4.1", "json-2-csv": "^5.5.5", "lodash": "^4.17.21", "moment": "^2.30.1", @@ -183,6 +185,530 @@ "node": ">=14.0.0" } }, + "node_modules/@aws-sdk/client-elasticache": { + "version": "3.665.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-elasticache/-/client-elasticache-3.665.0.tgz", + "integrity": "sha512-r1T7Yhv+jdVEoOT3h1oJtsKPYt82rEHv1PPYAknyYWUOYxb4tgpmaPusw50nqnNi47W7n/9jkqnk75IwMKEjfQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.665.0", + "@aws-sdk/client-sts": "3.665.0", + "@aws-sdk/core": "3.665.0", + "@aws-sdk/credential-provider-node": "3.665.0", + "@aws-sdk/middleware-host-header": "3.664.0", + "@aws-sdk/middleware-logger": "3.664.0", + "@aws-sdk/middleware-recursion-detection": "3.664.0", + "@aws-sdk/middleware-user-agent": "3.664.0", + "@aws-sdk/region-config-resolver": "3.664.0", + "@aws-sdk/types": "3.664.0", + "@aws-sdk/util-endpoints": "3.664.0", + "@aws-sdk/util-user-agent-browser": "3.664.0", + "@aws-sdk/util-user-agent-node": "3.664.0", + "@smithy/config-resolver": "^3.0.9", + "@smithy/core": "^2.4.7", + "@smithy/fetch-http-handler": "^3.2.9", + "@smithy/hash-node": "^3.0.7", + "@smithy/invalid-dependency": "^3.0.7", + "@smithy/middleware-content-length": "^3.0.9", + "@smithy/middleware-endpoint": "^3.1.4", + "@smithy/middleware-retry": "^3.0.22", + "@smithy/middleware-serde": "^3.0.7", + "@smithy/middleware-stack": "^3.0.7", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/node-http-handler": "^3.2.4", + "@smithy/protocol-http": "^4.1.4", + "@smithy/smithy-client": "^3.3.6", + "@smithy/types": "^3.5.0", + "@smithy/url-parser": "^3.0.7", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.22", + "@smithy/util-defaults-mode-node": "^3.0.22", + "@smithy/util-endpoints": "^2.1.3", + "@smithy/util-middleware": "^3.0.7", + "@smithy/util-retry": "^3.0.7", + "@smithy/util-utf8": "^3.0.0", + "@smithy/util-waiter": "^3.1.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-elasticache/node_modules/@aws-sdk/client-sso": { + "version": "3.665.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.665.0.tgz", + "integrity": "sha512-zje+oaIiyviDv5dmBWhGHifPTb0Idq/HatNPy+VEiwo2dxcQBexibD5CQE5e8CWZK123Br/9DHft+iNKdiY5bA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.665.0", + "@aws-sdk/middleware-host-header": "3.664.0", + "@aws-sdk/middleware-logger": "3.664.0", + "@aws-sdk/middleware-recursion-detection": "3.664.0", + "@aws-sdk/middleware-user-agent": "3.664.0", + "@aws-sdk/region-config-resolver": "3.664.0", + "@aws-sdk/types": "3.664.0", + "@aws-sdk/util-endpoints": "3.664.0", + "@aws-sdk/util-user-agent-browser": "3.664.0", + "@aws-sdk/util-user-agent-node": "3.664.0", + "@smithy/config-resolver": "^3.0.9", + "@smithy/core": "^2.4.7", + "@smithy/fetch-http-handler": "^3.2.9", + "@smithy/hash-node": "^3.0.7", + "@smithy/invalid-dependency": "^3.0.7", + "@smithy/middleware-content-length": "^3.0.9", + "@smithy/middleware-endpoint": "^3.1.4", + "@smithy/middleware-retry": "^3.0.22", + "@smithy/middleware-serde": "^3.0.7", + "@smithy/middleware-stack": "^3.0.7", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/node-http-handler": "^3.2.4", + "@smithy/protocol-http": "^4.1.4", + "@smithy/smithy-client": "^3.3.6", + "@smithy/types": "^3.5.0", + "@smithy/url-parser": "^3.0.7", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.22", + "@smithy/util-defaults-mode-node": "^3.0.22", + "@smithy/util-endpoints": "^2.1.3", + "@smithy/util-middleware": "^3.0.7", + "@smithy/util-retry": "^3.0.7", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-elasticache/node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.665.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.665.0.tgz", + "integrity": "sha512-FQ2YyM9/6y3clWkf3d60/W4c/HZy61hbfIsR4KIh8aGOifwfIx/UpZQ61pCr/TXTNqbaAVU2/sK+J1zFkGEoLw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.665.0", + "@aws-sdk/credential-provider-node": "3.665.0", + "@aws-sdk/middleware-host-header": "3.664.0", + "@aws-sdk/middleware-logger": "3.664.0", + "@aws-sdk/middleware-recursion-detection": "3.664.0", + "@aws-sdk/middleware-user-agent": "3.664.0", + "@aws-sdk/region-config-resolver": "3.664.0", + "@aws-sdk/types": "3.664.0", + "@aws-sdk/util-endpoints": "3.664.0", + "@aws-sdk/util-user-agent-browser": "3.664.0", + "@aws-sdk/util-user-agent-node": "3.664.0", + "@smithy/config-resolver": "^3.0.9", + "@smithy/core": "^2.4.7", + "@smithy/fetch-http-handler": "^3.2.9", + "@smithy/hash-node": "^3.0.7", + "@smithy/invalid-dependency": "^3.0.7", + "@smithy/middleware-content-length": "^3.0.9", + "@smithy/middleware-endpoint": "^3.1.4", + "@smithy/middleware-retry": "^3.0.22", + "@smithy/middleware-serde": "^3.0.7", + "@smithy/middleware-stack": "^3.0.7", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/node-http-handler": "^3.2.4", + "@smithy/protocol-http": "^4.1.4", + "@smithy/smithy-client": "^3.3.6", + "@smithy/types": "^3.5.0", + "@smithy/url-parser": "^3.0.7", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.22", + "@smithy/util-defaults-mode-node": "^3.0.22", + "@smithy/util-endpoints": "^2.1.3", + "@smithy/util-middleware": "^3.0.7", + "@smithy/util-retry": "^3.0.7", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.665.0" + } + }, + "node_modules/@aws-sdk/client-elasticache/node_modules/@aws-sdk/client-sts": { + "version": "3.665.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.665.0.tgz", + "integrity": "sha512-/OQEaWB1euXhZ/hV+wetDw1tynlrkNKzirzoiFuJ1EQsiIb9Ih/qjUF9KLdF1+/bXbnGu5YvIaAx80YReUchjg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.665.0", + "@aws-sdk/core": "3.665.0", + "@aws-sdk/credential-provider-node": "3.665.0", + "@aws-sdk/middleware-host-header": "3.664.0", + "@aws-sdk/middleware-logger": "3.664.0", + "@aws-sdk/middleware-recursion-detection": "3.664.0", + "@aws-sdk/middleware-user-agent": "3.664.0", + "@aws-sdk/region-config-resolver": "3.664.0", + "@aws-sdk/types": "3.664.0", + "@aws-sdk/util-endpoints": "3.664.0", + "@aws-sdk/util-user-agent-browser": "3.664.0", + "@aws-sdk/util-user-agent-node": "3.664.0", + "@smithy/config-resolver": "^3.0.9", + "@smithy/core": "^2.4.7", + "@smithy/fetch-http-handler": "^3.2.9", + "@smithy/hash-node": "^3.0.7", + "@smithy/invalid-dependency": "^3.0.7", + "@smithy/middleware-content-length": "^3.0.9", + "@smithy/middleware-endpoint": "^3.1.4", + "@smithy/middleware-retry": "^3.0.22", + "@smithy/middleware-serde": "^3.0.7", + "@smithy/middleware-stack": "^3.0.7", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/node-http-handler": "^3.2.4", + "@smithy/protocol-http": "^4.1.4", + "@smithy/smithy-client": "^3.3.6", + "@smithy/types": "^3.5.0", + "@smithy/url-parser": "^3.0.7", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.22", + "@smithy/util-defaults-mode-node": "^3.0.22", + "@smithy/util-endpoints": "^2.1.3", + "@smithy/util-middleware": "^3.0.7", + "@smithy/util-retry": "^3.0.7", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-elasticache/node_modules/@aws-sdk/core": { + "version": "3.665.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.665.0.tgz", + "integrity": "sha512-nqmNNf7Ml7qDXTIisDv+OYe/rl3nAW4cmR+HxrOCWdhTHe8xRdR5c45VPoh8nv1KIry5xtd+iqPrzzjydes+Og==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.664.0", + "@smithy/core": "^2.4.7", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/property-provider": "^3.1.7", + "@smithy/protocol-http": "^4.1.4", + "@smithy/signature-v4": "^4.2.0", + "@smithy/smithy-client": "^3.3.6", + "@smithy/types": "^3.5.0", + "@smithy/util-middleware": "^3.0.7", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-elasticache/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.664.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.664.0.tgz", + "integrity": "sha512-95rE+9Voaco0nmKJrXqfJAxSSkSWqlBy76zomiZrUrv7YuijQtHCW8jte6v6UHAFAaBzgFsY7QqBxs15u9SM7g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.664.0", + "@smithy/property-provider": "^3.1.7", + "@smithy/types": "^3.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-elasticache/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.664.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.664.0.tgz", + "integrity": "sha512-svaPwVfWV3g/qjd4cYHTUyBtkdOwcVjC+tSj6EjoMrpZwGUXcCbYe04iU0ARZ6tuH/u3vySbTLOGjSa7g8o9Qw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.664.0", + "@smithy/fetch-http-handler": "^3.2.9", + "@smithy/node-http-handler": "^3.2.4", + "@smithy/property-provider": "^3.1.7", + "@smithy/protocol-http": "^4.1.4", + "@smithy/smithy-client": "^3.3.6", + "@smithy/types": "^3.5.0", + "@smithy/util-stream": "^3.1.9", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-elasticache/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.665.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.665.0.tgz", + "integrity": "sha512-CSWBV5GqCkK78TTXq6qx40MWCt90t8rS/O7FIR4nbmoUhG/DysaC1G0om1fSx6k+GWcvIIIsSvD4hdbh8FRWKA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.664.0", + "@aws-sdk/credential-provider-http": "3.664.0", + "@aws-sdk/credential-provider-process": "3.664.0", + "@aws-sdk/credential-provider-sso": "3.665.0", + "@aws-sdk/credential-provider-web-identity": "3.664.0", + "@aws-sdk/types": "3.664.0", + "@smithy/credential-provider-imds": "^3.2.4", + "@smithy/property-provider": "^3.1.7", + "@smithy/shared-ini-file-loader": "^3.1.8", + "@smithy/types": "^3.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.665.0" + } + }, + "node_modules/@aws-sdk/client-elasticache/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.665.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.665.0.tgz", + "integrity": "sha512-cmJfVi4IM0WaKMQvPXhiS5mdIZyCoa04I3D+IEKpD2GAuVZa6tgwqfPyaApFDLjyedGGNFkC4MRgAjCcCl4WFg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.664.0", + "@aws-sdk/credential-provider-http": "3.664.0", + "@aws-sdk/credential-provider-ini": "3.665.0", + "@aws-sdk/credential-provider-process": "3.664.0", + "@aws-sdk/credential-provider-sso": "3.665.0", + "@aws-sdk/credential-provider-web-identity": "3.664.0", + "@aws-sdk/types": "3.664.0", + "@smithy/credential-provider-imds": "^3.2.4", + "@smithy/property-provider": "^3.1.7", + "@smithy/shared-ini-file-loader": "^3.1.8", + "@smithy/types": "^3.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-elasticache/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.664.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.664.0.tgz", + "integrity": "sha512-sQicIw/qWTsmMw8EUQNJXdrWV5SXaZc2zGdCQsQxhR6wwNO2/rZ5JmzdcwUADmleBVyPYk3KGLhcofF/qXT2Ng==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.664.0", + "@smithy/property-provider": "^3.1.7", + "@smithy/shared-ini-file-loader": "^3.1.8", + "@smithy/types": "^3.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-elasticache/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.665.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.665.0.tgz", + "integrity": "sha512-Xe8WW4r70bsetGQG3azFeK/gd+Q4OmNiidtRrG64y/V9TIvIqc7Y/yUZNhEgFkpG19o188VmXg/ulnG3E+MvLg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.665.0", + "@aws-sdk/token-providers": "3.664.0", + "@aws-sdk/types": "3.664.0", + "@smithy/property-provider": "^3.1.7", + "@smithy/shared-ini-file-loader": "^3.1.8", + "@smithy/types": "^3.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-elasticache/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.664.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.664.0.tgz", + "integrity": "sha512-10ltP1BfSKRJVXd8Yr5oLbo+VSDskWbps0X3szSsxTk0Dju1xvkz7hoIjylWLvtGbvQ+yb2pmsJYKCudW/4DJg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.664.0", + "@smithy/property-provider": "^3.1.7", + "@smithy/types": "^3.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.664.0" + } + }, + "node_modules/@aws-sdk/client-elasticache/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.664.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.664.0.tgz", + "integrity": "sha512-4tCXJ+DZWTq38eLmFgnEmO8X4jfWpgPbWoCyVYpRHCPHq6xbrU65gfwS9jGx25L4YdEce641ChI9TKLryuUgRA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.664.0", + "@smithy/protocol-http": "^4.1.4", + "@smithy/types": "^3.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-elasticache/node_modules/@aws-sdk/middleware-logger": { + "version": "3.664.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.664.0.tgz", + "integrity": "sha512-eNykMqQuv7eg9pAcaLro44fscIe1VkFfhm+gYnlxd+PH6xqapRki1E68VHehnIptnVBdqnWfEqLUSLGm9suqhg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.664.0", + "@smithy/types": "^3.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-elasticache/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.664.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.664.0.tgz", + "integrity": "sha512-jq27WMZhm+dY8BWZ9Ipy3eXtZj0lJzpaKQE3A3tH5AOIlUV/gqrmnJ9CdqVVef4EJsq9Yil4ZzQjKKmPsxveQg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.664.0", + "@smithy/protocol-http": "^4.1.4", + "@smithy/types": "^3.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-elasticache/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.664.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.664.0.tgz", + "integrity": "sha512-Kp5UwXwayO6d472nntiwgrxqay2KS9ozXNmKjQfDrUWbEzvgKI+jgKNMia8MMnjSxYoBGpQ1B8NGh8a6KMEJJg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.664.0", + "@aws-sdk/util-endpoints": "3.664.0", + "@smithy/core": "^2.4.7", + "@smithy/protocol-http": "^4.1.4", + "@smithy/types": "^3.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-elasticache/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.664.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.664.0.tgz", + "integrity": "sha512-o/B8dg8K+9714RGYPgMxZgAChPe/MTSMkf/eHXTUFHNik5i1HgVKfac22njV2iictGy/6GhpFsKa1OWNYAkcUg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.664.0", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/types": "^3.5.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.7", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-elasticache/node_modules/@aws-sdk/token-providers": { + "version": "3.664.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.664.0.tgz", + "integrity": "sha512-dBAvXW2/6bAxidvKARFxyCY2uCynYBKRFN00NhS1T5ggxm3sUnuTpWw1DTjl02CVPkacBOocZf10h8pQbHSK8w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.664.0", + "@smithy/property-provider": "^3.1.7", + "@smithy/shared-ini-file-loader": "^3.1.8", + "@smithy/types": "^3.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "^3.664.0" + } + }, + "node_modules/@aws-sdk/client-elasticache/node_modules/@aws-sdk/types": { + "version": "3.664.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.664.0.tgz", + "integrity": "sha512-+GtXktvVgpreM2b+NJL9OqZGsOzHwlCUrO8jgQUvH/yA6Kd8QO2YFhQCp0C9sSzTteZJVqGBu8E0CQurxJHPbw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^3.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-elasticache/node_modules/@aws-sdk/util-endpoints": { + "version": "3.664.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.664.0.tgz", + "integrity": "sha512-KrXoHz6zmAahVHkyWMRT+P6xJaxItgmklxEDrT+npsUB4d5C/lhw16Crcp9TDi828fiZK3GYKRAmmNhvmzvBNg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.664.0", + "@smithy/types": "^3.5.0", + "@smithy/util-endpoints": "^2.1.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-elasticache/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.664.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.664.0.tgz", + "integrity": "sha512-c/PV3+f1ss4PpskHbcOxTZ6fntV2oXy/xcDR9nW+kVaz5cM1G702gF0rvGLKPqoBwkj2rWGe6KZvEBeLzynTUQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.664.0", + "@smithy/types": "^3.5.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-elasticache/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.664.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.664.0.tgz", + "integrity": "sha512-l/m6KkgrTw1p/VTJTk0IoP9I2OnpWp3WbBgzxoNeh9cUcxTufIn++sBxKj5hhDql57LKWsckScG/MhFuH0vZZA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.664.0", + "@aws-sdk/types": "3.664.0", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/types": "^3.5.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, "node_modules/@aws-sdk/client-secrets-manager": { "version": "3.654.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.654.0.tgz", @@ -1397,6 +1923,12 @@ "node": ">=6" } }, + "node_modules/@ioredis/commands": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", + "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==", + "license": "MIT" + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1823,12 +2355,12 @@ } }, "node_modules/@smithy/abort-controller": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.4.tgz", - "integrity": "sha512-VupaALAQlXViW3/enTf/f5l5JZYSAxoJL7f0nanhNNKnww6DGCg1oYIuNP78KDugnkwthBO6iEcym16HhWV8RQ==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.5.tgz", + "integrity": "sha512-DhNPnqTqPoG8aZ5dWkFOgsuY+i0GQ3CI6hMmvCoduNsnU9gUZWZBwGfDQsTTB7NvFPkom1df7jMIJWU90kuXXg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.4.2", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { @@ -1836,15 +2368,15 @@ } }, "node_modules/@smithy/config-resolver": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.8.tgz", - "integrity": "sha512-Tv1obAC18XOd2OnDAjSWmmthzx6Pdeh63FbLin8MlPiuJ2ATpKkq0NcNOJFr0dO+JmZXnwu8FQxKJ3TKJ3Hulw==", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.9.tgz", + "integrity": "sha512-5d9oBf40qC7n2xUoHmntKLdqsyTMMo/r49+eqSIjJ73eDfEtljAxEhzIQ3bkgXJtR3xiv7YzMT/3FF3ORkjWdg==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^3.1.7", - "@smithy/types": "^3.4.2", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/types": "^3.5.0", "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.6", + "@smithy/util-middleware": "^3.0.7", "tslib": "^2.6.2" }, "engines": { @@ -1852,19 +2384,19 @@ } }, "node_modules/@smithy/core": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.4.6.tgz", - "integrity": "sha512-6lQQp99hnyuNNIzeTYSzCUXJHwvvFLY7hfdFGSJM95tjRDJGfzWYFRBXPaM9766LiiTsQ561KErtbufzUFSYUg==", + "version": "2.4.8", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.4.8.tgz", + "integrity": "sha512-x4qWk7p/a4dcf7Vxb2MODIf4OIcqNbK182WxRvZ/3oKPrf/6Fdic5sSElhO1UtXpWKBazWfqg0ZEK9xN1DsuHA==", "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-endpoint": "^3.1.3", - "@smithy/middleware-retry": "^3.0.21", - "@smithy/middleware-serde": "^3.0.6", - "@smithy/protocol-http": "^4.1.3", - "@smithy/smithy-client": "^3.3.5", - "@smithy/types": "^3.4.2", + "@smithy/middleware-endpoint": "^3.1.4", + "@smithy/middleware-retry": "^3.0.23", + "@smithy/middleware-serde": "^3.0.7", + "@smithy/protocol-http": "^4.1.4", + "@smithy/smithy-client": "^3.4.0", + "@smithy/types": "^3.5.0", "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-middleware": "^3.0.6", + "@smithy/util-middleware": "^3.0.7", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, @@ -1873,15 +2405,15 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.3.tgz", - "integrity": "sha512-VoxMzSzdvkkjMJNE38yQgx4CfnmT+Z+5EUXkg4x7yag93eQkVQgZvN3XBSHC/ylfBbLbAtdu7flTCChX9I+mVg==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.4.tgz", + "integrity": "sha512-S9bb0EIokfYEuar4kEbLta+ivlKCWOCFsLZuilkNy9i0uEUEHSi47IFLPaxqqCl+0ftKmcOTHayY5nQhAuq7+w==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^3.1.7", - "@smithy/property-provider": "^3.1.6", - "@smithy/types": "^3.4.2", - "@smithy/url-parser": "^3.0.6", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/property-provider": "^3.1.7", + "@smithy/types": "^3.5.0", + "@smithy/url-parser": "^3.0.7", "tslib": "^2.6.2" }, "engines": { @@ -1889,25 +2421,25 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "3.2.8", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.8.tgz", - "integrity": "sha512-Lqe0B8F5RM7zkw//6avq1SJ8AfaRd3ubFUS1eVp5WszV7p6Ne5hQ4dSuMHDpNRPhgTvj4va9Kd/pcVigHEHRow==", + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.9.tgz", + "integrity": "sha512-hYNVQOqhFQ6vOpenifFME546f0GfJn2OiQ3M0FDmuUu8V/Uiwy2wej7ZXxFBNqdx0R5DZAqWM1l6VRhGz8oE6A==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^4.1.3", - "@smithy/querystring-builder": "^3.0.6", - "@smithy/types": "^3.4.2", + "@smithy/protocol-http": "^4.1.4", + "@smithy/querystring-builder": "^3.0.7", + "@smithy/types": "^3.5.0", "@smithy/util-base64": "^3.0.0", "tslib": "^2.6.2" } }, "node_modules/@smithy/hash-node": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.6.tgz", - "integrity": "sha512-c/FHEdKK/7DU2z6ZE91L36ahyXWayR3B+FzELjnYq7wH5YqIseM24V+pWCS9kFn1Ln8OFGTf+pyYPiHZuX0s/Q==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.7.tgz", + "integrity": "sha512-SAGHN+QkrwcHFjfWzs/czX94ZEjPJ0CrWJS3M43WswDXVEuP4AVy9gJ3+AF6JQHZD13bojmuf/Ap/ItDeZ+Qfw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.4.2", + "@smithy/types": "^3.5.0", "@smithy/util-buffer-from": "^3.0.0", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" @@ -1917,12 +2449,12 @@ } }, "node_modules/@smithy/invalid-dependency": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.6.tgz", - "integrity": "sha512-czM7Ioq3s8pIXht7oD+vmgy4Wfb4XavU/k/irO8NdXFFOx7YAlsCCcKOh/lJD1mJSYQqiR7NmpZ9JviryD/7AQ==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.7.tgz", + "integrity": "sha512-Bq00GsAhHeYSuZX8Kpu4sbI9agH2BNYnqUmmbTGWOhki9NVsWn2jFr896vvoTMH8KAjNX/ErC/8t5QHuEXG+IA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.4.2", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" } }, @@ -1938,13 +2470,13 @@ } }, "node_modules/@smithy/middleware-content-length": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.8.tgz", - "integrity": "sha512-VuyszlSO49WKh3H9/kIO2kf07VUwGV80QRiaDxUfP8P8UKlokz381ETJvwLhwuypBYhLymCYyNhB3fLAGBX2og==", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.9.tgz", + "integrity": "sha512-t97PidoGElF9hTtLCrof32wfWMqC5g2SEJNxaVH3NjlatuNGsdxXRYO/t+RPnxA15RpYiS0f+zG7FuE2DeGgjA==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^4.1.3", - "@smithy/types": "^3.4.2", + "@smithy/protocol-http": "^4.1.4", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { @@ -1952,17 +2484,17 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.3.tgz", - "integrity": "sha512-KeM/OrK8MVFUsoJsmCN0MZMVPjKKLudn13xpgwIMpGTYpA8QZB2Xq5tJ+RE6iu3A6NhOI4VajDTwBsm8pwwrhg==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.4.tgz", + "integrity": "sha512-/ChcVHekAyzUbyPRI8CzPPLj6y8QRAfJngWcLMgsWxKVzw/RzBV69mSOzJYDD3pRwushA1+5tHtPF8fjmzBnrQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-serde": "^3.0.6", - "@smithy/node-config-provider": "^3.1.7", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", - "@smithy/url-parser": "^3.0.6", - "@smithy/util-middleware": "^3.0.6", + "@smithy/middleware-serde": "^3.0.7", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/shared-ini-file-loader": "^3.1.8", + "@smithy/types": "^3.5.0", + "@smithy/url-parser": "^3.0.7", + "@smithy/util-middleware": "^3.0.7", "tslib": "^2.6.2" }, "engines": { @@ -1970,18 +2502,18 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "3.0.21", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.21.tgz", - "integrity": "sha512-/h0fElV95LekVVEJuSw+aI11S1Y3zIUwBc6h9ZbUv43Gl2weXsbQwjLoet6j/Qtb0phfrSxS6pNg6FqgJOWZkA==", + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.23.tgz", + "integrity": "sha512-x9PbGXxkcXIpm6L26qRSCC+eaYcHwybRmqU8LO/WM2RRlW0g8lz6FIiKbKgGvHuoK3dLZRiQVSQJveiCzwnA5A==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^3.1.7", - "@smithy/protocol-http": "^4.1.3", - "@smithy/service-error-classification": "^3.0.6", - "@smithy/smithy-client": "^3.3.5", - "@smithy/types": "^3.4.2", - "@smithy/util-middleware": "^3.0.6", - "@smithy/util-retry": "^3.0.6", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/protocol-http": "^4.1.4", + "@smithy/service-error-classification": "^3.0.7", + "@smithy/smithy-client": "^3.4.0", + "@smithy/types": "^3.5.0", + "@smithy/util-middleware": "^3.0.7", + "@smithy/util-retry": "^3.0.7", "tslib": "^2.6.2", "uuid": "^9.0.1" }, @@ -2003,12 +2535,12 @@ } }, "node_modules/@smithy/middleware-serde": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.6.tgz", - "integrity": "sha512-KKTUSl1MzOM0MAjGbudeaVNtIDo+PpekTBkCNwvfZlKndodrnvRo+00USatiyLOc0ujjO9UydMRu3O9dYML7ag==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.7.tgz", + "integrity": "sha512-VytaagsQqtH2OugzVTq4qvjkLNbWehHfGcGr0JLJmlDRrNCeZoWkWsSOw1nhS/4hyUUWF/TLGGml4X/OnEep5g==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.4.2", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { @@ -2016,12 +2548,12 @@ } }, "node_modules/@smithy/middleware-stack": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.6.tgz", - "integrity": "sha512-2c0eSYhTQ8xQqHMcRxLMpadFbTXg6Zla5l0mwNftFCZMQmuhI7EbAJMx6R5eqfuV3YbJ3QGyS3d5uSmrHV8Khg==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.7.tgz", + "integrity": "sha512-EyTbMCdqS1DoeQsO4gI7z2Gzq1MoRFAeS8GkFYIwbedB7Lp5zlLHJdg+56tllIIG5Hnf9ZWX48YKSHlsKvugGA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.4.2", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { @@ -2029,14 +2561,14 @@ } }, "node_modules/@smithy/node-config-provider": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.7.tgz", - "integrity": "sha512-g3mfnC3Oo8pOI0dYuPXLtdW1WGVb3bR2tkV21GNkm0ZvQjLTtamXAwCWt/FCb0HGvKt3gHHmF1XerG0ICfalOg==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.8.tgz", + "integrity": "sha512-E0rU0DglpeJn5ge64mk8wTGEXcQwmpUTY5Zr7IzTpDLmHKiIamINERNZYrPQjg58Ck236sEKSwRSHA4CwshU6Q==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^3.1.6", - "@smithy/shared-ini-file-loader": "^3.1.7", - "@smithy/types": "^3.4.2", + "@smithy/property-provider": "^3.1.7", + "@smithy/shared-ini-file-loader": "^3.1.8", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { @@ -2044,15 +2576,15 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.2.3.tgz", - "integrity": "sha512-/gcm5DJ3k1b1zEInzBGAZC8ntJ+jwrz1NcSIu+9dSXd1FfG0G6QgkDI40tt8/WYUbHtLyo8fEqtm2v29koWo/w==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.2.4.tgz", + "integrity": "sha512-49reY3+JgLMFNm7uTAKBWiKCA6XSvkNp9FqhVmusm2jpVnHORYFeFZ704LShtqWfjZW/nhX+7Iexyb6zQfXYIQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^3.1.4", - "@smithy/protocol-http": "^4.1.3", - "@smithy/querystring-builder": "^3.0.6", - "@smithy/types": "^3.4.2", + "@smithy/abort-controller": "^3.1.5", + "@smithy/protocol-http": "^4.1.4", + "@smithy/querystring-builder": "^3.0.7", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { @@ -2060,12 +2592,12 @@ } }, "node_modules/@smithy/property-provider": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.6.tgz", - "integrity": "sha512-NK3y/T7Q/Bw+Z8vsVs9MYIQ5v7gOX7clyrXcwhhIBQhbPgRl6JDrZbusO9qWDhcEus75Tg+VCxtIRfo3H76fpw==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.7.tgz", + "integrity": "sha512-QfzLi1GPMisY7bAM5hOUqBdGYnY5S2JAlr201pghksrQv139f8iiiMalXtjczIP5f6owxFn3MINLNUNvUkgtPw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.4.2", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { @@ -2073,12 +2605,12 @@ } }, "node_modules/@smithy/protocol-http": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.3.tgz", - "integrity": "sha512-GcbMmOYpH9iRqtC05RbRnc/0FssxSTHlmaNhYBTgSgNCYpdR3Kt88u5GAZTBmouzv+Zlj/VRv92J9ruuDeJuEw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.4.tgz", + "integrity": "sha512-MlWK8eqj0JlpZBnWmjQLqmFp71Ug00P+m72/1xQB3YByXD4zZ+y9N4hYrR0EDmrUCZIkyATWHOXFgtavwGDTzQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.4.2", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { @@ -2086,12 +2618,12 @@ } }, "node_modules/@smithy/querystring-builder": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.6.tgz", - "integrity": "sha512-sQe08RunoObe+Usujn9+R2zrLuQERi3CWvRO3BvnoWSYUaIrLKuAIeY7cMeDax6xGyfIP3x/yFWbEKSXvOnvVg==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.7.tgz", + "integrity": "sha512-65RXGZZ20rzqqxTsChdqSpbhA6tdt5IFNgG6o7e1lnPVLCe6TNWQq4rTl4N87hTDD8mV4IxJJnvyE7brbnRkQw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.4.2", + "@smithy/types": "^3.5.0", "@smithy/util-uri-escape": "^3.0.0", "tslib": "^2.6.2" }, @@ -2100,12 +2632,12 @@ } }, "node_modules/@smithy/querystring-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.6.tgz", - "integrity": "sha512-UJKw4LlEkytzz2Wq+uIdHf6qOtFfee/o7ruH0jF5I6UAuU+19r9QV7nU3P/uI0l6+oElRHmG/5cBBcGJrD7Ozg==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.7.tgz", + "integrity": "sha512-Fouw4KJVWqqUVIu1gZW8BH2HakwLz6dvdrAhXeXfeymOBrZw+hcqaWs+cS1AZPVp4nlbeIujYrKA921ZW2WMPA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.4.2", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { @@ -2113,24 +2645,24 @@ } }, "node_modules/@smithy/service-error-classification": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.6.tgz", - "integrity": "sha512-53SpchU3+DUZrN7J6sBx9tBiCVGzsib2e4sc512Q7K9fpC5zkJKs6Z9s+qbMxSYrkEkle6hnMtrts7XNkMJJMg==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.7.tgz", + "integrity": "sha512-91PRkTfiBf9hxkIchhRKJfl1rsplRDyBnmyFca3y0Z3x/q0JJN480S83LBd8R6sBCkm2bBbqw2FHp0Mbh+ecSA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.4.2" + "@smithy/types": "^3.5.0" }, "engines": { "node": ">=16.0.0" } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.7.tgz", - "integrity": "sha512-IA4K2qTJYXkF5OfVN4vsY1hfnUZjaslEE8Fsr/gGFza4TAC2A9NfnZuSY2srQIbt9bwtjHiAayrRVgKse4Q7fA==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.8.tgz", + "integrity": "sha512-0NHdQiSkeGl0ICQKcJQ2lCOKH23Nb0EaAa7RDRId6ZqwXkw4LJyIyZ0t3iusD4bnKYDPLGy2/5e2rfUhrt0Acw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.4.2", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { @@ -2138,16 +2670,16 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.1.4.tgz", - "integrity": "sha512-72MiK7xYukNsnLJI9NqvUHqTu0ziEsfMsYNlWpiJfuGQnCTFKpckThlEatirvcA/LmT1h7rRO+pJD06PYsPu9Q==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.2.0.tgz", + "integrity": "sha512-LafbclHNKnsorMgUkKm7Tk7oJ7xizsZ1VwqhGKqoCIrXh4fqDDp73fK99HOEEgcsQbtemmeY/BPv0vTVYYUNEQ==", "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^3.0.0", - "@smithy/protocol-http": "^4.1.3", - "@smithy/types": "^3.4.2", + "@smithy/protocol-http": "^4.1.4", + "@smithy/types": "^3.5.0", "@smithy/util-hex-encoding": "^3.0.0", - "@smithy/util-middleware": "^3.0.6", + "@smithy/util-middleware": "^3.0.7", "@smithy/util-uri-escape": "^3.0.0", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" @@ -2157,16 +2689,16 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.3.5.tgz", - "integrity": "sha512-7IZi8J3Dr9n3tX+lcpmJ/5tCYIqoXdblFBaPuv0SEKZFRpCxE+TqIWL6I3t7jLlk9TWu3JSvEZAhtjB9yvB+zA==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.4.0.tgz", + "integrity": "sha512-nOfJ1nVQsxiP6srKt43r2My0Gp5PLWCW2ASqUioxIiGmu6d32v4Nekidiv5qOmmtzIrmaD+ADX5SKHUuhReeBQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-endpoint": "^3.1.3", - "@smithy/middleware-stack": "^3.0.6", - "@smithy/protocol-http": "^4.1.3", - "@smithy/types": "^3.4.2", - "@smithy/util-stream": "^3.1.8", + "@smithy/middleware-endpoint": "^3.1.4", + "@smithy/middleware-stack": "^3.0.7", + "@smithy/protocol-http": "^4.1.4", + "@smithy/types": "^3.5.0", + "@smithy/util-stream": "^3.1.9", "tslib": "^2.6.2" }, "engines": { @@ -2174,9 +2706,9 @@ } }, "node_modules/@smithy/types": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.4.2.tgz", - "integrity": "sha512-tHiFcfcVedVBHpmHUEUHOCCih8iZbIAYn9NvPsNzaPm/237I3imdDdZoOC8c87H5HBAVEa06tTgb+OcSWV9g5w==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.5.0.tgz", + "integrity": "sha512-QN0twHNfe8mNJdH9unwsCK13GURU7oEAZqkBI+rsvpv1jrmserO+WnLE7jidR9W/1dxwZ0u/CB01mV2Gms/K2Q==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -2186,13 +2718,13 @@ } }, "node_modules/@smithy/url-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.6.tgz", - "integrity": "sha512-47Op/NU8Opt49KyGpHtVdnmmJMsp2hEwBdyjuFB9M2V5QVOwA7pBhhxKN5z6ztKGrMw76gd8MlbPuzzvaAncuQ==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.7.tgz", + "integrity": "sha512-70UbSSR8J97c1rHZOWhl+VKiZDqHWxs/iW8ZHrHp5fCCPLSBE7GcUlUvKSle3Ca+J9LLbYCj/A79BxztBvAfpA==", "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^3.0.6", - "@smithy/types": "^3.4.2", + "@smithy/querystring-parser": "^3.0.7", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" } }, @@ -2253,14 +2785,14 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "3.0.21", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.21.tgz", - "integrity": "sha512-M/FhTBk4c/SsB91dD/M4gMGfJO7z/qJaM9+XQQIqBOf4qzZYMExnP7R4VdGwxxH8IKMGW+8F0I4rNtVRrcfPoA==", + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.23.tgz", + "integrity": "sha512-Y07qslyRtXDP/C5aWKqxTPBl4YxplEELG3xRrz2dnAQ6Lq/FgNrcKWmV561nNaZmFH+EzeGOX3ZRMbU8p1T6Nw==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^3.1.6", - "@smithy/smithy-client": "^3.3.5", - "@smithy/types": "^3.4.2", + "@smithy/property-provider": "^3.1.7", + "@smithy/smithy-client": "^3.4.0", + "@smithy/types": "^3.5.0", "bowser": "^2.11.0", "tslib": "^2.6.2" }, @@ -2269,17 +2801,17 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "3.0.21", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.21.tgz", - "integrity": "sha512-NiLinPvF86U3S2Pdx/ycqd4bnY5dmFSPNL5KYRwbNjqQFS09M5Wzqk8BNk61/47xCYz1X/6KeiSk9qgYPTtuDw==", + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.23.tgz", + "integrity": "sha512-9Y4WH7f0vnDGuHUa4lGX9e2p+sMwODibsceSV6rfkZOvMC+BY3StB2LdO1NHafpsyHJLpwAgChxQ38tFyd6vkg==", "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^3.0.8", - "@smithy/credential-provider-imds": "^3.2.3", - "@smithy/node-config-provider": "^3.1.7", - "@smithy/property-provider": "^3.1.6", - "@smithy/smithy-client": "^3.3.5", - "@smithy/types": "^3.4.2", + "@smithy/config-resolver": "^3.0.9", + "@smithy/credential-provider-imds": "^3.2.4", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/property-provider": "^3.1.7", + "@smithy/smithy-client": "^3.4.0", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { @@ -2287,13 +2819,13 @@ } }, "node_modules/@smithy/util-endpoints": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.1.2.tgz", - "integrity": "sha512-FEISzffb4H8DLzGq1g4MuDpcv6CIG15fXoQzDH9SjpRJv6h7J++1STFWWinilG0tQh9H1v2UKWG19Jjr2B16zQ==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.1.3.tgz", + "integrity": "sha512-34eACeKov6jZdHqS5hxBMJ4KyWKztTMulhuQ2UdOoP6vVxMLrOKUqIXAwJe/wiWMhXhydLW664B02CNpQBQ4Aw==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^3.1.7", - "@smithy/types": "^3.4.2", + "@smithy/node-config-provider": "^3.1.8", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { @@ -2313,12 +2845,12 @@ } }, "node_modules/@smithy/util-middleware": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.6.tgz", - "integrity": "sha512-BxbX4aBhI1O9p87/xM+zWy0GzT3CEVcXFPBRDoHAM+pV0eSW156pR+PSYEz0DQHDMYDsYAflC2bQNz2uaDBUZQ==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.7.tgz", + "integrity": "sha512-OVA6fv/3o7TMJTpTgOi1H5OTwnuUa8hzRzhSFDtZyNxi6OZ70L/FHattSmhE212I7b6WSOJAAmbYnvcjTHOJCA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.4.2", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { @@ -2326,13 +2858,13 @@ } }, "node_modules/@smithy/util-retry": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.6.tgz", - "integrity": "sha512-BRZiuF7IwDntAbevqMco67an0Sr9oLQJqqRCsSPZZHYRnehS0LHDAkJk/pSmI7Z8c/1Vet294H7fY2fWUgB+Rg==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.7.tgz", + "integrity": "sha512-nh1ZO1vTeo2YX1plFPSe/OXaHkLAHza5jpokNiiKX2M5YpNUv6RxGJZhpfmiR4jSvVHCjIDmILjrxKmP+/Ghug==", "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^3.0.6", - "@smithy/types": "^3.4.2", + "@smithy/service-error-classification": "^3.0.7", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { @@ -2340,14 +2872,14 @@ } }, "node_modules/@smithy/util-stream": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.1.8.tgz", - "integrity": "sha512-hoKOqSmb8FD3WLObuB5hwbM7bNIWgcnvkThokTvVq7J5PKjlLUK5qQQcB9zWLHIoSaIlf3VIv2OxZY2wtQjcRQ==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.1.9.tgz", + "integrity": "sha512-7YAR0Ub3MwTMjDfjnup4qa6W8gygZMxikBhFMPESi6ASsl/rZJhwLpF/0k9TuezScCojsM0FryGdz4LZtjKPPQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^3.2.8", - "@smithy/node-http-handler": "^3.2.3", - "@smithy/types": "^3.4.2", + "@smithy/fetch-http-handler": "^3.2.9", + "@smithy/node-http-handler": "^3.2.4", + "@smithy/types": "^3.5.0", "@smithy/util-base64": "^3.0.0", "@smithy/util-buffer-from": "^3.0.0", "@smithy/util-hex-encoding": "^3.0.0", @@ -2383,13 +2915,13 @@ } }, "node_modules/@smithy/util-waiter": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.1.5.tgz", - "integrity": "sha512-jYOSvM3H6sZe3CHjzD2VQNCjWBJs+4DbtwBMvUp9y5EnnwNa7NQxTeYeQw0CKCAdGGZ3QvVkyJmvbvs5M/B10A==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.1.6.tgz", + "integrity": "sha512-xs/KAwWOeCklq8aMlnpk25LgxEYHKOEodfjfKclDMLcBJEVEKzDLxZxBQyztcuPJ7F54213NJS8PxoiHNMdItQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^3.1.4", - "@smithy/types": "^3.4.2", + "@smithy/abort-controller": "^3.1.5", + "@smithy/types": "^3.5.0", "tslib": "^2.6.2" }, "engines": { @@ -3701,6 +4233,15 @@ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -5008,6 +5549,30 @@ "node": ">=10" } }, + "node_modules/ioredis": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.4.1.tgz", + "integrity": "sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==", + "license": "MIT", + "dependencies": { + "@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/ip": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", @@ -5357,11 +5922,23 @@ "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" }, + "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==", + "license": "MIT" + }, "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==", + "license": "MIT" + }, "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", @@ -6284,6 +6861,27 @@ "@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==", + "license": "MIT", + "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==", + "license": "MIT", + "dependencies": { + "redis-errors": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/regenerator-runtime": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", @@ -6934,6 +7532,12 @@ "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==", + "license": "MIT" + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", diff --git a/package.json b/package.json index ef95ef36d..b170d0045 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "makeitpretty": "prettier --write \"**/*.{css,js,json,jsx,scss}\"" }, "dependencies": { + "@aws-sdk/client-elasticache": "^3.665.0", "@aws-sdk/client-secrets-manager": "^3.654.0", "@aws-sdk/client-ses": "^3.654.0", "@aws-sdk/credential-provider-node": "^3.654.0", @@ -46,6 +47,7 @@ "graylog2": "^0.2.1", "inline-css": "^4.0.2", "intuit-oauth": "^4.1.2", + "ioredis": "^5.4.1", "json-2-csv": "^5.5.5", "lodash": "^4.17.21", "moment": "^2.30.1", diff --git a/redis/Dockerfile b/redis/Dockerfile new file mode 100644 index 000000000..b68d8fb2b --- /dev/null +++ b/redis/Dockerfile @@ -0,0 +1,20 @@ +# Use the official Redis image +FROM redis:7.0-alpine + +# Copy the Redis configuration file +COPY redis.conf /usr/local/etc/redis/redis.conf + +# Copy the entrypoint script +COPY entrypoint.sh /usr/local/bin/entrypoint.sh + +# Make the entrypoint script executable +RUN chmod +x /usr/local/bin/entrypoint.sh + +# Debugging step: List contents of /usr/local/bin +RUN ls -l /usr/local/bin + +# Expose Redis ports +EXPOSE 6379 16379 + +# Set the entrypoint +ENTRYPOINT ["entrypoint.sh"] diff --git a/redis/entrypoint.sh b/redis/entrypoint.sh new file mode 100644 index 000000000..a5a527e30 --- /dev/null +++ b/redis/entrypoint.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +LOCKFILE="/redis-lock/redis-cluster-init.lock" + +# Start Redis server in the background +redis-server /usr/local/etc/redis/redis.conf & + +# Wait for Redis server to start +sleep 5 + +# Only initialize the cluster if the lock file doesn't exist +if [ ! -f "$LOCKFILE" ]; then + echo "Initializing Redis Cluster..." + + # Create lock file to prevent further initialization attempts + touch "$LOCKFILE" + if [ $? -eq 0 ]; then + echo "Lock file created successfully at $LOCKFILE." + else + echo "Failed to create lock file." + fi + + # Initialize the Redis cluster + yes yes | redis-cli --cluster create \ + redis-node-1:6379 \ + redis-node-2:6379 \ + redis-node-3:6379 \ + --cluster-replicas 0 + + echo "Redis Cluster initialized." +else + echo "Cluster already initialized, skipping initialization." +fi + +# Keep the container running +tail -f /dev/null diff --git a/redis/redis.conf b/redis/redis.conf new file mode 100644 index 000000000..22533e22d --- /dev/null +++ b/redis/redis.conf @@ -0,0 +1,6 @@ +bind 0.0.0.0 +port 6379 +cluster-enabled yes +cluster-config-file nodes.conf +cluster-node-timeout 5000 +appendonly yes diff --git a/server.js b/server.js index 0ccfd9ed3..d84aa907e 100644 --- a/server.js +++ b/server.js @@ -1,26 +1,32 @@ -const express = require("express"); const cors = require("cors"); -const bodyParser = require("body-parser"); const path = require("path"); +const http = require("http"); +const Redis = require("ioredis"); +const express = require("express"); +const bodyParser = require("body-parser"); const compression = require("compression"); const cookieParser = require("cookie-parser"); -const http = require("http"); const { Server } = require("socket.io"); -const { createClient } = require("redis"); const { createAdapter } = require("@socket.io/redis-adapter"); -const logger = require("./server/utils/logger"); -const { redisSocketEvents } = require("./server/web-sockets/redisSocketEvents"); const { instrument } = require("@socket.io/admin-ui"); - const { isString, isEmpty } = require("lodash"); -const applyRedisHelpers = require("./server/utils/redisHelpers"); -const applyIOHelpers = require("./server/utils/ioHelpers"); + +const logger = require("./server/utils/logger"); +const { applyRedisHelpers } = require("./server/utils/redisHelpers"); +const { applyIOHelpers } = require("./server/utils/ioHelpers"); +const { redisSocketEvents } = require("./server/web-sockets/redisSocketEvents"); +const { ElastiCacheClient, DescribeCacheClustersCommand } = require("@aws-sdk/client-elasticache"); +const { default: InstanceManager } = require("./server/utils/instanceMgr"); // Load environment variables require("dotenv").config({ path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) }); +const CLUSTER_RETRY_BASE_DELAY = 100; +const CLUSTER_RETRY_MAX_DELAY = 5000; +const CLUSTER_RETRY_JITTER = 100; + /** * CORS Origin for Socket.IO * @type {string[][]} @@ -49,16 +55,16 @@ const SOCKETIO_CORS_ORIGIN = [ "https://old.imex.online", "https://www.old.imex.online", "https://wsadmin.imex.online", - "https://www.wsadmin.imex.online", - "http://localhost:3333", - "https://localhost:3333" + "https://www.wsadmin.imex.online" ]; +const SOCKETIO_CORS_ORIGIN_DEV = ["http://localhost:3333", "https://localhost:3333"]; + /** * Middleware for Express app * @param app */ -const applyMiddleware = (app) => { +const applyMiddleware = ({ app }) => { app.use(compression()); app.use(cookieParser()); app.use(bodyParser.json({ limit: "50mb" })); @@ -76,7 +82,7 @@ const applyMiddleware = (app) => { * Route groupings for Express app * @param app */ -const applyRoutes = (app) => { +const applyRoutes = ({ app }) => { app.use("/", require("./server/routes/miscellaneousRoutes")); app.use("/notifications", require("./server/routes/notificationsRoutes")); app.use("/render", require("./server/routes/renderRoutes")); @@ -102,37 +108,140 @@ const applyRoutes = (app) => { }); }; +/** + * Fetch Redis nodes from AWS ElastiCache + * @returns {Promise} + */ +const getRedisNodesFromAWS = async () => { + const client = new ElastiCacheClient({ + region: InstanceManager({ + imex: "ca-central-1", + rome: "us-east-2" + }) + }); + + const params = { + ReplicationGroupId: process.env.REDIS_CLUSTER_ID, + ShowCacheNodeInfo: true + }; + + try { + // Fetch the cache clusters associated with the replication group + const command = new DescribeCacheClustersCommand(params); + const response = await client.send(command); + const cacheClusters = response.CacheClusters; + + return cacheClusters.flatMap((cluster) => + cluster.CacheNodes.map((node) => `${node.Endpoint.Address}:${node.Endpoint.Port}`) + ); + } catch (err) { + logger.log(`Error fetching Redis nodes from AWS: ${err.message}`, "ERROR", "redis", "api"); + throw err; + } +}; + +/** + * Connect to Redis Cluster + * @returns {Promise} + */ +const connectToRedisCluster = async () => { + let redisServers; + + if (isString(process.env?.REDIS_CLUSTER_ID) && !isEmpty(process.env?.REDIS_CLUSTER_ID)) { + // Fetch Redis nodes from AWS if AWS environment variables are present + redisServers = await getRedisNodesFromAWS(); + } else { + // Use the Dockerized Redis cluster in development + if (isEmpty(process.env?.REDIS_URL) || !isString(process.env?.REDIS_URL)) { + logger.log(`[${process.env.NODE_ENV}] No or Malformed REDIS_URL present.`, "ERROR", "redis", "api"); + process.exit(1); + } + try { + redisServers = JSON.parse(process.env.REDIS_URL); + } catch (error) { + logger.log( + `[${process.env.NODE_ENV}] Failed to parse REDIS_URL: ${error.message}. Exiting...`, + "ERROR", + "redis", + "api" + ); + process.exit(1); + } + } + + const clusterRetryStrategy = (times) => { + const delay = + Math.min(CLUSTER_RETRY_BASE_DELAY + times * 50, CLUSTER_RETRY_MAX_DELAY) + Math.random() * CLUSTER_RETRY_JITTER; + logger.log( + `[${process.env.NODE_ENV}] Redis cluster not yet ready. Retrying in ${delay.toFixed(2)}ms`, + "ERROR", + "redis", + "api" + ); + return delay; + }; + + const redisCluster = new Redis.Cluster(redisServers, { + clusterRetryStrategy, + enableAutoPipelining: true, + enableReadyCheck: true, + redisOptions: { + // connectTimeout: 10000, // Timeout for connecting in ms + // idleTimeoutMillis: 30000, // Close idle connections after 30s + // maxRetriesPerRequest: 5 // Retry a maximum of 5 times per request + } + }); + + return new Promise((resolve, reject) => { + redisCluster.on("ready", () => { + logger.log(`[${process.env.NODE_ENV}] Redis cluster connection established.`, "INFO", "redis", "api"); + resolve(redisCluster); + }); + + redisCluster.on("error", (err) => { + logger.log(`[${process.env.NODE_ENV}] Redis cluster connection failed: ${err.message}`, "ERROR", "redis", "api"); + reject(err); + }); + }); +}; + /** * Apply Redis to the server * @param server * @param app */ -const applySocketIO = async (server, app) => { - // Redis client setup for Pub/Sub and Key-Value Store - const pubClient = createClient({ url: process.env.REDIS_URL || "redis://localhost:6379" }); +const applySocketIO = async ({ server, app }) => { + const redisCluster = await connectToRedisCluster(); + + // Handle errors + redisCluster.on("error", (err) => { + logger.log(`[${process.env.NODE_ENV}] Redis ERROR`, "ERROR", "redis", "api"); + }); + + const pubClient = redisCluster; const subClient = pubClient.duplicate(); pubClient.on("error", (err) => logger.log(`Redis pubClient error: ${err}`, "ERROR", "redis")); subClient.on("error", (err) => logger.log(`Redis subClient error: ${err}`, "ERROR", "redis")); - try { - await Promise.all([pubClient.connect(), subClient.connect()]); - logger.log(`[${process.env.NODE_ENV}] Connected to Redis`, "INFO", "redis", "api"); - } catch (redisError) { - logger.log("Failed to connect to Redis", "ERROR", "redis", redisError); - } - process.on("SIGINT", async () => { logger.log("Closing Redis connections...", "INFO", "redis", "api"); - await Promise.all([pubClient.disconnect(), subClient.disconnect()]); - process.exit(0); + try { + await Promise.all([pubClient.disconnect(), subClient.disconnect()]); + logger.log("Redis connections closed. Process will exit.", "INFO", "redis", "api"); + } catch (error) { + logger.log(`Error closing Redis connections: ${error.message}`, "ERROR", "redis", "api"); + } }); const ioRedis = new Server(server, { path: "/wss", adapter: createAdapter(pubClient, subClient), cors: { - origin: SOCKETIO_CORS_ORIGIN, + origin: + process.env?.NODE_ENV === "development" + ? [...SOCKETIO_CORS_ORIGIN, ...SOCKETIO_CORS_ORIGIN_DEV] + : SOCKETIO_CORS_ORIGIN, methods: ["GET", "POST"], credentials: true, exposedHeaders: ["set-cookie"] @@ -147,6 +256,7 @@ const applySocketIO = async (server, app) => { username: "admin", password: process.env.REDIS_ADMIN_PASS }, + mode: process.env.REDIS_ADMIN_MODE || "development" }); } @@ -161,19 +271,22 @@ const applySocketIO = async (server, app) => { } }); + const api = { + pubClient, + io, + ioRedis, + redisCluster + }; + app.use((req, res, next) => { - Object.assign(req, { - pubClient, - io, - ioRedis - }); + Object.assign(req, api); next(); }); - Object.assign(module.exports, { io, pubClient, ioRedis }); + Object.assign(module.exports, api); - return { pubClient, io, ioRedis }; + return api; }; /** @@ -186,16 +299,16 @@ const main = async () => { const server = http.createServer(app); - const { pubClient, ioRedis } = await applySocketIO(server, app); - const api = applyRedisHelpers(pubClient, app, logger); - const ioHelpers = applyIOHelpers(app, api, ioRedis, logger); + const { pubClient, ioRedis } = await applySocketIO({ server, app }); + const redisHelpers = applyRedisHelpers({ pubClient, app, logger }); + const ioHelpers = applyIOHelpers({ app, redisHelpers, ioRedis, logger }); // Legacy Socket Events require("./server/web-sockets/web-socket"); - applyMiddleware(app); - applyRoutes(app); - redisSocketEvents(ioRedis, api, ioHelpers); + applyMiddleware({ app }); + applyRoutes({ app }); + redisSocketEvents({ io: ioRedis, redisHelpers, ioHelpers, logger }); try { await server.listen(port); diff --git a/server/email/sendemail.js b/server/email/sendemail.js index 5dea4a075..f1586dbec 100644 --- a/server/email/sendemail.js +++ b/server/email/sendemail.js @@ -18,7 +18,7 @@ const ses = new aws.SES({ // The key apiVersion is no longer supported in v3, and can be removed. // @deprecated The client uses the "latest" apiVersion. apiVersion: "latest", - defaultProvider, + credentials: defaultProvider(), region: InstanceManager({ imex: "ca-central-1", rome: "us-east-2" @@ -96,7 +96,7 @@ const sendServerEmail = async ({ subject, text }) => { } }; -const sendProManagerWelcomeEmail = async ({to, subject, html}) => { +const sendProManagerWelcomeEmail = async ({ to, subject, html }) => { try { await transporter.sendMail({ from: `ProManager `, diff --git a/server/email/tasksEmails.js b/server/email/tasksEmails.js index 1f508be31..50ae88be7 100644 --- a/server/email/tasksEmails.js +++ b/server/email/tasksEmails.js @@ -15,7 +15,7 @@ const { taskEmailQueue } = require("./tasksEmailsQueue"); const ses = new aws.SES({ apiVersion: "latest", - defaultProvider, + credentials: defaultProvider(), region: InstanceManager({ imex: "ca-central-1", rome: "us-east-2" @@ -45,22 +45,20 @@ if (process.env.NODE_ENV !== "development") { // Handling SIGINT (e.g., Ctrl+C) process.on("SIGINT", async () => { await tasksEmailQueueCleanup(); - process.exit(0); }); // Handling SIGTERM (e.g., sent by system shutdown) process.on("SIGTERM", async () => { await tasksEmailQueueCleanup(); - process.exit(0); }); // Handling uncaught exceptions process.on("uncaughtException", async (err) => { await tasksEmailQueueCleanup(); - process.exit(1); // Exit with an 'error' code + throw err; }); // Handling unhandled promise rejections process.on("unhandledRejection", async (reason, promise) => { await tasksEmailQueueCleanup(); - process.exit(1); // Exit with an 'error' code + throw reason; }); } @@ -247,7 +245,7 @@ const tasksRemindEmail = async (req, res) => { const fromEmails = InstanceManager({ imex: "ImEX Online ", rome: - tasksRequest?.tasks[0].bodyshop.convenient_company === "promanager" + tasksRequest?.tasks[0].bodyshop.convenient_company === "promanager" ? "ProManager " : "Rome Online " }); @@ -283,7 +281,7 @@ const tasksRemindEmail = async (req, res) => { const endPoints = InstanceManager({ imex: process.env?.NODE_ENV === "test" ? "https://test.imex.online" : "https://imex.online", rome: - tasksRequest?.tasks[0].bodyshop.convenient_company === "promanager" + tasksRequest?.tasks[0].bodyshop.convenient_company === "promanager" ? process.env?.NODE_ENV === "test" ? "https//test.promanager.web-est.com" : "https://promanager.web-est.com" diff --git a/server/utils/ioHelpers.js b/server/utils/ioHelpers.js index 2a68925f9..3b3b15adb 100644 --- a/server/utils/ioHelpers.js +++ b/server/utils/ioHelpers.js @@ -1,4 +1,4 @@ -const applyIOHelpers = (app, api, io, logger) => { +const applyIOHelpers = ({ app, api, io, logger }) => { const getBodyshopRoom = (bodyshopID) => `bodyshop-broadcast-room:${bodyshopID}`; const ioHelpersAPI = { @@ -14,4 +14,4 @@ const applyIOHelpers = (app, api, io, logger) => { return ioHelpersAPI; }; -module.exports = applyIOHelpers; +module.exports = { applyIOHelpers }; diff --git a/server/utils/redisHelpers.js b/server/utils/redisHelpers.js index 99801000b..54d68773d 100644 --- a/server/utils/redisHelpers.js +++ b/server/utils/redisHelpers.js @@ -1,14 +1,14 @@ -const logger = require("./logger"); /** * Apply Redis helper functions * @param pubClient * @param app + * @param logger */ -const applyRedisHelpers = (pubClient, app, logger) => { +const applyRedisHelpers = ({ pubClient, app, logger }) => { // Store session data in Redis const setSessionData = async (socketId, key, value) => { try { - await pubClient.hSet(`socket:${socketId}`, key, JSON.stringify(value)); // Use Redis pubClient + await pubClient.hset(`socket:${socketId}`, key, JSON.stringify(value)); // Use Redis pubClient } catch (error) { logger.log(`Error Setting Session Data for socket ${socketId}: ${error}`, "ERROR", "redis"); } @@ -17,7 +17,7 @@ const applyRedisHelpers = (pubClient, app, logger) => { // Retrieve session data from Redis const getSessionData = async (socketId, key) => { try { - const data = await pubClient.hGet(`socket:${socketId}`, key); + const data = await pubClient.hget(`socket:${socketId}`, key); return data ? JSON.parse(data) : null; } catch (error) { logger.log(`Error Getting Session Data for socket ${socketId}: ${error}`, "ERROR", "redis"); @@ -38,7 +38,7 @@ const applyRedisHelpers = (pubClient, app, logger) => { try { // keyValues is expected to be an object { key1: value1, key2: value2, ... } const entries = Object.entries(keyValues).map(([key, value]) => [key, JSON.stringify(value)]); - await pubClient.hSet(`socket:${socketId}`, ...entries.flat()); + await pubClient.hset(`socket:${socketId}`, ...entries.flat()); } catch (error) { logger.log(`Error Setting Multiple Session Data for socket ${socketId}: ${error}`, "ERROR", "redis"); } @@ -47,7 +47,7 @@ const applyRedisHelpers = (pubClient, app, logger) => { // Retrieve multiple session data from Redis const getMultipleSessionData = async (socketId, keys) => { try { - const data = await pubClient.hmGet(`socket:${socketId}`, keys); + const data = await pubClient.hmget(`socket:${socketId}`, keys); // Redis returns an object with null values for missing keys, so we parse the non-null ones return Object.fromEntries(keys.map((key, index) => [key, data[index] ? JSON.parse(data[index]) : null])); } catch (error) { @@ -60,7 +60,7 @@ const applyRedisHelpers = (pubClient, app, logger) => { // Use Redis multi/pipeline to batch the commands const multi = pubClient.multi(); keyValueArray.forEach(([key, value]) => { - multi.hSet(`socket:${socketId}`, key, JSON.stringify(value)); + multi.hset(`socket:${socketId}`, key, JSON.stringify(value)); }); await multi.exec(); // Execute all queued commands } catch (error) { @@ -71,7 +71,7 @@ const applyRedisHelpers = (pubClient, app, logger) => { // Helper function to add an item to the end of the Redis list const addItemToEndOfList = async (socketId, key, newItem) => { try { - await pubClient.rPush(`socket:${socketId}:${key}`, JSON.stringify(newItem)); + await pubClient.rpush(`socket:${socketId}:${key}`, JSON.stringify(newItem)); } catch (error) { logger.log(`Error adding item to the end of the list for socket ${socketId}: ${error}`, "ERROR", "redis"); } @@ -80,7 +80,7 @@ const applyRedisHelpers = (pubClient, app, logger) => { // Helper function to add an item to the beginning of the Redis list const addItemToBeginningOfList = async (socketId, key, newItem) => { try { - await pubClient.lPush(`socket:${socketId}:${key}`, JSON.stringify(newItem)); + await pubClient.lpush(`socket:${socketId}:${key}`, JSON.stringify(newItem)); } catch (error) { logger.log(`Error adding item to the beginning of the list for socket ${socketId}: ${error}`, "ERROR", "redis"); } @@ -98,7 +98,7 @@ const applyRedisHelpers = (pubClient, app, logger) => { // Add methods to manage room users const addUserToRoom = async (room, user) => { try { - await pubClient.sAdd(room, JSON.stringify(user)); + await pubClient.sadd(room, JSON.stringify(user)); } catch (error) { logger.log(`Error adding user to room ${room}: ${error}`, "ERROR", "redis"); } @@ -106,7 +106,7 @@ const applyRedisHelpers = (pubClient, app, logger) => { const removeUserFromRoom = async (room, user) => { try { - await pubClient.sRem(room, JSON.stringify(user)); + await pubClient.srem(room, JSON.stringify(user)); } catch (error) { logger.log(`Error removing user to room ${room}: ${error}`, "ERROR", "redis"); } @@ -114,7 +114,7 @@ const applyRedisHelpers = (pubClient, app, logger) => { const getUsersInRoom = async (room) => { try { - const users = await pubClient.sMembers(room); + const users = await pubClient.smembers(room); return users.map((user) => JSON.parse(user)); } catch (error) { logger.log(`Error getting users in room ${room}: ${error}`, "ERROR", "redis"); @@ -143,70 +143,87 @@ const applyRedisHelpers = (pubClient, app, logger) => { next(); }); - // // Demo to show how all the helper functions work + // Demo to show how all the helper functions work // const demoSessionData = async () => { // const socketId = "testSocketId"; // - // // Store session data using setSessionData - // await exports.setSessionData(socketId, "field1", "Hello, Redis!"); - // - // // Retrieve session data using getSessionData - // const field1Value = await exports.getSessionData(socketId, "field1"); + // // 1. Test setSessionData and getSessionData + // await setSessionData(socketId, "field1", "Hello, Redis!"); + // const field1Value = await getSessionData(socketId, "field1"); // console.log("Retrieved single field value:", field1Value); // - // // Store multiple session data using setMultipleSessionData - // await exports.setMultipleSessionData(socketId, { field2: "Second Value", field3: "Third Value" }); - // - // // Retrieve multiple session data using getMultipleSessionData - // const multipleFields = await exports.getMultipleSessionData(socketId, ["field2", "field3"]); + // // 2. Test setMultipleSessionData and getMultipleSessionData + // await setMultipleSessionData(socketId, { field2: "Second Value", field3: "Third Value" }); + // const multipleFields = await getMultipleSessionData(socketId, ["field2", "field3"]); // console.log("Retrieved multiple field values:", multipleFields); // - // // Store multiple session data using setMultipleFromArraySessionData - // await exports.setMultipleFromArraySessionData(socketId, [ + // // 3. Test setMultipleFromArraySessionData + // await setMultipleFromArraySessionData(socketId, [ // ["field4", "Fourth Value"], // ["field5", "Fifth Value"] // ]); // // // Retrieve and log all fields - // const allFields = await exports.getMultipleSessionData(socketId, [ - // "field1", - // "field2", - // "field3", - // "field4", - // "field5" - // ]); + // const allFields = await getMultipleSessionData(socketId, ["field1", "field2", "field3", "field4", "field5"]); // console.log("Retrieved all field values:", allFields); // + // // 4. Test list functions // // Add item to the end of a Redis list - // await exports.addItemToEndOfList(socketId, "logEvents", { event: "Log Event 1", timestamp: new Date() }); - // await exports.addItemToEndOfList(socketId, "logEvents", { event: "Log Event 2", timestamp: new Date() }); + // await addItemToEndOfList(socketId, "logEvents", { event: "Log Event 1", timestamp: new Date() }); + // await addItemToEndOfList(socketId, "logEvents", { event: "Log Event 2", timestamp: new Date() }); // // // Add item to the beginning of a Redis list - // await exports.addItemToBeginningOfList(socketId, "logEvents", { event: "First Log Event", timestamp: new Date() }); + // await addItemToBeginningOfList(socketId, "logEvents", { event: "First Log Event", timestamp: new Date() }); // - // // Retrieve the entire list (using lRange) - // const logEvents = await pubClient.lRange(`socket:${socketId}:logEvents`, 0, -1); - // console.log("Log Events List:", logEvents.map(JSON.parse)); + // // Retrieve the entire list + // const logEventsData = await pubClient.lrange(`socket:${socketId}:logEvents`, 0, -1); + // const logEvents = logEventsData.map((item) => JSON.parse(item)); + // console.log("Log Events List:", logEvents); // - // // **Add the new code below to test clearList** - // - // // Clear the list using clearList - // await exports.clearList(socketId, "logEvents"); + // // 5. Test clearList + // await clearList(socketId, "logEvents"); // console.log("Log Events List cleared."); // // // Retrieve the list after clearing to confirm it's empty - // const logEventsAfterClear = await pubClient.lRange(`socket:${socketId}:logEvents`, 0, -1); + // const logEventsAfterClear = await pubClient.lrange(`socket:${socketId}:logEvents`, 0, -1); // console.log("Log Events List after clearing:", logEventsAfterClear); // Should be an empty array // - // // Clear session data - // await exports.clearSessionData(socketId); + // // 6. Test clearSessionData + // await clearSessionData(socketId); // console.log("Session data cleared."); - // }; // + // // 7. Test room functions + // const roomName = "testRoom"; + // const user1 = { id: 1, name: "Alice" }; + // const user2 = { id: 2, name: "Bob" }; + // + // // Add users to room + // await addUserToRoom(roomName, user1); + // await addUserToRoom(roomName, user2); + // + // // Get users in room + // const usersInRoom = await getUsersInRoom(roomName); + // console.log(`Users in room ${roomName}:`, usersInRoom); + // + // // Remove a user from room + // await removeUserFromRoom(roomName, user1); + // + // // Get users in room after removal + // const usersInRoomAfterRemoval = await getUsersInRoom(roomName); + // console.log(`Users in room ${roomName} after removal:`, usersInRoomAfterRemoval); + // + // // Clean up: remove remaining users from room + // await removeUserFromRoom(roomName, user2); + // + // // Verify room is empty + // const usersInRoomAfterCleanup = await getUsersInRoom(roomName); + // console.log(`Users in room ${roomName} after cleanup:`, usersInRoomAfterCleanup); // Should be empty + // }; // if (process.env.NODE_ENV === "development") { // demoSessionData(); // } - // "th1s1sr3d1s" (BCrypt) + return api; }; -module.exports = applyRedisHelpers; + +module.exports = { applyRedisHelpers }; diff --git a/server/web-sockets/redisSocketEvents.js b/server/web-sockets/redisSocketEvents.js index 5cda88569..f0c028d17 100644 --- a/server/web-sockets/redisSocketEvents.js +++ b/server/web-sockets/redisSocketEvents.js @@ -1,102 +1,18 @@ -const path = require("path"); -require("dotenv").config({ - path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) -}); -const logger = require("../utils/logger"); const { admin } = require("../firebase/firebase-handler"); -// Logging helper functions -function createLogEvent(socket, level, message) { - console.log(`[IOREDIS LOG EVENT] - ${socket.user.email} - ${socket.id} - ${message}`); - logger.log("ioredis-log-event", level, socket.user.email, null, { wsmessage: message }); -} - -const registerUpdateEvents = (socket) => { - socket.on("update-token", async (newToken) => { - try { - socket.user = await admin.auth().verifyIdToken(newToken); - createLogEvent(socket, "INFO", "Token updated successfully"); - socket.emit("token-updated", { success: true }); - } catch (error) { - createLogEvent(socket, "ERROR", `Token update failed: ${error.message}`); - socket.emit("token-updated", { success: false, error: error.message }); - // Optionally disconnect the socket if token verification fails - socket.disconnect(); - } - }); -}; - -const redisSocketEvents = (io, { addUserToRoom, getUsersInRoom, removeUserFromRoom }, { getBodyshopRoom }) => { - // Room management and broadcasting events - function registerRoomAndBroadcastEvents(socket) { - socket.on("join-bodyshop-room", async (bodyshopUUID) => { - try { - const room = getBodyshopRoom(bodyshopUUID); - socket.join(room); - await addUserToRoom(room, { uid: socket.user.uid, email: socket.user.email, socket: socket.id }); - createLogEvent(socket, "DEBUG", `Client joined bodyshop room: ${room}`); - - // Notify all users in the room about the updated user list - const usersInRoom = await getUsersInRoom(room); - io.to(room).emit("room-users-updated", usersInRoom); - } catch (error) { - createLogEvent(socket, "ERROR", `Error joining room: ${error}`); - } - }); - - socket.on("leave-bodyshop-room", async (bodyshopUUID) => { - try { - const room = getBodyshopRoom(bodyshopUUID); - socket.leave(room); - createLogEvent(socket, "DEBUG", `Client left bodyshop room: ${room}`); - } catch (error) { - createLogEvent(socket, "ERROR", `Error joining room: ${error}`); - } - }); - - socket.on("get-room-users", async (bodyshopUUID, callback) => { - try { - const usersInRoom = await getUsersInRoom(getBodyshopRoom(bodyshopUUID)); - callback(usersInRoom); - } catch (error) { - createLogEvent(socket, "ERROR", `Error getting room: ${error}`); - } - }); - - socket.on("broadcast-to-bodyshop", async (bodyshopUUID, message) => { - try { - const room = getBodyshopRoom(bodyshopUUID); - io.to(room).emit("bodyshop-message", message); - createLogEvent(socket, "INFO", `Broadcast message to bodyshop ${room}`); - } catch (error) { - createLogEvent(socket, "ERROR", `Error getting room: ${error}`); - } - }); - - socket.on("disconnect", async () => { - try { - createLogEvent(socket, "DEBUG", `User disconnected.`); - - // Get all rooms the socket is part of - const rooms = Array.from(socket.rooms).filter((room) => room !== socket.id); - for (const room of rooms) { - await removeUserFromRoom(room, { uid: socket.user.uid, email: socket.user.email, socket: socket.id }); - } - } catch (error) { - createLogEvent(socket, "ERROR", `Error getting room: ${error}`); - } - }); - } - - // Register all socket events for a given socket connection - function registerSocketEvents(socket) { - createLogEvent(socket, "DEBUG", `Registering RedisIO Socket Events.`); - - // Register room and broadcasting events - registerRoomAndBroadcastEvents(socket); - registerUpdateEvents(socket); - } +const redisSocketEvents = ({ + io, + redisHelpers: { setSessionData, clearSessionData }, // Note: Used if we persist user to Redis + ioHelpers: { getBodyshopRoom }, + logger +}) => { + // Logging helper functions + const createLogEvent = (socket, level, message) => { + console.log(`[IOREDIS LOG EVENT] - ${socket?.user?.email} - ${socket.id} - ${message}`); + logger.log("ioredis-log-event", level, socket?.user?.email, null, { wsmessage: message }); + }; + // Socket Auth Middleware const authMiddleware = (socket, next) => { try { if (socket.handshake.auth.token) { @@ -105,6 +21,9 @@ const redisSocketEvents = (io, { addUserToRoom, getUsersInRoom, removeUserFromRo .verifyIdToken(socket.handshake.auth.token) .then((user) => { socket.user = user; + // Note: if we ever want to capture user data across sockets + // Uncomment the following line and then remove the next() to a second then() + // return setSessionData(socket.id, "user", user); next(); }) .catch((error) => { @@ -122,7 +41,99 @@ const redisSocketEvents = (io, { addUserToRoom, getUsersInRoom, removeUserFromRo } }; - // Socket.IO Middleware and Connection + // Register Socket Events + const registerSocketEvents = (socket) => { + createLogEvent(socket, "DEBUG", `Registering RedisIO Socket Events.`); + + // Token Update Events + const registerUpdateEvents = (socket) => { + const updateToken = async (newToken) => { + try { + // noinspection UnnecessaryLocalVariableJS + const user = await admin.auth().verifyIdToken(newToken, true); + socket.user = user; + + // If We ever want to persist user Data across workers + // await setSessionData(socket.id, "user", user); + + createLogEvent(socket, "INFO", "Token updated successfully"); + + socket.emit("token-updated", { success: true }); + } catch (error) { + if (error.code === "auth/id-token-expired") { + createLogEvent(socket, "WARNING", "Stale token received, waiting for new token"); + socket.emit("token-updated", { + success: false, + error: "Stale token." + }); + } else { + createLogEvent(socket, "ERROR", `Token update failed: ${error.message}`); + socket.emit("token-updated", { success: false, error: error.message }); + // For any other errors, optionally disconnect the socket + socket.disconnect(); + } + } + }; + socket.on("update-token", updateToken); + }; + // Room Broadcast Events + const registerRoomAndBroadcastEvents = (socket) => { + const joinBodyshopRoom = (bodyshopUUID) => { + try { + const room = getBodyshopRoom(bodyshopUUID); + socket.join(room); + createLogEvent(socket, "DEBUG", `Client joined bodyshop room: ${room}`); + } catch (error) { + createLogEvent(socket, "ERROR", `Error joining room: ${error}`); + } + }; + + const leaveBodyshopRoom = (bodyshopUUID) => { + try { + const room = getBodyshopRoom(bodyshopUUID); + socket.leave(room); + createLogEvent(socket, "DEBUG", `Client left bodyshop room: ${room}`); + } catch (error) { + createLogEvent(socket, "ERROR", `Error joining room: ${error}`); + } + }; + + const broadcastToBodyshopRoom = (bodyshopUUID, message) => { + try { + const room = getBodyshopRoom(bodyshopUUID); + io.to(room).emit("bodyshop-message", message); + createLogEvent(socket, "DEBUG", `Broadcast message to bodyshop ${room}`); + } catch (error) { + createLogEvent(socket, "ERROR", `Error getting room: ${error}`); + } + }; + + socket.on("join-bodyshop-room", joinBodyshopRoom); + socket.on("leave-bodyshop-room", leaveBodyshopRoom); + socket.on("broadcast-to-bodyshop", broadcastToBodyshopRoom); + }; + // Disconnect Events + const registerDisconnectEvents = (socket) => { + const disconnect = () => { + createLogEvent(socket, "DEBUG", `User disconnected.`); + const rooms = Array.from(socket.rooms).filter((room) => room !== socket.id); + for (const room of rooms) { + socket.leave(room); + } + // If we ever want to persist the user across workers + // clearSessionData(socket.id); + }; + + socket.on("disconnect", disconnect); + }; + + // Call Handlers + registerRoomAndBroadcastEvents(socket); + registerUpdateEvents(socket); + registerDisconnectEvents(socket); + }; + + // Associate Middleware and Handlers io.use(authMiddleware); io.on("connection", registerSocketEvents); };