diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..f799096 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,46 @@ +{ + "name": "MQTT Explorer Development", + "dockerComposeFile": "docker-compose.yml", + "service": "app", + "workspaceFolder": "/workspace", + + "customizations": { + "vscode": { + "extensions": [ + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "ms-vscode.vscode-typescript-next", + "ms-azuretools.vscode-docker", + "eamodio.gitlens" + ], + "settings": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "typescript.tsdk": "node_modules/typescript/lib", + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + } + } + } + }, + + "forwardPorts": [3000, 8080, 1883], + "portsAttributes": { + "3000": { + "label": "MQTT Explorer Server", + "onAutoForward": "notify" + }, + "8080": { + "label": "Webpack Dev Server", + "onAutoForward": "notify" + }, + "1883": { + "label": "MQTT Broker", + "onAutoForward": "ignore" + } + }, + + "postCreateCommand": "yarn install", + + "remoteUser": "node" +} diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 0000000..0de4449 --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,21 @@ +version: '3.8' + +services: + app: + image: mcr.microsoft.com/devcontainers/javascript-node:20 + volumes: + - ../..:/workspace:cached + command: sleep infinity + network_mode: service:mosquitto + environment: + - MQTT_EXPLORER_USERNAME=dev + - MQTT_EXPLORER_PASSWORD=dev123 + + mosquitto: + image: eclipse-mosquitto:2 + ports: + - "1883:1883" + - "3000:3000" + - "8080:8080" + volumes: + - ./mosquitto.conf:/mosquitto/config/mosquitto.conf:ro diff --git a/.devcontainer/mosquitto.conf b/.devcontainer/mosquitto.conf new file mode 100644 index 0000000..e89e8fc --- /dev/null +++ b/.devcontainer/mosquitto.conf @@ -0,0 +1,4 @@ +# Mosquitto configuration for development +listener 1883 +allow_anonymous true +persistence false diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index ee88bab..79f6c0c 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -25,6 +25,10 @@ yarn install # Build the project yarn build +# Set password for browser testing +export MQTT_EXPLORER_USERNAME=admin +export MQTT_EXPLORER_PASSWORD=secretpassword + # Start the application yarn start @@ -322,5 +326,5 @@ yarn package-with-docker - The app uses Electron (see `package.json` for version) - MQTT communication is handled via [mqttjs](https://github.com/mqttjs/MQTT.js) - All code changes should pass linting (`yarn lint`) -- Node.js version requirement: >= 18 +- Node.js version requirement: >= 20 - The project uses workspace-like structure with separate package.json files for app and backend diff --git a/.github/workflows/copilot-setup.yml b/.github/workflows/copilot-setup.yml index e56a02d..007f557 100644 --- a/.github/workflows/copilot-setup.yml +++ b/.github/workflows/copilot-setup.yml @@ -14,7 +14,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: '18' + node-version: '20' - name: Get yarn cache directory path id: yarn-cache-dir-path diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4709545..45bdc21 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -74,3 +74,53 @@ jobs: run: echo '${{ steps.upload.outputs.file-url }}' id: artifact-upload-step - run: echo '' >> $GITHUB_STEP_SUMMARY + + test-browser: + runs-on: ubuntu-latest + services: + mosquitto: + image: eclipse-mosquitto:2 + ports: + - 1883:1883 + options: >- + --health-cmd "mosquitto_sub -t '$SYS/#' -C 1" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + steps: + - uses: actions/checkout@v4 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + - name: Install Dependencies + run: yarn install --frozen-lockfile + - name: Build Browser Mode + run: yarn build:server + - name: Test App + run: yarn test:app + - name: Test Backend + run: yarn test:backend + - name: Start Server in Background + run: | + yarn start:server & + echo $! > server.pid + env: + MQTT_EXPLORER_USERNAME: test + MQTT_EXPLORER_PASSWORD: test123 + PORT: 3000 + - name: Wait for Server + run: | + timeout 30 bash -c 'until curl -f http://localhost:3000; do sleep 1; done' + - name: Browser Smoke Test + run: | + # Test server is running + curl -f http://localhost:3000 || exit 1 + echo "Browser mode server is running successfully" + - name: Stop Server + if: always() + run: | + if [ -f server.pid ]; then + kill $(cat server.pid) || true + rm server.pid + fi diff --git a/.gitignore b/.gitignore index 312530e..d12aede 100644 --- a/.gitignore +++ b/.gitignore @@ -15,5 +15,5 @@ mqtt-explorer-mcp-screenshot.png screenshot-mcp-*.png test-mcp-introspection.js -# UI test artifacts +/data test-screenshot-*.png diff --git a/BROWSER_MODE.md b/BROWSER_MODE.md new file mode 100644 index 0000000..c0c3af2 --- /dev/null +++ b/BROWSER_MODE.md @@ -0,0 +1,193 @@ +# Browser Mode Documentation + +MQTT Explorer now supports running as a web application served by a Node.js server, in addition to the existing Electron desktop app. + +## Running in Browser Mode + +### Quick Start + +1. Build the application for browser mode: + ```bash + yarn build:server + ``` + +2. Start the server: + ```bash + yarn start:server + ``` + +3. Open your browser and navigate to `http://localhost:3000` + +4. You'll be prompted to log in with credentials that were generated on server startup. + +### Development Mode + +To run in development mode with hot reload: + +```bash +yarn dev:server +``` + +This starts both the webpack dev server and the backend server. + +## Authentication + +### Environment Variables + +You can set custom authentication credentials using environment variables: + +```bash +export MQTT_EXPLORER_USERNAME=admin +export MQTT_EXPLORER_PASSWORD=secretpassword +yarn start:server +``` + +### Generated Credentials + +If no environment variables are set, the server will generate credentials on first startup and save them to `data/credentials.json`. The generated credentials will be printed to the console: + +``` +============================================================ +Generated new credentials: +Username: user-abc123 +Password: 123e4567-e89b-12d3-a456-426614174000 +============================================================ +Please save these credentials. They will be persisted to: +/path/to/data/credentials.json +============================================================ +``` + +## Features + +### Certificate Upload + +In browser mode, certificate files are uploaded directly through the browser using the HTML5 File API. The certificates are: +- Read client-side as base64 +- Stored in the connection configuration +- Used when establishing MQTT connections + +### Data Storage + +In browser mode, all data is stored on the server: +- Credentials: `data/credentials.json` +- Uploaded certificates: `data/certificates/` +- File uploads: `data/uploads/` + +### Port Configuration + +The default port is 3000. You can change it using the `PORT` environment variable: + +```bash +PORT=8080 yarn start:server +``` + +## Architecture + +### Client-Server Communication + +- **Electron Mode**: Uses Electron IPC for communication between renderer and main process +- **Browser Mode**: Uses Socket.io WebSockets for real-time communication between browser and server + +The application automatically detects the environment and uses the appropriate transport layer. + +### Event Bus Abstraction + +Both Electron IPC and Socket.io implement the same `EventBusInterface`, allowing the application code to work seamlessly in both modes without modification. + +## Differences from Electron Mode + +### Browser Mode Limitations + +1. **File System Access**: Limited to server-side operations +2. **Native Dialogs**: File selection uses browser file input instead of native dialogs +3. **Auto-Updates**: Not available in browser mode +4. **Tray Icon**: Not available in browser mode + +### Browser Mode Advantages + +1. **No Installation**: Access from any browser +2. **Cross-Platform**: Works on any device with a modern browser +3. **Remote Access**: Can be deployed on a server for remote access +4. **Multi-User**: Can support authentication for multiple users + +## Security Considerations + +1. **HTTPS**: For production, always use HTTPS to encrypt credentials and MQTT data +2. **Authentication**: Keep credentials secure and rotate them regularly +3. **Network**: Ensure the server is on a trusted network or behind a firewall +4. **Environment Variables**: Use environment variables for production credentials, not the generated ones + +## Deployment + +For production deployment: + +1. Build the application: + ```bash + yarn build:server + ``` + +2. Set environment variables: + ```bash + export MQTT_EXPLORER_USERNAME=your_username + export MQTT_EXPLORER_PASSWORD=your_secure_password + export PORT=3000 + ``` + +3. Start the server: + ```bash + yarn start:server + ``` + +4. Use a reverse proxy (nginx, Apache) to add HTTPS and additional security features + +## Troubleshooting + +### Debugging + +Enable detailed Socket.IO connection and lifecycle debugging: + +```bash +DEBUG=mqtt-explorer:socketio* yarn start:server +``` + +Available debug namespaces: +- `mqtt-explorer:socketio` - General Socket.IO events and metrics +- `mqtt-explorer:socketio:connect` - Client connection events +- `mqtt-explorer:socketio:disconnect` - Client disconnection and cleanup +- `mqtt-explorer:socketio:subscriptions` - Subscription lifecycle tracking +- `mqtt-explorer:socketio:connections` - MQTT connection ownership + +This will log: +- Client connect/disconnect events +- Subscription counts per socket +- MQTT connection ownership tracking +- Memory leak detection metrics (subscriptions, handlers, connections) + +Example output: +``` +mqtt-explorer:socketio:connect Client connected: abc123de +mqtt-explorer:socketio [connect] clients=1 subscriptions=8 mqttConns=0 | socket[abc123de]: subs=8 conns=0 +mqtt-explorer:socketio:connections Connection my-mqtt owned by socket abc123de (total: 1) +mqtt-explorer:socketio:disconnect Client disconnected: abc123de +mqtt-explorer:socketio:subscriptions Removed 8 subscriptions for socket abc123de +mqtt-explorer:socketio [disconnect] clients=0 subscriptions=0 mqttConns=0 | socket[abc123de]: subs=0 conns=0 +``` + +### Authentication Fails + +1. Check the console output for the generated credentials +2. Clear browser session storage: `sessionStorage.clear()` in browser console +3. Restart the server to regenerate credentials + +### Connection Issues + +1. Check that the server is running: `http://localhost:3000` +2. Check browser console for Socket.io connection errors +3. Verify firewall rules allow the port + +### Certificate Upload Issues + +In browser mode, certificates are handled differently: +- Use the file upload button to select certificate files +- Files are read and encoded client-side +- Large certificate files (>16KB) will be rejected diff --git a/CI_CD.md b/CI_CD.md new file mode 100644 index 0000000..cf9dea8 --- /dev/null +++ b/CI_CD.md @@ -0,0 +1,149 @@ +# CI/CD Pipeline Documentation + +## Overview + +MQTT Explorer uses GitHub Actions for continuous integration and testing. The pipeline tests both Electron (desktop) and browser modes. + +## Workflows + +### Test Workflow (`.github/workflows/tests.yml`) + +This workflow runs on pull requests to `master`, `beta`, and `release` branches. + +#### Jobs + +##### 1. `test` - Electron Mode Tests + +Tests the traditional Electron desktop application: + +- **Environment**: Custom Docker container (`ghcr.io/thomasnordquist/mqtt-explorer-ui-tests:latest`) +- **Steps**: + 1. Install dependencies with frozen lockfile + 2. Build the Electron application + 3. Run unit tests (app + backend) + 4. Run UI tests with video recording + 5. Upload test video to S3 + 6. Display test results in GitHub summary + +**Artifacts**: UI test video (GIF format) uploaded to S3 + +##### 2. `test-browser` - Browser Mode Tests + +Tests the new browser/server mode: + +- **Environment**: Ubuntu latest with Node.js 20 +- **Services**: + - **Mosquitto MQTT Broker**: Eclipse Mosquitto v2 on port 1883 + - Health checks enabled + - Anonymous connections allowed +- **Steps**: + 1. Setup Node.js 20 + 2. Install dependencies + 3. Build browser mode (`yarn build:server`) + 4. Run unit tests (app + backend) + 5. Start server in background with test credentials + 6. Wait for server to be ready + 7. Run browser smoke tests + 8. Clean up server process + +**Environment Variables**: +- `MQTT_EXPLORER_USERNAME=test` +- `MQTT_EXPLORER_PASSWORD=test123` +- `PORT=3000` + +## Test Commands + +The following npm scripts are used in CI/CD: + +```bash +# Unit tests +yarn test # Run all tests (app + backend) +yarn test:app # Frontend tests only +yarn test:backend # Backend tests only + +# Build +yarn build # Build Electron mode +yarn build:server # Build browser mode + +# UI Tests (Electron only) +yarn ui-test # Run UI tests with video recording +``` + +## Adding New Tests + +### For Electron Mode + +Add tests to the `test` job. UI tests should be added to the test suite that `yarn ui-test` runs. + +### For Browser Mode + +Browser-specific tests should: +1. Use the pre-configured Mosquitto service +2. Connect to `mqtt://mosquitto:1883` +3. Test server endpoints at `http://localhost:3000` + +Example: +```yaml +- name: Browser Integration Test + run: | + # Test MQTT connection through server + curl -X POST http://localhost:3000/api/test +``` + +## Local Testing + +### Electron Mode + +```bash +yarn build +yarn test +yarn ui-test +``` + +### Browser Mode + +```bash +# Start Mosquitto in Docker +docker run -d -p 1883:1883 eclipse-mosquitto:2 + +# Build and test +yarn build:server +yarn test + +# Start server +MQTT_EXPLORER_USERNAME=test MQTT_EXPLORER_PASSWORD=test123 yarn start:server + +# Run manual tests +curl http://localhost:3000 +``` + +## GitHub Codespaces / Devcontainer + +The repository includes a devcontainer configuration that automatically sets up: +- Node.js 20 +- MQTT broker (Mosquitto) +- All development dependencies +- Port forwarding for development + +See [.devcontainer/README.md](.devcontainer/README.md) for details. + +## Troubleshooting + +### Browser Tests Failing + +1. **Server won't start**: Check if port 3000 is already in use +2. **MQTT connection fails**: Ensure Mosquitto service is healthy +3. **Timeout errors**: Increase timeout in "Wait for Server" step + +### Electron Tests Failing + +1. **UI tests timeout**: Check if the Docker container has display access +2. **Build fails**: Verify all dependencies are in yarn.lock + +## Future Improvements + +- [ ] Add E2E browser tests with Playwright +- [ ] Test WebSocket connections in browser mode +- [ ] Add performance benchmarks +- [ ] Test with different MQTT broker versions +- [ ] Add security scanning for browser mode diff --git a/Readme.md b/Readme.md index 4a5e0aa..9b7720f 100644 --- a/Readme.md +++ b/Readme.md @@ -18,8 +18,22 @@ Downloads can be found at the link above. This page is dedicated to its development. Pull-Requests and error reports are welcome. +## Quick Start with GitHub Codespaces + +The fastest way to start developing is with GitHub Codespaces: + +1. Click the green "Code" button above +2. Select "Codespaces" tab +3. Click "Create codespace on [branch]" +4. Wait for the environment to set up (includes Node.js and MQTT broker) +5. Run `yarn dev:server` to start development + +The devcontainer includes a pre-configured MQTT broker and all development tools. See [.devcontainer/README.md](.devcontainer/README.md) for details. + ## Run from sources +### Desktop Application (Electron) + ```bash npm install -g yarn yarn @@ -27,8 +41,23 @@ yarn build yarn start ``` +### Browser Mode (Web Application) + +MQTT Explorer can also run as a web application served by a Node.js server: + +```bash +npm install -g yarn +yarn +yarn build:server +yarn start:server +``` + +Then open your browser to `http://localhost:3000`. For more details, see [BROWSER_MODE.md](BROWSER_MODE.md). + ## Develop +### Desktop Application + Launch Application ```bash @@ -37,6 +66,16 @@ yarn yarn dev ``` +### Browser Mode + +Launch in development mode with hot reload: + +```bash +npm install -g yarn +yarn +yarn dev:server +``` + The `app` directory contains all the rendering logic, the `backend` directory currently contains the models, tests, connection management, `src` contains all the electron bindings. [mqttjs](https://github.com/mqttjs/MQTT.js) is used to facilitate communication to MQTT brokers. ## Automated Tests diff --git a/app/package.json b/app/package.json index bd1256b..aff9980 100644 --- a/app/package.json +++ b/app/package.json @@ -10,7 +10,7 @@ "mochatest": "mocha --require ts-node/register --require source-map-support/register --recursive src/*/**/*.spec.ts" }, "engines": { - "node": ">=18" + "node": ">=20" }, "author": "", "license": "CC-BY-ND-4.0", @@ -28,6 +28,7 @@ "d3-shape": "^1.3.5", "diff": "^4.0.1", "dot-prop": "^5.0.0", + "events": "^3.3.0", "get-value": "^3.0.1", "immutable": "^4.0.0-rc.12", "in-viewport": "^3.6.0", @@ -37,7 +38,9 @@ "lodash.throttle": "^4.1.1", "moving-average": "^1.0.0", "number-abbreviate": "^2.0.0", + "os-browserify": "^0.3.0", "parse-duration": "^0.1.1", + "path-browserify": "^1.0.1", "prismjs": "^1.15.0", "react": "^16.11", "react-ace": "^8", @@ -51,7 +54,8 @@ "redux-batched-actions": "0.5", "redux-thunk": "^2.3.0", "sha1": "^1.1.1", - "socket.io-client": "^2.2.0", + "socket.io-client": "^4.8.1", + "url": "^0.11.4", "uuid": "7" }, "devDependencies": { @@ -66,7 +70,7 @@ "@types/react-redux": "^7.0.9", "@types/react-resize-detector": "^4.0.1", "@types/sha1": "^1.1.1", - "@types/socket.io-client": "^1.4.32", + "@types/socket.io-client": "^3.0.0", "@types/uuid": "^7.0.2", "@types/vis": "^4.21.9", "chai": "^4.2.0", diff --git a/app/src/components/BrowserAuthWrapper.tsx b/app/src/components/BrowserAuthWrapper.tsx new file mode 100644 index 0000000..a9e5d90 --- /dev/null +++ b/app/src/components/BrowserAuthWrapper.tsx @@ -0,0 +1,65 @@ +import * as React from 'react' +import { LoginDialog } from './LoginDialog' + +interface BrowserAuthWrapperProps { + children: React.ReactNode +} + +const isBrowserMode = + typeof window !== 'undefined' && + (typeof process === 'undefined' || process.env?.BROWSER_MODE === 'true') + +export function BrowserAuthWrapper(props: BrowserAuthWrapperProps) { + const [isAuthenticated, setIsAuthenticated] = React.useState(false) + const [loginError, setLoginError] = React.useState() + const [showLogin, setShowLogin] = React.useState(false) + + React.useEffect(() => { + if (!isBrowserMode) { + // Not in browser mode, skip authentication + setIsAuthenticated(true) + return + } + + // Check if already authenticated + const username = sessionStorage.getItem('mqtt-explorer-username') + const password = sessionStorage.getItem('mqtt-explorer-password') + + if (username && password) { + // Try to use stored credentials + setIsAuthenticated(true) + } else { + // Show login dialog + setShowLogin(true) + } + }, []) + + const handleLogin = async (username: string, password: string) => { + try { + // Store credentials in session storage + sessionStorage.setItem('mqtt-explorer-username', username) + sessionStorage.setItem('mqtt-explorer-password', password) + + // The socket will use these credentials on next connection + setIsAuthenticated(true) + setShowLogin(false) + setLoginError(undefined) + + // Reload to reinitialize socket with new auth + window.location.reload() + } catch (error) { + setLoginError('Login failed. Please check your credentials.') + } + } + + if (!isBrowserMode) { + // Not in browser mode, render children directly + return <>{props.children} + } + + if (!isAuthenticated) { + return + } + + return <>{props.children} +} diff --git a/app/src/components/ConnectionSetup/BrowserCertificateFileSelection.tsx b/app/src/components/ConnectionSetup/BrowserCertificateFileSelection.tsx new file mode 100644 index 0000000..d1b30ad --- /dev/null +++ b/app/src/components/ConnectionSetup/BrowserCertificateFileSelection.tsx @@ -0,0 +1,140 @@ +import * as React from 'react' +import ClearAdornment from '../helper/ClearAdornment' +import Lock from '@material-ui/icons/Lock' +import { bindActionCreators } from 'redux' +import { Button, Theme, Tooltip, Typography } from '@material-ui/core' +import { CertificateParameters, ConnectionOptions } from '../../model/ConnectionOptions' +import { CertificateTypes } from '../../actions/ConnectionManager' +import { connect } from 'react-redux' +import { connectionManagerActions } from '../../actions' +import { withStyles } from '@material-ui/styles' +import { rendererRpc } from '../../../../events' +import { RpcEvents } from '../../../../events/EventsV2' + +function BrowserCertificateFileSelection(props: { + certificateType: CertificateTypes + title: string + certificate?: CertificateParameters + classes: any + actions: { + connectionManager: typeof connectionManagerActions + } + connection: ConnectionOptions +}) { + const fileInputRef = React.useRef(null) + + const clearCertificate = React.useCallback(() => { + props.actions.connectionManager.updateConnection(props.connection.id, { + [props.certificateType]: undefined, + }) + }, [props.connection, props.certificateType]) + + const handleFileSelect = React.useCallback( + async (event: React.ChangeEvent) => { + const file = event.target.files?.[0] + if (!file) { + return + } + + try { + // Read file content + const reader = new FileReader() + reader.onload = async e => { + const content = e.target?.result + if (typeof content === 'string') { + // Convert to base64 + const base64Data = content.split(',')[1] || content + + // Upload via IPC instead of HTTP POST + const result = await rendererRpc.call(RpcEvents.uploadCertificate, { + filename: file.name, + data: base64Data, + }) + + // Create certificate parameters + const certificate: CertificateParameters = { + name: result.name, + data: result.data, + } + + // Update connection + props.actions.connectionManager.updateConnection(props.connection.id, { + [props.certificateType]: certificate, + }) + } + } + reader.readAsDataURL(file) + } catch (error) { + console.error('Error uploading certificate:', error) + } + + // Reset input + if (fileInputRef.current) { + fileInputRef.current.value = '' + } + }, + [props.connection.id, props.certificateType, props.actions.connectionManager] + ) + + const handleButtonClick = () => { + fileInputRef.current?.click() + } + + return ( + + + + + + + + ) +} + +function ClearCertificate(props: { classes: any; certificate?: CertificateParameters; action: () => void }) { + if (!props.certificate) { + return null + } + + return ( + + + + {props.certificate.name} + + + ) +} + +const mapDispatchToProps = (dispatch: any) => { + return { + actions: { + connectionManager: bindActionCreators(connectionManagerActions, dispatch), + }, + } +} + +const styles = (theme: Theme) => ({ + certificateName: { + width: '100%', + height: 'calc(1em + 4px)', + overflow: 'hidden' as 'hidden', + whiteSpace: 'nowrap' as 'nowrap', + textOverflow: 'ellipsis' as 'ellipsis', + color: theme.palette.text.hint, + }, + button: { + marginTop: theme.spacing(3), + marginRight: theme.spacing(2), + }, +}) + +export default connect(undefined, mapDispatchToProps)(withStyles(styles)(BrowserCertificateFileSelection)) diff --git a/app/src/components/ConnectionSetup/Certificates.tsx b/app/src/components/ConnectionSetup/Certificates.tsx index 0277c1a..f72cdeb 100644 --- a/app/src/components/ConnectionSetup/Certificates.tsx +++ b/app/src/components/ConnectionSetup/Certificates.tsx @@ -1,5 +1,6 @@ import * as React from 'react' import CertificateFileSelection from './CertificateFileSelection' +import BrowserCertificateFileSelection from './BrowserCertificateFileSelection' import Undo from '@material-ui/icons/Undo' import { bindActionCreators } from 'redux' import { Button, Grid } from '@material-ui/core' @@ -8,6 +9,12 @@ import { connectionManagerActions } from '../../actions' import { ConnectionOptions } from '../../model/ConnectionOptions' import { Theme, withStyles } from '@material-ui/core/styles' +// Check if we're in browser mode +const isBrowserMode = + typeof window !== 'undefined' && + (typeof process === 'undefined' || process.env?.BROWSER_MODE === 'true') +const CertSelector = isBrowserMode ? BrowserCertificateFileSelection : CertificateFileSelection + interface Props { connection: ConnectionOptions classes: any @@ -45,7 +52,7 @@ class Certificates extends React.PureComponent {
- { /> - { /> - void + error?: string +} + +export function LoginDialog(props: LoginDialogProps) { + const [username, setUsername] = React.useState('') + const [password, setPassword] = React.useState('') + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault() + props.onLogin(username, password) + } + + return ( + + + Login to MQTT Explorer + + {props.error && ( + + {props.error} + + )} + setUsername(e.target.value)} + required + /> + setPassword(e.target.value)} + required + /> + + + + + + + ) +} diff --git a/app/src/components/UpdateNotifier.tsx b/app/src/components/UpdateNotifier.tsx index c7b4172..b5436dd 100644 --- a/app/src/components/UpdateNotifier.tsx +++ b/app/src/components/UpdateNotifier.tsx @@ -1,6 +1,5 @@ import compareVersions from 'compare-versions' import electron from 'electron' -import os from 'os' import React from 'react' import axios from 'axios' import Close from '@material-ui/icons/Close' @@ -182,9 +181,10 @@ class UpdateNotifier extends React.PureComponent { private assetForCurrentPlatform(asset: GithubAsset) { let regex: RegExp - if (os.platform() === 'darwin') { + const platform = this.getPlatform() + if (platform === 'darwin') { regex = /\.dmg$/ - } else if (os.platform() === 'win32') { + } else if (platform === 'win32') { regex = /\.exe$/ } else { regex = /\.AppImage$/ @@ -193,6 +193,14 @@ class UpdateNotifier extends React.PureComponent { return regex.test(asset.name) } + private getPlatform(): string { + if (typeof window === 'undefined') return 'linux' + const userAgent = window.navigator.userAgent.toLowerCase() + if (userAgent.includes('mac')) return 'darwin' + if (userAgent.includes('win')) return 'win32' + return 'linux' + } + private renderDownloads() { const latestUpdate = this.state.newerVersions[0] if (!latestUpdate || !latestUpdate.assets) { diff --git a/app/src/index.tsx b/app/src/index.tsx index ffe6a47..04db101 100644 --- a/app/src/index.tsx +++ b/app/src/index.tsx @@ -10,6 +10,7 @@ import { connect, Provider } from 'react-redux' import { ThemeProvider } from '@material-ui/styles' import './utils/tracking' import { themes } from './theme' +import { BrowserAuthWrapper } from './components/BrowserAuthWrapper' const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose const store = createStore(reducers, composeEnhancers(applyMiddleware(reduxThunk, batchDispatchMiddleware))) @@ -33,7 +34,9 @@ const Application = connect(mapStateToProps)(ApplicationRenderer) ReactDOM.render( - + + + , document.getElementById('app') ) diff --git a/app/src/mocks/electron.ts b/app/src/mocks/electron.ts new file mode 100644 index 0000000..2a2b394 --- /dev/null +++ b/app/src/mocks/electron.ts @@ -0,0 +1,12 @@ +// Mock electron module for browser environment +export const shell = { + openExternal: (url: string) => { + if (typeof window !== 'undefined') { + window.open(url, '_blank') + } + }, +} + +export default { + shell, +} diff --git a/app/webpack.browser.config.js b/app/webpack.browser.config.js new file mode 100644 index 0000000..3ae7f99 --- /dev/null +++ b/app/webpack.browser.config.js @@ -0,0 +1,102 @@ +// Browser-specific webpack configuration +const HtmlWebpackPlugin = require('html-webpack-plugin') +const webpack = require('webpack') +const path = require('path') + +module.exports = { + entry: { + app: './src/index.tsx', + bugtracking: './src/utils/bugtracking.ts', + }, + output: { + chunkFilename: '[name].bundle.js', + filename: '[name].bundle.js', + path: `${__dirname}/build`, + }, + optimization: { + minimize: false, + splitChunks: { + chunks: 'all', + minSize: 30000, + minChunks: 1, + maxAsyncRequests: 5, + maxInitialRequests: 3, + automaticNameDelimiter: '~', + cacheGroups: { + vendors: { + test: /[\\/]node_modules[\\/](react|react-dom|@material-ui|popper\.js|react|react-redux|prop-types|jss|redux|scheduler|react-transition-group)[\\/]/, + name: 'vendors', + chunks: 'all', + priority: -10, + }, + default: { + name: 'default', + minChunks: 2, + priority: -20, + reuseExistingChunk: true, + }, + }, + }, + runtimeChunk: 'single', + }, + devServer: { + hot: true, + liveReload: true, + }, + target: 'web', // Changed from 'electron-renderer' to 'web' + mode: 'production', + devtool: 'source-map', + resolve: { + extensions: ['.ts', '.mjs', '.m.js', '.tsx', '.js', '.json'], + modules: ['node_modules', path.resolve(__dirname, 'node_modules')], + alias: { + electron: require.resolve('./src/mocks/electron.ts'), + }, + fallback: { + // Browser fallbacks for Node.js modules + path: require.resolve('path-browserify'), + fs: false, + crypto: false, + url: require.resolve('url/'), + os: require.resolve('os-browserify/browser'), + events: require.resolve('events/'), + }, + }, + module: { + rules: [ + { + test: /\.tsx?$/, + use: [ + { + loader: 'ts-loader', + options: { + transpileOnly: true, // Skip type checking, we already did it with tsc + }, + }, + ], + exclude: /node_modules/, + }, + { enforce: 'pre', test: /\.js$/, loader: 'source-map-loader' }, + { + test: /\.css$/, + use: ['style-loader', 'css-loader'], + }, + { + test: /\.(png|jpg|gif)$/i, + type: 'asset/resource', + }, + ], + }, + plugins: [ + new HtmlWebpackPlugin({ template: './index.html', file: './build/index.html', inject: false }), + new webpack.DefinePlugin({ + 'process.env.BROWSER_MODE': JSON.stringify('true'), + }), + new webpack.NormalModuleReplacementPlugin(/EventSystem[\\/]EventBus$/, resource => { + console.log('Replacing EventBus:', resource.request); + resource.request = resource.request.replace(/EventBus$/, 'BrowserEventBus'); + }), + ], + externals: {}, + cache: false, +} diff --git a/app/yarn.lock b/app/yarn.lock index f0c96e0..6d02f0f 100644 --- a/app/yarn.lock +++ b/app/yarn.lock @@ -188,6 +188,11 @@ resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.25.tgz#f077fdc0b5d0078d30893396ff4827a13f99e817" integrity sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ== +"@socket.io/component-emitter@~3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz#821f8442f4175d8f0467b9daf26e3a18e2d02af2" + integrity sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA== + "@types/body-parser@*": version "1.19.5" resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4" @@ -668,10 +673,12 @@ dependencies: "@types/node" "*" -"@types/socket.io-client@^1.4.32": - version "1.4.36" - resolved "https://registry.yarnpkg.com/@types/socket.io-client/-/socket.io-client-1.4.36.tgz#e4f1ca065f84c20939e9850e70222202bd76ff3f" - integrity sha512-ZJWjtFBeBy1kRSYpVbeGYTElf6BqPQUkXDlHHD4k/42byCN5Rh027f4yARHCink9sKAkbtGZXEAmR0ZCnc2/Ag== +"@types/socket.io-client@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/socket.io-client/-/socket.io-client-3.0.0.tgz#d0b8ea22121b7c1df68b6a923002f9c8e3cefb42" + integrity sha512-s+IPvFoEIjKA3RdJz/Z2dGR4gLgysKi8owcnrVwNjgvc01Lk68LJDDsG2GRqegFITcxmvCMYM7bhMpwEMlHmDg== + dependencies: + socket.io-client "*" "@types/sockjs@^0.3.36": version "0.3.36" @@ -873,11 +880,6 @@ acorn@^8.0.4, acorn@^8.7.1, acorn@^8.8.2: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== -after@0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" - integrity sha512-QbJ0NTQ/I9DI3uSJA4cbexiwQeRAfjPScqIbSjUDd9TOrcg6pTkdgziesOqxBMBzit8vFCTwrP27t13vFOORRA== - ajv-formats@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" @@ -967,11 +969,6 @@ array-flatten@1.1.1: resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== -arraybuffer.slice@~0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675" - integrity sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog== - assertion-error@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" @@ -998,21 +995,11 @@ axios@^0.28.0: form-data "^4.0.0" proxy-from-env "^1.1.0" -backo2@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" - integrity sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA== - balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base64-arraybuffer@0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz#9818c79e059b1355f97e0428a017c838e90ba812" - integrity sha512-a1eIFi4R9ySrbiMuyTGx5e92uRH5tQY6kArNcFaKBUleIoLjdjBg7Zxm3Mqm3Kmkf27HLR/1fnxX9q8GQ7Iavg== - batch@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" @@ -1028,11 +1015,6 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -blob@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683" - integrity sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig== - body-parser@1.20.2: version "1.20.2" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" @@ -1115,6 +1097,14 @@ bytes@3.1.2: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + call-bind@^1.0.2, call-bind@^1.0.6, call-bind@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" @@ -1126,6 +1116,14 @@ call-bind@^1.0.2, call-bind@^1.0.6, call-bind@^1.0.7: get-intrinsic "^1.2.4" set-function-length "^1.2.1" +call-bound@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" + integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== + dependencies: + call-bind-apply-helpers "^1.0.2" + get-intrinsic "^1.3.0" + camel-case@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a" @@ -1301,21 +1299,6 @@ compare-versions@^3.5.0: resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62" integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA== -component-bind@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1" - integrity sha512-WZveuKPeKAG9qY+FkYDeADzdHyTYdIboXS59ixDeRJL5ZhxpqUnxSOwop4FQjMsiYm3/Or8cegVbpAHNA7pHxw== - -component-emitter@~1.3.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.1.tgz#ef1d5796f7d93f135ee6fb684340b26403c97d17" - integrity sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ== - -component-inherit@0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143" - integrity sha512-w+LhYREhatpVqTESyGFg3NlP6Iu0kEKUHETY9GoZP/pQyW4mHFZuFWRUCIqVPZ36ueVLtoOEZaAqbCF2RDndaA== - compressible@~2.0.16: version "2.0.18" resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" @@ -1819,12 +1802,12 @@ debug@4.3.4, debug@^4.1.0: dependencies: ms "2.1.2" -debug@~3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== +debug@~4.3.1, debug@~4.3.2: + version "4.3.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== dependencies: - ms "2.0.0" + ms "^2.1.3" decamelize@^4.0.0: version "4.0.0" @@ -2005,6 +1988,15 @@ dot-prop@^5.0.0: dependencies: is-obj "^2.0.0" +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + duplexer@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" @@ -2045,33 +2037,21 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== -engine.io-client@~3.5.0: - version "3.5.3" - resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.5.3.tgz#3254f61fdbd53503dc9a6f9d46a52528871ca0d7" - integrity sha512-qsgyc/CEhJ6cgMUwxRRtOndGVhIu5hpL5tR4umSpmX/MvkFoIxUTM7oFMDQumHNzlNLwSVy6qhstFPoWTf7dOw== +engine.io-client@~6.6.1: + version "6.6.3" + resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-6.6.3.tgz#815393fa24f30b8e6afa8f77ccca2f28146be6de" + integrity sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w== dependencies: - component-emitter "~1.3.0" - component-inherit "0.0.3" - debug "~3.1.0" - engine.io-parser "~2.2.0" - has-cors "1.1.0" - indexof "0.0.1" - parseqs "0.0.6" - parseuri "0.0.6" - ws "~7.4.2" - xmlhttprequest-ssl "~1.6.2" - yeast "0.1.2" + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" + engine.io-parser "~5.2.1" + ws "~8.17.1" + xmlhttprequest-ssl "~2.1.1" -engine.io-parser@~2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-2.2.1.tgz#57ce5611d9370ee94f99641b589f94c97e4f5da7" - integrity sha512-x+dN/fBH8Ro8TFwJ+rkB2AmuVw9Yu2mockR/p3W8f8YtExwFgDvBDi0GWyb4ZLkpahtDGZgtr3zLovanJghPqg== - dependencies: - after "0.8.2" - arraybuffer.slice "~0.0.7" - base64-arraybuffer "0.1.4" - blob "0.0.5" - has-binary2 "~1.0.2" +engine.io-parser@~5.2.1: + version "5.2.3" + resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.2.3.tgz#00dc5b97b1f233a23c9398d0209504cf5f94d92f" + integrity sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q== enhanced-resolve@^5.0.0: version "5.15.1" @@ -2106,6 +2086,11 @@ es-define-property@^1.0.0: dependencies: get-intrinsic "^1.2.4" +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + es-errors@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" @@ -2116,6 +2101,13 @@ es-module-lexer@^1.2.1: resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.4.1.tgz#41ea21b43908fe6a287ffcbe4300f790555331f5" integrity sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w== +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + escalade@^3.1.1: version "3.1.2" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" @@ -2166,7 +2158,7 @@ eventemitter3@^4.0.0: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== -events@^3.2.0: +events@^3.2.0, events@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== @@ -2367,6 +2359,30 @@ get-intrinsic@^1.1.3, get-intrinsic@^1.2.4: has-symbols "^1.0.3" hasown "^2.0.0" +get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + get-stream@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" @@ -2428,6 +2444,11 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + graceful-fs@^4.1.2, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" @@ -2450,18 +2471,6 @@ handle-thing@^2.0.0: resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== -has-binary2@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d" - integrity sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw== - dependencies: - isarray "2.0.1" - -has-cors@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39" - integrity sha512-g5VNKdkFuUuVCP9gYfDJHjK2nqdQJ7aDLTnycnc2+RvsOQbuLdF5pm7vuE5J76SEBIQjs4kQY/BWq74JUmjbXA== - has-flag@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" @@ -2484,6 +2493,11 @@ has-symbols@^1.0.3: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== +has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + has-tostringtag@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" @@ -2498,6 +2512,13 @@ hasown@^2.0.0: dependencies: function-bind "^1.1.2" +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + he@1.2.0, he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" @@ -2662,11 +2683,6 @@ in-viewport@^3.6.0: resolved "https://registry.yarnpkg.com/in-viewport/-/in-viewport-3.6.0.tgz#c59b4cdcaa41adb5bf5b8fe390c7d34259891f4a" integrity sha512-MhaJ7Pr3NhUyAfpULysTZZBUAYfJAX1O8PccW2gvXlbQduMrJz7qQQ5yzC7SAr/0g5LbeRk432yNjsLMCnYzJg== -indexof@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" - integrity sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg== - inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -2835,11 +2851,6 @@ is-wsl@^3.1.0: dependencies: is-inside-container "^1.0.0" -isarray@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e" - integrity sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ== - isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -3116,6 +3127,11 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -3270,7 +3286,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3: +ms@2.1.3, ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -3350,6 +3366,11 @@ object-inspect@^1.13.1: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== +object-inspect@^1.13.3: + version "1.13.4" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" + integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== + object-is@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.6.tgz#1a6a53aed2dd8f7e6775ff870bea58545956ab07" @@ -3409,6 +3430,11 @@ opener@^1.5.2: resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== +os-browserify@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" + integrity sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A== + p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -3464,16 +3490,6 @@ parse-duration@^0.1.1: resolved "https://registry.yarnpkg.com/parse-duration/-/parse-duration-0.1.3.tgz#c2c4d45d49513d544e129b2a5a07b9473545d19a" integrity sha512-hMOZHfUmjxO5hMKn7Eft+ckP2M4nV4yzauLXiw3PndpkASnx5r8pDAMcOAiqxoemqWjMWmz4fOHQM6n6WwETXw== -parseqs@0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.6.tgz#8e4bb5a19d1cdc844a08ac974d34e273afa670d5" - integrity sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w== - -parseuri@0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.6.tgz#e1496e829e3ac2ff47f39a4dd044b32823c4a25a" - integrity sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow== - parseurl@~1.3.2, parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" @@ -3487,6 +3503,11 @@ pascal-case@^3.1.2: no-case "^3.0.4" tslib "^2.0.3" +path-browserify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" + integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== + path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -3656,6 +3677,11 @@ proxy-from-env@^1.1.0: resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== +punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== + punycode@^2.1.0: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" @@ -3668,6 +3694,13 @@ qs@6.11.0: dependencies: side-channel "^1.0.4" +qs@^6.12.3: + version "6.14.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930" + integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w== + dependencies: + side-channel "^1.1.0" + raf-schd@^4.0.2: version "4.0.3" resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a" @@ -4171,6 +4204,35 @@ shell-quote@^1.8.1: resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680" integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== +side-channel-list@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" + integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + +side-channel-map@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" + integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + side-channel-map "^1.0.1" + side-channel@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" @@ -4181,6 +4243,17 @@ side-channel@^1.0.4: get-intrinsic "^1.2.4" object-inspect "^1.13.1" +side-channel@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" + integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + side-channel-list "^1.0.0" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" + signal-exit@^3.0.3: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" @@ -4200,31 +4273,23 @@ sirv@^2.0.3: mrmime "^2.0.0" totalist "^3.0.0" -socket.io-client@^2.2.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.5.0.tgz#34f486f3640dde9c2211fce885ac2746f9baf5cb" - integrity sha512-lOO9clmdgssDykiOmVQQitwBAF3I6mYcQAo7hQ7AM6Ny5X7fp8hIJ3HcQs3Rjz4SoggoxA1OgrQyY8EgTbcPYw== +socket.io-client@*, socket.io-client@^4.8.1: + version "4.8.1" + resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.8.1.tgz#1941eca135a5490b94281d0323fe2a35f6f291cb" + integrity sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ== dependencies: - backo2 "1.0.2" - component-bind "1.0.0" - component-emitter "~1.3.0" - debug "~3.1.0" - engine.io-client "~3.5.0" - has-binary2 "~1.0.2" - indexof "0.0.1" - parseqs "0.0.6" - parseuri "0.0.6" - socket.io-parser "~3.3.0" - to-array "0.1.4" + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.2" + engine.io-client "~6.6.1" + socket.io-parser "~4.2.4" -socket.io-parser@~3.3.0: - version "3.3.3" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.3.tgz#3a8b84823eba87f3f7624e64a8aaab6d6318a72f" - integrity sha512-qOg87q1PMWWTeO01768Yh9ogn7chB9zkKtQnya41Y355S0UmpXgpcrFwAgjYJxu9BdKug5r5e9YtVSeWhKBUZg== +socket.io-parser@~4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83" + integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew== dependencies: - component-emitter "~1.3.0" - debug "~3.1.0" - isarray "2.0.1" + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" sockjs@^0.3.24: version "0.3.24" @@ -4441,11 +4506,6 @@ tiny-warning@^1.0.2: resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== -to-array@0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" - integrity sha512-LhVdShQD/4Mk4zXNroIQZJC+Ap3zgLcDuwEdcmLv9CCO73NWockQDwyUnW/m8VX/EElfL6FcYx7EeutN4HJA6A== - to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -4527,6 +4587,14 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +url@^0.11.4: + version "0.11.4" + resolved "https://registry.yarnpkg.com/url/-/url-0.11.4.tgz#adca77b3562d56b72746e76b330b7f27b6721f3c" + integrity sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg== + dependencies: + punycode "^1.4.1" + qs "^6.12.3" + util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -4775,15 +4843,15 @@ ws@^8.16.0: resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.0.tgz#d145d18eca2ed25aaf791a183903f7be5e295fea" integrity sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow== -ws@~7.4.2: - version "7.4.6" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" - integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== +ws@~8.17.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== -xmlhttprequest-ssl@~1.6.2: - version "1.6.3" - resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.3.tgz#03b713873b01659dfa2c1c5d056065b27ddc2de6" - integrity sha512-3XfeQE/wNkvrIktn2Kf0869fC0BN6UpydVasGIeSm2B1Llihf7/0UfZM+eCkOw3P7bP4+qPgqhm7ZoxuJtFU0Q== +xmlhttprequest-ssl@~2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz#e9e8023b3f29ef34b97a859f584c5e6c61418e23" + integrity sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ== y18n@^5.0.5: version "5.0.8" @@ -4828,11 +4896,6 @@ yargs@16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" -yeast@0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" - integrity sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg== - yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" diff --git a/backend/package.json b/backend/package.json index 88146eb..3f5f7be 100644 --- a/backend/package.json +++ b/backend/package.json @@ -12,7 +12,7 @@ "postinstall": "yarn build" }, "engines": { - "node": ">=18" + "node": ">=20" }, "author": "", "license": "CC-BY-ND-4.0", diff --git a/backend/src/ConfigStorage.ts b/backend/src/ConfigStorage.ts index 0859c8f..d982415 100644 --- a/backend/src/ConfigStorage.ts +++ b/backend/src/ConfigStorage.ts @@ -2,14 +2,17 @@ import FileAsync from 'lowdb/adapters/FileAsync' import fs from 'fs-extra' import lowdb from 'lowdb' import path from 'path' -import { backendRpc } from '../../events' +import { Rpc } from '../../events/EventSystem/Rpc' import { storageClearEvent, storageLoadEvent, storageStoreEvent } from '../../events/StorageEvents' export default class ConfigStorage { private file: string private database: any - constructor(file: string) { + private rpc: Rpc + + constructor(file: string, rpc: Rpc) { this.file = file + this.rpc = rpc } private async getDb() { @@ -26,13 +29,13 @@ export default class ConfigStorage { } public async init() { - backendRpc.on(storageStoreEvent, async event => { + this.rpc.on(storageStoreEvent, async event => { const db = await this.getDb() await db.set(event.store, event.data).write() return }) - backendRpc.on(storageLoadEvent, async event => { + this.rpc.on(storageLoadEvent, async event => { const db = await this.getDb() const data = await db.get(event.store).value() return { @@ -41,7 +44,7 @@ export default class ConfigStorage { } }) - backendRpc.on(storageClearEvent, async event => { + this.rpc.on(storageClearEvent, async event => { const db = await this.getDb() const keys = await db.keys().value() for (const key of keys) { diff --git a/backend/src/index.ts b/backend/src/index.ts index 3a322ba..2a72ea4 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -4,15 +4,20 @@ import { AddMqttConnection, MqttMessage, addMqttConnectionEvent, - backendEvents, makeConnectionMessageEvent, makeConnectionStateEvent, makePublishEvent, removeConnection, } from '../../events' +import { EventBusInterface } from '../../events/EventSystem/EventBusInterface' export class ConnectionManager { private connections: { [s: string]: DataSource } = {} + private backendEvents: EventBusInterface + + constructor(backendEvents: EventBusInterface) { + this.backendEvents = backendEvents + } private handleConnectionRequest = (event: AddMqttConnection) => { const connectionId = event.id @@ -28,12 +33,12 @@ export class ConnectionManager { const connectionStateEvent = makeConnectionStateEvent(connectionId) connection.stateMachine.onUpdate.subscribe(state => { - backendEvents.emit(connectionStateEvent, state) + this.backendEvents.emit(connectionStateEvent, state) }) connection.connect(options) this.handleNewMessagesForConnection(connectionId, connection) - backendEvents.subscribe(makePublishEvent(connectionId), (msg: MqttMessage) => { + this.backendEvents.subscribe(makePublishEvent(connectionId), (msg: MqttMessage) => { this.connections[connectionId].publish(msg) }) } @@ -49,7 +54,7 @@ export class ConnectionManager { let decoded_payload = null decoded_payload = Base64Message.fromBuffer(buffer) - backendEvents.emit(messageEvent, { + this.backendEvents.emit(messageEvent, { topic, payload: decoded_payload, qos: packet.qos, @@ -60,8 +65,8 @@ export class ConnectionManager { } public manageConnections() { - backendEvents.subscribe(addMqttConnectionEvent, this.handleConnectionRequest) - backendEvents.subscribe(removeConnection, (connectionId: string) => { + this.backendEvents.subscribe(addMqttConnectionEvent, this.handleConnectionRequest) + this.backendEvents.subscribe(removeConnection, (connectionId: string) => { this.removeConnection(connectionId) }) } @@ -69,7 +74,7 @@ export class ConnectionManager { public removeConnection(connectionId: string) { const connection = this.connections[connectionId] if (connection) { - backendEvents.unsubscribeAll(makePublishEvent(connectionId)) + this.backendEvents.unsubscribeAll(makePublishEvent(connectionId)) connection.disconnect() delete this.connections[connectionId] connection.stateMachine.onUpdate.removeAllListeners() diff --git a/backend/yarn.lock b/backend/yarn.lock index be9da52..fb57ccd 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -2,96 +2,3 @@ # yarn lockfile v1 -"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" - integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== - -"@protobufjs/base64@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" - integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== - -"@protobufjs/codegen@^2.0.4": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" - integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== - -"@protobufjs/eventemitter@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" - integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== - -"@protobufjs/fetch@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" - integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== - dependencies: - "@protobufjs/aspromise" "^1.1.1" - "@protobufjs/inquire" "^1.1.0" - -"@protobufjs/float@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" - integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== - -"@protobufjs/inquire@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" - integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== - -"@protobufjs/path@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" - integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== - -"@protobufjs/pool@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" - integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== - -"@protobufjs/utf8@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" - integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== - -"@types/long@^4.0.1": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" - integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== - -"@types/node@>=13.7.0": - version "20.12.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.2.tgz#9facdd11102f38b21b4ebedd9d7999663343d72e" - integrity sha512-zQ0NYO87hyN6Xrclcqp7f8ZbXNbRfoGWNcMvHTPQp9UUrwI0mI7XBz+cu7/W6/VClYo2g63B0cjull/srU7LgQ== - dependencies: - undici-types "~5.26.4" - -long@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" - integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== - -protobufjs@^6.11.4: - version "6.11.4" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.4.tgz#29a412c38bf70d89e537b6d02d904a6f448173aa" - integrity sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw== - dependencies: - "@protobufjs/aspromise" "^1.1.2" - "@protobufjs/base64" "^1.1.2" - "@protobufjs/codegen" "^2.0.4" - "@protobufjs/eventemitter" "^1.1.0" - "@protobufjs/fetch" "^1.1.0" - "@protobufjs/float" "^1.0.2" - "@protobufjs/inquire" "^1.1.0" - "@protobufjs/path" "^1.1.2" - "@protobufjs/pool" "^1.1.0" - "@protobufjs/utf8" "^1.1.0" - "@types/long" "^4.0.1" - "@types/node" ">=13.7.0" - long "^4.0.0" - -undici-types@~5.26.4: - version "5.26.5" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" - integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== diff --git a/events/EventSystem/BrowserEventBus.ts b/events/EventSystem/BrowserEventBus.ts new file mode 100644 index 0000000..9f8ccd0 --- /dev/null +++ b/events/EventSystem/BrowserEventBus.ts @@ -0,0 +1,29 @@ +// Browser-specific EventBus implementation using Socket.io +import io from 'socket.io-client' +import { SocketIOClientEventBus } from './SocketIOClientEventBus' +import { Rpc } from './Rpc' + +// Get auth from sessionStorage or use empty (will show login dialog) +const username = typeof sessionStorage !== 'undefined' ? sessionStorage.getItem('mqtt-explorer-username') || '' : '' +const password = typeof sessionStorage !== 'undefined' ? sessionStorage.getItem('mqtt-explorer-password') || '' : '' + +// Connect to the server (same origin in browser mode) +const socket = io({ + auth: { + username, + password, + }, + reconnection: true, + reconnectionDelay: 1000, + reconnectionDelayMax: 5000, + reconnectionAttempts: Infinity, + transports: ['websocket', 'polling'], +}) + +export const rendererEvents = new SocketIOClientEventBus(socket) +export const rendererRpc = new Rpc(rendererEvents) + +// In browser mode, the backend is on the server +// For compatibility, export same instances (renderer communicates with server backend via socket) +export const backendEvents = rendererEvents +export const backendRpc = rendererRpc diff --git a/events/EventSystem/IpcMainEventBus.ts b/events/EventSystem/IpcMainEventBus.ts index 4eeffc0..e2d7f08 100644 --- a/events/EventSystem/IpcMainEventBus.ts +++ b/events/EventSystem/IpcMainEventBus.ts @@ -1,24 +1,54 @@ -import { IpcMain } from 'electron' +import { IpcMain, WebContents } from 'electron' import { Event } from '../Events' import { EventBusInterface } from './EventBusInterface' export class IpcMainEventBus implements EventBusInterface { private ipc: IpcMain - private client: any + private clients: Map = new Map() // webContentsId -> WebContents + private connectionOwners: Map = new Map() // connectionId -> webContentsId + private currentClient: WebContents | undefined + constructor(ipc: IpcMain) { this.ipc = ipc } public subscribe(subscribeEvent: Event, callback: (msg: MessageType) => void) { - console.log('subscribing', subscribeEvent.topic) this.ipc.on(subscribeEvent.topic, (event: any, arg: any) => { - this.client = event.sender + const sender = event.sender as WebContents + this.currentClient = sender + + // Track the client (O(1) operation) + if (!this.clients.has(sender.id)) { + this.clients.set(sender.id, sender) + + // Clean up when window is closed + sender.once('destroyed', () => { + this.clients.delete(sender.id) + + // Clean up owned connections + for (const [connectionId, webContentsId] of this.connectionOwners.entries()) { + if (webContentsId === sender.id) { + this.connectionOwners.delete(connectionId) + } + } + }) + } + + // Track connection ownership + if (subscribeEvent.topic === 'connection/add/mqtt' && arg?.id) { + this.connectionOwners.set(arg.id, sender.id) + } + + // Remove connection ownership + if (subscribeEvent.topic === 'connection/remove' && typeof arg === 'string') { + this.connectionOwners.delete(arg) + } + callback(arg) }) } public unsubscribeAll(event: Event) { - console.log('unsubscribeAll', event.topic) this.ipc.removeAllListeners(event.topic) } @@ -27,8 +57,44 @@ export class IpcMainEventBus implements EventBusInterface { } public emit(event: Event, msg: MessageType) { - if (!this.client.isDestroyed()) { - this.client.send(event.topic, msg) + const topic = event.topic + + // RPC responses go only to the requesting client + if (topic.includes('/response/')) { + if (this.currentClient && !this.currentClient.isDestroyed()) { + this.currentClient.send(topic, msg) + } + return } + + // Connection-specific events - optimized with early pattern match + if (topic.startsWith('conn/')) { + const parts = topic.split('/') + let connectionId: string | undefined + + if (parts.length === 2) { + connectionId = parts[1] + } else if (parts.length === 3 && (parts[1] === 'state' || parts[1] === 'publish')) { + connectionId = parts[2] + } + + if (connectionId) { + const ownerWebContentsId = this.connectionOwners.get(connectionId) + if (ownerWebContentsId !== undefined) { + const ownerClient = this.clients.get(ownerWebContentsId) + if (ownerClient && !ownerClient.isDestroyed()) { + ownerClient.send(topic, msg) + return + } + } + } + } + + // All other events go to all clients + this.clients.forEach(client => { + if (!client.isDestroyed()) { + client.send(topic, msg) + } + }) } } diff --git a/events/EventSystem/IpcMainEventBusV2.ts b/events/EventSystem/IpcMainEventBusV2.ts new file mode 100644 index 0000000..56842cf --- /dev/null +++ b/events/EventSystem/IpcMainEventBusV2.ts @@ -0,0 +1,61 @@ +import { IpcMain } from 'electron' +import { Event } from '../Events' +import { EventBusInterface } from './EventBusInterface' +import { MessageCodec } from './MessageCodec' + +/** + * Enhanced IPC Main Event Bus with Protobuf support + * + * This version uses binary serialization for better performance + * while maintaining backward compatibility with the old JSON-based system. + */ +export class IpcMainEventBusV2 implements EventBusInterface { + private ipc: IpcMain + private client: any + private useBinary: boolean + + constructor(ipc: IpcMain, useBinary: boolean = true) { + this.ipc = ipc + this.useBinary = useBinary + } + + public subscribe(subscribeEvent: Event, callback: (msg: MessageType) => void) { + console.log('subscribing', subscribeEvent.topic, this.useBinary ? '(binary)' : '(json)') + this.ipc.on(subscribeEvent.topic, (event: any, arg: any) => { + this.client = event.sender + + if (this.useBinary && arg instanceof Uint8Array) { + // Binary message - decode it + const { data } = MessageCodec.decodeWithPayload(arg) + callback(data) + } else { + // Regular JSON message + callback(arg) + } + }) + } + + public unsubscribeAll(event: Event) { + console.log('unsubscribeAll', event.topic) + this.ipc.removeAllListeners(event.topic) + } + + public unsubscribe(event: Event, callback: any) { + throw new Error('Not implemented') // Todo: implement + } + + public emit(event: Event, msg: MessageType) { + if (!this.client || this.client.isDestroyed()) { + return + } + + if (this.useBinary) { + // Encode as binary + const binary = MessageCodec.encode(event.topic, msg) + this.client.send(event.topic, binary) + } else { + // Send as JSON (legacy) + this.client.send(event.topic, msg) + } + } +} diff --git a/events/EventSystem/IpcRendererEventBusV2.ts b/events/EventSystem/IpcRendererEventBusV2.ts new file mode 100644 index 0000000..d0dd1e8 --- /dev/null +++ b/events/EventSystem/IpcRendererEventBusV2.ts @@ -0,0 +1,65 @@ +import { CallbackStore } from './CallbackStore' +import { EventBusInterface } from './EventBusInterface' +import { Event } from '../Events' +import { IpcRenderer } from 'electron' +import { MessageCodec } from './MessageCodec' + +/** + * Enhanced IPC Renderer Event Bus with Protobuf support + * + * This version uses binary serialization for better performance + * while maintaining backward compatibility with the old JSON-based system. + */ +export class IpcRendererEventBusV2 implements EventBusInterface { + private ipc: IpcRenderer + private callbacks: Array = [] + private useBinary: boolean + + constructor(ipc: IpcRenderer, useBinary: boolean = true) { + this.ipc = ipc + this.useBinary = useBinary + } + + public subscribe(event: Event, callback: (msg: MessageType) => void) { + const wrappedCallback = (_: any, arg: any) => { + if (this.useBinary && arg instanceof Uint8Array) { + // Binary message - decode it + const { data } = MessageCodec.decodeWithPayload(arg) + callback(data) + } else { + // Regular JSON message + callback(arg) + } + } + console.log('subscribing', event.topic, this.useBinary ? '(binary)' : '(json)') + this.ipc.on(event.topic, wrappedCallback) + this.callbacks.push({ + callback, + wrappedCallback, + }) + } + + public unsubscribeAll(event: Event) { + this.ipc.removeAllListeners(event.topic) + } + + public unsubscribe(event: Event, callback: any) { + const item = this.callbacks.find(store => store.callback === callback) + if (!item) { + return + } + this.ipc.removeListener(event.topic, item.wrappedCallback) + this.callbacks = this.callbacks.filter(a => a !== item) + } + + public emit(event: Event, msg: MessageType) { + if (this.useBinary) { + // Encode as binary + const binary = MessageCodec.encode(event.topic, msg) + this.ipc.send(event.topic, binary) + } else { + // Send as JSON (legacy) + this.ipc.send(event.topic, msg) + } + } +} diff --git a/events/EventSystem/MessageCodec.ts b/events/EventSystem/MessageCodec.ts new file mode 100644 index 0000000..cd76f7f --- /dev/null +++ b/events/EventSystem/MessageCodec.ts @@ -0,0 +1,74 @@ +/** + * Binary Message Codec using Protobuf + * + * This provides efficient binary serialization for IPC messages, + * avoiding JSON stringify/parse overhead. + */ + +import * as protobuf from 'protobufjs' + +// Define message schema +const messageSchema = { + nested: { + mqtt: { + nested: { + Envelope: { + fields: { + topic: { type: 'string', id: 1 }, + payload: { type: 'bytes', id: 2 }, + }, + }, + }, + }, + }, +} + +// Create root from JSON schema +const root = protobuf.Root.fromJSON(messageSchema) +const Envelope = root.lookupType('mqtt.Envelope') + +export interface BinaryMessage { + topic: string + payload: Uint8Array +} + +export class MessageCodec { + /** + * Encode a message to binary format + */ + public static encode(topic: string, data: any): Uint8Array { + // Serialize the payload to JSON, then to bytes + const jsonString = JSON.stringify(data) + const payloadBytes = new TextEncoder().encode(jsonString) + + // Create protobuf envelope + const message = Envelope.create({ + topic, + payload: payloadBytes, + }) + + // Encode to binary + return Envelope.encode(message).finish() + } + + /** + * Decode a binary message + */ + public static decode(binary: Uint8Array): BinaryMessage { + const message = Envelope.decode(binary) as any + return { + topic: message.topic, + payload: message.payload, + } + } + + /** + * Decode and parse payload as JSON + */ + public static decodeWithPayload(binary: Uint8Array): { topic: string; data: T } { + const { topic, payload } = this.decode(binary) + const jsonString = new TextDecoder().decode(payload) + const data = JSON.parse(jsonString) + return { topic, data } + } +} diff --git a/events/EventSystem/SocketIOClientEventBus.ts b/events/EventSystem/SocketIOClientEventBus.ts new file mode 100644 index 0000000..8513bae --- /dev/null +++ b/events/EventSystem/SocketIOClientEventBus.ts @@ -0,0 +1,42 @@ +import { Socket } from 'socket.io-client' +import { CallbackStore } from './CallbackStore' +import { EventBusInterface } from './EventBusInterface' +import { Event } from '../Events' + +export class SocketIOClientEventBus implements EventBusInterface { + private socket: Socket + private callbacks: Array = [] + + constructor(socket: Socket) { + this.socket = socket + } + + public subscribe(event: Event, callback: (msg: MessageType) => void) { + const wrappedCallback = (arg: any) => { + callback(arg) + } + console.log('subscribing', event.topic) + this.socket.on(event.topic, wrappedCallback) + this.callbacks.push({ + callback, + wrappedCallback, + }) + } + + public unsubscribeAll(event: Event) { + this.socket.removeAllListeners(event.topic) + } + + public unsubscribe(event: Event, callback: any) { + const item = this.callbacks.find(store => store.callback === callback) + if (!item) { + return + } + this.socket.off(event.topic, item.wrappedCallback) + this.callbacks = this.callbacks.filter(a => a !== item) + } + + public emit(event: Event, msg: MessageType) { + this.socket.emit(event.topic, msg) + } +} diff --git a/events/EventSystem/SocketIOServerEventBus.ts b/events/EventSystem/SocketIOServerEventBus.ts new file mode 100644 index 0000000..328ae6f --- /dev/null +++ b/events/EventSystem/SocketIOServerEventBus.ts @@ -0,0 +1,274 @@ +import { Server as SocketIOServer, Socket } from 'socket.io' +import { Event } from '../Events' +import { EventBusInterface } from './EventBusInterface' +import Debug from 'debug' + +const debug = Debug('mqtt-explorer:socketio') +const debugConnect = Debug('mqtt-explorer:socketio:connect') +const debugDisconnect = Debug('mqtt-explorer:socketio:disconnect') +const debugSubscriptions = Debug('mqtt-explorer:socketio:subscriptions') +const debugConnections = Debug('mqtt-explorer:socketio:connections') +const debugEmit = Debug('mqtt-explorer:socketio:emit') + +interface SocketSubscription { + topic: string + handler: (arg: any) => void +} + +export class SocketIOServerEventBus implements EventBusInterface { + private io: SocketIOServer + private clients: Map = new Map() // socketId -> Socket + + // Global handlers that apply to ALL sockets (like RPC endpoints) + private globalHandlers: Map void> = new Map() + + // Per-socket subscriptions for cleanup + private socketSubscriptions: Map = new Map() + + // Track which socket is currently processing a request + private currentSocket: Socket | undefined + + // Map connectionId -> socketId to route messages to correct client + private connectionOwners: Map = new Map() + + // Track which connections to close when a socket disconnects + private socketConnections: Map> = new Map() + + constructor(io: SocketIOServer) { + this.io = io + + // Register connection handler once + this.io.on('connection', socket => { + debugConnect('Client connected: %s', socket.id) + this.clients.set(socket.id, socket) + this.socketSubscriptions.set(socket.id, []) + this.socketConnections.set(socket.id, new Set()) + + // Register all global handlers on this socket + this.globalHandlers.forEach((handler, topic) => { + this.registerHandlerOnSocket(socket, topic, handler) + }) + + // Log connection metrics + this.logConnectionMetrics('connect', socket.id) + + socket.on('disconnect', () => { + debugDisconnect('Client disconnected: %s', socket.id) + this.cleanupSocket(socket) + this.clients.delete(socket.id) + this.logConnectionMetrics('disconnect', socket.id) + }) + }) + } + + private logConnectionMetrics(event: 'connect' | 'disconnect', socketId: string) { + const totalClients = this.clients.size + const totalSubscriptions = Array.from(this.socketSubscriptions.values()).reduce((sum, subs) => sum + subs.length, 0) + const totalConnections = this.connectionOwners.size + const socketSubs = this.socketSubscriptions.get(socketId)?.length || 0 + const socketConns = this.socketConnections.get(socketId)?.size || 0 + + debug( + '[%s] clients=%d subscriptions=%d mqttConns=%d | socket[%s]: subs=%d conns=%d', + event, + totalClients, + totalSubscriptions, + + totalConnections, + socketId.substring(0, 8), + socketSubs, + socketConns + ) + + debugSubscriptions( + 'Total subscriptions: %d across %d sockets (avg: %d per socket)', + totalSubscriptions, + totalClients, + totalClients > 0 ? Math.round(totalSubscriptions / totalClients) : 0 + ) + + debugConnections( + 'MQTT connections: %d total, %d owned by socket %s', + totalConnections, + socketConns, + socketId.substring(0, 8) + ) + } + + private registerHandlerOnSocket(socket: Socket, topic: string, handler: (socket: Socket, arg: any) => void) { + const wrappedHandler = (arg: any) => { + this.currentSocket = socket + + // Track connection ownership when a connection is added + if (topic === 'connection/add/mqtt' && arg?.id) { + this.connectionOwners.set(arg.id, socket.id) + const socketConns = this.socketConnections.get(socket.id) + if (socketConns) { + socketConns.add(arg.id) + } + debugConnections( + 'Connection %s owned by socket %s (total: %d)', + arg.id, + socket.id.substring(0, 8), + socketConns?.size || 0 + ) + } + + // Remove connection ownership when a connection is removed + if (topic === 'connection/remove' && typeof arg === 'string') { + this.connectionOwners.delete(arg) + const socketConns = this.socketConnections.get(socket.id) + if (socketConns) { + socketConns.delete(arg) + } + debugConnections( + 'Connection %s removed (socket %s remaining: %d)', + arg, + socket.id.substring(0, 8), + socketConns?.size || 0 + ) + } + + handler(socket, arg) + } + + socket.on(topic, wrappedHandler) + + // Track subscription for cleanup + const subscriptions = this.socketSubscriptions.get(socket.id) + if (subscriptions) { + subscriptions.push({ topic, handler: wrappedHandler }) + } + } + + private cleanupSocket(socket: Socket) { + debugDisconnect('Cleaning up socket %s', socket.id) + + // Remove all event listeners for this socket + const subscriptions = this.socketSubscriptions.get(socket.id) + if (subscriptions) { + subscriptions.forEach(({ topic, handler }) => { + socket.off(topic, handler) + }) + this.socketSubscriptions.delete(socket.id) + debugSubscriptions('Removed %d subscriptions for socket %s', subscriptions.length, socket.id.substring(0, 8)) + } + + // Close all MQTT connections owned by this socket + const ownedConnections = this.socketConnections.get(socket.id) + if (ownedConnections && ownedConnections.size > 0) { + debugConnections( + 'Socket %s owned %d connections, requesting cleanup', + socket.id.substring(0, 8), + ownedConnections.size + ) + + // Emit connection/remove for each owned connection + // This will be handled by ConnectionManager to actually close the MQTT connection + ownedConnections.forEach(connectionId => { + debugConnections('Auto-closing connection %s (owner disconnected)', connectionId) + // Simulate a remove request from this socket + const removeHandler = this.globalHandlers.get('connection/remove') + if (removeHandler) { + this.currentSocket = socket + removeHandler(socket, connectionId) + } + this.connectionOwners.delete(connectionId) + }) + + this.socketConnections.delete(socket.id) + } + + // Remove from clients set + this.clients.delete(socket.id) + + // Clear current socket if it was this one + if (this.currentSocket === socket) { + this.currentSocket = undefined + } + + debugDisconnect('Cleanup complete for socket %s', socket.id.substring(0, 8)) + } + + public subscribe(subscribeEvent: Event, callback: (msg: MessageType) => void) { + const handler = (socket: Socket, arg: any) => { + this.currentSocket = socket + callback(arg) + } + + // Store as global handler + this.globalHandlers.set(subscribeEvent.topic, handler) + + // Register on all currently connected clients + this.clients.forEach(client => { + this.registerHandlerOnSocket(client, subscribeEvent.topic, handler) + }) + } + + public unsubscribeAll(event: Event) { + // Remove from global handlers + this.globalHandlers.delete(event.topic) + + // Remove from all sockets + this.clients.forEach(client => { + const subscriptions = this.socketSubscriptions.get(client.id) + if (subscriptions) { + const toRemove = subscriptions.filter(s => s.topic === event.topic) + toRemove.forEach(({ handler }) => { + client.off(event.topic, handler) + }) + + // Update subscriptions list + this.socketSubscriptions.set( + client.id, + subscriptions.filter(s => s.topic !== event.topic) + ) + } + }) + } + + public unsubscribe(event: Event, callback: any) { + throw new Error('Not implemented - use unsubscribeAll instead') + } + + public emit(event: Event, msg: MessageType) { + const topic = event.topic + + // Check if this is an RPC response (contains /response/ in topic) + if (topic.includes('/response/')) { + if (this.currentSocket && this.currentSocket.connected) { + this.currentSocket.emit(topic, msg) + } + return + } + + // Check if this is a connection-specific event - optimized with early pattern match + // Patterns: conn/${connectionId}, conn/state/${connectionId}, conn/publish/${connectionId} + if (topic.startsWith('conn/')) { + const parts = topic.split('/') + let connectionId: string | undefined + + if (parts.length === 2) { + // conn/${connectionId} + connectionId = parts[1] + } else if (parts.length === 3 && (parts[1] === 'state' || parts[1] === 'publish')) { + // conn/state/${connectionId} or conn/publish/${connectionId} + connectionId = parts[2] + } + + if (connectionId) { + const ownerSocketId = this.connectionOwners.get(connectionId) + if (ownerSocketId) { + const ownerSocket = this.clients.get(ownerSocketId) + if (ownerSocket && ownerSocket.connected) { + ownerSocket.emit(topic, msg) + return + } + } + } + } + + // All other events go to all clients + this.io.emit(topic, msg) + } +} diff --git a/events/EventsV2.ts b/events/EventsV2.ts new file mode 100644 index 0000000..048784d --- /dev/null +++ b/events/EventsV2.ts @@ -0,0 +1,71 @@ +/** + * Simplified Event System V2 + * + * This provides a simpler, more type-safe way to define and use events. + * Instead of factory functions like makeConnectionStateEvent(id), + * you can now use: Events.connectionState(id) + */ + +import { Base64MessageDTO } from '../backend/src/Model/Base64Message' +import { DataSourceState, MqttOptions } from '../backend/src/DataSource' +import { UpdateInfo } from 'builder-util-runtime' +import { RpcEvent } from './EventSystem/Rpc' + +export type EventV2 = { + topic: string +} + +// Simple event definitions (no parameters) +export const Events = { + // Connection management + addMqttConnection: { topic: 'connection/add/mqtt' } as EventV2, + removeConnection: { topic: 'connection/remove' } as EventV2, + updateAvailable: { topic: 'app/update/available' } as EventV2, + + // Parameterized events (for connection-specific events) + connectionState: (connectionId: string) => ({ topic: `conn/state/${connectionId}` }) as EventV2, + connectionMessage: (connectionId: string) => ({ topic: `conn/${connectionId}` }) as EventV2, + publish: (connectionId: string) => ({ topic: `conn/publish/${connectionId}` }) as EventV2, +} + +// RPC Events - type-safe request/response patterns +export const RpcEvents = { + getAppVersion: { topic: 'getAppVersion' } as RpcEvent, + writeToFile: { topic: 'writeFile' } as RpcEvent<{ filePath: string; data: string; encoding?: string }, void>, + readFromFile: { topic: 'readFromFile' } as RpcEvent<{ filePath: string; encoding?: string }, Buffer>, + openDialog: { topic: 'openDialog' } as RpcEvent, + saveDialog: { topic: 'saveDialog' } as RpcEvent, + uploadCertificate: { topic: 'uploadCertificate' } as RpcEvent, +} + +// Type definitions +export interface AddMqttConnectionV2 { + id: string + options: MqttOptions +} + +export interface MqttMessageV2 { + topic: string + payload: Base64MessageDTO | null + qos: 0 | 1 | 2 + retain: boolean + messageId: number | undefined +} + +export interface CertificateUploadRequest { + filename: string + data: string // base64 encoded +} + +export interface CertificateUploadResponse { + name: string + data: string // base64 encoded +} + +// Electron dialog types (re-exported for convenience) +import { OpenDialogOptions, OpenDialogReturnValue, SaveDialogOptions, SaveDialogReturnValue } from 'electron' + +export type OpenDialogOptionsV2 = OpenDialogOptions +export type OpenDialogReturnValueV2 = OpenDialogReturnValue +export type SaveDialogOptionsV2 = SaveDialogOptions +export type SaveDialogReturnValueV2 = SaveDialogReturnValue diff --git a/events/OpenDialogRequest.ts b/events/OpenDialogRequest.ts index 44ef9d8..aba391e 100644 --- a/events/OpenDialogRequest.ts +++ b/events/OpenDialogRequest.ts @@ -1,6 +1,7 @@ import { OpenDialogOptions, OpenDialogReturnValue, SaveDialogOptions, SaveDialogReturnValue } from 'electron' import { RpcEvent } from './EventSystem/Rpc' +// Legacy functions - use RpcEvents from EventsV2.ts for new code export function makeOpenDialogRpc(): RpcEvent { return { topic: 'openDialog', diff --git a/events/index.ts b/events/index.ts index 02c1041..81f22d9 100644 --- a/events/index.ts +++ b/events/index.ts @@ -1,4 +1,5 @@ export * from './Events' +export * from './EventsV2' export * from './EventSystem/EventDispatcher' export * from './EventSystem/EventBus' export * from './EventSystem/EventBusInterface' diff --git a/package.json b/package.json index c9fd4a9..7e2bbd7 100644 --- a/package.json +++ b/package.json @@ -4,11 +4,12 @@ "description": "Explore your message queues", "main": "dist/src/electron.js", "engines": { - "node": ">=18" + "node": ">=20" }, "private": "true", "scripts": { "start": "electron .", + "start:server": "npx tsc && node dist/src/server.js", "test": "yarn test:app && yarn test:backend", "test:app": "cd app && yarn test", "test:backend": "cd backend && yarn test", @@ -18,6 +19,9 @@ "dev": "npm-run-all --parallel dev:*", "dev:app": "cd app && npm run dev", "dev:electron": "tsc && electron . --development", + "dev:server": "npm-run-all --parallel dev:server:*", + "dev:server:app": "cd app && npx webpack-dev-server --config webpack.browser.config.js --mode development --progress", + "dev:server:backend": "tsc && node dist/src/server.js", "lint": "npm-run-all --parallel lint:prettier lint:tslint lint:spellcheck", "lint:fix": "npm-run-all lint:tslint:fix lint:prettier:fix", "lint:prettier": "prettier --check \"**/*.ts{x,}\"", @@ -26,6 +30,7 @@ "lint:tslint:fix": "tslint -p ./ --fix", "lint:spellcheck": "cspell -e ./build -e \"node_modules\" \"**/*.ts{x,}\"", "build": "tsc && cd app && yarn run build && cd ..", + "build:server": "npx tsc && cd app && npx webpack --config webpack.browser.config.js --mode production && cd ..", "prepare-release": "ts-node scripts/prepare-release.ts", "package": "ts-node package.ts", "ui-test": "./scripts/uiTests.sh", @@ -84,15 +89,18 @@ "@semantic-release/changelog": "^6.0.3", "@semantic-release/commit-analyzer": "^12.0.0", "@semantic-release/git": "^10.0.1", + "@types/bcryptjs": "^3.0.0", "@types/chai": "^4.1.7", + "@types/express": "^5.0.6", "@types/fs-extra": "8", "@types/lowdb": "^1.0.6", "@types/mime": "^2.0.0", "@types/mocha": "^7.0.2", "@types/mustache": "4", - "@types/node": "^12.6.8", + "@types/node": "^25.0.3", "@types/semver": "7", "@types/sha1": "^1.1.1", + "@types/socket.io": "^3.0.2", "@types/uuid": "^8.3.4", "builder-util-runtime": "^9", "chai": "^4.2.0", @@ -120,18 +128,23 @@ "dependencies": { "about-window": "^1.12.1", "axios": "^0.28.0", + "bcryptjs": "^3.0.3", + "debug": "^4.3.4", "dot-prop": "^5.0.0", "electron-log": "4.4.6", "electron-updater": "^4.6", + "express": "^5.2.1", "fs-extra": "9", "js-base64": "^3.7.2", "json-to-ast": "^2.1.0", "lowdb": "^1.0.0", "mime": "^2.4.4", "mqtt": "^4.3.6", + "protobufjs": "^8.0.0", "sha1": "^1.1.1", + "socket.io": "^4.8.1", "sparkplug-payload": "^1.0.3", - "uuid": "^8.3.2", + "uuid": "^13.0.0", "yarn-run-all": "^3.1.1" } } diff --git a/src/AuthManager.ts b/src/AuthManager.ts new file mode 100644 index 0000000..aa818e7 --- /dev/null +++ b/src/AuthManager.ts @@ -0,0 +1,94 @@ +import * as fs from 'fs' +import * as path from 'path' +import * as bcrypt from 'bcryptjs' +import { v4 as uuidv4 } from 'uuid' + +export interface Credentials { + username: string + passwordHash: string +} + +export class AuthManager { + private credentialsPath: string + private credentials: Credentials | undefined + + constructor(credentialsPath: string) { + this.credentialsPath = credentialsPath + } + + public async initialize(): Promise { + // Try to get credentials from environment variables + const envUsername = process.env.MQTT_EXPLORER_USERNAME + const envPassword = process.env.MQTT_EXPLORER_PASSWORD + + if (envUsername && envPassword) { + // Use environment credentials + console.log('Using credentials from environment variables') + console.log('Username:', envUsername) + this.credentials = { + username: envUsername, + passwordHash: await bcrypt.hash(envPassword, 10), + } + return + } + + // Try to load from file + if (fs.existsSync(this.credentialsPath)) { + try { + const data = fs.readFileSync(this.credentialsPath, 'utf8') + this.credentials = JSON.parse(data) + console.log('Loaded credentials from', this.credentialsPath) + console.log('Username:', this.credentials!.username) + return + } catch (error) { + console.error('Failed to load credentials from file:', error) + } + } + + // Generate new credentials + const username = `user-${uuidv4().substring(0, 8)}` + const password = uuidv4() + + console.log('='.repeat(60)) + console.log('Generated new credentials:') + console.log('Username:', username) + console.log('Password:', password) + console.log('='.repeat(60)) + console.log('Please save these credentials. They will be persisted to:') + console.log(this.credentialsPath) + console.log('='.repeat(60)) + + this.credentials = { + username, + passwordHash: await bcrypt.hash(password, 10), + } + + // Save to file + try { + const dir = path.dirname(this.credentialsPath) + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }) + } + fs.writeFileSync(this.credentialsPath, JSON.stringify(this.credentials, null, 2)) + console.log('Credentials saved successfully') + } catch (error) { + console.error('Failed to save credentials:', error) + } + } + + public async verifyCredentials(username: string, password: string): Promise { + if (!this.credentials) { + return false + } + + if (username !== this.credentials.username) { + return false + } + + return bcrypt.compare(password, this.credentials.passwordHash) + } + + public getUsername(): string | undefined { + return this.credentials?.username + } +} diff --git a/src/electron.ts b/src/electron.ts index 81fed2e..f7d853d 100644 --- a/src/electron.ts +++ b/src/electron.ts @@ -19,7 +19,8 @@ import { import { shouldAutoUpdate, handleAutoUpdate } from './autoUpdater' import { registerCrashReporter } from './registerCrashReporter' import { makeOpenDialogRpc, makeSaveDialogRpc } from '../events/OpenDialogRequest' -import { backendRpc, getAppVersion, writeToFile, readFromFile } from '../events' +import { backendRpc, backendEvents, getAppVersion, writeToFile, readFromFile } from '../events' +import { RpcEvents } from '../events/EventsV2' registerCrashReporter() @@ -49,21 +50,35 @@ app.whenReady().then(() => { backendRpc.on(getAppVersion, async () => app.getVersion()) backendRpc.on(writeToFile, async ({ filePath, data, encoding }) => { - await fsPromise.writeFile(filePath, Buffer.from(data, 'base64'), { encoding }) + await fsPromise.writeFile(filePath, Buffer.from(data, 'base64'), { encoding: encoding as BufferEncoding }) }) backendRpc.on(readFromFile, async ({ filePath, encoding }) => { - return fsPromise.readFile(filePath, { encoding }) + if (encoding) { + const content = await fsPromise.readFile(filePath, { encoding: encoding as BufferEncoding }) + return Buffer.from(content) + } + return fsPromise.readFile(filePath) + }) + + // Certificate upload handler - works for both Electron and browser mode via IPC + backendRpc.on(RpcEvents.uploadCertificate, async ({ filename, data }) => { + // In Electron, we just return the data as-is since it's already read + // The client will use it directly + return { + name: filename, + data, + } }) }) autoUpdater.logger = log log.info('App starting...') -const connectionManager = new ConnectionManager() +const connectionManager = new ConnectionManager(backendEvents) connectionManager.manageConnections() -const configStorage = new ConfigStorage(path.join(app.getPath('userData'), 'settings.json')) +const configStorage = new ConfigStorage(path.join(app.getPath('userData'), 'settings.json'), backendRpc) configStorage.init() // Keep a global reference of the window object, if you don't, the window will diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..a9b86ba --- /dev/null +++ b/src/server.ts @@ -0,0 +1,177 @@ +import express from 'express' +import * as http from 'http' +import * as path from 'path' +import { Server } from 'socket.io' +import { promises as fsPromise } from 'fs' +import { Request, Response } from 'express' +import { AuthManager } from './AuthManager' +import { ConnectionManager } from '../backend/src/index' +import ConfigStorage from '../backend/src/ConfigStorage' +import { SocketIOServerEventBus } from '../events/EventSystem/SocketIOServerEventBus' +import { Rpc } from '../events/EventSystem/Rpc' +import { makeOpenDialogRpc, makeSaveDialogRpc } from '../events/OpenDialogRequest' +import { getAppVersion, writeToFile, readFromFile } from '../events' +import { RpcEvents } from '../events/EventsV2' + +const PORT = process.env.PORT || 3000 +const CREDENTIALS_PATH = path.join(process.cwd(), 'data', 'credentials.json') + +async function startServer() { + // Initialize authentication + const authManager = new AuthManager(CREDENTIALS_PATH) + await authManager.initialize() + + // Create Express app + const app = express() + const server = http.createServer(app) + const io = new Server(server, { + cors: { + origin: '*', + methods: ['GET', 'POST'], + }, + allowEIO3: true, // Allow Engine.IO v3 clients (backwards compatibility) + transports: ['websocket', 'polling'], // Support both transports + pingTimeout: 60000, // Increase ping timeout + pingInterval: 25000, // Ping interval + }) + + // Authentication middleware for Socket.io + io.use(async (socket, next) => { + const { username, password } = socket.handshake.auth + + if (!username || !password) { + return next(new Error('Authentication required')) + } + + const isValid = await authManager.verifyCredentials(username, password) + if (!isValid) { + return next(new Error('Invalid credentials')) + } + + console.log('Client authenticated:', username) + next() + }) + + // Initialize backend event bus with Socket.io + const backendEvents = new SocketIOServerEventBus(io) + const backendRpc = new Rpc(backendEvents) + + // Initialize connection manager + const connectionManager = new ConnectionManager(backendEvents) + connectionManager.manageConnections() + + // Initialize config storage + const configStorage = new ConfigStorage(path.join(process.cwd(), 'data', 'settings.json'), backendRpc) + configStorage.init() + + // Setup RPC handlers for file operations + backendRpc.on(makeOpenDialogRpc(), async request => { + // In browser mode, file selection is handled client-side via upload + // Return empty result as this will be handled differently + return { canceled: true, filePaths: [] } + }) + + backendRpc.on(makeSaveDialogRpc(), async request => { + // In browser mode, file saving is handled client-side via download + return { canceled: true, filePath: undefined } + }) + + backendRpc.on(getAppVersion, async () => { + // Return version from package.json + try { + const packageJsonPath = path.join(__dirname, '..', '..', 'package.json') + const packageJsonData = await fsPromise.readFile(packageJsonPath, 'utf8') + const packageJson = JSON.parse(packageJsonData) + return packageJson.version + } catch (e) { + return '0.0.0' + } + }) + + backendRpc.on(writeToFile, async ({ filePath, data, encoding }) => { + // In browser mode, we store files in the server's data directory + const dataDir = path.join(process.cwd(), 'data', 'uploads') + const safePath = path.join(dataDir, path.basename(filePath)) + + try { + await fsPromise.mkdir(dataDir, { recursive: true }) + if (encoding) { + await fsPromise.writeFile(safePath, Buffer.from(data, 'base64'), { encoding: encoding as BufferEncoding }) + } else { + await fsPromise.writeFile(safePath, Buffer.from(data, 'base64')) + } + } catch (error) { + console.error('Error writing file:', error) + throw error + } + }) + + backendRpc.on(readFromFile, async ({ filePath, encoding }) => { + // In browser mode, files are read from the server's data directory + const dataDir = path.join(process.cwd(), 'data', 'uploads') + const safePath = path.join(dataDir, path.basename(filePath)) + + try { + if (encoding) { + const content = await fsPromise.readFile(safePath, { encoding: encoding as BufferEncoding }) + return Buffer.from(content) + } + return await fsPromise.readFile(safePath) + } catch (error) { + console.error('Error reading file:', error) + throw error + } + }) + + // Certificate upload handler - via IPC for consistency + backendRpc.on(RpcEvents.uploadCertificate, async ({ filename, data }) => { + // Store certificate on server for browser mode + const dataDir = path.join(process.cwd(), 'data', 'certificates') + await fsPromise.mkdir(dataDir, { recursive: true }) + + const safePath = path.join(dataDir, path.basename(filename)) + await fsPromise.writeFile(safePath, Buffer.from(data, 'base64')) + + console.log('Certificate uploaded:', filename) + + // Return the certificate data for client to use + return { + name: filename, + data, + } + }) + + // Serve static files + app.use(express.static(path.join(__dirname, '..', '..', 'app', 'build'))) + + // Serve index.html for all other routes (SPA) + app.use((req: Request, res: Response) => { + res.sendFile(path.join(__dirname, '..', '..', 'app', 'index.html')) + }) + + // Start server + server.listen(PORT, () => { + console.log('='.repeat(60)) + console.log(`MQTT Explorer server running on http://localhost:${PORT}`) + console.log('='.repeat(60)) + }) + + // Handle graceful shutdown + process.on('SIGTERM' as any, () => { + console.log('SIGTERM received, closing connections...') + connectionManager.closeAllConnections() + server.close() + }) + + process.on('SIGINT' as any, () => { + console.log('SIGINT received, closing connections...') + connectionManager.closeAllConnections() + server.close() + process.exit(0) + }) +} + +startServer().catch(error => { + console.error('Failed to start server:', error) + process.exit(1) +}) diff --git a/src/spec/demoVideo.ts b/src/spec/demoVideo.ts index 9845eff..2633b20 100644 --- a/src/spec/demoVideo.ts +++ b/src/spec/demoVideo.ts @@ -32,7 +32,7 @@ const cleanUp = async (scenes: SceneBuilder, electronApp: ElectronApplication) = await electronApp.close() } -process.on('unhandledRejection', (error: Error | any) => { +process.on('unhandledRejection' as any, (error: Error | any) => { console.error('unhandledRejection', error.message, error.stack) process.exit(1) }) diff --git a/src/spec/leakTest.ts b/src/spec/leakTest.ts index b44bbcb..80c0491 100644 --- a/src/spec/leakTest.ts +++ b/src/spec/leakTest.ts @@ -7,7 +7,7 @@ import { clearSearch, searchTree } from './scenarios/searchTree' import { connectTo } from './scenarios/connect' import { reconnect } from './scenarios/reconnect' -process.on('unhandledRejection', (error: Error | any) => { +process.on('unhandledRejection' as any, (error: Error | any) => { console.error('unhandledRejection', error.message, error.stack) process.exit(1) }) diff --git a/tsconfig.json b/tsconfig.json index 425981f..8b5c552 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,6 +17,8 @@ }, "include": [ "src/electron.ts", + "src/server.ts", + "src/AuthManager.ts", "src/spec/electron.ts", "src/spec/demoVideo.ts", "src/spec/leakTest.ts", diff --git a/yarn.lock b/yarn.lock index d5b86db..d98b26e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1300,6 +1300,11 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz#abb11d99aeb6d27f1b563c38147a72d50058e339" integrity sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ== +"@socket.io/component-emitter@~3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz#821f8442f4175d8f0467b9daf26e3a18e2d02af2" + integrity sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA== + "@szmarczak/http-timer@^4.0.5": version "4.0.6" resolved "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz" @@ -1345,6 +1350,21 @@ "@tufjs/canonical-json" "2.0.0" minimatch "^9.0.4" +"@types/bcryptjs@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/bcryptjs/-/bcryptjs-3.0.0.tgz#d7be11653aa82cf17ffee4f3925f1f80cfc1add2" + integrity sha512-WRZOuCuaz8UcZZE4R5HXTco2goQSI2XxjGY3hbM/xDvwmqFWd4ivooImsMx65OKM6CtNKbnZ5YL+YwAwK7c1dg== + dependencies: + bcryptjs "*" + +"@types/body-parser@*": + version "1.19.6" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.6.tgz#1859bebb8fd7dac9918a45d54c1971ab8b5af474" + integrity sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + "@types/cacheable-request@^6.0.1": version "6.0.3" resolved "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz" @@ -1360,6 +1380,20 @@ resolved "https://registry.npmjs.org/@types/chai/-/chai-4.3.14.tgz" integrity sha512-Wj71sXE4Q4AkGdG9Tvq1u/fquNz9EdG4LIJMwVVII7ashjD/8cf8fyIfJAjRr6YcsXnSE8cOGQPq1gqeR8z+3w== +"@types/connect@*": + version "3.4.38" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== + dependencies: + "@types/node" "*" + +"@types/cors@^2.8.12": + version "2.8.19" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.19.tgz#d93ea2673fd8c9f697367f5eeefc2bbfa94f0342" + integrity sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg== + dependencies: + "@types/node" "*" + "@types/debug@^4.1.6": version "4.1.12" resolved "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz" @@ -1367,6 +1401,25 @@ dependencies: "@types/ms" "*" +"@types/express-serve-static-core@^5.0.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz#74f47555b3d804b54cb7030e6f9aa0c7485cfc5b" + integrity sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@^5.0.6": + version "5.0.6" + resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.6.tgz#2d724b2c990dcb8c8444063f3580a903f6d500cc" + integrity sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^5.0.0" + "@types/serve-static" "^2" + "@types/fs-extra@8": version "8.1.5" resolved "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.5.tgz" @@ -1386,6 +1439,11 @@ resolved "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz" integrity sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA== +"@types/http-errors@*": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.5.tgz#5b749ab2b16ba113423feb1a64a95dcd30398472" + integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== + "@types/keyv@^3.1.4": version "3.1.4" resolved "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz" @@ -1430,11 +1488,18 @@ resolved "https://registry.npmjs.org/@types/mustache/-/mustache-4.2.5.tgz" integrity sha512-PLwiVvTBg59tGFL/8VpcGvqOu3L4OuveNvPi0EYbWchRdEVP++yRUXJPFl+CApKEq13017/4Nf7aQ5lTtHUNsA== -"@types/node@*", "@types/node@^12.6.8": +"@types/node@*": version "12.20.55" resolved "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz" integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== +"@types/node@>=10.0.0", "@types/node@^25.0.3": + version "25.0.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-25.0.3.tgz#79b9ac8318f373fbfaaf6e2784893efa9701f269" + integrity sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA== + dependencies: + undici-types "~7.16.0" + "@types/node@>=13.7.0", "@types/node@^20.9.0": version "20.12.4" resolved "https://registry.npmjs.org/@types/node/-/node-20.12.4.tgz" @@ -1455,6 +1520,16 @@ "@types/node" "*" xmlbuilder ">=11.0.1" +"@types/qs@*": + version "6.14.0" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.14.0.tgz#d8b60cecf62f2db0fb68e5e006077b9178b85de5" + integrity sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ== + +"@types/range-parser@*": + version "1.2.7" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" + integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== + "@types/responselike@^1.0.0": version "1.0.3" resolved "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz" @@ -1467,6 +1542,21 @@ resolved "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz" integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ== +"@types/send@*": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@types/send/-/send-1.2.1.tgz#6a784e45543c18c774c049bff6d3dbaf045c9c74" + integrity sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ== + dependencies: + "@types/node" "*" + +"@types/serve-static@^2": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-2.2.0.tgz#d4a447503ead0d1671132d1ab6bd58b805d8de6a" + integrity sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ== + dependencies: + "@types/http-errors" "*" + "@types/node" "*" + "@types/sha1@^1.1.1": version "1.1.5" resolved "https://registry.npmjs.org/@types/sha1/-/sha1-1.1.5.tgz" @@ -1474,6 +1564,13 @@ dependencies: "@types/node" "*" +"@types/socket.io@^3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/socket.io/-/socket.io-3.0.2.tgz#606c9639e3f93bb8454cba8f5f0a283d47917759" + integrity sha512-pu0sN9m5VjCxBZVK8hW37ZcMe8rjn4HHggBN5CbaRTvFwv5jOmuIRZEuddsBPa9Th0ts0SIo3Niukq+95cMBbQ== + dependencies: + socket.io "*" + "@types/uuid@^8.3.4": version "8.3.4" resolved "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz" @@ -1514,6 +1611,22 @@ about-window@^1.12.1: resolved "https://registry.npmjs.org/about-window/-/about-window-1.15.2.tgz" integrity sha512-31mDAnLUfKm4uShfMzeEoS6a3nEto2tUt4zZn7qyAKedaTV4p0dGiW1n+YG8vtRh78mZiewghWJmoxDY+lHyYg== +accepts@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895" + integrity sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng== + dependencies: + mime-types "^3.0.0" + negotiator "^1.0.0" + +accepts@~1.3.4: + 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== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + acorn-walk@^8.1.1: version "8.3.2" resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz" @@ -1801,6 +1914,16 @@ base64-js@^1.3.1, base64-js@^1.5.1: resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +base64id@2.0.0, base64id@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6" + integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== + +bcryptjs@*, bcryptjs@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-3.0.3.tgz#4b93d6a398c48bfc9f32ee65d301174a8a8ea56f" + integrity sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g== + before-after-hook@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-3.0.2.tgz#d5665a5fa8b62294a5aa0a499f933f4a1016195d" @@ -1842,6 +1965,21 @@ bluebird@^3.5.5: resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== +body-parser@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-2.2.1.tgz#6df606b0eb0a6e3f783dde91dde182c24c82438c" + integrity sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw== + 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" + boolean@^3.0.1: version "3.2.0" resolved "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz" @@ -1955,6 +2093,11 @@ builtin-modules@^1.1.1: resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz" integrity sha512-wxXCdllwGhI2kCC0MnvTGYTMvnVZTvqgypkiTI8Pa5tcz2i6VqsqwYGgqwXji+4RgCzms6EajE4IxiUH6HH8nQ== +bytes@^3.1.2, bytes@~3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + cacache@^18.0.0, cacache@^18.0.2: version "18.0.3" resolved "https://registry.yarnpkg.com/cacache/-/cacache-18.0.3.tgz#864e2c18414e1e141ae8763f31e46c2cb96d1b21" @@ -2001,6 +2144,14 @@ caching-transform@^4.0.0: package-hash "^4.0.0" write-file-atomic "^3.0.0" +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: version "1.0.7" resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz" @@ -2012,6 +2163,14 @@ call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: get-intrinsic "^1.2.4" set-function-length "^1.2.1" +call-bound@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" + integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== + dependencies: + call-bind-apply-helpers "^1.0.2" + get-intrinsic "^1.3.0" + callsites@^3.0.0, callsites@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" @@ -2375,6 +2534,16 @@ configstore@^6.0.0: write-file-atomic "^3.0.3" xdg-basedir "^5.0.1" +content-disposition@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-1.0.1.tgz#a8b7bbeb2904befdfb6787e5c0c086959f605f9b" + integrity sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q== + +content-type@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + conventional-changelog-angular@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz#5eec8edbff15aa9b1680a8dcfbd53e2d7eb2ba7a" @@ -2424,6 +2593,16 @@ convert-source-map@^2.0.0: resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== +cookie-signature@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.2.tgz#57c7fc3cc293acab9fec54d73e15690ebe4a1793" + integrity sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg== + +cookie@^0.7.1, cookie@~0.7.2: + version "0.7.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" + integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== + core-util-is@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -2434,6 +2613,14 @@ core-util-is@^1.0.3, core-util-is@~1.0.0: resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== +cors@~2.8.5: + version "2.8.5" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + dependencies: + object-assign "^4" + vary "^1" + cosmiconfig@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-9.0.0.tgz#34c3fc58287b915f3ae905ab6dc3de258b55ad9d" @@ -2645,6 +2832,20 @@ debug@4, debug@4.3.4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, de dependencies: ms "2.1.2" +debug@^4.4.0, debug@^4.4.3: + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + +debug@~4.3.1, debug@~4.3.2, debug@~4.3.4: + version "4.3.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== + dependencies: + ms "^2.1.3" + decamelize@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" @@ -2709,6 +2910,11 @@ delayed-stream@~1.0.0: resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== +depd@^2.0.0, depd@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + detect-node@^2.0.4: version "2.1.0" resolved "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz" @@ -2804,6 +3010,15 @@ dotenv@^9.0.2: resolved "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz" integrity sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg== +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + duplexer2@~0.1.0: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" @@ -2831,6 +3046,11 @@ eastasianwidth@^0.2.0: resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + ejs@^3.1.8: version "3.1.10" resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" @@ -2916,6 +3136,11 @@ emojilib@^2.4.0: resolved "https://registry.yarnpkg.com/emojilib/-/emojilib-2.4.0.tgz#ac518a8bb0d5f76dda57289ccb2fdf9d39ae721e" integrity sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw== +encodeurl@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" + integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== + encoding@^0.1.13: version "0.1.13" resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" @@ -2930,6 +3155,26 @@ end-of-stream@^1.1.0, end-of-stream@^1.4.1: dependencies: once "^1.4.0" +engine.io-parser@~5.2.1: + version "5.2.3" + resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.2.3.tgz#00dc5b97b1f233a23c9398d0209504cf5f94d92f" + integrity sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q== + +engine.io@~6.6.0: + version "6.6.4" + resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.6.4.tgz#0a89a3e6b6c1d4b0c2a2a637495e7c149ec8d8ee" + integrity sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g== + dependencies: + "@types/cors" "^2.8.12" + "@types/node" ">=10.0.0" + accepts "~1.3.4" + base64id "2.0.0" + cookie "~0.7.2" + cors "~2.8.5" + debug "~4.3.1" + engine.io-parser "~5.2.1" + ws "~8.17.1" + env-ci@^11.0.0: version "11.0.0" resolved "https://registry.yarnpkg.com/env-ci/-/env-ci-11.0.0.tgz#0cbc2c55feb071a3651aaa9fa181a817e696595f" @@ -3014,6 +3259,11 @@ es-define-property@^1.0.0: dependencies: get-intrinsic "^1.2.4" +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + es-errors@^1.2.1, es-errors@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz" @@ -3026,6 +3276,13 @@ es-object-atoms@^1.0.0: dependencies: es-errors "^1.3.0" +es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + es-set-tostringtag@^2.0.3: version "2.0.3" resolved "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz" @@ -3054,6 +3311,11 @@ escalade@^3.1.1: resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz" integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== +escape-html@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + escape-string-regexp@4.0.0, escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" @@ -3079,6 +3341,11 @@ esutils@^1.1.6: resolved "https://registry.npmjs.org/esutils/-/esutils-1.1.6.tgz" integrity sha512-RG1ZkUT7iFJG9LSHr7KDuuMSlujfeTtMNIcInURxKAxhMtwQhI3NrQhz26gZQYlsYZQKzsnwtpKrFKj9K9Qu1A== +etag@^1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + event-stream@=3.3.4: version "3.3.4" resolved "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz" @@ -3145,6 +3412,40 @@ exponential-backoff@^3.1.1: resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6" integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw== +express@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/express/-/express-5.2.1.tgz#8f21d15b6d327f92b4794ecf8cb08a72f956ac04" + integrity sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw== + 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" + extract-zip@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz" @@ -3241,6 +3542,18 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" +finalhandler@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-2.1.1.tgz#a2c517a6559852bcdb06d1f8bd7f51b68fad8099" + integrity sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA== + 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" + find-cache-dir@^3.2.0: version "3.3.2" resolved "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz" @@ -3349,6 +3662,16 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fresh@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-2.0.0.tgz#8dd7df6a1b3a1b3a5cf186c05a5dd267622635a4" + integrity sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A== + from2@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" @@ -3489,11 +3812,35 @@ get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@ has-symbols "^1.0.3" hasown "^2.0.0" +get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + get-stdin@^9.0.0: version "9.0.0" resolved "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz" @@ -3652,6 +3999,11 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + got@^11.8.5: version "11.8.6" resolved "https://registry.npmjs.org/got/-/got-11.8.6.tgz" @@ -3740,6 +4092,11 @@ has-symbols@^1.0.2, has-symbols@^1.0.3: resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== +has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz" @@ -3814,6 +4171,17 @@ http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.1: resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz" integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== +http-errors@^2.0.0, http-errors@^2.0.1, http-errors@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.1.tgz#36d2f65bc909c8790018dd36fb4d93da6caae06b" + integrity sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ== + dependencies: + depd "~2.0.0" + inherits "~2.0.4" + setprototypeof "~1.2.0" + statuses "~2.0.2" + toidentifier "~1.0.1" + http-proxy-agent@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz" @@ -3885,6 +4253,13 @@ iconv-lite@^0.6.2: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" +iconv-lite@^0.7.0, iconv-lite@~0.7.0: + version "0.7.1" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.7.1.tgz#d4af1d2092f2bb05aab6296e5e7cd286d2f15432" + integrity sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + ieee754@^1.1.13: version "1.2.1" resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" @@ -3951,7 +4326,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3, inherits@~2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -4019,6 +4394,11 @@ ip-regex@^5.0.0: resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-5.0.0.tgz#cd313b2ae9c80c07bd3851e12bf4fa4dc5480632" integrity sha512-fOCG6lhoKKakwv+C6KdsOnGvgXnmgfmp0myi3bcNwj3qfwPAxRKWEuFhvEFF7ceYIz6+1jRZ+yguLFAmUNPEfw== +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + is-array-buffer@^3.0.4: version "3.0.4" resolved "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz" @@ -4153,6 +4533,11 @@ is-promise@^2.1.0: resolved "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== +is-promise@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3" + integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== + is-regex@^1.1.4: version "1.1.4" resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz" @@ -4726,6 +5111,11 @@ long@^4.0.0: resolved "https://registry.npmjs.org/long/-/long-4.0.0.tgz" integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== +long@^5.0.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/long/-/long-5.3.2.tgz#1d84463095999262d7d7b7f8bfd4a8cc55167f83" + integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA== + loupe@^2.3.6: version "2.3.7" resolved "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz" @@ -4847,6 +5237,16 @@ matcher@^3.0.0: dependencies: escape-string-regexp "^4.0.0" +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + +media-typer@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-1.1.0.tgz#6ab74b8f2d3320f2064b2a87a38e7931ff3a5561" + integrity sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw== + memorystream@^0.3.1: version "0.3.1" resolved "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz" @@ -4857,6 +5257,11 @@ meow@^12.0.1: resolved "https://registry.yarnpkg.com/meow/-/meow-12.1.1.tgz#e558dddbab12477b69b2e9a2728c327f191bace6" integrity sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw== +merge-descriptors@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz#ea922f660635a2249ee565e0449f951e6b603808" + integrity sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g== + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" @@ -4880,13 +5285,25 @@ mime-db@1.52.0: resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12: +mime-db@^1.54.0: + version "1.54.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" + integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== + +mime-types@^2.1.12, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: mime-db "1.52.0" +mime-types@^3.0.0, mime-types@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-3.0.2.tgz#39002d4182575d5af036ffa118100f2524b2e2ab" + integrity sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A== + dependencies: + mime-db "^1.54.0" + mime@^2.4.4, mime@^2.5.2: version "2.6.0" resolved "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz" @@ -5102,7 +5519,7 @@ ms@2.1.2: resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3, ms@^2.1.2: +ms@2.1.3, ms@^2.1.2, ms@^2.1.3: version "2.1.3" resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -5126,11 +5543,16 @@ mz@^2.4.0: object-assign "^4.0.1" thenify-all "^1.0.0" -negotiator@^0.6.3: +negotiator@0.6.3, negotiator@^0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== +negotiator@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a" + integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== + neo-async@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" @@ -5453,7 +5875,7 @@ nyc@15: test-exclude "^6.0.0" yargs "^15.0.2" -object-assign@^4.0.1: +object-assign@^4, object-assign@^4.0.1: version "4.1.1" resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== @@ -5463,6 +5885,11 @@ object-inspect@^1.13.1: resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz" integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== +object-inspect@^1.13.3: + version "1.13.4" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" + integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== + object-keys@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz" @@ -5478,6 +5905,13 @@ object.assign@^4.1.5: has-symbols "^1.0.3" object-keys "^1.1.1" +on-finished@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" @@ -5719,6 +6153,11 @@ parse5@^6.0.1: resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== +parseurl@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + path-exists@^2.0.0: version "2.1.0" resolved "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz" @@ -5777,6 +6216,11 @@ path-scurry@^1.11.0: lru-cache "^10.2.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" +path-to-regexp@^8.0.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.3.0.tgz#aa818a6981f99321003a08987d3cec9c3474cd1f" + integrity sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA== + path-type@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz" @@ -6006,6 +6450,32 @@ protobufjs@^6.11.3: "@types/node" ">=13.7.0" long "^4.0.0" +protobufjs@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-8.0.0.tgz#d884102c1fe8d0b1e2493789ad37bc7ea47c0893" + integrity sha512-jx6+sE9h/UryaCZhsJWbJtTEy47yXoGNYI4z8ZaRncM0zBKeRqjO2JEcOUYwrYGb1WLhXM1FfMzW3annvFv0rw== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/node" ">=13.7.0" + long "^5.0.0" + +proxy-addr@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + proxy-from-env@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" @@ -6041,6 +6511,13 @@ qrcode-terminal@^0.12.0: resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819" integrity sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ== +qs@^6.14.0: + version "6.14.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.0.tgz#c63fa40680d2c5c941412a0e899c89af60c0a930" + integrity sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w== + dependencies: + side-channel "^1.1.0" + queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" @@ -6058,6 +6535,21 @@ randombytes@^2.1.0: dependencies: safe-buffer "^5.1.0" +range-parser@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-3.0.2.tgz#3e3ada5ae5568f9095d84376fd3a49b8fb000a51" + integrity sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA== + dependencies: + bytes "~3.1.2" + http-errors "~2.0.1" + iconv-lite "~0.7.0" + unpipe "~1.0.0" + rc@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" @@ -6308,6 +6800,17 @@ roarr@^2.15.3: semver-compare "^1.0.0" sprintf-js "^1.1.2" +router@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/router/-/router-2.2.0.tgz#019be620b711c87641167cc79b99090f00b146ef" + integrity sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ== + dependencies: + debug "^4.4.0" + depd "^2.0.0" + is-promise "^4.0.0" + parseurl "^1.3.3" + path-to-regexp "^8.0.0" + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" @@ -6442,6 +6945,23 @@ semver@^7.3.2, semver@^7.3.5, semver@^7.3.8, semver@^7.5.3, semver@^7.6.0: dependencies: lru-cache "^6.0.0" +send@^1.1.0, send@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/send/-/send-1.2.1.tgz#9eab743b874f3550f40a26867bf286ad60d3f3ed" + integrity sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ== + dependencies: + debug "^4.4.3" + encodeurl "^2.0.0" + escape-html "^1.0.3" + etag "^1.8.1" + fresh "^2.0.0" + http-errors "^2.0.1" + mime-types "^3.0.2" + ms "^2.1.3" + on-finished "^2.4.1" + range-parser "^1.2.1" + statuses "^2.0.2" + serialize-error@^7.0.1: version "7.0.1" resolved "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz" @@ -6456,6 +6976,16 @@ serialize-javascript@6.0.0: dependencies: randombytes "^2.1.0" +serve-static@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-2.2.1.tgz#7f186a4a4e5f5b663ad7a4294ff1bf37cf0e98a9" + integrity sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw== + dependencies: + encodeurl "^2.0.0" + escape-html "^1.0.3" + parseurl "^1.3.3" + send "^1.2.0" + set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" @@ -6483,6 +7013,11 @@ set-function-name@^2.0.1: functions-have-names "^1.2.3" has-property-descriptors "^1.0.2" +setprototypeof@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + sha1@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/sha1/-/sha1-1.1.1.tgz" @@ -6520,6 +7055,35 @@ shell-quote@^1.6.1: resolved "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz" integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== +side-channel-list@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" + integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + +side-channel-map@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" + integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + side-channel-map "^1.0.1" + side-channel@^1.0.4: version "1.0.6" resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz" @@ -6530,6 +7094,17 @@ side-channel@^1.0.4: get-intrinsic "^1.2.4" object-inspect "^1.13.1" +side-channel@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" + integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + side-channel-list "^1.0.0" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" + signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.7" resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" @@ -6594,6 +7169,35 @@ smart-buffer@^4.0.2, smart-buffer@^4.2.0: resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== +socket.io-adapter@~2.5.2: + version "2.5.5" + resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz#c7a1f9c703d7756844751b6ff9abfc1780664082" + integrity sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg== + dependencies: + debug "~4.3.4" + ws "~8.17.1" + +socket.io-parser@~4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83" + integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew== + dependencies: + "@socket.io/component-emitter" "~3.1.0" + debug "~4.3.1" + +socket.io@*, socket.io@^4.8.1: + version "4.8.1" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.8.1.tgz#fa0eaff965cc97fdf4245e8d4794618459f7558a" + integrity sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg== + dependencies: + accepts "~1.3.4" + base64id "~2.0.0" + cors "~2.8.5" + debug "~4.3.2" + engine.io "~6.6.0" + socket.io-adapter "~2.5.2" + socket.io-parser "~4.2.4" + socks-proxy-agent@^8.0.3: version "8.0.3" resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-8.0.3.tgz#6b2da3d77364fde6292e810b496cb70440b9b89d" @@ -6742,6 +7346,11 @@ stat-mode@^1.0.0: resolved "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz" integrity sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg== +statuses@^2.0.1, statuses@^2.0.2, statuses@~2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382" + integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== + steno@^0.4.1: version "0.4.4" resolved "https://registry.npmjs.org/steno/-/steno-0.4.4.tgz" @@ -7094,6 +7703,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +toidentifier@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + traverse@~0.6.6: version "0.6.9" resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.9.tgz#76cfdbacf06382d460b76f8b735a44a6209d8b81" @@ -7274,6 +7888,15 @@ type-fest@^4.6.0, type-fest@^4.7.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.18.2.tgz#8d765c42e7280a11f4d04fb77a00dacc417c8b05" integrity sha512-+suCYpfJLAe4OXS6+PPXjW3urOS4IoP9waSiLuXfLgqZODKw/aWwASvzqE886wA0kQgGy0mIWyhd87VpqIy6Xg== +type-is@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-2.0.1.tgz#64f6cf03f92fce4015c2b224793f6bdd4b068c97" + integrity sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw== + dependencies: + content-type "^1.0.5" + media-typer "^1.1.0" + mime-types "^3.0.0" + typed-array-buffer@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz" @@ -7372,6 +7995,11 @@ undici-types@~5.26.4: resolved "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== +undici-types@~7.16.0: + version "7.16.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46" + integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw== + undici@^5.25.4: version "5.28.4" resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.4.tgz#6b280408edb6a1a604a9b20340f45b422e373068" @@ -7425,6 +8053,11 @@ universalify@^2.0.0: resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz" integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== +unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + update-browserslist-db@^1.0.13: version "1.0.13" resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz" @@ -7455,6 +8088,11 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== +uuid@^13.0.0: + version "13.0.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-13.0.0.tgz#263dc341b19b4d755eb8fe36b78d95a6b65707e8" + integrity sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w== + uuid@^8.3.2: version "8.3.2" resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz" @@ -7478,6 +8116,11 @@ validate-npm-package-name@^5.0.0: resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz#a316573e9b49f3ccd90dbb6eb52b3f06c6d604e8" integrity sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ== +vary@^1, vary@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + verror@^1.10.0: version "1.10.1" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.1.tgz#4bf09eeccf4563b109ed4b3d458380c972b0cdeb" @@ -7624,6 +8267,11 @@ ws@^7.5.5: resolved "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== +ws@~8.17.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== + xdg-basedir@^5.0.1: version "5.1.0" resolved "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz"