From 9c0c341dee2064f143670f2fc0803ce818c52286 Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Mon, 30 Dec 2024 01:47:21 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20move=20to=20Socket.IO?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/package.json | 2 +- api/server/index.js | 6 +- api/server/routes/websocket.js | 9 +- .../services/WebSocket/WebSocketServer.js | 101 ++----- client/package.json | 1 + client/src/components/Chat/Input/Call.tsx | 74 ++++- client/src/hooks/useCall.ts | 14 +- client/src/hooks/useWebSocket.ts | 59 ++-- package-lock.json | 281 +++++++++++++++++- 9 files changed, 413 insertions(+), 134 deletions(-) diff --git a/api/package.json b/api/package.json index 630273b8e6..8fc6b73384 100644 --- a/api/package.json +++ b/api/package.json @@ -98,13 +98,13 @@ "passport-ldapauth": "^3.0.1", "passport-local": "^1.0.0", "sharp": "^0.32.6", + "socket.io": "^4.8.1", "tiktoken": "^1.0.15", "traverse": "^0.6.7", "ua-parser-js": "^1.0.36", "winston": "^3.11.0", "winston-daily-rotate-file": "^4.7.1", "wrtc": "^0.4.7", - "ws": "^8.18.0", "youtube-transcript": "^1.2.1", "zod": "^3.22.4" }, diff --git a/api/server/index.js b/api/server/index.js index 6296c5bc5e..d1655ca74a 100644 --- a/api/server/index.js +++ b/api/server/index.js @@ -15,7 +15,7 @@ const { connectDb, indexSync } = require('~/lib/db'); const { isEnabled } = require('~/server/utils'); const { ldapLogin } = require('~/strategies'); const { logger } = require('~/config'); -const { WebSocketService } = require('./services/WebSocket/WebSocketServer'); +const { SocketIOService } = require('./services/WebSocket/WebSocketServer'); const validateImageRequest = require('./middleware/validateImageRequest'); const errorController = require('./controllers/ErrorController'); const configureSocialLogins = require('./socialLogins'); @@ -48,7 +48,7 @@ const startServer = async () => { }), ); - new WebSocketService(server); + new SocketIOService(server); await AppService(app); @@ -149,7 +149,7 @@ const startServer = async () => { logger.info(`Server listening at http://${host == '0.0.0.0' ? 'localhost' : host}:${port}`); } - logger.info(`WebSocket endpoint: ws://${host}:${port}`); + logger.info(`Socket.IO endpoint: http://${host}:${port}`); }); }; diff --git a/api/server/routes/websocket.js b/api/server/routes/websocket.js index 82d487f593..7955d61a39 100644 --- a/api/server/routes/websocket.js +++ b/api/server/routes/websocket.js @@ -4,15 +4,16 @@ const router = express.Router(); router.get('/', optionalJwtAuth, async (req, res) => { const isProduction = process.env.NODE_ENV === 'production'; - const useSSL = isProduction && process.env.SERVER_DOMAIN?.startsWith('https'); - const protocol = useSSL ? 'wss' : 'ws'; + const protocol = isProduction && req.secure ? 'https' : 'http'; + const serverDomain = process.env.SERVER_DOMAIN ? process.env.SERVER_DOMAIN.replace(/^https?:\/\//, '') : req.headers.host; - const wsUrl = `${protocol}://${serverDomain}/ws`; - res.json({ url: wsUrl }); + const socketIoUrl = `${protocol}://${serverDomain}`; + + res.json({ url: socketIoUrl }); }); module.exports = router; diff --git a/api/server/services/WebSocket/WebSocketServer.js b/api/server/services/WebSocket/WebSocketServer.js index be79f3a9a1..19b1c4e9f8 100644 --- a/api/server/services/WebSocket/WebSocketServer.js +++ b/api/server/services/WebSocket/WebSocketServer.js @@ -1,10 +1,10 @@ -const { WebSocketServer } = require('ws'); +const { Server } = require('socket.io'); const { RTCPeerConnection } = require('wrtc'); -module.exports.WebSocketService = class { - constructor(server) { - this.wss = new WebSocketServer({ server, path: '/ws' }); - this.log('Server initialized'); +module.exports.SocketIOService = class { + constructor(httpServer) { + this.io = new Server(httpServer, { path: '/socket.io' }); + this.log('Socket.IO Server initialized'); this.activeClients = new Map(); this.iceServers = [ { urls: 'stun:stun.l.google.com:19302' }, @@ -14,14 +14,14 @@ module.exports.WebSocketService = class { } log(msg) { - console.log(`[WSS ${new Date().toISOString()}] ${msg}`); + console.log(`[Socket.IO ${new Date().toISOString()}] ${msg}`); } setupHandlers() { - this.wss.on('connection', (ws) => { - const clientId = Date.now().toString(); + this.io.on('connection', (socket) => { + const clientId = socket.id; this.activeClients.set(clientId, { - ws, + socket, state: 'idle', audioBuffer: [], currentTranscription: '', @@ -30,44 +30,19 @@ module.exports.WebSocketService = class { this.log(`Client connected: ${clientId}`); - ws.on('message', async (raw) => { - let message; - try { - message = JSON.parse(raw); - } catch { - return; - } + socket.on('call-start', () => this.handleCallStart(clientId)); + socket.on('audio-chunk', (data) => this.handleAudioChunk(clientId, data)); + socket.on('processing-start', () => this.processAudioStream(clientId)); + socket.on('audio-received', () => this.confirmAudioReceived(clientId)); + socket.on('call-ended', () => this.handleCallEnd(clientId)); - switch (message.type) { - case 'call-start': - this.handleCallStart(clientId); - break; - - case 'audio-chunk': - await this.handleAudioChunk(clientId, message.data); - break; - - case 'processing-start': - await this.processAudioStream(clientId); - break; - - case 'audio-received': - this.confirmAudioReceived(clientId); - break; - - case 'call-ended': - this.handleCallEnd(clientId); - break; - } - }); - - ws.on('close', () => { + socket.on('disconnect', () => { this.handleCallEnd(clientId); this.activeClients.delete(clientId); this.log(`Client disconnected: ${clientId}`); }); - ws.on('error', (error) => { + socket.on('error', (error) => { this.log(`Error for client ${clientId}: ${error.message}`); this.handleCallEnd(clientId); }); @@ -104,12 +79,7 @@ module.exports.WebSocketService = class { peerConnection.onicecandidate = (event) => { if (event.candidate) { - client.ws.send( - JSON.stringify({ - type: 'ice-candidate', - candidate: event.candidate, - }), - ); + client.socket.emit('ice-candidate', { candidate: event.candidate }); } }; @@ -117,12 +87,7 @@ module.exports.WebSocketService = class { try { const offer = await peerConnection.createOffer(); await peerConnection.setLocalDescription(offer); - client.ws.send( - JSON.stringify({ - type: 'webrtc-offer', - sdp: peerConnection.localDescription, - }), - ); + client.socket.emit('webrtc-offer', { sdp: peerConnection.localDescription }); } catch (error) { this.log(`Negotiation failed for ${clientId}: ${error}`); } @@ -142,7 +107,7 @@ module.exports.WebSocketService = class { } client.audioBuffer.push(data); - client.ws.send(JSON.stringify({ type: 'audio-received' })); + client.socket.emit('audio-received'); } async processAudioStream(clientId) { @@ -155,28 +120,13 @@ module.exports.WebSocketService = class { try { // Process transcription - client.ws.send( - JSON.stringify({ - type: 'transcription', - data: 'Processing audio...', - }), - ); + client.socket.emit('transcription', { data: 'Processing audio...' }); // Stream LLM response - client.ws.send( - JSON.stringify({ - type: 'llm-response', - data: 'Processing response...', - }), - ); + client.socket.emit('llm-response', { data: 'Processing response...' }); // Stream TTS chunks - client.ws.send( - JSON.stringify({ - type: 'tts-chunk', - data: 'audio_data_here', - }), - ); + client.socket.emit('tts-chunk', { data: 'audio_data_here' }); } catch (error) { this.log(`Processing error for client ${clientId}: ${error.message}`); } finally { @@ -191,12 +141,7 @@ module.exports.WebSocketService = class { return; } - client.ws.send( - JSON.stringify({ - type: 'audio-received', - data: null, - }), - ); + client.socket.emit('audio-received', { data: null }); } handleCallEnd(clientId) { diff --git a/client/package.json b/client/package.json index 0a0379b6f1..08d46adc29 100644 --- a/client/package.json +++ b/client/package.json @@ -97,6 +97,7 @@ "remark-gfm": "^4.0.0", "remark-math": "^6.0.0", "remark-supersub": "^1.0.0", + "socket.io-client": "^4.8.1", "sse.js": "^2.5.0", "tailwind-merge": "^1.9.1", "tailwindcss-animate": "^1.0.5", diff --git a/client/src/components/Chat/Input/Call.tsx b/client/src/components/Chat/Input/Call.tsx index c79877374f..85aedbff1f 100644 --- a/client/src/components/Chat/Input/Call.tsx +++ b/client/src/components/Chat/Input/Call.tsx @@ -1,15 +1,51 @@ +import React from 'react'; import { useRecoilState } from 'recoil'; -import { Mic, Phone, PhoneOff } from 'lucide-react'; +import { Phone, PhoneOff } from 'lucide-react'; import { OGDialog, OGDialogContent, Button } from '~/components'; -import { useWebRTC, useWebSocket, useCall } from '~/hooks'; +import { useWebSocket, useCall } from '~/hooks'; import store from '~/store'; export const Call: React.FC = () => { - const { isConnected } = useWebSocket(); - const { isCalling, startCall, hangUp } = useCall(); + const { isConnected, sendMessage } = useWebSocket(); + const { isCalling, isProcessing, startCall, hangUp } = useCall(); const [open, setOpen] = useRecoilState(store.callDialogOpen(0)); + const [eventLog, setEventLog] = React.useState([]); + + const logEvent = (message: string) => { + console.log(message); + setEventLog((prev) => [...prev, message]); + }; + + React.useEffect(() => { + if (isConnected) { + logEvent('Connected to server.'); + } else { + logEvent('Disconnected from server.'); + } + }, [isConnected]); + + React.useEffect(() => { + if (isCalling) { + logEvent('Call started.'); + } else if (isProcessing) { + logEvent('Processing audio...'); + } else { + logEvent('Call ended.'); + } + }, [isCalling, isProcessing]); + + const handleStartCall = () => { + logEvent('Attempting to start call...'); + startCall(); + }; + + const handleHangUp = () => { + logEvent('Attempting to hang up call...'); + hangUp(); + }; + return ( @@ -27,24 +63,34 @@ export const Call: React.FC = () => { - {!isCalling ? ( + {isCalling ? ( + ) : ( + - ) : ( - )} + + {/* Debugging Information */} +
+

Event Log

+
    + {eventLog.map((log, index) => ( +
  • {log}
  • + ))} +
+
diff --git a/client/src/hooks/useCall.ts b/client/src/hooks/useCall.ts index 2890c9a601..7afcfc8263 100644 --- a/client/src/hooks/useCall.ts +++ b/client/src/hooks/useCall.ts @@ -7,7 +7,7 @@ const SILENCE_THRESHOLD = -50; const SILENCE_DURATION = 1000; const useCall = () => { - const { sendMessage: wsMessage } = useWebSocket(); + const { sendMessage: wsMessage, isConnected } = useWebSocket(); const [isCalling, setIsCalling] = useState(false); const [isProcessing, setIsProcessing] = useState(false); const audioContextRef = useRef(null); @@ -23,9 +23,7 @@ const useCall = () => { } const audioBlob = new Blob(audioChunksRef.current, { type: 'audio/webm' }); - // Send audio through WebRTC data channel webrtcServiceRef.current?.sendAudioChunk(audioBlob); - // Signal processing start via WebSocket wsMessage({ type: 'processing-start' }); audioChunksRef.current = []; @@ -34,17 +32,18 @@ const useCall = () => { const handleRTCMessage = useCallback((message: RTCMessage) => { if (message.type === 'audio-received') { - // Backend confirmed audio receipt setIsProcessing(true); } }, []); const startCall = useCallback(async () => { - // Initialize WebRTC with message handler + if (!isConnected) { + return; + } + webrtcServiceRef.current = new WebRTCService(handleRTCMessage); await webrtcServiceRef.current.initializeCall(); - // Signal call start via WebSocket wsMessage({ type: 'call-start' }); const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); @@ -53,7 +52,6 @@ const useCall = () => { analyserRef.current = audioContextRef.current.createAnalyser(); source.connect(analyserRef.current); - // Start VAD monitoring intervalRef.current = window.setInterval(() => { if (!analyserRef.current || !isCalling) { return; @@ -76,7 +74,7 @@ const useCall = () => { }, 100); setIsCalling(true); - }, [handleRTCMessage, wsMessage, sendAudioChunk]); + }, [handleRTCMessage, isConnected, wsMessage, sendAudioChunk, isCalling]); const hangUp = useCallback(async () => { if (intervalRef.current) { diff --git a/client/src/hooks/useWebSocket.ts b/client/src/hooks/useWebSocket.ts index fc16ec13af..804a8506c7 100644 --- a/client/src/hooks/useWebSocket.ts +++ b/client/src/hooks/useWebSocket.ts @@ -1,49 +1,58 @@ import { useEffect, useRef, useState, useCallback } from 'react'; import { useGetWebsocketUrlQuery } from 'librechat-data-provider/react-query'; +import { io, Socket } from 'socket.io-client'; import type { RTCMessage } from '~/common'; const useWebSocket = () => { - const { data: data } = useGetWebsocketUrlQuery(); + const { data } = useGetWebsocketUrlQuery(); const [isConnected, setIsConnected] = useState(false); - const wsRef = useRef(null); + const socketRef = useRef(null); const connect = useCallback(() => { if (!data || !data.url) { return; } - wsRef.current = new WebSocket(data.url); - wsRef.current.onopen = () => setIsConnected(true); - wsRef.current.onclose = () => setIsConnected(false); - wsRef.current.onerror = (err) => console.error('WebSocket error:', err); + socketRef.current = io(data.url, { transports: ['websocket'] }); - wsRef.current.onmessage = (event) => { - const msg: RTCMessage = JSON.parse(event.data); - switch (msg.type) { - case 'transcription': - // TODO: Handle transcription update - break; - case 'llm-response': - // TODO: Handle LLM streaming response - break; - case 'tts-chunk': - if (typeof msg.data === 'string') { - const audio = new Audio(`data:audio/mp3;base64,${msg.data}`); - audio.play().catch(console.error); - } - break; + socketRef.current.on('connect', () => { + setIsConnected(true); + }); + + socketRef.current.on('disconnect', () => { + setIsConnected(false); + }); + + socketRef.current.on('error', (err) => { + console.error('Socket.IO error:', err); + }); + + socketRef.current.on('transcription', (msg: RTCMessage) => { + // TODO: Handle transcription update + }); + + socketRef.current.on('llm-response', (msg: RTCMessage) => { + // TODO: Handle LLM streaming response + }); + + socketRef.current.on('tts-chunk', (msg: RTCMessage) => { + if (typeof msg.data === 'string') { + const audio = new Audio(`data:audio/mp3;base64,${msg.data}`); + audio.play().catch(console.error); } - }; + }); }, [data?.url]); useEffect(() => { connect(); - return () => wsRef.current?.close(); + return () => { + socketRef.current?.disconnect(); + }; }, [connect]); const sendMessage = useCallback((message: Record) => { - if (wsRef.current?.readyState === WebSocket.OPEN) { - wsRef.current.send(JSON.stringify(message)); + if (socketRef.current?.connected) { + socketRef.current.emit('message', message); } }, []); diff --git a/package-lock.json b/package-lock.json index d039c16f17..9632371cc7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -114,13 +114,13 @@ "passport-ldapauth": "^3.0.1", "passport-local": "^1.0.0", "sharp": "^0.32.6", + "socket.io": "^4.8.1", "tiktoken": "^1.0.15", "traverse": "^0.6.7", "ua-parser-js": "^1.0.36", "winston": "^3.11.0", "winston-daily-rotate-file": "^4.7.1", "wrtc": "^0.4.7", - "ws": "^8.18.0", "youtube-transcript": "^1.2.1", "zod": "^3.22.4" }, @@ -1601,6 +1601,8 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -1691,6 +1693,7 @@ "remark-gfm": "^4.0.0", "remark-math": "^6.0.0", "remark-supersub": "^1.0.0", + "socket.io-client": "^4.8.1", "sse.js": "^2.5.0", "tailwind-merge": "^1.9.1", "tailwindcss-animate": "^1.0.5", @@ -15980,6 +15983,11 @@ "node": ">=14.0.0" } }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + }, "node_modules/@stitches/core": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/@stitches/core/-/core-1.2.8.tgz", @@ -16364,6 +16372,14 @@ "@types/node": "*" } }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -18032,6 +18048,14 @@ } ] }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, "node_modules/base64url": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", @@ -20260,6 +20284,117 @@ "once": "^1.4.0" } }, + "node_modules/engine.io": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", + "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", + "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-client": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", + "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/enhanced-resolve": { "version": "5.17.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", @@ -34011,6 +34146,142 @@ "integrity": "sha512-9LK+E7Hv5R9u4g4C3p+jjLstaLe11MDsL21UpYaCNmapvMkYhqCV4A/f/3gyH8QjMyh6l68q9xC85vihY9ahMQ==", "dev": true }, + "node_modules/socket.io": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/socks": { "version": "2.8.3", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", @@ -37575,6 +37846,14 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "devOptional": true }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",