mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-19 18:00:15 +01:00
✨ feat: move to Socket.IO
This commit is contained in:
parent
7717d3a514
commit
9c0c341dee
9 changed files with 413 additions and 134 deletions
|
|
@ -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"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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}`);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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<string[]>([]);
|
||||
|
||||
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 (
|
||||
<OGDialog open={open} onOpenChange={setOpen}>
|
||||
<OGDialogContent className="w-96 p-8">
|
||||
|
|
@ -27,24 +63,34 @@ export const Call: React.FC = () => {
|
|||
</span>
|
||||
</div>
|
||||
|
||||
{!isCalling ? (
|
||||
{isCalling ? (
|
||||
<Button
|
||||
onClick={startCall}
|
||||
onClick={handleHangUp}
|
||||
className="flex items-center gap-2 rounded-full bg-red-500 px-6 py-3 text-white hover:bg-red-600"
|
||||
>
|
||||
<PhoneOff size={20} />
|
||||
<span>End Call</span>
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
onClick={handleStartCall}
|
||||
disabled={!isConnected}
|
||||
className="flex items-center gap-2 rounded-full bg-green-500 px-6 py-3 text-white hover:bg-green-600 disabled:opacity-50"
|
||||
>
|
||||
<Phone size={20} />
|
||||
<span>Start Call</span>
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
onClick={hangUp}
|
||||
className="flex items-center gap-2 rounded-full bg-red-500 px-6 py-3 text-white hover:bg-red-600"
|
||||
>
|
||||
<PhoneOff size={20} />
|
||||
<span>End Call</span>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{/* Debugging Information */}
|
||||
<div className="mt-4 w-full rounded-md bg-gray-100 p-4 shadow-sm">
|
||||
<h3 className="mb-2 text-lg font-medium">Event Log</h3>
|
||||
<ul className="h-32 overflow-y-auto text-xs text-gray-600">
|
||||
{eventLog.map((log, index) => (
|
||||
<li key={index}>{log}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</OGDialogContent>
|
||||
</OGDialog>
|
||||
|
|
|
|||
|
|
@ -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<AudioContext | null>(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) {
|
||||
|
|
|
|||
|
|
@ -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<WebSocket | null>(null);
|
||||
const socketRef = useRef<Socket | null>(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':
|
||||
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
|
||||
break;
|
||||
case 'llm-response':
|
||||
});
|
||||
|
||||
socketRef.current.on('llm-response', (msg: RTCMessage) => {
|
||||
// TODO: Handle LLM streaming response
|
||||
break;
|
||||
case 'tts-chunk':
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
});
|
||||
}, [data?.url]);
|
||||
|
||||
useEffect(() => {
|
||||
connect();
|
||||
return () => wsRef.current?.close();
|
||||
return () => {
|
||||
socketRef.current?.disconnect();
|
||||
};
|
||||
}, [connect]);
|
||||
|
||||
const sendMessage = useCallback((message: Record<string, unknown>) => {
|
||||
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
||||
wsRef.current.send(JSON.stringify(message));
|
||||
if (socketRef.current?.connected) {
|
||||
socketRef.current.emit('message', message);
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
|
|
|||
281
package-lock.json
generated
281
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue