diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..949e12cfe --- /dev/null +++ b/.dockerignore @@ -0,0 +1,21 @@ +# git folder +.git +.gitignore + +# npm +node_modules +npm-debug.log +yarn-error.log + +# build artifacts +dist +coverage + +# mac +.DS_Store + +# cache +.cache + +# personal env +.env \ No newline at end of file diff --git a/.env.template b/.env.template new file mode 100644 index 000000000..faf2fb80f --- /dev/null +++ b/.env.template @@ -0,0 +1,460 @@ +# ==================================================== +# MiroTalk SFU v.2.1.51 - Environment Configuration +# ==================================================== + +# config.js - Main configuration with: +# - All default values and documentation +# - Complex logic and validations +# - Safe to commit to version control +# +# .env - Environment overrides with: +# - Secrets and sensitive data (NEVER commit) +# - Environment-specific settings +# - Simple key=value format +# +# Why this works best: +# 1. SECURITY: Secrets stay out of codebase +# 2. FLEXIBILITY: Change settings without redeploy +# 3. SAFETY: Built-in defaults prevent crashes +# 4. DOCS: config.js explains all options +# +# Best practice: +# - Keep DEFAULT values in config.js +# - Keep SECRETS/ENV-SPECIFIC in .env +# - Add .env to .gitignore +# ==================================================== + +# ---------------------------------------------------- +# 1. Core System Configuration +# ---------------------------------------------------- + +NODE_ENV=development # Runtime environment: development|production +SFU_ANNOUNCED_IP= # Set your public IP address or domain for WebRTC announcements +SFU_LISTEN_IP=0.0.0.0 # Bind to all interfaces +SFU_MIN_PORT=40000 # Minimum WebRTC port range +SFU_MAX_PORT=40100 # Maximum WebRTC port range +SFU_NUM_WORKERS= # Number of worker processes (defaults to CPU count) +SFU_SERVER=false # Enable/disable WebRTC server (true|false) + +# ---------------------------------------------------- +# 2. Server Configuration +# ---------------------------------------------------- + +SERVER_HOST_URL= # Public server URL (e.g., https://yourdomain.com) +SERVER_LISTEN_IP=0.0.0.0 # Server bind IP +SERVER_LISTEN_PORT=3010 # Port to listen on +TRUST_PROXY=false # Trust proxy headers (true in production behind reverse proxy) +SERVER_SSL_CERT=../ssl/cert.pem # Path to SSL certificate +SERVER_SSL_KEY=../ssl/key.pem # Path to SSL private key +CORS_ORIGIN=* # Allowed CORS origins (comma-separated) + +# ---------------------------------------------------- +# 3. Logging Configuration +# ---------------------------------------------------- + +LOGS_JSON=false # Log output in JSON format (true|false) +LOGS_JSON_PRETTY=false # Pretty-print JSON logs (true|false) + +# ---------------------------------------------------- +# 4. Media Handling +# ---------------------------------------------------- + +# Recording +RECORDING_ENABLED=false # Enable server-side recording (true|false) +RECORDING_DIR='../rec' # Directory to store recordings (Relative to app/src/) +RECORDING_MAX_FILE_SIZE=1073741824 # Max recording file size in bytes (default: 1GB) +RECORDING_UPLOAD_TO_S3=false # Upload recordings to S3-compatible storage (MinIO, Wasabi, DigitalOcean Spaces, etc.) (true|false) +RECORDING_ENDPOINT= # Recording service endpoint es http://localhost:8080 + +# Rtmp streaming +RTMP_ENABLED=false # Enable RTMP streaming (true|false) +RTMP_DIR='../rtmp' # Directory to read files for RTMP streams (Relative to app/src/) +RTMP_FROM_FILE=true # Enable local file streaming +RTMP_FROM_URL=true # Enable URL streaming +RTMP_FROM_STREAM=true # Enable live stream (camera, microphone, screen, window) +RTMP_MAX_STREAMS=1 # Max simultaneous RTMP streams +RTMP_USE_NODE_MEDIA_SERVER=true # Use Node Media Server mirotalk-nms Docker image +RTMP_SERVER=rtmp://localhost:1935 # RTMP server URL +RTMP_APP_NAME=live # RTMP application name +RTMP_STREAM_KEY= # RTMP stream key (optional) +RTMP_SECRET=mirotalkRtmpSecret # RTMP API secret (Must match node-media-server config.js) +RTMP_API_SECRET=mirotalkRtmpApiSecret # RTMP internal secret +RTMP_EXPIRATION_HOURS=4 # RTMP URL validity duration (hours) +RTMP_FFMPEG_PATH= # RTMP Custom path to FFmpeg binary (auto-detected if empty) + +# ---------------------------------------------------- +# 5. Security & Authentication +# ---------------------------------------------------- + +# Middleware +IP_WHITELIST_ENABLED=false # Restrict access by IP (true|false) +IP_WHITELIST_ALLOWED=127.0.0.1,::1 # Allowed IPs (comma-separated) + +# Token +JWT_SECRET=mirotalksfu_jwt_secret # Secret for JWT tokens +JWT_EXPIRATION=1h # JWT token expiration (e.g., 1h, 7d) + +# OIDC +OIDC_ENABLED=false # Enable OpenID Connect (true|false) +OIDC_ALLOW_ROOMS_CREATION_FOR_AUTH_USERS=true # Allow all authenticated users via OIDC to create their own rooms +OIDC_ISSUER=https://server.example.com # OIDC provider URL +OIDC_BASE_URL= # OIDC base URL es https://yourdomain.com +OIDC_CLIENT_ID=clientID # OIDC client ID +OIDC_CLIENT_SECRET=clientSecret # OIDC client secret +OIDC_SECRET=mirotalksfu-oidc-secret # OIDC secret +OIDC_AUTH_REQUIRED=false # set to true if authentication is required for all routes +OIDC_AUTH_LOGOUT=true # Controls automatic logout from both your app and Auth0 (true|false) +OIDC_USERNAME_FORCE=true # Force the username to match OIDC email or name (true|false) +OIDC_USERNAME_AS_EMAIL=true # Set username as email from OIDC (true|false) +OIDC_USERNAME_AS_NAME=false # Set username as name from OIDC (true|false) + +# Host protection +HOST_PROTECTED=false # Enable host protection (true|false) +HOST_USER_AUTH=false # Enable user authentication (true|false) + +# Host users - Define host users in the format: username:password:displayName:allowedRooms (room1,room2...) +HOST_USERS="username:password:User:*|admin:admin:Admin:room1,room2|guest:guest:Guest:room1,room2" + +HOST_MAX_LOGIN_ATTEMPTS=5 # Maximum login attempts before temporary block +HOST_MIN_LOGIN_BLOCK_TIME=15 # Block duration in minutes after max attempts exceeded + +# Endpoints +HOST_USERS_FROM_DB=false # Use DB for user auth (true|false) + +USERS_API_SECRET=mirotalkweb_default_secret # Users API secret key +USERS_API_ENDPOINT=http://localhost:9000/api/v1/user/isAuth # User auth endpoint +USERS_ROOM_ALLOWED_ENDPOINT=http://localhost:9000/api/v1/user/isRoomAllowed # Room permission endpoint +USERS_ROOMS_ALLOWED_ENDPOINT=http://localhost:9000/api/v1/user/roomsAllowed # Allowed rooms endpoint +ROOM_EXISTS_ENDPOINT=http://localhost:9000/api/v1/room/exists # Room exists endpoint + +# Presenters +PRESENTERS=Miroslav Pejic,miroslav.pejic.85@gmail.com, # Presenter usernames (comma-separated) +PRESENTER_JOIN_FIRST=true # First joiner becomes presenter (true|false) + +# ---------------------------------------------------- +# 6. API Configuration +# ---------------------------------------------------- + +API_KEY_SECRET=mirotalksfu_default_secret # Secret for API authentication +API_ALLOW_STATS=true # Enable stats API (true|false) +API_ALLOW_MEETINGS=false # Allow meetings API endpoint (true|false) +API_ALLOW_MEETING=true # Allow single meeting API endpoint (true|false) +API_ALLOW_MEETING_END=false # Allow meeting end API endpoint (true|false) +API_ALLOW_JOIN=true # Allow join API endpoint (true|false) +API_ALLOW_TOKEN=false # Allow token-based API authentication (true|false) +API_ALLOW_SLACK=true # Allow Slack integration via API (true|false) +API_ALLOW_MATTERMOST=true # Allow Mattermost integration via API (true|false) + +# ---------------------------------------------------- +# 7. Third-Party Integrations +# ---------------------------------------------------- + +# ChatGPT Integration +CHATGPT_ENABLED=false # Enable ChatGPT integration (true|false) +CHATGPT_BASE_PATH=https://api.openai.com/v1/ # OpenAI base path +CHATGPT_API_KEY= # OpenAI API key +CHATGPT_MODEL=gpt-3.5-turbo # Model to use (gpt-3.5-turbo, gpt-4, etc.) +CHATGPT_MAX_TOKENS=1024 # Max response tokens +CHATGPT_TEMPERATURE=0.7 # Creativity level (0-1) + +# DeepSeek Integration +DEEP_SEEK_ENABLED=false # Enable DeepSeek integration (true|false) +DEEP_SEEK_BASE_PATH=https://api.deepseek.com/v1/ # DeepSeek base path +DEEP_SEEK_API_KEY= # DeepSeek API key +DEEP_SEEK_MODEL=deepseek-chat # Model to use (deepseek-chat, deepseek-coder, etc.) +DEEP_SEEK_MAX_TOKENS=1024 # Max response tokens +DEEP_SEEK_TEMPERATURE=0.7 # Creativity level (0-1) + +# Video AI (LiveAvatar) Integration +VIDEOAI_ENABLED=false # Enable video AI avatars (true|false) +VIDEOAI_BASE_PATH=https://api.liveavatar.com # LiveAvatar API base path +VIDEOAI_API_KEY= # LiveAvatar API key +VIDEOAI_MODE=FULL # LiveAvatar mode (FULL|LITE) +VIDEOAI_CONTEXT_ID= # LiveAvatar context ID (optional) +VIDEOAI_SYSTEM_LIMIT= # AI system prompt +VIDEOAI_SESSION_TIME_LIMIT= # Session time limit in seconds (empty = unlimited) + +# Email Alerts +EMAIL_ALERTS_ENABLED=false # Enable email alerts (true|false) +EMAIL_NOTIFICATIONS=false # Enable email notifications (true|false) +EMAIL_HOST=smtp.gmail.com # SMTP server host +EMAIL_PORT=587 # SMTP port +EMAIL_USERNAME=your_username # SMTP username +EMAIL_PASSWORD=your_password # SMTP password +EMAIL_FROM= # Sender email address +EMAIL_SEND_TO=sfu.mirotalk@gmail.com # Notification recipient + +# Slack Integration +SLACK_ENABLED=false # Enable Slack integration (true|false) +SLACK_SIGNING_SECRET= # Slack app signing secret + +# Mattermost Integration +MATTERMOST_ENABLED=false # Enable Mattermost (true|false) +MATTERMOST_SERVER_URL=YourMattermostServerUrl # Mattermost server URL +MATTERMOST_USERNAME=YourMattermostUsername # Mattermost username +MATTERMOST_PASSWORD=YourMattermostPassword # Mattermost password +MATTERMOST_TOKEN=YourMattermostToken # Mattermost slash command token +MATTERMOST_COMMAND_NAME=/sfu # Mattermost command name +MATTERMOST_DEFAULT_MESSAGE=Here is your meeting room: # Mattermost default message + +# Discord Integration +DISCORD_ENABLED=false # Enable Discord bot (true|false) +DISCORD_TOKEN= # Discord bot token +DISCORD_COMMAND_NAME=/sfu # Discord command name +DISCORD_DEFAULT_MESSAGE=Here is your SFU meeting room: # Discord default message +DISCORD_BASE_URL=https://sfu.mirotalk.com/join/ # Discord Base URL for meeting rooms + +# Ngrok Tunnel +NGROK_ENABLED=false # Enable Ngrok tunneling (true|false) +NGROK_AUTH_TOKEN= # Ngrok authentication token + +# Sentry Error Tracking +SENTRY_ENABLED=false # Enable Sentry error tracking (true|false) +SENTRY_LOG_LEVELS=error # Log levels to capture (comma-separated) +SENTRY_DSN= # Sentry DSN URL +SENTRY_TRACES_SAMPLE_RATE=0.2 # Error sampling rate (0-1) + +# Webhook Notifications +WEBHOOK_ENABLED=false # Enable webhook notifications (true|false) +WEBHOOK_URL=https://your-site.com/webhook-endpoint # Webhook endpoint URL + +# IP Geolocation +IP_LOOKUP_ENABLED=false # Enable IP lookup functionality (true|false) + +# S3-Compatible Object Storage Configuration +S3_ENABLED=false # Enable S3-compatible storage (true|false) +S3_ACCESS_KEY_ID= # Access Key ID (leave empty if using instance credentials or IAM roles) +S3_SECRET_ACCESS_KEY= # Secret Access Key (leave empty if using instance credentials or IAM roles) +S3_BUCKET=mirotalk # Name of your storage bucket (must exist) +S3_REGION= # Region or location (e.g., us-east-2, eu-west-2, or custom for non-AWS providers) +S3_ENDPOINT= # Custom endpoint URL for S3-compatible services (leave empty to auto-resolve from region). Use for MinIO, Wasabi, DigitalOcean Spaces, etc. +S3_FORCE_PATH_STYLE=false # Use path-style URLs (true|false). Set to true for most S3-compatible providers (MinIO, Wasabi, etc.) + +# ---------------------------------------------------- +# 8. UI Customization +# ---------------------------------------------------- + +# Branding injection +BRAND_HTML_INJECTION=true # Enable HTML injection for branding (true|false) + +# Widget Configuration +WIDGET_ENABLED=false # Enable branding widget (true|false) +WIDGET_ROOM_ID=support-room # Widget room ID (default: support-room) +WIDGET_THEME=dark # Widget theme (dark|light) +WIDGET_STATE=minimized # Widget initial state (normal|minimized|closed) +WIDGET_TYPE=support # Widget type (support) +WIDGET_SUPPORT_POSITION=top-right # Support widget position (top-right|top-left|bottom-right|bottom-left) +WIDGET_SUPPORT_EXPERT_IMAGES= # Comma-separated expert image URLs comma-separated + +WIDGET_SUPPORT_BUTTON_AUDIO=true # Show audio button in support widget (false|false) +WIDGET_SUPPORT_BUTTON_VIDEO=true # Show video button in support widget (false|false) +WIDGET_SUPPORT_BUTTON_SCREEN=true # Show screen share button in support widget (false|false) +WIDGET_SUPPORT_BUTTON_CHAT=true # Show chat button in support widget (false|false) +WIDGET_SUPPORT_BUTTON_JOIN=true # Show join button in support widget (false|false) + +WIDGET_SUPPORT_CHECK_ONLINE_STATUS=false # Check online room status (true|false) +WIDGET_SUPPORT_IS_ONLINE=true # Is support online (true|false) +WIDGET_SUPPORT_HEADING=Need Help? # Support widget heading +WIDGET_SUPPORT_SUBHEADING=Get instant support from our expert team! # Support widget subheading +WIDGET_SUPPORT_CONNECT_TEXT=connect in < 5 seconds # Connect text +WIDGET_SUPPORT_ONLINE_TEXT=We are online # Online text +WIDGET_SUPPORT_OFFLINE_TEXT=We are offline # Offline text +WIDGET_SUPPORT_POWERED_BY=Powered by MiroTalk SFU # Powered by text + +# Widget Alert Notifications +WIDGET_ALERT_ENABLED=false # Enable alert notifications (true|false) +WIDGET_ALERT_TYPE=email # Alert notification type (email) need EMAIL_ALERTS_ENABLED=true and configuration + +# App +UI_LANGUAGE=en # Interface language (en, es, fr, etc.) +APP_NAME=MiroTalk SFU # Application name +APP_TITLE= # Custom HTML title (leave empty for default) +APP_DESCRIPTION= # Application description +JOIN_DESCRIPTION= # Join screen description +JOIN_BUTTON_LABEL=JOIN ROOM # Join button text +CUSTOMIZE_BUTTON_LABEL=CUSTOMIZE ROOM # Customize button text +JOIN_LAST_LABEL=Your recent room: # Recent room label text + +# Site +SITE_TITLE= # Website title +SITE_ICON_PATH=../images/logo.svg # Favicon path +APPLE_TOUCH_ICON_PATH=../images/logo.svg # Apple touch icon path +NEW_ROOM_TITLE= # New room title +NEW_ROOM_DESC= # New room description + +# Meta +META_DESCRIPTION= # HTML meta description +META_KEYWORDS= # HTML meta keywords + +# OG +OG_TYPE=app-webrtc # OpenGraph type +OG_SITE_NAME=MiroTalk SFU # OG site name +OG_TITLE= # OG title +OG_DESCRIPTION= # OG description +OG_IMAGE_URL=https://sfu.mirotalk.com/images/mirotalksfu.png # OG image +OG_URL=https://sfu.mirotalk.com # OG URL + +# HTML +SHOW_TOP_SPONSORS=true # Show top sponsors section (true|false) +SHOW_FEATURES=true # Show features section (true|false) +SHOW_TEAMS=true # Show teams section (true|false) +SHOW_TRY_EASIER=true # Show "try easier" section (true|false) +SHOW_POWERED_BY=true # Show powered by (true|false) +SHOW_SPONSORS=true # Show sponsors (true|false) +SHOW_ADVERTISERS=true # Show advertisers (true|false) +SHOW_SUPPORT_US=true # Show support us section (true|false) +SHOW_FOOTER=true # Show footer (true|false) + +# Who Are You +WHO_ARE_YOU_TITLE= # Page title (default: MiroTalk SFU - Waiting for host to start the meeting) +WHO_ARE_YOU_WAITING_ROOM_HEADING= # Waiting room heading (default: Waiting for host...) +WHO_ARE_YOU_WAITING_ROOM_DESCRIPTION= # Waiting room description HTML +WHO_ARE_YOU_WAITING_ROOM_STATUS= # Status text (default: Checking room status...) +WHO_ARE_YOU_WAITING_ROOM_READY= # Ready text (default: Room is ready! Joining...) +WHO_ARE_YOU_WAITING_ROOM_WAITING= # Waiting text (default: Waiting for host to start the meeting...) +WHO_ARE_YOU_WAITING_ROOM_HOST_LINK= # Host link text (default: Are you the host?) +WHO_ARE_YOU_WAITING_ROOM_LOGIN_LINK= # Login link text (default: Login here) + +# Login +LOGIN_HEADING= # Login page heading (default: Welcome back) +LOGIN_DESCRIPTION= # Login page description (default: Enter your credentials to continue.) +LOGIN_BUTTON_LABEL= # Login button label (default: Login) + +# About +ABOUT_IMAGE_URL=../images/mirotalk-logo.gif # About section image +SUPPORT_URL=https://codecanyon.net/user/miroslavpejic85 # Support link URL +SUPPORT_TEXT=Support # Support button text +AUTHOR_LABEL=Author # Author label text +AUTHOR_NAME=Miroslav Pejic # Author name +LINKEDIN_URL=https://www.linkedin.com/in/miroslav-pejic-976a07101/ # LinkedIn profile +CONTACT_EMAIL=miroslav.pejic.85@gmail.com # Contact email +EMAIL_LABEL=Email # Email label text +EMAIL_SUBJECT=MiroTalk SFU info # Email subject +COPYRIGHT_TEXT=MiroTalk SFU, all rights reserved # Copyright text + +# ---------------------------------------------------- +# 9. UI Button Configuration +# ---------------------------------------------------- + +# Active Rooms +SHOW_ACTIVE_ROOMS=false # Show active rooms feature (true|false) + +# Popup Configuration +SHOW_SHARE_ROOM_POPUP=true # Show share room popup (true|false) +SHOW_SHARE_ROOM_QR_ON_HOVER=true # Show share room QR popup on mouse hover (true|false) + +# Main Control Buttons +SHOW_AUDIO_BUTTON=true # Show audio button (true|false) +SHOW_VIDEO_BUTTON=true # Show video button (true|false) +SHOW_SCREEN_BUTTON=true # Show screen share button (true|false) +SHOW_SWAP_CAMERA=true # Show camera swap button (true|false) +SHOW_RAISE_HAND=true # Show raise hand button (true|false) +SHOW_CHAT_BUTTON=true # Show chat button (true|false) +SHOW_PARTICIPANTS_BUTTON=true # Show participants button (true|false) +SHOW_SETTINGS=true # Show settings button (true|false) +SHOW_EXTRA_BUTTON=true # Show extra buttons (true|false) +SHOW_EXIT_BUTTON=true # Show exit button (true|false) +# Settings Extra Buttons +SHOW_SHARE_BUTTON=true # Show share button (true|false) +SHOW_HIDE_ME=true # Show hide me button (true|false) +SHOW_FULLSCREEN_BUTTON=true # Show fullscreen button (true|false) +SHOW_EMOJI=true # Show emoji button (true|false) +SHOW_TRANSCRIPTION=true # Show transcription button (true|false) +SHOW_POLL_BUTTON=true # Show poll button (true|false) +SHOW_EDITOR_BUTTON=true # Show editor button (true|false) +SHOW_WHITEBOARD=true # Show whiteboard button (true|false) +SHOW_DOCUMENT_PIP=true # Show document PiP button (true|false) +SHOW_SNAPSHOT=true # Show snapshot button (true|false) +SHOW_ABOUT=true # Show about button (true|false) + +# Settings Panel +SHOW_ROOMS=true # Show rooms (true|false) +ENABLE_FILE_SHARING=true # Enable file sharing (true|false) +SHOW_LOCK_ROOM=true # Show lock room button (true|false) +SHOW_UNLOCK_ROOM=true # Show unlock room button (true|false) +SHOW_BROADCASTING=true # Show broadcasting button (true|false) +SHOW_LOBBY=true # Show lobby button (true|false) +SHOW_EMAIL_INVITE=true # Show email invitation button (true|false) +SHOW_MIC_OPTIONS=true # Show mic options button (true|false) +SHOW_RTMP_TAB=true # Show RTMP tab (true|false) +SHOW_NOTIFICATIONS_TAB=true # Show notifications tab (true|false) +SHOW_MODERATOR_TAB=true # Show moderator tab (true|false) +SHOW_RECORDING_TAB=true # Show recording tab (true|false) +HOST_ONLY_RECORDING=true # Only host can record (true|false) +ENABLE_PUSH_TO_TALK=true # Enable push-to-talk (true|false) +SHOW_KEYBOARD_SHORTCUTS=true # Show keyboard shortcuts (true|false) +SHOW_VIRTUAL_BACKGROUND=true # Show virtual background (true|false) + +# Video Controls +ENABLE_PIP=true # Enable picture-in-picture (true|false) +SHOW_MIRROR_BUTTON=true # Show mirror button (true|false) +SHOW_FULLSCREEN=true # Show fullscreen button (true|false) +SHOW_SNAPSHOT_BUTTON=true # Show snapshot button (true|false) +SHOW_MUTE_AUDIO=true # Show mute audio button (true|false) +SHOW_PRIVACY_TOGGLE=true # Show privacy toggle (true|false) +SHOW_VOLUME_CONTROL=true # Show volume control (true|false) +SHOW_FOCUS_BUTTON=true # Show focus button (true|false) +SHOW_SEND_MESSAGE=true # Show send message button (true|false) +SHOW_SEND_FILE=true # Show send file button (true|false) +SHOW_SEND_VIDEO=true # Show send video button (true|false) +SHOW_MUTE_VIDEO=true # Show mute video button (true|false) +SHOW_GEO_LOCATION=true # Show geoLocation button (true|false) +SHOW_BAN_BUTTON=true # Show ban button (true|false) +SHOW_EJECT_BUTTON=true # Show eject button (true|false) + +# Chat Controls +SHOW_CHAT_PIN=true # Show chat pin button (true|false) +SHOW_CHAT_MAXIMIZE=true # Show chat maximize button (true|false) +SHOW_CHAT_SAVE=true # Show chat save button (true|false) +SHOW_CHAT_EMOJI=true # Show chat emoji button (true|false) +SHOW_CHAT_MARKDOWN=true # Show chat markdown button (true|false) +SHOW_CHAT_SPEECH=true # Show chat speech button (true|false) +ENABLE_CHAT_GPT=true # Enable ChatGPT in chat (true|false) +ENABLE_DEEP_SEEK=true # Enable DeepSeek in chat (true|false) + +# Poll Controls +SHOW_POLL_PIN=true # Show poll pin button (true|false) +SHOW_POLL_MAXIMIZE=true # Show poll maximize button (true|false) +SHOW_POLL_SAVE=true # Show poll save button (true|false) + +# Participants Controls +SHOW_SAVE_INFO=true # Show save info button (true|false) +SHOW_SEND_FILE_ALL=true # Show send file to all button (true|false) +SHOW_EJECT_ALL=true # Show eject all button (true|false) + +# Whiteboard Controls +SHOW_WB_LOCK=true # Show whiteboard lock button (true|false) + +# ---------------------------------------------------- +# 10. Feature Flags +# ---------------------------------------------------- + +SURVEY_ENABLED=false # Enable post-call survey (true|false) +SURVEY_URL= # Survey URL + +# Redirect +REDIRECT_ENABLED=false # Enable post-call redirect (true|false) +REDIRECT_URL= # Redirect URL + +# Stats +STATS_ENABLED=true # Enable usage statistics (true|false) +STATS_SRC=https://stats.mirotalk.com/script.js # Stats script URL +STATS_ID=41d26670-f275-45bb-af82-3ce91fe57756 # Stats tracking ID + +# Custom noise suppression +CUSTOM_NOISE_SUPPRESSION_ENABLED=true # Enable custom noise suppression, default one will still work if this is disabled (true|false) + +# ----------------------------------------------------- +# 11. Global Moderation Configuration +# ----------------------------------------------------- + +ROOM_MAX_PARTICIPANTS=1000 # Maximum participants per room +ROOM_LOBBY=false # Enable room lobby (true|false) + +# ---------------------------------------------------- +# 12. Mediasoup Configuration +# ---------------------------------------------------- + +MEDIASOUP_ROUTER_AUDIO_LEVEL_OBSERVER_ENABLED=true # Enable audio level observer (true|false) +MEDIASOUP_ROUTER_ACTIVE_SPEAKER_OBSERVER_ENABLED=false # Enable active speaker observer (true|false) +MEDIASOUP_LOG_LEVEL=error # Mediasoup log level (debug, warn, error) \ No newline at end of file diff --git a/.gitignore b/.gitignore index 81240bdea..b4b87b04c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,18 +8,19 @@ node_modules npm-debug.log -# package -package-lock.json - # cache .cache # personal +.env config.js docker-compose.yml docker-push.sh +data rec rtmp +rnnoiseBuild +security # virtual background custom \ No newline at end of file diff --git a/.prettierignore b/.prettierignore index 26f5c15f8..81947cd59 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,2 @@ .github +public/sfu \ No newline at end of file diff --git a/.prettierrc.js b/.prettierrc.js index c8c6a5496..c43fd34da 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -1,6 +1,6 @@ module.exports = { semi: true, - trailingComma: 'all', + trailingComma: 'es5', // Trailing commas only where valid in ES5 (objects, arrays, not function arguments) singleQuote: true, printWidth: 120, tabWidth: 4, diff --git a/Dockerfile b/Dockerfile index 2b4e9664d..c927a485f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,6 +6,7 @@ WORKDIR /src # Set environment variable to skip downloading prebuilt workers ENV MEDIASOUP_SKIP_WORKER_PREBUILT_DOWNLOAD="true" +ENV NODE_ENV="production" # Install necessary system packages and dependencies RUN apt-get update \ @@ -19,9 +20,9 @@ RUN apt-get update \ # Rename config.template.js to config.js COPY ./app/src/config.template.js ./app/src/config.js -# Copy package.json and install npm dependencies -COPY package.json . -RUN npm install +# Copy package*.json and install npm dependencies +COPY package*.json ./ +RUN npm ci --only=production --silent # Cleanup unnecessary packages and files RUN apt-get purge -y --auto-remove \ diff --git a/README.md b/README.md index 7ed1881d7..6c58e0844 100644 --- a/README.md +++ b/README.md @@ -10,14 +10,23 @@ ![License: Extended](https://img.shields.io/badge/License-Extended_Commercial_Use-darkgreen.svg) ![Community](https://img.shields.io/badge/Community-forum-pink.svg) +This project is proudly sponsored by + -

Free WebRTC - SFU - Simple, Secure, Scalable Real-Time Video Conferences with support for up to 8k resolution and 60fps. It's compatible with all major browsers and platforms

+

Recall.ai - API for meeting recording

+

+ Recall.ai – an API for recording Zoom, Google Meet, Microsoft Teams, and in-person meetings. +


+
+ +

MiroTalk SFU stands for Selective Forwarding Unit, built on Mediasoup, a powerful media server that routes video/audio streams between participants. It offers a rich set of features for WebRTC meetings, webinars, and more. Simple, Secure, Scalable Real-Time Video Conferences with support for up to 8K resolution and 60fps. It's compatible with all major browsers and platforms.

+

- Explore MiroTalk SFU + Explore MiroTalk SFU


@@ -32,7 +41,7 @@

- Join our Community for questions, help, support, ideas, and discussions on Discord + Join our Community for questions, help, support, ideas, and discussions on Discord

@@ -66,7 +75,7 @@ - Choose your audio input, output, and video source. - Supports video quality up to 4K. - Supports advance Video/Document Picture-in-Picture (PiP) offering a more streamlined and flexible viewing experience. -- Record your screen, audio, and video locally or on your Server. +- Record your screen, audio, and video locally, on your server, or in an S3 bucket for easy access and management! - Snapshot video frames and save them as PNG images. - Chat with an Emoji Picker for expressing feelings, private messages, Markdown support, and conversation saving. - ChatGPT (powered by OpenAI) for answering questions, providing information, and connecting users to relevant resources. @@ -108,16 +117,18 @@
- You can `directly join a room` by using link like: -- https://sfu.mirotalk.com/join?room=test&roomPassword=0&name=mirotalksfu&audio=0&video=0&screen=0¬ify=0&duration=unlimited +- https://sfu.mirotalk.com/join?room=test&roomPassword=0&name=random&avatar=0&audio=0&video=0&screen=0&chat=0¬ify=0&duration=unlimited | Params | Type | Description | | ------------ | -------------- | ------------------------- | | room | string | Room Id | | roomPassword | string/boolean | Room password | | name | string | User name | + | avatar | string/boolean | User avatar | | audio | boolean | Audio stream | | video | boolean | Video stream | | screen | boolean | Screen stream | + | chat | boolean | Chat | | notify | boolean | Welcome message | | hide | boolean | Hide myself | | duration | string | Meeting duration HH:MM:SS | @@ -130,36 +141,20 @@
-When [host.protected](https://docs.mirotalk.com/mirotalk-sfu/host-protection/) or `host.user_auth` is enabled, the host/users can provide a valid token for direct joining the room as specified in the `app/src/config.js` file. - -| Params | Value | Description | -| ---------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -| `host.protected` | `true` if protection is enabled, `false` if not (default false) | Requires the host to provide a valid username and password during room initialization. | -| `host.user_auth` | `true` if user authentication is required, `false` if not (default false). | Determines whether host authentication is required. | -| `host.users` | JSON array with user objects: `{"username": "username", "password": "password"}` | List of valid host users with their credentials. | - -Example: - -```js - host: { - protected: true, - user_auth: true, - users: [ - { - username: 'username', - password: 'password', - displayname: 'displayname', - allowed_rooms: ['*'], - }, - { - username: 'username2', - password: 'password2', - displayname: 'displayname2', - allowed_rooms: ['room1', 'room2'], - }, - //... - ], - }, +When [`HOST_PROTECTED`](https://docs.mirotalk.com/mirotalk-sfu/host-protection/) or `HOST_USER_AUTH` is enabled, hosts and users must provide a valid token to join a room directly. Token requirements and configuration details are specified in the `app/src/config.js` file. + +In the configuration file `.env`, you can enable security measures for your video conferencing room using the following parameters: + +- `HOST_PROTECTED`: Set to `true` to require the host to provide a valid username and password during room initialization. Default is `false`. +- `HOST_USER_AUTH`: Set to `true` to enable user authentication for hosts. Default is `false`. +- `HOST_USERS`: Define host users in the format: username:password:displayName:allowedRooms (room1,room2...) + +Example Configuration: + +```bash +HOST_PROTECTED=true +HOST_USER_AUTH=false +HOST_USERS="user1:pass1:user-1:*|user2@mail.com:pass2:user-2:*|user3:pass3:user-3:room1,room2" ``` @@ -169,7 +164,7 @@ Example:
-- Before running MiroTalk SFU, ensure you have `Node.js` and all [requirements](https://mediasoup.org/documentation/v3/mediasoup/installation/#requirements) installed. This project has been tested with Node version [18.X](https://nodejs.org/en/download). +- Before running MiroTalk SFU, ensure you have `Node.js` and all [requirements](https://mediasoup.org/documentation/v3/mediasoup/installation/#requirements) installed. This project has been tested with Node version [22.X](https://nodejs.org/en/download). - Requirements install example for `Ubuntu 24.04 LTS` @@ -191,7 +186,7 @@ $ apt install -y ffmpeg ![nodejs](public/images/nodejs.png) -Install `NodeJS 18.X` and `npm` using [Node Version Manager](https://docs.mirotalk.com/nvm/nvm/) +Install `NodeJS 22.X` and `npm` using [Node Version Manager](https://docs.mirotalk.com/nvm/nvm/) --- @@ -204,12 +199,14 @@ $ git clone https://github.com/miroslavpejic85/mirotalksfu.git $ cd mirotalksfu # Copy app/src/config.template.js in app/src/config.js and edit it if needed $ cp app/src/config.template.js app/src/config.js +# Copy .env.template to .env and edit it if needed +$ cp .env.template .env # Install dependencies - be patient, the first time will take a few minutes, in the meantime have a good coffee ;) $ npm install # Start the server $ npm start # If you want to start the server on a different port than the default use an env var -$ PORT=3011 npm start +$ SERVER_LISTEN_PORT=3011 npm start ``` - Open [https://localhost:3010](https://localhost:3010) or `:3011` if the default port has been changed in your browser. @@ -239,6 +236,8 @@ $ git clone https://github.com/miroslavpejic85/mirotalksfu.git $ cd mirotalksfu # Copy app/src/config.template.js in app/src/config.js IMPORTANT (edit it according to your needs) $ cp app/src/config.template.js app/src/config.js +# Copy .env.template to .env and edit it if needed +$ cp .env.template .env # Copy docker-compose.template.yml in docker-compose.yml and edit it if needed $ cp docker-compose.template.yml docker-compose.yml # (Optional) Get official image from Docker Hub @@ -253,6 +252,48 @@ $ docker-compose down +
+Self-Hosting + +
+ +![setup](/public/images/self-hosting.png) + +## **Requirements** + +- A `clean server` running **Ubuntu 22.04 or 24.04 LTS** +- **Root access** to the Server +- A **domain or subdomain** pointing to your server’s public IPv4 + +--- + +## Note + +When **prompted**, simply **enter your domain or subdomain**. Then wait for the installation to complete. + +```bash +# Install MiroTalk SFU +wget -qO sfu-install.sh https://docs.mirotalk.com/scripts/sfu/sfu-install.sh \ + && chmod +x sfu-install.sh \ + && ./sfu-install.sh +``` + +```bash +# Uninstall MiroTalk SFU +wget -qO sfu-uninstall.sh https://docs.mirotalk.com/scripts/sfu/sfu-uninstall.sh \ + && chmod +x sfu-uninstall.sh \ + && ./sfu-uninstall.sh +``` + +```bash +# Update MiroTalk SFU +wget -qO sfu-update.sh https://docs.mirotalk.com/scripts/sfu/sfu-update.sh \ + && chmod +x sfu-update.sh \ + && ./sfu-update.sh +``` + +
+
Embed a meeting @@ -264,12 +305,48 @@ To embed a meeting within `your service or app` using an iframe, you can use the ```html ``` +## WIdget + +To quickly add a support [widget](https://codepen.io/Miroslav-Pejic/pen/LEpzGXO) to your site, include the script in your `` and place the widget `
` at the end of your ``. Your support widget will be ready instantly! + +```html + + + + + + +
+ + +``` + +**[Explore Integrations →](https://docs.mirotalk.com/mirotalk-sfu/integration/)** +
@@ -283,53 +360,129 @@ To embed a meeting within `your service or app` using an iframe, you can use the - `Rest API:` The [API documentation](https://docs.mirotalk.com/mirotalk-sfu/api/) uses [swagger](https://swagger.io/) at https://localhost:3010/api/v1/docs or check it on live [here](https://sfu.mirotalk.com/api/v1/docs). +### 1. Get Server Statistics + +```bash +curl -X GET "http://localhost:3010/api/v1/stats" \ + -H "authorization: mirotalksfu_default_secret" \ + -H "Content-Type: application/json" + +curl -X GET "https://sfu.mirotalk.com/api/v1/stats" \ + -H "authorization: mirotalksfu_default_secret" \ + -H "Content-Type: application/json" +``` + +### 2. Get Active Meetings + +```bash +curl -X GET "http://localhost:3010/api/v1/meetings" \ + -H "authorization: mirotalksfu_default_secret" \ + -H "Content-Type: application/json" + +curl -X GET "https://sfu.mirotalk.com/api/v1/meetings" \ + -H "authorization: mirotalksfu_default_secret" \ + -H "Content-Type: application/json" +``` + +### 3. Create Meeting + +```bash +curl -X POST "http://localhost:3010/api/v1/meeting" \ + -H "authorization: mirotalksfu_default_secret" \ + -H "Content-Type: application/json" + +curl -X POST "https://sfu.mirotalk.com/api/v1/meeting" \ + -H "authorization: mirotalksfu_default_secret" \ + -H "Content-Type: application/json" +``` + +### 4. Join Meeting (Basic) + ```bash -# The response will give you the total of rooms and users. -$ curl -X GET "http://localhost:3010/api/v1/stats" -H "authorization: mirotalksfu_default_secret" -H "Content-Type: application/json" -$ curl -X GET "https://sfu.mirotalk.com/api/v1/stats" -H "authorization: mirotalksfu_default_secret" -H "Content-Type: application/json" -# The response will give you the active meetings (default disabled). -$ curl -X GET "http://localhost:3010/api/v1/meetings" -H "authorization: mirotalksfu_default_secret" -H "Content-Type: application/json" -$ curl -X GET "https://sfu.mirotalk.com/api/v1/meetings" -H "authorization: mirotalksfu_default_secret" -H "Content-Type: application/json" -# The response will give you a entrypoint / Room URL for your meeting. -$ curl -X POST "http://localhost:3010/api/v1/meeting" -H "authorization: mirotalksfu_default_secret" -H "Content-Type: application/json" -$ curl -X POST "https://sfu.mirotalk.com/api/v1/meeting" -H "authorization: mirotalksfu_default_secret" -H "Content-Type: application/json" -# The response will give you a entrypoint / URL for the direct join to the meeting. -$ curl -X POST "http://localhost:3010/api/v1/join" -H "authorization: mirotalksfu_default_secret" -H "Content-Type: application/json" --data '{"room":"test","roomPassword":"false","name":"mirotalksfu","audio":"false","video":"false","screen":"false","notify":"false","duration":"unlimited"}' -$ curl -X POST "https://sfu.mirotalk.com/api/v1/join" -H "authorization: mirotalksfu_default_secret" -H "Content-Type: application/json" --data '{"room":"test","roomPassword":"false","name":"mirotalksfu","audio":"false","video":"false","screen":"false","notify":"false","duration":"unlimited"}' -# The response will give you a entrypoint / URL for the direct join to the meeting with a token. -$ curl -X POST "http://localhost:3010/api/v1/join" -H "authorization: mirotalksfu_default_secret" -H "Content-Type: application/json" --data '{"room":"test","roomPassword":"false","name":"mirotalksfu","audio":"false","video":"false","screen":"false","notify":"false","duration":"unlimited","token":{"username":"username","password":"password","presenter":"true", "expire":"1h"}}' -$ curl -X POST "https://sfu.mirotalk.com/api/v1/join" -H "authorization: mirotalksfu_default_secret" -H "Content-Type: application/json" --data '{"room":"test","roomPassword":"false","name":"mirotalksfu","audio":"false","video":"false","screen":"false","notify":"false","duration":"unlimited","token":{"username":"username","password":"password","presenter":"true", "expire":"1h"}}' -# The response will give you a valid token for a meeting (default diabled) -$ curl -X POST "http://localhost:3010/api/v1/token" -H "authorization: mirotalksfu_default_secret" -H "Content-Type: application/json" --data '{"username":"username","password":"password","presenter":"true", "expire":"1h"}' -$ curl -X POST "https://sfu.mirotalk.com/api/v1/token" -H "authorization: mirotalksfu_default_secret" -H "Content-Type: application/json" --data '{"username":"username","password":"password","presenter":"true", "expire":"1h"}' +curl -X POST "http://localhost:3010/api/v1/join" \ + -H "authorization: mirotalksfu_default_secret" \ + -H "Content-Type: application/json" \ + --data '{"room":"test","roomPassword":false,"avatar":false,"name":"random","audio":false,"video":false,"screen":false,"chat":false,"notify":false,"duration":"unlimited"}' + +curl -X POST "https://sfu.mirotalk.com/api/v1/join" \ + -H "authorization: mirotalksfu_default_secret" \ + -H "Content-Type: application/json" \ + --data '{"room":"test","roomPassword":false,"name":"random","avatar":false,"audio":false,"video":false,"screen":false,"chat":false,"notify":false,"duration":"unlimited"}' +``` + +### 5. Join Meeting with Token + +```bash +curl -X POST "http://localhost:3010/api/v1/join" \ + -H "authorization: mirotalksfu_default_secret" \ + -H "Content-Type: application/json" \ + --data '{"room":"test","roomPassword":false,"name":"random","audio":false,"video":false,"screen":false,"chat":false,"notify":false,"duration":"unlimited","token":{"username":"username","password":"password","presenter":true,"expire":"1h"}}' + +curl -X POST "https://sfu.mirotalk.com/api/v1/join" \ + -H "authorization: mirotalksfu_default_secret" \ + -H "Content-Type: application/json" \ + --data '{"room":"test","roomPassword":false,"name":"random","audio":false,"video":false,"screen":false,"chat":false,"notify":false,"duration":"unlimited","token":{"username":"username","password":"password","presenter":true,"expire":"1h"}}' +``` + +### 6. Generate Token + +```bash +curl -X POST "http://localhost:3010/api/v1/token" \ + -H "authorization: mirotalksfu_default_secret" \ + -H "Content-Type: application/json" \ + --data '{"username":"username","password":"password","presenter":true,"expire":"1h"}' + +curl -X POST "https://sfu.mirotalk.com/api/v1/token" \ + -H "authorization: mirotalksfu_default_secret" \ + -H "Content-Type: application/json" \ + --data '{"username":"username","password":"password","presenter":true,"expire":"1h"}' ```
-Hetzner, Hostinger & Contabo +Cloudron, Hetzner, Netcup, Hostinger & Contabo
-[![Hetzner](public/sponsors/Hetzner.png)](https://hetzner.cloud/?ref=XdRifCzCK3bn) +[![Cloudron](public/sponsors/CloudronLogo.png)](https://www.cloudron.io/) + +MiroTalk integrates with [Cloudron](https://www.cloudron.io/), enabling easy, secure, and self-managed video conferencing for organizations. Cloudron automates deployment, updates, backups, and user management, letting teams focus on collaboration instead of server maintenance. Install MiroTalk in one click from the [Cloudron App Store](https://www.cloudron.io/store/index.html) and enjoy a reliable, enterprise-ready solution. + +--- + +[![Hetzner](public/sponsors/Hetzner.png)](https://www.hetzner.com) This application is running for `demonstration purposes` on [Hetzner](https://www.hetzner.com/), one of `the best` [cloud providers](https://www.hetzner.com/cloud) and [dedicated root servers](https://www.hetzner.com/dedicated-rootserver). --- -Use [my personal link](https://hetzner.cloud/?ref=XdRifCzCK3bn) to receive `€⁠20 IN CLOUD CREDITS`. +👉 Use [my personal link](https://hetzner.cloud/?ref=XdRifCzCK3bn) to receive `€⁠20 IN CLOUD CREDITS`. + +--- + +[![Netcup](public/sponsors/Netcup.png)](https://www.netcup.com/en/?ref=309627) + +Unlock `enterprise-grade performance` at a price you won’t believe. +Scalable, reliable, and built for businesses that demand more. + +👉 [Power Meets Value with Netcup Root Server](https://www.netcup.com/en/?ref=309627) --- [![Hostinger](public/advertisers/HostingerLogo.png)](https://hostinger.com/?REFERRALCODE=MIROTALK) -Fast, reliable hosting with 24/7 support and great performance. Start today! [Check out Hostinger now](https://hostinger.com/?REFERRALCODE=MIROTALK) +Fast, reliable hosting with 24/7 support and great performance. Start today! + +👉 [Check out Hostinger now](https://hostinger.com/?REFERRALCODE=MIROTALK) --- [![Contabo](public/advertisers/ContaboLogo.png)](https://www.dpbolvw.net/click-101027391-14462707) -Experience also top-tier German web hosting – dedicated servers, VPS, and web hosting at `unbeatable prices`. [Explore now here](https://www.dpbolvw.net/click-101027391-14462707) +Experience also top-tier German web hosting – dedicated servers, VPS, and web hosting at `unbeatable prices`. + +👉 [Explore now here](https://www.dpbolvw.net/click-101027391-14462707) --- @@ -391,13 +544,13 @@ To obtain a [MiroTalk SFU license](https://docs.mirotalk.com/license/licensing-o Do you find MiroTalk SFU indispensable for your needs? Join us in supporting this transformative project by [becoming a backer or sponsor](https://github.com/sponsors/miroslavpejic85). By doing so, not only will your logo prominently feature here, but you'll also drive the growth and sustainability of MiroTalk SFU. Your support is vital in ensuring that this valuable platform continues to thrive and remain accessible for all. Make an impact – back MiroTalk SFU today and be part of this exciting journey! -| | | -| ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -| [![BroadcastX](public/sponsors/BroadcastX.png)](https://broadcastx.de/) | [![Hetzner](public/sponsors/HetznerLogo.png)](https://hetzner.cloud/?ref=XdRifCzCK3bn) | -| [![LuvLounge](public/sponsors/LuvLounge.png)](https://luvlounge.ca) | [![QuestionPro](public/sponsors/QuestionPro.png)](https://www.questionpro.com) | -| [![BrowserStack](public/sponsors/BrowserStack.png)](https://www.browserstack.com) | [![CrystalSound](public/sponsors/CrystalSound.png)](https://crystalsound.ai) | -| [![Cloudron](public/sponsors/Cloudron.png)](https://cloudron.io) | [![Kiquix](public/sponsors/KiquixLogo.png)](https://kiquix.com) | -| [![LambdaTest](public/sponsors/LambdaTest.png)](https://lambdatest.com/pricing?coupon=QURFODlQUk9NT1RFUg==&refid=1149848) | | +| | | | +| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | +| [![EffectsSDK](public/sponsors/EffectsSDK.png)](https://effectssdk.ai/) | [![Hetzner](public/sponsors/HetznerLogo.png)](https://hetzner.cloud/?ref=XdRifCzCK3bn) | [![Netcup](public/sponsors/Netcup.png)](https://www.netcup.com/en/?ref=309627) | +| [![BroadcastX](public/sponsors/BroadcastX.png)](https://broadcastx.de/) | [![LuvLounge](public/sponsors/LuvLounge.png)](https://luvlounge.ca) | [![QuestionPro](public/sponsors/QuestionPro.png)](https://www.questionpro.com) | +| [![BrowserStack](public/sponsors/BrowserStack.png)](https://www.browserstack.com) | [![CrystalSound](public/sponsors/CrystalSound.png)](https://crystalsound.ai) | [![Cloudron](public/sponsors/Cloudron.png)](https://cloudron.io) | +| [![Kiquix](public/sponsors/KiquixLogo.png)](https://kiquix.com) | [![TestMuAI](public/sponsors/TestMuAIBlack.svg)](https://www.testmuai.com/?utm_medium=sponsor&utm_source=mirotalk) | | +| |
@@ -406,14 +559,28 @@ Do you find MiroTalk SFU indispensable for your needs? Join us in supporting thi --- -| | | -| ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | -| [![Hostinger](public/advertisers/Hostinger.png)](https://hostinger.com/?REFERRALCODE=MIROTALK) | [![Contabo](public/advertisers/Contabo.png)](https://www.dpbolvw.net/click-101027391-14462707) | +| | | | +| ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | +| [![Hostinger](public/advertisers/Hostinger.png)](https://hostinger.com/?REFERRALCODE=MIROTALK) | [![Contabo](public/advertisers/Contabo.png)](https://www.dpbolvw.net/click-101027391-14462707) | [![Rambox](public/advertisers/RamboxLogo.png)](https://rambox.app?via=mirotalk) | --- +## EffectsSDK ✨ + +[![EffectsSDK](public/sponsors/EffectsSDK.png)](https://effectssdk.ai/) + +`Enhance your video conferencing` experience with `advanced virtual backgrounds` and `noise suppression`. EffectsSDK offers powerful SDKs and plugins for fast integration. + +**Explore:** + +- 🎥 **[AI Video Effects Extension](https://chromewebstore.google.com/detail/effetti-webcam-ai-+-regis/iedbphhbpflhgpihkcceocomcdnemcbj)** – Add virtual backgrounds and effects to your webcam. +- 🔊 **[Noise Cancelling Extension](https://chromewebstore.google.com/detail/noise-cancelling-app/njmhcidcdbaannpafjdljminaigdgolj)** – Reduce background noise for clearer audio. +- 🛠️ **[Integrate EffectsSDK](https://github.com/EffectsSDK)** – Access SDKs and plugins for custom solutions. + +--- + ## Diving into Additional MiroTalk Projects:
@@ -457,3 +624,11 @@ Try also [MiroTalk WEB](https://github.com/miroslavpejic85/mirotalkwebrtc) a pla This project is tested with [BrowserStack](https://www.browserstack.com). --- + +![Star History Chart](https://app.repohistory.com/api/svg?repo=miroslavpejic85/mirotalksfu&type=Date&background=0D1117&color=62C3F8) + +--- + +

+ Built with ❤️ by Miroslav and the open-source community +

diff --git a/app/api/README.md b/app/api/README.md index 12d731716..0c3fb4c16 100644 --- a/app/api/README.md +++ b/app/api/README.md @@ -43,7 +43,7 @@ Embedding a meeting into a `service` or `app` requires using an `iframe` with th ```html @@ -55,7 +55,7 @@ Develop your `website` or `application`, and bring `video meetings` in with a si ```html diff --git a/app/api/join/join.js b/app/api/join/join.js index 0a6efe9d4..4a790e3ec 100644 --- a/app/api/join/join.js +++ b/app/api/join/join.js @@ -19,9 +19,11 @@ async function getJoin() { room: 'test', roomPassword: false, name: 'mirotalksfu', - audio: true, - video: true, - screen: true, + avatar: false, + audio: false, + video: false, + screen: false, + chat: false, hide: false, notify: true, duration: 'unlimited', diff --git a/app/api/join/join.php b/app/api/join/join.php index eafb0626a..f1ba471b5 100644 --- a/app/api/join/join.php +++ b/app/api/join/join.php @@ -20,9 +20,11 @@ "room" => "test", "roomPassword" => false, "name" => "mirotalksfu", - "audio" => true, - "video" => true, - "screen" => true, + "avatar" => false, + "audio" => false, + "video" => false, + "screen" => false, + "chat" => false, "hide" => false, "notify" => true, "duration" => "unlimited", diff --git a/app/api/join/join.py b/app/api/join/join.py index 2e737087e..c27104021 100644 --- a/app/api/join/join.py +++ b/app/api/join/join.py @@ -15,9 +15,11 @@ "room": "test", "roomPassword": "false", "name": "mirotalksfu", - "audio": "true", - "video": "true", - "screen": "true", + "avatar": "false", + "audio": "false", + "video": "false", + "screen": "false", + "chat": "false", "hide": "false", "notify": "true", "duration": "unlimited", diff --git a/app/api/join/join.sh b/app/api/join/join.sh index df7654575..5c3de0fe3 100755 --- a/app/api/join/join.sh +++ b/app/api/join/join.sh @@ -1,11 +1,34 @@ #!/bin/bash +# Configuration API_KEY_SECRET="mirotalksfu_default_secret" MIROTALK_URL="https://sfu.mirotalk.com/api/v1/join" +# Alternative URL for local testing: # MIROTALK_URL="http://localhost:3010/api/v1/join" -curl $MIROTALK_URL \ - --header "authorization: $API_KEY_SECRET" \ - --header "Content-Type: application/json" \ - --data '{"room":"test","roomPassword":"false","name":"mirotalksfu","audio":"true","video":"true","screen":"false","hide":"false","notify":"true","duration":"unlimited","token":{"username":"username","password":"password","presenter":"true", "expire":"1h"}}' \ - --request POST \ No newline at end of file +# Request data with proper JSON formatting +REQUEST_DATA='{ + "room": "test", + "roomPassword": false, + "name": "mirotalksfu", + "avatar": false, + "audio": false, + "video": false, + "screen": false, + "chat": false, + "hide": false, + "notify": true, + "duration": "unlimited", + "token": { + "username": "username", + "password": "password", + "presenter": true, + "expire": "1h" + } +}' + +# Make the API request +curl -X POST "$MIROTALK_URL" \ + -H "Authorization: $API_KEY_SECRET" \ + -H "Content-Type: application/json" \ + -d "$REQUEST_DATA" \ No newline at end of file diff --git a/app/api/meeting/endMeeting.js b/app/api/meeting/endMeeting.js new file mode 100644 index 000000000..8ba5041cf --- /dev/null +++ b/app/api/meeting/endMeeting.js @@ -0,0 +1,35 @@ +'use strict'; + +async function endMeeting() { + try { + // Use dynamic import with await + const { default: fetch } = await import('node-fetch'); + + const API_KEY_SECRET = 'mirotalksfu_default_secret'; + const MIROTALK_URL = 'https://sfu.mirotalk.com/api/v1/meeting'; + // const MIROTALK_URL = 'http://localhost:3010/api/v1/meeting'; + + const ROOM = 'test'; // Room name to end + + const response = await fetch(`${MIROTALK_URL}/${ROOM}`, { + method: 'DELETE', + headers: { + authorization: API_KEY_SECRET, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + // redirect: 'https://example.com/meeting-ended', // Optional: URL to redirect peers to (if empty, peers go to home page) + }), + }); + const data = await response.json(); + if (data.error) { + console.log('Error:', data.error); + } else { + console.log('result:', data); + } + } catch (error) { + console.error('Error fetching data:', error); + } +} + +endMeeting(); diff --git a/app/api/meeting/endMeeting.php b/app/api/meeting/endMeeting.php new file mode 100644 index 000000000..7d2c2bff5 --- /dev/null +++ b/app/api/meeting/endMeeting.php @@ -0,0 +1,35 @@ + 'https://example.com/meeting-ended', +]); + +$ch = curl_init(); +curl_setopt($ch, CURLOPT_URL, "$MIROTALK_URL/$ROOM"); +curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); +curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE'); +curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + +$headers = [ + 'authorization:' . $API_KEY_SECRET, + 'Content-Type: application/json' +]; + +curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); +$response = curl_exec($ch); +$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + +curl_close($ch); + +echo "Status code: $httpcode \n"; +$data = json_decode($response); +echo "result: "; +print_r($data); +echo "\n"; diff --git a/app/api/meeting/endMeeting.py b/app/api/meeting/endMeeting.py new file mode 100644 index 000000000..e10041aef --- /dev/null +++ b/app/api/meeting/endMeeting.py @@ -0,0 +1,29 @@ +# pip3 install requests +import requests +import json + +API_KEY_SECRET = "mirotalksfu_default_secret" +MIROTALK_URL = "https://sfu.mirotalk.com/api/v1/meeting" +# MIROTALK_URL = "http://localhost:3010/api/v1/meeting" + +ROOM = "test" + +headers = { + "authorization": API_KEY_SECRET, + "Content-Type": "application/json", +} + +# Optional: redirect URL (leave empty for home page) +data = { + # "redirect": "https://example.com/meeting-ended", +} + +response = requests.delete( + f"{MIROTALK_URL}/{ROOM}", + headers=headers, + json=data +) + +print("Status code:", response.status_code) +data = json.loads(response.text) +print("result:", data) diff --git a/app/api/meeting/endMeeting.sh b/app/api/meeting/endMeeting.sh new file mode 100644 index 000000000..2bf554c0e --- /dev/null +++ b/app/api/meeting/endMeeting.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +API_KEY_SECRET="mirotalksfu_default_secret" +MIROTALK_URL="https://sfu.mirotalk.com/api/v1/meeting" +# MIROTALK_URL="http://localhost:3010/api/v1/meeting" + +ROOM="test" + +# Optional: redirect URL (leave empty object for home page) +# BODY='{"redirect": "https://example.com/meeting-ended"}' +BODY='{}' + +curl "$MIROTALK_URL/$ROOM" \ + --header "authorization: $API_KEY_SECRET" \ + --header "Content-Type: application/json" \ + --data "$BODY" \ + --request DELETE diff --git a/app/api/swagger.yaml b/app/api/swagger.yaml index 0ece61779..701b57d24 100644 --- a/app/api/swagger.yaml +++ b/app/api/swagger.yaml @@ -66,6 +66,39 @@ paths: $ref: '#/definitions/MeetingResponse' '403': description: 'Unauthorized!' + /meeting/{room}: + delete: + tags: + - 'meeting' + summary: 'End meeting' + description: 'End an active meeting by room name. All connected peers will be disconnected and redirected to the specified URL or home page.' + parameters: + - in: path + name: room + required: true + type: string + description: 'The room name/ID of the meeting to end' + - in: body + name: body + required: false + description: 'Optional body with redirect URL' + schema: + $ref: '#/definitions/EndMeetingRequest' + consumes: + - 'application/json' + produces: + - 'application/json' + security: + - secretApiKey: [] + responses: + '200': + description: 'Meeting ended successfully' + schema: + $ref: '#/definitions/EndMeetingResponse' + '403': + description: 'Unauthorized!' + '404': + description: 'Room not found' /join: post: tags: @@ -162,6 +195,9 @@ definitions: name: type: string default: 'mirotalksfu' + avatar: + type: ['boolean', 'string'] # Allow boolean or string type + default: false audio: type: boolean default: false @@ -171,6 +207,9 @@ definitions: screen: type: boolean default: false + chat: + type: boolean + default: false hide: type: boolean default: false @@ -197,6 +236,22 @@ definitions: expire: type: string default: '1h' + EndMeetingRequest: + type: object + properties: + redirect: + type: string + description: 'URL to redirect peers to after the meeting ends. If empty, peers are redirected to the home page.' + default: '' + EndMeetingResponse: + type: 'object' + properties: + success: + type: boolean + message: + type: string + room: + type: string JoinResponse: type: 'object' properties: @@ -212,6 +267,8 @@ definitions: properties: name: type: string + avatar: + type: string presenter: type: boolean video: diff --git a/app/src/Discord.js b/app/src/Discord.js index 2b8f5ffd8..e719a02df 100644 --- a/app/src/Discord.js +++ b/app/src/Discord.js @@ -29,7 +29,7 @@ class Discord { } setupEventHandlers() { - this.discordClient.once('ready', () => { + this.discordClient.once('clientReady', () => { log.info(`Discord Bot Logged in as ${this.discordClient.user.tag}!`, '😎'); }); diff --git a/app/src/FixDurationOrRemux.js b/app/src/FixDurationOrRemux.js new file mode 100644 index 000000000..a554fe7dc --- /dev/null +++ b/app/src/FixDurationOrRemux.js @@ -0,0 +1,64 @@ +'use strict'; + +const { spawnSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); +const { fixWebmDurationBuffer } = require('./FixWebmDurationBuffer'); + +const Logger = require('./Logger'); +const log = new Logger('DurationOrRemux'); + +function hasFfmpeg() { + const r = spawnSync('ffmpeg', ['-version'], { stdio: 'ignore' }); + return r.status === 0; +} + +function remuxWithFfmpeg(inputPath, format = 'webm') { + const dir = path.dirname(inputPath); + const base = path.basename(inputPath, path.extname(inputPath)); + const out = path.join(dir, `${base}.fixed.${format}`); + + const args = [ + '-hide_banner', + '-loglevel', + 'error', + '-y', + '-i', + inputPath, + '-c', + 'copy', + ...(format === 'mp4' ? ['-movflags', '+faststart'] : []), + out, + ]; + const r = spawnSync('ffmpeg', args); + if (r.status !== 0 || !fs.existsSync(out)) return null; + + fs.renameSync(out, inputPath); + return inputPath; +} + +function fixDurationOrRemux(inputPath, durationMs) { + const ext = path.extname(inputPath).toLowerCase(); + const isWebm = ext === '.webm'; + const isMp4 = ext === '.mp4'; + + if (hasFfmpeg() && (isWebm || isMp4)) { + const ok = remuxWithFfmpeg(inputPath, isMp4 ? 'mp4' : 'webm'); + log.debug('ffmpeg detected remuxWithFfmpeg:', ok); + if (ok) return true; + } + + if (isWebm && Number.isFinite(durationMs)) { + const inBuf = fs.readFileSync(inputPath); + const outBuf = fixWebmDurationBuffer(inBuf, Number(durationMs)); + if (outBuf && outBuf.length) { + fs.writeFileSync(inputPath, outBuf); + log.debug('No ffmpeg detected fixWebmDurationBuffer - true'); + return true; + } + } + log.debug('No ffmpeg detected fixWebmDurationBuffer - false'); + return false; +} + +module.exports = { fixDurationOrRemux }; diff --git a/app/src/FixWebmDurationBuffer.js b/app/src/FixWebmDurationBuffer.js new file mode 100644 index 000000000..a806ef156 --- /dev/null +++ b/app/src/FixWebmDurationBuffer.js @@ -0,0 +1,192 @@ +'use strict'; + +// Minimal Node helper: fixWebmDurationBuffer(buffer: Buffer, durationMs: number) -> Buffer +function padHex(h) { + return h.length % 2 === 1 ? '0' + h : h; +} + +class WebmBase { + constructor(name, type) { + this.name = name || 'Unknown'; + this.type = type || 'Unknown'; + } + updateBySource() {} + setSource(s) { + this.source = s; + this.updateBySource(); + } + updateByData() {} + setData(d) { + this.data = d; + this.updateByData(); + } +} +class WebmUint extends WebmBase { + constructor() { + super('Uint', 'Uint'); + } + updateBySource() { + this.data = ''; + for (let i = 0; i < this.source.length; i++) this.data += padHex(this.source[i].toString(16)); + } + updateByData() { + const len = this.data.length / 2; + this.source = new Uint8Array(len); + for (let i = 0; i < len; i++) this.source[i] = parseInt(this.data.substr(i * 2, 2), 16); + } + getValue() { + return parseInt(this.data, 16); + } + setValue(v) { + this.setData(padHex(v.toString(16))); + } +} +class WebmFloat extends WebmBase { + constructor() { + super('Float', 'Float'); + } + _arrType() { + return this.source && this.source.length === 4 ? Float32Array : Float64Array; + } + updateBySource() { + const bytes = this.source.slice().reverse(); + const T = this._arrType(); + this.data = new T(bytes.buffer)[0]; + } + updateByData() { + const T = this._arrType(); + const fa = new T([this.data]); + const bytes = new Uint8Array(fa.buffer); + this.source = bytes.reverse(); + } + getValue() { + return this.data; + } + setValue(v) { + this.setData(v); + } +} +class WebmContainer extends WebmBase { + constructor(name) { + super(name || 'Container', 'Container'); + } + readByte() { + return this.source[this.offset++]; + } + readVint() { + const b0 = this.readByte(); + const bytes = 8 - b0.toString(2).length; + let v = b0 - (1 << (7 - bytes)); + for (let i = 0; i < bytes; i++) { + v = v * 256 + this.readByte(); + } + return v; + } + updateBySource() { + this.data = []; + for (this.offset = 0; this.offset < this.source.length; ) { + const id = this.readVint(); + const len = this.readVint(); + const end = Math.min(this.offset + len, this.source.length); + const bytes = this.source.slice(this.offset, end); + let ctor = WebmBase; + if (id === ID.Segment || id === ID.Info) ctor = WebmContainer; + else if (id === ID.TimecodeScale) ctor = WebmUint; + else if (id === ID.Duration) ctor = WebmFloat; + const elem = new ctor(); + elem.setSource(bytes); + this.data.push({ id, data: elem }); + this.offset = end; + } + } + writeVint(x, draft) { + let bytes = 1, + flag = 0x80; + while (x >= flag && bytes < 8) { + bytes++; + flag *= 0x80; + } + if (!draft) { + let val = flag + x; + for (let i = bytes - 1; i >= 0; i--) { + const c = val % 256; + this.source[this.offset + i] = c; + val = (val - c) / 256; + } + } + this.offset += bytes; + } + writeSections(draft) { + this.offset = 0; + for (const s of this.data) { + const content = s.data.source; + const len = content.length; + this.writeVint(s.id, draft); + this.writeVint(len, draft); + if (!draft) this.source.set(content, this.offset); + this.offset += len; + } + return this.offset; + } + updateByData() { + const len = this.writeSections(true); + this.source = new Uint8Array(len); + this.writeSections(false); + } + getSectionById(id) { + for (const s of this.data) { + if (s.id === id) return s.data; + } + return null; + } +} +class WebmFile extends WebmContainer { + constructor(src) { + super('File'); + this.setSource(src); + } + toBuffer() { + return Buffer.from(this.source.buffer); + } + fixDuration(durationMs) { + const segment = this.getSectionById(ID.Segment); + if (!segment) return false; + const info = segment.getSectionById(ID.Info); + if (!info) return false; + let scale = info.getSectionById(ID.TimecodeScale); + if (!scale) return false; + scale.setValue(1000000); // 1ms + let dur = info.getSectionById(ID.Duration); + if (dur) { + if (dur.getValue() > 0) return false; + dur.setValue(durationMs); + } else { + dur = new WebmFloat(); + dur.setValue(durationMs); + info.data.push({ id: ID.Duration, data: dur }); + } + info.updateByData(); + segment.updateByData(); + this.updateByData(); + return true; + } +} +const ID = { + Segment: 0x8538067, // 0x18538067 + Info: 0x549a966, // 0x1549A966 + TimecodeScale: 0xad7b1, // 0x2AD7B1 + Duration: 0x489, // 0x4489 +}; + +function fixWebmDurationBuffer(inputBuffer, durationMs) { + if (!Buffer.isBuffer(inputBuffer) || !Number.isFinite(durationMs)) return inputBuffer; + try { + const file = new WebmFile(new Uint8Array(inputBuffer)); + const fixed = file.fixDuration(Math.max(0, Math.round(durationMs))); + return fixed ? file.toBuffer() : inputBuffer; + } catch { + return inputBuffer; + } +} + +module.exports = { fixWebmDurationBuffer }; diff --git a/app/src/HtmlInjector.js b/app/src/HtmlInjector.js index 1fab646f6..835689fc6 100644 --- a/app/src/HtmlInjector.js +++ b/app/src/HtmlInjector.js @@ -1,5 +1,7 @@ const fs = require('fs'); +const chokidar = require('chokidar'); + const Logger = require('./Logger'); const log = new Logger('HtmlInjector'); @@ -10,6 +12,7 @@ class HtmlInjector { this.cache = {}; // Object to store cached files this.config = config; // Configuration containing metadata (OG, title, etc.) this.injectData = this.getInjectData(); // Initialize dynamic injection data + this.watcher = null; // File watcher instance this.preloadPages(filesPath); // Preload pages at startup this.watchFiles(filesPath); // Watch files for changes log.info('filesPath cached', this.filesPath); @@ -45,25 +48,34 @@ class HtmlInjector { filePaths.forEach((filePath) => this.loadFileToCache(filePath)); } - // Function to watch a file for changes and reload the cache - watchFileForChanges(filePath) { - fs.watch(filePath, (eventType) => { - if (eventType === 'change') { - log.debug(`File changed: ${filePath}`); - this.loadFileToCache(filePath); - log.debug(`Reload the file ${filePath} into cache`); - } + // Function to watch files for changes using chokidar + watchFiles(filePaths) { + if (this.watcher) { + this.watcher.close(); // Close existing watcher if any + } + + this.watcher = chokidar.watch(filePaths, { + persistent: true, + ignoreInitial: true, // Ignore initial 'add' events }); - } - // Function to watch all files for changes - watchFiles(filePaths) { - filePaths.forEach((filePath) => this.watchFileForChanges(filePath)); + this.watcher + .on('change', (filePath) => { + log.debug(`File changed: ${filePath}`); + this.loadFileToCache(filePath); + log.debug(`Reloaded file ${filePath} into cache`); + }) + .on('error', (error) => { + log.error(`Watcher error: ${error.message}`); + }); } // Function to inject dynamic data (e.g., OG, TITLE, etc.) into a given file injectHtml(filePath, res) { - // return res.send(this.cache[filePath]); + // Check if HTML injection is enabled in the config + if (!this.config?.htmlInjection) { + return res.send(this.cache[filePath]); + } if (!this.cache[filePath]) { log.error(`File not cached: ${filePath}`); @@ -77,7 +89,7 @@ class HtmlInjector { // Replace placeholders with dynamic data (OG, TITLE, etc.) const modifiedHTML = this.cache[filePath].replace( /{{(OG_[A-Z_]+)}}/g, - (_, key) => this.injectData[key] || '', + (_, key) => this.injectData[key] || '' ); if (!res.headersSent) { @@ -90,6 +102,13 @@ class HtmlInjector { } } } + + // Cleanup watcher when the instance is no longer needed + cleanup() { + if (this.watcher) { + this.watcher.close(); + } + } } module.exports = HtmlInjector; diff --git a/app/src/Logger.js b/app/src/Logger.js index a9a224d6f..bf4a66ef4 100644 --- a/app/src/Logger.js +++ b/app/src/Logger.js @@ -1,66 +1,102 @@ 'use strict'; const util = require('util'); - const colors = require('colors'); - const config = require('./config'); -config.console.colors ? colors.enable() : colors.disable(); +config.system?.console?.colors ? colors.enable() : colors.disable(); const options = { depth: null, - colors: true, + colors: config.system?.console?.colors || false, }; + +const LOGS_JSON = config.system?.console?.json; +const LOGS_JSON_PRETTY = config.system?.console?.json_pretty; + module.exports = class Logger { constructor(appName = 'miroTalkSfu') { this.appName = colors.yellow(appName); - this.debugOn = config.console.debug; + this.debugOn = config.system?.console?.debug; this.timeStart = Date.now(); this.timeEnd = null; this.timeElapsedMs = null; this.tzOptions = { - timeZone: process.env.TZ || config.console.timeZone || 'UTC', + timeZone: process.env.TZ || config.system?.console?.timeZone || 'UTC', hour12: false, }; } + jsonLog(level, appName, msg, op, extra = {}) { + const logObj = { + timestamp: new Date().toISOString(), + level, + app: appName, + message: msg, + ...extra, + }; + if (op && typeof op === 'object' && Object.keys(op).length > 0) { + logObj.data = op; + } + LOGS_JSON_PRETTY ? console.log(JSON.stringify(logObj, null, 2)) : console.log(JSON.stringify(logObj)); + } + debug(msg, op = '') { if (this.debugOn) { this.timeEnd = Date.now(); this.timeElapsedMs = this.getFormatTime(Math.floor(this.timeEnd - this.timeStart)); - console.debug( - '[' + this.getDateTime() + '] [' + this.appName + '] ' + msg, - util.inspect(op, options), - this.timeElapsedMs, - ); + if (LOGS_JSON) { + this.jsonLog('debug', this.appName, msg, op, { elapsed: this.timeElapsedMs }); + } else { + console.debug( + '[' + this.getDateTime() + '] [' + this.appName + '] ' + msg, + util.inspect(op, options), + this.timeElapsedMs + ); + } this.timeStart = Date.now(); } } log(msg, op = '') { - console.log('[' + this.getDateTime() + '] [' + this.appName + '] ' + msg, util.inspect(op, options)); + if (LOGS_JSON) { + this.jsonLog('log', this.appName, msg, op); + } else { + console.log('[' + this.getDateTime() + '] [' + this.appName + '] ' + msg, util.inspect(op, options)); + } } info(msg, op = '') { - console.info( - '[' + this.getDateTime() + '] [' + this.appName + '] ' + colors.green(msg), - util.inspect(op, options), - ); + if (LOGS_JSON) { + this.jsonLog('info', this.appName, msg, op); + } else { + console.info( + '[' + this.getDateTime() + '] [' + this.appName + '] ' + colors.green(msg), + util.inspect(op, options) + ); + } } warn(msg, op = '') { - console.warn( - '[' + this.getDateTime() + '] [' + this.appName + '] ' + colors.yellow(msg), - util.inspect(op, options), - ); + if (LOGS_JSON) { + this.jsonLog('warn', this.appName, msg, op); + } else { + console.warn( + '[' + this.getDateTime() + '] [' + this.appName + '] ' + colors.yellow(msg), + util.inspect(op, options) + ); + } } error(msg, op = '') { - console.error( - '[' + this.getDateTime() + '] [' + this.appName + '] ' + colors.red(msg), - util.inspect(op, options), - ); + if (LOGS_JSON) { + this.jsonLog('error', this.appName, msg, op); + } else { + console.error( + '[' + this.getDateTime() + '] [' + this.appName + '] ' + colors.red(msg), + util.inspect(op, options) + ); + } } getDateTime(color = true) { diff --git a/app/src/Mattermost.js b/app/src/Mattermost.js index d463b27c6..da2ac350d 100644 --- a/app/src/Mattermost.js +++ b/app/src/Mattermost.js @@ -20,12 +20,12 @@ class Mattermost { password, commands = '/sfu', texts = '/sfu', - } = config.mattermost || {}; + } = config.integrations.mattermost || {}; if (!enabled) return; // Check if Mattermost integration is enabled this.app = app; - this.allowed = config.api.allowed && config.api.allowed.mattermost; + this.allowed = config.api?.allowed?.mattermost || false; this.token = token; this.serverUrl = serverUrl; this.username = username; @@ -77,12 +77,8 @@ class Mattermost { processInput(input, payload, req) { for (const cmd of [...this.commands, ...this.texts]) { if (input.trim() === cmd.name) { - switch (cmd.name) { - case '/sfu': - payload.text = `${cmd.message} ${this.getMeetingURL(req)}`; - break; - default: - break; + if (cmd.message) { + payload.text = `${cmd.message} ${this.getMeetingURL(req)}`; } return true; } diff --git a/app/src/MutexManager.js b/app/src/MutexManager.js new file mode 100644 index 000000000..8de195d38 --- /dev/null +++ b/app/src/MutexManager.js @@ -0,0 +1,29 @@ +'use strict'; + +const { Mutex } = require('async-mutex'); + +// In-memory file mutex registry +const fileLocks = new Map(); + +function getFileMutex(filePath) { + if (!fileLocks.has(filePath)) { + fileLocks.set(filePath, new Mutex()); + } + return fileLocks.get(filePath); +} + +async function withFileLock(filePath, fn) { + const mutex = getFileMutex(filePath); + const release = await mutex.acquire(); + + try { + return await fn(); + } finally { + release(); + if (!mutex.isLocked()) { + fileLocks.delete(filePath); // Clean up when no one is waiting + } + } +} + +module.exports = { withFileLock }; diff --git a/app/src/Peer.js b/app/src/Peer.js index 0cf114e01..46d25b997 100644 --- a/app/src/Peer.js +++ b/app/src/Peer.js @@ -10,6 +10,7 @@ module.exports = class Peer { const { peer_uuid, peer_name, + peer_avatar, peer_presenter, peer_audio, peer_audio_volume, @@ -17,12 +18,14 @@ module.exports = class Peer { peer_video_privacy, peer_recording, peer_hand, + peer_lobby, } = peer_info; this.id = socket_id; this.peer_info = peer_info; this.peer_uuid = peer_uuid; this.peer_name = peer_name; + this.peer_avatar = peer_avatar; this.peer_presenter = peer_presenter; this.peer_audio = peer_audio; this.peer_video = peer_video; @@ -30,6 +33,7 @@ module.exports = class Peer { this.peer_video_privacy = peer_video_privacy; this.peer_recording = peer_recording; this.peer_hand = peer_hand; + this.peer_lobby = peer_lobby; this.transports = new Map(); this.consumers = new Map(); @@ -81,6 +85,10 @@ module.exports = class Peer { this.peer_info.peer_audio_volume = data.volume; this.peer_audio_volume = data.volume; break; + case 'lobby': + this.peer_info.peer_lobby = data.status; + this.peer_lobby = data.status; + break; default: break; } @@ -90,6 +98,10 @@ module.exports = class Peer { // TRANSPORT // #################################################### + hasTransport(transport_id) { + return this.transports.has(transport_id); + } + getTransports() { return JSON.parse(JSON.stringify([...this.transports])); } @@ -107,49 +119,34 @@ module.exports = class Peer { } async connectTransport(transport_id, dtlsParameters) { + if (!transport_id || !dtlsParameters) { + throw new Error('Missing required parameters for connecting a transport'); + } + if (!this.transports.has(transport_id)) { throw new Error(`Transport with ID ${transport_id} not found`); } + const transport = this.transports.get(transport_id); + try { - await this.transports.get(transport_id).connect({ - dtlsParameters: dtlsParameters, + // Connect the transport + await transport.connect({ dtlsParameters }); + log.debug('Transport connected successfully', { + transport_id, + peer_name: this.peer_name, }); } catch (error) { - log.error(`Failed to connect transport with ID ${transport_id}`, error); + log.error(`Failed to connect transport with ID ${transport_id}`, { + error: error.message, + peer_name: this.peer_name, + }); throw new Error(`Failed to connect transport with ID ${transport_id}`); } return true; } - close() { - this.transports.forEach((transport, transport_id) => { - try { - transport.close(); - this.delTransport(transport_id); - log.debug('Closed and deleted peer transport', { - transportInternal: transport.internal, - transport_closed: transport.closed, - }); - } catch (error) { - log.warn(`Error closing transport with ID ${transport_id}`, error.message); - } - }); - - const peerTransports = this.getTransports(); - const peerProducers = this.getProducers(); - const peerConsumers = this.getConsumers(); - - log.debug('CLOSE PEER - CHECK TRANSPORTS | PRODUCERS | CONSUMERS', { - peer_id: this.id, - peer_name: this.peer_name, - peerTransports: peerTransports, - peerProducers: peerProducers, - peerConsumers: peerConsumers, - }); - } - // #################################################### // PRODUCER // #################################################### @@ -171,11 +168,19 @@ module.exports = class Peer { } async createProducer(producerTransportId, producer_rtpParameters, producer_kind, producer_type) { + if (!producerTransportId || !producer_rtpParameters || !producer_kind || !producer_type) { + throw new Error('Missing required parameters for creating a producer'); + } + if (!this.transports.has(producerTransportId)) { throw new Error(`Producer transport with ID ${producerTransportId} not found`); } - const producerTransport = this.transports.get(producerTransportId); + const producerTransport = this.getTransport(producerTransportId); + + if (!producerTransport) { + throw new Error(`Consumer transport with ID ${producerTransportId} not found for peer ${this.peer_name}`); + } let producer; try { @@ -183,17 +188,25 @@ module.exports = class Peer { kind: producer_kind, rtpParameters: producer_rtpParameters, }); + + this.addProducer(producer.id, producer); } catch (error) { - log.error(`Error creating producer for transport ID ${producerTransportId}:`, error); + log.error(`Error creating producer for transport ID ${producerTransportId}`, { + error: error.message, + producer_kind, + producer_type, + }); throw new Error(`Failed to create producer for transport ID ${producerTransportId}`); } + if (!producer) { + throw new Error(`Producer creation failed for transport ID ${producerTransportId}`); + } + const { id, appData, type, kind, rtpParameters } = producer; appData.mediaType = producer_type; - this.addProducer(id, producer); - if (['simulcast', 'svc'].includes(type)) { const { scalabilityMode } = rtpParameters.encodings[0]; const spatialLayer = parseInt(scalabilityMode.substring(1, 2)); // 1/2/3 @@ -205,10 +218,10 @@ module.exports = class Peer { temporalLayer, }); } else { - log.debug('Producer ----->', { type, kind }); + log.debug('Producer created ----->', { type, kind }); } - producer.on('transportclose', () => { + producer.once('transportclose', () => { log.debug('Producer "transportclose" event', { producerId: id }); this.closeProducer(id); }); @@ -220,23 +233,32 @@ module.exports = class Peer { if (!this.producers.has(producer_id)) return; const producer = this.getProducer(producer_id); - const { id, kind, type, appData } = producer; try { - producer.close(); + if (!producer.closed) { + producer.close(); + } + + log.debug('Producer closed successfully', { + producer_id: producer.id, + peer_name: this.peer_name, + kind: producer.kind, + type: producer.type, + appData: producer.appData, + }); } catch (error) { - log.warn('Close Producer', error.message); + log.error(`Error closing producer with ID ${producer_id}`, { + error: error.message, + peer_name: this.peer_name, + }); } this.delProducer(producer_id); - log.debug('Producer closed and deleted', { - peer_name: this.peer_name, - kind: kind, - type: type, - appData: appData, - producer_id: id, + log.debug('Producer removed from peer', { + producer_id: producer.id, producer_closed: producer.closed, + peer_name: this.peer_name, }); } @@ -261,11 +283,19 @@ module.exports = class Peer { } async createConsumer(consumer_transport_id, producerId, rtpCapabilities) { + if (!consumer_transport_id || !producerId || !rtpCapabilities) { + throw new Error('Missing required parameters for creating a consumer'); + } + if (!this.transports.has(consumer_transport_id)) { throw new Error(`Consumer transport with ID ${consumer_transport_id} not found`); } - const consumerTransport = this.transports.get(consumer_transport_id); + const consumerTransport = this.getTransport(consumer_transport_id); + + if (!consumerTransport) { + throw new Error(`Consumer transport with ID ${consumer_transport_id} not found for peer ${this.peer_name}`); + } let consumer; try { @@ -273,17 +303,24 @@ module.exports = class Peer { producerId, rtpCapabilities, enableRtx: true, // Enable NACK for OPUS. - paused: true, - ignoreDtx: true, + paused: true, // Start the consumer in a paused state + ignoreDtx: true, // Ignore DTX (Discontinuous Transmission) }); + + this.addConsumer(consumer.id, consumer); } catch (error) { - log.error(`Error creating consumer for transport ID ${consumer_transport_id}`, error); + log.error(`Error creating consumer for transport ID ${consumer_transport_id}`, { + error: error.message, + producerId, + }); throw new Error(`Failed to create consumer for transport ID ${consumer_transport_id}`); } - const { id, type, kind, rtpParameters, producerPaused } = consumer; + if (!consumer) { + throw new Error(`Consumer creation failed for transport ID ${consumer_transport_id}`); + } - this.addConsumer(id, consumer); + const { id, type, kind, rtpParameters, producerPaused } = consumer; if (['simulcast', 'svc'].includes(type)) { // simulcast - L1T3/L2T3/L3T3 | svc - L3T3 @@ -308,23 +345,23 @@ module.exports = class Peer { }); } } else { - log.debug('Consumer ----->', { type, kind }); + log.debug('Consumer created ----->', { type, kind }); } - consumer.on('transportclose', () => { + consumer.once('transportclose', () => { log.debug('Consumer "transportclose" event', { consumerId: id }); this.removeConsumer(id); }); return { - consumer: consumer, + consumer, params: { producerId, - id: id, - kind: kind, - rtpParameters: rtpParameters, - type: type, - producerPaused: producerPaused, + id, + kind, + rtpParameters, + type, + producerPaused, }, }; } @@ -333,22 +370,98 @@ module.exports = class Peer { if (!this.consumers.has(consumer_id)) return; const consumer = this.getConsumer(consumer_id); - const { id, kind, type } = consumer; try { - consumer.close(); + if (!consumer.closed) { + consumer.close(); + } + + log.debug('Consumer closed successfully', { + consumer_id: consumer.id, + peer_name: this.peer_name, + kind: consumer.kind, + type: consumer.type, + }); } catch (error) { - log.warn('Close Consumer', error.message); + log.error(`Error closing consumer with ID ${consumer_id}`, { + error: error.message, + peer_name: this.peer_name, + }); } this.delConsumer(consumer_id); - log.debug('Consumer closed and deleted', { - peer_name: this.peer_name, - kind: kind, - type: type, - consumer_id: id, + log.debug('Consumer removed from peer', { + consumer_id: consumer.id, consumer_closed: consumer.closed, + peer_name: this.peer_name, + }); + } + + // #################################################### + // CLOSE PEER + // #################################################### + + close() { + log.debug('Starting peer cleanup', { + peer_id: this.id, + peer_name: this.peer_name, + transports: this.transports.size, + producers: this.producers.size, + consumers: this.consumers.size, + }); + + // Close all consumers first + for (const [consumer_id, consumer] of this.consumers.entries()) { + try { + if (!consumer.closed) { + consumer.close(); + } + } catch (error) { + log.warn('Error closing consumer during peer cleanup', { + consumer_id, + error: error.message, + }); + } + } + this.consumers.clear(); + + // Close all producers + for (const [producer_id, producer] of this.producers.entries()) { + try { + if (!producer.closed) { + producer.close(); + } + } catch (error) { + log.warn('Error closing producer during peer cleanup', { + producer_id, + error: error.message, + }); + } + } + this.producers.clear(); + + // Close all transports + for (const [transport_id, transport] of this.transports.entries()) { + try { + if (!transport.closed) { + transport.close(); + } + } catch (error) { + log.warn('Error closing transport during peer cleanup', { + transport_id, + error: error.message, + }); + } + } + this.transports.clear(); + + log.debug('Peer cleanup completed successfully', { + peer_id: this.id, + peer_name: this.peer_name, + transports: this.transports.size, + producers: this.producers.size, + consumers: this.consumers.size, }); } }; diff --git a/app/src/Room.js b/app/src/Room.js index 462e4a4f3..3876ab9e9 100644 --- a/app/src/Room.js +++ b/app/src/Room.js @@ -34,10 +34,21 @@ module.exports = class Room { this._hostOnlyRecording = false; // ########################## this.recording = { - recSyncServerRecording: config?.server?.recording?.enabled || false, - recSyncServerEndpoint: config?.server?.recording?.endpoint || '', + recSyncServerToS3: (config?.integrations?.s3?.enabled && config?.media?.recording?.uploadToS3) || false, + recSyncServerRecording: config?.media?.recording?.enabled || false, + recSyncServerEndpoint: config?.media?.recording?.endpoint || '', }; // ########################## + + this.notifications = { + mode: { + email: '', + }, + events: { + join: false, + }, + }; + this._moderator = { video_start_privacy: false, audio_start_muted: false, @@ -47,13 +58,15 @@ module.exports = class Room { screen_cant_share: false, chat_cant_privately: false, chat_cant_chatgpt: false, + chat_cant_deep_seek: false, media_cant_sharing: false, }; - this.survey = config.survey; - this.redirect = config.redirect; - this.videoAIEnabled = config?.videoAI?.enabled || false; + this.survey = config?.features?.survey; + this.redirect = config?.features?.redirect; + this.videoAIEnabled = config?.integrations?.videoAI?.enabled || false; + this.videoAISessionTimeLimit = config?.integrations?.videoAI?.sessionTimeLimit || 0; this.peers = new Map(); - this.bannedPeers = []; + this.bannedPeers = new Map(); // uuid -> timestamp, with TTL-based expiration this.webRtcTransport = config.mediasoup.webRtcTransport; this.router = null; this.routerSettings = config.mediasoup.router; @@ -62,15 +75,18 @@ module.exports = class Room { // RTMP configuration this.rtmpFileStreamer = null; this.rtmpUrlStreamer = null; - this.rtmp = config.server.rtmp || false; + this.rtmp = config?.media?.rtmp || false; // Polls this.polls = []; - this.isHostProtected = config.host.protected; + this.isHostProtected = config?.security?.host?.protected || false; // Share Media this.shareMediaData = {}; + + this.maxParticipants = config?.moderation?.room?.maxParticipants || 1000; + this.globalLobby = config?.moderation?.room?.lobby || false; } // #################################################### @@ -98,9 +114,15 @@ module.exports = class Room { survey: this.survey, redirect: this.redirect, videoAIEnabled: this.videoAIEnabled, + videoAISessionTimeLimit: this.videoAISessionTimeLimit, thereIsPolls: this.thereIsPolls(), shareMediaData: this.shareMediaData, + dominantSpeaker: this.activeSpeakerObserverEnabled, peers: JSON.stringify([...this.peers]), + peersCount: this.getPeersCount(), + maxParticipants: this.maxParticipants, + maxParticipantsReached: this.peers.size > this.maxParticipants, + globalLobby: this.globalLobby, }; } @@ -139,9 +161,9 @@ module.exports = class Room { return this.rtmpFileStreamer; } - async getRTMP(dir = 'rtmp') { + async getRTMP(dir = '../rtmp') { // - const folderPath = path.join(__dirname, '../', dir); + const folderPath = path.join(__dirname, dir); log.debug('[getRTMP] Files from dir', folderPath); try { @@ -151,7 +173,7 @@ module.exports = class Room { fs.mkdirSync(folderPath, { recursive: true }); } const files = fs.readdirSync(folderPath); - log.info('[getRTMP] Files', files); + log.debug('[getRTMP] Files', files); return files; } catch (error) { log.error(`[getRTMP] Error reading directory: ${error.message}`); @@ -223,7 +245,7 @@ module.exports = class Room { room, host = 'localhost', port = 1935, - inputVideoURL = 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4', + inputVideoURL = 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4' ) { if (!this.rtmp || !this.rtmp.enabled) { log.debug('[startRTMPfromURL] Server is not enabled or missing the config'); @@ -271,6 +293,7 @@ module.exports = class Room { // #################################################### getRTMPUrl(host, port) { + const rtmpUseNodeMediaServer = this.rtmp.useNodeMediaServer ?? true; const rtmpServer = this.rtmp.server != '' ? this.rtmp.server : false; const rtmpAppName = this.rtmp.appName != '' ? this.rtmp.appName : 'live'; const rtmpStreamKey = this.rtmp.streamKey != '' ? this.rtmp.streamKey : uuidv4(); @@ -279,7 +302,7 @@ module.exports = class Room { const rtmpServerURL = rtmpServer ? rtmpServer : `rtmp://${host}:${port}`; const rtmpServerPath = '/' + rtmpAppName + '/' + rtmpStreamKey; - const rtmpUrl = rtmpServerSecret + const rtmpUrl = rtmpUseNodeMediaServer ? this.generateRTMPUrl(rtmpServerURL, rtmpServerPath, rtmpServerSecret, expirationHours) : rtmpServerURL + rtmpServerPath; @@ -308,13 +331,19 @@ module.exports = class Room { .then((router) => { this.router = router; if (this.audioLevelObserverEnabled) { - this.startAudioLevelObservation(); + log.debug('Audio Level Observer enabled, starting observation...'); + this.startAudioLevelObservation().catch((err) => { + log.error('Failed to start audio level observation', err); + }); } if (this.activeSpeakerObserverEnabled) { - this.startActiveSpeakerObserver(); + log.debug('Active Speaker Observer enabled, starting observation...'); + this.startActiveSpeakerObserver().catch((err) => { + log.error('Failed to start active speaker observer', err); + }); } this.router.observer.on('close', () => { - log.info('---------------> Router is now closed as the last peer has left the room', { + log.debug('---------------> Router is now closed as the last peer has left the room', { room: this.id, }); }); @@ -326,11 +355,19 @@ module.exports = class Room { } closeRouter() { - this.router.close(); - log.debug('Close Room router', { - router_id: this.router.id, - router_closed: this.router.closed, - }); + if (this.router && !this.router.closed) { + this.router.close(); + log.debug('Router closed', { router_id: this.router.id }); + } + } + + async close() { + this.closeAudioLevelObserver(); + this.closeActiveSpeakerObserver(); + if (this.isRtmpFileStreamerActive()) this.stopRTMP(); + if (this.isRtmpUrlStreamerActive()) this.stopRTMPfromURL(); + this.closeRouter(); + log.debug('Room closed', { room_id: this.id }); } // #################################################### @@ -376,9 +413,9 @@ module.exports = class Room { peer_name: peer_name, audioVolume: audioVolume, }; - // Uncomment the following line for debugging // log.debug('Sending audio volume', data); this.sendToAll('audioVolume', data); + return; } }); }); @@ -392,6 +429,15 @@ module.exports = class Room { addProducerToAudioLevelObserver(producer) { if (this.audioLevelObserverEnabled) { this.audioLevelObserver.addProducer(producer); + log.debug('Producer added to audio level observer', { producer }); + } + } + + closeAudioLevelObserver() { + if (this.audioLevelObserver && !this.audioLevelObserver.closed) { + this.audioLevelObserver.close(); + this.audioLevelObserver = null; + log.debug('Audio Level Observer closed'); } } @@ -400,25 +446,40 @@ module.exports = class Room { // #################################################### async startActiveSpeakerObserver() { + log.debug('Start activeSpeakerObserver for signaling dominant speaker...'); this.activeSpeakerObserver = await this.router.createActiveSpeakerObserver(); this.activeSpeakerObserver.on('dominantspeaker', (dominantSpeaker) => { + if (!dominantSpeaker.producer) { + return; + } log.debug('activeSpeakerObserver "dominantspeaker" event', dominantSpeaker.producer.id); this.peers.forEach((peer) => { const { id, peer_audio, peer_name } = peer; - peer.producers.forEach((peerProducer) => { - if ( - peerProducer.id === dominantSpeaker.producer.id && - peerProducer.kind === 'audio' && - peer_audio - ) { - const data = { - peer_id: id, - peer_name: peer_name, - }; - // log.debug('Sending dominant speaker', data); - this.sendToAll('dominantSpeaker', data); + if (peer.producers instanceof Map) { + for (const peerProducer of peer.producers.values()) { + if ( + peerProducer.id === dominantSpeaker.producer.id && + peerProducer.kind === 'audio' && + peer_audio + ) { + let videoProducerId = null; + for (const p of peer.producers.values()) { + if (p.kind === 'video') { + videoProducerId = p.id; + break; + } + } + const data = { + producer_id: videoProducerId, + peer_id: id, + peer_name: peer_name, + }; + log.debug('Sending dominant speaker', data); + this.sendToAll('dominantSpeaker', data); + break; + } } - }); + } }); }); } @@ -426,9 +487,32 @@ module.exports = class Room { addProducerToActiveSpeakerObserver(producer) { if (this.activeSpeakerObserverEnabled) { this.activeSpeakerObserver.addProducer(producer); + log.debug('Producer added to active speaker observer', { producer }); + } + } + + closeActiveSpeakerObserver() { + if (this.activeSpeakerObserver && !this.activeSpeakerObserver.closed) { + this.activeSpeakerObserver.close(); + this.activeSpeakerObserver = null; + log.debug('Active Speaker Observer closed'); } } + // #################################################### + // ROOM NOTIFICATIONS + // #################################################### + + updateRoomNotifications(data) { + log.debug('Update room notifications', data); + this.notifications = data.notifications; + } + + getRoomNotifications() { + log.debug('get room notifications', this.notifications); + return this.notifications; + } + // #################################################### // ROOM MODERATOR // #################################################### @@ -465,6 +549,9 @@ module.exports = class Room { case 'chat_cant_chatgpt': this._moderator.chat_cant_chatgpt = data.status; break; + case 'chat_cant_deep_seek': + this._moderator.chat_cant_deep_seek = data.status; + break; case 'media_cant_sharing': this._moderator.media_cant_sharing = data.status; break; @@ -501,13 +588,16 @@ module.exports = class Room { return this.peers.size; } - getProducerListForPeer() { + getProducerListForPeer(socket_id) { const producerList = []; - this.peers.forEach((peer) => { + this.peers.forEach((peer, peerId) => { + // Skip the requesting peer's own producers to prevent self-consumption (echo) + if (peerId === socket_id) return; const { peer_name, peer_info } = peer; peer.producers.forEach((producer) => { producerList.push({ producer_id: producer.id, + producer_socket_id: peerId, peer_name: peer_name, peer_info: peer_info, type: producer.appData.mediaType, @@ -527,46 +617,70 @@ module.exports = class Room { this.delPeer(peer); if (this.getPeersCount() === 0) { - this.closeRouter(); + this.close(); } } + getPresenterPeers() { + return Array.from(this.peers.values()).filter((peer) => peer.peer_presenter); + } + + getLobbyPeers() { + return Array.from(this.peers.values()).filter((peer) => peer.peer_lobby); + } + // #################################################### // WebRTC TRANSPORT // #################################################### - async createWebRtcTransport(socket_id) { - if (!this.peers.has(socket_id)) { - throw new Error(`Peer with socket ID ${socket_id} not found in the room`); - } - - const { maxIncomingBitrate, initialAvailableOutgoingBitrate, listenInfos } = this.webRtcTransport; - - const webRtcTransportOptions = { + getWebRtcTransportOptions() { + const { iceConsentTimeout = 35, initialAvailableOutgoingBitrate, listenInfos } = this.webRtcTransport; + return { ...(this.webRtcServerActive ? { webRtcServer: this.webRtcServer } : { listenInfos: listenInfos }), enableUdp: true, enableTcp: true, preferUdp: true, - iceConsentTimeout: 35, + iceConsentTimeout, initialAvailableOutgoingBitrate, }; + } + + async createWebRtcTransport(socket_id) { + if (!this.peers.has(socket_id)) { + throw new Error(`Peer with socket ID ${socket_id} not found in the room`); + } + + const webRtcTransportOptions = this.getWebRtcTransportOptions(); log.debug('webRtcTransportOptions ----->', webRtcTransportOptions); - const transport = await this.router.createWebRtcTransport(webRtcTransportOptions); + let transport; + try { + transport = await this.router.createWebRtcTransport(webRtcTransportOptions); + if (!transport) { + throw new Error('Failed to create WebRTC Transport'); + } + } catch (error) { + log.error('Error creating WebRTC Transport', { error: error.message, socket_id }); + throw new Error('Error creating WebRTC Transport'); + } if (!transport) { - throw new Error('Failed to create WebRtc Transport'); + throw new Error(`Transport not found for socket ID ${socket_id}`); + } + + if (transport.closed) { + throw new Error('Transport is already closed'); } const { id, type, iceParameters, iceCandidates, dtlsParameters } = transport; + const { maxIncomingBitrate } = this.webRtcTransport; if (maxIncomingBitrate) { try { await transport.setMaxIncomingBitrate(maxIncomingBitrate); } catch (error) { - log.error('Failed to set max incoming bitrate', error); - throw new Error(`Failed to set max incoming bitrate for transport ${id}`); + log.warn('Failed to set max incoming bitrate', error); } } @@ -579,9 +693,15 @@ module.exports = class Room { throw new Error(`Failed to add peer transport ${id}`); } - log.debug('Transport created', { transportId: id, transportType: type }); + log.debug('Transport created', { + room_id: this.id, + transport_id: transport.id, + type: type, + peer_name: peer.peer_name, + }); const { peer_name } = peer; + const { iceConsentTimeout = 35 } = this.webRtcTransport; transport.observer.on('newproducer', (producer) => { log.debug('---> new producer created [id:%s]', producer.id); @@ -595,14 +715,40 @@ module.exports = class Room { log.debug('---> transport close [id:%s]', transport.id); }); + // Track ICE disconnect timeout so it can be cancelled on transport close + let iceDisconnectTimeout = null; + transport.on('icestatechange', (iceState) => { - if (iceState === 'disconnected' || iceState === 'closed') { - log.warn('ICE state changed, closing peer', { - peer_name: peer_name, - transport_id: id, - iceState: iceState, - }); - this.removePeer(socket_id); + const iceLog = { + peer_name: peer_name, + transport_id: id, + iceState: iceState, + }; + log.debug('ICE state changed', iceLog); + + if (iceState === 'disconnected') { + log.debug('ICE state disconnected for transport waiting before closing', iceLog); + iceDisconnectTimeout = setTimeout(() => { + iceDisconnectTimeout = null; + if (transport.iceState === 'disconnected') { + log.warn('Closing transport due to prolonged ICE disconnection', iceLog); + if (!transport.closed) { + transport.close(); + } + } + }, iceConsentTimeout * 1000); // Wait iceConsentTimeout seconds before closing + } else { + // Clear pending timeout when ICE state recovers or moves to another state + if (iceDisconnectTimeout) { + clearTimeout(iceDisconnectTimeout); + iceDisconnectTimeout = null; + } + if (iceState === 'closed') { + log.warn('ICE state closed, closing transport', iceLog); + if (!transport.closed) { + transport.close(); + } + } } }); @@ -621,14 +767,25 @@ module.exports = class Room { transport_id: id, dtlsState: dtlsState, }); - this.removePeer(socket_id); + if (!transport.closed) { + transport.close(); + } } }); transport.on('close', () => { + // Clear any pending ICE disconnect timeout + if (iceDisconnectTimeout) { + clearTimeout(iceDisconnectTimeout); + iceDisconnectTimeout = null; + } + // Remove all listeners from this transport to prevent memory leaks + transport.removeAllListeners(); + transport.observer.removeAllListeners(); log.debug('Transport closed', { peer_name: peer_name, transport_id: transport.id, + transport_closed: transport.closed, }); }); @@ -641,6 +798,10 @@ module.exports = class Room { } async connectPeerTransport(socket_id, transport_id, dtlsParameters) { + if (!socket_id || !transport_id || !dtlsParameters) { + throw new Error('Missing required parameters for connecting peer transport'); + } + if (!this.peers.has(socket_id)) { throw new Error(`Peer with socket ID ${socket_id} not found in the room`); } @@ -649,8 +810,17 @@ module.exports = class Room { try { await peer.connectTransport(transport_id, dtlsParameters); + log.debug('Peer transport connected successfully', { + socket_id, + transport_id, + peer_name: peer.peer_name, + }); } catch (error) { - log.error(`Failed to connect peer transport for socket ID ${socket_id}`, error); + log.error(`Failed to connect peer transport for socket ID ${socket_id}`, { + transport_id, + error: error.message, + peer_name: peer.peer_name, + }); throw new Error(`Failed to connect transport for peer with socket ID ${socket_id}`); } @@ -662,33 +832,50 @@ module.exports = class Room { // #################################################### async produce(socket_id, producerTransportId, rtpParameters, kind, type) { + if (!socket_id || !producerTransportId || !rtpParameters || !kind || !type) { + throw new Error('Missing required parameters for producing media'); + } + if (!this.peers.has(socket_id)) { throw new Error(`Peer with socket ID ${socket_id} not found in the room`); } const peer = this.getPeer(socket_id); - const { peer_name, peer_info } = peer; - let peerProducer; + if (!peer.hasTransport(producerTransportId)) { + throw new Error(`Transport with ID ${producerTransportId} not found for peer ${socket_id}`); + } + let peerProducer; try { peerProducer = await peer.createProducer(producerTransportId, rtpParameters, kind, type); } catch (error) { - log.error(`Error creating producer for peer ${peer_name} with socket ID ${socket_id}`, error); + log.error(`Error creating producer for peer ${peer.peer_name} with socket ID ${socket_id}`, { + producerTransportId, + kind, + type, + error: error.message, + }); throw new Error( - `Error creating producer for peer ${peer_name} with transport ID ${producerTransportId} type ${type} for peer ${socket_id}`, + `Failed to create producer for peer ${peer.peer_name} with transport ID ${producerTransportId}` ); } if (!peerProducer) { throw new Error( - `Failed to create producer for peer ${peer_name} with ID ${producerTransportId} for peer ${socket_id}`, + `Failed to create producer for peer ${peer_name} with ID ${producerTransportId} for peer ${socket_id}` ); } const { id } = peerProducer; + const producerTransport = peer.getTransport(producerTransportId); + + if (!producerTransport) { + throw new Error(`Producer transport with ID ${producerTransportId} not found for peer ${peer_name}`); + } + this.broadCast(socket_id, 'newProducers', [ { producer_id: id, @@ -699,6 +886,18 @@ module.exports = class Room { }, ]); + log.debug('Producer created successfully', { + producerTransportId, + producer_id: id, + peer_name: peer.peer_name, + kind, + type, + paused: peerProducer.paused, + appData: peerProducer.appData, + transport_state: `ICE:${producerTransport.iceState}, DTLS:${producerTransport.dtlsState}`, + codecs: rtpParameters.codecs?.map((c) => c.mimeType) || [], + }); + return id; } @@ -720,17 +919,20 @@ module.exports = class Room { // #################################################### async consume(socket_id, consumer_transport_id, producerId, rtpCapabilities, type) { + if (!socket_id || !consumer_transport_id || !producerId || !rtpCapabilities || !type) { + throw new Error('Missing required parameters for consuming media'); + } + if (!this.peers.has(socket_id)) { throw new Error(`Peer with socket ID ${socket_id} not found in the room`); } const peer = this.getPeer(socket_id); - const { peer_name } = peer; if (!this.router.canConsume({ producerId, rtpCapabilities })) { throw new Error( - `Cannot consume producer for peer ${peer_name} with ID ${producerId} type ${type}, router validation failed`, + `Cannot consume producer for peer ${peer_name} with ID ${producerId} type ${type}, router validation failed` ); } @@ -738,24 +940,38 @@ module.exports = class Room { try { peerConsumer = await peer.createConsumer(consumer_transport_id, producerId, rtpCapabilities); } catch (error) { - log.error(`Error creating consumer for peer with socket ID ${socket_id}`, error); + log.error(`Error creating consumer for peer ${peer_name} with socket ID ${socket_id}`, { + consumer_transport_id, + producerId, + type, + error: error.message, + }); throw new Error( - `Failed to create consumer for peer ${peer_name} with transport ID ${consumer_transport_id} and producer ID ${producerId} type ${type} for peer ${socket_id}`, + `Failed to create consumer for peer ${peer_name} with transport ID ${consumer_transport_id} and producer ID ${producerId} type ${type} for peer ${socket_id}` ); } if (!peerConsumer) { throw new Error( - `Consumer creation failed for peer ${peer_name} with transport ID ${consumer_transport_id} and producer ID ${producerId}`, + `Consumer creation failed for peer ${peer_name} with transport ID ${consumer_transport_id} and producer ID ${producerId}` ); } - const { consumer, params } = peerConsumer; + const consumerTransport = peer.getTransport(consumer_transport_id); + + if (!consumerTransport) { + throw new Error(`Consumer transport with ID ${consumer_transport_id} not found for peer ${peer_name}`); + } + const { consumer, params } = peerConsumer; const { id, kind } = consumer; - consumer.on('producerclose', () => { - log.debug('Consumer closed due to "producerclose" event'); + consumer.once('producerclose', () => { + log.debug('Consumer closed due to "producerclose" event', { + consumer_id: id, + producer_id: producerId, + peer_name, + }); peer.removeConsumer(id); @@ -766,6 +982,18 @@ module.exports = class Room { }); }); + log.debug('Consumer created successfully', { + consumer_transport_id, + consumer_id: id, + producer_id: producerId, + peer_name, + kind, + type, + paused: consumer.paused, + producerPaused: consumer.producerPaused, + transport_state: `ICE:${consumerTransport.iceState}, DTLS:${consumerTransport.dtlsState}`, + }); + return params; } @@ -774,17 +1002,24 @@ module.exports = class Room { // #################################################### addBannedPeer(uuid) { - if (!this.bannedPeers.includes(uuid)) { - this.bannedPeers.push(uuid); + if (!this.bannedPeers.has(uuid)) { + this.bannedPeers.set(uuid, Date.now()); log.debug('Added to the banned list', { uuid: uuid, - banned: this.bannedPeers, + banned: [...this.bannedPeers.keys()], }); } } isBanned(uuid) { - return this.bannedPeers.includes(uuid); + if (!this.bannedPeers.has(uuid)) return false; + const bannedAt = this.bannedPeers.get(uuid); + const BAN_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours + if (Date.now() - bannedAt > BAN_TTL_MS) { + this.bannedPeers.delete(uuid); + return false; + } + return true; } // #################################################### @@ -806,6 +1041,9 @@ module.exports = class Room { isLobbyEnabled() { return this._isLobbyEnabled; } + isGlobalLobbyEnabled() { + return this.globalLobby; + } isHostOnlyRecording() { return this._hostOnlyRecording; } diff --git a/app/src/RtmpFile.js b/app/src/RtmpFile.js index d206c7ad0..38f6a77ea 100644 --- a/app/src/RtmpFile.js +++ b/app/src/RtmpFile.js @@ -1,8 +1,7 @@ 'use strict'; const config = require('./config'); -const ffmpegPath = - config.server.rtmp && config.server.rtmp.ffmpegPath ? config.server.rtmp.ffmpegPath : '/usr/bin/ffmpeg'; +const ffmpegPath = config.media?.rtmp?.ffmpegPath || '/usr/bin/ffmpeg'; const ffmpeg = require('fluent-ffmpeg'); ffmpeg.setFfmpegPath(ffmpegPath); @@ -39,7 +38,7 @@ class RtmpFile { '-f flv', // Output format ]) .output(rtmpUrl) - .on('start', (commandLine) => log.info('ffmpeg process starting with command:', commandLine)) + .on('start', (commandLine) => log.debug('ffmpeg process starting with command:', commandLine)) .on('progress', (progress) => { /* log.debug('Processing', progress); */ }) @@ -50,13 +49,13 @@ class RtmpFile { } }) .on('end', () => { - log.info('FFmpeg processing finished'); + log.debug('FFmpeg processing finished'); this.ffmpegProcess = null; this.handleEnd(); }) .run(); - log.info('RtmpFile started', rtmpUrl); + log.debug('RtmpFile started', rtmpUrl); return true; } catch (error) { log.error('Error starting RtmpFile', error.message); @@ -69,7 +68,7 @@ class RtmpFile { try { this.ffmpegProcess.kill('SIGTERM'); this.ffmpegProcess = null; - log.info('RtmpFile stopped'); + log.debug('RtmpFile stopped'); return true; } catch (error) { log.error('Error stopping RtmpFile', error.message); diff --git a/app/src/RtmpStreamer.js b/app/src/RtmpStreamer.js index 944dad4fb..01123a04e 100644 --- a/app/src/RtmpStreamer.js +++ b/app/src/RtmpStreamer.js @@ -3,8 +3,7 @@ const config = require('./config'); const { PassThrough } = require('stream'); const ffmpeg = require('fluent-ffmpeg'); -const ffmpegPath = - config.server.rtmp && config.server.rtmp.ffmpegPath ? config.server.rtmp.ffmpegPath : '/usr/bin/ffmpeg'; +const ffmpegPath = config.media?.rtmp?.ffmpegPath || '/usr/bin/ffmpeg'; ffmpeg.setFfmpegPath(ffmpegPath); const Logger = require('./Logger'); @@ -32,7 +31,7 @@ class RtmpStreamer { .audioBitrate('128k') .outputOptions(['-f flv']) .output(this.rtmpUrl) - .on('start', (commandLine) => this.log.info('ffmpeg command', { id: this.rtmpKey, cmd: commandLine })) + .on('start', (commandLine) => this.log.debug('ffmpeg command', { id: this.rtmpKey, cmd: commandLine })) .on('progress', (progress) => { /* log.debug('Processing', progress); */ }) @@ -43,7 +42,7 @@ class RtmpStreamer { this.end(); }) .on('end', () => { - this.log.info('FFmpeg process ended', this.rtmpKey); + this.log.debug('FFmpeg process ended', this.rtmpKey); this.end(); }) .run(); @@ -61,12 +60,12 @@ class RtmpStreamer { if (this.stream) { this.stream.end(); this.stream = null; - this.log.info('RTMP streaming stopped', this.rtmpKey); + this.log.debug('RTMP streaming stopped', this.rtmpKey); } if (this.ffmpegStream && !this.ffmpegStream.killed) { this.ffmpegStream.kill('SIGTERM'); this.ffmpegStream = null; - this.log.info('FFMPEG closed successfully', this.rtmpKey); + this.log.debug('FFMPEG closed successfully', this.rtmpKey); } this.run = false; } diff --git a/app/src/RtmpUrl.js b/app/src/RtmpUrl.js index 9e57994fe..33225c4e2 100644 --- a/app/src/RtmpUrl.js +++ b/app/src/RtmpUrl.js @@ -1,8 +1,7 @@ 'use strict'; const config = require('./config'); -const ffmpegPath = - config.server.rtmp && config.server.rtmp.ffmpegPath ? config.server.rtmp.ffmpegPath : '/usr/bin/ffmpeg'; +const ffmpegPath = config.media?.rtmp?.ffmpegPath || '/usr/bin/ffmpeg'; const ffmpeg = require('fluent-ffmpeg'); ffmpeg.setFfmpegPath(ffmpegPath); @@ -35,7 +34,7 @@ class RtmpUrl { .size('1280x720') // Scale video to 1280x720 resolution .format('flv') // Set output format to FLV .output(rtmpUrl) - .on('start', (commandLine) => log.info('ffmpeg process starting with command:', commandLine)) + .on('start', (commandLine) => log.debug('ffmpeg process starting with command:', commandLine)) .on('progress', (progress) => { /* log.debug('Processing', progress); */ }) @@ -46,13 +45,13 @@ class RtmpUrl { } }) .on('end', () => { - log.info('FFmpeg processing finished'); + log.debug('FFmpeg processing finished'); this.ffmpegProcess = null; this.handleEnd(); }) .run(); - log.info('RtmpUrl started', rtmpUrl); + log.debug('RtmpUrl started', rtmpUrl); return true; } catch (error) { log.error('Error starting RtmpUrl', error.message); @@ -65,7 +64,7 @@ class RtmpUrl { try { this.ffmpegProcess.kill('SIGTERM'); this.ffmpegProcess = null; - log.info('RtmpUrl stopped'); + log.debug('RtmpUrl stopped'); return true; } catch (error) { log.error('Error stopping RtmpUrl', error.message); diff --git a/app/src/Server.js b/app/src/Server.js index 9e007ce79..c50a6ca5f 100644 --- a/app/src/Server.js +++ b/app/src/Server.js @@ -9,8 +9,10 @@ prod dependencies: { @mattermost/client : https://www.npmjs.com/package/@mattermost/client + @ngrok/ngrok : https://www.npmjs.com/package/@ngrok/ngrok @sentry/node : https://www.npmjs.com/package/@sentry/node axios : https://www.npmjs.com/package/axios + chokidar : https://www.npmjs.com/package/chokidar colors : https://www.npmjs.com/package/colors compression : https://www.npmjs.com/package/compression cors : https://www.npmjs.com/package/cors @@ -28,7 +30,6 @@ prod dependencies: { jsonwebtoken : https://www.npmjs.com/package/jsonwebtoken mediasoup : https://www.npmjs.com/package/mediasoup mediasoup-client : https://www.npmjs.com/package/mediasoup-client - ngrok : https://www.npmjs.com/package/ngrok nodemailer : https://www.npmjs.com/package/nodemailer openai : https://www.npmjs.com/package/openai qs : https://www.npmjs.com/package/qs @@ -39,6 +40,9 @@ prod dependencies: { } dev dependencies: { + @babel/core : https://www.npmjs.com/package/@babel/core + @babel/preset-env : https://www.npmjs.com/package/@babel/preset-env + babel-loader : https://www.npmjs.com/package/babel-loader mocha : https://www.npmjs.com/package/mocha node-fetch : https://www.npmjs.com/package/node-fetch nodemon : https://www.npmjs.com/package/nodemon @@ -46,6 +50,8 @@ dev dependencies: { proxyquire : https://www.npmjs.com/package/proxyquire should : https://www.npmjs.com/package/should sinon : https://www.npmjs.com/package/sinon + webpack : https://www.npmjs.com/package/webpack + webpack-cli : https://www.npmjs.com/package/webpack-cli } */ @@ -58,12 +64,17 @@ dev dependencies: { * @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon * @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970 * @author Miroslav Pejic - miroslav.pejic.85@gmail.com - * @version 1.7.76 + * @version 2.1.51 * */ const express = require('express'); const { auth, requiresAuth } = require('express-openid-connect'); +const { withFileLock } = require('./MutexManager'); +const { PassThrough } = require('stream'); +const { S3Client } = require('@aws-sdk/client-s3'); +const { Upload } = require('@aws-sdk/lib-storage'); +const { fixDurationOrRemux } = require('./FixDurationOrRemux'); const cors = require('cors'); const compression = require('compression'); const socketIo = require('socket.io'); @@ -73,13 +84,14 @@ const mediasoupClient = require('mediasoup-client'); const http = require('http'); const path = require('path'); const axios = require('axios'); -const ngrok = require('ngrok'); +const ngrok = require('@ngrok/ngrok'); const jwt = require('jsonwebtoken'); const fs = require('fs'); const sanitizeFilename = require('sanitize-filename'); const helmet = require('helmet'); const config = require('./config'); const checkXSS = require('./XSS'); +const mime = require('mime-types'); const Host = require('./Host'); const Room = require('./Room'); const Peer = require('./Peer'); @@ -97,12 +109,34 @@ const Mattermost = require('./Mattermost'); const restrictAccessByIP = require('./middleware/IpWhitelist'); const packageJson = require('../../package.json'); +// Login attempts limit +const rateLimit = require('express-rate-limit'); +const maxAttempts = config?.security?.host?.maxAttempts || 5; +const minBlockTime = config?.security?.host?.minBlockTime || 15; // minutes +// Extract client IP (supports proxies) +const ipKeyGenerator = (req) => + (req.headers['x-forwarded-for'] || req.headers['X-Forwarded-For'] || '').split(',')[0].trim() || + req.socket?.remoteAddress || + req.ip; +// Create login rate limiter +const loginLimiter = rateLimit({ + windowMs: minBlockTime * 60 * 1000, + max: maxAttempts, + message: { + message: `Too many login attempts. Please try again after ${minBlockTime} minute${minBlockTime == 1 ? '' : 's'}.`, + }, + keyGenerator: (req) => req.body?.username || ipKeyGenerator(req), +}); + +// Branding configuration +const brandHtmlInjection = config?.ui?.brand?.htmlInjection ?? true; + // Incoming Stream to RTPM const { v4: uuidv4 } = require('uuid'); const crypto = require('crypto-js'); const RtmpStreamer = require('./RtmpStreamer.js'); // Import the RtmpStreamer class -const rtmpCfg = config.server.rtmp; -const rtmpDir = rtmpCfg && rtmpCfg.dir ? rtmpCfg.dir : 'rtmp'; +const rtmpCfg = config?.media?.rtmp; +const rtmpDir = rtmpCfg?.dir || 'rtmp'; // File and Url Rtmp streams count let rtmpFileStreamsCount = 0; @@ -114,14 +148,14 @@ const nodemailer = require('./lib/nodemailer'); // Slack API const CryptoJS = require('crypto-js'); const qS = require('qs'); -const slackEnabled = config.slack.enabled; -const slackSigningSecret = config.slack.signingSecret; +const slackEnabled = config?.integrations?.slack?.enabled || false; +const slackSigningSecret = config?.integrations?.slack?.signingSecret || ''; const app = express(); const options = { - cert: fs.readFileSync(path.join(__dirname, config.server.ssl.cert), 'utf-8'), - key: fs.readFileSync(path.join(__dirname, config.server.ssl.key), 'utf-8'), + cert: fs.readFileSync(path.join(__dirname, config?.server?.ssl.cert || '../ssl/cert.pem'), 'utf-8'), + key: fs.readFileSync(path.join(__dirname, config?.server?.ssl.key || '../ssl/key.pem'), 'utf-8'), }; const corsOptions = { @@ -137,66 +171,97 @@ const io = socketIo(server, { cors: corsOptions, }); -const host = config.server.hostUrl || `http://localhost:${config.server.listen.port}`; -const trustProxy = !!config.server.trustProxy; +const host = config?.server?.hostUrl || `http://localhost:${config?.server?.listen?.port || 3010}`; +const trustProxy = Boolean(config?.server?.trustProxy); const jwtCfg = { - JWT_KEY: (config.jwt && config.jwt.key) || 'mirotalksfu_jwt_secret', - JWT_EXP: (config.jwt && config.jwt.exp) || '1h', + JWT_KEY: config?.security?.jwt?.key || 'mirotalksfu_jwt_secret', + JWT_EXP: config?.security?.jwt?.exp || '1h', }; const hostCfg = { - protected: config.host.protected, - user_auth: config.host.user_auth, - users: config.host.users, - users_from_db: config.host.users_from_db, - users_api_room_allowed: config.host.users_api_room_allowed, - users_api_rooms_allowed: config.host.users_api_rooms_allowed, - users_api_endpoint: config.host.users_api_endpoint, - users_api_secret_key: config.host.users_api_secret_key, - api_room_exists: config.host.api_room_exists, - users: config.host.users, - authenticated: !config.host.protected, + protected: config?.security?.host?.protected, + authenticated: !config?.security?.host?.protected, + user_auth: config?.security?.host?.user_auth, + users: config?.security?.host?.users, + users_from_db: config?.security?.host?.users_from_db, + users_api_room_allowed: config?.security?.host?.users_api_room_allowed, + users_api_rooms_allowed: config?.security?.host?.users_api_rooms_allowed, + users_api_endpoint: config?.security?.host?.users_api_endpoint, + users_api_secret_key: config?.security?.host?.users_api_secret_key, + api_room_exists: config?.security?.host?.api_room_exists, + presenters: config?.security?.host?.presenters, +}; + +const widget = { + enabled: config?.ui?.brand?.widget?.enabled, + roomId: config?.ui?.brand?.widget?.roomId || 'support-room', + alert: { + enabled: config?.ui?.brand?.widget?.alert?.enabled, + type: config?.ui?.brand?.widget?.alert?.type || 'email', + }, }; const restApi = { basePath: '/api/v1', // api endpoint path docs: host + '/api/v1/docs', // api docs - allowed: config.api?.allowed, + allowed: config.api?.allowed || {}, }; // Sentry monitoring -const sentryEnabled = config.sentry.enabled; -const sentryDSN = config.sentry.DSN; -const sentryTracesSampleRate = config.sentry.tracesSampleRate; -if (sentryEnabled) { +const sentryEnabled = config.integrations?.sentry?.enabled || false; +const sentryDSN = config.integrations.sentry.DSN; +const sentryTracesSampleRate = config.integrations.sentry.tracesSampleRate; +if (sentryEnabled && typeof sentryDSN === 'string' && sentryDSN.trim()) { + log.info('Sentry monitoring started...'); + Sentry.init({ dsn: sentryDSN, - integrations: [ - Sentry.captureConsoleIntegration({ - // ['log', 'info', 'warn', 'error', 'debug', 'assert'] - levels: ['error'], - }), - ], tracesSampleRate: sentryTracesSampleRate, }); - /* - log.log('test-log'); - log.info('test-info'); - log.warn('test-warning'); - log.error('test-error'); - log.debug('test-debug'); -*/ + + // Accept logLevels as an array, e.g., ['warn', 'error'] + const logLevels = config.integrations?.sentry?.logLevels || ['error']; + + const stripAnsi = (str) => (typeof str === 'string' ? str.replace(/\u001b\[[\d;]*m/g, '') : str); + + const sanitizeArgs = (args) => + args + .map((arg) => + typeof arg === 'string' ? stripAnsi(arg) : typeof arg === 'object' ? JSON.stringify(arg) : String(arg) + ) + .join(' '); + + const originalConsole = {}; + logLevels.forEach((level) => { + originalConsole[level] = console[level]; + console[level] = function (...args) { + switch (level) { + case 'warn': + Sentry.captureMessage(sanitizeArgs(args), 'warning'); + break; + case 'error': + args[0] instanceof Error + ? Sentry.captureException(args[0]) + : Sentry.captureException(new Error(sanitizeArgs(args))); + break; + } + originalConsole[level].apply(console, args); + }; + }); + + // log.error('Sentry error', { foo: 'bar' }); + // log.warn('Sentry warning'); } // Handle WebHook const webhook = { - enabled: config?.webhook?.enabled || false, - url: config?.webhook?.url || 'http://localhost:8888/webhook-endpoint', + enabled: config?.integrations?.webhook?.enabled || false, + url: config?.integrations?.webhook?.url || 'http://localhost:8888/webhook-endpoint', }; // Discord Bot -const { enabled, commands, token } = config.discord || {}; +const { enabled, commands, token } = config?.integrations?.discord || {}; if (enabled && commands.length > 0 && token) { const discordBot = new Discord(token, commands); @@ -212,12 +277,12 @@ const defaultStats = { // OpenAI/ChatGPT let chatGPT; -if (config.chatGPT.enabled) { - if (config.chatGPT.apiKey) { +if (config?.integrations?.chatGPT?.enabled) { + if (config?.integrations?.chatGPT?.apiKey) { const { OpenAI } = require('openai'); const configuration = { - basePath: config.chatGPT.basePath, - apiKey: config.chatGPT.apiKey, + basePath: config?.integrations?.chatGPT?.basePath, + apiKey: config?.integrations?.chatGPT?.apiKey, }; chatGPT = new OpenAI(configuration); } else { @@ -226,18 +291,18 @@ if (config.chatGPT.enabled) { } // OpenID Connect -const OIDC = config.oidc ? config.oidc : { enabled: false }; +const OIDC = config?.security?.oidc || { enabled: false }; // directory const dir = { - public: path.join(__dirname, '../../', 'public'), - rec: path.join(__dirname, '../', config?.server?.recording?.dir ? config.server.recording.dir + '/' : 'rec/'), - rtmp: path.join(__dirname, '../', config?.rtmp?.dir ? config.rtmp.dir + '/' : 'rtmp/'), + public: path.join(__dirname, '../../public'), + rec: path.join(__dirname, config?.media?.recording?.dir || 'rec'), + rtmp: path.join(__dirname, config?.media?.rtmp?.dir || 'rtmp'), }; // Rec directory create and set max file size -const recMaxFileSize = config?.server?.recording?.maxFileSize || 1 * 1024 * 1024 * 1024; // 1GB default -const serverRecordingEnabled = config?.server?.recording?.enabled; +const recMaxFileSize = config?.media?.recording?.maxFileSize || 1 * 1024 * 1024 * 1024; // 1GB default +const serverRecordingEnabled = config?.media?.recording?.enabled || false; if (serverRecordingEnabled) { log.debug('Server Recording enabled creating dir', dir.rtmp); if (!fs.existsSync(dir.rec)) { @@ -254,12 +319,28 @@ if (rtmpEnabled) { } } +// #################################################### +// AWS S3 SETUP +// #################################################### + +const s3Client = new S3Client({ + region: config?.integrations?.s3?.region, // Set your AWS region + credentials: { + accessKeyId: config?.integrations?.s3?.accessKeyId, + secretAccessKey: config?.integrations?.s3?.secretAccessKey, + }, + endpoint: config?.integrations?.s3?.endpoint || undefined, + forcePathStyle: config?.integrations?.s3?.forcePathStyle === true, +}); + // html views const views = { html: path.join(__dirname, '../../public/views'), about: path.join(__dirname, '../../', 'public/views/about.html'), landing: path.join(__dirname, '../../', 'public/views/landing.html'), login: path.join(__dirname, '../../', 'public/views/login.html'), + activeRooms: path.join(__dirname, '../../', 'public/views/activeRooms.html'), + customizeRoom: path.join(__dirname, '../../', 'public/views/customizeRoom.html'), newRoom: path.join(__dirname, '../../', 'public/views/newroom.html'), notFound: path.join(__dirname, '../../', 'public/views/404.html'), permission: path.join(__dirname, '../../', 'public/views/permission.html'), @@ -269,7 +350,15 @@ const views = { whoAreYou: path.join(__dirname, '../../', 'public/views/whoAreYou.html'), }; -const filesPath = [views.landing, views.newRoom, views.room, views.login]; +const filesPath = [ + views.landing, + views.newRoom, + views.room, + views.login, + views.whoAreYou, + views.activeRooms, + views.customizeRoom, +]; const htmlInjector = new HtmlInjector(filesPath, config.ui.brand); @@ -284,7 +373,7 @@ const streams = {}; // Collect all rtmp streams const webRtcServerActive = config.mediasoup.webRtcServerActive; // ip (server local IPv4) -const IPv4 = webRtcServerActive +const IP = webRtcServerActive ? config.mediasoup.webRtcServerOptions.listenInfos[0].ip : config.mediasoup.webRtcTransport.listenInfos[0].ip; @@ -297,34 +386,62 @@ let announcedAddress = webRtcServerActive const workers = []; let nextMediasoupWorkerIdx = 0; -// Autodetect announcedAddress (https://www.ipify.org) -if (!announcedAddress && IPv4 === '0.0.0.0') { - http.get( - { - host: 'api.ipify.org', - port: 80, - path: '/', - }, - (resp) => { - resp.on('data', (ip) => { - announcedAddress = ip.toString(); - if (webRtcServerActive) { - config.mediasoup.webRtcServerOptions.listenInfos.forEach((info) => { - info.announcedAddress = announcedAddress; - }); - } else { - config.mediasoup.webRtcTransport.listenInfos.forEach((info) => { - info.announcedAddress = announcedAddress; - }); +// Autodetect announcedAddress with multiple fallback services +if (!announcedAddress && IP === '0.0.0.0') { + const detectPublicIp = async () => { + const services = config.system?.services?.ip || [ + 'http://api.ipify.org', + 'http://ipinfo.io/ip', + 'http://ifconfig.me/ip', + ]; + + for (const service of services) { + try { + const ip = await fetchPublicIp(service); + if (ip) { + announcedAddress = ip; + updateAnnouncedAddress(ip); + startServer(); + return; } - startServer(); - }); - }, - ); + } catch (err) { + log.warn(`Failed to detect IP from ${service}`, err.message); + } + } + throw new Error('All public IP detection services failed! Please check your network connection'); + }; + + detectPublicIp().catch((err) => { + log.error('Public IP detection failed', err.message); + process.exit(1); + }); } else { startServer(); } +function fetchPublicIp(serviceUrl) { + return new Promise((resolve, reject) => { + http.get(serviceUrl, (resp) => { + if (resp.statusCode !== 200) { + return reject(new Error(`HTTP ${resp.statusCode}`)); + } + let data = ''; + resp.on('data', (chunk) => (data += chunk)); + resp.on('end', () => resolve(data.toString().trim())); + }).on('error', reject); + }); +} + +function updateAnnouncedAddress(ip) { + const target = webRtcServerActive + ? config.mediasoup.webRtcServerOptions.listenInfos + : config.mediasoup.webRtcTransport.listenInfos; + + target.forEach((info) => { + info.announcedAddress = ip; + }); +} + // Custom middleware function for OIDC authentication function OIDCAuth(req, res, next) { if (OIDC.enabled) { @@ -369,7 +486,16 @@ function startServer() { // Start the app app.set('trust proxy', trustProxy); // Enables trust for proxy headers (e.g., X-Forwarded-For) based on the trustProxy setting app.use(helmet.noSniff()); // Enable content type sniffing prevention - app.use(express.static(dir.public)); + // Use all static files from the public folder + app.use( + express.static(dir.public, { + setHeaders: (res, filePath) => { + if (filePath.endsWith('.js')) { + res.setHeader('Content-Type', 'application/javascript'); + } //... + }, + }) + ); app.use(cors(corsOptions)); app.use(compression()); app.use(express.json({ limit: '50mb' })); // Handles JSON payloads @@ -396,16 +522,6 @@ function startServer() { // Mattermost const mattermost = new Mattermost(app); - // POST start from here... - app.post('*', function (next) { - next(); - }); - - // GET start from here... - app.get('*', function (next) { - next(); - }); - // Remove trailing slashes in url handle bad requests app.use((err, req, res, next) => { if (err && (err instanceof SyntaxError || err.status === 400 || 'body' in err)) { @@ -433,33 +549,36 @@ function startServer() { // OpenID Connect - Dynamically set baseURL based on incoming host and protocol if (OIDC.enabled) { - const getDynamicConfig = (host, protocol) => { - const baseURL = `${protocol}://${host}`; - - const config = OIDC.baseUrlDynamic - ? { - ...OIDC.config, - baseURL, - } - : OIDC.config; + if (OIDC.baseUrlDynamic) { + // Cache auth middleware instances per origin to avoid re-running OIDC discovery on every request + const authMiddlewareCache = new Map(); - log.debug('OIDC baseURL', config.baseURL); - - return config; - }; + app.use((req, res, next) => { + const host = req.headers.host; + const protocol = req.protocol === 'https' ? 'https' : 'http'; + const origin = `${protocol}://${host}`; - // Apply the authentication middleware using dynamic baseURL configuration - app.use((req, res, next) => { - const host = req.headers.host; - const protocol = req.protocol === 'https' ? 'https' : 'http'; - const dynamicOIDCConfig = getDynamicConfig(host, protocol); + let cachedAuth = authMiddlewareCache.get(origin); + if (!cachedAuth) { + try { + cachedAuth = auth({ ...OIDC.config, baseURL: origin }); + authMiddlewareCache.set(origin, cachedAuth); + } catch (err) { + log.error('OIDC Auth Middleware Error', err); + return next(err); + } + } + cachedAuth(req, res, next); + }); + } else { + // Static baseURL: create auth middleware once at startup try { - auth(dynamicOIDCConfig)(req, res, next); + app.use(auth(OIDC.config)); } catch (err) { log.error('OIDC Auth Middleware Error', err); process.exit(1); } - }); + } } // Route to display user information @@ -474,7 +593,31 @@ function startServer() { log.debug('OIDC get Profile', user); return res.json(user); } - // OIDC disabled + + // Host protected: return displayname from JWT token + if (hostCfg.protected) { + const authHeader = req.headers.authorization; + const token = authHeader && authHeader.startsWith('Bearer ') ? authHeader.split(' ')[1] : null; + if (token) { + try { + const { username } = decodeToken(token); + const user = hostCfg.users.find((u) => u.username === username || u.displayname === username); + const displayname = user?.displayname || username; + log.debug('Host protected get Profile', { username, displayname }); + return res.json({ + name: displayname, + peer_name: { + force: true, + name: true, + }, + }); + } catch (err) { + log.warn('Profile token decode error', err.message); + } + } + } + + // OIDC disabled, no host protection res.status(201).json({ email: false, name: false, @@ -491,46 +634,43 @@ function startServer() { // Logout Route app.get('/logout', (req, res) => { if (OIDC.enabled) { - // - if (hostCfg.protected) { - const ip = authHost.getIP(req); - if (authHost.isAuthorizedIP(ip)) { - authHost.deleteIP(ip); - } - hostCfg.authenticated = false; - // - log.debug('[OIDC] ------> Logout', { - authenticated: hostCfg.authenticated, - authorizedIPs: authHost.getAuthorizedIPs(), - }); - } req.logout(); // Logout user } + if (hostCfg.protected) { + const ip = authHost.getIP(req); + if (authHost.isAuthorizedIP(ip)) { + authHost.deleteIP(ip); + } + hostCfg.authenticated = false; + // + log.debug('[OIDC] ------> Logout', { + authenticated: hostCfg.authenticated, + authorizedIPs: authHost.getAuthorizedIPs(), + }); + } res.redirect('/'); // Redirect to the home page after logout }); + // Favicon + app.get('/favicon.ico', (req, res) => res.status(204).end()); + // UI buttons configuration app.get('/config', (req, res) => { - res.status(200).json({ message: config.ui ? config.ui.buttons : false }); + res.status(200).json({ message: config?.ui?.buttons || false }); }); // Brand configuration app.get('/brand', (req, res) => { - res.status(200).json({ message: config.ui ? config.ui.brand : false }); + res.status(200).json({ message: brandHtmlInjection ? config?.ui?.brand : false }); }); // main page app.get('/', OIDCAuth, (req, res) => { //log.debug('/ - hostCfg ----->', hostCfg); if (!OIDC.enabled && hostCfg.protected) { - const ip = getIP(req); - if (allowedIP(ip)) { - htmlInjector.injectHtml(views.landing, res); - hostCfg.authenticated = true; - } else { - hostCfg.authenticated = false; - res.redirect('/login'); - } + hostCfg.authenticated = false; + res.redirect('/login'); + return; } else { return htmlInjector.injectHtml(views.landing, res); } @@ -549,24 +689,29 @@ function startServer() { //log.info('/newroom - hostCfg ----->', hostCfg); if (!OIDC.enabled && hostCfg.protected) { - const ip = getIP(req); - if (allowedIP(ip)) { - res.redirect('/'); - hostCfg.authenticated = true; - } else { - hostCfg.authenticated = false; - res.redirect('/login'); - } + hostCfg.authenticated = false; + res.redirect('/login'); + return; } else { htmlInjector.injectHtml(views.newRoom, res); } }); + // Get Active rooms + app.get('/activeRooms', OIDCAuth, (req, res) => { + htmlInjector.injectHtml(views.activeRooms, res); + }); + + // Get Customize room + app.get('/customizeRoom', OIDCAuth, (req, res) => { + htmlInjector.injectHtml(views.customizeRoom, res); + }); + // Check if room active (exists) app.post('/isRoomActive', (req, res) => { const { roomId } = checkXSS(req.body); - if (roomId && (hostCfg.protected || hostCfg.user_auth)) { + if (roomId && (hostCfg.protected || hostCfg.user_auth || OIDC.enabled)) { const roomActive = roomList.has(roomId); if (roomActive) log.debug('isRoomActive', { roomId, roomActive }); res.status(200).json({ message: roomActive }); @@ -575,6 +720,14 @@ function startServer() { } }); + // Check if Widget room active (exists) + app.post('/isWidgetRoomActive', (req, res) => { + const { roomId } = checkXSS(req.body); + const roomWidgetActive = roomId && roomId === widget.roomId && roomList.has(roomId); + log.debug('isWidgetRoomActive', { roomId, roomWidgetActive }); + res.status(200).json({ message: roomWidgetActive }); + }); + // Handle Direct join room with params app.get('/join/', async (req, res) => { if (Object.keys(req.query).length > 0) { @@ -582,10 +735,11 @@ function startServer() { log.debug('Direct Join', req.query); + // http://localhost:3010/join?room=test&name=mirotalksfu&audio=0&video=0&screen=0¬ify=0&chat=1 // http://localhost:3010/join?room=test&roomPassword=0&name=mirotalksfu&audio=1&video=1&screen=0&hide=0¬ify=1&duration=00:00:30 // http://localhost:3010/join?room=test&roomPassword=0&name=mirotalksfu&audio=1&video=1&screen=0&hide=0¬ify=0&token=token - const { room, roomPassword, name, audio, video, screen, hide, notify, duration, token, isPresenter } = + const { room, roomPassword, name, audio, video, screen, hide, notify, chat, duration, token, isPresenter } = checkXSS(req.query); if (!room) { @@ -594,9 +748,7 @@ function startServer() { } if (!Validator.isValidRoomName(room)) { - return res.status(400).json({ - message: 'Invalid Room name!\nPath traversal pattern detected!', - }); + return res.redirect('/'); } let peerUsername = ''; @@ -646,7 +798,7 @@ function startServer() { if (!allowRoomAccess && !roomAllowedForUser) { log.warn('Direct Room Join Unauthorized', room); - return OIDC.enabled ? res.redirect('/') : res.redirect('/whoAreYou/' + room); + return res.redirect('/whoAreYou/' + room); } } @@ -698,25 +850,25 @@ function startServer() { if (!OIDC.enabled && hostCfg.protected && hostCfg.users_from_db) { const roomExists = await roomExistsForUser(roomId); log.debug('/join/:roomId exists from API endpoint', roomExists); - return roomExists ? htmlInjector.injectHtml(views.room, res) : res.redirect('/login'); + return roomExists ? htmlInjector.injectHtml(views.room, res) : res.redirect('/login?room=' + roomId); } // 2. Protect room access with configuration check if (!OIDC.enabled && hostCfg.protected && !hostCfg.users_from_db) { const roomExists = hostCfg.users.some( - (user) => user.allowed_rooms && (user.allowed_rooms.includes(roomId) || roomList.has(roomId)), + (user) => user.allowed_rooms && (user.allowed_rooms.includes(roomId) || roomList.has(roomId)) ); log.debug('/join/:roomId exists from config allowed rooms', roomExists); return roomExists ? htmlInjector.injectHtml(views.room, res) : res.redirect('/whoAreYou/' + roomId); } htmlInjector.injectHtml(views.room, res); } else { - // Who are you? - !OIDC.enabled && hostCfg.protected ? res.redirect('/whoAreYou/' + roomId) : res.redirect('/'); + // Who are you? (waiting room) + OIDC.enabled || hostCfg.protected ? res.redirect('/whoAreYou/' + roomId) : res.redirect('/'); } }); // not specified correctly the room id - app.get('/join/*', (req, res) => { + app.get('/join/\\*', (req, res) => { res.redirect('/'); }); @@ -737,14 +889,14 @@ function startServer() { // Get stats endpoint app.get('/stats', (req, res) => { - const stats = config.stats ? config.stats : defaultStats; + const stats = config?.features?.stats || defaultStats; // log.debug('Send stats', stats); res.send(stats); }); // handle who are you: Presenter or Guest app.get('/whoAreYou/:roomId', (req, res) => { - res.sendFile(views.whoAreYou); + htmlInjector.injectHtml(views.whoAreYou, res); }); // handle login if user_auth enabled @@ -757,13 +909,18 @@ function startServer() { // handle logged on host protected app.get('/logged', (req, res) => { - const ip = getIP(req); - if (allowedIP(ip)) { - res.redirect('/'); - hostCfg.authenticated = true; + const { room } = checkXSS(req.query); + if (!OIDC.enabled && hostCfg.protected) { + const ip = getIP(req); + if (allowedIP(ip)) { + hostCfg.authenticated = true; + res.redirect(room ? '/join/' + room : '/'); + } else { + hostCfg.authenticated = false; + res.redirect(room ? '/login?room=' + room : '/login'); + } } else { - hostCfg.authenticated = false; - res.redirect('/login'); + res.redirect('/'); } }); @@ -772,11 +929,17 @@ function startServer() { // #################################################### // handle login on host protected - app.post('/login', async (req, res) => { + app.post('/login', loginLimiter, async (req, res) => { const ip = getIP(req); log.debug(`Request login to host from: ${ip}`, req.body); - const { username, password } = checkXSS(req.body); + const safeBody = checkXSS(req.body) || {}; + const { username, password } = safeBody; + + if (!username || !password) { + log.warn('Login failed: missing username or password', req.body); + return res.status(400).json({ message: 'Missing username or password' }); + } const isPeerValid = await isAuthPeer(username, password); @@ -790,23 +953,24 @@ function startServer() { authorizedIps: authHost.getAuthorizedIPs(), }); - const isPresenter = - config.presenters && config.presenters.join_first - ? true - : config.presenters && - config.presenters.list && - config.presenters.list.includes(username).toString(); + const isPresenter = Boolean( + hostCfg?.presenters?.join_first || hostCfg?.presenters?.list?.includes(username) + ); + const user = hostCfg.users.find((user) => user.displayname === username || user.username === username); const token = encodeToken({ username: username, password: password, presenter: isPresenter }); const allowedRooms = await getUserAllowedRooms(username, password); - return res.status(200).json({ message: token, allowedRooms: allowedRooms }); + log.debug('login -------------->', { displayName: user?.displayname || username }); + + return res + .status(200) + .json({ message: token, displayname: user?.displayname || username, allowedRooms: allowedRooms }); } if (isPeerValid) { log.debug('PEER LOGIN OK', { ip: ip, authorized: true }); - const isPresenter = - config.presenters && config.presenters.list && config.presenters.list.includes(username).toString(); + const isPresenter = hostCfg?.presenters?.list?.includes(username) || false; const token = encodeToken({ username: username, password: password, presenter: isPresenter }); const allowedRooms = await getUserAllowedRooms(username, password); return res.status(200).json({ message: token, allowedRooms: allowedRooms }); @@ -816,82 +980,234 @@ function startServer() { }); // #################################################### - // KEEP RECORDING ON SERVER DIR + // RECORDING UTILITY // #################################################### - app.post('/recSync', (req, res) => { - // Store recording... - if (serverRecordingEnabled) { - // - try { - const { fileName } = checkXSS(req.query); + function isValidRequest(req, fileName, roomId, durationMs = false, checkContentType = true) { + const contentType = req.headers['content-type']; + if (checkContentType && contentType !== 'application/octet-stream') { + throw new Error('Invalid content type'); + } - if (!fileName) { - return res.status(400).send('Filename not provided'); - } + if (!fileName || sanitizeFilename(fileName) !== fileName || !Validator.isValidRecFileNameFormat(fileName)) { + throw new Error('Invalid file name'); + } - // Sanitize and validate filename - const safeFileName = sanitizeFilename(fileName); - if (safeFileName !== fileName || !Validator.isValidRecFileNameFormat(fileName)) { - log.warn('[RecSync] - Invalid file name:', fileName); - return res.status(400).send('Invalid file name'); - } + if (!roomList || typeof roomList.has !== 'function' || !roomList.has(roomId)) { + throw new Error('Invalid room ID'); + } - const parts = fileName.split('_'); - const roomId = parts[1]; + if ( + durationMs && + typeof durationMs !== 'undefined' && + (!Number.isFinite(Number(durationMs)) || Number(durationMs) <= 0) + ) { + throw new Error('Invalid durationMs'); + } + } - if (!roomList.has(roomId)) { - log.warn('[RecSync] - RoomID not exists in filename', fileName); - return res.status(400).send('Invalid file name'); - } + function getRoomIdFromFilename(fileName) { + const parts = fileName.split('_'); + if (parts.length >= 2) { + return parts[1]; + } + throw new Error('Invalid file name format'); + } - // Ensure directory exists - if (!fs.existsSync(dir.rec)) { - fs.mkdirSync(dir.rec, { recursive: true }); - } + function deleteFile(filePath) { + if (!fs.existsSync(filePath)) return false; - // Resolve and validate file path - const filePath = path.resolve(dir.rec, fileName); - if (!filePath.startsWith(path.resolve(dir.rec))) { - log.warn('[RecSync] - Attempt to save file outside allowed directory:', fileName); - return res.status(400).send('Invalid file path'); - } + try { + fs.unlinkSync(filePath); + log.info(`[Upload] File ${filePath} removed from local after S3 upload`); + } catch (err) { + log.error(`[Upload] Failed to delete local file ${filePath}`, err.message); + } + } - //Validate content type - if (!['application/octet-stream'].includes(req.headers['content-type'])) { - log.warn('[RecSync] - Invalid content type:', req.headers['content-type']); - return res.status(400).send('Invalid content type'); - } + // #################################################### + // RECORDING HANDLERS + // #################################################### + + async function uploadToS3(filePath, fileName, roomId, bucket, s3Client) { + if (!fs.existsSync(filePath)) return false; + + return withFileLock(filePath, async () => { + const fileStream = fs.createReadStream(filePath); + const key = `recordings/${roomId}/${fileName}`; + + const mimeType = mime.lookup(fileName) || 'application/octet-stream'; + + log.debug(`[Upload] Uploading ${fileName}`, { bucket: bucket, key: key, mimeType: mimeType }); + + const upload = new Upload({ + client: s3Client, + params: { + Bucket: bucket, + Key: key, + Body: fileStream, + ContentType: mimeType, + Metadata: { + 'room-id': roomId, + 'file-name': fileName, + }, + }, + }); + + await upload.done(); - // Set up write stream and handle file upload + return { success: true, fileName, key }; + }); + } + + async function saveLocally(filePath, req, recMaxFileSize) { + return withFileLock(filePath, () => { + return new Promise((resolve, reject) => { const writeStream = fs.createWriteStream(filePath, { flags: 'a' }); let receivedBytes = 0; req.on('data', (chunk) => { receivedBytes += chunk.length; if (receivedBytes > recMaxFileSize) { - req.destroy(); // Stop receiving data - writeStream.destroy(); // Stop writing data - log.warn('[RecSync] - File size exceeds limit:', fileName); - return res.status(413).send('File too large'); + req.destroy(); + writeStream.destroy(); + return reject(new Error('File size exceeds limit')); } }); req.pipe(writeStream); - writeStream.on('error', (err) => { - log.error('[RecSync] - Error writing to file:', err.message); - res.status(500).send('Internal Server Error'); - }); + writeStream.on('finish', () => resolve({ status: 'file_saved_locally', path: filePath })); + writeStream.on('error', reject); + }); + }); + } - writeStream.on('finish', () => { - log.debug('[RecSync] - File saved successfully:', fileName); - res.status(200).send('File uploaded successfully'); - }); - } catch (err) { - log.error('[RecSync] - Error processing upload', err.message); - res.status(500).send('Internal Server Error'); + // #################################################### + // RECORDING ROUTE HANDLER + // #################################################### + + app.post('/recSync', async (req, res) => { + if (!serverRecordingEnabled) { + return res.status(403).json({ error: 'Recording disabled' }); + } + + if (!fs.existsSync(dir.rec)) { + fs.mkdirSync(dir.rec, { recursive: true }); + } + + try { + const start = Date.now(); + + const { fileName } = checkXSS(req.query); + const roomId = getRoomIdFromFilename(fileName); + + isValidRequest(req, fileName, roomId); + + const filePath = path.resolve(dir.rec, fileName); + const passThrough = new PassThrough(); + + let totalBytes = 0; + + passThrough.on('data', (chunk) => { + totalBytes += chunk.length; + }); + + req.pipe(passThrough); + + const localStream = passThrough.pipe(new PassThrough()); + + await saveLocally(filePath, localStream, recMaxFileSize); + + const duration = ((Date.now() - start) / 1000).toFixed(2); + const sizeMB = (totalBytes / 1024 / 1024).toFixed(2); + + log.info(`[Upload] Saved ${fileName} (${sizeMB} MB) in ${duration}s`); + + return res.status(200).json({ status: 'upload_complete', fileName }); + } catch (error) { + log.error('Upload error:', error.message); + + if (error.message.includes('exceeds limit')) { + res.status(413).json({ error: 'File too large' }); + } else if (['Invalid content type', 'Invalid file name', 'Invalid room ID'].includes(error.message)) { + res.status(400).json({ error: error.message }); + } else if (error.message.includes('already in progress')) { + res.status(429).json({ error: 'Upload already in progress' }); + } else { + res.status(500).json({ error: 'Internal Server Error' }); + } + } + }); + + app.post('/recSyncFixWebm', async (req, res) => { + try { + const { fileName, durationMs } = checkXSS(req.query); + const roomId = getRoomIdFromFilename(fileName); + + isValidRequest(req, fileName, roomId, durationMs, false); + + const filePath = path.resolve(dir.rec, fileName); + + if (!fs.existsSync(filePath)) { + return res.status(404).json({ message: 'File not found' }); + } + + if (durationMs && fs.existsSync(filePath)) { + try { + fixDurationOrRemux(filePath, Number(durationMs)); + } catch (e) { + console.warn('Finalize fix skipped:', e?.message || e); + } + } + + return res.status(200).json({ message: 'OK' }); + } catch (err) { + console.error('recSyncFixWebm error', err); + return res.status(500).json({ message: 'Internal error' }); + } + }); + + app.post('/recSyncFinalize', async (req, res) => { + try { + const shouldUploadToS3 = config?.integrations?.s3?.enabled && config?.media?.recording?.uploadToS3; + if (!shouldUploadToS3 || !serverRecordingEnabled) { + return res.status(403).json({ error: 'Recording disabled' }); + } + const start = Date.now(); + + const { fileName, durationMs } = checkXSS(req.query); + const roomId = getRoomIdFromFilename(fileName); + + isValidRequest(req, fileName, roomId, durationMs, false); + + const filePath = path.resolve(dir.rec, fileName); + + if (!fs.existsSync(filePath)) { + return res.status(500).json({ error: 'Rec Finalization failed file not exists' }); + } + + if (durationMs && fs.existsSync(filePath)) { + try { + fixDurationOrRemux(filePath, Number(durationMs)); + } catch (e) { + log.warn('Finalize fix skipped:', e?.message || e); + } } + + const bucket = config?.integrations?.s3?.bucket; + const s3 = await uploadToS3(filePath, fileName, roomId, bucket, s3Client); + + const duration = ((Date.now() - start) / 1000).toFixed(2); + + log.info(`[Rec Finalization] done ${fileName} in ${duration}s`, { ...s3 }); + + deleteFile(filePath); // Delete local file after successful upload + + return res.status(200).json({ status: 's3_upload_complete', ...s3 }); + } catch (error) { + log.error('Rec Finalization error', error.message); + return res.status(500).json({ error: 'Rec Finalization failed' }); } }); @@ -934,13 +1250,16 @@ function startServer() { res.json({ enabled: rtmpEnabled }); }); - app.post('/initRTMP', checkRTMPApiSecret, checkMaxStreams, async (req, res) => { + app.post('/initRTMP', checkRTMPApiSecret, checkMaxStreams, (req, res) => { if (!rtmpCfg || !rtmpCfg.enabled) { return res.status(400).send('RTMP server is not enabled or missing the config'); } - const domainName = config.ngrok.enabled ? 'localhost' : req.headers.host.split(':')[0]; + const domainName = config?.integrations?.ngrok?.enabled + ? 'localhost' + : req.headers.host?.split(':')[0] || 'localhost'; + const rtmpUseNodeMediaServer = rtmpCfg.useNodeMediaServer ?? true; const rtmpServer = rtmpCfg.server != '' ? rtmpCfg.server : false; const rtmpServerAppName = rtmpCfg.appName != '' ? rtmpCfg.appName : 'live'; const rtmpStreamKey = rtmpCfg.streamKey != '' ? rtmpCfg.streamKey : uuidv4(); @@ -949,12 +1268,13 @@ function startServer() { const rtmpServerURL = rtmpServer ? rtmpServer : `rtmp://${domainName}:1935`; const rtmpServerPath = '/' + rtmpServerAppName + '/' + rtmpStreamKey; - const rtmp = rtmpServerSecret + const rtmp = rtmpUseNodeMediaServer ? generateRTMPUrl(rtmpServerURL, rtmpServerPath, rtmpServerSecret, expirationHours) : rtmpServerURL + rtmpServerPath; log.info('initRTMP', { headers: req.headers, + rtmpUseNodeMediaServer: rtmpUseNodeMediaServer, rtmpServer, rtmpServerSecret, rtmpServerURL, @@ -1020,12 +1340,12 @@ function startServer() { app.get('/:roomId', (req, res) => { const { roomId } = checkXSS(req.params); - if (!roomId) { - log.warn('/:roomId empty', roomId); + if (!Validator.isValidRoomName(roomId)) { + log.warn('/:roomId not valid', roomId); return res.redirect('/'); } - log.debug('Detected roomId --> redirect to /join?room=roomId'); + log.debug(`Detected roomId --> redirect to /join?room=${roomId}`); res.redirect(`/join/${roomId}`); }); @@ -1193,6 +1513,44 @@ function startServer() { }); }); + // request end meeting room endpoint + app.delete(restApi.basePath + '/meeting/:room', (req, res) => { + try { + // Check if endpoint allowed + if (restApi.allowed && !restApi.allowed.meetingEnd) { + return res.status(403).json({ + success: false, + error: 'This endpoint has been disabled. Please contact the administrator for further information.', + }); + } + // check if user was authorized for the api call + const { host, authorization } = req.headers; + const api = new ServerApi(host, authorization); + if (!api.isAuthorized()) { + log.debug('MiroTalk end meeting - Unauthorized', { + header: req.headers, + body: req.body, + }); + return res.status(403).json({ error: 'Unauthorized!' }); + } + // End the meeting + const { room } = req.params; + const { redirect } = req.body || {}; + const result = api.endMeeting(roomList, room, redirect); + const status = result.success ? 200 : 404; + res.status(status).json(result); + // log.debug the output if all done + log.debug('MiroTalk end meeting - Authorized', { + header: req.headers, + room: room, + result: result, + }); + } catch (error) { + console.error('Error ending meeting', error); + res.status(500).json({ success: false, error: 'Failed to end meeting.' }); + } + }); + // #################################################### // SLACK API // #################################################### @@ -1202,7 +1560,7 @@ function startServer() { if (restApi.allowed && !restApi.allowed.slack) { return res.end( - '`This endpoint has been disabled`. Please contact the administrator for further information.', + '`This endpoint has been disabled`. Please contact the administrator for further information.' ); } @@ -1230,67 +1588,158 @@ function startServer() { return res.end('`Wrong signature` - Verification failed!'); }); - // not match any of page before, so 404 not found - app.get('*', function (req, res) { - res.sendFile(views.notFound); - }); - // #################################################### - // SERVER CONFIG + // AUTHORIZED API IF ALLOWED // #################################################### - function getServerConfig(tunnel = false) { - return { - // General Server Information - server_listen: host, - server_tunnel: tunnel, - trust_proxy: trustProxy, - - // Core Configurations - cors_options: corsOptions, - jwtCfg: jwtCfg, - rest_api: restApi, - - // Middleware and UI - middleware: config.middleware, - configUI: config.ui, - - // Security, Authorization, and User Management - oidc: OIDC.enabled ? OIDC : false, - hostProtected: hostCfg.protected || hostCfg.user_auth ? hostCfg : false, - ip_lookup_enabled: config.IPLookup?.enabled ? config.IPLookup : false, - presenters: config.presenters, + // request active rooms endpoint + app.get(restApi.basePath + '/activeRooms', (req, res) => { + // Check if endpoint allowed + if (!config.ui?.rooms?.showActive) { + return res.status(403).json({ + error: 'This endpoint has been disabled. Please contact the administrator for further information.', + }); + } + // check if user was authorized for the api call + const { host, authorization = config.api.keySecret } = req.headers; + const api = new ServerApi(host, authorization); + + // Get active rooms + const activeRooms = api.getActiveRooms(roomList); + res.json({ activeRooms: activeRooms }); + + // log.debug the output if all done + log.debug('MiroTalk get active rooms - Authorized', { + header: req.headers, + body: req.body, + activeRooms: activeRooms, + }); + }); + + // not match any of page before, so 404 not found + app.use((req, res) => { + res.sendFile(views.notFound); + }); + + // Global error handler for URIError and other errors + app.use((err, req, res, next) => { + if (err instanceof URIError) { + log.warn('Malformed URI detected', { + url: req.url, + ip: getIP(req), + error: err.message, + }); + return res.status(400).send({ status: 400, message: 'Invalid URL encoding' }); + } + // Handle other errors + log.error('Unhandled error', { + url: req.url, + error: err.message, + stack: err.stack, + }); + res.status(500).send({ status: 500, message: 'Internal server error' }); + }); + + // #################################################### + // SERVER CONFIG + // #################################################### + + function getServerConfig(tunnel = false) { + const safeConfig = { + // Network & Connectivity + network: { + server_listen: host, + server_tunnel: tunnel, + trust_proxy: trustProxy, + sfu: { + announcedIP: announcedAddress, + listenIP: IP, + numWorker: config.mediasoup?.numWorkers, + rtcMinPort: config.mediasoup?.worker?.rtcMinPort, + rtcMaxPort: config.mediasoup?.worker?.rtcMaxPort, + }, + ngrok_enabled: config.ngrok?.enabled ? config.ngrok : false, + }, + + // Security & Authentication + security: { + cors: corsOptions, + jwtCfg: jwtCfg, + host: hostCfg?.protected || hostCfg?.user_auth ? hostCfg : { presenters: hostCfg.presenters }, + ip_lookup: config.integrations?.IPLookup?.enabled ? config.integrations.IPLookup : false, + oidc: OIDC?.enabled ? OIDC : false, + middleware: { + IpWhitelist: config?.security?.middleware?.IpWhitelist?.enabled + ? config.security.middleware.IpWhitelist + : false, + //... + }, + }, + + // API & Services + api: { + rest_api: restApi, + webhook: webhook.enabled ? webhook : false, + }, + + // Media Configuration + media: { + mediasoup: { + listenInfos: config.mediasoup?.webRtcTransport?.listenInfos, + worker_bin: mediasoup?.workerBin, + }, + rtmp: rtmpCfg?.enabled ? rtmpCfg : false, + videoAI: config.integrations?.videoAI?.enabled ? config.integrations.videoAI : false, + server_recording: config?.media?.recording?.enabled ? config.media.recording : false, + }, // Communication Integrations - discord_enabled: config.discord?.enabled ? config.discord : false, - mattermost_enabled: config.mattermost?.enabled ? config.mattermost : false, - slack_enabled: slackEnabled ? config.slack : false, - chatGPT_enabled: config.chatGPT?.enabled ? config.chatGPT : false, - - // Media and Video Configurations - mediasoup_listenInfos: config.mediasoup.webRtcTransport.listenInfos, - mediasoup_worker_bin: mediasoup.workerBin, - rtmp_enabled: rtmpCfg.enabled ? rtmpCfg : false, - videAI_enabled: config.videoAI.enabled ? config.videoAI : false, - serverRec: config?.server?.recording, - - // Centralized Logging - sentry_enabled: sentryEnabled ? config.sentry : false, - - // Additional Configurations and Features - survey_enabled: config.survey?.enabled ? config.survey : false, - redirect_enabled: config.redirect?.enabled ? config.redirect : false, - stats_enabled: config.stats?.enabled ? config.stats : false, - ngrok_enabled: config.ngrok?.enabled ? config.ngrok : false, - email_alerts: config.email?.alert ? config.email : false, - webhook: webhook, + integrations: { + discord: config.integrations?.discord?.enabled ? config.integrations.discord : false, + mattermost: config.integrations?.mattermost?.enabled ? config.integrations.mattermost : false, + slack: slackEnabled ? config.integrations?.slack : false, + chatGPT: config.integrations?.chatGPT?.enabled ? config.integrations.chatGPT : false, + deepSeek: config.integrations?.deepSeek?.enabled ? config.integrations.deepSeek : false, + email_alerts: config?.integrations?.email?.alert ? config.integrations.email : false, + }, + + // UI & Branding + ui: { + brand: config.ui?.brand, + buttons: config.ui?.buttons, + }, + + // Monitoring & Analytics + monitoring: { + sentry: sentryEnabled ? config.integrations?.sentry : false, + stats: config.features?.stats?.enabled ? config.features.stats : false, + system_info: config.system?.info, + }, + + // Features & Functionality + features: { + survey: config.features?.survey?.enabled ? config.features.survey : false, + redirect: config.features?.redirect?.enabled ? config.features.redirect : false, + }, + + // Global Moderation & Management + moderation: { + room: { + maxParticipants: config?.moderation?.room?.maxParticipants || 1000, + lobby: config?.moderation?.room?.lobby || false, + }, + }, // Version Information - app_version: packageJson.version, - node_version: process.versions.node, - mediasoup_server_version: mediasoup.version, - mediasoup_client_version: mediasoupClient.version, + versions: { + app: packageJson?.version, + node: process.versions.node, + server_version: mediasoup?.version, + client_version: mediasoupClient?.version, + }, }; + + return safeConfig; } // #################################################### @@ -1299,24 +1748,34 @@ function startServer() { async function ngrokStart() { try { - await ngrok.authtoken(config.ngrok.authToken); - await ngrok.connect(config.server.listen.port); - const api = ngrok.getApi(); - const list = await api.listTunnels(); - const tunnel = list.tunnels[0].public_url; - log.info('Server config', getServerConfig(tunnel)); + await ngrok.authtoken(config?.integrations?.ngrok?.authToken); + const listener = await ngrok.forward({ addr: config?.server?.listen?.port }); + const tunnelUrl = listener.url(); + log.info('Server config', getServerConfig(tunnelUrl)); } catch (err) { - log.error('Ngrok Start error: ', err.body); + log.warn('Ngrok Start error', err); await ngrok.kill(); process.exit(1); } } + // #################################################### + // HANDLE SERVER CLIENT ERRORS + // #################################################### + + server.on('clientError', (err, socket) => { + if (socket.writable) { + socket.end('HTTP/1.1 400 Bad Request\r\n\r\n'); + } + socket.destroy(); + log.warn('Client connection error', { error: err.message, code: err.code }); + }); + // #################################################### // START SERVER // #################################################### - server.listen(config.server.listen.port, () => { + server.listen(config?.server?.listen?.port || 3010, () => { log.log( `%c @@ -1328,10 +1787,10 @@ function startServer() { ╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚══════╝╚══════╝╚═╝ ╚═╝ ╚═══╝ ╚══════╝╚═╝ ╚═╝ started... `, - 'font-family:monospace', + 'font-family:monospace' ); - if (config.ngrok.enabled && config.ngrok.authToken !== '') { + if (config?.integrations?.ngrok?.enabled && config?.integrations?.ngrok?.authToken !== '') { return ngrokStart(); } log.info('Server config', getServerConfig()); @@ -1387,8 +1846,14 @@ function startServer() { } worker.on('died', () => { - log.error('Mediasoup worker died, exiting in 2 seconds... [pid:%d]', worker.pid); - setTimeout(() => process.exit(1), 2000); + log.error('Mediasoup worker died, exiting in 5 seconds...', worker.pid); + + nodemailer.sendEmailAlert('alert', { + subject: 'Worker Died', + body: `The Worker with PID ${worker.pid} has died unexpectedly!`, + }); + + setTimeout(() => process.exit(1), 5000); }); workers.push(worker); @@ -1396,9 +1861,9 @@ function startServer() { /* setInterval(async () => { const usage = await worker.getResourceUsage(); - log.info('mediasoup Worker resource usage', { worker_pid: worker.pid, usage: usage }); + log.debug('mediasoup Worker resource usage', { worker_pid: worker.pid, usage: usage }); const dump = await worker.dump(); - log.info('mediasoup Worker dump', { worker_pid: worker.pid, dump: dump }); + log.debug('mediasoup Worker dump', { worker_pid: worker.pid, dump: dump }); }, 120000); */ } @@ -1457,13 +1922,13 @@ function startServer() { const peer_ip = getIpSocket(socket); // Get peer Geo Location - if (config.IPLookup.enabled && peer_ip != '::1') { + if (config?.integrations?.IPLookup?.enabled && peer_ip != '::1') { dataObject.peer_geo = await getPeerGeoLocation(peer_ip); } const data = checkXSS(dataObject); - log.info('User joined', data); + log.debug('User joined', data); if (!Validator.isValidRoomName(socket.room_id)) { log.warn('[Join] - Invalid room name', socket.room_id); @@ -1472,10 +1937,19 @@ function startServer() { const room = getRoom(socket); - const { peer_name, peer_id, peer_uuid, peer_token, os_name, os_version, browser_name, browser_version } = - data.peer_info; + const { + peer_name, + peer_id, + peer_uuid, + peer_token, + peer_presenter, + os_name, + os_version, + browser_name, + browser_version, + } = data.peer_info; - let is_presenter = true; + let is_presenter = peer_presenter; // User Auth required or detect token, we check if peer valid if (hostCfg.user_auth || peer_token) { @@ -1502,7 +1976,7 @@ function startServer() { is_presenter = presenter === '1' || presenter === 'true' || - (config.presenters.join_first && room.getPeersCount() === 0); + (hostCfg?.presenters?.join_first && room?.getPeersCount() === 0); log.debug('[Join] - HOST PROTECTED - USER AUTH check peer', { ip: peer_ip, @@ -1533,7 +2007,7 @@ function startServer() { // check if banned... if (room.isBanned(peer_uuid)) { - log.info('[Join] - peer is banned!', { + log.debug('[Join] - peer is banned!', { room_id: data.room_id, peer: { name: peer_name, @@ -1547,15 +2021,21 @@ function startServer() { return cb('isBanned'); } + // Remove old peer with same socket.id before adding new one + const existingPeer = room.getPeer(socket.id); + if (existingPeer) { + room.removePeer(socket.id); + } + room.addPeer(new Peer(socket.id, data)); const activeRooms = getActiveRooms(); - log.info('[Join] - current active rooms', activeRooms); + log.debug('[Join] - current active rooms', activeRooms); const activeStreams = getRTMPActiveStreams(); - log.info('[Join] - current active RTMP streams', activeStreams); + log.debug('[Join] - current active RTMP streams', activeStreams); if (!(socket.room_id in presenters)) presenters[socket.room_id] = {}; @@ -1566,17 +2046,16 @@ function startServer() { peer_uuid: peer_uuid, is_presenter: is_presenter, }; - // first we check if the username match the presenters username - if (config.presenters && config.presenters.list && config.presenters.list.includes(peer_name)) { + // first we check if the username match the presenters username else if join_first enabled + if ( + hostCfg?.presenters?.list?.includes(peer_name) || + (hostCfg?.presenters?.join_first && Object.keys(presenters[socket.room_id]).length === 0) + ) { + presenter.is_presenter = true; presenters[socket.room_id][socket.id] = presenter; - } else { - // if not match the presenters username, the first one join room is the presenter - if (Object.keys(presenters[socket.room_id]).length === 0) { - presenters[socket.room_id][socket.id] = presenter; - } } - log.info('[Join] - Connected presenters grp by roomId', presenters); + log.debug('[Join] - Connected presenters grp by roomId', presenters); const isPresenter = peer_token ? is_presenter @@ -1590,23 +2069,24 @@ function startServer() { }); } - peer.updatePeerInfo({ type: 'presenter', status: isPresenter }); - - log.info('[Join] - Is presenter', { + log.debug('[Join] - Is Peer presenter', { roomId: socket.room_id, peer_name: peer_name, peer_presenter: isPresenter, }); + peer.updatePeerInfo({ type: 'presenter', status: isPresenter }); + if (room.isLocked() && !isPresenter) { log.debug('The user was rejected because the room is locked, and they are not a presenter'); return cb('isLocked'); } - if (room.isLobbyEnabled() && !isPresenter) { + if ((room.isLobbyEnabled() || room.isGlobalLobbyEnabled()) && !isPresenter) { log.debug( - 'The user is currently waiting to join the room because the lobby is enabled, and they are not a presenter', + 'The user is currently waiting to join the room because the lobby is enabled, and they are not a presenter' ); + peer.updatePeerInfo({ type: 'lobby', status: true }); room.broadCast(socket.id, 'roomLobby', { peer_id: peer_id, peer_name: peer_name, @@ -1623,26 +2103,46 @@ function startServer() { } } - // SCENARIO: Notify when the first user join room and is awaiting assistance... - if (room.getPeersCount() === 1) { - nodemailer.sendEmailAlert('join', { - room_id: room.id, - peer_name: peer_name, - domain: socket.handshake.headers.host.split(':')[0], - os: os_name ? `${os_name} ${os_version}` : '', - browser: browser_name ? `${browser_name} ${browser_version}` : '', - }); // config.email.alert: true + // Email body payload + const emailPayload = { + room_id: room.id, + peer_name: peer_name, + domain: socket.handshake.headers.host.split(':')[0], + os: os_name ? `${os_name} ${os_version}` : '', + browser: browser_name ? `${browser_name} ${browser_version}` : '', + }; + + const firstJoin = room.getPeersCount() === 1; + const guestJoin = room.getPeersCount() === 2; + + // SCENARIO: Notify when the first user join room and is awaiting assistance (global email alert) + if (firstJoin && !widget.alert.enabled) { + nodemailer.sendEmailAlert('join', emailPayload); } - // handle WebHook - if (webhook.enabled) { - // Trigger a POST request when a user joins - axios - .post(webhook.url, { event: 'join', data }) - .then((response) => log.debug('Join event tracked:', response.data)) - .catch((error) => log.error('Error tracking join event:', error.message)); + // SCENARIO: Notify when the first guest user join room and presenter in (room email notification) + if (guestJoin) { + const notifications = room.getRoomNotifications(); + log.debug('Room notifications on guest join', { notifications: notifications }); + if (notifications?.mode?.email && notifications?.events?.join) { + nodemailer.sendEmailNotifications('join', emailPayload, notifications); + } + } + + // SCENARIO: Notify when a user joins the widget room for expert assistance + if (firstJoin && widget.enabled && widget.alert && widget.alert.enabled && widget.roomId === room.id) { + switch (widget.alert.type) { + case 'email': + nodemailer.sendEmailAlert('widget', emailPayload); + break; + // case slack, discord, webhook, ... + default: + log.warn('Unknown alert type for widget', { type: widget.type }); + } } + handleJoinWebHook(room.id, data.peer_info); + cb(room.toJson()); }); @@ -1652,22 +2152,19 @@ function startServer() { } const { room, peer } = getRoomAndPeer(socket); + const peerInfo = getPeerInfo(peer); - const { peer_name } = peer || 'undefined'; - - log.debug('Get RouterRtpCapabilities', peer_name); + log.debug('Request: getRouterRtpCapabilities', peerInfo); try { - const getRouterRtpCapabilities = room.getRtpCapabilities(); - - //log.debug('Get RouterRtpCapabilities callback', { callback: getRouterRtpCapabilities }); - - callback(getRouterRtpCapabilities); + const rtpCapabilities = room.getRtpCapabilities(); + callback(rtpCapabilities); } catch (err) { - log.error('Get RouterRtpCapabilities error', err); - callback({ + log.error('Failed to get Router RTP Capabilities', { error: err.message, + peerInfo, }); + callback({ error: err.message }); } }); @@ -1677,22 +2174,15 @@ function startServer() { } const { room, peer } = getRoomAndPeer(socket); + const peerInfo = getPeerInfo(peer); - const { peer_name, peer_info } = peer; - - log.debug('Create WebRtc transport', peer_name); + log.debug('Create WebRTC transport request received', peerInfo); try { const createWebRtcTransport = await room.createWebRtcTransport(socket.id); - - //log.debug('Create WebRtc transport callback', { callback: createWebRtcTransport }); - callback(createWebRtcTransport); } catch (err) { - log.error('Create WebRtc Transport error', { - error: err, - peer_info, - }); + log.warn('Create WebRTC Transport warning', { error: err.message, peerInfo }); callback({ error: err.message }); } }); @@ -1703,19 +2193,15 @@ function startServer() { } const { room, peer } = getRoomAndPeer(socket); + const peerInfo = getPeerInfo(peer); - const { peer_name } = peer || 'undefined'; - - log.debug('Connect transport', { peer_name: peer_name, transport_id: transport_id }); + log.debug('Connect transport request received', { transport_id, peerInfo }); try { const connectTransport = await room.connectPeerTransport(socket.id, transport_id, dtlsParameters); - - //log.debug('Connect transport', { callback: connectTransport }); - callback(connectTransport); } catch (err) { - log.error('Connect transport error', err); + log.warn('Connect transport warning', { error: err.message, peerInfo }); callback({ error: err.message }); } }); @@ -1731,24 +2217,25 @@ function startServer() { return callback({ error: 'Peer not found' }); } - const { peer_name } = peer || 'undefined'; + const peerInfo = getPeerInfo(peer); - log.debug('Restart ICE', { peer_name: peer_name, transport_id: transport_id }); + log.debug('Restart ICE request received', { transport_id: transport_id, peerInfo }); try { const transport = peer.getTransport(transport_id); if (!transport) { - throw new Error(`Restart ICE, transport with id "${transport_id}" not found`); + log.warn(`Restart ICE attempt failed. Transport with ID "${transport_id}" not found.`); + return callback({ error: `Transport with id "${transport_id}" not found` }); } const iceParameters = await transport.restartIce(); - log.debug('Restart ICE callback', { callback: iceParameters }); + log.debug('ICE Restart successful', { transport_id: transport_id, iceParameters }); callback(iceParameters); } catch (err) { - log.error('Restart ICE error', err); + log.warn('Restart ICE warning', { error: err.message, peerInfo }); callback({ error: err.message }); } }); @@ -1764,11 +2251,11 @@ function startServer() { return callback({ error: 'Peer not found' }); } - const { peer_name } = peer || 'undefined'; + const peerInfo = getPeerInfo(peer); const data = { room_id: room.id, - peer_name: peer_name, + peer_name: peerInfo.peer_name, peer_id: socket.id, kind: kind, type: appData.mediaType, @@ -1783,18 +2270,18 @@ function startServer() { producerTransportId, rtpParameters, kind, - appData.mediaType, + appData.mediaType ); log.debug('Produce', { kind: kind, type: appData.mediaType, - peer_name: peer_name, - peer_id: socket.id, producer_id: producer_id, + peer_id: socket.id, + peerInfo: peerInfo, }); - // add & monitor producer audio level and active speaker + // add & monitor producer audio level and dominant speaker if (kind === 'audio') { room.addProducerToAudioLevelObserver({ producerId: producer_id }); room.addProducerToActiveSpeakerObserver({ producerId: producer_id }); @@ -1802,7 +2289,10 @@ function startServer() { callback({ producer_id }); } catch (err) { - log.error('Producer transport error', err); + log.warn('Producer transport error', { + error: err, + peerInfo, + }); callback({ error: err.message }); } }); @@ -1818,25 +2308,27 @@ function startServer() { return callback({ error: 'Peer not found' }); } - const { peer_name } = peer || 'undefined'; + const peerInfo = getPeerInfo(peer); try { const params = await room.consume(socket.id, consumerTransportId, producerId, rtpCapabilities, type); log.debug('Consuming', { - peer_name: peer_name, producer_type: type, producer_id: producerId, consumer_id: params ? params.id : undefined, + peerInfo: peerInfo, }); callback(params); } catch (err) { - log.error('Consumer transport error', { + log.warn('Consumer transport error', { error: err, type, + consumerTransportId, producerId, rtpCapabilities, + peerInfo, }); callback({ error: err.message }); } @@ -1845,19 +2337,26 @@ function startServer() { socket.on('producerClosed', (data) => { if (!roomExists(socket)) return; - try { - const { room, peer } = getRoomAndPeer(socket); + const { room, peer } = getRoomAndPeer(socket); - room.closeProducer(socket.id, data.producer_id); + const peerInfo = getPeerInfo(peer); + + if (peer) peer.updatePeerInfo(data); // peer_info.audio OR video OFF - if (peer) peer.updatePeerInfo(data); // peer_info.audio OR video OFF + try { + room.closeProducer(socket.id, data.producer_id); } catch (err) { - log.error('Producer Close error', err.message); + log.warn('Producer Close error', { + error: err.message, + peerInfo, + }); } }); socket.on('pauseProducer', async ({ producer_id, type }, callback) => { - if (!roomExists(socket)) return; + if (!roomExists(socket)) { + return callback({ error: 'Room not found' }); + } const peer = getPeer(socket); @@ -1873,21 +2372,27 @@ function startServer() { return callback({ error: `Producer with id "${producer_id}" type "${type}" not found` }); } + const peerInfo = getPeerInfo(peer); + try { await producer.pause(); - const { peer_name } = peer || 'undefined'; - - log.debug('Producer paused', { peer_name, producer_id, type }); + log.debug('Producer paused', { producer_id, type, peerInfo }); callback('successfully'); } catch (error) { + log.warn('Pause producer', { + error: error, + peerInfo, + }); callback({ error: error.message }); } }); socket.on('resumeProducer', async ({ producer_id, type }, callback) => { - if (!roomExists(socket)) return; + if (!roomExists(socket)) { + return callback({ error: 'Room not found' }); + } const peer = getPeer(socket); @@ -1903,21 +2408,27 @@ function startServer() { return callback({ error: `producer with id "${producer_id}" type "${type}" not found` }); } + const peerInfo = getPeerInfo(peer); + try { await producer.resume(); - const { peer_name } = peer || 'undefined'; - - log.debug('Producer resumed', { peer_name, producer_id, type }); + log.debug('Producer resumed', { producer_id, type, peerInfo }); callback('successfully'); } catch (error) { + log.warn('Resume producer', { + error: error, + peerInfo, + }); callback({ error: error.message }); } }); socket.on('resumeConsumer', async ({ consumer_id, type }, callback) => { - if (!roomExists(socket)) return; + if (!roomExists(socket)) { + return callback({ error: 'Room not found' }); + } const peer = getPeer(socket); @@ -1933,15 +2444,19 @@ function startServer() { return callback({ error: `consumer with id "${consumer_id}" type "${type}" not found` }); } + const peerInfo = getPeerInfo(peer); + try { await consumer.resume(); - const { peer_name } = peer || 'undefined'; - - log.debug('Consumer resumed', { peer_name, consumer_id, type }); + log.debug('Consumer resumed', { consumer_id, type, peerInfo }); callback('successfully'); } catch (error) { + log.warn('Resume consumer', { + error: error, + peerInfo, + }); callback({ error: error.message }); } }); @@ -1955,14 +2470,16 @@ function startServer() { log.debug('Get producers', peer_name); - // send all the current producer to newly joined member - const producerList = room.getProducerListForPeer(); + // send all the current producer to newly joined member (excluding own producers) + const producerList = room.getProducerListForPeer(socket.id); socket.emit('newProducers', producerList); }); socket.on('getPeerCounts', async ({}, callback) => { - if (!roomExists(socket)) return; + if (!roomExists(socket)) { + return callback({ error: 'Room not found' }); + } const room = getRoom(socket); @@ -1978,14 +2495,16 @@ function startServer() { const data = checkXSS(dataObject); + log.debug('cmd', data); + + if (!Validator.isValidData(data)) return; + const room = getRoom(socket); const peer = getPeer(socket); if (!room || !peer) return; - log.debug('cmd', data); - switch (data.type) { case 'privacy': peer.updatePeerInfo({ type: data.type, status: data.active }); @@ -2014,12 +2533,17 @@ function startServer() { const data = checkXSS(dataObject); + if (!Validator.isValidData(data)) { + log.warn('Room action invalid data', { peer_id: socket.id, room_id: socket.room_id }); + return; + } + + log.debug('Room action:', data); + const isPresenter = isPeerPresenter(socket.room_id, socket.id, data.peer_name, data.peer_uuid); const room = getRoom(socket); - log.debug('Room action:', data); - switch (data.action) { case 'broadcasting': if (!isPresenter) return; @@ -2049,6 +2573,10 @@ function startServer() { room.setLocked(false); room.broadCast(socket.id, 'roomAction', data.action); break; + case 'globalLobbyOn': + if (!room.isGlobalLobbyEnabled()) return; + room.setLobbyEnabled(true); + break; case 'lobbyOn': if (!isPresenter) return; room.setLobbyEnabled(true); @@ -2070,7 +2598,7 @@ function startServer() { room.broadCast(socket.id, 'roomAction', data.action); break; case 'isBanned': - log.info('The user has been banned from the room due to spamming messages', data); + log.debug('The user has been banned from the room due to spamming messages', data); room.addBannedPeer(data.peer_uuid); break; default: @@ -2101,12 +2629,24 @@ function startServer() { broadcast: data.broadcast, }); - if (data.peers_id && data.broadcast) { - for (let peer_id in data.peers_id) { - room.sendTo(data.peers_id[peer_id], 'roomLobby', data); + const pears_id = data.peers_id ? data.peers_id : [data.peer_id]; + + // Also send to all presenters to update lobby UI on there side + const send_to_pears_id = pears_id.concat(room.getPresenterPeers().map((peer) => peer.id)); + + for (const peer_id of send_to_pears_id) { + room.sendTo(peer_id, 'roomLobby', data); + } + + if (data.lobby_status === 'accept') { + for (const peer_id of pears_id) { + const peer = room.getPeer(peer_id); + if (!peer.peer_lobby) continue; + + peer.updatePeerInfo({ type: 'lobby', status: false }); + + handleJoinWebHook(room.id, peer.peer_info); } - } else { - room.sendTo(data.peer_id, 'roomLobby', data); } }); @@ -2117,6 +2657,8 @@ function startServer() { log.debug('Peer action', data); + if (!Validator.isValidData(data)) return; + const presenterActions = [ 'mute', 'unmute', @@ -2134,7 +2676,7 @@ function startServer() { socket.room_id, socket.id, data.from_peer_name, - data.from_peer_uuid, + data.from_peer_uuid ); if (!isPresenter) return; } @@ -2157,6 +2699,8 @@ function startServer() { const data = checkXSS(dataObject); + if (!Validator.isValidData(data)) return; + peer.updatePeerInfo(data); if (data.broadcast) { @@ -2165,11 +2709,38 @@ function startServer() { } }); + socket.on('updateRoomNotifications', (dataObject, cb) => { + if (!roomExists(socket)) return; + + if (config.integrations?.email?.notify !== true) { + const message = + 'Email notifications are disabled by the admin. Enable this feature in your self-hosted instance for full functionality.'; + log.debug(message); + return cb({ error: message }); + } + + const data = checkXSS(dataObject); + + if (!Validator.isValidData(data)) return; + + const room = getRoom(socket); + + const isPresenter = isPeerPresenter(socket.room_id, socket.id, data.peer_name, data.peer_uuid); + + if (!isPresenter) return; + + room.updateRoomNotifications(data); + + return cb({ message: true }); + }); + socket.on('updateRoomModerator', (dataObject) => { if (!roomExists(socket)) return; const data = checkXSS(dataObject); + if (!Validator.isValidData(data)) return; + const room = getRoom(socket); const isPresenter = isPeerPresenter(socket.room_id, socket.id, data.peer_name, data.peer_uuid); @@ -2186,6 +2757,7 @@ function startServer() { case 'screen_cant_share': case 'chat_cant_privately': case 'chat_cant_chatgpt': + case 'chat_cant_deep_seek': case 'media_cant_sharing': room.broadCast(socket.id, 'updateRoomModerator', moderator); break; @@ -2199,6 +2771,8 @@ function startServer() { const data = checkXSS(dataObject); + if (!Validator.isValidData(data)) return; + const room = getRoom(socket); const isPresenter = isPeerPresenter(socket.room_id, socket.id, data.peer_name, data.peer_uuid); @@ -2213,7 +2787,9 @@ function startServer() { }); socket.on('getRoomInfo', async (_, cb) => { - if (!roomExists(socket)) return; + if (!roomExists(socket)) { + return cb({ error: 'Room not found' }); + } const { room, peer } = getRoomAndPeer(socket); @@ -2229,6 +2805,8 @@ function startServer() { const data = checkXSS(dataObject); + if (!Validator.isValidData(data)) return; + if (!isValidFileName(data.fileName)) { log.debug('File name not valid', data); return; @@ -2254,6 +2832,8 @@ function startServer() { const data = checkXSS(dataObject); + if (!Validator.isValidData(data)) return; + const room = getRoom(socket); room.broadCast(socket.id, 'fileAbort', data); @@ -2264,6 +2844,8 @@ function startServer() { const data = checkXSS(dataObject); + if (!Validator.isValidData(data)) return; + const room = getRoom(socket); room.broadCast(socket.id, 'receiveFileAbort', data); @@ -2274,6 +2856,8 @@ function startServer() { const data = checkXSS(dataObject); + if (!Validator.isValidData(data)) return; + if (data.action == 'open' && !isValidHttpURL(data.video_url)) { log.debug('Video src not valid', data); return; @@ -2309,17 +2893,30 @@ function startServer() { const data = checkXSS(dataObject); + if (!Validator.isValidData(data)) return; + const room = getRoom(socket); log.debug('Whiteboard', data); room.broadCast(socket.id, 'whiteboardAction', data); }); + // Video drawing overlay: relay batched drawing strokes to all peers in the room + socket.on('videoDrawing', (dataObject) => { + if (!roomExists(socket)) return; + const data = checkXSS(dataObject); + const room = getRoom(socket); + // log.debug('Video drawing', data); + room.broadCast(socket.id, 'videoDrawing', data); + }); + socket.on('setVideoOff', (dataObject) => { if (!roomExists(socket)) return; const data = checkXSS(dataObject); + if (!Validator.isValidData(data)) return; + log.debug('Video off data', data.peer_name); const room = getRoom(socket); @@ -2332,6 +2929,8 @@ function startServer() { const data = checkXSS(dataObject); + if (!Validator.isValidData(data)) return; + log.debug('Recording action', data); const room = getRoom(socket); @@ -2359,6 +2958,8 @@ function startServer() { const data = checkXSS(dataObject); + if (!Validator.isValidData(data)) return; + const { room, peer } = getRoomAndPeer(socket); const { peer_name } = peer || 'undefined'; @@ -2375,7 +2976,7 @@ function startServer() { return; } - log.info('message', data); + log.debug('message', data); data.to_peer_id == 'all' ? room.broadCast(socket.id, 'message', data) @@ -2383,34 +2984,51 @@ function startServer() { }); socket.on('getChatGPT', async ({ time, room, name, prompt, context }, cb) => { - if (!roomExists(socket)) return; + if (!roomExists(socket)) { + return cb({ message: 'Room not found' }); + } - if (!config.chatGPT.enabled) return cb({ message: 'ChatGPT seems disabled, try later!' }); + if (!config?.integrations?.chatGPT?.enabled) { + return cb({ message: 'ChatGPT integration is disabled. Please try again later!' }); + } // https://platform.openai.com/docs/api-reference/completions/create try { + if (!prompt || !Array.isArray(context)) { + throw new Error('Invalid input: Prompt or context is missing or invalid'); + } // Add the prompt to the context context.push({ role: 'user', content: prompt }); + // Call OpenAI's API to generate response const completion = await chatGPT.chat.completions.create({ - model: config.chatGPT.model || 'gpt-3.5-turbo', + model: config?.integrations?.chatGPT?.model || 'gpt-3.5-turbo', messages: context, - max_tokens: config.chatGPT.max_tokens, - temperature: config.chatGPT.temperature, + max_tokens: config?.integrations?.chatGPT?.max_tokens || 1024, + temperature: config?.integrations?.chatGPT?.temperature || 0.7, }); - // Extract message from completion + + // Extract the assistant's response const message = completion.choices[0].message.content.trim(); + if (!message) { + throw new Error('ChatGPT returned an empty response.'); + } + // Add response to context context.push({ role: 'assistant', content: message }); - // Log conversation details - log.info('ChatGPT', { - time: time, - room: room, - name: name, - context: context, + + // Log the conversation details + log.debug('ChatGPT Response', { + time, + room, + name, + prompt, + response: message, + context, }); + // Callback response to client - cb({ message: message, context: context }); + cb({ message, context }); } catch (error) { if (error.name === 'APIError') { log.error('ChatGPT', { @@ -2420,230 +3038,256 @@ function startServer() { code: error.code, type: error.type, }); - cb({ message: error.message }); + return cb({ message: `ChatGPT API Error: ${error.message}` }); } else { - // Non-API error - log.error('ChatGPT', error); - cb({ message: error.message }); + // Handle general errors + log.error('ChatGPT Error', error); + cb({ message: `Error: ${error.message}` }); } } }); - // https://docs.heygen.com/reference/list-avatars-v2 - socket.on('getAvatarList', async ({}, cb) => { - if (!config.videoAI.enabled || !config.videoAI.apiKey) - return cb({ error: 'Video AI seems disabled, try later!' }); + socket.on('getDeepSeek', async ({ time, room, name, prompt, context }, cb) => { + if (!roomExists(socket)) { + return cb({ message: 'Room not found' }); + } + + if (!config?.integrations?.deepSeek?.enabled) { + return cb({ message: 'DeepSeek integration is disabled. Please try again later!' }); + } try { - const response = await axios.get(`${config.videoAI.basePath}/v2/avatars`, { - headers: { - 'Content-Type': 'application/json', - 'X-Api-Key': config.videoAI.apiKey, + if (!prompt || !Array.isArray(context)) { + throw new Error('Invalid input: Prompt or context is missing or invalid.'); + } + + // Add the prompt to the context + context.push({ role: 'user', content: prompt }); + + // Call DeepSeek's API to generate response + const response = await axios.post( + `${config?.integrations?.deepSeek?.basePath}chat/completions`, + { + model: config?.integrations?.deepSeek?.model || 'deepseek-chat', + messages: context, + max_tokens: config?.integrations?.deepSeek?.max_tokens || 1024, + temperature: config?.integrations?.deepSeek?.temperature || 0.7, }, - }); + { + headers: { + Authorization: `Bearer ${config?.integrations?.deepSeek?.apiKey}`, + 'Content-Type': 'application/json', + }, + } + ); - const data = { response: response.data.data }; + // Extract the assistant's response + const message = response.data.choices[0]?.message?.content?.trim(); + if (!message) { + throw new Error('DeepSeek returned an empty response.'); + } - //log.debug('getAvatarList', data); + // Add response to context + context.push({ role: 'assistant', content: message }); - cb(data); + // Log the conversation details + log.debug('DeepSeek Response', { + time, + room, + name, + prompt, + response: message, + context, + }); + + // Send the response back to the client + cb({ message, context }); } catch (error) { - log.error('getAvatarList', error.response.data); - cb({ error: error.response?.status === 500 ? 'Internal server error' : error.response.data.message }); + // Handle API-specific errors + if (error.response) { + log.error('DeepSeek API Error', { + status: error.response.status, + data: error.response.data, + }); + return cb({ message: `DeepSeek API Error: ${error.response.data?.message || error.message}` }); + } + + // Handle general errors + log.error('DeepSeek Error', error); + cb({ message: `Error: ${error.message}` }); } }); - // https://docs.heygen.com/reference/list-voices-v2 - socket.on('getVoiceList', async ({}, cb) => { - if (!config.videoAI.enabled || !config.videoAI.apiKey) + // https://docs.liveavatar.com/reference/list_public_avatars_v1_avatars_public_get + // https://docs.liveavatar.com/reference/list_user_avatars_v1_avatars_get + socket.on('getAvatarList', async ({}, cb) => { + if (!config?.integrations?.videoAI?.enabled || !config?.integrations?.videoAI?.apiKey) return cb({ error: 'Video AI seems disabled, try later!' }); try { - const response = await axios.get(`${config.videoAI.basePath}/v2/voices`, { - headers: { - 'Content-Type': 'application/json', - 'X-Api-Key': config.videoAI.apiKey, - }, - }); + const headers = { + 'Content-Type': 'application/json', + 'X-API-KEY': config?.integrations?.videoAI?.apiKey, + }; - const data = { response: response.data.data }; + const [publicRes, privateRes] = await Promise.allSettled([ + axios.get(`${config?.integrations?.videoAI?.basePath}/v1/avatars/public?page_size=100`, { + headers, + }), + axios.get(`${config?.integrations?.videoAI?.basePath}/v1/avatars?page_size=100`, { headers }), + ]); - //log.debug('getVoiceList', data); + const publicAvatars = publicRes.status === 'fulfilled' ? publicRes.value.data?.data?.results || [] : []; + const privateAvatars = + privateRes.status === 'fulfilled' ? privateRes.value.data?.data?.results || [] : []; - cb(data); - } catch (error) { - log.error('getVoiceList', error.response.data); - cb({ error: error.response?.status === 500 ? 'Internal server error' : error.response.data.message }); - } - }); - - // https://docs.heygen.com/reference/new-session - socket.on('streamingNew', async ({ quality, avatar_id, voice_id }, cb) => { - if (!roomExists(socket)) return; - - if (!config.videoAI.enabled || !config.videoAI.apiKey) - return cb({ error: 'Video AI seems disabled, try later!' }); - try { - const voice = voice_id ? { voice_id: voice_id } : {}; - const response = await axios.post( - `${config.videoAI.basePath}/v1/streaming.new`, - { - quality, - avatar_id, - voice: voice, - }, - { - headers: { - accept: 'application/json', - 'content-type': 'application/json', - 'x-api-key': config.videoAI.apiKey, - }, - }, - ); + // Normalize LiveAvatar fields to match client expectations + const avatars = [...publicAvatars, ...privateAvatars].map((a) => ({ + avatar_id: a.id, + avatar_name: a.name, + preview_image_url: a.preview_url, + preview_video_url: null, + is_paid: false, + })); - const data = { response: response.data }; + const data = { response: { avatars } }; - log.debug('streamingNew', data); + //log.debug('getAvatarList', data); cb(data); } catch (error) { - log.error('streamingNew', error.response.data); - cb({ error: error.response?.status === 500 ? 'Internal server error' : error.response.data }); + log.error('getAvatarList', error.response?.data || error.message); + cb({ error: error.response?.status === 500 ? 'Internal server error' : error.message }); } }); - // https://docs.heygen.com/reference/start-session - socket.on('streamingStart', async ({ session_id, sdp }, cb) => { - if (!roomExists(socket)) return; - - if (!config.videoAI.enabled || !config.videoAI.apiKey) + // https://docs.liveavatar.com/reference/list_voices_v1_voices_get + socket.on('getVoiceList', async ({}, cb) => { + if (!config?.integrations?.videoAI?.enabled || !config?.integrations?.videoAI?.apiKey) return cb({ error: 'Video AI seems disabled, try later!' }); try { - const response = await axios.post( - `${config.videoAI.basePath}/v1/streaming.start`, - { session_id, sdp }, - { - headers: { - 'Content-Type': 'application/json', - 'X-Api-Key': config.videoAI.apiKey, - }, + const response = await axios.get(`${config?.integrations?.videoAI?.basePath}/v1/voices?page_size=100`, { + headers: { + 'Content-Type': 'application/json', + 'X-API-KEY': config?.integrations?.videoAI?.apiKey, }, - ); + }); - const data = { response: response.data.data }; + // Normalize LiveAvatar fields to match client expectations + const voices = (response.data?.data?.results || []).map((v) => ({ + voice_id: v.id, + name: v.name, + language: v.language, + gender: v.gender, + is_paid: false, + })); - log.debug('startSessionAi', data); + const data = { response: { voices } }; + + //log.debug('getVoiceList', data); cb(data); } catch (error) { - log.error('streamingStart', error.response.data); - cb({ error: error.response?.status === 500 ? 'Internal server error' : error.response.data.message }); + log.error('getVoiceList', error.response?.data || error.message); + cb({ error: error.response?.status === 500 ? 'Internal server error' : error.message }); } }); - // https://docs.heygen.com/reference/submit-ice-information - socket.on('streamingICE', async ({ session_id, candidate }, cb) => { + // https://docs.liveavatar.com/reference/create_session_token_v1_sessions_token_post + socket.on('createSessionToken', async ({ quality, avatar_id, voice_id }, cb) => { if (!roomExists(socket)) return; - if (!config.videoAI.enabled || !config.videoAI.apiKey) + if (!config?.integrations?.videoAI?.enabled || !config?.integrations?.videoAI?.apiKey) return cb({ error: 'Video AI seems disabled, try later!' }); - try { - const response = await axios.post( - `${config.videoAI.basePath}/v1/streaming.ice`, - { session_id, candidate }, - { - headers: { - 'Content-Type': 'application/json', - 'X-Api-Key': config.videoAI.apiKey, - }, - }, - ); - - const data = { response: response.data }; + const mode = config?.integrations?.videoAI?.mode || 'FULL'; + const contextId = config?.integrations?.videoAI?.contextId; - log.debug('streamingICE', data); - - cb(data); - } catch (error) { - log.error('streamingICE', error.response.data); - cb({ error: error.response?.status === 500 ? 'Internal server error' : error.response.data.message }); - } - }); + const avatarPersona = {}; + if (voice_id) avatarPersona.voice_id = voice_id; + if (contextId) avatarPersona.context_id = contextId; - // https://docs.heygen.com/reference/send-task - socket.on('streamingTask', async ({ session_id, text }, cb) => { - if (!roomExists(socket)) return; + const body = { + mode, + avatar_id, + video_settings: { quality: quality || 'high' }, + }; - if (!config.videoAI.enabled || !config.videoAI.apiKey) - return cb({ error: 'Video AI seems disabled, try later!' }); + if (mode === 'FULL') { + body.avatar_persona = avatarPersona; + } - try { const response = await axios.post( - `${config.videoAI.basePath}/v1/streaming.task`, - { - session_id, - text, - }, + `${config?.integrations?.videoAI?.basePath}/v1/sessions/token`, + body, { headers: { - 'Content-Type': 'application/json', - 'X-Api-Key': config.videoAI.apiKey, + accept: 'application/json', + 'content-type': 'application/json', + 'X-API-KEY': config?.integrations?.videoAI?.apiKey, }, - }, + } ); const data = { response: response.data }; - log.debug('streamingTask', data); + log.debug('createSessionToken', data); cb(data); } catch (error) { - log.error('streamingTask', error.response.data); - cb({ error: error.response?.status === 500 ? 'Internal server error' : error.response.data.message }); + log.error('createSessionToken', error.response?.data || error.message); + cb({ + error: + error.response?.status === 500 + ? 'Internal server error' + : error.response?.data || error.message, + }); } }); - // https://docs.heygen.com/reference/interrupt-task - socket.on('streamingInterrupt', async ({ session_id, text }, cb) => { + // https://docs.liveavatar.com/reference/start_session_v1_sessions_start_post + socket.on('startSession', async ({ session_token }, cb) => { if (!roomExists(socket)) return; - if (!config.videoAI.enabled || !config.videoAI.apiKey) + if (!config?.integrations?.videoAI?.enabled || !config?.integrations?.videoAI?.apiKey) return cb({ error: 'Video AI seems disabled, try later!' }); try { const response = await axios.post( - `${config.videoAI.basePath}/v1/streaming.interrupt`, - { - session_id, - }, + `${config?.integrations?.videoAI?.basePath}/v1/sessions/start`, + {}, { headers: { - 'Content-Type': 'application/json', - 'X-Api-Key': config.videoAI.apiKey, + accept: 'application/json', + Authorization: `Bearer ${session_token}`, }, - }, + } ); - const data = { response: response.data }; + const data = { response: response.data.data }; - log.debug('streamingInterrupt', data); + log.debug('startSession', data); cb(data); } catch (error) { - log.error('streamingInterrupt', error.response.data); - cb({ error: error.response?.status === 500 ? 'Internal server error' : error.response.data.message }); + log.error('startSession', error.response?.data || error.message); + cb({ + error: + error.response?.data?.message || + (error.response?.status === 500 ? 'Internal server error' : error.message), + }); } }); socket.on('talkToOpenAI', async ({ text, context }, cb) => { if (!roomExists(socket)) return; - if (!config.videoAI.enabled || !config.videoAI.apiKey) + if (!config?.integrations?.videoAI?.enabled || !config?.integrations?.videoAI?.apiKey) return cb({ error: 'Video AI seems disabled, try later!' }); + try { - const systemLimit = config.videoAI.systemLimit; + const systemLimit = config?.integrations?.videoAI?.systemLimit; const arr = { messages: [...context, { role: 'system', content: systemLimit }, { role: 'user', content: text }], model: 'gpt-3.5-turbo', @@ -2664,34 +3308,35 @@ function startServer() { } }); - // https://docs.heygen.com/reference/close-session - socket.on('streamingStop', async ({ session_id }, cb) => { + // https://docs.liveavatar.com/reference/stop_session_v1_sessions_stop_post + socket.on('stopSession', async ({ session_id }, cb) => { if (!roomExists(socket)) return; - if (!config.videoAI.enabled || !config.videoAI.apiKey) + if (!config?.integrations?.videoAI?.enabled || !config?.integrations?.videoAI?.apiKey) return cb({ error: 'Video AI seems disabled, try later!' }); + try { const response = await axios.post( - `${config.videoAI.basePath}/v1/streaming.stop`, + `${config?.integrations?.videoAI?.basePath}/v1/sessions/stop`, { session_id, }, { headers: { 'Content-Type': 'application/json', - 'X-Api-Key': config.videoAI.apiKey, + 'X-API-KEY': config?.integrations?.videoAI?.apiKey, }, - }, + } ); const data = { response: response.data }; - log.debug('streamingStop', data); + log.debug('stopSession', data); cb(data); } catch (error) { - log.error('streamingStop', error.response.data); - cb({ error: error.response?.status === 500 ? 'Internal server error' : error.response.data.message }); + log.error('stopSession', error.response?.data || error.message); + cb({ error: error.response?.status === 500 ? 'Internal server error' : error.message }); } }); @@ -2714,13 +3359,21 @@ function startServer() { } const data = checkXSS(dataObject); + + if (!Validator.isValidData(data)) return cb(false); + const { peer_name, peer_uuid, file } = data; const isPresenter = isPeerPresenter(socket.room_id, socket.id, peer_name, peer_uuid); if (!isPresenter) return cb(false); const room = getRoom(socket); - const host = config.ngrok.enabled ? 'localhost' : socket.handshake.headers.host.split(':')[0]; - const rtmp = await room.startRTMP(socket.id, room, host, 1935, `../${rtmpDir}/${file}`); + + const DEFAULT_HOST = 'localhost'; + const host = config?.ngrok?.enabled + ? DEFAULT_HOST + : socket?.handshake?.headers?.host?.split(':')[0] || DEFAULT_HOST; + + const rtmp = await room.startRTMP(socket.id, room, host, 1935, `${rtmpDir}/${file}`); if (rtmp !== false) rtmpFileStreamsCount++; @@ -2758,12 +3411,20 @@ function startServer() { } const data = checkXSS(dataObject); + + if (!Validator.isValidData(data)) return cb(false); + const { peer_name, peer_uuid, inputVideoURL } = data; const isPresenter = isPeerPresenter(socket.room_id, socket.id, peer_name, peer_uuid); if (!isPresenter) return cb(false); const room = getRoom(socket); - const host = config.ngrok.enabled ? 'localhost' : socket.handshake.headers.host.split(':')[0]; + + const DEFAULT_HOST = 'localhost'; + const host = config?.integrations?.ngrok?.enabled + ? DEFAULT_HOST + : socket?.handshake?.headers?.host?.split(':')[0] || DEFAULT_HOST; + const rtmp = await room.startRTMPfromURL(socket.id, room, host, 1935, inputVideoURL); if (rtmp !== false) rtmpUrlStreamsCount++; @@ -2798,6 +3459,8 @@ function startServer() { const data = checkXSS(dataObject); + if (!Validator.isValidData(data)) return; + const { question, options } = data; const room = getRoom(socket); @@ -2820,6 +3483,8 @@ function startServer() { const data = checkXSS(dataObject); + if (!Validator.isValidData(data)) return; + const { room, peer } = getRoomAndPeer(socket); const { peer_name } = peer || socket.id; @@ -2852,6 +3517,8 @@ function startServer() { const data = checkXSS(dataObject); + if (!Validator.isValidData(data)) return; + const { index, question, options } = data; const room = getRoom(socket); @@ -2923,7 +3590,11 @@ function startServer() { }); socket.on('disconnect', (reason) => { - if (!roomExists(socket)) return; + if (!roomExists(socket)) { + // Clean up socket listeners even if room doesn't exist + socket.removeAllListeners(); + return; + } const { room, peer } = getRoomAndPeer(socket); @@ -2933,8 +3604,29 @@ function startServer() { log.debug('[Disconnect] - peer name', { peer_name, reason }); + if (webhook.enabled) { + const data = { + timestamp: log.getDateTime(false), + room_id: socket.room_id, + peer: peer?.peer_info, + reason: reason, + }; + // Trigger a POST request when a user disconnects + axios + .post(webhook.url, { event: 'disconnect', data }, { timeout: 5000 }) + .then((response) => log.debug('Disconnect event tracked:', response.data)) + .catch((error) => log.error('Error tracking disconnect event:', error.message)); + } + room.removePeer(socket.id); + room.broadCast(socket.id, 'removeMe', removeMeData(room, peer_name, isPresenter)); + + // Clean up this peer's presenter entry immediately + if (socket.room_id in presenters && socket.id in presenters[socket.room_id]) { + delete presenters[socket.room_id][socket.id]; + } + if (room.getPeersCount() === 0) { // stopRTMPActiveStreams(isPresenter, room); @@ -2943,37 +3635,23 @@ function startServer() { delete presenters[socket.room_id]; - log.info('[Disconnect] - Last peer - current presenters grouped by roomId', presenters); + log.debug('[Disconnect] - Last peer - current presenters grouped by roomId', presenters); const activeRooms = getActiveRooms(); - log.info('[Disconnect] - Last peer - current active rooms', activeRooms); + log.debug('[Disconnect] - Last peer - current active rooms', activeRooms); const activeStreams = getRTMPActiveStreams(); - log.info('[Disconnect] - Last peer - current active RTMP streams', activeStreams); + log.debug('[Disconnect] - Last peer - current active RTMP streams', activeStreams); } - room.broadCast(socket.id, 'removeMe', removeMeData(room, peer_name, isPresenter)); - - if (isPresenter) removeIP(socket); - - if (webhook.enabled) { - const data = { - timestamp: log.getDateTime(false), - room_id: socket.room_id, - peer_name: peer_name, - presenter: isPresenter, - reason: reason, - }; - // Trigger a POST request when a user disconnects - axios - .post(webhook.url, { event: 'disconnect', data }) - .then((response) => log.debug('Disconnect event tracked:', response.data)) - .catch((error) => log.error('Error tracking disconnect event:', error.message)); - } + removeIP(socket); socket.room_id = null; + + // Clean up all socket event listeners to prevent memory leaks + socket.removeAllListeners(); }); socket.on('exitRoom', (_, callback) => { @@ -2991,10 +3669,28 @@ function startServer() { log.debug('Exit room', peer_name); + if (webhook.enabled) { + const data = { + timestamp: log.getDateTime(false), + room_id: socket.room_id, + peer: peer?.peer_info, + }; + // Trigger a POST request when a user exits + axios + .post(webhook.url, { event: 'exit', data }, { timeout: 5000 }) + .then((response) => log.debug('ExitRoom event tracked:', response.data)) + .catch((error) => log.error('Error tracking exitRoom event:', error.message)); + } + room.removePeer(socket.id); room.broadCast(socket.id, 'removeMe', removeMeData(room, peer_name, isPresenter)); + // Clean up this peer's presenter entry immediately + if (socket.room_id in presenters && socket.id in presenters[socket.room_id]) { + delete presenters[socket.room_id][socket.id]; + } + if (room.getPeersCount() === 0) { // stopRTMPActiveStreams(isPresenter, room); @@ -3003,32 +3699,18 @@ function startServer() { delete presenters[socket.room_id]; - log.info('[REMOVE ME] - Last peer - current presenters grouped by roomId', presenters); + log.debug('[REMOVE ME] - Last peer - current presenters grouped by roomId', presenters); const activeRooms = getActiveRooms(); - log.info('[REMOVE ME] - Last peer - current active rooms', activeRooms); + log.debug('[REMOVE ME] - Last peer - current active rooms', activeRooms); const activeStreams = getRTMPActiveStreams(); - log.info('[REMOVE ME] - Last peer - current active RTMP streams', activeStreams); + log.debug('[REMOVE ME] - Last peer - current active RTMP streams', activeStreams); } - if (isPresenter) removeIP(socket); - - if (webhook.enabled) { - const data = { - timestamp: log.getDateTime(false), - room_id: socket.room_id, - peer_name: peer_name, - presenter: isPresenter, - }; - // Trigger a POST request when a user exits - axios - .post(webhook.url, { event: 'exit', data }) - .then((response) => log.debug('ExitROom event tracked:', response.data)) - .catch((error) => log.error('Error tracking exitRoom event:', error.message)); - } + removeIP(socket); socket.room_id = null; @@ -3037,6 +3719,23 @@ function startServer() { // Helpers + async function handleJoinWebHook(room_id, peer_info) { + // handle WebHook + if (webhook.enabled) { + // Trigger a POST request when a user joins + const data = { + timestamp: log.getDateTime(false), + room_id, + peer_info, + }; + + axios + .post(webhook.url, { event: 'join', data }, { timeout: 5000 }) + .then((response) => log.debug('Join event tracked:', response.data)) + .catch((error) => log.error('Error tracking join event:', error.message)); + } + } + function getRoomAndPeer(socket) { const room = getRoom(socket); @@ -3059,6 +3758,28 @@ function startServer() { return roomList.has(socket.room_id); } + function getPeerInfo(peer) { + if (!peer || !peer.peer_info) { + return { + peer_name: peer?.peer_name || 'Unknown', + isDesktop: false, + os: 'Unknown', + browser: 'Unknown', + }; + } + + const { peer_name, peer_info } = peer; + const { is_desktop_device, os_name, os_version, browser_name, browser_version } = peer_info; + + return { + peer_name: peer_name || 'Unknown', + isDesktop: Boolean(is_desktop_device), + os: os_name && os_version ? `${os_name} ${os_version}` : os_name || 'Unknown', + browser: + browser_name && browser_version ? `${browser_name} ${browser_version}` : browser_name || 'Unknown', + }; + } + function isValidFileName(fileName) { const invalidChars = /[\\\/\?\*\|:"<>]/; return !invalidChars.test(fileName); @@ -3083,7 +3804,7 @@ function startServer() { peer_counts: peerCounts, isPresenter: isPresenter, }; - log.debug('[REMOVE ME DATA]', data); + log.debug('Peer removed from the room', data); return data; } }); @@ -3117,12 +3838,12 @@ function startServer() { if (room.isRtmpFileStreamerActive()) { room.stopRTMP(); rtmpFileStreamsCount--; - log.info('[REMOVE ME] - Stop RTMP Stream From FIle', rtmpFileStreamsCount); + log.debug('[REMOVE ME] - Stop RTMP Stream From FIle', rtmpFileStreamsCount); } if (room.isRtmpUrlStreamerActive()) { room.stopRTMPfromURL(); rtmpUrlStreamsCount--; - log.info('[REMOVE ME] - Stop RTMP Stream From URL', rtmpUrlStreamsCount); + log.debug('[REMOVE ME] - Stop RTMP Stream From URL', rtmpUrlStreamsCount); } } } @@ -3143,15 +3864,11 @@ function startServer() { function isPeerPresenter(room_id, peer_id, peer_name, peer_uuid) { try { - if ( - config.presenters && - config.presenters.join_first && - (!presenters[room_id] || !presenters[room_id][peer_id]) - ) { + if (hostCfg?.presenters?.join_first && (!presenters[room_id] || !presenters[room_id][peer_id])) { // Presenter not in the presenters config list, disconnected, or peer_id changed... for (const [existingPeerID, presenter] of Object.entries(presenters[room_id] || {})) { if (presenter.peer_name === peer_name) { - log.info('Presenter found', { + log.debug('Presenter found', { room: room_id, peer_id: existingPeerID, peer_name: peer_name, @@ -3163,15 +3880,22 @@ function startServer() { } const isPresenter = - (config.presenters && - config.presenters.join_first && - typeof presenters[room_id] === 'object' && - Object.keys(presenters[room_id][peer_id]).length > 1 && - presenters[room_id][peer_id]['peer_name'] === peer_name && - presenters[room_id][peer_id]['peer_uuid'] === peer_uuid) || - (config.presenters && config.presenters.list && config.presenters.list.includes(peer_name)); - - log.debug('isPeerPresenter', { + // 1. Check if join_first mode is enabled and peer matches presenter criteria: + // - Presenters list contains the peer's room_id and peer_id + // - Peer's name and UUID match the stored values + // - Presenter object has additional properties (length > 1) + (hostCfg?.presenters?.join_first && + presenters[room_id]?.[peer_id]?.peer_name === peer_name && + presenters[room_id]?.[peer_id]?.peer_uuid === peer_uuid && + Object.keys(presenters[room_id]?.[peer_id] || {}).length > 1) || + // 2. Check if peer_name exists in the static presenters list configuration + hostCfg?.presenters?.list?.includes(peer_name) || + // 3. Check if peer is explicitly marked as presenter (e.g., from token) + presenters[room_id]?.[peer_id]?.is_presenter || + // 4. Default case (not a presenter) + false; + + log.debug('isPeerPresenter Check', { room_id: room_id, peer_id: peer_id, peer_name: peer_name, @@ -3181,7 +3905,7 @@ function startServer() { return isPresenter; } catch (err) { - log.error('isPeerPresenter', err); + log.error('isPeerPresenter Check error', err); return false; } } @@ -3200,7 +3924,7 @@ function startServer() { }, { timeout: 5000, // Timeout set to 5 seconds (5000 milliseconds) - }, + } ); return response.data && response.data.message === true; } catch (error) { @@ -3290,26 +4014,29 @@ function startServer() { const hostUserAuthenticated = hostCfg.protected && hostCfg.authenticated; const roomExist = roomList.has(roomId); const roomCount = roomList.size; + const OIDCAllowRoomCreationForAuthUsers = OIDC.allow_rooms_creation_for_auth_users; const allowRoomAccess = - (!hostCfg.protected && !OIDC.enabled) || // No host protection and OIDC mode enabled (default) - (OIDCUserAuthenticated && roomExist) || // User authenticated via OIDC and room Exist - (hostUserAuthenticated && roomExist) || // User authenticated via Login and room Exist - ((OIDCUserAuthenticated || hostUserAuthenticated) && roomCount === 0) || // User authenticated joins the first room - roomExist; // User Or Guest join an existing Room + (!hostCfg.protected && !OIDC.enabled) || // Default open access + (OIDCUserAuthenticated && roomExist) || // OIDC auth & room exists + (hostUserAuthenticated && roomExist) || // Host login auth & room exists + ((OIDCUserAuthenticated || hostUserAuthenticated) && roomCount === 0) || // First room creation + (OIDCUserAuthenticated && OIDCAllowRoomCreationForAuthUsers) || // Allow room creation if authenticated via OIDC + roomExist; // Fallback: allow anyone if room exists log.debug(logMessage, { - OIDCUserAuthenticated: OIDCUserAuthenticated, - hostUserAuthenticated: hostUserAuthenticated, - roomExist: roomExist, - roomCount: roomCount, + OIDCUserAuthenticated, + hostUserAuthenticated, + roomExist, + roomCount, extraInfo: { - roomId: roomId, + roomId, OIDCUserEnabled: OIDC.enabled, hostProtected: hostCfg.protected, hostAuthenticated: hostCfg.authenticated, + OIDCAllowRoomCreationForAuthUsers, }, - allowRoomAccess: allowRoomAccess, + allowRoomAccess, }); return allowRoomAccess; @@ -3328,7 +4055,7 @@ function startServer() { }, { timeout: 5000, // Timeout set to 5 seconds (5000 milliseconds) - }, + } ); log.debug('AXIOS roomExistsForUser', { room: room, exists: true }); return response.data && response.data.message === true; @@ -3355,7 +4082,7 @@ function startServer() { }, { timeout: 5000, // Timeout set to 5 seconds (5000 milliseconds) - }, + } ); const allowedRooms = response.data ? response.data.message : {}; log.debug('AXIOS getUserAllowedRooms', allowedRooms); @@ -3368,7 +4095,7 @@ function startServer() { // Get allowed rooms for user from config.js file if (hostCfg.protected && !hostCfg.users_from_db) { - const isOIDCEnabled = config.oidc && config.oidc.enabled; + const isOIDCEnabled = config?.security?.oidc?.enabled; const user = hostCfg.users.find((user) => user.displayname === username || user.username === username); @@ -3376,79 +4103,97 @@ function startServer() { log.debug('getUserAllowedRooms - user not found', username); return false; } + log.debug('CONFIG getUserAllowedRooms', user.allowed_rooms); return user.allowed_rooms; } + log.debug('getUserAllowedRooms *'); return ['*']; } async function isRoomAllowedForUser(message, username, room) { - const logData = { message, username, room }; + if (!username || !room) { + log.debug('isRoomAllowedForUser - missing username or room', { username, room }); + return false; + } + const logData = { message, username, room }; log.debug('isRoomAllowedForUser ------>', logData); - if (!username || !room) return false; + try { + const isOIDCEnabled = config?.security?.oidc?.enabled; - const isOIDCEnabled = config.oidc && config.oidc.enabled; + if (hostCfg.protected || hostCfg.user_auth) { + // Check API first if configured + if (hostCfg.users_from_db && hostCfg.users_api_room_allowed) { + try { + const response = await axios.post( + hostCfg.users_api_room_allowed, + { + email: username, + username: username, + room: room, + api_secret_key: hostCfg.users_api_secret_key, + }, + { + timeout: hostCfg.users_api_timeout || 5000, + } + ); + + if (response.data && (response.data === true || response.data.message === true)) { + log.debug('AXIOS isRoomAllowedForUser - allowed access', { room, username }); + return true; + } + log.debug('AXIOS isRoomAllowedForUser - denied access', { room, username }); + return false; + } catch (error) { + log.error('AXIOS isRoomAllowedForUser - check failed', error.message); + // Fail closed (deny access) if API check fails + return false; + } + } - if (hostCfg.protected || hostCfg.user_auth) { - // Check if allowed room for user from DB... - if (hostCfg.users_from_db && hostCfg.users_api_room_allowed) { - try { - // Using either email or username, as the username can also be an email here. - const response = await axios.post( - hostCfg.users_api_room_allowed, - { - email: username, - username: username, - room: room, - api_secret_key: hostCfg.users_api_secret_key, - }, - { - timeout: 5000, // Timeout set to 5 seconds (5000 milliseconds) - }, - ); - log.debug('AXIOS isRoomAllowedForUser', { room: room, allowed: true }); - return response.data && response.data.message === true; - } catch (error) { - log.error('AXIOS isRoomAllowedForUser error', error.message); - return false; + // Check presenter list + if (hostCfg?.presenters?.list?.includes(username)) { + log.debug('isRoomAllowedForUser - User in presenters list', { username }); + return true; } - } - const isInPresenterLists = config.presenters.list.includes(username); + // Find user in configuration + const user = hostCfg.users?.find((u) => u.displayname === username || u.username === username); - if (isInPresenterLists) { - log.debug('isRoomAllowedForUser - user in presenters list room allowed', room); - return true; - } + // For OIDC, we might want additional checks even when enabled + if (isOIDCEnabled) { + log.debug('isRoomAllowedForUser - OIDC enabled, allowing access', { username }); + return true; + } - const user = hostCfg.users.find((user) => user.displayname === username || user.username === username); + if (!user) { + log.debug('isRoomAllowedForUser - User not found in configuration', { username }); + return false; + } - if (!isOIDCEnabled && !user) { - log.debug('isRoomAllowedForUser - user not found', username); - return false; - } + // Check allowed rooms + const isAllowed = + !user.allowed_rooms || user.allowed_rooms.includes('*') || user.allowed_rooms.includes(room); - if ( - isOIDCEnabled || - !user.allowed_rooms || - (user.allowed_rooms && (user.allowed_rooms.includes('*') || user.allowed_rooms.includes(room))) - ) { - log.debug('isRoomAllowedForUser - user room allowed', room); - return true; + log.debug( + isAllowed ? 'isRoomAllowedForUser - Room allowed' : 'isRoomAllowedForUser - Room not allowed', + { room, username } + ); + return isAllowed; } - log.debug('isRoomAllowedForUser - user room not allowed', room); - return false; + log.debug('isRoomAllowedForUser - No protection enabled, allowing access', { room, username }); + return true; + } catch (error) { + log.error('isRoomAllowedForUser - Unexpected error', error); + return false; // Fail closed } - - log.debug('isRoomAllowedForUser - No host protected or user_auth enabled, user room allowed', room); - return true; } async function getPeerGeoLocation(ip) { - const endpoint = config.IPLookup.getEndpoint(ip); + const endpoint = config?.integrations?.IPLookup?.getEndpoint(ip); log.debug('Get peer geo', { ip: ip, endpoint: endpoint }); return axios .get(endpoint) @@ -3457,21 +4202,29 @@ function startServer() { } function getIP(req) { - return req.headers['x-forwarded-for'] || req.headers['X-Forwarded-For'] || req.socket.remoteAddress || req.ip; + const forwarded = req.headers['x-forwarded-for'] || req.headers['X-Forwarded-For']; + if (forwarded) { + return forwarded.split(',')[0].trim(); + } + return req.socket.remoteAddress || req.ip; } function getIpSocket(socket) { - return ( - socket.handshake.headers['x-forwarded-for'] || - socket.handshake.headers['X-Forwarded-For'] || - socket.handshake.address - ); + const forwarded = socket.handshake.headers['x-forwarded-for'] || socket.handshake.headers['X-Forwarded-For']; + if (forwarded) { + return forwarded.split(',')[0].trim(); + } + return socket.handshake.address; + } + + function updateHostAuthenticatedFlag() { + hostCfg.authenticated = !hostCfg.protected || authHost.getAuthorizedIPs().length > 0; } function allowedIP(ip) { const authorizedIPs = authHost.getAuthorizedIPs(); const authorizedIP = authHost.isAuthorizedIP(ip); - log.info('Allowed IPs', { + log.debug('Allowed IPs', { ip: ip, authorizedIP: authorizedIP, authorizedIPs: authorizedIPs, @@ -3485,11 +4238,144 @@ function startServer() { if (ip && allowedIP(ip)) { authHost.deleteIP(ip); hostCfg.authenticated = false; - log.info('Remove IP from auth', { - ip: ip, + log.debug('Remove IP from auth', { + removedIp: ip, authorizedIps: authHost.getAuthorizedIPs(), }); } } } } + +// #################################################### +// GRACEFUL SHUTDOWN HANDLERS +// #################################################### + +let isShuttingDown = false; + +async function gracefulShutdown(signal) { + if (isShuttingDown) { + log.warn(`${signal} received again, forcing exit...`); + process.exit(1); + } + + isShuttingDown = true; + log.info(`${signal} received, starting graceful shutdown...`); + + try { + // 1. Stop accepting new connections + log.debug('Closing HTTP server...'); + server.close(() => { + log.info('HTTP server closed'); + }); + + // 2. Close all active rooms and notify peers + log.debug(`Closing ${roomList.size} active rooms...`); + for (const [roomId, room] of roomList.entries()) { + try { + // Notify all peers in the room + room.sendToAll('serverShutdown', { message: 'Server is shutting down' }); + + // Stop any active RTMP streams + if (room.isRtmpFileStreamerActive()) { + await room.stopRTMP(); + } + if (room.isRtmpUrlStreamerActive()) { + await room.stopRTMPfromURL(); + } + + // Remove all peers from the room + const peers = room.getPeers(); + for (const [peerId] of peers) { + room.removePeer(peerId); + } + + roomList.delete(roomId); + } catch (err) { + log.error(`Error closing room ${roomId}:`, err.message); + } + } + + // 3. Close all RTMP streams + log.debug(`Closing ${Object.keys(streams).length} RTMP streams...`); + for (const [key, stream] of Object.entries(streams)) { + try { + if (stream && typeof stream.end === 'function') { + stream.end(); + } + delete streams[key]; + } catch (err) { + log.error(`Error closing RTMP stream ${key}:`, err.message); + } + } + + // 4. Disconnect all Socket.IO clients + log.debug('Disconnecting all Socket.IO clients...'); + const sockets = await io.fetchSockets(); + for (const socket of sockets) { + socket.disconnect(true); + } + + // 5. Close Socket.IO server + log.debug('Closing Socket.IO server...'); + io.close(); + + // 6. Close all mediasoup workers + log.debug(`Closing ${workers.length} mediasoup workers...`); + for (const worker of workers) { + try { + worker.close(); + } catch (err) { + log.error('Error closing mediasoup worker:', err.message); + } + } + + // 7. Cleanup HTML injector + log.debug('Cleaning up HTML injector...'); + htmlInjector.cleanup(); + + // 8. Close ngrok if active + if (config?.integrations?.ngrok?.enabled) { + log.debug('Closing ngrok tunnel...'); + await ngrok.kill(); + } + + log.info('Graceful shutdown completed successfully'); + process.exit(0); + } catch (err) { + log.error('Error during graceful shutdown:', err.message); + process.exit(1); + } +} + +// Set a timeout for forced shutdown if graceful shutdown takes too long +function forceShutdown(signal) { + setTimeout(() => { + log.error(`Graceful shutdown timeout exceeded, forcing exit...`); + process.exit(1); + }, 30000); // 30 seconds timeout +} + +process.on('SIGINT', () => { + log.debug('PROCESS', 'SIGINT'); + forceShutdown('SIGINT'); + gracefulShutdown('SIGINT'); +}); + +process.on('SIGTERM', () => { + log.debug('PROCESS', 'SIGTERM'); + forceShutdown('SIGTERM'); + gracefulShutdown('SIGTERM'); +}); + +// Handle uncaught exceptions and rejections +process.on('uncaughtException', (err) => { + log.error('Uncaught Exception:', err); + forceShutdown('uncaughtException'); + gracefulShutdown('uncaughtException'); +}); + +process.on('unhandledRejection', (reason, promise) => { + log.error('Unhandled Rejection at:', promise, 'reason:', reason); + // Don't exit on unhandled rejection, just log it +}); diff --git a/app/src/ServerApi.js b/app/src/ServerApi.js index 18e74c4d6..487e527e5 100644 --- a/app/src/ServerApi.js +++ b/app/src/ServerApi.js @@ -6,8 +6,8 @@ const CryptoJS = require('crypto-js'); const config = require('./config'); const { v4: uuidV4 } = require('uuid'); -const JWT_KEY = (config.jwt && config.jwt.key) || 'mirotalksfu_jwt_secret'; -const JWT_EXP = (config.jwt && config.jwt.exp) || '1h'; +const JWT_KEY = config.security?.jwt?.key || 'mirotalksfu_jwt_secret'; +const JWT_EXP = config.security?.jwt?.exp || '1h'; module.exports = class ServerApi { constructor(host = null, authorization = null) { @@ -27,6 +27,14 @@ module.exports = class ServerApi { return { timestamp, totalRooms, totalUsers }; } + getActiveRooms(roomList) { + return Array.from(roomList.entries()).map(([roomId, room]) => ({ + id: roomId, + peers: room.peers.size, + join: 'https://' + this._host + '/' + roomId, + })); + } + getMeetings(roomList) { const meetings = Array.from(roomList.entries()).map(([id, room]) => { const peers = Array.from(room.peers.values()).map( @@ -52,7 +60,7 @@ module.exports = class ServerApi { hand: peer_hand, os: os_name ? `${os_name} ${os_version}` : '', browser: browser_name ? `${browser_name} ${browser_version}` : '', - }), + }) ); return { roomId: id, @@ -62,20 +70,49 @@ module.exports = class ServerApi { return meetings; } + endMeeting(roomList, room, redirect = '') { + if (!roomList.has(room)) { + return { success: false, error: 'Room not found' }; + } + + const roomObj = roomList.get(room); + + // Notify all peers to exit (clients handle 'ejectAll' by redirecting) + roomObj.sendToAll('cmd', { + type: 'ejectAll', + peer_name: 'API', + broadcast: true, + redirect: redirect || '', + }); + + // Remove all peers and close transports + const peers = roomObj.getPeers(); + for (const [peerId] of peers) { + roomObj.removePeer(peerId); + } + + // Delete room from the active list + roomList.delete(room); + + return { success: true, message: 'Meeting ended', room: room }; + } + getMeetingURL() { return 'https://' + this._host + '/join/' + uuidV4(); } getJoinURL(data) { // Get data - const { room, roomPassword, name, audio, video, screen, hide, notify, duration, token } = data; + const { room, roomPassword, name, avatar, audio, video, screen, chat, hide, notify, duration, token } = data; const roomValue = room || uuidV4(); - const nameValue = name || 'User-' + this.getRandomNumber(); const roomPasswordValue = roomPassword || false; + const nameValue = name || 'User-' + this.getRandomNumber(); + const avatarValue = avatar || false; const audioValue = audio || false; const videoValue = video || false; const screenValue = screen || false; + const chatValue = chat || false; const hideValue = hide || false; const notifyValue = notify || false; const durationValue = duration || 'unlimited'; @@ -88,9 +125,11 @@ module.exports = class ServerApi { `room=${roomValue}` + `&roomPassword=${roomPasswordValue}` + `&name=${encodeURIComponent(nameValue)}` + + `&avatar=${encodeURIComponent(avatarValue)}` + `&audio=${audioValue}` + `&video=${videoValue}` + `&screen=${screenValue}` + + `&chat=${chatValue}` + `&hide=${hideValue}` + `¬ify=${notifyValue}` + `&duration=${durationValue}` + diff --git a/app/src/Validator.js b/app/src/Validator.js index 075a07ebf..b97624870 100644 --- a/app/src/Validator.js +++ b/app/src/Validator.js @@ -9,14 +9,19 @@ function isValidRoomName(input) { return false; } const room = checkXSS(input); - return !room ? false : !hasPathTraversal(room); + + if (!room || ['false', 'undefined', '', null, undefined, 'favicon.ico'].includes(room.trim().toLowerCase())) { + return false; + } + + return !hasPathTraversal(room); } function isValidRecFileNameFormat(input) { if (!input || typeof input !== 'string') { return false; } - const validPattern = /^Rec_[a-zA-Z0-9_-]+\.webm$/; + const validPattern = /^Rec_[a-zA-Z0-9_.-]+\.webm$/; if (!validPattern.test(input)) { return false; } @@ -51,8 +56,22 @@ function hasPathTraversal(input) { return false; } +function isValidEmail(email) { + const emailRegex = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i; + return emailRegex.test(email); +} + +function isValidData(data) { + if (!data || typeof data !== 'object') { + return false; + } + return Object.keys(data).length > 0; +} + module.exports = { isValidRoomName, isValidRecFileNameFormat, hasPathTraversal, + isValidEmail, + isValidData, }; diff --git a/app/src/XSS.js b/app/src/XSS.js index db69e7abe..8bbf92cbe 100644 --- a/app/src/XSS.js +++ b/app/src/XSS.js @@ -14,7 +14,7 @@ const log = new Logger('Xss'); // Configure DOMPurify purify.setConfig({ ALLOWED_TAGS: ['a', 'img', 'div', 'span', 'svg', 'g', 'p'], // Allow specific tags - ALLOWED_ATTR: ['href', 'src', 'title', 'id', 'class', 'target'], // Allow specific attributes + ALLOWED_ATTR: ['href', 'src', 'title', 'id', 'class', 'target', 'width', 'height'], // Allow specific attributes ALLOWED_URI_REGEXP: /^(?!data:|javascript:|vbscript:|file:|view-source:).*/, // Disallow dangerous URIs }); diff --git a/app/src/config.template.js b/app/src/config.template.js index ca2d7f19c..46173b12e 100644 --- a/app/src/config.template.js +++ b/app/src/config.template.js @@ -1,747 +1,1836 @@ 'use strict'; -const packageJson = require('../../package.json'); +/** + * ============================================== + * MiroTalk SFU v2.1.51 - Configuration File + * ============================================== + * + * This file contains all configurable settings for the MiroTalk SFU application. + * Environment variables can override most settings (see each section for details). + * + * Structure: + * 1. Core System Configuration + * 2. Server Settings + * 3. Media Handling + * 4. Security & Authentication + * 5. API Configuration + * 6. Third-Party Integrations + * 7. UI/UX Customization + * 8. Feature Flags + * 9. Mediasoup (WebRTC) Settings + */ +const dotenv = require('dotenv').config(); +const packageJson = require('../../package.json'); const os = require('os'); +const fs = require('fs'); +const splitChar = ','; -// ############################# -// HELPERS -// ############################# +// ============================================== +// 1. Environment Detection & System Configuration +// ============================================== -const platform = os.platform(); +const PLATFORM = os.platform(); +const IS_DOCKER = fs.existsSync('/.dockerenv'); +const ENVIRONMENT = process.env.NODE_ENV || 'development'; +const ANNOUNCED_IP = process.env.SFU_ANNOUNCED_IP || ''; +const LISTEN_IP = process.env.SFU_LISTEN_IP || '0.0.0.0'; +const IPv4 = getIPv4(); -function getFFmpegPath(platform) { - switch (platform) { - case 'darwin': - return '/usr/local/bin/ffmpeg'; // macOS - case 'linux': - return '/usr/bin/ffmpeg'; // Linux - case 'win32': - return 'C:\\ffmpeg\\bin\\ffmpeg.exe'; // Windows - default: - return '/usr/bin/ffmpeg'; // Centos or others... - } -} +// ============================================== +// 2. WebRTC Port Configuration +// ============================================== -// https://api.ipify.org +const RTC_MIN_PORT = parseInt(process.env.SFU_MIN_PORT) || 40000; +const RTC_MAX_PORT = parseInt(process.env.SFU_MAX_PORT) || 40100; +const NUM_CPUS = os.cpus().length; +const NUM_WORKERS = Math.min(process.env.SFU_NUM_WORKERS || NUM_CPUS, NUM_CPUS); -function getIPv4() { - const ifaces = os.networkInterfaces(); - for (const interfaceName in ifaces) { - const iface = ifaces[interfaceName]; - for (const { address, family, internal } of iface) { - if (family === 'IPv4' && !internal) { - return address; - } - } - } - return '0.0.0.0'; // Default to 0.0.0.0 if no external IPv4 address found -} +// ============================================== +// 3. FFmpeg Path Configuration +// ============================================== + +const RTMP_FFMPEG_PATH = process.env.RTMP_FFMPEG_PATH || getFFmpegPath(PLATFORM); -/* - IPv4 Configuration Guide: - 1. Localhost Setup: - - For local development with Docker, replace `getIPv4()` with '127.0.0.1'. - 2. Production Setup: - - Replace `getIPv4()` with the 'Public Static IPv4 Address' of the server hosting this application. - - For AWS EC2 instances, replace `getIPv4()` with the 'Elastic IP' associated with the instance. - This ensures the public IP remains consistent across instance reboots. - Note: Always enclose the IP address in single quotes ''. -*/ -const IPv4 = getIPv4(); // Replace with the appropriate IPv4 address for your environment. - -/* - Set the port range for WebRTC communication. This range is used for the dynamic allocation of UDP ports for media streams. - - Each participant requires 2 ports: one for audio and one for video. - - The default configuration supports up to 50 participants (50 * 2 ports = 100 ports). - - To support more participants, simply increase the port range. - Note: - - When running in Docker, use 'network mode: host' for improved performance. - - Alternatively, enable 'webRtcServerActive: true' mode for better scalability. -*/ -const rtcMinPort = 40000; -const rtcMaxPort = 40100; - -const numWorkers = require('os').cpus().length; - -const ffmpegPath = getFFmpegPath(platform); +// ============================================== +// Main Configuration Export +// ============================================== module.exports = { - console: { - /* - timeZone: Time Zone corresponding to timezone identifiers from the IANA Time Zone Database es 'Europe/Rome' default UTC - */ - timeZone: 'UTC', - debug: true, - colors: true, + // ============================================== + // 1. Core System Configuration + // ============================================== + + system: { + /** + * System Information + * ------------------ + * - Hardware/OS details collected automatically + * - Used for diagnostics and optimization + */ + info: { + environment: ENVIRONMENT, + os: { + type: os.type(), + release: os.release(), + arch: os.arch(), + }, + cpu: { + cores: NUM_CPUS, + model: os.cpus()[0].model, + }, + memory: { + total: (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB', + }, + isDocker: IS_DOCKER, + }, + + /** + * Console Configuration + * --------------------- + * - timeZone: IANA timezone (e.g., 'Europe/Rome') + * - debug: Enable debug logging in non-production + * - colors: Colorized console output + * - json: Log output in JSON format + * - json_pretty: Pretty-print JSON logs + */ + console: { + timeZone: 'UTC', + debug: ENVIRONMENT !== 'production', + json: process.env.LOGS_JSON === 'true', + json_pretty: process.env.LOGS_JSON_PRETTY === 'true', + colors: process.env.LOGS_JSON === 'true' ? false : true, + }, + + /** + * External Services Configuration + * ------------------------------- + * - ip: Services to detect public IP address + */ + services: { + ip: ['http://api.ipify.org', 'http://ipinfo.io/ip', 'http://ifconfig.me/ip'], + }, }, + + // ============================================== + // 2. Server Configuration + // ============================================== + server: { - hostUrl: '', // default to http://localhost:port + /** + * Host Configuration + * ------------------ + * - hostUrl: Public URL (e.g., 'https://yourdomain.com') + * - listen: IP and port to bind to + */ + hostUrl: process.env.SERVER_HOST_URL || 'https://localhost:3010', listen: { - // app listen on - ip: '0.0.0.0', - port: process.env.PORT || 3010, + ip: process.env.SERVER_LISTEN_IP || '0.0.0.0', + port: process.env.SERVER_LISTEN_PORT || 3010, }, - trustProxy: false, // Enables trust for proxy headers (e.g., X-Forwarded-For) based on the trustProxy setting + + /** + * Security Settings + * ----------------- + * - trustProxy: Trust X-Forwarded-* headers + * - ssl: SSL certificate paths + * - cors: Cross-origin resource sharing + */ + trustProxy: process.env.TRUST_PROXY === 'true', ssl: { - // ssl/README.md - cert: '../ssl/cert.pem', - key: '../ssl/key.pem', + cert: process.env.SERVER_SSL_CERT || '../ssl/cert.pem', + key: process.env.SERVER_SSL_KEY || '../ssl/key.pem', }, cors: { - /* - origin: Allow specified origin es ['https://example.com', 'https://subdomain.example.com', 'http://localhost:3010'] or all origins if not specified - methods: Allow only GET and POST methods - */ - origin: '*', + origin: process.env.CORS_ORIGIN || '*', methods: ['GET', 'POST'], }, + }, + + // ============================================== + // 3. Media Handling Configuration + // ============================================== + + media: { + /** + * Recording Configuration + * ======================= + * Server side recording functionality. + * + * Core Settings: + * ------------------------ + * - enabled : Enable recording functionality + * - uploadToS3 : Upload recording to AWS S3 bucket [true/false] + * - endpoint : Leave empty ('') to store recordings locally OR + * - Set to a valid URL (e.g., 'http://localhost:8080/') to: + * - Push recordings to a remote server + * - Store in cloud storage services + * - Send to processing pipelines + * - dir : Storage directory for recordings (Relative to app/src/ default: app/rec) + * - maxFileSize : Maximum recording size (1GB default) + * + * Docker Note: + * ------------ + * - When running in Docker, ensure the recording directory exists and is properly mounted: + * 1. Create the directory (e.g., 'app/rec') + * 2. Configure as volume in docker-compose.yml + * 3. Set appropriate permissions + * 4. Restart container after changes + */ recording: { - /* - The recording will be saved to the directory designated within your Server app/ - Note: if you use Docker: Create the "app/rec" directory, configure it as a volume in docker-compose.yml, - ensure proper permissions, and start the Docker container. - */ - enabled: false, - endpoint: '', // Change the URL if you want to save the recording to a different server or cloud service (http://localhost:8080), otherwise leave it as is (empty). - dir: 'rec', - maxFileSize: 1 * 1024 * 1024 * 1024, // 1 GB + enabled: process.env.RECORDING_ENABLED === 'true', + uploadToS3: process.env.RECORDING_UPLOAD_TO_S3 === 'true', + endpoint: process.env.RECORDING_ENDPOINT || '', + dir: process.env.RECORDING_DIR || '../rec', + maxFileSize: process.env.RECORDING_MAX_FILE_SIZE || 1 * 1024 * 1024 * 1024, // 1GB }, + + /** + * RTMP Configuration + * ================= + * Configures Real-Time Messaging Protocol (RTMP) for audio/video/data streaming. + * + * Core Settings + * ------------ + * - enabled : Enable/disable RTMP streaming (default: false) + * - fromFile : Enable local file streaming (default: true) + * - fromUrl : Enable URL streaming (default: true) + * - fromStream : Enable live stream input (default: true) + * - maxStreams : Maximum simultaneous streams (default: 1) + * - useNodeMediaServer : Use NodeMediaServer instead of nginx-rtmp (default: true) + * - server : RTMP server URL (default: 'rtmp://localhost:1935') + * - appName : Application name (default: 'live') + * - streamKey : Optional authentication key (auto-generated UUID if empty) + * - secret : Must match NodeMediaServer's config.js (default: 'mirotalkRtmpSecret') + * - apiSecret : WebRTC→RTMP API secret (default: 'mirotalkRtmpApiSecret') + * - expirationHours : Stream URL expiry in hours (default: 4) + * - dir : Video storage directory (Relative to app/src/ default: app/rtmp) + * - ffmpegPath : FFmpeg binary path (auto-detected) + * - platform : Current OS platform (auto-detected) + * + * Server Management + * ---------------- + * NodeMediaServer (mirotalk/nms:latest): + * - Start: npm run nms-start + * - Stop: npm run nms-stop + * - Logs: npm run nms-logs + * + * NGINX-RTMP (mirotalk/rtmp:latest): + * - Start: npm run rtmp-start + * - Stop: npm run rtmp-stop + * - Logs: npm run rtmp-logs + * + * Implementation Notes: + * -------------------- + * 1. For NodeMediaServer: + * - Mandatory values: appName (falls back to 'live'), streamKey (auto-generated) + * - URL format: rtmp://host:port/appName/streamKey?sign=expiration-token + * + * 2. Default Behavior: + * - If server URL is empty, uses localhost:1935 + * - If no streamKey provided, generates UUIDv4 + * - When useNodeMediaServer=true, generates signed URLs with expiration + * + * Requirements: + * ------------- + * - RTMP server must be running + * - Port 1935 must be accessible + * - FFmpeg must be installed + * + * Documentation: + * -------------- + * - https://docs.mirotalk.com/mirotalk-sfu/rtmp/ + */ rtmp: { - /* - Real-Time Messaging Protocol (RTMP) is a communication protocol for streaming audio, video, and data over the Internet. (beta) - - Configuration: - - enabled: Enable or disable the RTMP streaming feature. Set to 'true' to enable, 'false' to disable. - - fromFile: Enable or disable the RTMP streaming from File. Set to 'true' to enable, 'false' to disable. - - fromUrl: Enable or disable the RTMP streaming from Url. Set to 'true' to enable, 'false' to disable. - - fromStream: Enable or disable the RTMP Streamer. Set to 'true' to enable, 'false' to disable. - - maxStreams: Specifies the maximum number of simultaneous streams permitted for File, URL, and Stream. The default value is 1. - - server: The URL of the RTMP server. Leave empty to use the built-in MiroTalk RTMP server (rtmp://localhost:1935). Change the URL to connect to a different RTMP server. - - appName: The application name for the RTMP stream. Default is 'mirotalk'. - - streamKey: The stream key for the RTMP stream. Leave empty if not required. - - secret: The secret key for RTMP streaming. Must match the secret in rtmpServers/node-media-server/src/config.js. Leave empty if no authentication is needed. - - apiSecret: The API secret for streaming WebRTC to RTMP through the MiroTalk API. - - expirationHours: The number of hours before the RTMP URL expires. Default is 4 hours. - - dir: Directory where your video files are stored to be streamed via RTMP. - - ffmpegPath: Path of the ffmpeg installation on the system (which ffmpeg) - - platform: 'darwin', 'linux', 'win32', etc. - - Important: Before proceeding, make sure your RTMP server is up and running. - For more information, refer to the documentation here: https://docs.mirotalk.com/mirotalk-sfu/rtmp/. - You can start the server by running the following command: - - Start: npm run nms-start - Start the RTMP server. - - Stop: npm run npm-stop - Stop the RTMP server. - - Logs: npm run npm-logs - View the logs of the RTMP server. - */ - enabled: false, - fromFile: true, - fromUrl: true, - fromStream: true, - maxStreams: 1, - server: 'rtmp://localhost:1935', - appName: 'mirotalk', - streamKey: '', - secret: 'mirotalkRtmpSecret', - apiSecret: 'mirotalkRtmpApiSecret', - expirationHours: 4, - dir: 'rtmp', - ffmpegPath: ffmpegPath, - platform: platform, + enabled: process.env.RTMP_ENABLED === 'true', + fromFile: process.env.RTMP_FROM_FILE !== 'false', + fromUrl: process.env.RTMP_FROM_URL !== 'false', + fromStream: process.env.RTMP_FROM_STREAM !== 'false', + maxStreams: parseInt(process.env.RTMP_MAX_STREAMS) || 1, + useNodeMediaServer: process.env.RTMP_USE_NODE_MEDIA_SERVER !== 'false', + server: process.env.RTMP_SERVER || 'rtmp://localhost:1935', + appName: process.env.RTMP_APP_NAME || 'live', + streamKey: process.env.RTMP_STREAM_KEY || '', + secret: process.env.RTMP_SECRET || 'mirotalkRtmpSecret', + apiSecret: process.env.RTMP_API_SECRET || 'mirotalkRtmpApiSecret', + expirationHours: parseInt(process.env.RTMP_EXPIRATION_HOURS) || 4, + dir: process.env.RTMP_DIR || '../rtmp', + ffmpegPath: RTMP_FFMPEG_PATH, + platform: PLATFORM, }, }, - middleware: { - /* - Middleware: - - IP Whitelist: Access to the instance is restricted to only the specified IP addresses in the allowed list. This feature is disabled by default. - - ... - */ - IpWhitelist: { - enabled: false, - allowed: ['127.0.0.1', '::1'], + + // ============================================== + // 4. Security & Authentication + // ============================================== + + security: { + /** + * IP Whitelisting + * ------------------------ + * - enabled: Restrict access to specified IPs + * - allowedIPs: Array of permitted IP addresses + */ + middleware: { + IpWhitelist: { + enabled: process.env.IP_WHITELIST_ENABLED === 'true', + allowedIPs: process.env.IP_WHITELIST_ALLOWED + ? process.env.IP_WHITELIST_ALLOWED.split(splitChar) + .map((ip) => ip.trim()) + .filter((ip) => ip !== '') + : ['127.0.0.1', '::1'], + }, }, - }, - api: { - // Default secret key for app/api - keySecret: 'mirotalksfu_default_secret', - // Define which endpoints are allowed - allowed: { - stats: true, - meetings: false, - meeting: true, - join: true, - token: false, - slack: true, - mattermost: true, - //... + + /** + * JWT Configuration + * ------------------------ + * - key: Secret for JWT signing + * - exp: Token expiration time + */ + jwt: { + key: process.env.JWT_SECRET || 'mirotalksfu_jwt_secret', + exp: process.env.JWT_EXPIRATION || '1h', }, - }, - jwt: { - /* - JWT https://jwt.io/ - Securely manages credentials for host configurations and user authentication, enhancing security and streamlining processes. + + /** + * OpenID Connect (OIDC) Authentication Configuration + * ================================================= + * Configures authentication using OpenID Connect (OIDC), allowing integration with + * identity providers like Auth0, Okta, Keycloak, etc. + * + * Structure: + * - enabled : Master switch for OIDC authentication + * - baseURLDynamic : Whether to dynamically resolve base URL + * allow_rooms_creation_for_auth_users : Allow all authenticated users via OIDC to create their own rooms + * - peer_name : Controls which user attributes to enforce/request + * - config : Core OIDC provider settings + * + * Core Settings: + * - issuerBaseURL : Provider's discovery endpoint (e.g., https://your-tenant.auth0.com) + * - baseURL : Your application's base URL + * - clientID : Client identifier issued by provider + * - clientSecret : Client secret issued by provider + * - secret : Application session secret + * - authRequired : Whether all routes require authentication + * - auth0Logout : Whether to use provider's logout endpoint + * - authorizationParams: OAuth/OIDC flow parameters including: + * - response_type : OAuth response type ('code' for Authorization Code Flow) + * - scope : Requested claims (openid, profile, email) + * - routes : Endpoint path configuration for: + * - callback : OAuth callback handler path + * - login : Custom login path (false to disable) + * - logout : Custom logout path + * */ - key: 'mirotalksfu_jwt_secret', - exp: '1h', - }, - oidc: { - /* - OIDC stands for OpenID Connect, which is an authentication protocol built on top of OAuth 2.0. - It provides a simple identity layer on the OAuth 2.0 protocol, allowing clients to verify the identity of the end-user - based on the authentication performed by an authorization server. - How to configure your own Provider: - 1. Sign up for an account at https://auth0.com. - 2. Navigate to https://manage.auth0.com/ to create a new application tailored to your specific requirements. - For those seeking an open-source solution, check out: https://github.com/panva/node-oidc-provider - */ - enabled: false, - baseURLDynamic: false, - peer_name: { - force: true, // Enforce using profile data for peer_name - email: true, // Use email as peer_name - name: false, // Don't use full name (family_name + given_name) - }, - config: { - issuerBaseURL: 'https://server.example.com', - baseURL: `http://localhost:${process.env.PORT ? process.env.PORT : 3010}`, // https://sfu.mirotalk.com - clientID: 'clientID', - clientSecret: 'clientSecret', - secret: 'mirotalksfu-oidc-secret', - authorizationParams: { - response_type: 'code', - scope: 'openid profile email', + oidc: { + enabled: process.env.OIDC_ENABLED === 'true', + baseURLDynamic: false, // Set true if your app has dynamic base URLs + + // ================================================================================================== + allow_rooms_creation_for_auth_users: process.env.OIDC_ALLOW_ROOMS_CREATION_FOR_AUTH_USERS !== 'false', + // ================================================================================================== + + // User identity requirements + peer_name: { + force: process.env.OIDC_USERNAME_FORCE !== 'false', // Forces the username to match the OIDC email or name. If true, the user won't be able to change their name when joining a room + email: process.env.OIDC_USERNAME_AS_EMAIL !== 'false', // Uses the OIDC email as the username. + name: process.env.OIDC_USERNAME_AS_NAME === 'true', // Uses the OIDC name as the username }, - authRequired: false, // Set to true if authentication is required for all routes - auth0Logout: true, // Set to true to enable logout with Auth0 - routes: { - callback: '/auth/callback', // Indicating the endpoint where your application will handle the callback from the authentication provider after a user has been authenticated. - login: false, // Dedicated route in your application for user login. - logout: '/logout', // Indicating the endpoint where your application will handle user logout requests. + + // Provider configuration + config: { + // Required provider settings + issuerBaseURL: process.env.OIDC_ISSUER || 'https://server.example.com', + baseURL: process.env.OIDC_BASE_URL || `http://localhost:${process.env.PORT || 3010}`, + clientID: process.env.OIDC_CLIENT_ID || 'clientID', + clientSecret: process.env.OIDC_CLIENT_SECRET || 'clientSecret', + + // Session configuration + secret: process.env.OIDC_SECRET || 'mirotalksfu-oidc-secret', + authRequired: process.env.OIDC_AUTH_REQUIRED === 'true', // Whether all routes require authentication + auth0Logout: process.env.OIDC_AUTH_LOGOUT !== 'false', // Use provider's logout endpoint + + // OAuth/OIDC flow parameters + authorizationParams: { + response_type: 'code', // Use authorization code flow + scope: 'openid profile email', // Request standard claims + }, + + // Route customization + routes: { + callback: '/auth/callback', // OAuth callback path + login: false, // Disable default login route + logout: '/logout', // Custom logout path + }, }, }, - }, - host: { - /* - Host Protection (default: false) - To enhance host security, enable host protection - user auth and provide valid - usernames and passwords in the users array or active users_from_db using users_api_endpoint for check. - When oidc.enabled is utilized alongside host protection, the authenticated user will be recognized as valid. - */ - protected: false, - user_auth: false, - users_from_db: false, // if true ensure that api.token is also set to true. - users_api_endpoint: 'http://localhost:9000/api/v1/user/isAuth', - users_api_room_allowed: 'http://localhost:9000/api/v1/user/isRoomAllowed', - users_api_rooms_allowed: 'http://localhost:9000/api/v1/user/roomsAllowed', - api_room_exists: 'http://localhost:9000/api/v1/room/exists', - //users_api_endpoint: 'https://webrtc.mirotalk.com/api/v1/user/isAuth', - //users_api_room_allowed: 'https://webrtc.mirotalk.com/api/v1/user/isRoomAllowed', - //users_api_rooms_allowed: 'https://webrtc.mirotalk.com/api/v1/user/roomsAllowed', - //api_room_exists: 'https://webrtc.mirotalk.com//api/v1/room/exists', - users_api_secret_key: 'mirotalkweb_default_secret', - users: [ - { - username: 'username', - password: 'password', - displayname: 'username displayname', - allowed_rooms: ['*'], - }, - { - username: 'username2', - password: 'password2', - displayname: 'username2 displayname', - allowed_rooms: ['room1', 'room2'], - }, - { - username: 'username3', - password: 'password3', - displayname: 'username3 displayname', - }, - //... - ], - }, - presenters: { - list: [ - /* - By default, the presenter is identified as the first participant to join the room, distinguished by their username and UUID. - Additional layers can be added to specify valid presenters and co-presenters by setting designated usernames. - */ - 'Miroslav Pejic', - 'miroslav.pejic.85@gmail.com', - ], - join_first: true, // Set to true for traditional behavior, false to prioritize presenters - }, - chatGPT: { - /* - ChatGPT - 1. Goto https://platform.openai.com/ - 2. Create your account - 3. Generate your APIKey https://platform.openai.com/account/api-keys - */ - enabled: false, - basePath: 'https://api.openai.com/v1/', - apiKey: '', - model: 'gpt-3.5-turbo', - max_tokens: 1000, - temperature: 0, - }, - videoAI: { - /* - HeyGen Video AI - 1. Goto https://app.heygen.com - 2. Create your account - 3. Generate your APIKey https://app.heygen.com/settings?nav=API - */ - enabled: false, - basePath: 'https://api.heygen.com', - apiKey: '', - systemLimit: - 'You are a streaming avatar from MiroTalk SFU, an industry-leading product that specialize in videos communications.', - }, - email: { - /* - Configure email settings for notifications or alerts - Refer to the documentation for Gmail configuration: https://support.google.com/mail/answer/185833?hl=en - */ - alert: false, - host: 'smtp.gmail.com', - port: 587, - username: 'your_username', - password: 'your_password', - sendTo: 'sfu.mirotalk@gmail.com', - }, - ngrok: { - /* - Ngrok - 1. Goto https://ngrok.com - 2. Get started for free - 3. Copy YourNgrokAuthToken: https://dashboard.ngrok.com/get-started/your-authtoken - */ - enabled: false, - authToken: '', - }, - sentry: { - /* - Sentry - 1. Goto https://sentry.io/ - 2. Create account - 3. On dashboard goto Settings/Projects/YourProjectName/Client Keys (DSN) - */ - enabled: false, - DSN: '', - tracesSampleRate: 0.5, - }, - webhook: { - /* - Enable or disable webhook functionality. - Set `enabled` to `true` to activate webhook sending of socket events (join, exitRoom, disconnect) - */ - enabled: false, - url: 'https://your-site.com/webhook-endpoint', - }, - mattermost: { - /* - Mattermost: https://mattermost.com - 1. Navigate to Main Menu > Integrations > Slash Commands in Mattermost. - 2. Click on Add Slash Command and configure the following settings: - - Title: Enter a descriptive title (e.g., `SFU Command`). - - Command Trigger Word: Set the trigger word to `sfu`. - - Callback URLs: Enter the URL for your Express server (e.g., `https://yourserver.com/mattermost`). - - Request Method: Select POST. - - Enable Autocomplete: Check the box for Autocomplete. - - Autocomplete Description: Provide a brief description (e.g., `Get MiroTalk SFU meeting room`). - 3. Save the slash command and copy the generated token (YourMattermostToken). - */ - enabled: false, - serverUrl: 'YourMattermostServerUrl', - username: 'YourMattermostUsername', - password: 'YourMattermostPassword', - token: 'YourMattermostToken', - commands: [ - { - name: '/sfu', - message: 'Here is your meeting room:', - }, - //.... - ], - texts: [ - { - name: '/sfu', - message: 'Here is your meeting room:', + + /** + * Host Protection Configuration + * ============================ + * Controls access to host-level functionality and room management. + * + * Authentication Methods: + * ---------------------- + * - Local users (defined in config or via HOST_USERS env variable) + * - API/database validation (users_from_db=true) + * + * Core Settings: + * -------------- + * - protected : Enable/disable host protection globally + * - user_auth : Require user authentication for host access + * - users_from_db : Fetch users from API/database instead of local config + * - maxAttempts : Maximum login attempts before temporary block + * - minBlockTime : Block duration in minutes after max attempts exceeded + * + * API Integration: + * --------------- + * - users_api_secret_key : Secret key for API authentication + * - users_api_endpoint : Endpoint to validate user credentials + * - users_api_room_allowed : Endpoint to check if user can access a room + * - users_api_rooms_allowed : Endpoint to get allowed rooms for a user + * - api_room_exists : Endpoint to verify if a room exists + * + * Local User Configuration: + * ------------------------ + * - users: Array of authorized users (used if users_from_db=false) + * - Define via HOST_USERS env variable: + * HOST_USERS=username:password:displayname:room1,room2|username2:password2:displayname2:* + * (Each user separated by '|', fields by ':', allowed_rooms comma-separated or '*' for all) + * - If HOST_USERS is not set, falls back to DEFAULT_USERNAME, DEFAULT_PASSWORD, etc. + * - Fields: + * - username : Login username + * - password : Login password + * - displayname : User's display name + * - allowed_rooms : List of rooms user can access ('*' for all) + * + * Presenter Management: + * -------------------- + * - list : Array of usernames who can be presenters + * - join_first : First joiner becomes presenter (default: true) + * + * Documentation: + * ------------- + * https://docs.mirotalk.com/mirotalk-sfu/host-protection/ + */ + host: { + protected: process.env.HOST_PROTECTED === 'true', + user_auth: process.env.HOST_USER_AUTH === 'true', + + maxAttempts: process.env.HOST_MAX_LOGIN_ATTEMPTS || 5, + minBlockTime: process.env.HOST_MIN_LOGIN_BLOCK_TIME || 15, // in minutes + + users_from_db: process.env.HOST_USERS_FROM_DB === 'true', + users_api_secret_key: process.env.USERS_API_SECRET || 'mirotalkweb_default_secret', + users_api_endpoint: process.env.USERS_API_ENDPOINT || 'http://localhost:9000/api/v1/user/isAuth', // 'https://webrtc.mirotalk.com/api/v1/user/isAuth' + users_api_room_allowed: + process.env.USERS_ROOM_ALLOWED_ENDPOINT || 'http://localhost:9000/api/v1/user/isRoomAllowed', // 'https://webrtc.mirotalk.com/api/v1/user/isRoomAllowed' + users_api_rooms_allowed: + process.env.USERS_ROOMS_ALLOWED_ENDPOINT || 'http://localhost:9000/api/v1/user/roomsAllowed', // 'https://webrtc.mirotalk.com/api/v1/user/roomsAllowed' + api_room_exists: process.env.ROOM_EXISTS_ENDPOINT || 'http://localhost:9000/api/v1/room/exists', // 'https://webrtc.mirotalk.com//api/v1/room/exists' + + users: process.env.HOST_USERS + ? process.env.HOST_USERS.split('|').map((userStr) => { + const [username, password, displayname, allowedRoomsStr] = userStr.split(':'); + return { + username: username || '', + password: password || '', + displayname: displayname || '', + allowed_rooms: allowedRoomsStr + ? allowedRoomsStr + .split(',') + .map((room) => room.trim()) + .filter((room) => room !== '') + : ['*'], + }; + }) + : [ + { + username: 'username', + password: 'password', + displayname: 'username displayname', + allowed_rooms: ['*'], + }, + { + username: 'username2', + password: 'password2', + displayname: 'username2 displayname', + allowed_rooms: ['room1', 'room2'], + }, + { + username: 'username3', + password: 'password3', + displayname: 'username3 displayname', + }, + //... + ], + + presenters: { + list: process.env.PRESENTERS + ? process.env.PRESENTERS.split(splitChar) + .map((presenter) => presenter.trim()) + .filter((presenter) => presenter !== '') + : ['Miroslav Pejic', 'miroslav.pejic.85@gmail.com'], + join_first: process.env.PRESENTER_JOIN_FIRST !== 'false', }, - //.... - ], + }, }, - slack: { - /* - Slack - 1. Goto https://api.slack.com/apps/ - 2. Create your app - 3. On Settings - Basic Information - App Credentials, chose your Signing Secret - 4. Create a Slash Commands and put as Request URL: https://your.domain.name/slack - */ - enabled: false, - signingSecret: '', + + // ============================================== + // 5. API Configuration + // ============================================== + + /** + * API Security & Endpoint Configuration + * ==================================== + * Controls access to the SFU's API endpoints and integration settings. + * + * Security Settings: + * ----------------- + * - keySecret : Authentication secret for API requests + * (Always override default in production) + * + * Endpoint Control: + * ----------------- + * - stats : Enable/disable system statistics endpoint [true/false] (default: true) + * - meetings : Enable/disable meetings list endpoint [true/false] (default: true) + * - meeting : Enable/disable single meeting operations [true/false] (default: true) + * - join : Enable/disable meeting join endpoint [true/false] (default: true) + * - token : Enable/disable token generation endpoint [true/false] (default: false) + * - slack : Enable/disable Slack webhook integration [true/false] (default: true) + * - mattermost : Enable/disable Mattermost webhook integration [true/false] (default: true) + * + * API Documentation: + * ------------------ + * - Complete API reference: https://docs.mirotalk.com/mirotalk-sfu/api/ + * - Webhook setup: See integration guides for Slack/Mattermost + */ + api: { + keySecret: process.env.API_KEY_SECRET || 'mirotalksfu_default_secret', + allowed: { + stats: process.env.API_ALLOW_STATS !== 'false', + meetings: process.env.API_ALLOW_MEETINGS === 'true', + meeting: process.env.API_ALLOW_MEETING !== 'false', + meetingEnd: process.env.API_ALLOW_MEETING_END === 'true', + join: process.env.API_ALLOW_JOIN !== 'false', + token: process.env.API_ALLOW_TOKEN === 'true', + slack: process.env.API_ALLOW_SLACK !== 'false', + mattermost: process.env.API_ALLOW_MATTERMOST !== 'false', + }, }, - discord: { - /* - Discord - 1. Go to the Discord Developer Portal: https://discord.com/developers/. - 2. Create a new application and name it whatever you like. - 3. Under the Bot section, click Add Bot and confirm. - 4. Copy your bot token (this will be used later). - 5. Under OAuth2 -> URL Generator, select bot scope, and under Bot Permissions, select the permissions you need (e.g., Send Messages and Read Messages). - 6. Copy the generated invite URL, open it in a browser, and invite the bot to your Discord server. - 7. Add the Bot in the Server channel permissions - 8. Type /sfu (commands.name) in the channel, the response will return a URL for the meeting - */ - enabled: false, - token: '', - commands: [ - { - name: '/sfu', - message: 'Here is your SFU meeting room:', - baseUrl: 'https://sfu.mirotalk.com/join/', + + // ============================================== + // 6. Third-Party Integrations + // ============================================== + + integrations: { + /** + * ChatGPT Integration Configuration + * ================================ + * OpenAI API integration for AI-powered chat functionality + * + * Setup Instructions: + * ------------------ + * 1. Go to https://platform.openai.com/ + * 2. Create your OpenAI account + * 3. Generate your API key at https://platform.openai.com/account/api-keys + * + * Core Settings: + * ------------- + * - enabled : Enable/disable ChatGPT integration [true/false] (default: false) + * - basePath : OpenAI API endpoint (default: 'https://api.openai.com/v1/') + * - apiKey : OpenAI API secret key (ALWAYS store in .env) + * - model : GPT model version (default: 'gpt-3.5-turbo') + * + * Advanced Settings: + * ----------------- + * - max_tokens: Maximum response length (default: 1024 tokens) + * - temperature: Creativity control (0=strict, 1=creative) (default: 0) + * + * Usage Example: + * ------------- + * 1. Supported Models: + * - gpt-3.5-turbo (recommended) + * - gpt-4 + * - gpt-4-turbo + * - ... + * + * 2. Temperature Guide: + * - 0.0: Factual responses + * - 0.7: Balanced + * - 1.0: Maximum creativity + */ + chatGPT: { + enabled: process.env.CHATGPT_ENABLED === 'true', + basePath: process.env.CHATGPT_BASE_PATH || 'https://api.openai.com/v1/', + apiKey: process.env.CHATGPT_API_KEY || '', + model: process.env.CHATGPT_MODEL || 'gpt-3.5-turbo', + max_tokens: parseInt(process.env.CHATGPT_MAX_TOKENS) || 1024, + temperature: parseInt(process.env.CHATGPT_TEMPERATURE) || 0.7, + }, + + /** + * DeepDeek Integration Configuration + * ================================ + * DeepDeek API integration for AI-powered chat functionality + * + * Setup Instructions: + * ------------------ + * 1. Go to https://deepseek.com/ + * 2. Create your DeepDeek account + * 3. Generate your API key at https://deepseek.com/account/api-keys + * + * Core Settings: + * ------------- + * - enabled : Enable/disable DeepDeek integration [true/false] (default: false) + * - basePath : DeepDeek API endpoint (default: 'https://api.deepseek.com/v1/') + * - apiKey : DeepDeek API secret key (ALWAYS store in .env) + * - model : DeepDeek model version (default: 'deepdeek-chat') + * + * Advanced Settings: + * ----------------- + * - max_tokens: Maximum response length (default: 1024 tokens) + * - temperature: Creativity control (0=strict, 1=creative) (default: 0) + * + * Usage Example: + * ------------- + * 1. Supported Models: + * - deepseek-chat (recommended) + * - deepseek-coder + * - deepseek-math + * - deepseek-llm + * - ... + * + * 2. Temperature Guide: + * - 0.0: Factual responses + * - 0.7: Balanced + * - 1.0: Maximum creativity + * + */ + deepSeek: { + enabled: process.env.DEEP_SEEK_ENABLED === 'true', + basePath: process.env.DEEP_SEEK_BASE_PATH || 'https://api.deepseek.com/v1/', + apiKey: process.env.DEEP_SEEK_API_KEY || '', + model: process.env.DEEP_SEEK_MODEL || 'deepseek-chat', + max_tokens: parseInt(process.env.DEEP_SEEK_MAX_TOKENS) || 1024, + temperature: parseInt(process.env.DEEP_SEEK_TEMPERATURE) || 0.7, + }, + + /** + * LiveAvatar Video AI Configuration + * ================================= + * AI-powered avatar streaming integration (migrated from HeyGen) + * + * Setup Instructions: + * ------------------ + * 1. Go to https://app.liveavatar.com + * 2. Create your LiveAvatar account + * 3. Generate your API key from settings + * + * Core Settings: + * ------------- + * - enabled : Enable/disable Video AI [true/false] (default: false) + * - basePath : LiveAvatar API endpoint (default: 'https://api.liveavatar.com') + * - apiKey : From LiveAvatar account (ALWAYS store in .env) + * - mode : Session mode - FULL (managed LLM) or LITE (custom pipeline) + * - contextId : Optional context ID for avatar personality/knowledge + * + * AI Behavior: + * ----------- + * - systemLimit: Personality/behavior instructions for the AI avatar + * (default: Streaming avatar instructions for MiroTalk SFU) + */ + videoAI: { + enabled: process.env.VIDEOAI_ENABLED === 'true', + basePath: process.env.VIDEOAI_BASE_PATH || 'https://api.liveavatar.com', + apiKey: process.env.VIDEOAI_API_KEY || '', + mode: process.env.VIDEOAI_MODE || 'FULL', + contextId: process.env.VIDEOAI_CONTEXT_ID || '', + systemLimit: process.env.VIDEOAI_SYSTEM_LIMIT || 'You are a streaming avatar from MiroTalk SFU...', + sessionTimeLimit: process.env.VIDEOAI_SESSION_TIME_LIMIT + ? parseInt(process.env.VIDEOAI_SESSION_TIME_LIMIT, 10) + : 0, // Session time limit in seconds (0 = unlimited) + }, + + /** + * Email Notification Configuration + * =============================== + * SMTP settings for system alerts and notifications + * + * Core Settings: + * ------------- + * - alert : Enable/disable email alerts [true/false] (default: false) + * - notify : Enable/disable room email notifications [true/false] (default: false) + * - host : SMTP server address (default: 'smtp.gmail.com') + * - port : SMTP port (default: 587 for TLS) + * - username : SMTP auth username + * - password : SMTP auth password (store ONLY in .env) + * - from : Sender email address (default: same as username) + * - sendTo : Recipient email for alerts + * + * Common Providers: + * ---------------- + * Gmail: + * - host: smtp.gmail.com + * - port: 587 + * + * Office365: + * - host: smtp.office365.com + * - port: 587 + * + * SendGrid: + * - host: smtp.sendgrid.net + * - port: 587 + */ + email: { + alert: process.env.EMAIL_ALERTS_ENABLED === 'true', + notify: process.env.EMAIL_NOTIFICATIONS === 'true', + host: process.env.EMAIL_HOST || 'smtp.gmail.com', + port: parseInt(process.env.EMAIL_PORT) || 587, + username: process.env.EMAIL_USERNAME || 'your_username', + password: process.env.EMAIL_PASSWORD || 'your_password', + from: process.env.EMAIL_FROM || process.env.EMAIL_USERNAME, + sendTo: process.env.EMAIL_SEND_TO || 'sfu.mirotalk@gmail.com', + }, + + /** + * Slack Integration Configuration + * ============================== + * Settings for Slack slash commands and interactivity + * + * Setup Instructions: + * ------------------ + * 1. Create a Slack app at https://api.slack.com/apps + * 2. Under "Basic Information" → "App Credentials": + * - Copy the Signing Secret + * 3. Enable "Interactivity & Shortcuts" and "Slash Commands" + * 4. Set Request URL to: https://your-domain.com/slack/commands + * + * Core Settings: + * ------------- + * - enabled : Enable/disable Slack integration [true/false] (default: false) + * - signingSecret : From Slack app credentials (store ONLY in .env) + * + */ + slack: { + enabled: process.env.SLACK_ENABLED === 'true', + signingSecret: process.env.SLACK_SIGNING_SECRET || '', + }, + + /** + * Mattermost Integration Configuration + * =================================== + * Settings for Mattermost slash commands and bot integration + * + * Setup Instructions: + * ------------------ + * 1. Go to Mattermost System Console → Integrations → Bot Accounts + * 2. Create a new bot account and copy: + * - Server URL (e.g., 'https://chat.yourdomain.com') + * - Access Token + * 3. For slash commands: + * - Navigate to Integrations → Slash Commands + * - Set Command: '/sfu' + * - Set Request URL: 'https://your-sfu-server.com/mattermost/commands' + * + * Core Settings: + * ------------- + * - enabled : Enable/disable integration [true/false] (default: false) + * - serverUrl : Mattermost server URL (include protocol) + * - token : Bot account access token (most secure option) + * - OR + * - username : Legacy auth username (less secure) + * - password : Legacy auth password (deprecated) + * + * Command Configuration: + * --------------------- + * - commands : Slash command definitions: + * - name : Command trigger (e.g., '/sfu') + * - message : Default response template + * + */ + mattermost: { + enabled: process.env.MATTERMOST_ENABLED === 'true', + serverUrl: process.env.MATTERMOST_SERVER_URL || '', + username: process.env.MATTERMOST_USERNAME || '', + password: process.env.MATTERMOST_PASSWORD || '', + token: process.env.MATTERMOST_TOKEN || '', + commands: [ + { + name: process.env.MATTERMOST_COMMAND_NAME || '/sfu', + message: process.env.MATTERMOST_DEFAULT_MESSAGE || 'Here is your meeting room:', + }, + ], + texts: [ + { + name: process.env.MATTERMOST_COMMAND_NAME || '/sfu', + message: process.env.MATTERMOST_DEFAULT_MESSAGE || 'Here is your meeting room:', + }, + ], + }, + + /** + * Discord Integration Configuration + * ================================ + * Settings for Discord bot and slash commands integration + * + * Setup Instructions: + * ------------------ + * 1. Create a Discord application at https://discord.com/developers/applications + * 2. Navigate to "Bot" section and: + * - Click "Add Bot" + * - Copy the bot token (DISCORD_TOKEN) + * 3. Under "OAuth2 → URL Generator": + * - Select "bot" and "applications.commands" scopes + * - Select required permissions (see below) + * 4. Invite bot to your server using generated URL + * + * Core Settings: + * ------------- + * - enabled : Enable/disable Discord bot [true/false] (default: false) + * - token : Bot token from Discord Developer Portal (store in .env) + * + * Command Configuration: + * --------------------- + * - commands : Slash command definitions: + * - name : Command trigger (e.g., '/sfu') + * - message : Response template + * - baseUrl : Meeting room base URL + * + */ + discord: { + enabled: process.env.DISCORD_ENABLED === 'true', + token: process.env.DISCORD_TOKEN || '', + commands: [ + { + name: process.env.DISCORD_COMMAND_NAME || '/sfu', + message: process.env.DISCORD_DEFAULT_MESSAGE || 'Here is your SFU meeting room:', + baseUrl: process.env.DISCORD_BASE_URL || 'https://sfu.mirotalk.com/join/', + }, + ], + }, + + /** + * Ngrok Tunnel Configuration + * ========================= + * Secure tunneling for local development and testing + * + * Setup Instructions: + * ------------------ + * 1. Sign up at https://dashboard.ngrok.com/signup + * 2. Get your auth token from: + * https://dashboard.ngrok.com/get-started/your-authtoken + * 3. For reserved domains/subdomains: + * - Upgrade to paid plan if needed + * - Reserve at https://dashboard.ngrok.com/cloud-edge/domains + * + * Core Settings: + * ------------- + * - enabled : Enable/disable Ngrok tunneling [true/false] (default: false) + * - authToken : Your Ngrok authentication token (from dashboard) + */ + ngrok: { + enabled: process.env.NGROK_ENABLED === 'true', + authToken: process.env.NGROK_AUTH_TOKEN || '', + }, + + /** + * Sentry Error Tracking Configuration + * ================================== + * Real-time error monitoring and performance tracking + * + * Setup Instructions: + * ------------------ + * 1. Create a project at https://sentry.io/signup/ + * 2. Get your DSN from: + * Project Settings → Client Keys (DSN) + * 3. Configure alert rules and integrations as needed + * + * Core Settings: + * ------------- + * enabled : Enable/disable Sentry [true/false] (default: false) + * logLevels : Array of log levels to capture (default: ['error']) + * DSN : Data Source Name (from Sentry dashboard) + * tracesSampleRate : Percentage of transactions to capture (0.0-1.0) + * + * Performance Tuning: + * ------------------ + * - Production : 0.1-0.2 (10-20% of transactions) + * - Staging : 0.5-1.0 + * - Development : 0.0 (disable performance tracking) + * + */ + sentry: { + enabled: process.env.SENTRY_ENABLED === 'true', + logLevels: process.env.SENTRY_LOG_LEVELS + ? process.env.SENTRY_LOG_LEVELS.split(splitChar).map((level) => level.trim()) + : ['error'], + DSN: process.env.SENTRY_DSN || '', + tracesSampleRate: Math.min(Math.max(parseFloat(process.env.SENTRY_TRACES_SAMPLE_RATE) || 0.5, 0), 1), + }, + + /** + * Webhook Configuration Settings + * ============================= + * Controls the webhook notification system for sending event data to external services. + * + * Core Settings: + * --------------------- + * - enabled: Turns webhook notifications on/off + * - url: The endpoint URL where webhook payloads will be sent in JSON format + * + * Implementation Guide: + * -------------------- + * - For complete implementation examples, refer to: + * - Project demo: /mirotalksfu/webhook/ folder + */ + webhook: { + enabled: process.env.WEBHOOK_ENABLED === 'true', + url: process.env.WEBHOOK_URL || 'https://your-site.com/webhook-endpoint', + }, + + /** + * IP Geolocation Service Configuration + * =================================== + * Enables lookup of geographical information based on IP addresses using the GeoJS.io API. + * + * Core Settings: + * --------------------- + * - enabled: Enable/disable the IP lookup functionality [true/false] default false + * + * Service Details: + * -------------- + * - Uses GeoJS.io free API service (https://www.geojs.io/) + * - Returns JSON data containing: + * - Country, region, city + * - Latitude/longitude + * - Timezone and organization + * - Rate limits: 60 requests/minute (free tier) + */ + IPLookup: { + enabled: process.env.IP_LOOKUP_ENABLED === 'true', + getEndpoint(ip) { + return `https://get.geojs.io/v1/ip/geo/${ip}.json`; }, - ], - }, - IPLookup: { - /* - GeoJS - https://www.geojs.io/docs/v1/endpoints/geo/ - */ - enabled: false, - getEndpoint(ip) { - return `https://get.geojs.io/v1/ip/geo/${ip}.json`; + }, + + /** + * Example for AWS S3 Storage Configuration + * =========================== + * Enables cloud file storage using Amazon Simple Storage Service (S3). + * + * Core Settings: + * -------------- + * - enabled: Enable/disable AWS S3 integration [true/false] + * - accessKeyId: AWS access key ID (store in .env) + * - secretAccessKey: AWS secret access key (store in .env) + * - region: AWS region where the S3 bucket is located + * - bucket: Name of the S3 bucket to use for storage + * + * Advanced Settings: + * -------------- + * - endpoint: Custom S3 endpoint URL (if empty to auto-resolve from region). Useful for S3-compatible services like MinIO, Wasabi, DigitalOcean Spaces, etc. + * - forcePathStyle: Set to true for S3-compatible services like MinIO, Wasabi, etc. + * + * Service Setup: + * ------------- + * 1. Create an S3 Bucket: + * - Sign in to AWS Management Console + * - Navigate to S3 service + * - Click "Create bucket" + * - Choose unique name (e.g., 'mirotalk') + * - Select region (must match AWS_REGION in config) + * - Enable desired settings (versioning, logging, etc.) + * + * 2. Get Security Credentials: + * - Create IAM user with programmatic access + * - Attach 'AmazonS3FullAccess' policy (or custom minimal policy) + * - Save Access Key ID and Secret Access Key + * + * 3. Configure CORS (for direct uploads): + * [ + * { + * "AllowedHeaders": ["*"], + * "AllowedMethods": ["PUT", "POST"], + * "AllowedOrigins": ["*"], + * "ExposeHeaders": [] + * } + * ] + * + * Technical Details: + * ----------------- + * - Default region: us-east-2 (Ohio) + * - Direct upload uses presigned URLs (expire after 1 hour by default) + * - Recommended permissions for direct upload: + * - s3:PutObject + * - s3:GetObject + * - s3:DeleteObject + */ + s3: { + enabled: process.env.S3_ENABLED === 'true', + accessKeyId: process.env.S3_ACCESS_KEY_ID || 'your-access-key-id', + secretAccessKey: process.env.S3_SECRET_ACCESS_KEY || 'your-secret-access-key', + bucket: process.env.S3_BUCKET || 'mirotalk', + region: process.env.S3_REGION || 'us-east-2', + endpoint: process.env.S3_ENDPOINT || '', + forcePathStyle: process.env.S3_FORCE_PATH_STYLE === 'true', }, }, - survey: { - /* - QuestionPro - 1. GoTo https://www.questionpro.com/ - 2. Create your account - 3. Create your custom survey - */ - enabled: false, - url: '', - }, - redirect: { - /* - Redirect URL on leave room - Upon leaving the room, users who either opt out of providing feedback or if the survey is disabled - will be redirected to a specified URL. If enabled false the default '/newroom' URL will be used. - */ - enabled: false, - url: '', - }, + + // ============================================== + // 7. UI/UX Customization + // ============================================== + ui: { - /* - Customize your MiroTalk instance - Branding and customizations require a license: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970 - */ + /** + * Branding & Appearance Configuration + * ----------------------------------- + * Controls all aspects of the application's visual identity, content, and metadata. + * Supports environment variable overrides for deployment-specific customization. + * + * ============================================== + * LICENSE REQUIRED: + * ============================================== + * - https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970 + */ + rooms: { + /** + * Room Display Settings + * --------------------- + * - showActive: Show active rooms in the UI (default: false) + * https://sfu.mirotalk.com/activeRooms + */ + showActive: process.env.SHOW_ACTIVE_ROOMS === 'true', + }, brand: { + /** + * Application Branding + * -------------------- + * Core application identity and user interface text elements. + * + * Note: + * Set BRAND_HTML_INJECTION to 'false' to disable HTML injection. + * This allows for static branding in the public/views folder, without dynamic content injection. + */ + htmlInjection: process.env.BRAND_HTML_INJECTION !== 'false', + app: { - language: 'en', // https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes - name: 'MiroTalk SFU', - title: 'MiroTalk SFU
Free browser based Real-time video calls.
Simple, Secure, Fast.', + language: process.env.UI_LANGUAGE || 'en', + name: process.env.APP_NAME || 'MiroTalk SFU', + title: + process.env.APP_TITLE || + '

MiroTalk SFU

Free browser based Real-time video calls.
Simple, Secure, Fast.', description: - 'Start your next video call with a single click. No download, plug-in, or login is required. Just get straight to talking, messaging, and sharing your screen.', - joinDescription: 'Pick a room name.
How about this one?', - joinButtonLabel: 'JOIN ROOM', - joinLastLabel: 'Your recent room:', + process.env.APP_DESCRIPTION || + 'Start your next video call with a single click. No download, plug-in, or login is required.', + joinDescription: process.env.JOIN_DESCRIPTION || 'Pick a room name.
How about this one?', + joinButtonLabel: process.env.JOIN_BUTTON_LABEL || 'JOIN ROOM', + customizeButtonLabel: process.env.CUSTOMIZE_BUTTON_LABEL || 'CUSTOMIZE ROOM', + joinLastLabel: process.env.JOIN_LAST_LABEL || 'Your recent room:', }, + + /** + * Website Configuration + * -------------------- + * Site-wide settings including icons and page-specific content. + */ site: { - title: 'MiroTalk SFU, Free Video Calls, Messaging and Screen Sharing', - icon: '../images/logo.svg', - appleTouchIcon: '../images/logo.svg', - newRoomTitle: 'Pick name.
Share URL.
Start conference.', + title: process.env.SITE_TITLE || 'MiroTalk SFU, Free Video Calls, Messaging and Screen Sharing', + icon: process.env.SITE_ICON_PATH || '../images/logo.svg', + appleTouchIcon: process.env.APPLE_TOUCH_ICON_PATH || '../images/logo.svg', + newRoomTitle: process.env.NEW_ROOM_TITLE || 'Pick name.
Share URL.
Start conference.', newRoomDescription: - "Each room has its disposable URL. Just pick a room name and share your custom URL. It's that easy.", + process.env.NEW_ROOM_DESC || 'Each room has its disposable URL. Just pick a name and share.', }, + + /** + * SEO Metadata + * ------------ + * Search engine optimization elements. + */ meta: { description: - 'MiroTalk SFU powered by WebRTC and mediasoup, Real-time Simple Secure Fast video calls, messaging and screen sharing capabilities in the browser.', - keywords: - 'webrtc, miro, mediasoup, mediasoup-client, self hosted, voip, sip, real-time communications, chat, messaging, meet, webrtc stun, webrtc turn, webrtc p2p, webrtc sfu, video meeting, video chat, video conference, multi video chat, multi video conference, peer to peer, p2p, sfu, rtc, alternative to, zoom, microsoft teams, google meet, jitsi, meeting', + process.env.META_DESCRIPTION || + 'MiroTalk SFU powered by WebRTC and mediasoup for real-time video communications.', + keywords: process.env.META_KEYWORDS || 'webrtc, video calls, conference, screen sharing, mirotalk, sfu', }, + + /** + * OpenGraph/Social Media + * --------------------- + * Metadata for rich social media sharing. + */ og: { - type: 'app-webrtc', - siteName: 'MiroTalk SFU', - title: 'Click the link to make a call.', - description: 'MiroTalk SFU calling provides real-time video calls, messaging and screen sharing.', - image: 'https://sfu.mirotalk.com/images/mirotalksfu.png', - url: 'https://sfu.mirotalk.com', + type: process.env.OG_TYPE || 'app-webrtc', + siteName: process.env.OG_SITE_NAME || 'MiroTalk SFU', + title: process.env.OG_TITLE || 'Click the link to make a call.', + description: + process.env.OG_DESCRIPTION || 'MiroTalk SFU provides real-time video calls and screen sharing.', + image: process.env.OG_IMAGE_URL || 'https://sfu.mirotalk.com/images/mirotalksfu.png', + url: process.env.OG_URL || 'https://sfu.mirotalk.com', }, + + /** + * UI Section Visibility + * --------------------- + * Toggle display of various page sections. + * Set to 'false' via environment variables to hide. + */ html: { - features: true, - teams: true, - tryEasier: true, - poweredBy: true, - sponsors: true, - advertisers: true, - footer: true, + topSponsors: process.env.SHOW_TOP_SPONSORS !== 'false', + features: process.env.SHOW_FEATURES !== 'false', + teams: process.env.SHOW_TEAMS !== 'false', + tryEasier: process.env.SHOW_TRY_EASIER !== 'false', + poweredBy: process.env.SHOW_POWERED_BY !== 'false', + sponsors: process.env.SHOW_SPONSORS !== 'false', + advertisers: process.env.SHOW_ADVERTISERS !== 'false', + supportUs: process.env.SHOW_SUPPORT_US !== 'false', + footer: process.env.SHOW_FOOTER !== 'false', + }, + + /** + * Who Are You? Section + * --------------------- + * Prompts users to identify themselves before joining a room. + * Customizable text and button labels. + */ + whoAreYou: { + title: process.env.WHO_ARE_YOU_TITLE || 'MiroTalk SFU - Waiting for host to start the meeting', + waitingRoomHeading: process.env.WHO_ARE_YOU_WAITING_ROOM_HEADING || 'Waiting for host...', + waitingRoomDescription: + process.env.WHO_ARE_YOU_WAITING_ROOM_DESCRIPTION || + "The meeting hasn't started yet.
You'll join automatically when the host opens the room.", + waitingRoomStatus: process.env.WHO_ARE_YOU_WAITING_ROOM_STATUS || 'Checking room status...', + waitingRoomReady: process.env.WHO_ARE_YOU_WAITING_ROOM_READY || 'Room is ready! Joining...', + waitingRoomWaiting: + process.env.WHO_ARE_YOU_WAITING_ROOM_WAITING || 'Waiting for host to start the meeting...', + waitingRoomHostLink: process.env.WHO_ARE_YOU_WAITING_ROOM_HOST_LINK || 'Are you the host?', + waitingRoomLoginLink: process.env.WHO_ARE_YOU_WAITING_ROOM_LOGIN_LINK || 'Login here', }, + + /** + * Login Page Section + * --------------------- + * Customizable heading, description, and button label for the login page. + */ + login: { + heading: process.env.LOGIN_HEADING || 'Welcome back', + description: process.env.LOGIN_DESCRIPTION || 'Enter your credentials to continue.', + buttonLabel: process.env.LOGIN_BUTTON_LABEL || 'Login', + }, + + /** + * About/Credits Section + * --------------------- + * Contains author information, version, and support links. + * Supports HTML content for flexible formatting. + */ about: { - imageUrl: '../images/mirotalk-logo.gif', + imageUrl: process.env.ABOUT_IMAGE_URL || '../images/mirotalk-logo.gif', title: `WebRTC SFU v${packageJson.version}`, html: ` - -


- Author: - - Miroslav Pejic +
+
+ ${process.env.AUTHOR_LABEL || 'Author'}: +
+ ${process.env.AUTHOR_NAME || 'Miroslav Pejic'} -

- Email: - - miroslav.pejic.85@gmail.com +
+ ${process.env.EMAIL_LABEL || 'Email'}: +
+ ${process.env.CONTACT_EMAIL || 'miroslav.pejic.85@gmail.com'} -


- © 2025 MiroTalk SFU, all rights reserved + + © ${new Date().getFullYear()} ${process.env.COPYRIGHT_TEXT || 'MiroTalk SFU, all rights reserved'} +
- `, + `, + }, + + /** + * Widget Configuration + * -------------------- + * Controls the appearance and behavior of the support widget. + * Supports dynamic configuration via environment variables. + */ + widget: { + enabled: process.env.WIDGET_ENABLED === 'true', + roomId: process.env.WIDGET_ROOM_ID || 'support-room', + theme: process.env.WIDGET_THEME || 'dark', + widgetState: process.env.WIDGET_STATE || 'minimized', + widgetType: process.env.WIDGET_TYPE || 'support', + supportWidget: { + position: process.env.WIDGET_SUPPORT_POSITION || 'top-right', + expertImages: process.env.WIDGET_SUPPORT_EXPERT_IMAGES + ? process.env.WIDGET_SUPPORT_EXPERT_IMAGES.split(splitChar) + .map((url) => url.trim()) + .filter(Boolean) + : [ + 'https://photo.cloudron.pocketsolution.net/uploads/original/95/7d/a5f7f7a2c89a5fee7affda5f013c.jpeg', + ], + buttons: { + audio: process.env.WIDGET_SUPPORT_BUTTON_AUDIO !== 'false', + video: process.env.WIDGET_SUPPORT_BUTTON_VIDEO !== 'false', + screen: process.env.WIDGET_SUPPORT_BUTTON_SCREEN !== 'false', + chat: process.env.WIDGET_SUPPORT_BUTTON_CHAT !== 'false', + join: process.env.WIDGET_SUPPORT_BUTTON_JOIN !== 'false', + }, + checkOnlineStatus: process.env.WIDGET_SUPPORT_CHECK_ONLINE_STATUS === 'true', + isOnline: process.env.WIDGET_SUPPORT_IS_ONLINE !== 'false', + customMessages: { + heading: process.env.WIDGET_SUPPORT_HEADING || 'Need Help?', + subheading: + process.env.WIDGET_SUPPORT_SUBHEADING || 'Get instant support from our expert team!', + connectText: process.env.WIDGET_SUPPORT_CONNECT_TEXT || 'connect in < 5 seconds', + onlineText: process.env.WIDGET_SUPPORT_ONLINE_TEXT || 'We are online', + offlineText: process.env.WIDGET_SUPPORT_OFFLINE_TEXT || 'We are offline', + poweredBy: process.env.WIDGET_SUPPORT_POWERED_BY || 'Powered by MiroTalk SFU', + }, + }, + alert: { + enabled: process.env.WIDGET_ALERT_ENABLED === 'true', + type: process.env.WIDGET_ALERT_TYPE || 'email', + }, }, //... }, - /* - Toggle the visibility of specific HTML elements within the room - */ + + /** + * UI Button Configuration + * --------------------- + * Organized by component/functionality area + */ buttons: { + // Popup Configuration + popup: { + shareRoomPopup: process.env.SHOW_SHARE_ROOM_POPUP !== 'false', + shareRoomQrOnHover: process.env.SHOW_SHARE_ROOM_QR_ON_HOVER !== 'false', + }, + // Main control buttons visible in the UI main: { - shareButton: true, // presenter - hideMeButton: true, - startAudioButton: true, - startVideoButton: true, - startScreenButton: true, - swapCameraButton: true, - chatButton: true, - pollButton: true, - editorButton: true, - raiseHandButton: true, - transcriptionButton: true, - whiteboardButton: true, - documentPiPButton: true, - snapshotRoomButton: true, - emojiRoomButton: true, - settingsButton: true, - aboutButton: true, - exitButton: true, + shareButton: process.env.SHOW_SHARE_BUTTON !== 'false', + hideMeButton: process.env.SHOW_HIDE_ME !== 'false', + fullScreenButton: process.env.SHOW_FULLSCREEN_BUTTON !== 'false', + startAudioButton: process.env.SHOW_AUDIO_BUTTON !== 'false', + startVideoButton: process.env.SHOW_VIDEO_BUTTON !== 'false', + startScreenButton: process.env.SHOW_SCREEN_BUTTON !== 'false', + swapCameraButton: process.env.SHOW_SWAP_CAMERA !== 'false', + chatButton: process.env.SHOW_CHAT_BUTTON !== 'false', + participantsButton: process.env.SHOW_PARTICIPANTS_BUTTON !== 'false', + pollButton: process.env.SHOW_POLL_BUTTON !== 'false', + editorButton: process.env.SHOW_EDITOR_BUTTON !== 'false', + raiseHandButton: process.env.SHOW_RAISE_HAND !== 'false', + transcriptionButton: process.env.SHOW_TRANSCRIPTION !== 'false', + whiteboardButton: process.env.SHOW_WHITEBOARD !== 'false', + documentPiPButton: process.env.SHOW_DOCUMENT_PIP !== 'false', + snapshotRoomButton: process.env.SHOW_SNAPSHOT !== 'false', + emojiRoomButton: process.env.SHOW_EMOJI !== 'false', + settingsButton: process.env.SHOW_SETTINGS !== 'false', + aboutButton: process.env.SHOW_ABOUT !== 'false', + exitButton: process.env.SHOW_EXIT_BUTTON !== 'false', + extraButton: process.env.SHOW_EXTRA_BUTTON !== 'false', }, + // Settings panel buttons and options settings: { - fileSharing: true, - lockRoomButton: true, // presenter - unlockRoomButton: true, // presenter - broadcastingButton: true, // presenter - lobbyButton: true, // presenter - sendEmailInvitation: true, // presenter - micOptionsButton: true, // presenter - tabRTMPStreamingBtn: true, // presenter - tabModerator: true, // presenter - tabRecording: true, - host_only_recording: true, // presenter - pushToTalk: true, - keyboardShortcuts: true, - virtualBackground: true, + activeRooms: process.env.SHOW_ROOMS !== 'false', + fileSharing: process.env.ENABLE_FILE_SHARING !== 'false', + lockRoomButton: process.env.SHOW_LOCK_ROOM !== 'false', + unlockRoomButton: process.env.SHOW_UNLOCK_ROOM !== 'false', + broadcastingButton: process.env.SHOW_BROADCASTING !== 'false', + lobbyButton: process.env.SHOW_LOBBY !== 'false', + sendEmailInvitation: process.env.SHOW_EMAIL_INVITE !== 'false', + micOptionsButton: process.env.SHOW_MIC_OPTIONS !== 'false', + tabRTMPStreamingBtn: process.env.SHOW_RTMP_TAB !== 'false', + tabNotificationsBtn: process.env.SHOW_NOTIFICATIONS_TAB !== 'false', + tabModerator: process.env.SHOW_MODERATOR_TAB !== 'false', + tabRecording: process.env.SHOW_RECORDING_TAB !== 'false', + host_only_recording: process.env.HOST_ONLY_RECORDING !== 'false', + pushToTalk: process.env.ENABLE_PUSH_TO_TALK !== 'false', + keyboardShortcuts: process.env.SHOW_KEYBOARD_SHORTCUTS !== 'false', + virtualBackground: process.env.SHOW_VIRTUAL_BACKGROUND !== 'false', + customNoiseSuppression: process.env.CUSTOM_NOISE_SUPPRESSION_ENABLED !== 'false', }, + + // Video controls for producer (local user) producerVideo: { - videoPictureInPicture: true, - videoMirrorButton: true, - fullScreenButton: true, - snapShotButton: true, - muteAudioButton: true, - videoPrivacyButton: true, - audioVolumeInput: true, + videoPictureInPicture: process.env.ENABLE_PIP !== 'false', + videoMirrorButton: process.env.SHOW_MIRROR_BUTTON !== 'false', + fullScreenButton: process.env.SHOW_FULLSCREEN !== 'false', + snapShotButton: process.env.SHOW_SNAPSHOT_BUTTON !== 'false', + focusVideoButton: process.env.SHOW_FOCUS_BUTTON !== 'false', + muteAudioButton: process.env.SHOW_MUTE_AUDIO !== 'false', + videoPrivacyButton: process.env.SHOW_PRIVACY_TOGGLE !== 'false', + audioVolumeInput: process.env.SHOW_VOLUME_CONTROL !== 'false', + drawingButton: process.env.SHOW_DRAWING_BUTTON !== 'false', }, + + // Video controls for consumer (remote users) consumerVideo: { - videoPictureInPicture: true, - videoMirrorButton: true, - fullScreenButton: true, - snapShotButton: true, - focusVideoButton: true, - sendMessageButton: true, - sendFileButton: true, - sendVideoButton: true, - muteVideoButton: true, - muteAudioButton: true, - audioVolumeInput: true, - geolocationButton: true, // Presenter - banButton: true, // presenter - ejectButton: true, // presenter + videoPictureInPicture: process.env.ENABLE_PIP !== 'false', + videoMirrorButton: process.env.SHOW_MIRROR_BUTTON !== 'false', + fullScreenButton: process.env.SHOW_FULLSCREEN !== 'false', + snapShotButton: process.env.SHOW_SNAPSHOT_BUTTON !== 'false', + focusVideoButton: process.env.SHOW_FOCUS_BUTTON !== 'false', + sendMessageButton: process.env.SHOW_SEND_MESSAGE !== 'false', + sendFileButton: process.env.SHOW_SEND_FILE !== 'false', + sendVideoButton: process.env.SHOW_SEND_VIDEO !== 'false', + muteVideoButton: process.env.SHOW_MUTE_VIDEO !== 'false', + muteAudioButton: process.env.SHOW_MUTE_AUDIO !== 'false', + audioVolumeInput: process.env.SHOW_VOLUME_CONTROL !== 'false', + geolocationButton: process.env.SHOW_GEO_LOCATION !== 'false', + banButton: process.env.SHOW_BAN_BUTTON !== 'false', + ejectButton: process.env.SHOW_EJECT_BUTTON !== 'false', + drawingButton: process.env.SHOW_DRAWING_BUTTON !== 'false', }, + + // Controls when video is off videoOff: { - sendMessageButton: true, - sendFileButton: true, - sendVideoButton: true, - muteAudioButton: true, - audioVolumeInput: true, - geolocationButton: true, // Presenter - banButton: true, // presenter - ejectButton: true, // presenter + sendMessageButton: process.env.SHOW_SEND_MESSAGE !== 'false', + sendFileButton: process.env.SHOW_SEND_FILE !== 'false', + sendVideoButton: process.env.SHOW_SEND_VIDEO !== 'false', + muteAudioButton: process.env.SHOW_MUTE_AUDIO !== 'false', + audioVolumeInput: process.env.SHOW_VOLUME_CONTROL !== 'false', + geolocationButton: process.env.SHOW_GEO_LOCATION !== 'false', + banButton: process.env.SHOW_BAN_BUTTON !== 'false', + ejectButton: process.env.SHOW_EJECT_BUTTON !== 'false', }, + + // Chat interface controls chat: { - chatPinButton: true, - chatMaxButton: true, - chatSaveButton: true, - chatEmojiButton: true, - chatMarkdownButton: true, - chatSpeechStartButton: true, - chatGPT: true, + chatPinButton: process.env.SHOW_CHAT_PIN !== 'false', + chatMaxButton: process.env.SHOW_CHAT_MAXIMIZE !== 'false', + chatSaveButton: process.env.SHOW_CHAT_SAVE !== 'false', + chatEmojiButton: process.env.SHOW_CHAT_EMOJI !== 'false', + chatMarkdownButton: process.env.SHOW_CHAT_MARKDOWN !== 'false', + chatSpeechStartButton: process.env.SHOW_CHAT_SPEECH !== 'false', + chatGPT: process.env.ENABLE_CHAT_GPT !== 'false', + deepSeek: process.env.ENABLE_DEEP_SEEK !== 'false', }, + + // Poll interface controls poll: { - pollPinButton: true, - pollMaxButton: true, - pollSaveButton: true, + pollPinButton: process.env.SHOW_POLL_PIN !== 'false', + pollMaxButton: process.env.SHOW_POLL_MAXIMIZE !== 'false', + pollSaveButton: process.env.SHOW_POLL_SAVE !== 'false', }, + + // Participants list controls participantsList: { - saveInfoButton: true, // presenter - sendFileAllButton: true, // presenter - ejectAllButton: true, // presenter - sendFileButton: true, // presenter & guests - geoLocationButton: true, // presenter - banButton: true, // presenter - ejectButton: true, // presenter + saveInfoButton: process.env.SHOW_SAVE_INFO !== 'false', + sendFileAllButton: process.env.SHOW_SEND_FILE_ALL !== 'false', + ejectAllButton: process.env.SHOW_EJECT_ALL !== 'false', + sendFileButton: process.env.SHOW_SEND_FILE !== 'false', + geoLocationButton: process.env.SHOW_GEO_LOCATION !== 'false', + banButton: process.env.SHOW_BAN_BUTTON !== 'false', + ejectButton: process.env.SHOW_EJECT_BUTTON !== 'false', }, + + // Whiteboard controls whiteboard: { - whiteboardLockButton: true, // presenter + whiteboardLockButton: process.env.SHOW_WB_LOCK !== 'false', }, - //... }, }, - stats: { - /* - Umami: https://github.com/umami-software/umami - We use our Self-hosted Umami to track aggregated usage statistics in order to improve our service. - */ - enabled: true, - src: 'https://stats.mirotalk.com/script.js', - id: '41d26670-f275-45bb-af82-3ce91fe57756', + + // ============================================== + // 8. Feature Flags + // ============================================== + + features: { + /** + * Survey Configuration (QuestionPro) + * ================================= + * Settings for user feedback and survey integration + * + * Setup Instructions: + * ------------------ + * 1. Sign up at https://www.questionpro.com/ + * 2. Create survey: + * - Use template or custom questions + * - Configure survey logic and branching + * 3. Get survey URL: + * - Publish survey + * - Copy "Collect Responses" link + */ + survey: { + enabled: process.env.SURVEY_ENABLED === 'true', + url: process.env.SURVEY_URL || '', + }, + + /** + * Post-Call Redirect + * --------------------- + * - enabled: Redirect after call ends + * - url: Redirect destination URL + */ + redirect: { + enabled: process.env.REDIRECT_ENABLED === 'true', + url: process.env.REDIRECT_URL || '', + }, + + /** + * Usage Statistics Configuration (Umami) + * ===================================== + * Privacy-focused analytics tracking for service improvement + * + * Setup Instructions: + * ------------------ + * 1. Self-host Umami or use cloud version: + * - GitHub: https://github.com/umami-software/umami + * - Official Docs: https://umami.is/docs + * 2. Create website entry in Umami dashboard + * 3. Obtain tracking script URL and website ID + * + * Privacy & Security: + * ------------------ + * - No cookies used (GDPR compliant) + * - No persistent user tracking + * - All data aggregated and anonymized + * - Self-hosted option keeps data in your infrastructure + * + * Core Settings: + * ------------- + * - enabled : Enable/disable analytics [true/false] (default: true) + * - src : Umami tracking script URL + * - id : Your website ID from Umami + */ + stats: { + enabled: process.env.STATS_ENABLED !== 'false', + src: process.env.STATS_SRC || 'https://stats.mirotalk.com/script.js', + id: process.env.STATS_ID || '41d26670-f275-45bb-af82-3ce91fe57756', + }, + }, + + /** + * Moderation Configuration + * ======================= + * Controls global moderation features. + * + * Core Settings: + * -------------- + * - room.maxParticipants: Maximum number of participants allowed per room. + * - lobby: Enable/disable lobby feature for pre-approval of participants. + * Adjust to limit room size and manage server load. + */ + moderation: { + room: { + maxParticipants: parseInt(process.env.ROOM_MAX_PARTICIPANTS) || 1000, // Maximum participants per room + lobby: process.env.ROOM_LOBBY === 'true', // Enable lobby feature + }, }, + + // ============================================== + // 9. Mediasoup (WebRTC) Configuration + // ============================================== + + /** + * Mediasoup Integration Resources + * ============================== + * Core WebRTC components powering MiroTalk SFU + * + * Essential Links: + * --------------- + * - 🌐 Website : https://mediasoup.org + * - 💬 Forum : https://mediasoup.discourse.group + * + * 📚 Documentation: + * ---------------- + * - Client API : https://mediasoup.org/documentation/v3/mediasoup-client/api/ + * - Server API : https://mediasoup.org/documentation/v3/mediasoup/api/ + * - Protocols : https://mediasoup.org/documentation/v3/mediasoup/rtp-parameters-and-capabilities/ + * + * 🔧 Key Components: + * ----------------- + * - Router : Manages RTP streams + * - Transport : Network connection handler + * - Producer : Media sender + * - Consumer : Media receiver + * + * Mediasoup Configuration + * ----------------------- + * This configuration defines settings for mediasoup workers, routers, + * WebRTC servers, and transports. These settings control how the SFU + * (Selective Forwarding Unit) handles media processing and networking. + */ mediasoup: { - // Worker settings - numWorkers: numWorkers, + /** + * Worker Configuration + * -------------------- + * Workers are separate processes that handle media processing. + * Multiple workers can run in parallel for load balancing. + */ worker: { - rtcMinPort: rtcMinPort, - rtcMaxPort: rtcMaxPort, - disableLiburing: false, // https://github.com/axboe/liburing - logLevel: 'error', - logTags: ['info', 'ice', 'dtls', 'rtp', 'srtp', 'rtcp', 'rtx', 'bwe', 'score', 'simulcast', 'svc', 'sctp'], + rtcMinPort: RTC_MIN_PORT, // Minimum UDP/TCP port for ICE, DTLS, RTP + rtcMaxPort: RTC_MAX_PORT, // Maximum UDP/TCP port for ICE, DTLS, RTP + + // Disable Linux io_uring for certain operations (false = use if available) + disableLiburing: false, + + // Logging level (error, warn, debug, etc.) + logLevel: process.env.MEDIASOUP_LOG_LEVEL || 'error', + + // Detailed logging for specific components: + logTags: [ + 'info', // General information + 'ice', // ICE (Interactive Connectivity Establishment) events + 'dtls', // DTLS handshake and encryption + 'rtp', // RTP packet flow + 'srtp', // Secure RTP encryption + 'rtcp', // RTCP control protocol + 'rtx', // Retransmissions + 'bwe', // Bandwidth estimation + 'score', // Network score calculations + 'simulcast', // Simulcast layers + 'svc', // Scalable Video Coding + 'sctp', // SCTP data channels + ], }, - // Router settings + numWorkers: NUM_WORKERS, // Number of mediasoup worker processes to create + + /** + * Router Configuration + * -------------------- + * Routers manage media streams and define what codecs are supported. + * Each mediasoup worker can host multiple routers. + */ router: { - audioLevelObserverEnabled: true, - activeSpeakerObserverEnabled: false, + // Enable audio level monitoring (for detecting who is speaking) + audioLevelObserverEnabled: process.env.MEDIASOUP_ROUTER_AUDIO_LEVEL_OBSERVER_ENABLED !== 'false', + + // Disable active speaker detection (uses more CPU) + activeSpeakerObserverEnabled: process.env.MEDIASOUP_ROUTER_ACTIVE_SPEAKER_OBSERVER_ENABLED === 'true', + + /** + * Supported Media Codecs + * ---------------------- + * Defines what codecs the SFU can receive and forward. + * Order matters - first is preferred during negotiation. + */ mediaCodecs: [ + // Opus audio codec (standard for WebRTC) { kind: 'audio', mimeType: 'audio/opus', - clockRate: 48000, - channels: 2, + clockRate: 48000, // Standard sample rate for WebRTC + channels: 2, // Stereo audio }, + + // VP8 video codec (widely supported, good for compatibility) { kind: 'video', mimeType: 'video/VP8', - clockRate: 90000, + clockRate: 90000, // Standard video clock rate parameters: { - 'x-google-start-bitrate': 1000, + 'x-google-start-bitrate': 1000, // Initial bitrate (kbps) }, }, + + // VP9 video codec (better compression than VP8) + // Profile 0: Most widely supported VP9 profile { kind: 'video', mimeType: 'video/VP9', clockRate: 90000, parameters: { - 'profile-id': 0, // Default profile for wider compatibility + 'profile-id': 0, // Baseline profile 'x-google-start-bitrate': 1000, }, }, + + // VP9 Profile 2: Supports HDR and 10/12-bit color { kind: 'video', mimeType: 'video/VP9', clockRate: 90000, parameters: { - 'profile-id': 2, // High profile for modern devices + 'profile-id': 2, // Advanced profile 'x-google-start-bitrate': 1000, }, }, + + // H.264 Baseline profile (widest hardware support) { kind: 'video', mimeType: 'video/h264', clockRate: 90000, parameters: { - 'packetization-mode': 1, - 'profile-level-id': '42e01f', // Baseline profile for compatibility - 'level-asymmetry-allowed': 1, + 'packetization-mode': 1, // Required for WebRTC + 'profile-level-id': '42e01f', // Baseline 3.1 + 'level-asymmetry-allowed': 1, // Allows different levels 'x-google-start-bitrate': 1000, }, }, + + // H.264 Main profile (better compression than Baseline) { kind: 'video', mimeType: 'video/h264', clockRate: 90000, parameters: { 'packetization-mode': 1, - 'profile-level-id': '4d0032', // High profile for modern devices + 'profile-level-id': '4d0032', // Main 4.0 'level-asymmetry-allowed': 1, 'x-google-start-bitrate': 1000, }, }, ], }, - // WebRtcServerOptions - webRtcServerActive: false, + + /** + * WebRTC Server Configuration + * --------------------------- + * WebRTC servers handle ICE (connection establishment) and DTLS (encryption). + * Can be disabled if using plain WebRtcTransport instead. + * + * Best used when: + * - Running in controlled environments with fixed IPs + * - Need to minimize port usage across workers + * - Using StatefulSets/DaemonSets in Kubernetes + * + * Kubernetes considerations: + * - Requires stable network identity (use StatefulSet) + * - Needs NodePort/LoadBalancer with externalTrafficPolicy: Local + * - Port ranges must be carefully allocated to avoid conflicts + * + * Optional Config: + * - https://mediasoup.discourse.group/t/mediasoup-3-17-0-released/6805 + */ + webRtcServerActive: process.env.SFU_SERVER === 'true', // Enable if SFU_SERVER=true webRtcServerOptions: { + // Network interfaces and ports for ICE candidates listenInfos: [ - // { protocol: 'udp', ip: '0.0.0.0', announcedAddress: IPv4, port: rtcMinPort }, - // { protocol: 'tcp', ip: '0.0.0.0', announcedAddress: IPv4, port: rtcMinPort }, + /** + * UDP Configuration + * Preferred for media transport (lower latency) + * Kubernetes implications: + * - Each Pod needs unique ports if sharing host network + * - Consider using hostPort when not using LoadBalancer + */ { protocol: 'udp', - ip: '0.0.0.0', - announcedAddress: IPv4, - portRange: { min: rtcMinPort, max: rtcMinPort + numWorkers }, + ip: LISTEN_IP, // Local IP to bind to + announcedAddress: IPv4, // Public IP sent to clients + portRange: { + min: RTC_MIN_PORT, + max: RTC_MIN_PORT + NUM_WORKERS, // Port range per worker + }, }, + /** + * TCP Configuration + * Fallback for restrictive networks (higher latency) + * Kubernetes implications: + * - Helps with networks blocking UDP + * - May require separate Service definition in k8s + */ { protocol: 'tcp', - ip: '0.0.0.0', + ip: LISTEN_IP, announcedAddress: IPv4, - portRange: { min: rtcMinPort, max: rtcMinPort + numWorkers }, + portRange: { + min: RTC_MIN_PORT, + max: RTC_MIN_PORT + NUM_WORKERS, + }, }, ], }, - // WebRtcTransportOptions + + /** + * WebRTC Transport Configuration + * ------------------------------ + * Transports handle the actual media flow between clients and the SFU. + * These settings affect bandwidth management and network behavior. + * + * Preferred when: + * - Running in cloud environments with auto-scaling + * - Need dynamic port allocation + * - Kubernetes Pods are ephemeral + * + * Kubernetes considerations: + * - Requires wide port range exposure (50000-60000 typical) + * - Works better with ClusterIP Services + * - More resilient to Pod restarts + */ webRtcTransport: { + // Network interfaces for media transmission listenInfos: [ - // { protocol: 'udp', ip: IPv4, portRange: { min: rtcMinPort, max: rtcMaxPort } }, - // { protocol: 'tcp', ip: IPv4, portRange: { min: rtcMinPort, max: rtcMaxPort } }, + /** + * UDP Transport Settings + * Kubernetes implications: + * - Needs hostNetwork or privileged Pod for port access + * - Consider port range size based on expected scale + */ { protocol: 'udp', - ip: '0.0.0.0', + ip: LISTEN_IP, announcedAddress: IPv4, - portRange: { min: rtcMinPort, max: rtcMaxPort }, + portRange: { + min: RTC_MIN_PORT, + max: RTC_MAX_PORT, // Wider range than WebRtcServer + }, }, + /** + * TCP Transport Settings + * Kubernetes implications: + * - Less efficient but more compatible + * - May require different Service configuration + */ { protocol: 'tcp', - ip: '0.0.0.0', + ip: LISTEN_IP, announcedAddress: IPv4, - portRange: { min: rtcMinPort, max: rtcMaxPort }, + portRange: { + min: RTC_MIN_PORT, + max: RTC_MAX_PORT, + }, }, ], - initialAvailableOutgoingBitrate: 1000000, - minimumAvailableOutgoingBitrate: 600000, - maxSctpMessageSize: 262144, - maxIncomingBitrate: 1500000, + + iceConsentTimeout: 35, // Timeout for ICE consent (seconds) + + /** + * Bandwidth Control Settings + * Kubernetes implications: + * - These values should be tuned based on Node resources + * - Consider network plugin overhead (Calico, Cilium etc.) + */ + initialAvailableOutgoingBitrate: 2500000, // 2.5 Mbps initial bitrate + minimumAvailableOutgoingBitrate: 1000000, // 1 Mbps minimum guaranteed + maxIncomingBitrate: 3000000, // 3 Mbps max per producer + + /** + * Data Channel Settings + * Kubernetes implications: + * - Affects memory allocation per transport + * - Larger sizes may require Pod resource adjustments + */ + maxSctpMessageSize: 262144, // 256 KB max message size for data channels }, }, }; + +// ============================================== +// Helper Functions +// ============================================== + +/** + * Get IPv4 Address + * ---------------- + * - Prioritizes ANNOUNCED_IP if set + * - Falls back to local IP detection + */ +function getIPv4() { + if (ANNOUNCED_IP) return ANNOUNCED_IP; + + switch (ENVIRONMENT) { + case 'development': + return IS_DOCKER ? '127.0.0.1' : getLocalIPv4(); + case 'production': + return ANNOUNCED_IP; + default: + return getLocalIPv4(); + } +} + +/** + * Detect Local IPv4 Address + * ------------------------- + * - Handles different OS network interfaces + * - Filters out virtual/docker interfaces + */ +function getLocalIPv4() { + const ifaces = os.networkInterfaces(); + const platform = os.platform(); + + const PRIORITY_CONFIG = { + win32: [{ name: 'Ethernet' }, { name: 'Wi-Fi' }, { name: 'Local Area Connection' }], + darwin: [{ name: 'en0' }, { name: 'en1' }], + linux: [{ name: 'eth0' }, { name: 'wlan0' }], + }; + + const VIRTUAL_INTERFACES = { + all: ['docker', 'veth', 'tun', 'lo'], + win32: ['Virtual', 'vEthernet', 'Teredo', 'Bluetooth'], + darwin: ['awdl', 'bridge', 'utun'], + linux: ['virbr', 'kube', 'cni'], + }; + + const platformPriorities = PRIORITY_CONFIG[platform] || []; + const virtualExcludes = [...VIRTUAL_INTERFACES.all, ...(VIRTUAL_INTERFACES[platform] || [])]; + + // Check priority interfaces first + for (const { name: ifName } of platformPriorities) { + const matchingIfaces = platform === 'win32' ? Object.keys(ifaces).filter((k) => k.includes(ifName)) : [ifName]; + for (const interfaceName of matchingIfaces) { + const addr = findValidAddress(ifaces[interfaceName]); + if (addr) return addr; + } + } + + // Fallback to scanning all non-virtual interfaces + const fallbackAddress = scanAllInterfaces(ifaces, virtualExcludes); + if (fallbackAddress) return fallbackAddress; + + return '0.0.0.0'; +} + +/** + * Scan All Network Interfaces + * --------------------------- + * - Checks all interfaces excluding virtual ones + */ +function scanAllInterfaces(ifaces, excludes) { + for (const [name, addresses] of Object.entries(ifaces)) { + if (excludes.some((ex) => name.toLowerCase().includes(ex.toLowerCase()))) { + continue; + } + const addr = findValidAddress(addresses); + if (addr) return addr; + } + return null; +} + +/** + * Find Valid Network Address + * -------------------------- + * - Filters out internal and link-local addresses + */ +function findValidAddress(addresses) { + return addresses?.find((addr) => addr.family === 'IPv4' && !addr.internal && !addr.address.startsWith('169.254.')) + ?.address; +} + +/** + * Get FFmpeg Path + * --------------- + * - Checks common installation locations + * - Platform-specific paths + */ +function getFFmpegPath(platform) { + const paths = { + darwin: ['/usr/local/bin/ffmpeg', '/opt/homebrew/bin/ffmpeg'], + linux: ['/usr/bin/ffmpeg', '/usr/local/bin/ffmpeg'], + win32: ['C:\\ffmpeg\\bin\\ffmpeg.exe', 'C:\\Program Files\\ffmpeg\\bin\\ffmpeg.exe'], + }; + + const platformPaths = paths[platform] || ['/usr/bin/ffmpeg']; + + for (const path of platformPaths) { + try { + fs.accessSync(path); + return path; + } catch (e) { + continue; + } + } + + return platformPaths[0]; +} diff --git a/app/src/lib/nodemailer.js b/app/src/lib/nodemailer.js index d0c28a96b..e9e750f05 100644 --- a/app/src/lib/nodemailer.js +++ b/app/src/lib/nodemailer.js @@ -1,34 +1,45 @@ 'use strict'; const nodemailer = require('nodemailer'); +const { isValidEmail } = require('../Validator'); const config = require('../config'); const Logger = require('../Logger'); const log = new Logger('NodeMailer'); +const APP_NAME = config.ui.brand.app.name || 'MiroTalk SFU'; + // #################################################### // EMAIL CONFIG // #################################################### -const EMAIL_HOST = config.email ? config.email.host : false; -const EMAIL_PORT = config.email ? config.email.port : false; -const EMAIL_USERNAME = config.email ? config.email.username : false; -const EMAIL_PASSWORD = config.email ? config.email.password : false; -const EMAIL_SEND_TO = config.email ? config.email.sendTo : false; -const EMAIL_ALERT = config.email ? config.email.alert : false; +const emailConfig = config.integrations?.email || {}; +const EMAIL_ALERT = emailConfig.alert || false; +const EMAIL_NOTIFY = emailConfig.notify || false; +const EMAIL_HOST = emailConfig.host || false; +const EMAIL_PORT = emailConfig.port || false; +const EMAIL_USERNAME = emailConfig.username || false; +const EMAIL_PASSWORD = emailConfig.password || false; +const EMAIL_FROM = emailConfig.from || emailConfig.username; +const EMAIL_SEND_TO = emailConfig.sendTo || false; -if (EMAIL_ALERT && EMAIL_HOST && EMAIL_PORT && EMAIL_USERNAME && EMAIL_PASSWORD && EMAIL_SEND_TO) { +if ((EMAIL_ALERT || EMAIL_NOTIFY) && EMAIL_HOST && EMAIL_PORT && EMAIL_USERNAME && EMAIL_PASSWORD && EMAIL_SEND_TO) { log.info('Email', { alert: EMAIL_ALERT, + notify: EMAIL_NOTIFY, host: EMAIL_HOST, port: EMAIL_PORT, username: EMAIL_USERNAME, password: EMAIL_PASSWORD, + from: EMAIL_FROM, + to: EMAIL_SEND_TO, }); } +const IS_TLS_PORT = EMAIL_PORT === 465; const transport = nodemailer.createTransport({ host: EMAIL_HOST, port: EMAIL_PORT, + secure: IS_TLS_PORT, auth: { user: EMAIL_USERNAME, pass: EMAIL_PASSWORD, @@ -42,9 +53,45 @@ const transport = nodemailer.createTransport({ function sendEmailAlert(event, data) { if (!EMAIL_ALERT || !EMAIL_HOST || !EMAIL_PORT || !EMAIL_USERNAME || !EMAIL_PASSWORD || !EMAIL_SEND_TO) return; - log.info('sendEMailAlert', { + log.debug('sendEMailAlert', { + event: event, + data: data, + }); + + let subject = false; + let body = false; + + switch (event) { + case 'join': + subject = getJoinRoomSubject(data); + body = getJoinRoomBody(data); + break; + case 'widget': + subject = getWidgetRoomSubject(data); + body = getWidgetRoomBody(data); + break; + case 'alert': + subject = getAlertSubject(data); + body = getAlertBody(data); + break; + default: + break; + } + + if (subject && body) { + sendEmail(subject, body); + return true; + } + return false; +} + +function sendEmailNotifications(event, data, notifications) { + if (!EMAIL_NOTIFY || !EMAIL_HOST || !EMAIL_PORT || !EMAIL_USERNAME || !EMAIL_PASSWORD) return; + + log.debug('sendEmailNotifications', { event: event, data: data, + notifications: notifications, }); let subject = false; @@ -55,19 +102,26 @@ function sendEmailAlert(event, data) { subject = getJoinRoomSubject(data); body = getJoinRoomBody(data); break; - // ... + // left... default: break; } - if (subject && body) sendEmail(subject, body); + const emailSendTo = notifications?.mode?.email; + + if (subject && body && isValidEmail(emailSendTo)) { + sendEmail(subject, body, emailSendTo); + return true; + } + log.error('sendEmailNotifications: Invalid email', { email: emailTo }); + return false; } -function sendEmail(subject, body) { +function sendEmail(subject, body, emailSendTo = false) { transport .sendMail({ - from: EMAIL_USERNAME, - to: EMAIL_SEND_TO, + from: EMAIL_FROM, + to: emailSendTo ? emailSendTo : EMAIL_SEND_TO, subject: subject, html: body, }) @@ -80,7 +134,7 @@ function sendEmail(subject, body) { function getJoinRoomSubject(data) { const { room_id } = data; - return `MiroTalk SFU - New user Join to Room ${room_id}`; + return `${APP_NAME} - New user Join to Room ${room_id}`; } function getJoinRoomBody(data) { const { peer_name, room_id, domain, os, browser } = data; @@ -137,6 +191,63 @@ function getJoinRoomBody(data) { `; } +// ========== +// Widget +// ========== + +function getWidgetRoomSubject(data) { + const { room_id } = data; + return `${APP_NAME} WIDGET - New user Wait for expert assistance in Room ${room_id}`; +} + +function getWidgetRoomBody(data) { + return getJoinRoomBody(data); +} + +// ========== +// Alert +// ========== + +function getAlertSubject(data) { + const { subject } = data; + return subject || `${APP_NAME} - Alert`; +} + +function getAlertBody(data) { + const { body } = data; + + const currentDataTime = getCurrentDataTime(); + + return ` +

🚨 Alert Notification

+ + + + + + + + + + +
⚠️ Alert${body}
🕒 Date, Time${currentDataTime}
+ `; +} + // #################################################### // UTILITY // #################################################### @@ -149,4 +260,5 @@ function getCurrentDataTime() { module.exports = { sendEmailAlert, + sendEmailNotifications, }; diff --git a/app/src/middleware/IpWhitelist.js b/app/src/middleware/IpWhitelist.js index 770e8b17f..d32f8f899 100644 --- a/app/src/middleware/IpWhitelist.js +++ b/app/src/middleware/IpWhitelist.js @@ -4,11 +4,10 @@ const config = require('../config'); const Logger = require('../Logger'); const log = new Logger('RestrictAccessByIP'); -const IpWhitelistEnabled = config.middleware ? config.middleware.IpWhitelist.enabled : false; -const allowedIPs = config.middleware ? config.middleware.IpWhitelist.allowed : []; +const { enabled = false, allowedIPs = [] } = config?.security?.middleware?.IpWhitelist || {}; const restrictAccessByIP = (req, res, next) => { - if (!IpWhitelistEnabled) return next(); + if (!enabled) return next(); // const clientIP = req.headers['x-forwarded-for'] || req.headers['X-Forwarded-For'] || req.socket.remoteAddress || req.ip; diff --git a/cloud/README.md b/cloud/README.md index 1385cf952..4c1077438 100644 --- a/cloud/README.md +++ b/cloud/README.md @@ -16,12 +16,8 @@ npm start ## Edit config.js -In the MiroTalk SFU `app/src/config.js` file, change the endpoint to send recording chunks: - -```js -recording: { - endpoint: 'http://localhost:8080', // Change it with your Server endpoint - dir: 'rec', - enabled: true, -}, +In the MiroTalk SFU `.env` file, change the endpoint to send recording chunks: + +```bash +RECORDING_ENDPOINT=http://localhost:8080 ``` diff --git a/cloud/package-lock.json b/cloud/package-lock.json new file mode 100644 index 000000000..30a601f5e --- /dev/null +++ b/cloud/package-lock.json @@ -0,0 +1,1228 @@ +{ + "name": "cloud", + "version": "1.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "cloud", + "version": "1.0.1", + "license": "AGPLv3", + "dependencies": { + "cors": "2.8.5", + "express": "^5.1.0", + "helmet": "^8.1.0", + "sanitize-filename": "^1.6.3" + }, + "devDependencies": { + "nodemon": "^3.1.10" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/helmet": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", + "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sanitize-filename": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", + "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", + "license": "WTFPL OR ISC", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "node_modules/semver": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.0.tgz", + "integrity": "sha512-DrfFnPzblFmNrIZzg5RzHegbiRWg7KMR7btwi2yjHwx06zsUbO5g613sVwEV7FTwmzJu+Io0lJe2GJ3LxqpvBQ==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", + "license": "WTFPL", + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utf8-byte-length": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", + "license": "(WTFPL OR MIT)" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + } + } +} diff --git a/cloud/package.json b/cloud/package.json index c776a8ffc..c6ffb7db7 100644 --- a/cloud/package.json +++ b/cloud/package.json @@ -1,6 +1,6 @@ { "name": "cloud", - "version": "1.0.0", + "version": "1.0.1", "description": "Cloud server to handle MiroTalk SFU recording uploads", "main": "server.js", "scripts": { @@ -15,11 +15,11 @@ "license": "AGPLv3", "dependencies": { "cors": "2.8.5", - "express": "^4.21.2", - "helmet": "^8.0.0", + "express": "^5.1.0", + "helmet": "^8.1.0", "sanitize-filename": "^1.6.3" }, "devDependencies": { - "nodemon": "^3.1.9" + "nodemon": "^3.1.10" } } diff --git a/docker-compose.template.yml b/docker-compose.template.yml index f2b107fc4..f75035356 100644 --- a/docker-compose.template.yml +++ b/docker-compose.template.yml @@ -4,16 +4,20 @@ services: container_name: mirotalksfu hostname: mirotalksfu restart: unless-stopped + # The following ports are only required if 'network_mode: host' is NOT enabled. + # If you enable 'network_mode: host', you can comment out or remove the 'ports' section below. + # network_mode: 'host' + ports: + - '3010:3010/tcp' + - '40000-40100:40000-40100/tcp' + - '40000-40100:40000-40100/udp' volumes: - ./app/src/config.js:/src/app/src/config.js:ro - # Mandatory volume if `server.recording.enabled` is true in app/src/config.js + - ./.env:/src/.env:ro + # Mandatory volume if `RECORDING_ENABLED=true` in the .env # - ./app/rec:/src/app/rec - # Mandatory volume if `server.rtmp.enabled.fromFile` is true in app/src/config.js + # Mandatory volume if `RTMP_FROM_FILE=true` in the .env # - ./app/rtmp:/src/app/rtmp # Optional volumes for real-time updates: # - ./app/:/src/app/:ro # - ./public/:/src/public/:ro - ports: - - '3010:3010/tcp' - - '40000-40100:40000-40100/tcp' - - '40000-40100:40000-40100/udp' diff --git a/install.sh b/install.sh index 6b1b20a5b..0fcc96108 100755 --- a/install.sh +++ b/install.sh @@ -79,11 +79,11 @@ if [ "$answer" != "${answer#[Yy]}" ] ;then apt-get install -y python3.8 python3-pip - log "Install Node.js 18.x and npm" + log "Install Node.js 22.x and npm" apt install -y curl dirmngr apt-transport-https lsb-release ca-certificates - curl -fsSL https://deb.nodesource.com/setup_18.x | bash - + curl -fsSL https://deb.nodesource.com/setup_22.x | bash - apt-get install -y nodejs @@ -96,6 +96,7 @@ if [ "$answer" != "${answer#[Yy]}" ] ;then fi CONFIG=app/src/config.js +ENV=.env if ! [ -f "$CONFIG" ]; then @@ -103,6 +104,7 @@ if ! [ -f "$CONFIG" ]; then cp app/src/config.template.js $CONFIG + cp .env.template $ENV fi printf 'Use docker (y/n)? ' diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..ca2808cf3 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,12303 @@ +{ + "name": "mirotalksfu", + "version": "2.1.51", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mirotalksfu", + "version": "2.1.51", + "license": "AGPL-3.0", + "dependencies": { + "@aws-sdk/client-s3": "^3.1010.0", + "@aws-sdk/lib-storage": "^3.1010.0", + "@mattermost/client": "11.4.0", + "@ngrok/ngrok": "1.7.0", + "@sentry/node": "^10.44.0", + "async-mutex": "^0.5.0", + "axios": "^1.13.6", + "chokidar": "^5.0.0", + "colors": "1.4.0", + "compression": "1.8.1", + "cors": "2.8.6", + "crypto-js": "4.2.0", + "discord.js": "^14.25.1", + "dompurify": "^3.3.3", + "dotenv": "^17.3.1", + "express": "5.2.1", + "express-openid-connect": "^2.19.4", + "express-rate-limit": "^8.3.1", + "fluent-ffmpeg": "^2.1.3", + "he": "^1.2.0", + "helmet": "^8.1.0", + "httpolyglot": "0.1.2", + "js-yaml": "^4.1.1", + "jsdom": "^29.0.0", + "jsonwebtoken": "^9.0.3", + "mediasoup": "3.19.18", + "mediasoup-client": "3.18.7", + "mime-types": "^3.0.2", + "nodemailer": "^8.0.2", + "openai": "^6.32.0", + "qs": "6.15.0", + "sanitize-filename": "^1.6.3", + "socket.io": "4.8.3", + "swagger-ui-express": "5.0.1", + "uuid": "13.0.0" + }, + "devDependencies": { + "@babel/core": "^7.29.0", + "@babel/preset-env": "^7.29.2", + "babel-loader": "^10.1.1", + "mocha": "^11.7.5", + "node-fetch": "^3.3.2", + "nodemon": "^3.1.14", + "prettier": "3.8.1", + "proxyquire": "^2.1.3", + "should": "^13.2.3", + "sinon": "^21.0.3", + "webpack": "^5.105.4", + "webpack-cli": "^7.0.2" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.0.1.tgz", + "integrity": "sha512-2SZFvqMyvboVV1d15lMf7XiI3m7SDqXUuKaTymJYLN6dSGadqp+fVojqJlVoMlbZnlTmu3S0TLwLTJpvBMO1Aw==", + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^3.1.1", + "@csstools/css-color-parser": "^4.0.2", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.2.6" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.0.3.tgz", + "integrity": "sha512-Q6mU0Z6bfj6YvnX2k9n0JxiIwrCFN59x/nWmYQnAqP000ruX/yV+5bp/GRcF5T8ncvfwJQ7fgfP74DlpKExILA==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.7" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "license": "MIT" + }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/crc32c": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.1010.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1010.0.tgz", + "integrity": "sha512-XUqXFrn/FGLLzO5OXu9iAtt492kj9Z7Yk8b0iPFxeJoIhaa61YOgR84chOExvnjm2+JTYyGNZiVPmgnFB3jxXA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.20", + "@aws-sdk/credential-provider-node": "^3.972.21", + "@aws-sdk/middleware-bucket-endpoint": "^3.972.8", + "@aws-sdk/middleware-expect-continue": "^3.972.8", + "@aws-sdk/middleware-flexible-checksums": "^3.974.0", + "@aws-sdk/middleware-host-header": "^3.972.8", + "@aws-sdk/middleware-location-constraint": "^3.972.8", + "@aws-sdk/middleware-logger": "^3.972.8", + "@aws-sdk/middleware-recursion-detection": "^3.972.8", + "@aws-sdk/middleware-sdk-s3": "^3.972.20", + "@aws-sdk/middleware-ssec": "^3.972.8", + "@aws-sdk/middleware-user-agent": "^3.972.21", + "@aws-sdk/region-config-resolver": "^3.972.8", + "@aws-sdk/signature-v4-multi-region": "^3.996.8", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-endpoints": "^3.996.5", + "@aws-sdk/util-user-agent-browser": "^3.972.8", + "@aws-sdk/util-user-agent-node": "^3.973.7", + "@smithy/config-resolver": "^4.4.11", + "@smithy/core": "^3.23.11", + "@smithy/eventstream-serde-browser": "^4.2.12", + "@smithy/eventstream-serde-config-resolver": "^4.3.12", + "@smithy/eventstream-serde-node": "^4.2.12", + "@smithy/fetch-http-handler": "^5.3.15", + "@smithy/hash-blob-browser": "^4.2.13", + "@smithy/hash-node": "^4.2.12", + "@smithy/hash-stream-node": "^4.2.12", + "@smithy/invalid-dependency": "^4.2.12", + "@smithy/md5-js": "^4.2.12", + "@smithy/middleware-content-length": "^4.2.12", + "@smithy/middleware-endpoint": "^4.4.25", + "@smithy/middleware-retry": "^4.4.42", + "@smithy/middleware-serde": "^4.2.14", + "@smithy/middleware-stack": "^4.2.12", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/node-http-handler": "^4.4.16", + "@smithy/protocol-http": "^5.3.12", + "@smithy/smithy-client": "^4.12.5", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.41", + "@smithy/util-defaults-mode-node": "^4.2.44", + "@smithy/util-endpoints": "^3.3.3", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-retry": "^4.2.12", + "@smithy/util-stream": "^4.5.19", + "@smithy/util-utf8": "^4.2.2", + "@smithy/util-waiter": "^4.2.13", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.973.20", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.20.tgz", + "integrity": "sha512-i3GuX+lowD892F3IuJf8o6AbyDupMTdyTxQrCJGcn71ni5hTZ82L4nQhcdumxZ7XPJRJJVHS/CR3uYOIIs0PVA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/xml-builder": "^3.972.11", + "@smithy/core": "^3.23.11", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/property-provider": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/signature-v4": "^5.3.12", + "@smithy/smithy-client": "^4.12.5", + "@smithy/types": "^4.13.1", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/crc64-nvme": { + "version": "3.972.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.972.5.tgz", + "integrity": "sha512-2VbTstbjKdT+yKi8m7b3a9CiVac+pL/IY2PHJwsaGkkHmuuqkJZIErPck1h6P3T9ghQMLSdMPyW6Qp7Di5swFg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.972.18", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.18.tgz", + "integrity": "sha512-X0B8AlQY507i5DwjLByeU2Af4ARsl9Vr84koDcXCbAkplmU+1xBFWxEPrWRAoh56waBne/yJqEloSwvRf4x6XA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.20", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.972.20", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.20.tgz", + "integrity": "sha512-ey9Lelj001+oOfrbKmS6R2CJAiXX7QKY4Vj9VJv6L2eE6/VjD8DocHIoYqztTm70xDLR4E1jYPTKfIui+eRNDA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.20", + "@aws-sdk/types": "^3.973.6", + "@smithy/fetch-http-handler": "^5.3.15", + "@smithy/node-http-handler": "^4.4.16", + "@smithy/property-provider": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/smithy-client": "^4.12.5", + "@smithy/types": "^4.13.1", + "@smithy/util-stream": "^4.5.19", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.972.20", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.20.tgz", + "integrity": "sha512-5flXSnKHMloObNF+9N0cupKegnH1Z37cdVlpETVgx8/rAhCe+VNlkcZH3HDg2SDn9bI765S+rhNPXGDJJPfbtA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.20", + "@aws-sdk/credential-provider-env": "^3.972.18", + "@aws-sdk/credential-provider-http": "^3.972.20", + "@aws-sdk/credential-provider-login": "^3.972.20", + "@aws-sdk/credential-provider-process": "^3.972.18", + "@aws-sdk/credential-provider-sso": "^3.972.20", + "@aws-sdk/credential-provider-web-identity": "^3.972.20", + "@aws-sdk/nested-clients": "^3.996.10", + "@aws-sdk/types": "^3.973.6", + "@smithy/credential-provider-imds": "^4.2.12", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.972.20", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.20.tgz", + "integrity": "sha512-gEWo54nfqp2jABMu6HNsjVC4hDLpg9HC8IKSJnp0kqWtxIJYHTmiLSsIfI4ScQjxEwpB+jOOH8dOLax1+hy/Hw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.20", + "@aws-sdk/nested-clients": "^3.996.10", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.972.21", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.21.tgz", + "integrity": "sha512-hah8if3/B/Q+LBYN5FukyQ1Mym6PLPDsBOBsIgNEYD6wLyZg0UmUF/OKIVC3nX9XH8TfTPuITK+7N/jenVACWA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "^3.972.18", + "@aws-sdk/credential-provider-http": "^3.972.20", + "@aws-sdk/credential-provider-ini": "^3.972.20", + "@aws-sdk/credential-provider-process": "^3.972.18", + "@aws-sdk/credential-provider-sso": "^3.972.20", + "@aws-sdk/credential-provider-web-identity": "^3.972.20", + "@aws-sdk/types": "^3.973.6", + "@smithy/credential-provider-imds": "^4.2.12", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.972.18", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.18.tgz", + "integrity": "sha512-Tpl7SRaPoOLT32jbTWchPsn52hYYgJ0kpiFgnwk8pxTANQdUymVSZkzFvv1+oOgZm1CrbQUP9MBeoMZ9IzLZjA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.20", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.972.20", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.20.tgz", + "integrity": "sha512-p+R+PYR5Z7Gjqf/6pvbCnzEHcqPCpLzR7Yf127HjJ6EAb4hUcD+qsNRnuww1sB/RmSeCLxyay8FMyqREw4p1RA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.20", + "@aws-sdk/nested-clients": "^3.996.10", + "@aws-sdk/token-providers": "3.1009.0", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.972.20", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.20.tgz", + "integrity": "sha512-rWCmh8o7QY4CsUj63qopzMzkDq/yPpkrpb+CnjBEFSOg/02T/we7sSTVg4QsDiVS9uwZ8VyONhq98qt+pIh3KA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.20", + "@aws-sdk/nested-clients": "^3.996.10", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/lib-storage": { + "version": "3.1010.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.1010.0.tgz", + "integrity": "sha512-jafLXyFGKrlMz6BaiTpfQQYn2Lro5mKMOzBaprwIs1zY4j+W299cB+vf2wFrUAqw+MAPj2+hHRZTze7nMDdwoQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.12", + "@smithy/middleware-endpoint": "^4.4.25", + "@smithy/smithy-client": "^4.12.5", + "buffer": "5.6.0", + "events": "3.3.0", + "stream-browserify": "3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-s3": "^3.1010.0" + } + }, + "node_modules/@aws-sdk/lib-storage/node_modules/buffer": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.972.8.tgz", + "integrity": "sha512-WR525Rr2QJSETa9a050isktyWi/4yIGcmY3BQ1kpHqb0LqUglQHCS8R27dTJxxWNZvQ0RVGtEZjTCbZJpyF3Aw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-arn-parser": "^3.972.3", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-config-provider": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.972.8.tgz", + "integrity": "sha512-5DTBTiotEES1e2jOHAq//zyzCjeMB78lEHd35u15qnrid4Nxm7diqIf9fQQ3Ov0ChH1V3Vvt13thOnrACmfGVQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.6", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.974.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.974.0.tgz", + "integrity": "sha512-BmdDjqvnuYaC4SY7ypHLXfCSsGYGUZkjCLSZyUAAYn1YT28vbNMJNDwhlfkvvE+hQHG5RJDlEmYuvBxcB9jX1g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "^3.973.20", + "@aws-sdk/crc64-nvme": "^3.972.5", + "@aws-sdk/types": "^3.973.6", + "@smithy/is-array-buffer": "^4.2.2", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-stream": "^4.5.19", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.8.tgz", + "integrity": "sha512-wAr2REfKsqoKQ+OkNqvOShnBoh+nkPurDKW7uAeVSu6kUECnWlSJiPvnoqxGlfousEY/v9LfS9sNc46hjSYDIQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.6", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.972.8.tgz", + "integrity": "sha512-KaUoFuoFPziIa98DSQsTPeke1gvGXlc5ZGMhy+b+nLxZ4A7jmJgLzjEF95l8aOQN2T/qlPP3MrAyELm8ExXucw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.8.tgz", + "integrity": "sha512-CWl5UCM57WUFaFi5kB7IBY1UmOeLvNZAZ2/OZ5l20ldiJ3TiIz1pC65gYj8X0BCPWkeR1E32mpsCk1L1I4n+lA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.8.tgz", + "integrity": "sha512-BnnvYs2ZEpdlmZ2PNlV2ZyQ8j8AEkMTjN79y/YA475ER1ByFYrkVR85qmhni8oeTaJcDqbx364wDpitDAA/wCA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.6", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.972.20", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.20.tgz", + "integrity": "sha512-yhva/xL5H4tWQgsBjwV+RRD0ByCzg0TcByDCLp3GXdn/wlyRNfy8zsswDtCvr1WSKQkSQYlyEzPuWkJG0f5HvQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.20", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-arn-parser": "^3.972.3", + "@smithy/core": "^3.23.11", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/signature-v4": "^5.3.12", + "@smithy/smithy-client": "^4.12.5", + "@smithy/types": "^4.13.1", + "@smithy/util-config-provider": "^4.2.2", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-stream": "^4.5.19", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.972.8.tgz", + "integrity": "sha512-wqlK0yO/TxEC2UsY9wIlqeeutF6jjLe0f96Pbm40XscTo57nImUk9lBcw0dPgsm0sppFtAkSlDrfpK+pC30Wqw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.972.21", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.21.tgz", + "integrity": "sha512-62XRl1GDYPpkt7cx1AX1SPy9wgNE9Iw/NPuurJu4lmhCWS7sGKO+kS53TQ8eRmIxy3skmvNInnk0ZbWrU5Dpyg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.20", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-endpoints": "^3.996.5", + "@smithy/core": "^3.23.11", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-retry": "^4.2.12", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.996.10", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.996.10.tgz", + "integrity": "sha512-SlDol5Z+C7Ivnc2rKGqiqfSUmUZzY1qHfVs9myt/nxVwswgfpjdKahyTzLTx802Zfq0NFRs7AejwKzzzl5Co2w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "^3.973.20", + "@aws-sdk/middleware-host-header": "^3.972.8", + "@aws-sdk/middleware-logger": "^3.972.8", + "@aws-sdk/middleware-recursion-detection": "^3.972.8", + "@aws-sdk/middleware-user-agent": "^3.972.21", + "@aws-sdk/region-config-resolver": "^3.972.8", + "@aws-sdk/types": "^3.973.6", + "@aws-sdk/util-endpoints": "^3.996.5", + "@aws-sdk/util-user-agent-browser": "^3.972.8", + "@aws-sdk/util-user-agent-node": "^3.973.7", + "@smithy/config-resolver": "^4.4.11", + "@smithy/core": "^3.23.11", + "@smithy/fetch-http-handler": "^5.3.15", + "@smithy/hash-node": "^4.2.12", + "@smithy/invalid-dependency": "^4.2.12", + "@smithy/middleware-content-length": "^4.2.12", + "@smithy/middleware-endpoint": "^4.4.25", + "@smithy/middleware-retry": "^4.4.42", + "@smithy/middleware-serde": "^4.2.14", + "@smithy/middleware-stack": "^4.2.12", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/node-http-handler": "^4.4.16", + "@smithy/protocol-http": "^5.3.12", + "@smithy/smithy-client": "^4.12.5", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-body-length-node": "^4.2.3", + "@smithy/util-defaults-mode-browser": "^4.3.41", + "@smithy/util-defaults-mode-node": "^4.2.44", + "@smithy/util-endpoints": "^3.3.3", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-retry": "^4.2.12", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.8.tgz", + "integrity": "sha512-1eD4uhTDeambO/PNIDVG19A6+v4NdD7xzwLHDutHsUqz0B+i661MwQB2eYO4/crcCvCiQG4SRm1k81k54FEIvw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.6", + "@smithy/config-resolver": "^4.4.11", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.996.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.8.tgz", + "integrity": "sha512-n1qYFD+tbqZuyskVaxUE+t10AUz9g3qzDw3Tp6QZDKmqsjfDmZBd4GIk2EKJJNtcCBtE5YiUjDYA+3djFAFBBg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "^3.972.20", + "@aws-sdk/types": "^3.973.6", + "@smithy/protocol-http": "^5.3.12", + "@smithy/signature-v4": "^5.3.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.1009.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1009.0.tgz", + "integrity": "sha512-KCPLuTqN9u0Rr38Arln78fRG9KXpzsPWmof+PZzfAHMMQq2QED6YjQrkrfiH7PDefLWEposY1o4/eGwrmKA4JA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.973.20", + "@aws-sdk/nested-clients": "^3.996.10", + "@aws-sdk/types": "^3.973.6", + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.973.6", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.6.tgz", + "integrity": "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.972.3", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.972.3.tgz", + "integrity": "sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.996.5", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.996.5.tgz", + "integrity": "sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-endpoints": "^3.3.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.723.0.tgz", + "integrity": "sha512-Yf2CS10BqK688DRsrKI/EO6B8ff5J86NXe4C+VCysK7UOgN0l1zOTeTukZ3H8Q9tYYX3oaF1961o8vRkFm7Nmw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.972.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.8.tgz", + "integrity": "sha512-B3KGXJviV2u6Cdw2SDY2aDhoJkVfY/Q/Trwk2CMSkikE1Oi6gRzxhvhIfiRpHfmIsAhV4EA54TVEX8K6CbHbkA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.973.6", + "@smithy/types": "^4.13.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.973.7", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.7.tgz", + "integrity": "sha512-Hz6EZMUAEzqUd7e+vZ9LE7mn+5gMbxltXy18v+YSFY+9LBJz15wkNZvw5JqfX3z0FS9n3bgUtz3L5rAsfh4YlA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "^3.972.21", + "@aws-sdk/types": "^3.973.6", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-config-provider": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.972.11", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.11.tgz", + "integrity": "sha512-iitV/gZKQMvY9d7ovmyFnFuTHbBAtrmLnvaSb/3X8vOKyevwtpmEtyc8AdhVWZe0pI/1GsHxlEvQeOePFzy7KQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "fast-xml-parser": "5.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", + "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.0.tgz", + "integrity": "sha512-vSH118/wwM/pLR38g/Sgk05sNtro6TlTJKuiMXDaZqPUfjTFcudpCOt00IhOfj+1BFAX+UFAlzCU+6WXr3GLFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", + "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "regexpu-core": "^6.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.6.tgz", + "integrity": "sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.11" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", + "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", + "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", + "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", + "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz", + "integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz", + "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-remap-async-to-generator": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", + "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", + "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", + "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", + "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", + "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/template": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", + "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", + "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", + "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", + "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", + "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", + "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz", + "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", + "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", + "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", + "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", + "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", + "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", + "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", + "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", + "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", + "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", + "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", + "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", + "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.2.tgz", + "integrity": "sha512-DYD23veRYGvBFhcTY1iUvJnDNpuqNd/BzBwCvzOTKUnJjKg5kpUBh3/u9585Agdkgj+QuygG7jLfOPWMa2KVNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.28.6", + "@babel/plugin-syntax-import-attributes": "^7.28.6", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.29.0", + "@babel/plugin-transform-async-to-generator": "^7.28.6", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.6", + "@babel/plugin-transform-class-properties": "^7.28.6", + "@babel/plugin-transform-class-static-block": "^7.28.6", + "@babel/plugin-transform-classes": "^7.28.6", + "@babel/plugin-transform-computed-properties": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-dotall-regex": "^7.28.6", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.6", + "@babel/plugin-transform-exponentiation-operator": "^7.28.6", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.28.6", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.28.6", + "@babel/plugin-transform-modules-systemjs": "^7.29.0", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", + "@babel/plugin-transform-numeric-separator": "^7.28.6", + "@babel/plugin-transform-object-rest-spread": "^7.28.6", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.28.6", + "@babel/plugin-transform-optional-chaining": "^7.28.6", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.28.6", + "@babel/plugin-transform-private-property-in-object": "^7.28.6", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.29.0", + "@babel/plugin-transform-regexp-modifiers": "^7.28.6", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.28.6", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.28.6", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.15", + "babel-plugin-polyfill-corejs3": "^0.14.0", + "babel-plugin-polyfill-regenerator": "^0.6.6", + "core-js-compat": "^3.48.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz", + "integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.2.tgz", + "integrity": "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.1.1" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.1.tgz", + "integrity": "sha512-BvqN0AMWNAnLk9G8jnUT77D+mUbY/H2b3uDTvg2isJkHaOufUE2R3AOwxWo7VBQKT1lOdwdvorddo2B/lk64+w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@discordjs/builders": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.13.1.tgz", + "integrity": "sha512-cOU0UDHc3lp/5nKByDxkmRiNZBpdp0kx55aarbiAfakfKJHlxv/yFW1zmIqCAmwH5CRlrH9iMFKJMpvW4DPB+w==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/formatters": "^0.6.2", + "@discordjs/util": "^1.2.0", + "@sapphire/shapeshift": "^4.0.0", + "discord-api-types": "^0.38.33", + "fast-deep-equal": "^3.1.3", + "ts-mixer": "^6.0.4", + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/collection": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz", + "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/@discordjs/formatters": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.2.tgz", + "integrity": "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ==", + "license": "Apache-2.0", + "dependencies": { + "discord-api-types": "^0.38.33" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/rest": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.6.0.tgz", + "integrity": "sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/collection": "^2.1.1", + "@discordjs/util": "^1.1.1", + "@sapphire/async-queue": "^1.5.3", + "@sapphire/snowflake": "^3.5.3", + "@vladfrangu/async_event_emitter": "^2.4.6", + "discord-api-types": "^0.38.16", + "magic-bytes.js": "^1.10.0", + "tslib": "^2.6.3", + "undici": "6.21.3" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/rest/node_modules/@discordjs/collection": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", + "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/util": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.2.0.tgz", + "integrity": "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg==", + "license": "Apache-2.0", + "dependencies": { + "discord-api-types": "^0.38.33" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/ws": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz", + "integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/collection": "^2.1.0", + "@discordjs/rest": "^2.5.1", + "@discordjs/util": "^1.1.0", + "@sapphire/async-queue": "^1.5.2", + "@types/ws": "^8.5.10", + "@vladfrangu/async_event_emitter": "^2.2.4", + "discord-api-types": "^0.38.1", + "tslib": "^2.6.2", + "ws": "^8.17.0" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/ws/node_modules/@discordjs/collection": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", + "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-1.0.0.tgz", + "integrity": "sha512-dDlz3W405VMFO4w5kIP9DOmELBcvFQGmLoKSdIRstBDubKFYwaNHV1NnlzMCQpXQFGWVALmeMORAuiLx18AvZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.17.0" + } + }, + "node_modules/@exodus/bytes": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", + "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, + "node_modules/@fastify/otel": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@fastify/otel/-/otel-0.17.1.tgz", + "integrity": "sha512-K4wyxfUZx2ux5o+b6BtTqouYFVILohLZmSbA2tKUueJstNcBnoGPVhllCaOvbQ3ZrXdUxUC/fyrSWSCqHhdOPg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.212.0", + "@opentelemetry/semantic-conventions": "^1.28.0", + "minimatch": "^10.2.4" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" + } + }, + "node_modules/@fastify/otel/node_modules/@opentelemetry/api-logs": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.212.0.tgz", + "integrity": "sha512-TEEVrLbNROUkYY51sBJGk7lO/OLjuepch8+hmpM6ffMJQ2z/KVCjdHuCFX6fJj8OkJP2zckPjrJzQtXU3IAsFg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@fastify/otel/node_modules/@opentelemetry/instrumentation": { + "version": "0.212.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.212.0.tgz", + "integrity": "sha512-IyXmpNnifNouMOe0I/gX7ENfv2ZCNdYTF0FpCsoBcpbIHzk81Ww9rQTYTnvghszCg7qGrIhNvWC8dhEifgX9Jg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.212.0", + "import-in-the-middle": "^2.0.6", + "require-in-the-middle": "^8.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@fastify/otel/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@fastify/otel/node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@fastify/otel/node_modules/import-in-the-middle": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-2.0.6.tgz", + "integrity": "sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw==", + "license": "Apache-2.0", + "dependencies": { + "acorn": "^8.15.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^2.2.0", + "module-details-from-path": "^1.0.4" + } + }, + "node_modules/@fastify/otel/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@lukeed/csprng": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", + "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@lukeed/uuid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@lukeed/uuid/-/uuid-2.0.1.tgz", + "integrity": "sha512-qC72D4+CDdjGqJvkFMMEAtancHUQ7/d/tAiHf64z8MopFDmcrtbcJuerDtFceuAfQJ2pDSfCKCtbqoGBNnwg0w==", + "license": "MIT", + "dependencies": { + "@lukeed/csprng": "^1.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@mattermost/client": { + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/@mattermost/client/-/client-11.4.0.tgz", + "integrity": "sha512-QzYIpxFRPA+NDCCxk9bPwnFRpgZq8d1KKYXic8AJJiPXjja6IJDOkpDfGgo4b1q0AofUQ+u7Js3Fh2mxerwz5g==", + "license": "MIT", + "peerDependencies": { + "@mattermost/types": "11.4.0", + "typescript": "^4.3.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@mattermost/types": { + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/@mattermost/types/-/types-11.4.0.tgz", + "integrity": "sha512-oYt7vvsa60hPpujcCBYeyd+22OwprEAtFXgVJrsdd9pR1qkEGU4F+uv8bh6ZTBSZAQnc2/xXQPZJGGR06toteA==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "typescript": "^4.3.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@ngrok/ngrok": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok/-/ngrok-1.7.0.tgz", + "integrity": "sha512-P06o9TpxrJbiRbHQkiwy/rUrlXRupc+Z8KT4MiJfmcdWxvIdzjCaJOdnNkcOTs6DMyzIOefG5tvk/HLdtjqr0g==", + "license": "(MIT OR Apache-2.0)", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@ngrok/ngrok-android-arm64": "1.7.0", + "@ngrok/ngrok-darwin-arm64": "1.7.0", + "@ngrok/ngrok-darwin-universal": "1.7.0", + "@ngrok/ngrok-darwin-x64": "1.7.0", + "@ngrok/ngrok-freebsd-x64": "1.7.0", + "@ngrok/ngrok-linux-arm-gnueabihf": "1.7.0", + "@ngrok/ngrok-linux-arm64-gnu": "1.7.0", + "@ngrok/ngrok-linux-arm64-musl": "1.7.0", + "@ngrok/ngrok-linux-x64-gnu": "1.7.0", + "@ngrok/ngrok-linux-x64-musl": "1.7.0", + "@ngrok/ngrok-win32-arm64-msvc": "1.7.0", + "@ngrok/ngrok-win32-ia32-msvc": "1.7.0", + "@ngrok/ngrok-win32-x64-msvc": "1.7.0" + } + }, + "node_modules/@ngrok/ngrok-android-arm64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-android-arm64/-/ngrok-android-arm64-1.7.0.tgz", + "integrity": "sha512-8tco3ID6noSaNy+CMS7ewqPoIkIM6XO5COCzsUp3Wv3XEbMSyn65RN6cflX2JdqLfUCHcMyD0ahr9IEiHwqmbQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-darwin-arm64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-darwin-arm64/-/ngrok-darwin-arm64-1.7.0.tgz", + "integrity": "sha512-+dmJSOzSO+MNDVrPOca2yYDP1W3KfP4qOlAkarIeFRIfqonQwq3QCBmcR7HAlZocLsSqEwyG6KP4RRvAuT0WGQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-darwin-universal": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-darwin-universal/-/ngrok-darwin-universal-1.7.0.tgz", + "integrity": "sha512-fDEfewyE2pWGFBhOSwQZObeHUkc65U1l+3HIgSOe094TMHsqmyJD0KTCgW9KSn0VP4OvDZbAISi1T3nvqgZYhQ==", + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-darwin-x64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-darwin-x64/-/ngrok-darwin-x64-1.7.0.tgz", + "integrity": "sha512-+fwMi5uHd9G8BS42MMa9ye6exI5lwTcjUO6Ut497Vu0qgLONdVRenRqnEePV+Q3KtQR7NjqkMnomVfkr9MBjtw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-freebsd-x64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-freebsd-x64/-/ngrok-freebsd-x64-1.7.0.tgz", + "integrity": "sha512-2OGgbrjy3yLRrqAz5N6hlUKIWIXSpR5RjQa2chtZMsSbszQ6c9dI+uVQfOKAeo05tHMUgrYAZ7FocC+ig0dzdQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-arm-gnueabihf": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-arm-gnueabihf/-/ngrok-linux-arm-gnueabihf-1.7.0.tgz", + "integrity": "sha512-SN9YIfEQiR9xN90QVNvdgvAemqMLoFVSeTWZs779145hQMhvF9Qd9rnWi6J+2uNNK10OczdV1oc/nq1es7u/3g==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-arm64-gnu": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-arm64-gnu/-/ngrok-linux-arm64-gnu-1.7.0.tgz", + "integrity": "sha512-KDMgzPKFU2kbpVSaA2RZBBia5IPdJEe063YlyVFnSMJmPYWCUnMwdybBsucXfV9u1Lw/ZjKTKotIlbTWGn3HGw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-arm64-musl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-arm64-musl/-/ngrok-linux-arm64-musl-1.7.0.tgz", + "integrity": "sha512-e66vUdVrBlQ0lT9ZdamB4U604zt5Gualt8/WVcUGzbu8s5LajWd6g/mzZCUjK4UepjvMpfgmCp1/+rX7Rk8d5A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-x64-gnu": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-x64-gnu/-/ngrok-linux-x64-gnu-1.7.0.tgz", + "integrity": "sha512-M6gF0DyOEFqXLfWxObfL3bxYZ4+PnKBHuyLVaqNfFN9Y5utY2mdPOn5422Ppbk4XoIK5/YkuhRqPJl/9FivKEw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-linux-x64-musl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-linux-x64-musl/-/ngrok-linux-x64-musl-1.7.0.tgz", + "integrity": "sha512-4Ijm0dKeoyzZTMaYxR2EiNjtlK81ebflg/WYIO1XtleFrVy4UJEGnxtxEidYoT4BfCqi4uvXiK2Mx216xXKvog==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-win32-arm64-msvc": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-win32-arm64-msvc/-/ngrok-win32-arm64-msvc-1.7.0.tgz", + "integrity": "sha512-u7qyWIJI2/YG1HTBnHwUR1+Z2tyGfAsUAItJK/+N1G0FeWJhIWQvSIFJHlaPy4oW1Dc8mSDBX9qvVsiQgLaRFg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-win32-ia32-msvc": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-win32-ia32-msvc/-/ngrok-win32-ia32-msvc-1.7.0.tgz", + "integrity": "sha512-/UdYUsLNv/Q8j9YJsyIfq/jLCoD8WP+NidouucTUzSoDtmOsXBBT3itLrmPiZTEdEgKiFYLuC1Zon8XQQvbVLA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@ngrok/ngrok-win32-x64-msvc": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@ngrok/ngrok-win32-x64-msvc/-/ngrok-win32-x64-msvc-1.7.0.tgz", + "integrity": "sha512-UFJg/duEWzZlLkEs61Gz6/5nYhGaKI62I8dvUGdBR3NCtIMagehnFaFxmnXZldyHmCM8U0aCIFNpWRaKcrQkoA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.213.0.tgz", + "integrity": "sha512-zRM5/Qj6G84Ej3F1yt33xBVY/3tnMxtL1fiDIxYbDWYaZ/eudVw3/PBiZ8G7JwUxXxjW8gU4g6LnOyfGKYHYgw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/context-async-hooks": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.6.0.tgz", + "integrity": "sha512-L8UyDwqpTcbkIK5cgwDRDYDoEhQoj8wp8BwsO19w3LB1Z41yEQm2VJyNfAi9DrLP/YTqXqWpKHyZfR9/tFYo1Q==", + "license": "Apache-2.0", + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.0.tgz", + "integrity": "sha512-HLM1v2cbZ4TgYN6KEOj+Bbj8rAKriOdkF9Ed3tG25FoprSiQl7kYc+RRT6fUZGOvx0oMi5U67GoFdT+XUn8zEg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/instrumentation": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.213.0.tgz", + "integrity": "sha512-3i9NdkET/KvQomeh7UaR/F4r9P25Rx6ooALlWXPIjypcEOUxksCmVu0zA70NBJWlrMW1rPr/LRidFAflLI+s/w==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.213.0", + "import-in-the-middle": "^3.0.0", + "require-in-the-middle": "^8.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-amqplib": { + "version": "0.60.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-amqplib/-/instrumentation-amqplib-0.60.0.tgz", + "integrity": "sha512-q/B2IvoVXRm1M00MvhnzpMN6rKYOszPXVsALi6u0ss4AYHe+TidZEtLW9N1ZhrobI1dSriHnBqqtAOZVAv07sg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.213.0", + "@opentelemetry/semantic-conventions": "^1.33.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-connect": { + "version": "0.56.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.56.0.tgz", + "integrity": "sha512-PKp+sSZ7AfzMvGgO3VCyo1inwNu+q7A1k9X88WK4PQ+S6Hp7eFk8pie+sWHDTaARovmqq5V2osav3lQej2B0nw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.213.0", + "@opentelemetry/semantic-conventions": "^1.27.0", + "@types/connect": "3.4.38" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-dataloader": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-dataloader/-/instrumentation-dataloader-0.30.0.tgz", + "integrity": "sha512-MXHP2Q38cd2OhzEBKAIXUi9uBlPEYzF6BNJbyjUXBQ6kLaf93kRC41vNMIz0Nl5mnuwK7fDvKT+/lpx7BXRwdg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.213.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-express": { + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-express/-/instrumentation-express-0.61.0.tgz", + "integrity": "sha512-Xdmqo9RZuZlL29Flg8QdwrrX7eW1CZ7wFQPKHyXljNymgKhN1MCsYuqQ/7uxavhSKwAl7WxkTzKhnqpUApLMvQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.213.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-fs": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.32.0.tgz", + "integrity": "sha512-koR6apx0g0wX6RRiPpjA4AFQUQUbXrK16kq4/SZjVp7u5cffJhNkY4TnITxcGA4acGSPYAfx3NHRIv4Khn1axQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.213.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-generic-pool": { + "version": "0.56.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.56.0.tgz", + "integrity": "sha512-fg+Jffs6fqrf0uQS0hom7qBFKsbtpBiBl8+Vkc63Gx8xh6pVh+FhagmiO6oM0m3vyb683t1lP7yGYq22SiDnqg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.213.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-graphql": { + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.61.0.tgz", + "integrity": "sha512-pUiVASv6nh2XrerTvlbVHh7vKFzscpgwiQ/xvnZuAIzQ5lRjWVdRPUuXbvZJ/Yq79QsE81TZdJ7z9YsXiss1ew==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.213.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-hapi": { + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.59.0.tgz", + "integrity": "sha512-33wa4mEr+9+ztwdgLor1SeBu4Opz4IsmpcLETXAd3VmBrOjez8uQtrsOhPCa5Vhbm5gzDlMYTgFRLQzf8/YHFA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.213.0", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-http": { + "version": "0.213.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-http/-/instrumentation-http-0.213.0.tgz", + "integrity": "sha512-B978Xsm5XEPGhm1P07grDoaOFLHapJPkOG9h016cJsyWWxmiLnPu2M/4Nrm7UCkHSiLnkXgC+zVGUAIahy8EEA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.6.0", + "@opentelemetry/instrumentation": "0.213.0", + "@opentelemetry/semantic-conventions": "^1.29.0", + "forwarded-parse": "2.1.2" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-ioredis": { + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.61.0.tgz", + "integrity": "sha512-hsHDadUtAFbws1YSDc1XW0svGFKiUbqv2td1Cby+UAiwvojm1NyBo/taifH0t8CuFZ0x/2SDm0iuTwrM5pnVOg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.213.0", + "@opentelemetry/redis-common": "^0.38.2", + "@opentelemetry/semantic-conventions": "^1.33.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-kafkajs": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.22.0.tgz", + "integrity": "sha512-wJU4IBQMUikdJAcTChLFqK5lo+flo7pahqd8DSLv7uMxsdOdAHj6RzKYAm8pPfUS6ItKYutYyuicwKaFwQKsoA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.213.0", + "@opentelemetry/semantic-conventions": "^1.30.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-knex": { + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-knex/-/instrumentation-knex-0.57.0.tgz", + "integrity": "sha512-vMCSh8kolEm5rRsc+FZeTZymWmIJwc40hjIKnXH4O0Dv/gAkJJIRXCsPX5cPbe0c0j/34+PsENd0HqKruwhVYw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.213.0", + "@opentelemetry/semantic-conventions": "^1.33.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-koa": { + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.61.0.tgz", + "integrity": "sha512-lvrfWe9ShK/D2X4brmx8ZqqeWPfRl8xekU0FCn7C1dHm5k6+rTOOi36+4fnaHAP8lig9Ux6XQ1D4RNIpPCt1WQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.213.0", + "@opentelemetry/semantic-conventions": "^1.36.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0" + } + }, + "node_modules/@opentelemetry/instrumentation-lru-memoizer": { + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.57.0.tgz", + "integrity": "sha512-cEqpUocSKJfwDtLYTTJehRLWzkZ2eoePCxfVIgGkGkb83fMB71O+y4MvRHJPbeV2bdoWdOVrl8uO0+EynWhTEA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.213.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mongodb": { + "version": "0.66.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.66.0.tgz", + "integrity": "sha512-d7m9QnAY+4TCWI4q1QRkfrc6fo/92VwssaB1DzQfXNRvu51b78P+HJlWP7Qg6N6nkwdb9faMZNBCZJfftmszkw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.213.0", + "@opentelemetry/semantic-conventions": "^1.33.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mongoose": { + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.59.0.tgz", + "integrity": "sha512-6/jWU+c1NgznkVLDU/2y0bXV2nJo3o9FWZ9mZ9nN6T/JBNRoMnVXZl2FdBmgH+a5MwaWLs5kmRJTP5oUVGIkPw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.213.0", + "@opentelemetry/semantic-conventions": "^1.33.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mysql": { + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.59.0.tgz", + "integrity": "sha512-r+V/Fh0sm7Ga8/zk/TI5H5FQRAjwr0RrpfPf8kNIehlsKf12XnvIaZi8ViZkpX0gyPEpLXqzqWD6QHlgObgzZw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.213.0", + "@opentelemetry/semantic-conventions": "^1.33.0", + "@types/mysql": "2.15.27" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-mysql2": { + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.59.0.tgz", + "integrity": "sha512-n9/xrVCRBfG9egVbffnlU1uhr+HX0vF4GgtAB/Bvm48wpFgRidqD8msBMiym1kRYzmpWvJqTxNT47u1MkgBEdw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.213.0", + "@opentelemetry/semantic-conventions": "^1.33.0", + "@opentelemetry/sql-common": "^0.41.2" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-pg": { + "version": "0.65.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.65.0.tgz", + "integrity": "sha512-W0zpHEIEuyZ8zvb3njaX9AAbHgPYOsSWVOoWmv1sjVRSF6ZpBqtlxBWbU+6hhq1TFWBeWJOXZ8nZS/PUFpLJYQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.213.0", + "@opentelemetry/semantic-conventions": "^1.34.0", + "@opentelemetry/sql-common": "^0.41.2", + "@types/pg": "8.15.6", + "@types/pg-pool": "2.0.7" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-redis": { + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.61.0.tgz", + "integrity": "sha512-JnPexA034/0UJRsvH96B0erQoNOqKJZjE2ZRSw9hiTSC23LzE0nJE/u6D+xqOhgUhRnhhcPHq4MdYtmUdYTF+Q==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.213.0", + "@opentelemetry/redis-common": "^0.38.2", + "@opentelemetry/semantic-conventions": "^1.27.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-tedious": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.32.0.tgz", + "integrity": "sha512-BQS6gG8RJ1foEqfEZ+wxoqlwfCAzb1ZVG0ad8Gfe4x8T658HJCLGLd4E4NaoQd8EvPfLqOXgzGaE/2U4ytDSWA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.213.0", + "@opentelemetry/semantic-conventions": "^1.33.0", + "@types/tedious": "^4.0.14" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/instrumentation-undici": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.23.0.tgz", + "integrity": "sha512-LL0VySzKVR2cJSFVZaTYpZl1XTpBGnfzoQPe2W7McS2267ldsaEIqtQY6VXs2KCXN0poFjze5110PIpxHDaDGg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/instrumentation": "^0.213.0", + "@opentelemetry/semantic-conventions": "^1.24.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.7.0" + } + }, + "node_modules/@opentelemetry/redis-common": { + "version": "0.38.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/redis-common/-/redis-common-0.38.2.tgz", + "integrity": "sha512-1BCcU93iwSRZvDAgwUxC/DV4T/406SkMfxGqu5ojc3AvNI+I9GhV7v0J1HljsczuuhcnFLYqD5VmwVXfCGHzxA==", + "license": "Apache-2.0", + "engines": { + "node": "^18.19.0 || >=20.6.0" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.6.0.tgz", + "integrity": "sha512-D4y/+OGe3JSuYUCBxtH5T9DSAWNcvCb/nQWIga8HNtXTVPQn59j0nTBAgaAXxUVBDl40mG3Tc76b46wPlZaiJQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.6.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.6.0.tgz", + "integrity": "sha512-g/OZVkqlxllgFM7qMKqbPV9c1DUPhQ7d4n3pgZFcrnrNft9eJXZM2TNHTPYREJBrtNdRytYyvwjgL5geDKl3EQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.6.0", + "@opentelemetry/resources": "2.6.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.40.0.tgz", + "integrity": "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/@opentelemetry/sql-common": { + "version": "0.41.2", + "resolved": "https://registry.npmjs.org/@opentelemetry/sql-common/-/sql-common-0.41.2.tgz", + "integrity": "sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "^2.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0" + } + }, + "node_modules/@panva/asn1.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz", + "integrity": "sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@prisma/instrumentation": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-7.4.2.tgz", + "integrity": "sha512-r9JfchJF1Ae6yAxcaLu/V1TGqBhAuSDe3mRNOssBfx1rMzfZ4fdNvrgUBwyb/TNTGXFxlH9AZix5P257x07nrg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/instrumentation": "^0.207.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.8" + } + }, + "node_modules/@prisma/instrumentation/node_modules/@opentelemetry/api-logs": { + "version": "0.207.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.207.0.tgz", + "integrity": "sha512-lAb0jQRVyleQQGiuuvCOTDVspc14nx6XJjP4FspJ1sNARo3Regq4ZZbrc3rN4b1TYSuUCvgH+UXUPug4SLOqEQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@prisma/instrumentation/node_modules/@opentelemetry/instrumentation": { + "version": "0.207.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.207.0.tgz", + "integrity": "sha512-y6eeli9+TLKnznrR8AZlQMSJT7wILpXH+6EYq5Vf/4Ao+huI7EedxQHwRgVUOMLFbe7VFDvHJrX9/f4lcwnJsA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.207.0", + "import-in-the-middle": "^2.0.0", + "require-in-the-middle": "^8.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@prisma/instrumentation/node_modules/import-in-the-middle": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-2.0.6.tgz", + "integrity": "sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw==", + "license": "Apache-2.0", + "dependencies": { + "acorn": "^8.15.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^2.2.0", + "module-details-from-path": "^1.0.4" + } + }, + "node_modules/@sapphire/async-queue": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz", + "integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@sapphire/shapeshift": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz", + "integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v16" + } + }, + "node_modules/@sapphire/snowflake": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz", + "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, + "node_modules/@sentry/core": { + "version": "10.44.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.44.0.tgz", + "integrity": "sha512-aa7CiDaNFZvHpqd97LJhuskolfJ/4IH5xyuVVLnv7l6B0v9KTwskPUxb0tH1ej3FxuzfH+i8iTiTFuqpfHS3QA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/node": { + "version": "10.44.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-10.44.0.tgz", + "integrity": "sha512-q+/WR9ZeF9Af8uyehOj2tQQOa7LH07mJfOuDus5X6G6cLuugdRUGUBB5Qhw+J/ULSxbzGADBZv6AYOyoGaNx7w==", + "license": "MIT", + "dependencies": { + "@fastify/otel": "0.17.1", + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/context-async-hooks": "^2.6.0", + "@opentelemetry/core": "^2.6.0", + "@opentelemetry/instrumentation": "^0.213.0", + "@opentelemetry/instrumentation-amqplib": "0.60.0", + "@opentelemetry/instrumentation-connect": "0.56.0", + "@opentelemetry/instrumentation-dataloader": "0.30.0", + "@opentelemetry/instrumentation-express": "0.61.0", + "@opentelemetry/instrumentation-fs": "0.32.0", + "@opentelemetry/instrumentation-generic-pool": "0.56.0", + "@opentelemetry/instrumentation-graphql": "0.61.0", + "@opentelemetry/instrumentation-hapi": "0.59.0", + "@opentelemetry/instrumentation-http": "0.213.0", + "@opentelemetry/instrumentation-ioredis": "0.61.0", + "@opentelemetry/instrumentation-kafkajs": "0.22.0", + "@opentelemetry/instrumentation-knex": "0.57.0", + "@opentelemetry/instrumentation-koa": "0.61.0", + "@opentelemetry/instrumentation-lru-memoizer": "0.57.0", + "@opentelemetry/instrumentation-mongodb": "0.66.0", + "@opentelemetry/instrumentation-mongoose": "0.59.0", + "@opentelemetry/instrumentation-mysql": "0.59.0", + "@opentelemetry/instrumentation-mysql2": "0.59.0", + "@opentelemetry/instrumentation-pg": "0.65.0", + "@opentelemetry/instrumentation-redis": "0.61.0", + "@opentelemetry/instrumentation-tedious": "0.32.0", + "@opentelemetry/instrumentation-undici": "0.23.0", + "@opentelemetry/resources": "^2.6.0", + "@opentelemetry/sdk-trace-base": "^2.6.0", + "@opentelemetry/semantic-conventions": "^1.40.0", + "@prisma/instrumentation": "7.4.2", + "@sentry/core": "10.44.0", + "@sentry/node-core": "10.44.0", + "@sentry/opentelemetry": "10.44.0", + "import-in-the-middle": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sentry/node-core": { + "version": "10.44.0", + "resolved": "https://registry.npmjs.org/@sentry/node-core/-/node-core-10.44.0.tgz", + "integrity": "sha512-jUGsadMrvZ08UMbqJBfjFFMk1k3VbyxfUypf0iDGGgyLmuHotYQPo/5aND+o2KxMDXR60LwcQrMoZFpanK6jXQ==", + "license": "MIT", + "dependencies": { + "@sentry/core": "10.44.0", + "@sentry/opentelemetry": "10.44.0", + "import-in-the-middle": "^3.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.1.0", + "@opentelemetry/core": "^1.30.1 || ^2.1.0", + "@opentelemetry/instrumentation": ">=0.57.1 <1", + "@opentelemetry/resources": "^1.30.1 || ^2.1.0", + "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", + "@opentelemetry/semantic-conventions": "^1.39.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@opentelemetry/context-async-hooks": { + "optional": true + }, + "@opentelemetry/core": { + "optional": true + }, + "@opentelemetry/instrumentation": { + "optional": true + }, + "@opentelemetry/resources": { + "optional": true + }, + "@opentelemetry/sdk-trace-base": { + "optional": true + }, + "@opentelemetry/semantic-conventions": { + "optional": true + } + } + }, + "node_modules/@sentry/opentelemetry": { + "version": "10.44.0", + "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-10.44.0.tgz", + "integrity": "sha512-zP4vP8tBxjlmxQ4VcWOwZ0b3lPUxlYPg9FqJwANm9SRJN+7V5psm8TIaAtu9uqtIcJMRHdXkOM4cAggNiLk0KA==", + "license": "MIT", + "dependencies": { + "@sentry/core": "10.44.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.1.0", + "@opentelemetry/core": "^1.30.1 || ^2.1.0", + "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", + "@opentelemetry/semantic-conventions": "^1.39.0" + } + }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "license": "BSD-3-Clause" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.1.1.tgz", + "integrity": "sha512-cO5W33JgAPbOh07tvZjUOJ7oWhtaqGHiZw+11DPbyqh2kHTBc3eF/CjJDeQ4205RLQsX6rxCuYOroFQwl7JDRw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-9.0.3.tgz", + "integrity": "sha512-ZgYY7Dc2RW+OUdnZ1DEHg00lhRt+9BjymPKHog4PRFzr1U3MbK57+djmscWyKxzO1qfunHqs4N45WWyKIFKpiQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "type-detect": "^4.1.0" + } + }, + "node_modules/@sinonjs/samsam/node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@smithy/abort-controller": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.12.tgz", + "integrity": "sha512-xolrFw6b+2iYGl6EcOL7IJY71vvyZ0DJ3mcKtpykqPe2uscwtzDZJa1uVQXyP7w9Dd+kGwYnPbMsJrGISKiY/Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.2.2.tgz", + "integrity": "sha512-St+kVicSyayWQca+I1rGitaOEH6uKgE8IUWoYnnEX26SWdWQcL6LvMSD19Lg+vYHKdT9B2Zuu7rd3i6Wnyb/iw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader-native": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.2.3.tgz", + "integrity": "sha512-jA5k5Udn7Y5717L86h4EIv06wIr3xn8GM1qHRi/Nf31annXcXHJjBKvgztnbn2TxH3xWrPBfgwHsOwZf0UmQWw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-base64": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.4.11", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.11.tgz", + "integrity": "sha512-YxFiiG4YDAtX7WMN7RuhHZLeTmRRAOyCbr+zB8e3AQzHPnUhS8zXjB1+cniPVQI3xbWsQPM0X2aaIkO/ME0ymw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-config-provider": "^4.2.2", + "@smithy/util-endpoints": "^3.3.3", + "@smithy/util-middleware": "^4.2.12", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.23.11", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.11.tgz", + "integrity": "sha512-952rGf7hBRnhUIaeLp6q4MptKW8sPFe5VvkoZ5qIzFAtx6c/QZ/54FS3yootsyUSf9gJX/NBqEBNdNR7jMIlpQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-body-length-browser": "^4.2.2", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-stream": "^4.5.19", + "@smithy/util-utf8": "^4.2.2", + "@smithy/uuid": "^1.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.12.tgz", + "integrity": "sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.12", + "@smithy/property-provider": "^4.2.12", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.12.tgz", + "integrity": "sha512-FE3bZdEl62ojmy8x4FHqxq2+BuOHlcxiH5vaZ6aqHJr3AIZzwF5jfx8dEiU/X0a8RboyNDjmXjlbr8AdEyLgiA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.13.1", + "@smithy/util-hex-encoding": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.12.tgz", + "integrity": "sha512-XUSuMxlTxV5pp4VpqZf6Sa3vT/Q75FVkLSpSSE3KkWBvAQWeuWt1msTv8fJfgA4/jcJhrbrbMzN1AC/hvPmm5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.3.12", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.12.tgz", + "integrity": "sha512-7epsAZ3QvfHkngz6RXQYseyZYHlmWXSTPOfPmXkiS+zA6TBNo1awUaMFL9vxyXlGdoELmCZyZe1nQE+imbmV+Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.12.tgz", + "integrity": "sha512-D1pFuExo31854eAvg89KMn9Oab/wEeJR6Buy32B49A9Ogdtx5fwZPqBHUlDzaCDpycTFk2+fSQgX689Qsk7UGA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.12.tgz", + "integrity": "sha512-+yNuTiyBACxOJUTvbsNsSOfH9G9oKbaJE1lNL3YHpGcuucl6rPZMi3nrpehpVOVR2E07YqFFmtwpImtpzlouHQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/eventstream-codec": "^4.2.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.15", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.15.tgz", + "integrity": "sha512-T4jFU5N/yiIfrtrsb9uOQn7RdELdM/7HbyLNr6uO/mpkj1ctiVs7CihVr51w4LyQlXWDpXFn4BElf1WmQvZu/A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.12", + "@smithy/querystring-builder": "^4.2.12", + "@smithy/types": "^4.13.1", + "@smithy/util-base64": "^4.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-blob-browser": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.13.tgz", + "integrity": "sha512-YrF4zWKh+ghLuquldj6e/RzE3xZYL8wIPfkt0MqCRphVICjyyjH8OwKD7LLlKpVEbk4FLizFfC1+gwK6XQdR3g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/chunked-blob-reader": "^5.2.2", + "@smithy/chunked-blob-reader-native": "^4.2.3", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.12.tgz", + "integrity": "sha512-QhBYbGrbxTkZ43QoTPrK72DoYviDeg6YKDrHTMJbbC+A0sml3kSjzFtXP7BtbyJnXojLfTQldGdUR0RGD8dA3w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-stream-node": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.12.tgz", + "integrity": "sha512-O3YbmGExeafuM/kP7Y8r6+1y0hIh3/zn6GROx0uNlB54K9oihAL75Qtc+jFfLNliTi6pxOAYZrRKD9A7iA6UFw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.12.tgz", + "integrity": "sha512-/4F1zb7Z8LOu1PalTdESFHR0RbPwHd3FcaG1sI3UEIriQTWakysgJr65lc1jj6QY5ye7aFsisajotH6UhWfm/g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", + "integrity": "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/md5-js": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.12.tgz", + "integrity": "sha512-W/oIpHCpWU2+iAkfZYyGWE+qkpuf3vEXHLxQQDx9FPNZTTdnul0dZ2d/gUFrtQ5je1G2kp4cjG0/24YueG2LbQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.12.tgz", + "integrity": "sha512-YE58Yz+cvFInWI/wOTrB+DbvUVz/pLn5mC5MvOV4fdRUc6qGwygyngcucRQjAhiCEbmfLOXX0gntSIcgMvAjmA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.4.25", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.25.tgz", + "integrity": "sha512-dqjLwZs2eBxIUG6Qtw8/YZ4DvzHGIf0DA18wrgtfP6a50UIO7e2nY0FPdcbv5tVJKqWCCU5BmGMOUwT7Puan+A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.11", + "@smithy/middleware-serde": "^4.2.14", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "@smithy/url-parser": "^4.2.12", + "@smithy/util-middleware": "^4.2.12", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.4.42", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.42.tgz", + "integrity": "sha512-vbwyqHRIpIZutNXZpLAozakzamcINaRCpEy1MYmK6xBeW3xN+TyPRA123GjXnuxZIjc9848MRRCugVMTXxC4Eg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/service-error-classification": "^4.2.12", + "@smithy/smithy-client": "^4.12.5", + "@smithy/types": "^4.13.1", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-retry": "^4.2.12", + "@smithy/uuid": "^1.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.14.tgz", + "integrity": "sha512-+CcaLoLa5apzSRtloOyG7lQvkUw2ZDml3hRh4QiG9WyEPfW5Ke/3tPOPiPjUneuT59Tpn8+c3RVaUvvkkwqZwg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.11", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.12.tgz", + "integrity": "sha512-kruC5gRHwsCOuyCd4ouQxYjgRAym2uDlCvQ5acuMtRrcdfg7mFBg6blaxcJ09STpt3ziEkis6bhg1uwrWU7txw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.3.12", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.12.tgz", + "integrity": "sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.12", + "@smithy/shared-ini-file-loader": "^4.4.7", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.4.16", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.16.tgz", + "integrity": "sha512-ULC8UCS/HivdCB3jhi+kLFYe4B5gxH2gi9vHBfEIiRrT2jfKiZNiETJSlzRtE6B26XbBHjPtc8iZKSNqMol9bw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/querystring-builder": "^4.2.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.12.tgz", + "integrity": "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", + "integrity": "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.12.tgz", + "integrity": "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "@smithy/util-uri-escape": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.12.tgz", + "integrity": "sha512-P2OdvrgiAKpkPNKlKUtWbNZKB1XjPxM086NeVhK+W+wI46pIKdWBe5QyXvhUm3MEcyS/rkLvY8rZzyUdmyDZBw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.12.tgz", + "integrity": "sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.4.7", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.7.tgz", + "integrity": "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.3.12", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.12.tgz", + "integrity": "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.2", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-middleware": "^4.2.12", + "@smithy/util-uri-escape": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.12.5", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.5.tgz", + "integrity": "sha512-UqwYawyqSr/aog8mnLnfbPurS0gi4G7IYDcD28cUIBhsvWs1+rQcL2IwkUQ+QZ7dibaoRzhNF99fAQ9AUcO00w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.23.11", + "@smithy/middleware-endpoint": "^4.4.25", + "@smithy/middleware-stack": "^4.2.12", + "@smithy/protocol-http": "^5.3.12", + "@smithy/types": "^4.13.1", + "@smithy/util-stream": "^4.5.19", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.12.tgz", + "integrity": "sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.2.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.2.tgz", + "integrity": "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.2.tgz", + "integrity": "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.3.tgz", + "integrity": "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", + "integrity": "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.2.tgz", + "integrity": "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.41", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.41.tgz", + "integrity": "sha512-M1w1Ux0rSVvBOxIIiqbxvZvhnjQ+VUjJrugtORE90BbadSTH+jsQL279KRL3Hv0w69rE7EuYkV/4Lepz/NBW9g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.12", + "@smithy/smithy-client": "^4.12.5", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.44", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.44.tgz", + "integrity": "sha512-YPze3/lD1KmWuZsl9JlfhcgGLX7AXhSoaCDtiPntUjNW5/YY0lOHjkcgxyE9x/h5vvS1fzDifMGjzqnNlNiqOQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.4.11", + "@smithy/credential-provider-imds": "^4.2.12", + "@smithy/node-config-provider": "^4.3.12", + "@smithy/property-provider": "^4.2.12", + "@smithy/smithy-client": "^4.12.5", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.3.3.tgz", + "integrity": "sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.2.tgz", + "integrity": "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.12.tgz", + "integrity": "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.12.tgz", + "integrity": "sha512-1zopLDUEOwumjcHdJ1mwBHddubYF8GMQvstVCLC54Y46rqoHwlIU+8ZzUeaBcD+WCJHyDGSeZ2ml9YSe9aqcoQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.2.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.5.19", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.19.tgz", + "integrity": "sha512-v4sa+3xTweL1CLO2UP0p7tvIMH/Rq1X4KKOxd568mpe6LSLMQCnDHs4uv7m3ukpl3HvcN2JH6jiCS0SNRXKP/w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.15", + "@smithy/node-http-handler": "^4.4.16", + "@smithy/types": "^4.13.1", + "@smithy/util-base64": "^4.3.2", + "@smithy/util-buffer-from": "^4.2.2", + "@smithy/util-hex-encoding": "^4.2.2", + "@smithy/util-utf8": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", + "integrity": "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", + "integrity": "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-waiter": { + "version": "4.2.13", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.13.tgz", + "integrity": "sha512-2zdZ9DTHngRtcYxJK1GUDxruNr53kv5W2Lupe0LMU+Imr6ohQg8M2T14MNkj1Y0wS3FFwpgpGQyvuaMF7CiTmQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.12", + "@smithy/types": "^4.13.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/uuid": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.2.tgz", + "integrity": "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/events-alias": { + "name": "@types/events", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.3.tgz", + "integrity": "sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g==", + "license": "MIT" + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/mysql": { + "version": "2.15.27", + "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.27.tgz", + "integrity": "sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "22.13.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.14.tgz", + "integrity": "sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/pg": { + "version": "8.15.6", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.6.tgz", + "integrity": "sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, + "node_modules/@types/pg-pool": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/pg-pool/-/pg-pool-2.0.7.tgz", + "integrity": "sha512-U4CwmGVQcbEuqpyju8/ptOKg6gEC+Tqsvj2xS9o1g71bUh8twxnC6ZL5rZKCsGN0iyH0CwgUyc9VR5owNQF9Ng==", + "license": "MIT", + "dependencies": { + "@types/pg": "*" + } + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/tedious": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/@types/tedious/-/tedious-4.0.14.tgz", + "integrity": "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vladfrangu/async_event_emitter": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz", + "integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-import-phases": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.3.tgz", + "integrity": "sha512-jtKLnfoOzm28PazuQ4dVBcE9Jeo6ha1GAJvq3N0LlNOszmTfx+wSycBehn+FN0RnyeR77IBxN/qVYMw0Rlj0Xw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.reduce": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.8.tgz", + "integrity": "sha512-DwuEqgXFBwbmZSRqt3BpQigWNUoqw9Ml2dTWdF3B2zQlQX4OeUE0zyuzX0fX0IbTvjdkZbcBTU3idgpO78qkTw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-array-method-boxes-properly": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "is-string": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/async": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ==" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/async-mutex": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", + "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/awaitqueue": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/awaitqueue/-/awaitqueue-3.3.0.tgz", + "integrity": "sha512-zLxDhzQbzHmOyvxi7g3OlfR7jLrcmElStPxfLPpJkrFSDm71RSrY/MvsDA8Btlx8X1nOHUzGhQvc6bdUjL2f2w==", + "license": "ISC", + "dependencies": { + "debug": "^4.4.3" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mediasoup" + } + }, + "node_modules/axios": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", + "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-loader": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-10.1.1.tgz", + "integrity": "sha512-JwKSzk2kjIe7mgPK+/lyZ2QAaJcpahNAdM+hgR2HI8D0OJVkdj8Rl6J3kaLYki9pwF7P2iWnD8qVv80Lq1ABtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^5.0.0" + }, + "engines": { + "node": "^18.20.0 || ^20.10.0 || >=22.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0 || ^8.0.0-beta.1", + "@rspack/core": "^1.0.0 || ^2.0.0-0", + "webpack": ">=5.61.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.15.tgz", + "integrity": "sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.6", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.0.tgz", + "integrity": "sha512-AvDcMxJ34W4Wgy4KBIIePQTAOP1Ie2WFwkQp3dB7FQ/f0lI5+nM96zUnYEOE1P9sEg0es5VCP0HxiWu5fUHZAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.6", + "core-js-compat": "^3.48.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.6.tgz", + "integrity": "sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.6" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "license": "MIT", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.8", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.8.tgz", + "integrity": "sha512-Y1fOuNDowLfgKOypdc9SPABfoWXuZHBOyCS4cD52IeZBhr4Md6CLLs6atcxVrzRmQ06E7hSlm5bHHApPKR/byA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", + "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true, + "license": "ISC" + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001760", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001760.tgz", + "integrity": "sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/cjs-module-lexer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", + "license": "MIT" + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/core-js-compat": { + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.48.0.tgz", + "integrity": "sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "license": "MIT" + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/diff": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", + "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/discord-api-types": { + "version": "0.38.42", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.42.tgz", + "integrity": "sha512-qs1kya7S84r5RR8m9kgttywGrmmoHaRifU1askAoi+wkoSefLpZP6aGXusjNw5b0jD3zOg3LTwUa3Tf2iHIceQ==", + "license": "MIT", + "workspaces": [ + "scripts/actions/documentation" + ] + }, + "node_modules/discord.js": { + "version": "14.25.1", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.25.1.tgz", + "integrity": "sha512-2l0gsPOLPs5t6GFZfQZKnL1OJNYFcuC/ETWsW4VtKVD/tg4ICa9x+jb9bkPffkMdRpRpuUaO/fKkHCBeiCKh8g==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/builders": "^1.13.0", + "@discordjs/collection": "1.5.3", + "@discordjs/formatters": "^0.6.2", + "@discordjs/rest": "^2.6.0", + "@discordjs/util": "^1.2.0", + "@discordjs/ws": "^1.2.3", + "@sapphire/snowflake": "3.5.3", + "discord-api-types": "^0.38.33", + "fast-deep-equal": "3.1.3", + "lodash.snakecase": "4.1.1", + "magic-bytes.js": "^1.10.0", + "tslib": "^2.6.3", + "undici": "6.21.3" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/dompurify": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.3.tgz", + "integrity": "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/dotenv": { + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/engine.io": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", + "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", + "license": "MIT", + "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/enhanced-resolve": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", + "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/envinfo": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", + "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==", + "dev": true, + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/es-abstract": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/events-alias": { + "name": "events", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-openid-connect": { + "version": "2.19.4", + "resolved": "https://registry.npmjs.org/express-openid-connect/-/express-openid-connect-2.19.4.tgz", + "integrity": "sha512-3YFPZ4MgUPhwfHbCaJKEij7uTc0vF4KpGKsuc3D1IhNMooiP6w8p1HBaaDQOE2KaAas22UghxVECxPpcC/gfOA==", + "license": "MIT", + "dependencies": { + "base64url": "^3.0.1", + "clone": "^2.1.2", + "cookie": "^0.7.2", + "debug": "^4.4.1", + "futoin-hkdf": "^1.5.3", + "http-errors": "^1.8.1", + "joi": "^17.13.3", + "jose": "^2.0.7", + "on-headers": "^1.1.0", + "openid-client": "^4.9.1", + "url-join": "^4.0.1", + "util-promisify": "3.0.0" + }, + "engines": { + "node": "^10.19.0 || >=12.0.0 < 13 || >=13.7.0 < 14 || >= 14.2.0" + }, + "peerDependencies": { + "express": ">= 4.17.0" + } + }, + "node_modules/express-openid-connect/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express-openid-connect/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express-openid-connect/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express-rate-limit": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz", + "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==", + "license": "MIT", + "dependencies": { + "ip-address": "10.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/express/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/express/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fake-mediastreamtrack": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/fake-mediastreamtrack/-/fake-mediastreamtrack-2.2.1.tgz", + "integrity": "sha512-SITLc7UTDirSdgLGORrlmqjWLJtbtfIz/xO7DwVbJH3f0ds+NQok4ccl/WEzz49NSgiUlXf2wDW0+y5C+TO6EA==", + "license": "ISC", + "dependencies": { + "@lukeed/uuid": "^2.0.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fast-xml-builder": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.3.tgz", + "integrity": "sha512-1o60KoFw2+LWKQu3IdcfcFlGTW4dpqEWmjhYec6H82AYZU2TVBXep6tMl8Z1Y+wM+ZrzCwe3BZ9Vyd9N2rIvmg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.1.3" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.4.1.tgz", + "integrity": "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "fast-xml-builder": "^1.0.0", + "strnum": "^2.1.2" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/fill-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz", + "integrity": "sha512-tcgI872xXjwFF4xgQmLxi76GnwJG3g/3isB1l4/G5Z4zrbddGpBjqZCO9oEAcB5wX0Hj/5iQB3toxfO7in1hHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-object": "~1.0.1", + "merge-descriptors": "~1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flatbuffers": { + "version": "25.9.23", + "resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-25.9.23.tgz", + "integrity": "sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ==", + "license": "Apache-2.0" + }, + "node_modules/fluent-ffmpeg": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.3.tgz", + "integrity": "sha512-Be3narBNt2s6bsaqP6Jzq91heDgOEaDCJAXcE3qcma/EJBSy5FB4cvO31XBInuAuKBx8Kptf8dkhjK0IOru39Q==", + "license": "MIT", + "dependencies": { + "async": "^0.2.9", + "which": "^1.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/forwarded-parse": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/forwarded-parse/-/forwarded-parse-2.1.2.tgz", + "integrity": "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==", + "license": "MIT" + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/futoin-hkdf": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/futoin-hkdf/-/futoin-hkdf-1.5.3.tgz", + "integrity": "sha512-SewY5KdMpaoCeh7jachEWFsh1nNlaDjNHZXWqL5IGwtpEYHTgkr2+AMCgNwKWkcc0wpSYrZfR7he4WdmHFtDxQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/h264-profile-level-id": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/h264-profile-level-id/-/h264-profile-level-id-2.3.2.tgz", + "integrity": "sha512-hnq1UDlw7WGJV6GCr/g7wnkHYUjdAY2bis9rgn2JqSdQS2WfVvnt1ZE9g8nTguracodf5LLKZOwURsDN49YtBQ==", + "license": "ISC", + "dependencies": { + "debug": "^4.4.3" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mediasoup" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/helmet": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", + "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "license": "BSD-2-Clause" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/httpolyglot": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/httpolyglot/-/httpolyglot-0.1.2.tgz", + "integrity": "sha512-ouHI1AaQMLgn4L224527S5+vq6hgvqPteurVfbm7ChViM3He2Wa8KP1Ny7pTYd7QKnDSPKcN8JYfC8r/lmsE3A==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "license": "BSD-3-Clause" + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/import-in-the-middle": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-3.0.0.tgz", + "integrity": "sha512-OnGy+eYT7wVejH2XWgLRgbmzujhhVIATQH0ztIeRilwHBjTeG3pD+XnH3PKX0r9gJ0BuJmJ68q/oh9qgXnNDQg==", + "license": "Apache-2.0", + "dependencies": { + "acorn": "^8.15.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^2.2.0", + "module-details-from-path": "^1.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", + "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "license": "MIT" + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/joi": { + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/jose": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/jose/-/jose-2.0.7.tgz", + "integrity": "sha512-5hFWIigKqC+e/lRyQhfnirrAqUdIPMB7SJRqflJaO29dW7q5DFvH1XCSTmv6PQ6pb++0k6MJlLRoS0Wv4s38Wg==", + "license": "MIT", + "dependencies": { + "@panva/asn1.js": "^1.0.0" + }, + "engines": { + "node": ">=10.13.0 < 13 || >=13.7.0" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "29.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.0.tgz", + "integrity": "sha512-9FshNB6OepopZ08unmmGpsF7/qCjxGPbo3NbgfJAnPeHXnsODE9WWffXZtRFRFe0ntzaAOcSKNJFz8wiyvF1jQ==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^5.0.1", + "@asamuzakjp/dom-selector": "^7.0.2", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.1", + "@exodus/bytes": "^1.15.0", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.7", + "parse5": "^8.0.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.1", + "undici": "^7.24.3", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.1", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/jsdom/node_modules/undici": { + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.3.tgz", + "integrity": "sha512-eJdUmK/Wrx2d+mnWWmwwLRyA7OQCkLap60sk3dOK4ViZR7DKwwptwuIvFBg2HaiP9ESaEdhtpSymQPvytpmkCA==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loader-runner": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true, + "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==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-bytes.js": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.13.0.tgz", + "integrity": "sha512-afO2mnxW7GDTXMm5/AoN1WuOcdoKhtgXjIvHmobqTD1grNplhGdv3PFOyjCVmrnOZBIT/gD/koDKpYG+0mvHcg==", + "license": "MIT" + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "license": "ISC" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "license": "CC0-1.0" + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/mediasoup": { + "version": "3.19.18", + "resolved": "https://registry.npmjs.org/mediasoup/-/mediasoup-3.19.18.tgz", + "integrity": "sha512-NRDgJpeFVstZlggVswD+SPTzMUtV4fw74Q1LFPqPnxBBBlKUH2bWKW40bTavTQnlZgiyODWGhVxNQO2TDOwXJw==", + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "debug": "^4.4.3", + "flatbuffers": "^25.9.23", + "h264-profile-level-id": "^2.3.2", + "node-fetch": "^3.3.2", + "supports-color": "^10.2.2", + "tar": "^7.5.11" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mediasoup" + } + }, + "node_modules/mediasoup-client": { + "version": "3.18.7", + "resolved": "https://registry.npmjs.org/mediasoup-client/-/mediasoup-client-3.18.7.tgz", + "integrity": "sha512-110f+zYEvSllYoF6didbIznIvSrTwMY9CqDhdWpq1M0goX1HWLoBdguiYB/MqcA858R2dOKDarP1R4vv5RAg1w==", + "license": "ISC", + "dependencies": { + "@types/debug": "^4.1.12", + "@types/events-alias": "npm:@types/events@^3.0.3", + "awaitqueue": "^3.3.0", + "debug": "^4.4.3", + "events-alias": "npm:events@^3.3.0", + "fake-mediastreamtrack": "^2.2.1", + "h264-profile-level-id": "^2.3.2", + "sdp-transform": "^3.0.0", + "supports-color": "^10.2.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mediasoup" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mocha": { + "version": "11.7.5", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.5.tgz", + "integrity": "sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig==", + "dev": true, + "license": "MIT", + "dependencies": { + "browser-stdout": "^1.3.1", + "chokidar": "^4.0.1", + "debug": "^4.3.5", + "diff": "^7.0.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^10.4.5", + "he": "^1.2.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^9.0.5", + "ms": "^2.1.3", + "picocolors": "^1.1.1", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^9.2.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/mocha/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/mocha/node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/mocha/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/module-details-from-path": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", + "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", + "license": "MIT" + }, + "node_modules/module-not-found-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/module-not-found-error/-/module-not-found-error-1.0.1.tgz", + "integrity": "sha512-pEk4ECWQXV6z2zjhRZUongnLJNUeGQJ3w6OQ5ctGwD+i5o93qjRQUk2Rt6VdNeu3sEP0AB4LcfvdebpxBRVr4g==", + "dev": true, + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nodemailer": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.2.tgz", + "integrity": "sha512-zbj002pZAIkWQFxyAaqoxvn+zoIwRnS40hgjqTXudKOOJkiFFgBeNqjgD3/YCR12sZnrghWYBY+yP1ZucdDRpw==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/nodemon": { + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz", + "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^10.2.1", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/balanced-match": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.3.tgz", + "integrity": "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/nodemon/node_modules/brace-expansion": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", + "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/nodemon/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/nodemon/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/nodemon/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.getownpropertydescriptors": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.8.tgz", + "integrity": "sha512-qkHIGe4q0lSYMv0XI4SsBTJz3WaURhLvd0lKSgtVuOsJ2krg4SgMw3PIRQFMp07yi++UR3se2mkcLqsBNpBb/A==", + "license": "MIT", + "dependencies": { + "array.prototype.reduce": "^1.0.6", + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "gopd": "^1.0.1", + "safe-array-concat": "^1.1.2" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/oidc-token-hash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.1.0.tgz", + "integrity": "sha512-y0W+X7Ppo7oZX6eovsRkuzcSM40Bicg2JEJkDJ4irIt1wsYAP5MLSNv+QAogO8xivMffw/9OvV3um1pxXgt1uA==", + "license": "MIT", + "engines": { + "node": "^10.13.0 || >=12.0.0" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openai": { + "version": "6.32.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.32.0.tgz", + "integrity": "sha512-j3k+BjydAf8yQlcOI7WUQMQTbbF5GEIMAE2iZYCOzwwB3S2pCheaWYp+XZRNAch4jWVc52PMDGRRjutao3lLCg==", + "license": "Apache-2.0", + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/openid-client": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-4.9.1.tgz", + "integrity": "sha512-DYUF07AHjI3QDKqKbn2F7RqozT4hyi4JvmpodLrq0HHoNP7t/AjeG/uqiBK1/N2PZSAQEThVjDLHSmJN4iqu/w==", + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.1.0", + "got": "^11.8.0", + "jose": "^2.0.5", + "lru-cache": "^6.0.0", + "make-error": "^1.3.6", + "object-hash": "^2.0.1", + "oidc-token-hash": "^5.0.1" + }, + "engines": { + "node": "^10.19.0 || >=12.0.0 < 13 || >=13.7.0 < 14 || >= 14.2.0" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/openid-client/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/openid-client/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-expression-matcher": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.1.3.tgz", + "integrity": "sha512-qdVgY8KXmVdJZRSS1JdEPOKPdTiEK/pi0RkcT2sw1RhXxohdujUlJFPuS1TSkevZ9vzd3ZlL7ULl1MHGTApKzQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz", + "integrity": "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/proxyquire": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-2.1.3.tgz", + "integrity": "sha512-BQWfCqYM+QINd+yawJz23tbBM40VIGXOdDw3X344KcclI/gtBbdWF6SlQ4nK/bYhF9d27KYug9WzljHC6B9Ysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-keys": "^1.0.2", + "module-not-found-error": "^1.0.1", + "resolve": "^1.11.1" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpu-core": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.2", + "regjsgen": "^0.8.0", + "regjsparser": "^0.13.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.2.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", + "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.1.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-in-the-middle": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz", + "integrity": "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3" + }, + "engines": { + "node": ">=9.3.0 || >=8.10.0 <9.0.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "license": "MIT" + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sanitize-filename": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", + "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", + "license": "WTFPL OR ISC", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/sdp-transform": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-3.0.0.tgz", + "integrity": "sha512-gfYVRGxjHkGF2NPeUWHw5u6T/KGFtS5/drPms73gaSuMaVHKCY3lpLnGDfswVQO0kddeePoti09AwhYP4zA8dQ==", + "license": "MIT", + "bin": { + "sdp-verify": "checker.js" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/should": { + "version": "13.2.3", + "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", + "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "should-equal": "^2.0.0", + "should-format": "^3.0.3", + "should-type": "^1.4.0", + "should-type-adaptors": "^1.0.1", + "should-util": "^1.0.0" + } + }, + "node_modules/should-equal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", + "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "should-type": "^1.4.0" + } + }, + "node_modules/should-format": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", + "integrity": "sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "should-type": "^1.3.0", + "should-type-adaptors": "^1.0.1" + } + }, + "node_modules/should-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", + "integrity": "sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/should-type-adaptors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", + "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "should-type": "^1.3.0", + "should-util": "^1.0.0" + } + }, + "node_modules/should-util": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz", + "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==", + "dev": true, + "license": "MIT" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sinon": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-21.0.3.tgz", + "integrity": "sha512-0x8TQFr8EjADhSME01u1ZK31yv2+bd6Z5NrBCHVM+n4qL1wFqbxftmeyi3bwlr49FbbzRfrqSFOpyHCOh/YmYA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "^15.1.1", + "@sinonjs/samsam": "^9.0.3", + "diff": "^8.0.3", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/sinon/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/socket.io": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.3.tgz", + "integrity": "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.4.1", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "license": "MIT", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "license": "MIT", + "dependencies": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strnum": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.0.tgz", + "integrity": "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/swagger-ui-dist": { + "version": "5.20.2", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.20.2.tgz", + "integrity": "sha512-zP2biZvCt6R1IAz/iGcjeEViHez7UPHUFfMFyF6jcTKS1ZIP2cgr+KSZEMhBnpIcFfDrZxkD8v56taL5A8phuA==", + "license": "Apache-2.0", + "dependencies": { + "@scarf/scarf": "=1.4.0" + } + }, + "node_modules/swagger-ui-express": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz", + "integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==", + "license": "MIT", + "dependencies": { + "swagger-ui-dist": ">=5.0.0" + }, + "engines": { + "node": ">= v0.10.32" + }, + "peerDependencies": { + "express": ">=4.0.0 || >=5.0.0-beta" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tar": { + "version": "7.5.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.11.tgz", + "integrity": "sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/terser": { + "version": "5.46.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", + "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.17", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.17.tgz", + "integrity": "sha512-YR7PtUp6GMU91BgSJmlaX/rS2lGDbAF7D+Wtq7hRO+MiljNmodYvqslzCFiYVAgW+Qoaaia/QUIP4lGXufjdZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/tldts": { + "version": "7.0.25", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.25.tgz", + "integrity": "sha512-keinCnPbwXEUG3ilrWQZU+CqcTTzHq9m2HhoUP2l7Xmi8l1LuijAXLpAJ5zRW+ifKTNscs4NdCkfkDCBYm352w==", + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.25" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.25", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.25.tgz", + "integrity": "sha512-ZjCZK0rppSBu7rjHYDYsEaMOIbbT+nWF57hKkv4IUmZWBNrBWBOjIElc0mKRgLM8bm7x/BBlof6t2gi/Oq/Asw==", + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tough-cookie": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", + "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", + "license": "WTFPL", + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, + "node_modules/ts-mixer": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", + "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici": { + "version": "6.21.3", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", + "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "license": "MIT" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "license": "MIT" + }, + "node_modules/utf8-byte-length": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", + "license": "(WTFPL OR MIT)" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/util-promisify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/util-promisify/-/util-promisify-3.0.0.tgz", + "integrity": "sha512-uWRZJMjSWt/A1J1exfqz7xiKx2kVpAHR5qIDr6WwwBMQHDoKbo2I1kQN62iA2uXHxOSVpZRDvbm8do+4ijfkNA==", + "license": "MIT", + "dependencies": { + "object.getownpropertydescriptors": "^2.0.3" + } + }, + "node_modules/uuid": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", + "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/watchpack": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/webpack": { + "version": "5.105.4", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.4.tgz", + "integrity": "sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.16.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.20.0", + "es-module-lexer": "^2.0.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.3.1", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.17", + "watchpack": "^2.5.1", + "webpack-sources": "^3.3.4" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-7.0.2.tgz", + "integrity": "sha512-dB0R4T+C/8YuvM+fabdvil6QE44/ChDXikV5lOOkrUeCkW5hTJv2pGLE3keh+D5hjYw8icBaJkZzpFoaHV4T+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "^1.0.0", + "commander": "^14.0.3", + "cross-spawn": "^7.0.6", + "envinfo": "^7.14.0", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^6.0.1" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.101.0", + "webpack-bundle-analyzer": "^4.0.0 || ^5.0.0", + "webpack-dev-server": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/webpack-merge": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", + "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/workerpool": { + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.4.tgz", + "integrity": "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json index 84e640151..a5db8c439 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mirotalksfu", - "version": "1.7.76", + "version": "2.1.51", "description": "WebRTC SFU browser-based video calls", "main": "Server.js", "scripts": { @@ -8,15 +8,15 @@ "start": "node app/src/Server.js", "start-dev": "nodemon app/src/Server.js", "debug": "DEBUG='mediasoup*' node app/src/Server.js", - "compile": "npx browserify public/sfu/MediasoupClientCompile.js -o public/sfu/MediasoupClient.js", + "client-compile": "npx webpack", "lint": "npx prettier --write .", "docker-build": "docker build --tag mirotalk/sfu:latest .", - "docker-rmi": "docker images |grep '' |awk '{print $3}' |xargs docker rmi", + "docker-rmi": "docker image prune -f", "docker-push": "docker push mirotalk/sfu:latest", "docker-pull": "docker pull mirotalk/sfu:latest", "docker-rmc": "docker container rm mirotalksfu --force", - "docker-run": "docker run -d -p 40000-40100:40000-40100 -p 3010:3010 -v ./app/src/config.js:/src/app/src/config.js:ro --name mirotalksfu mirotalk/sfu:latest", - "docker-run-vm": "docker run -d -p 40000-40100:40000-40100 -p 3010:3010 -v ./app/:/src/app/:ro -v ./public/:/src/public/:ro --name mirotalksfu mirotalk/sfu:latest", + "docker-run": "docker run -d -p 40000-40100:40000-40100 -p 3010:3010 -v ./.env:/src/.env -v ./app/src/config.js:/src/app/src/config.js:ro --name mirotalksfu mirotalk/sfu:latest", + "docker-run-vm": "docker run -d -p 40000-40100:40000-40100 -p 3010:3010 -v ./.env:/src/.env -v ./app/:/src/app/:ro -v ./public/:/src/public/:ro --name mirotalksfu mirotalk/sfu:latest", "docker-start": "docker start mirotalksfu", "docker-stop": "docker stop mirotalksfu", "rtmp-start": "docker-compose -f rtmpServers/nginx-rtmp/docker-compose.yml up -d", @@ -54,45 +54,57 @@ "author": "Miroslav Pejic", "license": "AGPL-3.0", "engines": { - "node": ">=18" + "node": ">=22" }, "dependencies": { - "@mattermost/client": "10.2.0", - "@sentry/node": "^9.5.0", - "axios": "^1.8.2", + "@aws-sdk/client-s3": "^3.1010.0", + "@aws-sdk/lib-storage": "^3.1010.0", + "@mattermost/client": "11.4.0", + "@ngrok/ngrok": "1.7.0", + "@sentry/node": "^10.44.0", + "async-mutex": "^0.5.0", + "axios": "^1.13.6", + "chokidar": "^5.0.0", "colors": "1.4.0", - "compression": "1.8.0", - "cors": "2.8.5", + "compression": "1.8.1", + "cors": "2.8.6", "crypto-js": "4.2.0", - "discord.js": "^14.18.0", - "dompurify": "^3.2.4", - "express": "4.21.2", - "express-openid-connect": "^2.17.1", + "discord.js": "^14.25.1", + "dompurify": "^3.3.3", + "dotenv": "^17.3.1", + "express": "5.2.1", + "express-openid-connect": "^2.19.4", + "express-rate-limit": "^8.3.1", "fluent-ffmpeg": "^2.1.3", "he": "^1.2.0", - "helmet": "^8.0.0", + "helmet": "^8.1.0", "httpolyglot": "0.1.2", - "js-yaml": "^4.1.0", - "jsdom": "^26.0.0", - "jsonwebtoken": "^9.0.2", - "mediasoup": "3.15.5", - "mediasoup-client": "3.9.1", - "ngrok": "^5.0.0-beta.2", - "nodemailer": "^6.10.0", - "openai": "^4.86.2", - "qs": "6.14.0", + "js-yaml": "^4.1.1", + "jsdom": "^29.0.0", + "jsonwebtoken": "^9.0.3", + "mediasoup": "3.19.18", + "mediasoup-client": "3.18.7", + "mime-types": "^3.0.2", + "nodemailer": "^8.0.2", + "openai": "^6.32.0", + "qs": "6.15.0", "sanitize-filename": "^1.6.3", - "socket.io": "4.8.1", + "socket.io": "4.8.3", "swagger-ui-express": "5.0.1", - "uuid": "11.1.0" + "uuid": "13.0.0" }, "devDependencies": { - "mocha": "^11.1.0", + "@babel/core": "^7.29.0", + "@babel/preset-env": "^7.29.2", + "babel-loader": "^10.1.1", + "mocha": "^11.7.5", "node-fetch": "^3.3.2", - "nodemon": "^3.1.9", - "prettier": "3.5.3", + "nodemon": "^3.1.14", + "prettier": "3.8.1", "proxyquire": "^2.1.3", "should": "^13.2.3", - "sinon": "^19.0.2" + "sinon": "^21.0.3", + "webpack": "^5.105.4", + "webpack-cli": "^7.0.2" } } diff --git a/public/advertisers/Rambox.png b/public/advertisers/Rambox.png new file mode 100644 index 000000000..e8ef85900 Binary files /dev/null and b/public/advertisers/Rambox.png differ diff --git a/public/advertisers/RamboxLogo.png b/public/advertisers/RamboxLogo.png new file mode 100644 index 000000000..671fe8115 Binary files /dev/null and b/public/advertisers/RamboxLogo.png differ diff --git a/public/css/ActiveRooms.css b/public/css/ActiveRooms.css new file mode 100644 index 000000000..1229f5cc1 --- /dev/null +++ b/public/css/ActiveRooms.css @@ -0,0 +1,195 @@ +@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@500&display=swap'); + +:root { + --primary-bg: #1e2229; + --card-bg: #15181d; + --accent: #1870d7; + --accent2: #2186ff; + --text: #f6f8fa; + --card-shadow: 0 2px 8px #15181d; +} + +body { + font-family: 'Montserrat'; + background: var(--primary-bg); + margin: 0; + padding: 0; + color: var(--text); +} + +.container { + max-width: 1200px; + margin: 40px auto; + background: var(--card-bg); + border-radius: 12px; + box-shadow: var(--card-shadow); + padding: 32px; +} + +h1 { + text-align: center; + color: var(--accent2); + margin-bottom: 32px; + letter-spacing: 1px; +} + +.search-bar { + display: flex; + justify-content: center; + margin-bottom: 28px; +} + +.search-input { + padding: 10px 16px; + border-radius: 6px 0 0 6px; + border: none; + font-size: 1.1em; + background: #23262e; + color: var(--text); + outline: none; + width: 220px; +} + +.search-btn { + padding: 10px 20px; + border-radius: 0 6px 6px 0; + border: none; + background: var(--accent); + color: #fff; + font-size: 1.1em; + cursor: pointer; + font-weight: bold; + transition: background 0.2s; +} + +.search-btn:hover { + background: var(--accent2); +} + +.rooms { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 28px; + justify-content: center; +} + +.room-card { + background: var(--accent); + border-radius: 12px; + box-shadow: 0 1px 6px #15181d; + padding: 32px 24px; + min-height: 220px; + text-align: center; + transition: + box-shadow 0.2s, + transform 0.2s; + color: var(--text); + position: relative; + display: flex; + flex-direction: column; + justify-content: flex-start; +} + +.room-card:hover { + cursor: pointer; + transform: translateY(-4px) scale(1.03); +} + +.room-title { + font-size: 1.15em; + color: #f6f8fa; + margin-bottom: 14px; + font-weight: bold; + letter-spacing: 0.5px; + word-break: break-all; + overflow-wrap: anywhere; + max-width: 100%; +} + +.peer-count { + font-size: 2.3em; + color: #ffd700; + font-weight: bold; + margin-bottom: 8px; + margin-top: auto; + align-self: center; +} + +.peer-label { + font-size: 0.9em; + color: #f6f8fa; + opacity: 0.7; + margin-bottom: 60px; + align-self: center; +} + +.empty { + text-align: center; + color: #b3c6e0; + margin-top: 40px; + font-size: 1.2em; +} + +.refresh-btn { + display: block; + margin: 0 auto 32px auto; + padding: 10px 28px; + background: var(--accent2); + color: #fff; + border: none; + border-radius: 7px; + cursor: pointer; + font-size: 1.1em; + font-weight: bold; + letter-spacing: 0.5px; + box-shadow: 0 1px 4px #15181d; + transition: background 0.2s; +} + +.refresh-btn:hover { + background: var(--accent); +} + +.join-btn { + display: block; + margin: 0 auto 0 auto; + position: absolute; + left: 50%; + bottom: 18px; + transform: translateX(-50%); + padding: 10px 24px; + background: var(--accent2); + color: #fff; + border-radius: 6px; + font-size: 1em; + font-weight: bold; + text-decoration: none; + transition: background 0.2s; + box-shadow: 0 1px 4px #15181d; +} + +.join-btn:hover { + background: var(--accent); +} + +@media (max-width: 900px) { + .container { + max-width: 98vw; + padding: 16px; + } + .rooms { + gap: 16px; + } +} + +@media (max-width: 600px) { + .rooms { + grid-template-columns: 1fr; + } + .room-card { + padding: 20px 10px; + } + .peer-label { + margin-bottom: 70px; + } +} diff --git a/public/css/CustomizeRoom.css b/public/css/CustomizeRoom.css new file mode 100644 index 000000000..66c1dbaef --- /dev/null +++ b/public/css/CustomizeRoom.css @@ -0,0 +1,467 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap'); + +:root { + /* Base colors (keep simple for Dark Reader compatibility) */ + --cr-bg-color: #06070a; + --cr-bg-overlay: + radial-gradient(900px circle at 50% 10%, rgba(255, 255, 255, 0.06), transparent 55%), + radial-gradient(700px circle at 20% 70%, rgba(255, 255, 255, 0.035), transparent 60%); + --cr-bg-overlay-opacity: 1; + + --cr-card-bg: rgba(255, 255, 255, 0.05); + --cr-card-border: rgba(255, 255, 255, 0.12); + --cr-card-border-strong: rgba(255, 255, 255, 0.18); + + --cr-text: rgba(255, 255, 255, 0.92); + --cr-text-muted: rgba(255, 255, 255, 0.7); + + --cr-input-bg: rgba(255, 255, 255, 0.04); + --cr-input-border: rgba(255, 255, 255, 0.14); + --cr-input-border-focus: rgba(255, 255, 255, 0.28); + + --cr-shadow: 0 18px 60px rgba(0, 0, 0, 0.55); + + /* Accent */ + --cr-accent-green: #22c55e; + --cr-accent-green-2: #16a34a; +} + +* { + box-sizing: border-box; +} + +html { + height: 100%; + color-scheme: dark; + background-color: var(--cr-bg-color); +} + +body { + min-height: 100%; + margin: 0; + font-family: + Inter, + system-ui, + -apple-system, + Segoe UI, + Roboto, + Helvetica, + Arial, + sans-serif; + background-color: var(--cr-bg-color); + color: var(--cr-text); + position: relative; +} + +.cr-home { + position: fixed; + top: 14px; + left: 14px; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 8px; + border-radius: 14px; + border: 1px solid rgba(255, 255, 255, 0.12); + background: rgba(255, 255, 255, 0.04); + backdrop-filter: blur(10px); + z-index: 10; + text-decoration: none; + transition: + background 160ms ease, + border-color 160ms ease, + transform 120ms ease; +} + +.cr-home:hover { + background: rgba(255, 255, 255, 0.07); + border-color: rgba(255, 255, 255, 0.18); +} + +.cr-home:active { + transform: translateY(1px); +} + +.cr-home:focus-visible { + outline: 3px solid rgba(255, 255, 255, 0.25); + outline-offset: 2px; +} + +.cr-home img { + display: block; +} + +/* Subtle ambient overlay (kept separate so Dark Reader has less to "interpret") */ +body::before { + content: ''; + position: fixed; + inset: 0; + background: var(--cr-bg-overlay); + opacity: var(--cr-bg-overlay-opacity); + pointer-events: none; + z-index: -1; +} + +.cr-page { + min-height: 100vh; + padding: 32px 16px; + display: grid; + place-items: center; +} + +.cr-card { + width: min(520px, 100%); + background: var(--cr-card-bg); + border: 1px solid var(--cr-card-border); + border-radius: 18px; + box-shadow: var(--cr-shadow); + padding: 28px 22px; + backdrop-filter: blur(10px); +} + +/* Accessibility & Dark Reader edge cases */ +@media (prefers-contrast: more), (forced-colors: active) { + body::before { + display: none; + } + .cr-card { + backdrop-filter: none; + } +} + +.cr-title { + font-size: 28px; + line-height: 1.2; + font-weight: 700; + margin: 0 0 22px 0; + letter-spacing: -0.02em; +} + +.cr-form { + display: grid; + gap: 16px; +} + +.cr-field { + display: grid; + gap: 8px; +} + +.cr-label { + font-size: 15px; + font-weight: 600; + color: var(--cr-text); +} + +.cr-input { + height: 48px; + padding: 0 14px; + border-radius: 12px; + border: 1px solid var(--cr-input-border); + background: var(--cr-input-bg); + color: var(--cr-text); + outline: none; + font-size: 16px; + transition: + border-color 160ms ease, + background 160ms ease, + box-shadow 160ms ease; +} + +.cr-input::placeholder { + color: rgba(255, 255, 255, 0.4); +} + +.cr-input:focus { + border-color: var(--cr-input-border-focus); + box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.06); +} + +.cr-input-wrap { + position: relative; + display: block; +} + +.cr-input-wrap .cr-input { + width: 100%; + padding-right: 96px; +} + +.cr-input-action { + position: absolute; + top: 6px; + right: 6px; + height: 36px; + padding: 0 12px; + border-radius: 10px; + border: 1px solid rgba(255, 255, 255, 0.16); + background: rgba(255, 255, 255, 0.06); + color: var(--cr-text); + font-weight: 700; + cursor: pointer; + transition: + background 160ms ease, + border-color 160ms ease, + transform 120ms ease; +} + +.cr-input-action:hover { + background: rgba(255, 255, 255, 0.1); + border-color: rgba(255, 255, 255, 0.22); +} + +.cr-input-action:active { + transform: translateY(1px); +} + +.cr-input-action:focus-visible { + outline: 3px solid rgba(255, 255, 255, 0.25); + outline-offset: 2px; +} + +.cr-toggle-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 12px; + margin-top: 4px; +} + +.cr-toggle { + display: flex; + align-items: center; + justify-content: space-between; + gap: 14px; + padding: 14px 14px; + border-radius: 14px; + border: 1px solid var(--cr-card-border); + background: rgba(255, 255, 255, 0.03); + cursor: pointer; + user-select: none; + transition: + border-color 160ms ease, + background 160ms ease, + transform 120ms ease; +} + +.cr-toggle:focus-within, +.cr-toggle:hover { + border-color: var(--cr-card-border-strong); + background: rgba(255, 255, 255, 0.05); +} + +.cr-toggle:active { + transform: translateY(1px); +} + +.cr-toggle-text { + font-size: 16px; + font-weight: 600; + color: var(--cr-text); +} + +.cr-switch-wrap { + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + width: 54px; + height: 32px; + flex: 0 0 auto; +} + +.cr-switch-wrap input { + position: absolute; + inset: 0; + opacity: 0; + cursor: pointer; + margin: 0; +} + +.cr-switch { + width: 54px; + height: 32px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.14); + border: 1px solid rgba(255, 255, 255, 0.14); + position: relative; + transition: + background 160ms ease, + border-color 160ms ease, + box-shadow 160ms ease; +} + +.cr-switch::after { + content: ''; + position: absolute; + top: 50%; + left: 4px; + width: 24px; + height: 24px; + transform: translateY(-50%); + border-radius: 50%; + background: rgba(255, 255, 255, 0.92); + box-shadow: 0 8px 18px rgba(0, 0, 0, 0.35); + transition: left 160ms ease; +} + +.cr-switch-wrap input:checked + .cr-switch { + background: linear-gradient(180deg, var(--cr-accent-green), var(--cr-accent-green-2)); + border-color: rgba(34, 197, 94, 0.65); + box-shadow: 0 0 0 4px rgba(34, 197, 94, 0.14); +} + +.cr-switch-wrap input:checked + .cr-switch::after { + left: 26px; +} + +.cr-switch-wrap input:focus-visible + .cr-switch { + box-shadow: + 0 0 0 4px rgba(255, 255, 255, 0.1), + 0 0 0 7px rgba(34, 197, 94, 0.14); +} + +.cr-hint { + margin: 0; + font-size: 12px; + color: var(--cr-text-muted); +} + +.cr-submit { + margin-top: 6px; + height: 52px; + border-radius: 14px; + border: 1px solid rgba(255, 255, 255, 0.18); + background: rgba(255, 255, 255, 0.9); + color: rgba(0, 0, 0, 0.92); + font-size: 18px; + font-weight: 700; + cursor: pointer; + transition: + transform 120ms ease, + filter 120ms ease, + box-shadow 160ms ease; +} + +.cr-submit:hover { + filter: brightness(1.02); + box-shadow: 0 14px 34px rgba(0, 0, 0, 0.35); +} + +.cr-submit:active { + transform: translateY(1px); +} + +.cr-error { + margin: 0; + font-size: 13px; + color: rgba(255, 138, 138, 0.95); +} + +.cr-preview-row { + display: grid; + grid-template-columns: 1fr auto; + gap: 10px; + align-items: center; +} + +.cr-actions { + display: inline-flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; + justify-content: flex-end; +} + +.cr-preview { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; + font-size: 13px; +} + +.cr-copy { + height: 48px; + padding: 0 14px; + border-radius: 12px; + border: 1px solid rgba(255, 255, 255, 0.16); + background: rgba(255, 255, 255, 0.06); + color: var(--cr-text); + font-weight: 700; + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 10px; + transition: + background 160ms ease, + border-color 160ms ease, + transform 120ms ease; +} + +.cr-copy:hover { + background: rgba(255, 255, 255, 0.1); + border-color: rgba(255, 255, 255, 0.22); +} + +.cr-copy:active { + transform: translateY(1px); +} + +.cr-copy:disabled { + opacity: 0.55; + cursor: not-allowed; +} + +.cr-qr-wrap { + margin-top: 10px; + padding: 12px; + border-radius: 14px; + border: 1px solid var(--cr-card-border); + background: rgba(255, 255, 255, 0.03); + display: grid; + gap: 10px; + place-items: center; +} + +.cr-qr { + width: 180px; + height: 180px; + padding: 10px; + border-radius: 14px; + border: 1px solid var(--cr-input-border); + background: var(--cr-input-bg); + display: grid; + place-items: center; +} + +.cr-qr canvas, +.cr-qr img { + display: block; + max-width: 100%; + max-height: 100%; + border-radius: 10px; +} + +.cr-qr-hint { + text-align: center; +} + +@media (max-width: 520px) { + .cr-card { + padding: 24px 18px; + border-radius: 16px; + } + + .cr-title { + font-size: 24px; + } + + .cr-toggle-grid { + grid-template-columns: 1fr 1fr; + } + + .cr-preview-row { + grid-template-columns: 1fr; + } + + .cr-actions { + justify-content: flex-start; + } +} diff --git a/public/css/Editor.css b/public/css/Editor.css index 53a839aca..2e81ca32c 100644 --- a/public/css/Editor.css +++ b/public/css/Editor.css @@ -4,7 +4,7 @@ padding: 20px; width: var(--editor-container-width); height: var(--editor-container-height); - border: var(--border); + /* border: var(--border); */ border-radius: 10px; background: var(--body-bg); box-shadow: var(--box-shadow); diff --git a/public/css/GroupChat.css b/public/css/GroupChat.css index 95b28308f..a3245a1e7 100644 --- a/public/css/GroupChat.css +++ b/public/css/GroupChat.css @@ -17,7 +17,7 @@ min-height: var(--msger-height); padding: 3px; background: var(--msger-bg); - border: var(--border); + /* border: var(--border); */ border-radius: 10px; box-shadow: var(--box-shadow); transition: @@ -116,8 +116,10 @@ /* Chat app container */ .chat-app .chat { position: relative; + display: flex; + flex-direction: column; margin-left: 300px; - border-left: var(--border); + /* border-left: var(--border); */ border-radius: 10px; /* border: 1px solid lime; */ } @@ -125,6 +127,7 @@ /* Chat header */ .chat .chat-header { padding: 15px 20px; /* top, right, bottom, left */ + flex: 0 0 auto; border-bottom: var(--border); height: 70px; max-height: 70px; @@ -134,7 +137,6 @@ } .all-participants-img { - border: var(--border); width: 40px; margin-right: 5px; cursor: pointer; @@ -173,10 +175,11 @@ /* Chat history */ .chat .chat-history { padding: 20px; - height: calc(100vh - 210px); - min-height: 490px; - max-height: 490px; - border-bottom: var(--border); + flex: 1 1 auto; + height: auto; + min-height: 0; + max-height: none; + /* border-bottom: var(--border); */ overflow-y: auto; overflow-x: hidden; /* border: 1px solid lime; */ @@ -268,8 +271,13 @@ /* Chat message */ .chat .chat-message { + z-index: 4; padding: 20px; - max-height: 140px; + flex: 0 0 auto; + max-height: none; + position: sticky; + bottom: 0; + background: transparent !important; overflow-y: auto; overflow-x: hidden; /* border: 1px solid lime; */ @@ -369,7 +377,7 @@ input[type='text'] { padding: 8px 16px; background: var(--body-bg); border-radius: 5px; - border: var(--border); + /* border: var(--border); */ transition: all 0.3s ease-in-out; } @@ -382,5 +390,6 @@ input[type='text'] { @media screen and (max-width: 600px) { .people-list { width: 100% !important; + height: 100% !important; } } diff --git a/public/css/Polls.css b/public/css/Polls.css index ebdfa204d..48d72769d 100644 --- a/public/css/Polls.css +++ b/public/css/Polls.css @@ -8,7 +8,7 @@ max-height: 700px; border-radius: 10px; background: var(--body-bg); - border: var(--border); + /* border: var(--border); */ box-shadow: var(--box-shadow); overflow: hidden; transition: max-width 0.5s ease-in-out; diff --git a/public/css/Room.css b/public/css/Room.css index 6a0fb4946..de83b12bb 100644 --- a/public/css/Room.css +++ b/public/css/Room.css @@ -67,6 +67,7 @@ body { --------------------------------------------------------------*/ #loadingDiv { + z-index: 9999; color: #fff; padding: 30px; text-align: center; @@ -100,6 +101,7 @@ body { } .init-user { + z-index: 9999; display: flex; padding: 5px; } @@ -148,6 +150,10 @@ body { .init-modal-size { width: 480px !important; } + .init-user button { + width: 42px !important; + height: 42px !important; + } } .init-user select { @@ -345,60 +351,13 @@ body { } /*-------------------------------------------------------------- -# Buttons bar +# Session Time --------------------------------------------------------------*/ -#control { - z-index: 3; - position: absolute; - display: none; - padding: 5px; - - top: var(--btns-top); - right: var(--btns-right); - left: var(--btns-left); - margin-left: var(--btns-margin-left); - width: var(--btns-width); - flex-direction: var(--btns-flex-direction); - - justify-content: center; - grid-gap: 0.5rem; - - -webkit-transform: translate(0%, -50%); - -ms-transform: translate(0%, -50%); - transform: translate(0%, -50%); - - /* border: var(--border); */ - /* box-shadow: var(--box-shadow); */ - border-radius: 10px; -} - -#control button { - font-size: 1rem; - padding: 10px; - background: var(--btns-bg-color); - border-radius: 10px; - border: none !important; - /* border: var(--border); */ - /* box-shadow: var(--box-shadow); */ - transition: all 0.3s ease-in-out; -} - -#control button:hover { - transform: translateY(-3px); - background: var(--body-bg) !important; -} - -#control button i { - font-size: 1.2rem; -} - -#exitButton { - color: red; -} - -#toggleExtraButton { - color: #66beff; +.current-session-time { + font-size: 14px; + color: white; + margin-right: 5px; } /*-------------------------------------------------------------- @@ -424,9 +383,49 @@ body { /* box-shadow: var(--box-shadow); */ } +/* Split button (main action + device arrow) */ +#bottomButtons .split-btn { + position: relative; + display: inline-flex; + align-items: stretch; + overflow: visible; + border-radius: 10px; +} + +/* Let the dropdown menu anchor to the whole split button (not only the arrow). */ +#bottomButtons #startAudioDeviceDropdown, +#bottomButtons #startVideoDeviceDropdown, +#bottomButtons #settingsExtraDropdown { + position: static; +} + +#bottomButtons .split-btn > button { + border-radius: 0; + margin: 0; +} + +#bottomButtons .split-btn > button:not(.device-dropdown-toggle) { + border-top-left-radius: 10px; + border-bottom-left-radius: 10px; +} + +#bottomButtons .split-btn .device-dropdown-toggle { + border-radius: 0; + border-top-right-radius: 10px; + border-bottom-right-radius: 10px; + border-left: var(--border); + margin: 0; +} + +/* Keep the group visually together when hovering. */ +#bottomButtons .split-btn button:hover { + transform: none; +} + #bottomButtons button { - width: 48px; - font-size: 1.4rem; + width: 50px; + height: 50px; + font-size: 1.6rem; padding: 10px; border-radius: 10px; background: var(--btns-bg-color); @@ -434,22 +433,249 @@ body { transition: all 0.3s ease-in-out; } +/* Quick device picker (Start Audio/Video) */ +#bottomButtons .device-dropdown-toggle { + width: 35px; + height: 50px; + padding: 0; + display: inline-flex; + align-items: center; + justify-content: center; + line-height: 1; +} + +#bottomButtons .device-dropdown-toggle i { + color: #fff !important; + font-size: 0.9rem; +} + +#bottomButtons .dropup .dropdown-menu { + z-index: 9999; + position: absolute; + left: 0; + padding: 10px 5px; + bottom: 14px !important; + border: var(--border); + background: var(--body-bg); + box-shadow: var(--box-shadow); +} + +/* Override bottomButtons button rule for dropdown menu items */ +#bottomButtons .dropdown-menu button { + width: auto; + font-size: 0.8em; + padding: 8px 16px; + border-radius: 0; + background: none; + transition: background 0.2s; +} + +/* Quick device picker dropdown menu: keep items left-aligned and avoid wrapping */ +#bottomButtons #startAudioDeviceMenu, +#bottomButtons #startVideoDeviceMenu { + min-width: 280px !important; + max-width: calc(100vw - 24px) !important; + text-align: left; + border: var(--border) !important; +} + +#bottomButtons #settingsExtraMenu { + min-width: 180px !important; + max-width: calc(100vw - 24px) !important; + text-align: left; + border: var(--border) !important; +} + +/* Mobile: keep settingsExtraMenu inside viewport and scrollable */ +@media (max-width: 600px) { + #bottomButtons #startAudioDeviceMenu, + #bottomButtons #startVideoDeviceMenu, + #bottomButtons #settingsExtraMenu { + min-width: unset; + max-width: 90vw !important; + left: 5vw !important; + right: 5vw !important; + position: fixed !important; + bottom: 70px !important; + z-index: 9999; + overflow-y: auto; + max-height: 60vh; + border-radius: var(--border-radius); + box-shadow: var(--box-shadow); + } +} + +/* Audio device dropdown: section headers + divider (Microphones / Speakers / Camera) */ +#bottomButtons #startAudioDeviceMenu .device-menu-header, +#bottomButtons #startVideoDeviceMenu .device-menu-header { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 12px; + font-size: 1rem; + font-weight: 600; + color: #fff; + user-select: none; +} + +#bottomButtons #startAudioDeviceMenu .device-menu-header i, +#bottomButtons #startVideoDeviceMenu .device-menu-header i { + width: 18px; + text-align: center; +} + +#bottomButtons #startAudioDeviceMenu .device-menu-divider, +#bottomButtons #startVideoDeviceMenu .device-menu-divider { + height: 1px; + background: var(--border-color); + opacity: 0.6; + margin: 8px 0; +} + +#bottomButtons #startAudioDeviceMenu button, +#bottomButtons #startVideoDeviceMenu button, +#bottomButtons #settingsExtraMenu button { + display: flex; + width: 100%; + height: 35px; + align-items: center; + justify-content: flex-start; + text-align: left; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + transform: none !important; +} + +#bottomButtons #settingsExtraMenu button i { + width: 20px; + min-width: 20px; + text-align: center; + margin-right: 10px; + font-size: 1.1em; + display: inline-block; +} + +/* Action buttons in device menu */ +#bottomButtons #startAudioDeviceMenu .device-menu-action-btn, +#bottomButtons #startVideoDeviceMenu .device-menu-action-btn, +#bottomButtons #settingsExtraMenu .device-menu-action-btn { + background: var(--body-bg); + border: 1px solid var(--border-color); + color: #fff; + font-weight: 500; + gap: 8px; + transition: all 0.2s ease; +} + +#bottomButtons #startAudioDeviceMenu .device-menu-action-btn:hover, +#bottomButtons #startVideoDeviceMenu .device-menu-action-btn:hover, +#bottomButtons #settingsExtraMenu .device-menu-action-btn:hover { + background: var(--btns-bg-color) !important; + border-color: var(--btns-color); + transform: translateY(-1px) !important; +} + +#bottomButtons #startAudioDeviceMenu .device-menu-action-btn i, +#bottomButtons #startVideoDeviceMenu .device-menu-action-btn i, +#bottomButtons #settingsExtraMenu .device-menu-action-btn i { + width: 18px; + text-align: center; +} + #bottomButtons button:hover { transform: var(--btns-hover-scale); background: var(--body-bg) !important; } @media screen and (max-width: 500px) { + #bottomButtons { + gap: 0.3rem; + padding: 3px; + } #bottomButtons button { - width: 42px; - font-size: 1rem; + width: 38px; + height: 38px; + font-size: 0.95rem; + padding: 8px; + margin: 0 1px; + } + #bottomButtons .device-dropdown-toggle { + width: 19px; + height: 38px; + } + #bottomButtons .device-dropdown-toggle i { + font-size: 0.8rem; } } -@media screen and (max-width: 350px) { +@media screen and (max-width: 360px) { + #bottomButtons { + gap: 0.15rem; + padding: 2px; + } #bottomButtons button { - width: 30px; - font-size: 0.6rem; + width: 28px; + height: 28px; + font-size: 0.65rem; + padding: 6px; + margin: 0 1px; + } + #bottomButtons .device-dropdown-toggle { + width: 14px; + height: 28px; + } + #bottomButtons .device-dropdown-toggle i { + font-size: 0.7rem; + } +} + +#participantsButton { + position: relative; +} + +/* Participants count badge for participants button */ +#participantsCountBadge { + position: absolute; + top: 2px; + right: 2px; + min-width: 16px; + height: 16px; + background: #e74c3c; + color: #fff; + border-radius: 8px; + font-size: 11px; + display: flex; + align-items: center; + justify-content: center; + padding: 0 0.4em; + pointer-events: none; + z-index: 2; + display: none; + box-sizing: border-box; + transition: + font-size 0.2s, + min-width 0.2s, + height 0.2s; +} + +@media (max-width: 600px) { + #participantsCountBadge { + font-size: 12px; + min-width: 15px; + height: 15px; + top: 1px; + right: 1px; + } +} + +@media (max-width: 400px) { + #participantsCountBadge { + font-size: 13px; + min-width: 14px; + height: 14px; + top: 0px; + right: 0px; } } @@ -463,6 +689,34 @@ body { align-items: center; } +#qrRoomPopupContainer { + z-index: 9999; + position: fixed; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + padding: 10px; + background: var(--body-bg); + border: var(--border); + border-radius: var(--border-radius); + box-shadow: var(--box-shadow); +} + +#qrText { + margin-top: 10px; + color: #fff; + font-size: 16px; + font-weight: bold; + text-align: center; + width: 100%; +} + +#qrRoomPopup { + width: 256px; + height: 256px; +} + /*-------------------------------------------------------------- # My settings --------------------------------------------------------------*/ @@ -478,12 +732,16 @@ body { box-shadow: var(--box-shadow); border: var(--border); border-radius: 10px; - overflow-y: auto; + overflow-y: auto; /* Single overflow handling */ overflow-x: hidden; + display: flex; + flex-direction: column; } .mySettingsMain { display: flex; + flex: 1; + min-height: 0; } /* Medium screens */ @@ -568,8 +826,8 @@ body { .title { display: inline-flex; - justify-content: center; /* Vertical centering */ - align-items: center; /* Horizontal centering */ + justify-content: center; + align-items: center; text-align: left; color: white; } @@ -584,8 +842,8 @@ body { .inline-check-box { margin-bottom: 20px; display: inline-flex; - justify-content: center; /* Vertical centering */ - align-items: center; /* Horizontal centering */ + justify-content: center; + align-items: center; text-align: left; color: white; } @@ -642,7 +900,7 @@ body { } .settingsTable td:first-child { - width: auto; + width: 75%; } .settingsTable td:last-child { @@ -705,7 +963,7 @@ body { width: 100%; border-collapse: collapse; border: var(--border); - table-layout: fixed; /* Ensures equal column width */ + table-layout: fixed; } .file-table th, .file-table td { @@ -751,6 +1009,17 @@ body { align-items: center; } +.input-container button { + flex: 1; + width: 20px; +} + +.buttons-container { + display: flex; + justify-content: space-between; + gap: 6px; +} + #rtmpStreamURL, #rtmpLiveUrl { margin-top: 5px; @@ -771,11 +1040,6 @@ body { background: var(--select-bg) !important; } -.input-container button { - flex: 1; - width: 20px; -} - .btn-custom { width: 100%; background: var(--body-bg); @@ -798,7 +1062,7 @@ body { .dropdown-menu { position: fixed; /* max-height: 300px; */ - border: none; + border: none !important; border-radius: 10px !important; background: var(--body-bg) !important; box-shadow: var(--box-shadow); @@ -840,6 +1104,10 @@ body { background: transparent; } +.dropdown-toggle { + color: #66beff; +} + /* Hide the default Bootstrap dropdown icon */ .dropdown-toggle::after { display: none !important; @@ -864,23 +1132,74 @@ body { .tabActions { position: relative; width: 65%; + overflow: hidden; + display: flex; + flex-direction: column; } @media screen and (max-width: 830px) { - .tab { - display: inline; - width: 100%; - min-height: auto; + .mySettingsMain { flex-direction: row; - border-right: none !important; } - .tabActions { - width: 100%; + .tab { + width: 60px; + min-width: 60px; + max-width: 60px; + flex-direction: column; + border-right: var(--border); + height: 100vh; + min-height: 100vh; + padding: 10px 0; + background: var(--body-bg); + } + .tab button { + width: 44px; + height: 44px; + margin: 10px auto; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.5rem; + padding: 0; border-radius: 10px; + box-sizing: border-box; + transition: + background 0.2s, + transform 0.2s; + } + .tab button i, + .tab button svg { + margin: 0; + font-size: 1.5rem; } .tabButtonText { display: none !important; } + .tabActions { + width: 100%; + border-radius: 10px; + overflow: visible; + } +} + +@media screen and (max-width: 500px) { + .tab { + width: 48px; + min-width: 48px; + max-width: 48px; + padding: 6px 0; + border-radius: 8px; + } + .tab button { + width: 38px; + height: 38px; + margin: 8px auto; + font-size: 1.2rem; + border-radius: 8px; + } + .tabActions { + padding: 6px; + } } /* Style the buttons inside the tab */ @@ -913,15 +1232,14 @@ body { /* Style the tab content */ .tabcontent { display: none; - margin-top: 15px; padding: 6px 12px; width: 100%; - max-height: 585px; - min-height: 700px; + flex: 1; + max-height: none; + min-height: auto; border-top: none; background-color: var(--body-bg); - overflow-x: hidden; - overflow-y: auto; + overflow: visible; } .tabcontent button { @@ -990,7 +1308,7 @@ body { margin: 0 2px; background-color: #ddd; border-radius: 5px; - transition: background-color 0.3s ease; + transition: all 0.3s ease; } .active { @@ -1009,6 +1327,23 @@ body { display: none; } +/*-------------------------------------------------------------- +# Settings Notifications +--------------------------------------------------------------*/ + +#notificationsModeDiv input[type='text'] { + width: 100%; + color: #fff; + padding: 10px; + margin: 10px 0; + border: var(--border); + border-radius: 4px; +} + +#notifyEmailCleanBtn { + width: 130px; +} + /*-------------------------------------------------------------- # Transcription Room --------------------------------------------------------------*/ @@ -1022,7 +1357,7 @@ body { min-height: var(--transcription-height); min-width: var(--transcription-width); background: var(--trx-bg); - border: var(--border); + /* border: var(--border); */ border-radius: 10px; box-shadow: var(--box-shadow); resize: both; @@ -1147,8 +1482,8 @@ body { .transcription-inputarea { display: inline-flex; padding: 10px; - border: none; - border: var(--border); + background: var(--body-bg); + border: none !important; } .transcription-inputarea select { @@ -1185,42 +1520,127 @@ body { --color-border-over: var(--body-bg); } +/* Emoji Picker (Room) */ + .roomEmoji { - z-index: 9; + z-index: 18; position: absolute; display: none; - top: 50%; - left: 50%; - border-radius: 10px; - border: var(--border); background: var(--body-bg); + border: var(--border); + border-radius: 10px; box-shadow: var(--box-shadow); + top: 50%; + left: 50%; + transform: translate(-50%, -50%); --rgb-background: var(--body-bg); --color-border-over: var(--body-bg); --font-family: 'Montserrat'; - transform: translate(-50%, -50%); } -.userEmoji { - z-index: 9; - position: absolute; - left: 80px; - bottom: 60px; +.room-emoji-header { + display: flex; + justify-content: space-between; + align-items: center; + background: var(--body-bg); + border-top-left-radius: 12px; + border-top-right-radius: 12px; + padding: 8px 12px 4px 12px; + cursor: move; } -.emojiPickerHeader { - display: flex; - padding: 10px; - justify-content: flex-start; +.room-emoji-title { + font-weight: bold; + font-size: 1.05rem; color: #fff; +} + +.room-emoji-close-btn { + display: inline-block; + position: static; + margin-left: 8px; + font-size: 1.2rem; + background: none; + border: none; + cursor: pointer; + color: #fff; +} + +.room-emoji-close-btn:hover { background: var(--body-bg); - border-top-left-radius: 10px; - border-top-right-radius: 10px; - cursor: move; + border-radius: 50%; } -#closeEmojiPickerContainer { - font-size: 1.3rem; +.room-emoji-tab-container { + display: flex; + justify-content: center; + margin: 0 0 8px 0; + gap: 6px; +} + +.room-emoji-tab { + margin-top: 10px; + background: none; + color: #fff; + border: none; + border-radius: 8px; + padding: 3px 14px; + font-size: 0.95rem; + cursor: pointer; + transition: background 0.2s; +} +.room-emoji-tab.active { + background: rgba(255, 255, 255, 0.08); +} + +.room-emoji-mart { + display: block; +} + +.room-emoji-grid { + display: none; + gap: 8px; + font-size: 2rem; + max-width: 360px; + margin: 0 auto; + justify-items: center; + align-items: center; + padding: 16px 16px; +} +.room-emoji-grid.visible { + display: grid; + grid-template-columns: repeat(6, 1fr); + grid-auto-rows: 56px; +} + +.room-emoji-btn { + width: 44px; + height: 44px; + display: flex; + align-items: center; + justify-content: center; + font-size: 2rem; + background: none; + border: none; + cursor: pointer; + border-radius: 50%; + transition: + background 0.15s, + transform 0.15s; + color: #fff; +} +.room-emoji-btn:hover { + background: rgba(255, 255, 255, 0.18); + border-radius: 50%; + transform: scale(1.15); +} + +.userEmoji { + z-index: 18; + position: absolute; + left: 15px; + bottom: 60px; + border-radius: 10px; } /*-------------------------------------------------------------- @@ -1339,6 +1759,13 @@ select { display: block !important; } +.top-center { + position: fixed; + top: 10px; + left: 50%; + transform: translateX(-50%); +} + .center { position: fixed; top: 50%; @@ -1517,15 +1944,44 @@ progress { position: absolute; margin: auto; padding: 10px; + max-width: 100vw; + max-height: 100vh; width: var(--wb-width); height: var(--wb-height); background: var(--wb-bg); border: var(--border); box-shadow: var(--box-shadow); border-radius: 10px; + box-sizing: border-box; /* overflow: hidden; */ } +/* Mobile optimizations */ +@media (max-width: 768px) { + #whiteboard { + padding: 5px; + border-radius: 5px; + } + + .whiteboard-header { + padding: 2px; + } + + .whiteboard-header-title button, + .whiteboard-header-options button { + padding: 5px; + font-size: 0.75rem; + min-width: 28px; + height: 28px; + } + + .whiteboardColorPicker { + width: 16px; + height: 12px; + margin: 1px; + } +} + /* #wbCanvas { border: var(--border); } */ @@ -1546,18 +2002,20 @@ progress { } .whiteboard-header-title { - display: inline; + display: flex; align-items: center; - float: left; } .whiteboard-header-title button { font-size: 1.2rem; + height: 36px; } .whiteboard-header-options { - position: absolute; - right: 10px; + display: flex; + align-items: center; + gap: 4px; + margin-left: auto; } .whiteboard-header-options button { @@ -1565,16 +2023,17 @@ progress { } .whiteboard-header-options .dropdown { - margin-left: 10px; - float: right; + margin-left: 0; + float: none; } .whiteboard-header-options .dropdown-menu { max-height: calc(var(--wb-height) * 1px) !important; + max-width: 130px; } .whiteboard-header-options .dropdown-menu button { - width: 160px; + width: 100%; } .whiteboard-header-options .dropdown-menu button:hover { background: var(--body-bg); @@ -1586,47 +2045,253 @@ progress { appearance: none; padding: 0; width: 20px; - height: 15px; + height: 20px; margin: 2px; - border-radius: 20px; - border: solid 0.5px #afadad38; + border-radius: 50%; + border: 2px solid #afadad38; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1); + background: transparent; + cursor: pointer; + transition: + box-shadow 0.2s, + border 0.2s, + transform 0.2s; + display: inline-block; + vertical-align: middle; } .whiteboardColorPicker:hover { - transform: translateY(-3px); - transition: all 0.3s ease-in-out; - cursor: pointer; + box-shadow: 0 0 0 2px rgba(102, 190, 255, 0.18); + border: 2px solid #66beff; + transform: scale(1.12); } .whiteboardColorPicker::-webkit-color-swatch { border: none; - border-radius: 20px; + border-radius: 50%; padding: 0; } .whiteboardColorPicker::-webkit-color-swatch-wrapper { border: none; - border-radius: 20px; + border-radius: 50%; padding: 0; } .whiteboardColorPicker::-moz-color-swatch { border: none; - border-radius: 20px; + border-radius: 50%; padding: 0; } .whiteboardColorPicker::-moz-color-swatch-wrapper { border: none; - border-radius: 20px; + border-radius: 50%; padding: 0; } .whiteboardColorPicker::color-swatch { border: none; - border-radius: 20px; + border-radius: 50%; padding: 0; } .whiteboardColorPicker::color-swatch-wrapper { border: none; - border-radius: 20px; + border-radius: 50%; padding: 0; } +/* Whiteboard Shortcuts Styles */ +#whiteboardShortcutsContent { + display: none; +} + +.wb-shortcuts-container { + text-align: left; + font-family: 'Segoe UI', Arial, sans-serif; + background: var(--body-bg); + padding: 0.5rem 0.7rem; + border-radius: 10px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18); + max-width: 340px; + margin: 0 auto; + font-size: 0.85em; + max-height: 70vh; +} + +.wb-shortcuts-title { + margin-top: 1rem; + margin-bottom: 0.5rem; + font-size: 1.05rem; + font-weight: 600; + color: var(--text-color); + display: flex; + align-items: center; + gap: 0.5rem; + padding-bottom: 0.3rem; + border-bottom: 1px solid var(--border-color, #e0e0e0); +} + +.wb-shortcuts-title:first-child { + margin-top: 0; +} + +.wb-shortcuts-title i { + color: var(--text-color); + font-size: 1rem; +} + +.wb-shortcuts-code { + background: var(--body-bg); + padding: 0.5rem 0.5rem; + border-radius: 6px; + white-space: pre; + overflow-x: auto; + font-family: 'Courier New', monospace; + font-size: 0.8rem; + line-height: 1.4; + color: var(--text-color); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.13); + filter: brightness(0.92); +} + +.wb-shortcuts-text { + color: #66beff; + font-weight: 600; + font-size: 0.9rem; + letter-spacing: 0.02em; + background: linear-gradient(135deg, rgba(102, 190, 255, 0.11), rgba(102, 190, 255, 0.06)); + padding: 4px 10px; + border-radius: 6px; + display: inline-block; + box-shadow: 0 1px 4px rgba(102, 190, 255, 0.07); +} + +.wb-shortcuts-list { + list-style: none; + padding-left: 0; + background: var(--body-bg); + border-radius: 6px; + padding: 0.3rem 0.2rem; + filter: brightness(0.97); +} + +.wb-shortcuts-list li { + margin: 0.3rem 0; + padding: 0.3rem 0.5rem; + background: rgba(255, 255, 255, 0.03); + border-radius: 4px; + color: var(--text-color); + font-weight: 500; + font-size: 0.85rem; + transition: all 0.2s ease; +} + +.wb-shortcuts-list li:hover { + background: rgba(0, 0, 0, 0.04); + border-left: 2px solid var(--border-color, #e0e0e0); +} + +/* Sticky Note Dialog Styles */ +.sticky-note-form { + display: flex; + background: var(--body-bg); + flex-direction: column; + gap: 1.2rem; + padding: 1.2rem; + border-radius: 12px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); + border: 1px solid rgba(255, 215, 0, 0.18); + max-width: 100%; + box-sizing: border-box; +} + +.sticky-note-colors-row { + display: flex; + gap: 1rem; + width: 100%; + flex-wrap: wrap; +} + +.sticky-note-textarea { + width: 100%; + padding: 12px; + border: 2px solid rgba(255, 215, 0, 0.3); + border-radius: 10px; + background: rgba(255, 235, 59, 0.07); + color: var(--text-color, #ffffff); + font-size: 1rem; + font-family: 'Segoe UI', Arial, sans-serif; + resize: vertical; + transition: all 0.3s ease; + box-shadow: 0 2px 8px rgba(255, 215, 0, 0.08); + box-sizing: border-box; +} + +.sticky-note-color-group { + display: flex; + flex-direction: column; + gap: 0.5rem; + flex: 1; + min-width: 120px; +} + +.sticky-note-color-label { + font-weight: 600; + font-size: 0.95rem; + color: var(--text-color, #ffffff); + display: flex; + align-items: center; + gap: 0.5rem; +} + +.sticky-note-color-input { + width: 100%; + height: 50px; + border: none !important; + border-radius: 10px; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 1px 4px rgba(255, 215, 0, 0.07); +} + +.sticky-note-color-input:hover { + transform: scale(1.01); +} + +.sticky-note-color-input::-webkit-color-swatch-wrapper { + padding: 4px; + border-radius: 10px; +} + +.sticky-note-color-input::-webkit-color-swatch { + border: none; + border-radius: 8px; +} + +.sticky-note-color-input::-moz-color-swatch { + border: none; + border-radius: 8px; +} + +/* Responsive styles for small screens */ +@media (max-width: 600px) { + .sticky-note-form { + padding: 0.7rem; + border-radius: 8px; + gap: 0.7rem; + } + .sticky-note-colors-row { + flex-direction: column; + gap: 0.7rem; + } + .sticky-note-color-group { + min-width: 0; + } + .sticky-note-textarea { + font-size: 0.95rem; + padding: 8px; + } + .sticky-note-color-input { + height: 38px; + border-radius: 8px; + } +} + /*-------------------------------------------------------------- # Video exit, pin/Unpin btn --------------------------------------------------------------*/ @@ -1711,6 +2376,12 @@ hr { background: transparent !important; } +.fa-phone-slash, +.fa-microphone-slash, +.fa-video-slash { + color: red !important; +} + /*-------------------------------------------------------------- # Pulsate class effect --------------------------------------------------------------*/ @@ -1759,6 +2430,26 @@ hr { } } +/*-------------------------------------------------------------- +# Unread message count badge +--------------------------------------------------------------*/ + +.unread-count { + display: inline-block; + background-color: #ff3b30; + color: #fff; + font-size: 11px; + font-weight: 700; + min-width: 18px; + height: 18px; + line-height: 18px; + padding: 0 5px; + border-radius: 9px; + text-align: center; + margin-left: 4px; + vertical-align: middle; +} + /*-------------------------------------------------------------- # Video AI Avatars --------------------------------------------------------------*/ @@ -1776,65 +2467,185 @@ hr { overflow: auto; } -.container-flex { - display: flex; - flex-direction: column; +#avatarVideoAIPreview { + width: 100%; + max-width: 200px; + border-radius: 10px; + display: block; + margin: 0 auto; } -/* Custom avatar audio UI */ +.avatarSelectedName { + font-size: 0.9rem; + text-align: center; + margin-top: 6px; + color: var(--body-color); + font-weight: 500; + min-height: 1.3em; +} -#audio-container { - margin-top: 20px; - color: #fff; - border-radius: 5px; +.avatarSearchRow { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + padding: 10px 0; } -#audio-container audio { - width: 320px; - outline: none; +.avatarSearch { + flex: 1; + max-width: 200px; + padding: 4px 8px !important; + font-size: 0.8rem !important; + border-radius: 6px !important; } -#audio-container audio::-webkit-media-controls-panel { - background: var(--body-bg); +#avatarVideoAIcontainer { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); + gap: 10px; + max-height: 400px; + overflow-y: auto; padding: 5px; } -#audio-container audio::-webkit-media-controls-timeline { - background: var(--body-bg); - border-radius: 5px; - margin: 5px; +.avatarCount { + font-size: 0.85rem; + color: var(--body-color); + padding: 4px 0; + white-space: nowrap; } -/* Styling for the audio volume controls */ -#audio-container audio::-webkit-media-controls-volume-slider-container { - color: #fff; +.avatarCard { + display: flex; + flex-direction: column; + align-items: center; + cursor: pointer; + border-radius: 8px; + padding: 6px; + transition: + background 0.2s, + transform 0.15s; } -#audio-container audio::-webkit-media-controls-volume-slider { +.avatarCard:hover { background: var(--body-bg); - border-radius: 5px; + transform: translateY(-2px); } -/* Styling for the play/pause button */ -#audio-container audio::-webkit-media-controls-play-button, -#audio-container audio::-webkit-media-controls-pause-button { - background-color: #fff; - border-radius: 50%; - width: 30px; - height: 30px; - margin: 0 10px; +.avatarCard.selected { + background: var(--body-bg); + outline: 2px solid var(--body-color); + outline-offset: -2px; } -/* Styling for the time display */ -#audio-container audio::-webkit-media-controls-current-time-display, -#audio-container audio::-webkit-media-controls-time-remaining-display { - color: #fff; +.avatarImg { + width: 100%; + height: 120px; + object-fit: cover; + border-radius: 6px; + cursor: pointer; +} + +.avatarLabel { + font-size: 0.75rem; + text-align: center; + margin-top: 4px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: 100%; + display: block; +} + +.container-flex { + display: flex; + flex-direction: column; } #avatarVideoAIStart { margin-bottom: 5px; } +/* Empty chat illustration and message */ +.empty-chat-notice { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 1.5rem; + text-align: center; + padding: 2rem 1rem; + opacity: 0.85; + transition: opacity 0.2s; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 100%; +} +.empty-chat-img { + width: 50%; + max-width: 240px; + opacity: 0.8; + margin: 0 auto 0.5rem auto; +} +.empty-chat-title { + font-size: 1.3rem; + font-weight: 600; + color: #e0e0e0; + margin: 0.5rem 0 0.2rem 0; +} +.empty-chat-desc { + color: #b0b0b0; + max-width: 350px; + margin: 0 auto; + font-size: 1rem; +} + +/* Mic Guidance */ + +.mic-guidance { + padding: 10px; + border-radius: 6px; + margin: 10px 0; + font-size: 13px; + line-height: 1.4; +} + +.mic-guidance p { + margin: 5px 0; +} + +.mic-guidance .title { + display: flex; + align-items: center; + gap: 6px; + font-weight: 600; +} + +.mic-guidance.ios { + background: var(--body-bg); + border-left: 3px solid #ffc107; +} + +.mic-guidance.ios .title { + color: #ffc107; +} + +.mic-guidance.mobile { + background: var(--body-bg); + border-left: 3px solid #2196f3; +} + +.mic-guidance.mobile .title { + color: #2196f3; +} + +.mic-guidance .text { + color: #ddd; +} + /* z-index: - 1 videoMediaContainer diff --git a/public/css/Root.css b/public/css/Root.css index 5dbddc1ed..4e7bde863 100644 --- a/public/css/Root.css +++ b/public/css/Root.css @@ -2,14 +2,14 @@ :root { --body-bg: radial-gradient(#393939, #000000); - --border: 0.5px solid rgb(255 255 255 / 32%); + --border: 0.1px solid rgb(255 255 255 / 32%); --border-radius: 1rem; --msger-width: 800px; --msger-height: 700px; --msger-bubble-width: 85%; --msger-bg: radial-gradient(#393939, #000000); - --wb-width: 800px; - --wb-height: 600px; + --wb-width: 1920px; + --wb-height: 1080px; --wb-bg: radial-gradient(#393939, #000000); --select-bg: #2c2c2c; --left-msg-bg: #252d31; @@ -20,21 +20,7 @@ --settings-bg: radial-gradient(#393939, #000000); --tab-btn-active: rgb(42 42 42 / 70%); --btns-bg-color: rgba(0, 0, 0, 0.7); - /* buttons bar horizontal */ - --btns-top: 50%; - --btns-right: 0%; - --btns-left: 10px; - --btns-margin-left: 0px; - --btns-width: 60px; - --btns-flex-direction: column; - /* buttons bar horizontal - --btns-top: 95%; - --btns-right: 25%; - --btns-left: 50%; - --btns-margin-left: -160px; - --btns-width: 320px; - --btns-flex-direction: row; - */ + /* bottom buttons bar horizontal default */ --bottom-btns-top: auto; --bottom-btns-left: 50%; @@ -58,7 +44,7 @@ --dd-color: #ffffff; - --videoBar-active: 0.1px solid rgba(102, 190, 255, 0.32); + --videoBar-active: 1px solid rgba(102, 190, 255, 0.32); } * { diff --git a/public/css/VideoGrid.css b/public/css/VideoGrid.css index 390d3c96c..11da0d4c5 100644 --- a/public/css/VideoGrid.css +++ b/public/css/VideoGrid.css @@ -21,6 +21,23 @@ /* border: 3px solid blue; */ } +/* Video container with mouse light effect */ +#videoMediaContainer.mouse-light { + background: + radial-gradient( + 1024px 1024px at var(--mouse-x, 50%) var(--mouse-y, 50%), + rgba(255, 255, 255, 0.3) 0%, + rgba(255, 255, 255, 0.18) 20%, + rgba(255, 255, 255, 0.08) 40%, + rgba(0, 0, 0, 0) 60%, + rgba(0, 0, 0, 0.1) 80%, + rgba(0, 0, 0, 0.25) 100% + ), + var(--body-bg, linear-gradient(135deg, #222, #444)); + background-blend-mode: multiply, normal; + transition: background 0.2s; +} + #videoPinMediaContainer { z-index: 1; position: absolute; @@ -42,11 +59,12 @@ align-self: center; overflow: hidden; display: inline-block; - background: transparent; border-radius: 10px; - /* border: var(--border); */ + background: var(--body-bg); box-shadow: var(--box-shadow); animation: show 0.4s ease; + container-type: inline-size; + container-name: camera; } /* .Camera:hover { @@ -74,7 +92,7 @@ } .videoAvatarImage { - z-index: 7; + z-index: 1; position: absolute; display: none; width: var(--vmi-wh); @@ -111,6 +129,36 @@ background: var(--body-bg); } +.rec-indicator { + display: none; + margin-left: 8px; + white-space: nowrap; + background: transparent; + animation: none; +} + +.rec-indicator.active { + display: inline; + animation: recPulsate 3s ease-out infinite; +} + +.rec-indicator.paused { + animation: none; + opacity: 0.5; +} + +@keyframes recPulsate { + 0% { + opacity: 0.5; + } + 50% { + opacity: 1; + } + 100% { + opacity: 0.5; + } +} + .fscreen { position: absolute; right: 0; @@ -123,46 +171,53 @@ background: rgba(0, 0, 0, 0.1); } +/* Video Menu Bar */ .videoMenuBar { z-index: 2; - position: fixed; - display: inline; + position: absolute; /* inside video feed by default */ top: 0; left: 0; - padding: 15px; width: 100%; - font-size: small; - font-weight: bold; - text-align: center; - background: var(--body-bg); - cursor: default; - overflow: hidden; -} - -.videoMenuBarClose { - position: absolute; display: flex; - top: 30px; - right: 30px; - padding: 10px; - border-radius: 50%; - color: white; align-items: center; - justify-content: center; - cursor: pointer; - user-select: none; + flex-direction: row-reverse; /* invert buttons order visually */ + justify-content: flex-start; /* with row-reverse, this packs to the right */ + flex-wrap: nowrap; + gap: clamp(2px, 1vw, 8px); + padding: clamp(4px, 1.2vw, 8px); + backdrop-filter: blur(6px); + -webkit-backdrop-filter: blur(6px); + border-top-left-radius: 10px; + border-top-right-radius: 10px; background: var(--body-bg); + /* box-shadow: var(--box-shadow); */ + cursor: default; + overflow-x: auto; + overflow-y: hidden; + scrollbar-width: none; /* Firefox */ + white-space: nowrap; + -webkit-overflow-scrolling: touch; } -.videoMenuBarClose:hover { - background: var(--btns-bg-color); +/* Mobile floating menubar when appended to body */ +.videoMenuBar.mobile-floating { + z-index: 2; + position: fixed; + top: env(safe-area-inset-top, 0px); + left: 0; + right: 0; + width: 100vw; + border-radius: 0 0 10px 10px; } .videoAvatarMenuBar, .videoMenuBarShare { z-index: 2; position: absolute; - display: inline; + display: flex; + flex-direction: row-reverse; + align-items: center; + gap: 1px; top: 0; left: 0; padding: 10px; @@ -175,27 +230,47 @@ overflow: hidden; } +.avatar-session-timer { + font-size: 14px; + color: white; + margin-right: auto; + padding: 4px 8px; + white-space: nowrap; +} + .videoMenuBar input, .videoMenuBar button, .videoAvatarMenuBar button, .videoMenuBarShare button { - font-size: 1.2rem; - float: right; + font-size: clamp(0.9rem, 1vw, 1.05rem); + min-width: 36px; + min-height: 36px; color: #fff; - background: transparent; - border-radius: 5px; - display: inline; + background: var(--body-bg); + border-radius: 10px; + display: inline-flex; + align-items: center; + justify-content: center; border: none; + outline: none; + cursor: pointer; } .videoMenuBar button:hover, .videoAvatarMenuBar button:hover, .videoMenuBarShare button:hover { - color: grey; - transition: all 0.3s ease-in-out; + color: #fff; + background: var(--btns-bg-color); } -.expand-video .fa-bars { +/* Touch-friendly sizing for buttons within menu bar */ +.videoMenuBar button { + min-width: 32px; + min-height: 32px; + flex: 0 0 auto; +} + +.fa-bars { color: #66beff !important; } @@ -205,29 +280,29 @@ } .expand-video-content { - z-index: 1; + z-index: 5; display: none; - position: fixed; - right: 10px; - width: calc(100% - 20px); - max-width: 500px; - padding: 20px; + position: absolute; + width: 100%; + height: 100%; + padding: 14px; border-radius: 5px; background: var(--body-bg); box-shadow: var(--box-shadow); + overflow: auto; } -.expand-video:hover .expand-video-content { - z-index: 1; +.expand-video-content.show { + z-index: 5; display: grid !important; grid-gap: 5px 5px; - grid-template-columns: 50%; + grid-template-columns: 1fr 1fr; grid-template-areas: 'header header' 'controls controls'; align-content: start; justify-items: start; - overflow-y: auto; + overflow: auto; } .peer-name-container { @@ -241,8 +316,8 @@ .expand-video-content .peer-name-header { grid-area: header; width: 100%; - padding: 40px; - height: 120px; + padding: 10px; + height: 100px; background: var(--btns-bg-color); background-size: cover; background-position: center; @@ -255,12 +330,13 @@ } .expand-video-content .peer-name { - font-size: 18px; + text-align: center; + font-size: 10px; font-weight: bold; color: #fff; background: var(--body-bg); border-radius: 10px; - padding: 6px; + padding: 5px; width: 100%; } @@ -272,16 +348,53 @@ height: 6px; } -.expand-video-content button { +/* Button Group Container */ +.expand-video-content .button-group { display: flex !important; - font-size: 16px; - color: #fff; - background: var(--btns-bg-color); + gap: 8px; + flex-wrap: nowrap; width: 100%; - height: 45px; +} + +/* Individual Buttons */ +.expand-video-content .button-group button { + text-align: center; + padding: 0 10px; + font-size: 15px; + color: #fff; + background: var(--body-bg); + min-width: 42px; + min-height: 42px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); justify-content: center; align-items: center; + display: inline-flex; /* center icon + label */ + gap: 6px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; border-radius: 10px; + border: none; + cursor: pointer; + transition: + background 0.2s ease, + transform 0.1s ease; +} + +/* Icon alignment inside buttons */ +.expand-video-content .button-group button i { + line-height: 1; + pointer-events: none; /* avoid accidental icon-only clicks interference */ +} + +/* Hover & Active Effects */ +.expand-video-content .button-group button:hover { + background: var(--btns-bg-color); + transform: scale(1.05); +} + +.expand-video-content .button-group button:active { + transform: scale(0.95); } .expand-video-content button:hover { @@ -295,17 +408,32 @@ margin-right: auto; } +/* Keep peer name header on the left, controls on the right */ +.videoMenuBar .peer-name-header { + order: 9999; /* with row-reverse, last = leftmost */ + padding: 0; + background: transparent; + height: auto; +} +.videoMenuBar .peer-name-header .peer-name-container { + margin: 0; + padding: 0; +} +.videoMenuBar .peer-name-header .peer-name { + background: transparent; + padding: 0; + font-size: 0.95rem; +} + .videoCircle { position: absolute; width: var(--vmi-wh); height: var(--vmi-wh); border-radius: 50%; - /* center */ - top: 0; - left: 0; - right: 0; - bottom: 0; - margin: auto; + /* reliable centering - works in all scenarios */ + top: 50% !important; + left: 50% !important; + transform: translate(-50%, -50%) !important; transition: box-shadow 0.3s ease; } @@ -348,6 +476,12 @@ video:fullscreen { transform: rotateY(180deg); } +.videoCircle.mirror { + -webkit-transform: translate(-50%, -50%) rotateY(180deg) !important; + -moz-transform: translate(-50%, -50%) rotateY(180deg) !important; + transform: translate(-50%, -50%) rotateY(180deg) !important; +} + .blur { -webkit-filter: blur(5px); -moz-filter: blur(5px); @@ -374,20 +508,172 @@ input[type='range'] { } } -@media screen and (max-width: 500px) { - .username { +@container camera (max-width: 260px) { + .videoMenuBar { + gap: 0px; + padding: 4px; + } + .videoMenuBar button, + .videoAvatarMenuBar button, + .videoMenuBarShare button { + font-size: 0.7rem; + min-width: 24px; + min-height: 24px; + } + .videoMenuBar input[type='range'] { + width: clamp(20px, 20%, 40px); + } + .expand-video-content { + padding: 10px; + } + .expand-video-content .button-group button { font-size: 10px; + min-width: 28px; + min-height: 28px; + gap: 1px; + } +} + +@container camera (min-width: 261px) and (max-width: 400px) { + .videoMenuBar { + gap: 2px; + padding: 3px; } - .videoMenuBar input, .videoMenuBar button, + .videoAvatarMenuBar button, + .videoMenuBarShare button { + font-size: 0.8rem; + min-width: 28px; + min-height: 28px; + } + .videoMenuBar input[type='range'] { + width: clamp(30px, 25%, 60px); + } + .expand-video-content .button-group button { + font-size: 15px; + min-width: 36px; + min-height: 36px; + gap: 3px; + } +} + +@container camera (min-width: 401px) and (max-width: 640px) { + .videoMenuBar { + gap: 6px; + padding: 8px; + } + .videoMenuBar button, + .videoAvatarMenuBar button, + .videoMenuBarShare button { + font-size: 0.9rem; + min-width: 32px; + min-height: 32px; + } + .videoMenuBar input[type='range'] { + width: clamp(40px, 20%, 80px); + } + .expand-video-content .button-group button { + font-size: 15px; + min-width: 42px; + min-height: 42px; + gap: 6px; + } +} + +@container camera (min-width: 641px) { + .videoMenuBar { + gap: 8px; + padding: 10px; + } + .videoMenuBar button, + .videoAvatarMenuBar button, .videoMenuBarShare button { font-size: 1rem; + min-width: 36px; + min-height: 36px; } - .expand-video-content { - min-width: 100%; - left: 0px; + .videoMenuBar input[type='range'] { + width: clamp(50px, 20%, 100px); } - .expand-video-content .peer-name { - font-size: 12px; + .expand-video-content .button-group button { + font-size: 15px; + min-width: 42px; + min-height: 42px; + gap: 6px; + } +} + +/*-------------------------------------------------------------- +# Video Drawing Overlay (Fabric.js) +--------------------------------------------------------------*/ + +/* Fabric.js wraps the canvas in a .canvas-container div. + Use a dedicated class so it works when .Camera is removed (pinned state). */ +.video-drawing-wrap { + position: absolute !important; + top: 0; + left: 0; + z-index: 1; /* Above video (0), below videoMenuBar (2) */ + border-radius: 10px; + overflow: hidden; +} + +/* The raw canvas element before Fabric wraps it */ +.video-drawing-canvas { + position: absolute; + top: 0; + left: 0; +} + +/* When drawing is inactive, let all events pass through to the video */ +.video-drawing-inactive { + pointer-events: none !important; +} + +/* When drawing is active, capture pointer events and show crosshair */ +.video-drawing-active { + pointer-events: auto !important; + cursor: crosshair; +} + +/* Ensure the upper-canvas (Fabric.js event layer) inherits pointer-events */ +.video-drawing-active .upper-canvas { + pointer-events: auto !important; + cursor: crosshair !important; +} + +.video-drawing-inactive .upper-canvas { + pointer-events: none !important; +} + +/* Floating label showing who is drawing on the video */ +.video-drawing-label { + position: absolute; + top: 8px; + left: 8px; + z-index: 10; + padding: 3px 8px; + max-width: calc(100% - 16px); + background: rgba(0, 0, 0, 0.65); + color: #fff; + font-size: 12px; + font-weight: 500; + border-radius: 6px; + pointer-events: none; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + opacity: 0; + transition: opacity 0.35s ease; +} + +/* Smaller label on narrow video tiles (mobile) */ +@container camera (max-width: 250px) { + .video-drawing-label { + top: 4px; + left: 4px; + max-width: calc(100% - 8px); + padding: 2px 5px; + font-size: 10px; } } diff --git a/public/css/landing.css b/public/css/landing.css index e31bf8bc8..40a82d555 100644 --- a/public/css/landing.css +++ b/public/css/landing.css @@ -1082,7 +1082,38 @@ label { border-radius: 6px; filter: brightness(1.1) saturate(1.2); box-shadow: 0 6px 20px rgba(0, 123, 255, 0.4); + transform: translateY(-2px); + transition: + background 0.2s ease, + box-shadow 0.2s ease, + transform 0.12s ease; +} + +.button-secondary { + background: linear-gradient(65deg, rgba(255, 255, 255, 0.08) 0%, rgba(255, 255, 255, 0.03) 100%); + box-shadow: + 0 10px 24px rgba(0, 0, 0, 0.35), + inset 0 1px 0 rgba(255, 255, 255, 0.06); + backdrop-filter: blur(6px); + -webkit-backdrop-filter: blur(6px); + border: none; + color: rgba(255, 255, 255, 0.88) !important; + border-radius: 6px; + transition: + background 0.2s ease, + border-color 0.2s ease, + box-shadow 0.2s ease, + transform 0.12s ease, + filter 0.2s ease; } + +.button-secondary:hover { + background: linear-gradient(65deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.04) 100%); + border-color: rgba(255, 255, 255, 0.3); + filter: brightness(1.03) saturate(1.05); + transform: translateY(-2px); +} + .button-block { display: flex; } @@ -1642,6 +1673,13 @@ main { display: inline-flex; justify-content: center; } + +.footer-copyright { + font-size: 14px; + line-height: 22px; + color: #8a94a7; +} + .footer-brand, .footer-links, .footer-social-links { @@ -1911,52 +1949,314 @@ main { } /*-------------------------------------------------------------- -# Sponsors +# Top Sponsors Section (Featured) --------------------------------------------------------------*/ -.clients .section-inner { - padding-top: 26px; - padding-bottom: 26px; +#topSponsors { + background: linear-gradient(180deg, rgba(0, 0, 0, 0.2) 0%, rgba(0, 0, 0, 0.05) 50%, transparent 100%); } -.clients-inner ul { +#topSponsors .section-inner { + padding-top: 3px; + padding-bottom: 3px; +} + +#topSponsors .clients-inner { + max-width: 100%; + margin: 0 auto; + padding: 0 24px; +} + +#topSponsors .clients-inner ul { display: flex; + flex-direction: row; + align-items: center; + justify-content: center; flex-wrap: wrap; + width: 100%; +} + +#topSponsors .clients-inner li { + display: flex; flex-direction: column; + align-items: center; + padding: 2px 8px; + width: 100%; +} + +#topSponsors .clients-logo { + display: flex; + cursor: pointer; justify-content: center; align-items: center; + padding: 0.625rem 1.125rem; + border-radius: 10px; + border: 1px solid rgba(255, 255, 255, 0.04) !important; + background-color: #1a1d23 !important; + background-image: linear-gradient( + 135deg, + rgba(26, 29, 35, 0.85) 0%, + rgba(31, 34, 40, 0.95) 50%, + rgba(26, 29, 35, 0.85) 100% + ) !important; + width: 100%; + max-width: 100%; + margin: 0 auto; + box-shadow: + 0 2px 10px rgba(0, 0, 0, 0.35), + inset 0 1px 0 rgba(255, 255, 255, 0.03) !important; + transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + overflow: hidden; +} + +#topSponsors .clients-logo::after { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(180deg, rgba(255, 255, 255, 0.03) 0%, transparent 50%, rgba(0, 0, 0, 0.1) 100%); + pointer-events: none; +} + +#topSponsors .clients-logo::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.03), transparent); + transition: left 0.6s ease; +} + +#topSponsors .clients-logo:hover::before { + left: 100%; +} + +#topSponsors .clients-logo:hover { + transform: translateY(-2px) !important; + border-color: rgba(255, 255, 255, 0.08) !important; + box-shadow: + 0 4px 20px rgba(0, 0, 0, 0.45), + inset 0 1px 0 rgba(255, 255, 255, 0.05) !important; + background-image: linear-gradient( + 135deg, + rgba(31, 34, 40, 0.9) 0%, + rgba(36, 39, 45, 0.98) 50%, + rgba(31, 34, 40, 0.9) 100% + ) !important; +} + +#topSponsors .clients-size-logo { + width: 220px; + height: 55px; + object-fit: contain; + filter: brightness(0.98) contrast(1.12) saturate(1.05); + transition: + filter 0.35s ease, + transform 0.35s ease; + position: relative; + z-index: 2; +} + +#topSponsors .clients-logo:hover .clients-size-logo { + filter: brightness(1.08) contrast(1.18) saturate(1.12); + transform: scale(1.01); +} + +#topSponsors .sponsor-link { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + text-decoration: none; + color: inherit; + cursor: pointer; +} + +/*-------------------------------------------------------------- +# Sponsors & Powered By Sections (Grid) +--------------------------------------------------------------*/ + +.clients .section-inner { + padding-top: 26px; + padding-bottom: 26px; +} + +.clients-inner ul { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 24px; margin: 0; + max-width: 1200px; + margin-left: auto; + margin-right: auto; } .clients-inner li { - padding: 12px 28px; + display: flex; + justify-content: center; + align-items: center; } .clients-size-logo { - width: 240px; - height: auto; + width: 200px; + height: 60px; + object-fit: contain; + transition: + filter 0.4s ease, + transform 0.4s ease; + filter: brightness(1) contrast(1.15) saturate(1.1); + position: relative; + z-index: 1; +} + +.clients-size-logo:hover { + filter: brightness(1.15) contrast(1.2) saturate(1.15); + transform: scale(1.02); } .clients-logo { display: flex; + cursor: pointer; justify-content: center; - padding: 1rem; - border-radius: 1rem; - background: radial-gradient(circle at bottom, #000000 0%, #000000 0%, #1d2026 100%); + align-items: center; + padding: 2rem 1.5rem !important; + border-radius: 20px; + border: none !important; + background-color: #1a1d23 !important; + background-image: linear-gradient(135deg, #1a1d23 0%, #1f2228 50%, #1a1d23 100%) !important; + box-shadow: + 0 4px 20px rgba(0, 0, 0, 0.6), + inset 0 1px 0 rgba(255, 255, 255, 0.05) !important; + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); + min-height: 100px !important; + height: auto !important; + position: relative; + overflow: hidden; +} + +.clients-logo::after { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 1px; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent); +} + +.clients-logo::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.04), transparent); + transition: left 0.6s ease; +} + +.clients-logo:hover::before { + left: 100%; } .clients-logo:hover { - background: radial-gradient(circle at bottom, #000000 0%, #000000 0%, #0e0e0e 100%); + transform: translateY(-4px) !important; + border-color: rgba(255, 255, 255, 0.15) !important; + box-shadow: + 0 8px 32px rgba(0, 0, 0, 0.8), + inset 0 1px 0 rgba(255, 255, 255, 0.08) !important; + background-image: linear-gradient(135deg, #1f2228 0%, #24272d 50%, #1f2228 100%) !important; } -@media (min-width: 641px) { - .clients .clients-inner ul { - flex-direction: row; +@media (min-width: 1024px) { + .clients-inner ul { + grid-template-columns: repeat(4, 1fr); } } -@media (max-width: 641px) { + +@media (min-width: 641px) and (max-width: 1023px) { + .clients-inner ul { + grid-template-columns: repeat(3, 1fr); + } +} + +@media (max-width: 640px) { + .clients-inner ul { + grid-template-columns: repeat(2, 1fr); + gap: 16px; + } + .clients-size-logo { - width: 300px; + width: 150px; + height: 50px; + } + + .clients-logo { + padding: 1.5rem 1rem !important; + min-height: 80px; + border-radius: 16px; + border-width: 1.5px !important; + } + + #topSponsors { + padding: 32px 0; + } + + #topSponsors .section-inner { + padding-top: 4px; + padding-bottom: 4px; + } + + #topSponsors .clients-inner { + max-width: 100%; + padding: 0 16px; + } + + #topSponsors .clients-inner li { + padding: 2px 8px; + } + + #topSponsors .clients-logo { + padding: 0.75rem 1rem; + max-width: 100%; + border-radius: 10px; + } + + #topSponsors .clients-size-logo { + width: 200px; + height: 50px; + } +} + +@media (min-width: 641px) and (max-width: 1024px) { + #topSponsors .section-inner { + padding-top: 5px; + padding-bottom: 5px; + } + + #topSponsors .clients-inner { + max-width: 100%; + padding: 0 20px; + } + + #topSponsors .clients-inner li { + padding: 3px 10px; + } + + #topSponsors .clients-logo { + padding: 0.8125rem 1.125rem; + max-width: 100%; + border-radius: 11px; + } + + #topSponsors .clients-size-logo { + width: 210px; + height: 52px; } } @@ -2077,6 +2377,11 @@ main { # Common --------------------------------------------------------------*/ +.blue { + font-weight: bold; + color: #00bfff; +} + .tac { text-align: center; } @@ -2113,6 +2418,18 @@ main { #joinRoomForm { display: none; + margin-top: 32px; + background: rgba(70, 120, 249, 0.08); + border-radius: 10px; + padding: 32px 24px; + transition: + box-shadow 0.3s, + border-color 0.3s; +} + +#joinRoomForm:focus-within, +#joinRoomForm:hover { + border-color: #376df9; } /*-------------------------------------------------------------- @@ -2123,3 +2440,186 @@ main { pointer-events: none; opacity: 0.5; } + +/*-------------------------------------------------------------- +# Support Us Section +--------------------------------------------------------------*/ + +#supportUs .cta-inner { + background: linear-gradient(135deg, #0a0c0f 0%, #15181d 50%, #1d2026 100%) !important; +} + +.support-us-content { + text-align: center; + padding: 32px 24px; +} + +.support-icon { + margin-bottom: 24px; +} + +.support-icon .fa-heart { + font-size: 48px; + color: #ff6b6b; +} + +.heartbeat { + animation: heartbeat 1.5s ease-in-out infinite; +} + +@keyframes heartbeat { + 0%, + 100% { + transform: scale(1); + } + 10%, + 30% { + transform: scale(1.1); + } + 20%, + 40% { + transform: scale(1); + } +} + +.support-options { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 20px; + max-width: 900px; + margin: 0 auto; +} + +.support-card { + display: flex; + flex-direction: column; + align-items: center; + padding: 28px 20px; + background-color: #1a1d23 !important; + background-image: linear-gradient(135deg, #1a1d23 0%, #1f2228 50%, #1a1d23 100%) !important; + border: 2px solid rgba(255, 255, 255, 0.1) !important; + border-radius: 20px; + text-decoration: none; + color: #8a94a7 !important; + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); + min-height: 160px; + position: relative; + overflow: hidden; + box-shadow: + 0 4px 20px rgba(0, 0, 0, 0.6), + inset 0 1px 0 rgba(255, 255, 255, 0.05) !important; +} + +.support-card::after { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 1px; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent); +} + +.support-card::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.04), transparent); + transition: left 0.6s ease; +} + +.support-card:hover::before { + left: 100%; +} + +.support-card:hover { + transform: translateY(-4px) !important; + border-color: rgba(255, 255, 255, 0.15) !important; + box-shadow: + 0 8px 32px rgba(0, 0, 0, 0.8), + inset 0 1px 0 rgba(255, 255, 255, 0.08) !important; + background-image: linear-gradient(135deg, #1f2228 0%, #24272d 50%, #1f2228 100%) !important; +} + +.support-card-icon svg { + filter: brightness(0) invert(1) !important; +} + +.support-card-icon { + margin-bottom: 12px; + width: 48px; + height: 48px; + display: flex; + align-items: center; + justify-content: center; + background: rgba(255, 255, 255, 0.08) !important; + border-radius: 50%; + transition: all 0.3s ease; + position: relative; + z-index: 1; +} + +.support-card:hover .support-card-icon { + background: rgba(255, 255, 255, 0.15) !important; + transform: scale(1.1); +} + +.support-card-title-small { + margin: 0 0 8px 0; + font-size: 18px; + font-weight: 600; + color: #fff !important; + text-align: center; +} + +.support-card-description-small { + margin: 0; + font-size: 13px; + color: rgba(255, 255, 255, 0.7) !important; + text-align: center; + line-height: 1.5; +} + +.support-footer { + margin-top: 32px; + padding-top: 24px; + border-top: 1px solid rgba(255, 255, 255, 0.1) !important; +} + +.support-footer a { + text-decoration: none; +} + +.support-footer a:hover { + color: #fff !important; +} + +@media (max-width: 900px) { + .support-options { + grid-template-columns: repeat(2, 1fr); + gap: 16px; + } +} + +@media (max-width: 480px) { + .support-options { + grid-template-columns: 1fr; + gap: 16px; + } + + .support-icon .fa-heart { + font-size: 36px; + } + + .support-us-content { + padding: 24px 16px; + } + + .support-card { + padding: 24px 16px; + min-height: 140px; + } +} diff --git a/public/css/login.css b/public/css/login.css new file mode 100644 index 000000000..9028904af --- /dev/null +++ b/public/css/login.css @@ -0,0 +1,277 @@ +/* Center card section (shared with waitingRoom.css) */ +.center-card-section { + display: flex; + align-items: center; + min-height: calc(100vh - 200px); + padding-top: 0 !important; + padding-bottom: 0 !important; +} +.center-card-section .container { + width: 100%; +} +.center-card-section .hero-figure { + display: none; +} +@media (min-width: 1025px) { + .center-card-section .hero-figure { + display: block; + } +} + +/* Login Card */ +.login-card { + max-width: 552px; + width: 100%; + margin: 0 auto; + padding: 56px 48px 48px; + background: linear-gradient(160deg, rgba(15, 15, 25, 0.95) 40%, rgba(70, 120, 249, 0.12) 100%); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 20px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); + text-align: center; +} + +/* Form section transition */ +.login-form-section { + transition: opacity 0.3s ease; +} + +/* Icon */ +.login-icon { + width: 72px; + height: 72px; + margin: 0 auto 24px; + border-radius: 50%; + background: rgba(70, 120, 249, 0.1); + border: 1px solid rgba(70, 120, 249, 0.2); + display: flex; + align-items: center; + justify-content: center; +} +.login-icon i { + font-size: 30px; + color: #4678f9; +} + +/* Success variant */ +.login-icon-success { + background: linear-gradient(135deg, rgba(46, 206, 137, 0.2), rgba(46, 206, 137, 0.06)) !important; + border-color: rgba(46, 206, 137, 0.3) !important; +} +.login-icon-success i { + color: #2ece89 !important; +} + +/* Title */ +.login-title { + font-size: 26px; + font-weight: 700; + color: #fff; + margin-bottom: 10px; + letter-spacing: -0.4px; +} + +/* Description */ +.login-description { + font-size: 15px; + color: rgba(255, 255, 255, 0.55); + line-height: 1.7; + margin-bottom: 32px; + max-width: 400px; + margin-left: auto; + margin-right: auto; +} + +/* Input group with icon */ +.login-input-group { + display: flex; + align-items: center; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 12px; + padding: 0 16px; + margin-bottom: 16px; + transition: + border-color 0.25s ease, + background 0.25s ease, + box-shadow 0.25s ease; +} +.login-input-group:focus-within { + border-color: rgba(70, 120, 249, 0.4); + background: rgba(70, 120, 249, 0.04); +} +.login-input-group:focus-within > i:first-child { + color: #4678f9; +} +.login-input-group > i:first-child { + color: rgba(255, 255, 255, 0.35); + font-size: 15px; + margin-right: 12px; + flex-shrink: 0; + transition: color 0.25s ease; +} +.login-input-group .form-input { + flex: 1; + background: transparent !important; + border: none !important; + box-shadow: none !important; + color: #fff; + padding: 14px 0; + font-size: 15px; + outline: none; + text-align: left; +} +.login-input-group .form-input::placeholder { + color: rgba(255, 255, 255, 0.3); +} + +/* Password toggle button */ +.password-toggle { + background: none; + border: none; + color: rgba(255, 255, 255, 0.3); + cursor: pointer; + padding: 4px 0 4px 8px; + font-size: 14px; + line-height: 1; + transition: color 0.2s ease; + flex-shrink: 0; +} +.password-toggle:hover { + color: rgba(255, 255, 255, 0.6); +} + +/* Login button */ +.login-btn { + width: 100%; + padding: 15px; + margin-top: 10px; + margin-bottom: 0; + border: none; + border-radius: 12px; + background: linear-gradient(135deg, #4678f9, #3b5de7); + color: #fff; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: + transform 0.15s ease, + box-shadow 0.15s ease, + opacity 0.15s ease; +} +.login-btn:hover:not(:disabled) { + box-shadow: 0 4px 16px rgba(70, 120, 249, 0.3); +} +.login-btn:active:not(:disabled) { + box-shadow: 0 2px 8px rgba(70, 120, 249, 0.15); +} +.login-btn:disabled { + opacity: 0.7; + cursor: not-allowed; +} + +/* Login button loader */ +.login-btn-loader { + display: inline-flex; + align-items: center; + gap: 6px; +} + +/* Join Room title */ +.join-room-title { + font-size: 22px; + font-weight: 700; + color: #fff; + margin-bottom: 24px; + line-height: 1.5; +} + +/* Room input row: input + random button side by side */ +.room-input-row { + display: flex; + gap: 10px; + align-items: stretch; + margin-bottom: 16px; +} +.room-input-row .login-input-group { + flex: 1; + margin-bottom: 0; +} + +/* Random room button */ +.random-room-btn { + width: 52px; + min-width: 52px; + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 12px; + background: rgba(255, 255, 255, 0.05); + color: rgba(255, 255, 255, 0.6); + font-size: 17px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: + border-color 0.2s ease, + color 0.2s ease, + background 0.2s ease; +} +.random-room-btn:hover { + border-color: rgba(70, 120, 249, 0.35); + color: #4678f9; + background: rgba(70, 120, 249, 0.06); +} +.random-room-btn:active { + transform: scale(0.95); +} + +/* Join room button */ +.join-room-btn { + width: 100%; + padding: 15px; + border: none; + border-radius: 12px; + background: linear-gradient(135deg, #2ece89, #27b578); + color: #fff; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: + transform 0.15s ease, + box-shadow 0.15s ease; +} +.join-room-btn:hover { + box-shadow: 0 4px 16px rgba(46, 206, 137, 0.3); +} +.join-room-btn:active { + box-shadow: 0 2px 8px rgba(46, 206, 137, 0.15); +} + +/* Responsive */ +@media (min-width: 641px) { + .login-card { + min-width: 420px; + } +} +@media (min-width: 1025px) { + .login-card { + margin: 0; + } +} +@media (max-width: 640px) { + .login-card { + margin: 0 16px; + padding: 44px 28px 36px; + } + .login-title, + .join-room-title { + font-size: 22px; + } + .login-icon { + width: 60px; + height: 60px; + } + .login-icon i { + font-size: 26px; + } +} diff --git a/public/css/waitingRoom.css b/public/css/waitingRoom.css new file mode 100644 index 000000000..b24a7ff86 --- /dev/null +++ b/public/css/waitingRoom.css @@ -0,0 +1,182 @@ +/* Center card section */ +.center-card-section { + display: flex; + align-items: center; + min-height: calc(100vh - 200px); + padding-top: 0 !important; + padding-bottom: 0 !important; +} +.center-card-section .container { + width: 100%; +} +.center-card-section .hero-figure { + display: none; +} +@media (min-width: 1025px) { + .center-card-section .hero-figure { + display: block; + } +} + +/* Waiting Room Card */ +.waiting-card { + max-width: 552px; + width: 100%; + margin: 0 auto; + padding: 56px 48px 48px; + background: linear-gradient(160deg, rgba(15, 15, 25, 0.95) 40%, rgba(70, 120, 249, 0.12) 100%); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 20px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); + text-align: center; +} + +/* Icon */ +.waiting-icon { + width: 72px; + height: 72px; + margin: 0 auto 24px; + border-radius: 50%; + background: rgba(70, 120, 249, 0.1); + border: 1px solid rgba(70, 120, 249, 0.2); + display: flex; + align-items: center; + justify-content: center; + animation: iconPulse 3s ease-in-out infinite; +} +.waiting-icon i { + font-size: 30px; + color: #4678f9; +} + +@keyframes iconPulse { + 0%, + 100% { + box-shadow: 0 0 0 0 rgba(70, 120, 249, 0.25); + } + 50% { + box-shadow: 0 0 0 16px rgba(70, 120, 249, 0); + } +} + +/* Title */ +.waiting-title { + font-size: 26px; + font-weight: 700; + color: #fff; + margin-bottom: 10px; + letter-spacing: -0.4px; +} + +/* Description */ +.waiting-description { + font-size: 15px; + color: rgba(255, 255, 255, 0.55); + line-height: 1.7; + margin-bottom: 32px; + max-width: 400px; + margin-left: auto; + margin-right: auto; +} + +/* Spinner */ +.waiting-spinner { + display: flex; + justify-content: center; + gap: 10px; + margin-bottom: 18px; +} +.waiting-spinner-dot { + width: 12px; + height: 12px; + border-radius: 50%; + background: #4678f9; + animation: dotBounce 1.4s ease-in-out infinite; +} +.waiting-spinner-dot:nth-child(2) { + animation-delay: 0.2s; +} +.waiting-spinner-dot:nth-child(3) { + animation-delay: 0.4s; +} + +@keyframes dotBounce { + 0%, + 80%, + 100% { + transform: scale(0.6); + opacity: 0.35; + } + 40% { + transform: scale(1); + opacity: 1; + } +} + +/* Status */ +.waiting-status { + font-size: 14px; + color: rgba(255, 255, 255, 0.45); + margin-bottom: 24px; + transition: + color 0.3s ease, + font-weight 0.3s ease; +} +.waiting-status.ready { + color: #2ece89; + font-weight: 600; + font-size: 15px; +} + +/* Divider */ +.waiting-divider { + border: none; + border-top: 1px solid rgba(255, 255, 255, 0.08); + margin: 0 0 20px; +} + +/* Host login link */ +.host-login-link { + font-size: 14px; + color: rgba(255, 255, 255, 0.45); + margin: 0; +} +.host-login-link a { + color: #4678f9; + text-decoration: none; + font-weight: 600; + margin-left: 6px; + transition: color 0.2s; +} +.host-login-link a:hover { + color: #6d9bff; + text-decoration: underline; +} + +/* Responsive */ +@media (min-width: 641px) { + .waiting-card { + min-width: 420px; + } +} +@media (min-width: 1025px) { + .waiting-card { + margin: 0; + } +} +@media (max-width: 640px) { + .waiting-card { + margin: 0 16px; + padding: 44px 28px 36px; + } + .waiting-title { + font-size: 22px; + } + .waiting-icon { + width: 60px; + height: 60px; + } + .waiting-icon i { + font-size: 26px; + } +} diff --git a/public/css/widgets/Support.css b/public/css/widgets/Support.css new file mode 100644 index 000000000..ce76fefdc --- /dev/null +++ b/public/css/widgets/Support.css @@ -0,0 +1,852 @@ +/* Keyframes */ +@keyframes widgetPulse { + 0% { + transform: scale(1); + opacity: 1; + } + 50% { + transform: scale(1.1); + opacity: 0.8; + } + 100% { + transform: scale(1); + opacity: 1; + } +} + +@keyframes fadeInDown { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Main Widget Styles */ +.mirotalk-support-widget { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + background: linear-gradient(135deg, #1f1f2e 0%, #2a2a3e 100%); + color: #fff; + padding: 24px; + border-radius: 20px; + width: 360px; + max-height: 90vh; + height: auto; + overflow-y: auto; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); + position: fixed; + transition: all 0.3s ease; + border: 1px solid rgba(255, 255, 255, 0.1); + z-index: 9999; +} + +/* Light Theme Overrides */ +.mirotalk-support-widget.light-theme { + background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); + color: #333; + border: 1px solid rgba(0, 0, 0, 0.1); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15); +} + +/* Position Classes */ +.mirotalk-support-widget.bottom-right, +.mirotalk-minimized-btn.bottom-right, +.mirotalk-reopener-btn.bottom-right { + bottom: 20px; + right: 20px; +} + +.mirotalk-support-widget.bottom-left, +.mirotalk-minimized-btn.bottom-left, +.mirotalk-reopener-btn.bottom-left { + bottom: 20px; + left: 20px; +} + +.mirotalk-support-widget.top-right, +.mirotalk-minimized-btn.top-right, +.mirotalk-reopener-btn.top-right { + top: 20px; + right: 20px; +} + +.mirotalk-support-widget.top-left, +.mirotalk-minimized-btn.top-left, +.mirotalk-reopener-btn.top-left { + top: 20px; + left: 20px; +} + +/* Widget Hover Effects */ +.mirotalk-support-widget:hover { + transform: translateY(-2px); + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5); +} + +.mirotalk-support-widget.light-theme:hover { + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.2); +} + +/* Online Indicator */ +.mirotalk-support-widget .online-indicator { + display: flex; + align-items: center; + margin-bottom: 16px; + animation: fadeInDown 0.6s ease; +} + +.mirotalk-minimized-btn .status-dot, +.mirotalk-support-widget .status-dot { + width: 12px; + height: 12px; + border-radius: 50%; + margin-right: 8px; +} + +.mirotalk-minimized-btn .status-dot.online, +.mirotalk-support-widget .status-dot.online { + background: #00e676; + box-shadow: 0 0 8px rgba(0, 230, 118, 0.4); + animation: widgetPulse 2s infinite; +} + +.mirotalk-minimized-btn .status-dot.offline, +.mirotalk-support-widget .status-dot.offline { + background: #ff5252; + box-shadow: 0 0 8px rgba(255, 82, 82, 0.4); +} + +.mirotalk-support-widget .online-text { + color: #00e676; + font-weight: 600; + font-size: 14px; +} + +.mirotalk-support-widget .offline-text { + color: #ff5252; + font-weight: 600; + font-size: 14px; +} + +/* Widget Controls */ +.mirotalk-support-widget .widget-controls { + display: flex; + align-items: center; + gap: 8px; + margin-left: auto; +} + +.mirotalk-support-widget .minimize-btn { + background: rgba(255, 255, 255, 0.1); + border: none; + color: #aaa; + cursor: pointer; + padding: 4px 6px; + border-radius: 4px; + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; +} + +.mirotalk-support-widget.light-theme .minimize-btn { + background: rgba(0, 0, 0, 0.05); + color: #666; +} + +.mirotalk-support-widget .minimize-btn:hover { + background: rgba(255, 255, 255, 0.2); + color: #fff; +} + +.mirotalk-support-widget.light-theme .minimize-btn:hover { + background: rgba(0, 0, 0, 0.1); + color: #333; +} + +.mirotalk-support-widget .close-btn { + cursor: pointer; + font-weight: bold; + font-size: 18px; + line-height: 1; + padding: 4px; + border-radius: 4px; + transition: all 0.2s ease; + color: #aaa; +} + +.mirotalk-support-widget.light-theme .close-btn { + color: #666; +} + +.mirotalk-support-widget .close-btn:hover { + background: rgba(255, 255, 255, 0.1); + color: #fff; + transform: scale(1.1); +} + +.mirotalk-support-widget.light-theme .close-btn:hover { + background: rgba(0, 0, 0, 0.1); + color: #333; +} + +/* Content Elements */ +.mirotalk-support-widget .main-heading { + font-size: 24px; + font-weight: 700; + margin-bottom: 12px; + background: linear-gradient(135deg, #fff 0%, #e0e0e0 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + animation: fadeInUp 0.6s ease 0.1s both; +} + +.mirotalk-support-widget.light-theme .main-heading { + background: linear-gradient(135deg, #333 0%, #555 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.mirotalk-support-widget .subheading { + font-size: 16px; + line-height: 1.5; + margin-bottom: 24px; + color: #e0e0e0; + animation: fadeInUp 0.6s ease 0.2s both; +} + +.mirotalk-support-widget.light-theme .subheading { + color: #666; +} + +.mirotalk-support-widget .expert-images { + display: flex; + justify-content: center; + margin-bottom: 12px; + animation: fadeInUp 0.6s ease 0.3s both; +} + +.mirotalk-support-widget .expert-img { + width: 44px; + height: 44px; + border-radius: 50%; + border: 3px solid #333; + margin: 0 6px; + transition: all 0.3s ease; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} + +.mirotalk-support-widget.light-theme .expert-img { + border: 3px solid #e0e0e0; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +} + +.mirotalk-support-widget .connect-text { + text-align: center; + font-size: 14px; + margin-bottom: 24px; + color: #bbb; + animation: fadeInUp 0.6s ease 0.4s both; +} + +.mirotalk-support-widget.light-theme .connect-text { + color: #777; +} + +/* Buttons */ +.mirotalk-support-widget .btn { + display: flex; + align-items: center; + width: 100%; + background: linear-gradient(135deg, #333 0%, #444 100%); + color: white; + border: none; + border-radius: 12px; + padding: 14px 16px; + margin-bottom: 12px; + cursor: pointer; + transition: all 0.3s ease; + font-size: 16px; + font-weight: 500; + position: relative; + overflow: hidden; + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.mirotalk-support-widget.light-theme .btn { + background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); + color: #333; + border: 1px solid rgba(0, 0, 0, 0.1); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); +} + +.mirotalk-support-widget .btn:hover { + background: linear-gradient(135deg, #444 0%, #555 100%); + transform: translateY(-1px); + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3); +} + +.mirotalk-support-widget.light-theme .btn:hover { + background: linear-gradient(135deg, #e9ecef 0%, #dee2e6 100%); + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1); +} + +.mirotalk-support-widget .btn:active { + transform: translateY(0); +} + +.mirotalk-support-widget .btn-icon { + background: linear-gradient(135deg, #00e676 0%, #00c853 100%); + border-radius: 50%; + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 4px 12px rgba(0, 230, 118, 0.3); + transition: all 0.3s ease; + flex-shrink: 0; +} + +.mirotalk-support-widget .btn-text { + flex: 1; + text-align: center; + margin-left: -32px; +} + +.mirotalk-support-widget .btn:hover .btn-icon { + transform: scale(1.1); + box-shadow: 0 6px 16px rgba(0, 230, 118, 0.4); +} + +/* Footer */ +.mirotalk-support-widget .footer-text { + text-align: center; + font-size: 12px; + color: #888; + margin-top: 24px; + animation: fadeInUp 0.6s ease 0.5s both; +} + +.mirotalk-support-widget.light-theme .footer-text { + color: #999; +} + +.mirotalk-support-widget .footer-link { + color: #00e676; + text-decoration: none; + transition: color 0.2s ease; +} + +.mirotalk-support-widget .footer-link:hover { + color: #00c853; +} + +.mirotalk-support-widget .mirotalk-powered-by { + color: #00e676; + font-weight: bold; +} + +.mirotalk-support-widget.light-theme .mirotalk-powered-by { + color: #00c853; +} + +/* Widget States */ +.mirotalk-support-widget.minimized { + display: none; +} + +/* Minimized Button */ +.mirotalk-minimized-btn { + position: fixed; + background: linear-gradient(135deg, #1f1f2e 0%, #2a2a3e 100%); + color: #fff; + padding: 12px 16px; + border-radius: 25px; + cursor: pointer; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3); + transition: all 0.3s ease; + z-index: 9998; + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.mirotalk-minimized-btn.light-theme { + background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); + color: #333; + border: 1px solid rgba(0, 0, 0, 0.1); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); +} + +.mirotalk-minimized-btn:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4); +} + +.mirotalk-minimized-btn.light-theme:hover { + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2); +} + +.mirotalk-minimized-btn .minimized-content { + display: flex; + align-items: center; + gap: 8px; + font-weight: 600; + font-size: 14px; +} + +/* Reopener Button - Fully Rounded */ +.mirotalk-reopener-btn { + position: fixed; + background: linear-gradient(135deg, #23233a 0%, #2a2a3e 100%); + color: #fff; + padding: 0; + width: 80px; + height: 80px; + border-radius: 50%; + cursor: pointer; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); + transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); + z-index: 9998; + display: flex; + align-items: center; + justify-content: center; + border: 2px solid rgba(255, 255, 255, 0.15); + font-weight: 600; + font-size: 16px; + letter-spacing: 0.02em; + user-select: none; +} + +.mirotalk-reopener-btn.light-theme { + background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); + color: #222; + border: 2px solid rgba(0, 0, 0, 0.13); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12); +} + +.mirotalk-reopener-btn:hover { + transform: translateY(-4px) scale(1.07); + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6); + background: linear-gradient(135deg, #2a2a3e 0%, #23233a 100%); + color: #00e676; + border-color: #00e676; +} + +.mirotalk-reopener-btn.light-theme:hover { + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.18); + background: linear-gradient(135deg, #e9ecef 0%, #f8f9fa 100%); + color: #00c853; + border-color: #00c853; +} + +.mirotalk-reopener-btn .reopener-content { + display: flex; + flex-direction: column; + align-items: center; + gap: 2px; +} + +.mirotalk-reopener-btn .reopener-content span { + font-size: 11px; + font-weight: 600; + text-align: center; + line-height: 1.1; +} + +/* Responsive Design */ +@media (max-width: 1200px) { + .mirotalk-support-widget { + width: 300px; + padding: 22px; + } +} + +@media (max-width: 992px) { + .mirotalk-support-widget { + width: 280px; + padding: 20px; + bottom: 15px; + right: 15px; + } + + .mirotalk-support-widget .main-heading { + font-size: 22px; + } + + .mirotalk-support-widget .subheading { + font-size: 15px; + margin-bottom: 20px; + } + + .mirotalk-support-widget .expert-img { + width: 40px; + height: 40px; + margin: 0 4px; + } + + .mirotalk-support-widget .btn { + padding: 12px 14px; + font-size: 15px; + margin-bottom: 10px; + } + + .mirotalk-support-widget .btn-icon { + width: 28px; + height: 28px; + } + + .mirotalk-support-widget .btn-text { + margin-left: -28px; + } +} + +@media (max-width: 768px) { + .mirotalk-support-widget { + width: 260px; + padding: 18px; + bottom: 12px; + right: 12px; + border-radius: 16px; + } + + .mirotalk-support-widget.bottom-left, + .mirotalk-support-widget.top-left { + left: 12px; + } + + .mirotalk-support-widget.top-right, + .mirotalk-support-widget.top-left { + top: 12px; + } + + .mirotalk-support-widget .main-heading { + font-size: 20px; + margin-bottom: 10px; + } + + .mirotalk-support-widget .subheading { + font-size: 14px; + margin-bottom: 18px; + line-height: 1.4; + } + + .mirotalk-support-widget .expert-images { + margin-bottom: 10px; + } + + .mirotalk-support-widget .expert-img { + width: 36px; + height: 36px; + margin: 0 3px; + border-width: 2px; + } + + .mirotalk-support-widget .connect-text { + font-size: 13px; + margin-bottom: 18px; + } + + .mirotalk-support-widget .btn { + padding: 11px 12px; + font-size: 14px; + margin-bottom: 8px; + border-radius: 10px; + } + + .mirotalk-support-widget .btn-icon { + width: 26px; + height: 26px; + } + + .mirotalk-support-widget .btn-text { + margin-left: -26px; + font-size: 14px; + } + + .mirotalk-reopener-btn.bottom-right { + bottom: 12px; + right: 12px; + } + + .mirotalk-reopener-btn.bottom-left { + bottom: 12px; + left: 12px; + } + + .mirotalk-reopener-btn.top-right { + top: 12px; + right: 12px; + } + + .mirotalk-reopener-btn.top-left { + top: 12px; + left: 12px; + } +} + +@media (max-width: 480px) { + .mirotalk-support-widget { + width: 240px; + padding: 16px; + bottom: 10px; + right: 10px; + border-radius: 14px; + } + + .mirotalk-support-widget.bottom-left, + .mirotalk-support-widget.top-left { + left: 10px; + } + + .mirotalk-support-widget.top-right, + .mirotalk-support-widget.top-left { + top: 10px; + } + + .mirotalk-support-widget .online-indicator { + margin-bottom: 12px; + } + + .mirotalk-minimized-btn .status-dot .mirotalk-support-widget .status-dot { + width: 10px; + height: 10px; + } + + .mirotalk-support-widget .online-text, + .mirotalk-support-widget .offline-text { + font-size: 13px; + } + + .mirotalk-support-widget .close-btn { + font-size: 16px; + padding: 2px; + } + + .mirotalk-support-widget .main-heading { + font-size: 18px; + margin-bottom: 8px; + } + + .mirotalk-support-widget .subheading { + font-size: 13px; + margin-bottom: 16px; + } + + .mirotalk-support-widget .expert-images { + margin-bottom: 8px; + } + + .mirotalk-support-widget .expert-img { + width: 32px; + height: 32px; + margin: 0 2px; + } + + .mirotalk-support-widget .connect-text { + font-size: 12px; + margin-bottom: 16px; + } + + .mirotalk-support-widget .btn { + padding: 10px; + font-size: 13px; + margin-bottom: 6px; + border-radius: 8px; + } + + .mirotalk-support-widget .btn-icon { + width: 24px; + height: 24px; + } + + .mirotalk-support-widget .btn-icon svg { + width: 14px; + height: 14px; + } + + .mirotalk-support-widget .btn-text { + margin-left: -24px; + font-size: 13px; + } + + .mirotalk-support-widget .footer-text { + margin-top: 16px; + font-size: 10px; + } + + .mirotalk-reopener-btn.bottom-right { + bottom: 10px; + right: 10px; + } + + .mirotalk-reopener-btn.bottom-left { + bottom: 10px; + left: 10px; + } + + .mirotalk-reopener-btn.top-right { + top: 10px; + right: 10px; + } + + .mirotalk-reopener-btn.top-left { + top: 10px; + left: 10px; + } +} + +@media (max-width: 360px) { + .mirotalk-support-widget { + width: 220px; + padding: 14px; + bottom: 8px; + right: 8px; + } + + .mirotalk-support-widget.bottom-left, + .mirotalk-support-widget.top-left { + left: 8px; + } + + .mirotalk-support-widget.top-right, + .mirotalk-support-widget.top-left { + top: 8px; + } + + .mirotalk-support-widget .main-heading { + font-size: 16px; + } + + .mirotalk-support-widget .subheading { + font-size: 12px; + } + + .mirotalk-support-widget .expert-img { + width: 28px; + height: 28px; + } + + .mirotalk-support-widget .btn { + padding: 8px; + font-size: 12px; + } + + .mirotalk-support-widget .btn-icon { + width: 22px; + height: 22px; + } + + .mirotalk-support-widget .btn-icon svg { + width: 12px; + height: 12px; + } + + .mirotalk-support-widget .btn-text { + margin-left: -22px; + font-size: 12px; + } +} + +/* Landscape Orientation */ +@media (max-height: 600px) and (orientation: landscape) { + .mirotalk-support-widget { + padding: 12px; + width: 280px; + max-height: 90vh; + overflow-y: auto; + } + + .mirotalk-support-widget .main-heading { + font-size: 18px; + margin-bottom: 6px; + } + + .mirotalk-support-widget .subheading { + font-size: 13px; + margin-bottom: 12px; + } + + .mirotalk-support-widget .expert-images { + margin-bottom: 6px; + } + + .mirotalk-support-widget .connect-text { + margin-bottom: 12px; + } + + .mirotalk-support-widget .btn { + padding: 8px 10px; + margin-bottom: 4px; + } + + .mirotalk-support-widget .footer-text { + margin-top: 12px; + } +} + +/* High DPI Displays */ +@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { + .mirotalk-support-widget { + border-width: 0.5px; + } + + .mirotalk-support-widget .btn { + border-width: 0.5px; + } + + .mirotalk-support-widget .expert-img { + border-width: 1.5px; + } +} + +/* Accessibility - Reduced Motion */ +@media (prefers-reduced-motion: reduce) { + .mirotalk-support-widget, + .mirotalk-support-widget *, + .mirotalk-minimized-btn, + .mirotalk-reopener-btn { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } + + .mirotalk-minimized-btn .status-dot.online, + .mirotalk-support-widget .status-dot.online { + animation: none; + } +} + +/* Focus States for Accessibility */ +.mirotalk-support-widget .btn:focus, +.mirotalk-support-widget .minimize-btn:focus, +.mirotalk-support-widget .close-btn:focus, +.mirotalk-minimized-btn:focus, +.mirotalk-reopener-btn:focus { + outline: 2px solid #00e676; + outline-offset: 2px; +} + +/* Print Styles */ +@media print { + .mirotalk-support-widget, + .mirotalk-minimized-btn, + .mirotalk-reopener-btn { + display: none !important; + } +} diff --git a/public/images/admin.png b/public/images/admin.png new file mode 100644 index 000000000..07856acd0 Binary files /dev/null and b/public/images/admin.png differ diff --git a/public/images/deepSeek.png b/public/images/deepSeek.png new file mode 100644 index 000000000..eb05814b1 Binary files /dev/null and b/public/images/deepSeek.png differ diff --git a/public/images/mediasoup.png b/public/images/mediasoup.png new file mode 100644 index 000000000..97ce79949 Binary files /dev/null and b/public/images/mediasoup.png differ diff --git a/public/images/self-hosting.png b/public/images/self-hosting.png new file mode 100644 index 000000000..96a8139c4 Binary files /dev/null and b/public/images/self-hosting.png differ diff --git a/public/images/transparentBg.png b/public/images/transparentBg.png new file mode 100644 index 000000000..972f809b7 Binary files /dev/null and b/public/images/transparentBg.png differ diff --git a/public/js/ActiveRooms.js b/public/js/ActiveRooms.js new file mode 100644 index 000000000..777651a01 --- /dev/null +++ b/public/js/ActiveRooms.js @@ -0,0 +1,89 @@ +'use strict'; + +console.log(window.location); + +const isTest = false; // Set to true for testing with mock data + +const roomsDiv = document.getElementById('rooms'); +const searchInput = document.getElementById('searchInput'); +const searchBtn = document.getElementById('search-btn'); +const refreshBtn = document.getElementById('refresh-btn'); + +let allRooms = []; + +searchBtn.addEventListener('click', handleSearch); +refreshBtn.addEventListener('click', fetchRooms); + +function setRoomsContent(html) { + roomsDiv.innerHTML = html; +} + +function getRoomsData(res) { + return !isTest ? res.data.activeRooms || [] : mockRooms(); +} + +function mockRooms(roomCount = 1000) { + return Array.from({ length: roomCount }, () => { + const id = getUUID(); + return { + id, + peers: Math.floor(Math.random() * 10) + 1, + join: `${window.location.origin}/${id}`, + }; + }); +} + +function getUUID() { + return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => + (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16) + ); +} + +async function fetchRooms() { + setRoomsContent('
Loading...
'); + try { + const res = await axios.get('/api/v1/activeRooms'); + if (res.status !== 200) throw new Error('Failed to fetch active rooms'); + allRooms = getRoomsData(res); + renderRooms(allRooms); + } catch (err) { + const errorMsg = err.response?.data?.error || err.message; + setRoomsContent(`
${errorMsg}
`); + } +} + +function renderRooms(rooms) { + if (!rooms.length) { + setRoomsContent('
No active rooms found.
'); + return; + } + setRoomsContent( + rooms + .map( + (room) => ` +
+
+ + ${room.id} +
+
+ + ${room.peers} +
+
${room.peers === 1 ? 'peer' : 'peers'}
+ + Join + +
+ ` + ) + .join('') + ); +} + +function handleSearch() { + const value = searchInput.value.trim().toLowerCase(); + renderRooms(!value ? allRooms : allRooms.filter((room) => room.id.toLowerCase().includes(value))); +} + +fetchRooms(); diff --git a/public/js/Brand.js b/public/js/Brand.js index 87359902b..6adcc21c5 100644 --- a/public/js/Brand.js +++ b/public/js/Brand.js @@ -16,15 +16,28 @@ const appTitle = document.getElementById('appTitle'); const appDescription = document.getElementById('appDescription'); const joinDescription = document.getElementById('joinDescription'); const joinRoomBtn = document.getElementById('joinRoomButton'); +const customizeRoomBtn = document.getElementById('customizeRoomButton'); const joinLastLabel = document.getElementById('joinLastLabel'); +const topSponsors = document.getElementById('topSponsors'); const features = document.getElementById('features'); const teams = document.getElementById('teams'); const tryEasier = document.getElementById('tryEasier'); const poweredBy = document.getElementById('poweredBy'); const sponsors = document.getElementById('sponsors'); const advertisers = document.getElementById('advertisers'); +const supportUs = document.getElementById('supportUs'); const footer = document.getElementById('footer'); + +const waitingRoomHeading = document.getElementById('waitingRoomHeading'); +const waitingRoomDescription = document.getElementById('waitingRoomDescription'); +const waitingRoomStatus = document.getElementById('waitingStatus'); +const waitingRoomHostLink = document.getElementById('waitingRoomHostLink'); +const waitingRoomLoginLink = document.getElementById('waitingRoomLoginLink'); + +const loginHeading = document.getElementById('loginHeading'); +const loginDescription = document.getElementById('loginDescription'); +const loginButton = document.getElementById('loginButton'); //... // app/src/config.js - ui.brand @@ -37,6 +50,7 @@ let BRAND = { 'Start your next video call with a single click. No download, plug-in, or login is required. Just get straight to talking, messaging, and sharing your screen.', joinDescription: 'Pick a room name.
How about this one?', joinButtonLabel: 'JOIN ROOM', + customizeButtonLabel: 'CUSTOMIZE ROOM', joinLastLabel: 'Your recent room:', }, site: { @@ -54,17 +68,35 @@ let BRAND = { 'webrtc, miro, mediasoup, mediasoup-client, self hosted, voip, sip, real-time communications, chat, messaging, meet, webrtc stun, webrtc turn, webrtc p2p, webrtc sfu, video meeting, video chat, video conference, multi video chat, multi video conference, peer to peer, p2p, sfu, rtc, alternative to, zoom, microsoft teams, google meet, jitsi, meeting', }, html: { + topSponsors: true, features: true, teams: true, tryEasier: true, poweredBy: true, sponsors: true, advertisers: true, + supportUs: true, footer: true, }, + whoAreYou: { + title: 'MiroTalk SFU - Waiting for host to start the meeting', + waitingRoomHeading: 'Waiting for host...', + waitingRoomDescription: + "The meeting hasn't started yet.
You'll join automatically when the host opens the room.", + waitingRoomStatus: 'Checking room status...', + waitingRoomReady: 'Room is ready! Joining...', + waitingRoomWaiting: 'Waiting for host to start the meeting...', + waitingRoomHostLink: 'Are you the host?', + waitingRoomLoginLink: 'Login here', + }, + login: { + heading: 'Welcome back', + description: 'Enter your credentials to continue.', + buttonLabel: 'Login', + }, about: { imageUrl: '../images/mirotalk-logo.gif', - title: 'WebRTC SFU v1.7.76', + title: 'WebRTC SFU v2.1.51', html: ` - + diff --git a/public/views/Room.html b/public/views/Room.html index 07bd1f134..921c23169 100644 --- a/public/views/Room.html +++ b/public/views/Room.html @@ -103,7 +103,7 @@ - + @@ -119,11 +119,18 @@ + + + + + + + @@ -167,10 +174,11 @@

Loading

- + + @@ -196,44 +204,131 @@

Paste Image URL

-
- - - - - - - - - - - - - - +
- - - - - +
+ + + +
+
+ + + +
- + - - -
- -
-
- + +
+ +
+
+ +