diff --git a/.env.development b/.env.development index e4c9ebd5..1b5c435d 100644 --- a/.env.development +++ b/.env.development @@ -5,8 +5,8 @@ REACT_APP_DEMO_MODE=false REACT_APP_BASENAME= # Nostr relay configuration for profile fetching -REACT_APP_OWN_RELAY_URL=ws://localhost:7000 -REACT_APP_NOSTR_RELAY_URLS=wss://relay.damus.io,wss://relay.nostr.band,wss://relay.snort.social,wss://vault.iris.to +REACT_APP_OWN_RELAY_URL=ws://localhost:9001 +# REACT_APP_NOSTR_RELAY_URLS=wss://your-relay1.com,wss://your-relay2.com,wss://your-relay3.com # More info https://create-react-app.dev/docs/advanced-configuration ESLINT_NO_DEV_ERRORS=true diff --git a/.env.production b/.env.production new file mode 100644 index 00000000..e7eba346 --- /dev/null +++ b/.env.production @@ -0,0 +1,23 @@ +# Production Environment Configuration +# Panel integrated into relay server + +# Demo mode (set to false for production) +REACT_APP_DEMO_MODE=false + +# Direct wallet service access +REACT_APP_WALLET_BASE_URL= + +# Router configuration (empty since served from relay server root) +REACT_APP_BASENAME= +PUBLIC_URL= + +# Asset serving configuration (optional - adjust for your domain) +# REACT_APP_ASSETS_BUCKET=https://your-domain.com + +# Nostr relay configuration for profile fetching +REACT_APP_OWN_RELAY_URL=ws://localhost:9001 +# REACT_APP_NOSTR_RELAY_URLS=wss://your-relay1.com,wss://your-relay2.com + +# Development optimizations +ESLINT_NO_DEV_ERRORS=true +TSC_COMPILE_ON_ERROR=true \ No newline at end of file diff --git a/.env.production.example b/.env.production.example index d5963d33..31ab8908 100644 --- a/.env.production.example +++ b/.env.production.example @@ -1,23 +1,20 @@ # Production Environment Configuration Example # Copy this file to .env.production and update with your actual values -# API Base URLs - Update these to your actual backend URLs -REACT_APP_BASE_URL=https://your-domain.com/panel -REACT_APP_WALLET_BASE_URL=https://your-domain.com/wallet - -# Asset serving configuration -REACT_APP_ASSETS_BUCKET=https://your-domain.com - -# Demo mode (set to true for demo, false for production) +# Demo mode (set to false for production) REACT_APP_DEMO_MODE=false -# Router basename - where your React app will be served from -# Use empty string for root path, or /admin, /dashboard, etc. -REACT_APP_BASENAME=/front +# Router configuration for /panel/ path (served through nginx) +REACT_APP_BASENAME=/panel +PUBLIC_URL=/panel + +# Asset serving configuration (optional - adjust for your domain) +# REACT_APP_ASSETS_BUCKET=https://your-domain.com -# Public URL for static assets - should match REACT_APP_BASENAME -PUBLIC_URL=/front +# Nostr relay configuration for profile fetching +# REACT_APP_OWN_RELAY_URL= # β Auto-detected from current domain +# REACT_APP_NOSTR_RELAY_URLS=wss://your-relay1.com,wss://your-relay2.com -# More info https://create-react-app.dev/docs/advanced-configuration +# Development optimizations ESLINT_NO_DEV_ERRORS=true TSC_COMPILE_ON_ERROR=true \ No newline at end of file diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 14b7746c..781c225c 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -42,7 +42,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -56,7 +56,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 # βΉοΈ Command-line programs to run using the OS shell. # π See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -69,4 +69,4 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.gitignore b/.gitignore index 12f24f8a..8ebb7f2c 100644 --- a/.gitignore +++ b/.gitignore @@ -22,7 +22,7 @@ .env.development.local .env.test.local .env.production.local -.env.production + npm-debug.log* yarn-debug.log* diff --git a/README.md b/README.md index b905ae28..4a691b74 100644 --- a/README.md +++ b/README.md @@ -71,49 +71,51 @@ The panel uses **NIP-07** ([window.nostr capability](https://nostr-nips.com/nip- The HORNETS Relay Panel is built with a microservices architecture comprising: -### Services & Dependencies -- **Frontend (React App)**: Port 3000 (dev) - The admin dashboard interface -- **Panel API**: Port 9002 - Backend service for panel operations -- **[Relay Service](https://github.com/HORNET-Storage/HORNETS-Nostr-Relay)**: Port 9001 - WebSocket service for Nostr relay functionality +### Integrated Architecture +The panel is now **integrated directly into the relay server** for simplified deployment: + +- **Relay + Panel Server**: Port 9002 - Serves both the React app (static files) and panel API +- **[Relay WebSocket](https://github.com/HORNET-Storage/HORNETS-Nostr-Relay)**: Port 9001 - WebSocket service for Nostr relay functionality - **[Wallet Service](https://github.com/HORNET-Storage/Super-Neutrino-Wallet)**: Port 9003 - Backend service for wallet operations - **[Media Moderation](https://github.com/HORNET-Storage/NestShield)**: Port 8000 - Content moderation and filtering service -### Reverse Proxy Architecture +### Hybrid Architecture ``` -Client Request +Client Request (http://localhost or your-domain.com) β -Nginx (Port 80/443) +Nginx Proxy (Port 80/443) - Optional but recommended for production β -βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ -β Route Distribution: β -β βββββββββββββββββββ βββββββββββββββββββ ββββββββββββββββ β -β β / β Relay β β /front/ β React β β /panel/ β APIβ β -β β (Port 9001) β β (Port 3000) β β (Port 9002) β β -β βββββββββββββββββββ βββββββββββββββββββ ββββββββββββββββ β -β βββββββββββββββββββ βββββββββββββββββββ β -β β /wallet/ β Walletβ β/moderate/ β Mediaβ β -β β (Port 9003) β β (Port 8000) β β -β βββββββββββββββββββ βββββββββββββββββββ β -βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ +βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ +β Route Distribution: β +β βββββββββββββββββββββββββββ βββββββββββββββββββββββββββ β +β β / β Relay + Panel β β /wallet/ β Wallet API β β +β β (Port 9002) β β (Port 9003) β β +β β βββ /api/* β Panel API β β β β +β β βββ /* β React App β β β β +β βββββββββββββββββββββββββββ βββββββββββββββββββββββββββ β +β β +β WebSocket Connection: β +β βββββββββββββββββββββββββββ β +β β ws:// β Relay WebSocket β β +β β (Port 9001) β β +β βββββββββββββββββββββββββββ β +βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ ``` -## π§ Why Use a Reverse Proxy? +## π§ Deployment Options -### Benefits of Reverse Proxy Setup (Recommended) -1. **Security**: Single entry point with centralized security headers -2. **SSL/TLS Termination**: Handle HTTPS certificates at the proxy level -3. **Load Balancing**: Distribute traffic across service instances -4. **Clean URLs**: User-friendly paths (`/front/`, `/panel/`) instead of ports -5. **Single Domain**: All services accessible from one domain -6. **WebSocket Support**: Proper handling of WebSocket connections for the relay -7. **Tunnel Compatibility**: Works seamlessly with ngrok and other tunneling services +### Direct Access (Development) +For development, you can run services directly: +- **Relay + Panel**: `http://localhost:9002` (no proxy needed) +- **Wallet Service**: `http://localhost:9003` (direct API calls) -### Direct Access (Development Only) -While possible, direct port access has limitations: -- Multiple ports to manage -- CORS issues between services -- No unified SSL certificate -- Poor user experience with port numbers in URLs +### Nginx Proxy (Production Recommended) +For production deployment, nginx handles: +1. **Wallet Service Proxying**: `/wallet/*` β `localhost:9003` +2. **SSL Termination**: Single certificate for entire application +3. **WebSocket Proxying**: Proper upgrade headers for relay WebSocket +4. **Static Asset Caching**: Optimal performance for React app +5. **Security Headers**: CORS, CSP, and other protections ## π Prerequisites @@ -147,61 +149,74 @@ yarn install ### 3. Environment Configuration #### Development Setup -For development, you can use the default configuration or create `.env.development`: -```bash -# Optional - defaults work for local development +For development, create `.env.development` with your local service URLs: +```env REACT_APP_BASE_URL=http://localhost:9002 REACT_APP_WALLET_BASE_URL=http://localhost:9003 +REACT_APP_ASSETS_BUCKET=http://localhost REACT_APP_DEMO_MODE=false +REACT_APP_BASENAME= + +# Nostr relay configuration for profile fetching +REACT_APP_OWN_RELAY_URL=ws://localhost:9001 +# REACT_APP_NOSTR_RELAY_URLS=wss://your-relay1.com,wss://your-relay2.com,wss://your-relay3.com + +# More info https://create-react-app.dev/docs/advanced-configuration +ESLINT_NO_DEV_ERRORS=true +TSC_COMPILE_ON_ERROR=true ``` #### Production Setup -For production, minimal environment configuration is needed thanks to **dynamic URL detection**: +For production deployment, you need explicit service URL configuration: -```bash -cp .env.production.example .env.production -``` +Create `.env.production` for production builds: -Edit `.env.production` (most values are now auto-detected): ```env -# Router configuration for reverse proxy -REACT_APP_BASENAME=/front -PUBLIC_URL=/front - -# Optional: Demo mode (defaults to false) +# Demo mode (set to false for production) REACT_APP_DEMO_MODE=false -# Optional: Custom Nostr relay URLs (defaults to popular relays) -# REACT_APP_NOSTR_RELAY_URLS=wss://relay.damus.io,wss://relay.nostr.band +# Service URLs +REACT_APP_WALLET_BASE_URL=http://localhost:9003 # Optional - leave empty to disable wallet features +REACT_APP_OWN_RELAY_URL=ws://localhost:9001 # Required for profile fetching + +# Router configuration (empty for direct access) +REACT_APP_BASENAME= +PUBLIC_URL= + +# Optional: Custom Nostr relay URLs (comma-separated list) +# REACT_APP_NOSTR_RELAY_URLS=wss://your-relay1.com,wss://your-relay2.com # Development optimizations ESLINT_NO_DEV_ERRORS=true TSC_COMPILE_ON_ERROR=true ``` -**π― Key Improvement**: The panel now **automatically detects** API URLs from `window.location.origin`, meaning: -- β **No need to specify `REACT_APP_BASE_URL` or `REACT_APP_WALLET_BASE_URL`** -- β **Same build works on ANY domain** (localhost, your-domain.com, ngrok tunnels, etc.) -- β **No environment-specific rebuilds required** + +**π― Key Requirements**: +- β **Relay URL Required** - REACT_APP_OWN_RELAY_URL must be configured for profile fetching +- β **Wallet URL Optional** - REACT_APP_WALLET_BASE_URL can be empty to disable wallet features +- β **Panel Routing Auto-Detection** - Panel paths (REACT_APP_BASENAME/PUBLIC_URL) can be auto-detected +- β **Build-Time Configuration** - Service URLs are baked into the JavaScript bundle during build +- β **Simple Deployment** - No reverse proxy needed for basic functionality ### 4. Start Development Server -#### Using yarn (standard) +#### Using provided script (recommended - handles Node.js compatibility) ```bash -yarn start +./start-app.sh # Linux/macOS +start.bat # Windows ``` -#### Using provided script (handles Node.js compatibility) +#### Using yarn directly ```bash -./start-app.sh # Linux/macOS -start.bat # Windows +yarn start ``` The development server will start on `http://localhost:3000` ## π Deployment -### Scenario 1: With Reverse Proxy (Recommended) +### Production Deployment #### Step 1: Build the Application ```bash @@ -213,177 +228,47 @@ yarn build yarn build # Linux/macOS ``` -#### Step 2: Configure Nginx -Create an nginx configuration file: -```bash -# Create the configuration file -sudo nano /etc/nginx/sites-available/hornets-relay -``` - -Add this configuration (adjust domains and paths as needed): -```nginx -# WebSocket connection upgrade mapping -map $http_upgrade $connection_upgrade { - default upgrade; - '' close; -} - -server { - listen 80; - server_name your-domain.com; # Replace with your domain - - # Forward client IP and protocol - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - # Panel API - location /panel/ { - rewrite ^/panel/(.*)$ /$1 break; - proxy_pass http://127.0.0.1:9002; - } - - # Wallet service - location /wallet/ { - rewrite ^/wallet/(.*)$ /$1 break; - proxy_pass http://127.0.0.1:9003; - } - - # Media moderation service (optional) - location /moderate/ { - rewrite ^/moderate/(.*)$ /$1 break; - proxy_pass http://127.0.0.1:8000; - } - - # Frontend React app - location /front/ { - rewrite ^/front/(.*)$ /$1 break; - proxy_pass http://127.0.0.1:3000; # Development: proxy to dev server - - # Production: Serve static files instead (uncomment and comment above) - # try_files $uri $uri/ /front/index.html; - # root /var/www/html; # Path to your built files - } - - # Default location - Relay service with WebSocket support - location / { - proxy_pass http://127.0.0.1:9001; - - # WebSocket headers - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $connection_upgrade; - proxy_set_header Host $host; - proxy_cache_bypass $http_upgrade; - - # Extended timeouts for WebSocket connections - proxy_read_timeout 86400s; - proxy_send_timeout 86400s; - proxy_connect_timeout 60s; - } - - # Health check endpoint - location /health { - access_log off; - return 200 "healthy\n"; - add_header Content-Type text/plain; - } -} -``` - -Enable the configuration: -```bash -sudo ln -s /etc/nginx/sites-available/hornets-relay /etc/nginx/sites-enabled/ -sudo nginx -t # Test configuration -``` +#### Step 2: Deploy to Relay Server +Copy the built files to your relay server's web directory and start the services: -#### Step 3: Serve Built Files -Copy the built files to your web server: ```bash -# Example for nginx -sudo cp -r build/* /var/www/html/front/ -``` +# Copy build files to relay server web directory +cp -r build/* /path/to/relay/web/ -#### Step 4: Start Services -Ensure all backend services are running: -```bash -# Start in order of dependency -./relay-service & # Port 9001 -./panel-api & # Port 9002 -./wallet-service & # Port 9003 -./media-moderation & # Port 8000 (optional) +# Start services (adjust ports as needed) +./relay-websocket-service & # Port 9001 +./relay-server-with-panel & # Port 9002 (serves both API and panel) +./wallet-service & # Port 9003 ``` -#### Step 5: Start Nginx -```bash -sudo systemctl start nginx -sudo systemctl enable nginx -``` +#### Step 3: Access the Panel +- **Panel**: `http://localhost:9002/` (or your configured domain) +- **Wallet Service**: `http://localhost:9003/` (direct access) +- **Relay WebSocket**: `ws://localhost:9001/` (WebSocket connection) -### Scenario 2: Direct Access (Development/Testing) +**β This setup works without any reverse proxy configuration!** -#### Step 1: Build with Root Path -Update `.env.production`: -```env -REACT_APP_BASENAME= -PUBLIC_URL= -# Note: API URLs are now auto-detected, no need to specify them! -``` +> **Note**: Reverse proxy setup with nginx is possible but currently requires additional configuration. The direct access method above is the recommended approach for most users. -#### Step 2: Build and Serve -```bash -yarn build -# Serve the build folder (default port 3000) -serve -s build -``` - -#### Step 3: Configure CORS -Ensure your backend services accept requests from the frontend origin. - -## π Tunneling & Remote Access - -### Using ngrok -```bash -# Install ngrok -npm install -g ngrok - -# With reverse proxy setup (random domain) -ngrok http 80 - -# With reverse proxy setup (custom domain - requires ngrok pro) -ngrok http --url=your-domain.ngrok.io 80 - -# Direct access to React app (without reverse proxy) -ngrok http 3000 - -# Example output: -# Forwarding https://abc123.ngrok.io -> http://localhost:80 -``` - -### Environment Variables for Tunneling -**Great news!** Thanks to dynamic URL detection, **no environment variable changes are needed** when using tunnels. The panel automatically adapts to any domain: - -- β `ngrok http 80` β Panel works immediately at `https://abc123.ngrok.io/front/` -- β Custom domain tunnel β Panel works immediately -- β Any hosting provider β Panel works immediately - -**No rebuilds or environment changes required!** ## π§ Configuration Options > **π Major Improvement**: The panel now uses **dynamic URL detection** instead of hardcoded environment variables. This means **one build works everywhere** - no more environment-specific builds or complex URL configuration! ### REACT_APP_BASENAME -Controls where the React app is served from: -- `/front` - App accessible at `https://domain.com/front/` -- `/admin` - App accessible at `https://domain.com/admin/` -- `` (empty) - App accessible at `https://domain.com/` +Controls the React app's routing base path: +- `` (empty) - App accessible at `https://domain.com/` (recommended for direct access) +- `/panel` - App accessible at `https://domain.com/panel/` (for reverse proxy setups) + +**Note**: For the current working setup, leave this empty (`REACT_APP_BASENAME=`) since the panel is served from the root path. ### Service URLs -**π― Auto-Detection**: Service URLs are now automatically detected in production: -- **Panel API**: `${window.location.origin}/panel` (auto-detected) -- **Wallet Service**: `${window.location.origin}/wallet` (auto-detected) -- **Relay WebSocket**: `wss://${window.location.host}` (auto-detected) +**π― Configuration Requirements**: +- **Wallet Service**: `REACT_APP_WALLET_BASE_URL=http://localhost:9003` (optional - leave empty to disable wallet features) +- **Relay WebSocket**: `REACT_APP_OWN_RELAY_URL=ws://localhost:9001` (required for profile fetching) +- **Panel API**: Auto-detected from current origin (no configuration needed) + +**Note**: When wallet URL is not configured, send/receive buttons will show a helpful message about rebuilding with wallet configuration. **Manual Override** (development only): - **REACT_APP_BASE_URL**: Panel API endpoint (dev mode only) diff --git a/fixed_nginx_config.conf b/fixed_nginx_config.conf new file mode 100644 index 00000000..b55f89f8 --- /dev/null +++ b/fixed_nginx_config.conf @@ -0,0 +1,89 @@ +# Define upstream servers for each service (using explicit IPv4 addresses) +upstream transcribe_api { + server 127.0.0.1:8000; +} + +upstream relay_service { + server 127.0.0.1:9001; +} + +upstream panel_service { + server 127.0.0.1:9002; +} + +upstream wallet_service { + server 127.0.0.1:9003; +} + +# WebSocket connection upgrade mapping +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +# Main server block listening on HTTP +server { + listen 80; # Nginx listens on port 80 locally + server_name _; # Accept all hostnames (localhost, ngrok, custom domains, etc.) + + # Basic Security Headers + add_header X-Frame-Options "SAMEORIGIN"; + add_header X-Content-Type-Options "nosniff"; + add_header X-XSS-Protection "1; mode=block"; + server_tokens off; + + # Forward client IP and protocol + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $host; + + location /transcribe/ { + rewrite ^/transcribe/(.*)$ /$1 break; + proxy_pass http://transcribe_api; + } + + # Panel access - Admin dashboard (back to working rewrite pattern) + location /panel/ { + rewrite ^/panel/(.*)$ /$1 break; + proxy_pass http://panel_service; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /wallet/ { + rewrite ^/wallet/(.*)$ /$1 break; + proxy_pass http://wallet_service; + } + + # Health check endpoint + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + + # Default location - Relay service with WebSocket support + location / { + proxy_pass http://relay_service; + + # WebSocket-specific headers + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + + # Extended timeouts for WebSocket connections + proxy_read_timeout 86400s; + proxy_send_timeout 86400s; + proxy_connect_timeout 60s; + + # Additional headers for tunnel compatibility + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Real-IP $remote_addr; + } +} \ No newline at end of file diff --git a/package.json b/package.json index a887e5d5..f292698b 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "react-slick": "^0.28.1", "react-verification-input": "^2.0.3", "round-slider": "altence/round-slider#refactor/slider", + "serve": "^14.2.4", "slick-carousel": "^1.8.1", "styled-components": "^5.3.0", "typeface-lato": "^1.1.13", diff --git a/src/components/auth/LoginForm/LoginForm.tsx b/src/components/auth/LoginForm/LoginForm.tsx index 817b7f5f..866e1b9b 100644 --- a/src/components/auth/LoginForm/LoginForm.tsx +++ b/src/components/auth/LoginForm/LoginForm.tsx @@ -7,6 +7,7 @@ import { useLogin } from '@app/hooks/useLogin'; import { setUser } from '@app/store/slices/userSlice'; import { persistToken } from '@app/services/localStorage.service'; import { notificationController } from '@app/controllers/notificationController'; +import { NostrExtensionCheck } from '@app/components/auth/NostrExtensionCheck'; import * as S from './LoginForm.styles'; import * as Auth from '@app/components/layouts/AuthLayout/AuthLayout.styles'; import { NostrProvider } from '@app/types/nostr'; @@ -28,6 +29,8 @@ export const LoginForm: React.FC = () => { const dispatch = useDispatch(); const [form] = Form.useForm(); + const [hasNostrExtension, setHasNostrExtension] = useState(!!window.nostr); // Initialize with current state + const [isCheckingExtension, setIsCheckingExtension] = useState(!window.nostr); // Only check if not already available useEffect(() => { const fetchPublicKey = async () => { @@ -35,22 +38,38 @@ export const LoginForm: React.FC = () => { if (window.nostr) { const pubkey = await window.nostr.getPublicKey(); form.setFieldsValue({ npub: pubkey }); - } else { - console.warn('Nostr extension is not available'); + setHasNostrExtension(true); + setIsCheckingExtension(false); } } catch (error) { console.error('Failed to get public key:', error); } }; + // If extension is already available, fetch immediately + if (window.nostr) { + fetchPublicKey(); + return; + } + + // Only start polling if no extension is detected initially const intervalId = setInterval(() => { if (window.nostr) { fetchPublicKey(); clearInterval(intervalId); } - }, 1000); // Retry every 1 second + }, 1000); + + // Stop checking after 5 seconds to avoid infinite polling + const timeoutId = setTimeout(() => { + setIsCheckingExtension(false); + clearInterval(intervalId); + }, 5000); - return () => clearInterval(intervalId); // Clear the interval on component unmount + return () => { + clearInterval(intervalId); + clearTimeout(timeoutId); + }; }, [form]); @@ -96,6 +115,19 @@ export const LoginForm: React.FC = () => { }; + // Show extension check only if we've finished checking and no extension was found + if (!isCheckingExtension && !hasNostrExtension) { + return ( + + { + setHasNostrExtension(true); + setIsCheckingExtension(false); + }} /> + + ); + } + + // Show login form (either extension is available or we're still checking in background) return ( diff --git a/src/components/auth/NostrExtensionCheck/NostrExtensionCheck.tsx b/src/components/auth/NostrExtensionCheck/NostrExtensionCheck.tsx new file mode 100644 index 00000000..417e9da4 --- /dev/null +++ b/src/components/auth/NostrExtensionCheck/NostrExtensionCheck.tsx @@ -0,0 +1,235 @@ +import React, { useState, useEffect } from 'react'; +import { Alert, Card, Button, Space, Typography } from 'antd'; +import { ExclamationCircleOutlined, CheckCircleOutlined, ReloadOutlined } from '@ant-design/icons'; +import styled from 'styled-components'; + +const { Title, Text, Link } = Typography; + +const StyledCard = styled(Card)` + max-width: 600px; + margin: 0 auto; + text-align: center; + + .ant-card-body { + padding: 2rem; + + @media (max-width: 480px) { + padding: 1.5rem; + } + } + + @media (max-width: 480px) { + margin: 0 1rem; + } +`; + +const ExtensionList = styled.div` + margin: 1.5rem 0; + text-align: left; + + .extension-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 1rem; + margin: 0.75rem 0; + border: 1px solid var(--border-color); + border-radius: 8px; + background: var(--background-color); + + @media (max-width: 480px) { + flex-direction: column; + align-items: flex-start; + gap: 0.75rem; + } + + .extension-info { + display: flex; + flex-direction: column; + gap: 0.25rem; + flex: 1; + } + + .extension-name { + font-weight: 600; + font-size: 1rem; + color: var(--text-main-color); + } + + .extension-desc { + font-size: 0.875rem; + color: var(--text-light-color); + } + + .extension-links { + display: flex; + gap: 0.75rem; + + @media (max-width: 480px) { + width: 100%; + justify-content: flex-start; + } + } + } +`; + +const StyledAlert = styled(Alert)` + margin-bottom: 1.5rem; + + &.ant-alert-info { + background-color: rgba(24, 144, 255, 0.1); + border: 1px solid rgba(24, 144, 255, 0.3); + + .ant-alert-message { + color: var(--text-main-color); + font-weight: 600; + } + + .ant-alert-description { + color: var(--text-light-color); + } + } +`; + +const IconWrapper = styled.div` + font-size: 3rem; + margin-bottom: 1rem; + color: var(--warning-color); + + &.success { + color: var(--success-color); + } +`; + +interface NostrExtensionCheckProps { + onExtensionReady?: () => void; +} + +export const NostrExtensionCheck: React.FC = ({ onExtensionReady }) => { + const [hasExtension, setHasExtension] = useState(false); + const [isChecking, setIsChecking] = useState(true); + + const checkExtension = () => { + setIsChecking(true); + + // Check if window.nostr exists + const extensionExists = typeof window !== 'undefined' && !!window.nostr; + setHasExtension(extensionExists); + setIsChecking(false); + + if (extensionExists && onExtensionReady) { + onExtensionReady(); + } + }; + + useEffect(() => { + // Initial check + checkExtension(); + + // Set up polling to detect when extension becomes available + const pollInterval = setInterval(() => { + if (window.nostr && !hasExtension) { + checkExtension(); + clearInterval(pollInterval); + } + }, 1000); + + return () => clearInterval(pollInterval); + }, [hasExtension, onExtensionReady]); + + const extensions = [ + { + name: 'nos2x', + description: 'Chrome/Firefox extension', + chromeUrl: 'https://chrome.google.com/webstore/detail/nos2x/kpgefcfmnafjgpblomihpgmejjdanjjp', + firefoxUrl: 'https://addons.mozilla.org/en-US/firefox/addon/nos2x/' + }, + { + name: 'Alby', + description: 'Bitcoin & Nostr wallet extension', + chromeUrl: 'https://chrome.google.com/webstore/detail/alby-bitcoin-lightning-wa/iokeahhehimjnekafflcihljlcjccdbe', + firefoxUrl: 'https://addons.mozilla.org/en-US/firefox/addon/alby/' + }, + { + name: 'Flamingo', + description: 'Chrome extension for Nostr', + chromeUrl: 'https://chrome.google.com/webstore/detail/flamingo/hjiadbgecmepjlengpkocjccpjeimbip' + } + ]; + + if (isChecking) { + return ( + + + + Checking for Nostr Extension... + + + ); + } + + if (hasExtension) { + return ( + } + style={{ marginBottom: '1rem' }} + /> + ); + } + + return ( + + + + + + Nostr Extension Required + + + This relay panel requires a NIP-07 compatible Nostr extension to authenticate. + Please install one of the recommended extensions below: + + + + {extensions.map((ext, index) => ( + + + {ext.name} + {ext.description} + + + {ext.chromeUrl && ( + + Chrome + + )} + {ext.firefoxUrl && ( + + Firefox + + )} + + + ))} + + + + + } + onClick={checkExtension} + size="large" + > + Check Again + + + ); +}; \ No newline at end of file diff --git a/src/components/auth/NostrExtensionCheck/index.ts b/src/components/auth/NostrExtensionCheck/index.ts new file mode 100644 index 00000000..8dd9a8b9 --- /dev/null +++ b/src/components/auth/NostrExtensionCheck/index.ts @@ -0,0 +1 @@ +export { NostrExtensionCheck } from './NostrExtensionCheck'; \ No newline at end of file diff --git a/src/components/common/IconUpload.tsx b/src/components/common/IconUpload.tsx index 6ff3e7de..c97c8021 100644 --- a/src/components/common/IconUpload.tsx +++ b/src/components/common/IconUpload.tsx @@ -1,7 +1,9 @@ import React, { useState, useRef, useEffect } from 'react'; import { Input, Upload, Button, message, Tabs, Avatar } from 'antd'; import { UploadOutlined, LinkOutlined, LoadingOutlined } from '@ant-design/icons'; -import { uploadToBlossom, isValidUrl, isImageUrl } from '@app/utils/blossomUpload'; +import { isValidUrl, isImageUrl } from '@app/utils/blossomUpload'; +import { readToken } from '@app/services/localStorage.service'; +import config from '@app/config/config'; import type { RcFile } from 'antd/es/upload/interface'; interface IconUploadProps { @@ -38,7 +40,7 @@ const IconUpload: React.FC = ({ } }; - // Handle file upload + // Handle file upload using new simplified API const handleFileUpload = async (file: RcFile): Promise => { try { setUploading(true); @@ -55,10 +57,35 @@ const IconUpload: React.FC = ({ return false; } - // Upload to Blossom server - const result = await uploadToBlossom(file); + // Get JWT token for authentication + const token = readToken(); + if (!token) { + message.error('Authentication required. Please log in.'); + return false; + } + + // Create form data for the API + const formData = new FormData(); + formData.append('image', file); + formData.append('panel_url', window.location.origin); // Auto-detect current panel URL + + // Upload using panel API + const response = await fetch(`${config.baseURL}/api/relay/icon`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + }, + body: formData + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({ error: 'Upload failed' })); + throw new Error(errorData.error || `HTTP error! status: ${response.status}`); + } + + const result = await response.json(); - // Update URL input and parent component + // Update URL input and parent component with the returned URL setUrlInput(result.url); onChange?.(result.url); diff --git a/src/components/relay-dashboard/Balance/components/SendButton/SendButton.tsx b/src/components/relay-dashboard/Balance/components/SendButton/SendButton.tsx index fd9e72b3..09969062 100644 --- a/src/components/relay-dashboard/Balance/components/SendButton/SendButton.tsx +++ b/src/components/relay-dashboard/Balance/components/SendButton/SendButton.tsx @@ -1,12 +1,18 @@ import React, { useState } from 'react'; +import { message } from 'antd'; import * as S from '../TopUpBalanceButton/TopUpBalanceButton.styles'; import SendModal from '../SendModal/SendModal'; import { useAppSelector } from '@app/hooks/reduxHooks'; +import config from '@app/config/config'; const SendButton: React.FC = () => { const { theme } = useAppSelector((state) => state.theme); const [isModalOpen, setIsModalOpen] = useState(false); const handleButtonClick = () => { + if (!config.isWalletEnabled) { + message.warning('Wallet functionality is not available. Please rebuild the panel with REACT_APP_WALLET_BASE_URL configured in your environment file.'); + return; + } setIsModalOpen(true); }; const handleModalClose = () => { diff --git a/src/components/relay-dashboard/Balance/components/TopUpBalanceButton/TopUpBalanceButton.tsx b/src/components/relay-dashboard/Balance/components/TopUpBalanceButton/TopUpBalanceButton.tsx index 67898e18..129ab7b8 100644 --- a/src/components/relay-dashboard/Balance/components/TopUpBalanceButton/TopUpBalanceButton.tsx +++ b/src/components/relay-dashboard/Balance/components/TopUpBalanceButton/TopUpBalanceButton.tsx @@ -1,7 +1,9 @@ import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { message } from 'antd'; import { useAppSelector } from '@app/hooks/reduxHooks'; import { TopUpBalanceModal } from '../TopUpBalanceModal/TopUpBalanceModal'; +import config from '@app/config/config'; import * as S from './TopUpBalanceButton.styles'; export const TopUpBalanceButton: React.FC = () => { @@ -10,6 +12,10 @@ export const TopUpBalanceButton: React.FC = () => { const [isModalOpen, setIsModalOpen] = useState(false); const handleButtonClick = () => { + if (!config.isWalletEnabled) { + message.warning('Wallet functionality is not available. Please rebuild the panel with REACT_APP_WALLET_BASE_URL configured in your environment file.'); + return; + } setIsModalOpen(true); }; diff --git a/src/config/config.ts b/src/config/config.ts index 2a9553c4..0b00dcb0 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -1,41 +1,32 @@ // config.ts -// Dynamic URL detection - panel works from anywhere! +// Dynamic URL detection - panel and API run from same origin const getBaseURL = (): string => { // Demo mode override for testing if (process.env.REACT_APP_DEMO_MODE === 'true') { return 'http://localhost:10002'; } - // Development mode - use localhost - if (process.env.NODE_ENV === 'development') { - return process.env.REACT_APP_BASE_URL || 'http://localhost:9002'; - } - - // Production - use current origin + /panel path - // This makes the panel work from ANY domain without rebuilding - return `${window.location.origin}/panel`; + // For both development and production, panel and API are served from same origin + // API routes are at /api/* while panel is served from root + return process.env.REACT_APP_BASE_URL || window.location.origin; }; -const getWalletURL = (): string => { +const getWalletURL = (): string | null => { // Demo mode override for testing if (process.env.REACT_APP_DEMO_MODE === 'true') { return 'http://localhost:9003'; } - // Development mode - use localhost - if (process.env.NODE_ENV === 'development') { - return process.env.REACT_APP_WALLET_BASE_URL?.trim() || 'http://localhost:9003'; - } - - // Production - use current origin + /wallet path - return `${window.location.origin}/wallet`; + // Wallet URL is optional - return null if not configured + return process.env.REACT_APP_WALLET_BASE_URL || null; }; const config = { baseURL: getBaseURL(), isDemoMode: process.env.REACT_APP_DEMO_MODE === 'true', walletBaseURL: getWalletURL(), + isWalletEnabled: getWalletURL() !== null, // Nostr relay configuration nostrRelayUrls: process.env.REACT_APP_NOSTR_RELAY_URLS?.split(',').map(url => url.trim()) || [ @@ -46,11 +37,13 @@ const config = { ], // User's own relay URL (primary relay for profile fetching) - // In production, use the current domain as the relay WebSocket URL - ownRelayUrl: process.env.REACT_APP_OWN_RELAY_URL?.trim() || - (process.env.NODE_ENV === 'production' - ? `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}` - : null), + // Always require explicit relay URL configuration + ownRelayUrl: (() => { + if (!process.env.REACT_APP_OWN_RELAY_URL?.trim()) { + throw new Error('REACT_APP_OWN_RELAY_URL must be explicitly configured in environment variables'); + } + return process.env.REACT_APP_OWN_RELAY_URL.trim(); + })(), // Notification settings notifications: { diff --git a/src/constants/kindMapping.ts b/src/constants/kindMapping.ts index 775ba543..dc904499 100644 --- a/src/constants/kindMapping.ts +++ b/src/constants/kindMapping.ts @@ -25,6 +25,7 @@ const kindMapping: { [key: number]: { description: string; nip: string } } = { 1022: { description: 'Bid confirmation', nip: '15' }, 1040: { description: 'OpenTimestamps', nip: '03' }, 1059: { description: 'Gift Wrap', nip: '59' }, + 1060: { description: 'Double Ratchet DM', nip: '117' }, 1063: { description: 'File Metadata', nip: '94' }, 1311: { description: 'Live Chat Message', nip: '53' }, 1617: { description: 'Patches', nip: '34' }, diff --git a/src/constants/relaySettings.ts b/src/constants/relaySettings.ts index 97314ba8..4ad824cb 100644 --- a/src/constants/relaySettings.ts +++ b/src/constants/relaySettings.ts @@ -34,11 +34,14 @@ export const noteOptions = [ { kind: 10000, kindString: 'kind10000', description: 'Mute List', category: 1 }, { kind: 10001, kindString: 'kind10001', description: 'Pinned Note(s)', category: 1 }, { kind: 10002, kindString: 'kind10002', description: 'Tiny Relay List', category: 1 }, + { kind: 1060, kindString: 'kind1060', description: 'Double Ratchet DM', category: 1 }, + { kind: 1063, kindString: 'kind1063', description: 'File Metadata', category: 1 }, { kind: 1984, kindString: 'kind1984', description: 'Reporting', category: 1 }, { kind: 30000, kindString: 'kind30000', description: 'Custom Follow List', category: 1 }, { kind: 30008, kindString: 'kind30008', description: 'Profile Badge', category: 2 }, { kind: 30009, kindString: 'kind30009', description: 'Badge Definition', category: 2 }, { kind: 30023, kindString: 'kind30023', description: 'Formatted Articles', category: 1 }, + { kind: 30078, kindString: 'kind30078', description: 'Application-specific Data', category: 1 }, { kind: 30079, kindString: 'kind30079', description: 'Event Paths', category: 1 }, //{ kind: 9734, kindString: 'kind9734', description: 'Lightning Zap Request', category: 2 }, { kind: 9735, kindString: 'kind9735', description: 'Zap Receipt', category: 2 }, diff --git a/src/index.tsx b/src/index.tsx index 27624de2..3e2cf8e0 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -27,18 +27,24 @@ root.render( // ); -serviceWorkerRegistration.register({ - onUpdate: (registration) => { - const waitingServiceWorker = registration.waiting; +// Disable service worker for localhost testing to avoid caching issues +if (window.location.hostname !== 'localhost') { + serviceWorkerRegistration.register({ + onUpdate: (registration) => { + const waitingServiceWorker = registration.waiting; - if (waitingServiceWorker) { - waitingServiceWorker.addEventListener('statechange', (event) => { - if ((event.target as EventTarget).state === 'activated') window.location.reload(); - }); - waitingServiceWorker.postMessage({ type: 'SKIP_WAITING' }); - } - }, -}); + if (waitingServiceWorker) { + waitingServiceWorker.addEventListener('statechange', (event) => { + if ((event.target as EventTarget).state === 'activated') window.location.reload(); + }); + waitingServiceWorker.postMessage({ type: 'SKIP_WAITING' }); + } + }, + }); +} else { + // Unregister service worker on localhost to prevent caching conflicts + serviceWorkerRegistration.unregister(); +} reportWebVitals(); diff --git a/yarn.lock b/yarn.lock index a1434d19..f5a16418 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2895,12 +2895,17 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== +"@zeit/schemas@2.36.0": + version "2.36.0" + resolved "https://registry.yarnpkg.com/@zeit/schemas/-/schemas-2.36.0.tgz#7a1b53f4091e18d0b404873ea3e3c83589c765f2" + integrity sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg== + abab@^2.0.3, abab@^2.0.5: version "2.0.6" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== -accepts@~1.3.4, accepts@~1.3.8: +accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: version "1.3.8" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== @@ -2991,6 +2996,16 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== +ajv@8.12.0: + version "8.12.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" + integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.4, ajv@^6.12.5: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -3016,6 +3031,13 @@ alphanum-sort@^1.0.0: resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" integrity sha512-0FcBfdcmaumGPQ0qPn7Q5qTgz/ooXgIyp1rf8ik5bGX8mpE2YHjC0P/eyQvxu1GURYQgq9ozf2mteQ5ZD9YiyQ== +ansi-align@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59" + integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w== + dependencies: + string-width "^4.1.0" + ansi-colors@^3.0.0: version "3.2.4" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" @@ -3053,6 +3075,11 @@ ansi-regex@^5.0.0, ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== +ansi-regex@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654" + integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== + ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -3072,6 +3099,11 @@ ansi-styles@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + antd-mask-input@^2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/antd-mask-input/-/antd-mask-input-2.0.7.tgz#202d706eb83571646835bf52e9bfe1d1e4ea5eaf" @@ -3149,6 +3181,16 @@ aproba@^1.1.1: resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== +arch@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11" + integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ== + +arg@5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" + integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== + arg@^4.1.0: version "4.1.3" resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" @@ -3807,6 +3849,20 @@ boolbase@^1.0.0, boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== +boxen@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-7.0.0.tgz#9e5f8c26e716793fc96edcf7cf754cdf5e3fbf32" + integrity sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg== + dependencies: + ansi-align "^3.0.1" + camelcase "^7.0.0" + chalk "^5.0.1" + cli-boxes "^3.0.0" + string-width "^5.1.2" + type-fest "^2.13.0" + widest-line "^4.0.1" + wrap-ansi "^8.0.1" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -3980,6 +4036,11 @@ builtin-status-codes@^3.0.0: resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" integrity sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ== +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== + bytes@3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" @@ -4130,6 +4191,11 @@ camelcase@^6.0.0, camelcase@^6.1.0, camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== +camelcase@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-7.0.1.tgz#f02e50af9fd7782bc8b88a3558c32fd3a388f048" + integrity sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw== + camelize@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.1.tgz#89b7e16884056331a35d6b5ad064332c91daa6c3" @@ -4162,6 +4228,13 @@ case-sensitive-paths-webpack-plugin@2.3.0: resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.3.0.tgz#23ac613cc9a856e4f88ff8bb73bbb5e989825cf7" integrity sha512-/4YgnZS8y1UXXmC02xD5rRrBEu6T5ub+mQHLNRj0fzTRbgdBYhsNo2V5EqwgqrExjxsjtF/OpAKAMkKsxbD5XQ== +chalk-template@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/chalk-template/-/chalk-template-0.4.0.tgz#692c034d0ed62436b9062c1707fadcd0f753204b" + integrity sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg== + dependencies: + chalk "^4.1.2" + chalk@2.4.2, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -4171,7 +4244,12 @@ chalk@2.4.2, chalk@^2.4.1, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1: +chalk@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.0.1.tgz#ca57d71e82bb534a296df63bbacc4a1c22b2a4b6" + integrity sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w== + +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -4179,6 +4257,11 @@ chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@^5.0.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.4.1.tgz#1b48bf0963ec158dce2aacf69c093ae2dd2092d8" + integrity sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w== + char-regex@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" @@ -4312,6 +4395,11 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== +cli-boxes@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-3.0.0.tgz#71a10c716feeba005e4504f36329ef0b17cf3145" + integrity sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g== + cli-cursor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" @@ -4327,6 +4415,15 @@ cli-truncate@2.1.0, cli-truncate@^2.1.0: slice-ansi "^3.0.0" string-width "^4.2.0" +clipboardy@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/clipboardy/-/clipboardy-3.0.0.tgz#f3876247404d334c9ed01b6f269c11d09a5e3092" + integrity sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg== + dependencies: + arch "^2.2.0" + execa "^5.1.1" + is-wsl "^2.2.0" + cliui@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" @@ -4478,13 +4575,26 @@ compose-function@3.0.3: dependencies: arity-n "^1.0.4" -compressible@~2.0.18: +compressible@~2.0.16, compressible@~2.0.18: version "2.0.18" resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== dependencies: mime-db ">= 1.43.0 < 2" +compression@1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" + integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== + dependencies: + accepts "~1.3.5" + bytes "3.0.0" + compressible "~2.0.16" + debug "2.6.9" + on-headers "~1.0.2" + safe-buffer "5.1.2" + vary "~1.1.2" + compression@^1.7.4: version "1.7.5" resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.5.tgz#fdd256c0a642e39e314c478f6c2cd654edd74c93" @@ -4543,6 +4653,11 @@ constants-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" integrity sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ== +content-disposition@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" + integrity sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA== + content-disposition@0.5.4: version "0.5.4" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" @@ -5267,6 +5382,11 @@ deep-equal@^2.0.5: which-collection "^1.0.1" which-typed-array "^1.1.13" +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" @@ -5589,6 +5709,11 @@ duplexify@^3.4.2, duplexify@^3.6.0: readable-stream "^2.0.0" stream-shift "^1.0.0" +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + echarts-for-react@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/echarts-for-react/-/echarts-for-react-3.0.2.tgz#ac5859157048a1066d4553e34b328abb24f2b7c1" @@ -7566,7 +7691,7 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== -ini@^1.3.5: +ini@^1.3.5, ini@~1.3.0: version "1.3.8" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== @@ -7971,6 +8096,11 @@ is-plain-object@^2.0.3, is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" +is-port-reachable@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-port-reachable/-/is-port-reachable-4.0.0.tgz#dac044091ef15319c8ab2f34604d8794181f8c2d" + integrity sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig== + is-potential-custom-element-name@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" @@ -9382,6 +9512,18 @@ mime-db@1.52.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.53.0.tgz#3cb63cd820fc29896d9d4e8c32ab4fcd74ccb447" integrity sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg== +mime-db@~1.33.0: + version "1.33.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" + integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ== + +mime-types@2.1.18: + version "2.1.18" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" + integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ== + dependencies: + mime-db "~1.33.0" + mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" @@ -9436,7 +9578,7 @@ minimatch@3.0.4: dependencies: brace-expansion "^1.1.7" -minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: +minimatch@3.1.2, minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -10219,7 +10361,7 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== -path-is-inside@^1.0.2: +path-is-inside@1.0.2, path-is-inside@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" integrity sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w== @@ -10244,6 +10386,11 @@ path-to-regexp@0.1.12: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7" integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ== +path-to-regexp@3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-3.3.0.tgz#f7f31d32e8518c2660862b644414b6d5c63a611b" + integrity sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw== + path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" @@ -11353,6 +11500,11 @@ randomfill@^1.0.4: randombytes "^2.0.5" safe-buffer "^5.1.0" +range-parser@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" + integrity sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A== + range-parser@^1.2.1, range-parser@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" @@ -11740,6 +11892,16 @@ rc-virtual-list@^3.2.0, rc-virtual-list@^3.5.1: rc-resize-observer "^1.0.0" rc-util "^5.36.0" +rc@^1.0.1, rc@^1.1.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + react-app-polyfill@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/react-app-polyfill/-/react-app-polyfill-2.0.0.tgz#a0bea50f078b8a082970a9d853dc34b6dcc6a3cf" @@ -12202,6 +12364,21 @@ regexpu-core@^6.2.0: unicode-match-property-ecmascript "^2.0.0" unicode-match-property-value-ecmascript "^2.1.0" +registry-auth-token@3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.3.2.tgz#851fd49038eecb586911115af845260eec983f20" + integrity sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ== + dependencies: + rc "^1.1.6" + safe-buffer "^5.0.1" + +registry-url@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942" + integrity sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA== + dependencies: + rc "^1.0.1" + regjsgen@^0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.8.0.tgz#df23ff26e0c5b300a6470cad160a9d090c3a37ab" @@ -12525,16 +12702,16 @@ safe-array-concat@^1.1.2: has-symbols "^1.0.3" isarray "^2.0.5" +safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - safe-regex-test@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377" @@ -12715,6 +12892,19 @@ serialize-javascript@^5.0.1: dependencies: randombytes "^2.1.0" +serve-handler@6.1.6: + version "6.1.6" + resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.6.tgz#50803c1d3e947cd4a341d617f8209b22bd76cfa1" + integrity sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ== + dependencies: + bytes "3.0.0" + content-disposition "0.5.2" + mime-types "2.1.18" + minimatch "3.1.2" + path-is-inside "1.0.2" + path-to-regexp "3.3.0" + range-parser "1.2.0" + serve-index@^1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" @@ -12738,6 +12928,23 @@ serve-static@1.16.2: parseurl "~1.3.3" send "0.19.0" +serve@^14.2.4: + version "14.2.4" + resolved "https://registry.yarnpkg.com/serve/-/serve-14.2.4.tgz#ba4c425c3c965f496703762e808f34b913f42fb0" + integrity sha512-qy1S34PJ/fcY8gjVGszDB3EXiPSk5FKhUa7tQe0UPRddxRidc2V6cNHPNewbE1D7MAkgLuWEt3Vw56vYy73tzQ== + dependencies: + "@zeit/schemas" "2.36.0" + ajv "8.12.0" + arg "5.0.2" + boxen "7.0.0" + chalk "5.0.1" + chalk-template "0.4.0" + clipboardy "3.0.0" + compression "1.7.4" + is-port-reachable "4.0.0" + serve-handler "6.1.6" + update-check "1.5.4" + set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" @@ -13275,6 +13482,15 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2 is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + string.prototype.includes@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz#eceef21283640761a81dbe16d6c7171a4edf7d92" @@ -13389,6 +13605,13 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" +strip-ansi@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" @@ -13429,6 +13652,11 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + style-loader@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.3.0.tgz#828b4a3b3b7e7aa5847ce7bae9e874512114249e" @@ -13999,6 +14227,11 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== +type-fest@^2.13.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" + integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== + type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -14260,6 +14493,14 @@ update-browserslist-db@^1.1.1: escalade "^3.2.0" picocolors "^1.1.0" +update-check@1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/update-check/-/update-check-1.5.4.tgz#5b508e259558f1ad7dbc8b4b0457d4c9d28c8743" + integrity sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ== + dependencies: + registry-auth-token "3.3.2" + registry-url "3.1.0" + uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -14755,6 +14996,13 @@ which@^2.0.1, which@^2.0.2: dependencies: isexe "^2.0.0" +widest-line@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-4.0.1.tgz#a0fc673aaba1ea6f0a0d35b3c2795c9a9cc2ebf2" + integrity sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig== + dependencies: + string-width "^5.0.1" + word-wrap@^1.2.5, word-wrap@~1.2.3: version "1.2.5" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" @@ -14954,6 +15202,15 @@ wrap-ansi@^7.0.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^8.0.1: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"