mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-25 12:48:53 +01:00
✨ feat: Implement WebRTC messaging and audio handling in the WebRTC service
This commit is contained in:
parent
cf4b73b5e3
commit
9a33292f88
8 changed files with 674 additions and 137 deletions
|
|
@ -111,6 +111,7 @@
|
|||
"winston": "^3.11.0",
|
||||
"winston-daily-rotate-file": "^4.7.1",
|
||||
"youtube-transcript": "^1.2.1",
|
||||
"wrtc": "^0.4.7",
|
||||
"ws": "^8.18.0",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
const { WebSocketServer } = require('ws');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { RTCPeerConnection } = require('wrtc');
|
||||
|
||||
module.exports.WebSocketService = class {
|
||||
constructor(server) {
|
||||
this.wss = new WebSocketServer({ server, path: '/ws' });
|
||||
this.log('Server initialized');
|
||||
this.clientAudioBuffers = new Map();
|
||||
this.activeClients = new Map();
|
||||
this.iceServers = [
|
||||
{ urls: 'stun:stun.l.google.com:19302' },
|
||||
{ urls: 'stun:stun1.l.google.com:19302' },
|
||||
];
|
||||
this.setupHandlers();
|
||||
}
|
||||
|
||||
|
|
@ -17,7 +20,13 @@ module.exports.WebSocketService = class {
|
|||
setupHandlers() {
|
||||
this.wss.on('connection', (ws) => {
|
||||
const clientId = Date.now().toString();
|
||||
this.clientAudioBuffers.set(clientId, []);
|
||||
this.activeClients.set(clientId, {
|
||||
ws,
|
||||
state: 'idle',
|
||||
audioBuffer: [],
|
||||
currentTranscription: '',
|
||||
isProcessing: false,
|
||||
});
|
||||
|
||||
this.log(`Client connected: ${clientId}`);
|
||||
|
||||
|
|
@ -29,42 +38,175 @@ module.exports.WebSocketService = class {
|
|||
return;
|
||||
}
|
||||
|
||||
if (message.type === 'audio-chunk') {
|
||||
if (!this.clientAudioBuffers.has(clientId)) {
|
||||
this.clientAudioBuffers.set(clientId, []);
|
||||
}
|
||||
this.clientAudioBuffers.get(clientId).push(message.data);
|
||||
}
|
||||
switch (message.type) {
|
||||
case 'call-start':
|
||||
this.handleCallStart(clientId);
|
||||
break;
|
||||
|
||||
if (message.type === 'request-response') {
|
||||
const filePath = path.join(__dirname, './assets/response.mp3');
|
||||
const audioFile = fs.readFileSync(filePath);
|
||||
ws.send(JSON.stringify({ type: 'audio-response', data: audioFile.toString('base64') }));
|
||||
}
|
||||
case 'audio-chunk':
|
||||
await this.handleAudioChunk(clientId, message.data);
|
||||
break;
|
||||
|
||||
if (message.type === 'call-ended') {
|
||||
const allChunks = this.clientAudioBuffers.get(clientId);
|
||||
this.writeAudioFile(clientId, allChunks);
|
||||
this.clientAudioBuffers.delete(clientId);
|
||||
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', () => {
|
||||
this.handleCallEnd(clientId);
|
||||
this.activeClients.delete(clientId);
|
||||
this.log(`Client disconnected: ${clientId}`);
|
||||
this.clientAudioBuffers.delete(clientId);
|
||||
});
|
||||
|
||||
ws.on('error', (error) => {
|
||||
this.log(`Error for client ${clientId}: ${error.message}`);
|
||||
this.handleCallEnd(clientId);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
writeAudioFile(clientId, base64Chunks) {
|
||||
if (!base64Chunks || base64Chunks.length === 0) {
|
||||
async handleCallStart(clientId) {
|
||||
const client = this.activeClients.get(clientId);
|
||||
if (!client) {
|
||||
return;
|
||||
}
|
||||
const filePath = path.join(__dirname, `recorded_${clientId}.webm`);
|
||||
const buffer = Buffer.concat(
|
||||
base64Chunks.map((chunk) => Buffer.from(chunk.split(',')[1], 'base64')),
|
||||
|
||||
try {
|
||||
client.state = 'active';
|
||||
client.audioBuffer = [];
|
||||
client.currentTranscription = '';
|
||||
client.isProcessing = false;
|
||||
|
||||
const peerConnection = new RTCPeerConnection({
|
||||
iceServers: this.iceServers,
|
||||
sdpSemantics: 'unified-plan',
|
||||
});
|
||||
|
||||
client.peerConnection = peerConnection;
|
||||
client.dataChannel = peerConnection.createDataChannel('audio', {
|
||||
ordered: true,
|
||||
maxRetransmits: 3,
|
||||
});
|
||||
|
||||
client.dataChannel.onopen = () => this.log(`Data channel opened for ${clientId}`);
|
||||
client.dataChannel.onmessage = async (event) => {
|
||||
await this.handleAudioChunk(clientId, event.data);
|
||||
};
|
||||
|
||||
peerConnection.onicecandidate = (event) => {
|
||||
if (event.candidate) {
|
||||
client.ws.send(
|
||||
JSON.stringify({
|
||||
type: 'ice-candidate',
|
||||
candidate: event.candidate,
|
||||
}),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
peerConnection.onnegotiationneeded = async () => {
|
||||
try {
|
||||
const offer = await peerConnection.createOffer();
|
||||
await peerConnection.setLocalDescription(offer);
|
||||
client.ws.send(
|
||||
JSON.stringify({
|
||||
type: 'webrtc-offer',
|
||||
sdp: peerConnection.localDescription,
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
this.log(`Negotiation failed for ${clientId}: ${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
this.log(`Call started for client ${clientId}`);
|
||||
} catch (error) {
|
||||
this.log(`Error starting call for ${clientId}: ${error.message}`);
|
||||
this.handleCallEnd(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
async handleAudioChunk(clientId, data) {
|
||||
const client = this.activeClients.get(clientId);
|
||||
if (!client || client.state !== 'active') {
|
||||
return;
|
||||
}
|
||||
|
||||
client.audioBuffer.push(data);
|
||||
client.ws.send(JSON.stringify({ type: 'audio-received' }));
|
||||
}
|
||||
|
||||
async processAudioStream(clientId) {
|
||||
const client = this.activeClients.get(clientId);
|
||||
if (!client || client.state !== 'active' || client.isProcessing) {
|
||||
return;
|
||||
}
|
||||
|
||||
client.isProcessing = true;
|
||||
|
||||
try {
|
||||
// Process transcription
|
||||
client.ws.send(
|
||||
JSON.stringify({
|
||||
type: 'transcription',
|
||||
data: 'Processing audio...',
|
||||
}),
|
||||
);
|
||||
|
||||
// Stream LLM response
|
||||
client.ws.send(
|
||||
JSON.stringify({
|
||||
type: 'llm-response',
|
||||
data: 'Processing response...',
|
||||
}),
|
||||
);
|
||||
|
||||
// Stream TTS chunks
|
||||
client.ws.send(
|
||||
JSON.stringify({
|
||||
type: 'tts-chunk',
|
||||
data: 'audio_data_here',
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
this.log(`Processing error for client ${clientId}: ${error.message}`);
|
||||
} finally {
|
||||
client.isProcessing = false;
|
||||
client.audioBuffer = [];
|
||||
}
|
||||
}
|
||||
|
||||
confirmAudioReceived(clientId) {
|
||||
const client = this.activeClients.get(clientId);
|
||||
if (!client) {
|
||||
return;
|
||||
}
|
||||
|
||||
client.ws.send(
|
||||
JSON.stringify({
|
||||
type: 'audio-received',
|
||||
data: null,
|
||||
}),
|
||||
);
|
||||
fs.writeFileSync(filePath, buffer);
|
||||
this.log(`Saved audio to ${filePath}`);
|
||||
}
|
||||
|
||||
handleCallEnd(clientId) {
|
||||
const client = this.activeClients.get(clientId);
|
||||
if (!client) {
|
||||
return;
|
||||
}
|
||||
|
||||
client.state = 'idle';
|
||||
client.audioBuffer = [];
|
||||
client.currentTranscription = '';
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -56,6 +56,17 @@ export type BadgeItem = {
|
|||
isAvailable: boolean;
|
||||
};
|
||||
|
||||
export interface RTCMessage {
|
||||
type:
|
||||
| 'audio-chunk'
|
||||
| 'audio-received'
|
||||
| 'transcription'
|
||||
| 'llm-response'
|
||||
| 'tts-chunk'
|
||||
| 'call-ended';
|
||||
data?: string | ArrayBuffer | null;
|
||||
}
|
||||
|
||||
export type AssistantListItem = {
|
||||
id: string;
|
||||
name: string;
|
||||
|
|
|
|||
|
|
@ -1,67 +1,106 @@
|
|||
import { useState, useRef, useCallback } from 'react';
|
||||
import useWebSocket from './useWebSocket';
|
||||
import { WebRTCService } from '../services/WebRTC/WebRTCService';
|
||||
import type { RTCMessage } from '~/common';
|
||||
import useWebSocket from './useWebSocket';
|
||||
|
||||
const SILENCE_THRESHOLD = -50;
|
||||
const SILENCE_DURATION = 1000;
|
||||
|
||||
const useCall = () => {
|
||||
const { sendMessage } = useWebSocket();
|
||||
const { sendMessage: wsMessage } = useWebSocket();
|
||||
const [isCalling, setIsCalling] = useState(false);
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
const audioContextRef = useRef<AudioContext | null>(null);
|
||||
const analyserRef = useRef<AnalyserNode | null>(null);
|
||||
const audioChunksRef = useRef<Blob[]>([]);
|
||||
const silenceStartRef = useRef<number | null>(null);
|
||||
const intervalRef = useRef<number | null>(null);
|
||||
const webrtcServiceRef = useRef<WebRTCService | null>(null);
|
||||
|
||||
const checkSilence = useCallback(() => {
|
||||
if (!analyserRef.current || !isCalling) {
|
||||
const sendAudioChunk = useCallback(() => {
|
||||
if (audioChunksRef.current.length === 0) {
|
||||
return;
|
||||
}
|
||||
const data = new Float32Array(analyserRef.current.frequencyBinCount);
|
||||
analyserRef.current.getFloatFrequencyData(data);
|
||||
const avg = data.reduce((a, b) => a + b) / data.length;
|
||||
if (avg < SILENCE_THRESHOLD) {
|
||||
if (!silenceStartRef.current) {
|
||||
silenceStartRef.current = Date.now();
|
||||
} else if (Date.now() - silenceStartRef.current > SILENCE_DURATION) {
|
||||
sendMessage({ type: 'request-response' });
|
||||
silenceStartRef.current = null;
|
||||
}
|
||||
} else {
|
||||
silenceStartRef.current = null;
|
||||
|
||||
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 = [];
|
||||
setIsProcessing(true);
|
||||
}, [wsMessage]);
|
||||
|
||||
const handleRTCMessage = useCallback((message: RTCMessage) => {
|
||||
if (message.type === 'audio-received') {
|
||||
// Backend confirmed audio receipt
|
||||
setIsProcessing(true);
|
||||
}
|
||||
}, [isCalling, sendMessage]);
|
||||
}, []);
|
||||
|
||||
const startCall = useCallback(async () => {
|
||||
webrtcServiceRef.current = new WebRTCService(sendMessage);
|
||||
// Initialize WebRTC with message handler
|
||||
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 });
|
||||
audioContextRef.current = new AudioContext();
|
||||
const source = audioContextRef.current.createMediaStreamSource(stream);
|
||||
analyserRef.current = audioContextRef.current.createAnalyser();
|
||||
source.connect(analyserRef.current);
|
||||
|
||||
intervalRef.current = window.setInterval(checkSilence, 100);
|
||||
// Start VAD monitoring
|
||||
intervalRef.current = window.setInterval(() => {
|
||||
if (!analyserRef.current || !isCalling) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = new Float32Array(analyserRef.current.frequencyBinCount);
|
||||
analyserRef.current.getFloatFrequencyData(data);
|
||||
const avg = data.reduce((a, b) => a + b) / data.length;
|
||||
|
||||
if (avg < SILENCE_THRESHOLD) {
|
||||
if (silenceStartRef.current === null) {
|
||||
silenceStartRef.current = Date.now();
|
||||
} else if (Date.now() - silenceStartRef.current > SILENCE_DURATION) {
|
||||
sendAudioChunk();
|
||||
silenceStartRef.current = null;
|
||||
}
|
||||
} else {
|
||||
silenceStartRef.current = null;
|
||||
}
|
||||
}, 100);
|
||||
|
||||
setIsCalling(true);
|
||||
}, [checkSilence, sendMessage]);
|
||||
}, [handleRTCMessage, wsMessage, sendAudioChunk]);
|
||||
|
||||
const hangUp = useCallback(async () => {
|
||||
if (intervalRef.current) {
|
||||
clearInterval(intervalRef.current);
|
||||
}
|
||||
|
||||
analyserRef.current = null;
|
||||
audioContextRef.current?.close();
|
||||
audioContextRef.current = null;
|
||||
|
||||
await webrtcServiceRef.current?.endCall();
|
||||
webrtcServiceRef.current = null;
|
||||
setIsCalling(false);
|
||||
sendMessage({ type: 'call-ended' });
|
||||
}, [sendMessage]);
|
||||
|
||||
return { isCalling, startCall, hangUp };
|
||||
setIsCalling(false);
|
||||
setIsProcessing(false);
|
||||
wsMessage({ type: 'call-ended' });
|
||||
}, [wsMessage]);
|
||||
|
||||
return {
|
||||
isCalling,
|
||||
isProcessing,
|
||||
startCall,
|
||||
hangUp,
|
||||
};
|
||||
};
|
||||
|
||||
export default useCall;
|
||||
|
|
|
|||
|
|
@ -1,75 +1,44 @@
|
|||
import { useRef, useCallback } from 'react';
|
||||
import { WebRTCService } from '../services/WebRTC/WebRTCService';
|
||||
import type { RTCMessage } from '~/common';
|
||||
import useWebSocket from './useWebSocket';
|
||||
|
||||
const SILENCE_THRESHOLD = -50;
|
||||
const SILENCE_DURATION = 1000;
|
||||
|
||||
const useWebRTC = () => {
|
||||
const { sendMessage } = useWebSocket();
|
||||
const localStreamRef = useRef<MediaStream | null>(null);
|
||||
const audioContextRef = useRef<AudioContext | null>(null);
|
||||
const analyserRef = useRef<AnalyserNode | null>(null);
|
||||
const silenceStartTime = useRef<number | null>(null);
|
||||
const isProcessingRef = useRef(false);
|
||||
const webrtcServiceRef = useRef<WebRTCService | null>(null);
|
||||
|
||||
const log = (msg: string) => console.log(`[WebRTC ${new Date().toISOString()}] ${msg}`);
|
||||
|
||||
const processAudioLevel = () => {
|
||||
if (!analyserRef.current || !isProcessingRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dataArray = new Float32Array(analyserRef.current.frequencyBinCount);
|
||||
analyserRef.current.getFloatFrequencyData(dataArray);
|
||||
const average = dataArray.reduce((a, b) => a + b) / dataArray.length;
|
||||
|
||||
if (average < SILENCE_THRESHOLD) {
|
||||
if (!silenceStartTime.current) {
|
||||
silenceStartTime.current = Date.now();
|
||||
log(`Silence started: ${average}dB`);
|
||||
} else if (Date.now() - silenceStartTime.current > SILENCE_DURATION) {
|
||||
log('Silence threshold reached - requesting response');
|
||||
sendMessage({ type: 'request-response' });
|
||||
silenceStartTime.current = null;
|
||||
const handleRTCMessage = useCallback(
|
||||
(message: RTCMessage) => {
|
||||
switch (message.type) {
|
||||
case 'audio-chunk':
|
||||
sendMessage({ type: 'processing-start' });
|
||||
break;
|
||||
case 'transcription':
|
||||
case 'llm-response':
|
||||
case 'tts-chunk':
|
||||
// TODO: Handle streaming responses
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
silenceStartTime.current = null;
|
||||
}
|
||||
|
||||
requestAnimationFrame(processAudioLevel);
|
||||
};
|
||||
},
|
||||
[sendMessage],
|
||||
);
|
||||
|
||||
const startLocalStream = async () => {
|
||||
try {
|
||||
log('Starting audio capture');
|
||||
localStreamRef.current = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||
|
||||
audioContextRef.current = new AudioContext();
|
||||
const source = audioContextRef.current.createMediaStreamSource(localStreamRef.current);
|
||||
analyserRef.current = audioContextRef.current.createAnalyser();
|
||||
|
||||
source.connect(analyserRef.current);
|
||||
isProcessingRef.current = true;
|
||||
processAudioLevel();
|
||||
|
||||
log('Audio capture started');
|
||||
webrtcServiceRef.current = new WebRTCService(handleRTCMessage);
|
||||
await webrtcServiceRef.current.initializeCall();
|
||||
sendMessage({ type: 'call-start' });
|
||||
} catch (error) {
|
||||
log(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const stopLocalStream = useCallback(() => {
|
||||
log('Stopping audio capture');
|
||||
isProcessingRef.current = false;
|
||||
audioContextRef.current?.close();
|
||||
localStreamRef.current?.getTracks().forEach((track) => track.stop());
|
||||
|
||||
localStreamRef.current = null;
|
||||
audioContextRef.current = null;
|
||||
analyserRef.current = null;
|
||||
silenceStartTime.current = null;
|
||||
}, []);
|
||||
webrtcServiceRef.current?.endCall();
|
||||
webrtcServiceRef.current = null;
|
||||
sendMessage({ type: 'call-ended' });
|
||||
}, [sendMessage]);
|
||||
|
||||
return { startLocalStream, stopLocalStream };
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,39 +1,47 @@
|
|||
import { useEffect, useRef, useState, useCallback } from 'react';
|
||||
import { useGetWebsocketUrlQuery } from 'librechat-data-provider/react-query';
|
||||
import type { RTCMessage } from '~/common';
|
||||
|
||||
const useWebSocket = () => {
|
||||
const { data: url } = useGetWebsocketUrlQuery();
|
||||
const { data: data } = useGetWebsocketUrlQuery();
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
|
||||
console.log('wsConfig:', url?.url);
|
||||
|
||||
const connect = useCallback(() => {
|
||||
if (!url?.url) {
|
||||
if (!data || !data.url) {
|
||||
return;
|
||||
}
|
||||
|
||||
wsRef.current = new WebSocket(url?.url);
|
||||
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);
|
||||
|
||||
wsRef.current.onmessage = (event) => {
|
||||
const msg = JSON.parse(event.data);
|
||||
if (msg.type === 'audio-response') {
|
||||
const audioData = msg.data;
|
||||
const audio = new Audio(`data:audio/mp3;base64,${audioData}`);
|
||||
audio.play().catch(console.error);
|
||||
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;
|
||||
}
|
||||
};
|
||||
}, [url?.url]);
|
||||
}, [data?.url]);
|
||||
|
||||
useEffect(() => {
|
||||
connect();
|
||||
return () => wsRef.current?.close();
|
||||
}, [connect]);
|
||||
|
||||
const sendMessage = useCallback((message: any) => {
|
||||
const sendMessage = useCallback((message: Record<string, unknown>) => {
|
||||
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
||||
wsRef.current.send(JSON.stringify(message));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,36 +1,55 @@
|
|||
import type { RTCMessage } from '~/common';
|
||||
export class WebRTCService {
|
||||
private peerConnection: RTCPeerConnection | null = null;
|
||||
private dataChannel: RTCDataChannel | null = null;
|
||||
private mediaRecorder: MediaRecorder | null = null;
|
||||
private sendMessage: (msg: any) => void;
|
||||
private onMessage: (msg: RTCMessage) => void;
|
||||
|
||||
constructor(sendMessage: (msg: any) => void) {
|
||||
this.sendMessage = sendMessage;
|
||||
constructor(onMessage: (msg: RTCMessage) => void) {
|
||||
this.onMessage = onMessage;
|
||||
}
|
||||
|
||||
async initializeCall() {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||
this.peerConnection = new RTCPeerConnection();
|
||||
stream.getTracks().forEach((track) => this.peerConnection?.addTrack(track, stream));
|
||||
this.dataChannel = this.peerConnection.createDataChannel('audio');
|
||||
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||
this.mediaRecorder = new MediaRecorder(stream);
|
||||
|
||||
this.mediaRecorder.ondataavailable = (e) => {
|
||||
if (e.data.size > 0) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
this.sendMessage({
|
||||
type: 'audio-chunk',
|
||||
data: reader.result,
|
||||
});
|
||||
};
|
||||
reader.readAsDataURL(e.data);
|
||||
if (e.data.size > 0 && this.dataChannel?.readyState === 'open') {
|
||||
e.data.arrayBuffer().then((buffer) => {
|
||||
this.dataChannel?.send(buffer);
|
||||
});
|
||||
}
|
||||
};
|
||||
this.mediaRecorder.start();
|
||||
|
||||
this.mediaRecorder.start(100);
|
||||
this.setupDataChannel();
|
||||
}
|
||||
|
||||
private setupDataChannel() {
|
||||
if (!this.dataChannel) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.dataChannel.onmessage = (event) => {
|
||||
this.onMessage({
|
||||
type: 'audio-chunk',
|
||||
data: event.data,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
public async sendAudioChunk(audioBlob: Blob) {
|
||||
if (this.dataChannel && this.dataChannel.readyState === 'open') {
|
||||
this.dataChannel.send(await audioBlob.arrayBuffer());
|
||||
}
|
||||
}
|
||||
|
||||
async endCall() {
|
||||
this.mediaRecorder?.stop();
|
||||
this.dataChannel?.close();
|
||||
this.peerConnection?.close();
|
||||
this.peerConnection = null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
352
package-lock.json
generated
352
package-lock.json
generated
|
|
@ -127,6 +127,7 @@
|
|||
"winston": "^3.11.0",
|
||||
"winston-daily-rotate-file": "^4.7.1",
|
||||
"youtube-transcript": "^1.2.1",
|
||||
"wrtc": "^0.4.7",
|
||||
"ws": "^8.18.0",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
|
|
@ -24421,8 +24422,7 @@
|
|||
"node_modules/abbrev": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
|
||||
},
|
||||
"node_modules/abort-controller": {
|
||||
"version": "3.0.0",
|
||||
|
|
@ -26036,6 +26036,15 @@
|
|||
"node": ">= 0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
|
||||
"integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cohere-ai": {
|
||||
"version": "7.9.1",
|
||||
"resolved": "https://registry.npmjs.org/cohere-ai/-/cohere-ai-7.9.1.tgz",
|
||||
|
|
@ -26286,6 +26295,12 @@
|
|||
"integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
|
||||
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/constants-browserify": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
|
||||
|
|
@ -26977,6 +26992,12 @@
|
|||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/delegates": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
|
||||
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/denque": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
||||
|
|
@ -29196,6 +29217,25 @@
|
|||
"universalify": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-minipass": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz",
|
||||
"integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"minipass": "^2.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-minipass/node_modules/minipass": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
|
||||
"integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.1.2",
|
||||
"yallist": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
|
|
@ -29253,6 +29293,70 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/gauge": {
|
||||
"version": "2.7.4",
|
||||
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
|
||||
"integrity": "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==",
|
||||
"deprecated": "This package is no longer supported.",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"aproba": "^1.0.3",
|
||||
"console-control-strings": "^1.0.0",
|
||||
"has-unicode": "^2.0.0",
|
||||
"object-assign": "^4.1.0",
|
||||
"signal-exit": "^3.0.0",
|
||||
"string-width": "^1.0.1",
|
||||
"strip-ansi": "^3.0.1",
|
||||
"wide-align": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/gauge/node_modules/ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
|
||||
"integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/gauge/node_modules/is-fullwidth-code-point": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
|
||||
"integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"number-is-nan": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/gauge/node_modules/string-width": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
|
||||
"integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"code-point-at": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^1.0.0",
|
||||
"strip-ansi": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/gauge/node_modules/strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
"integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/gaxios": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.2.0.tgz",
|
||||
|
|
@ -29797,6 +29901,12 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-unicode": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
|
||||
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/hash-base": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz",
|
||||
|
|
@ -34877,6 +34987,25 @@
|
|||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/minizlib": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz",
|
||||
"integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"minipass": "^2.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/minizlib/node_modules/minipass": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
|
||||
"integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.1.2",
|
||||
"yallist": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
|
|
@ -35023,6 +35152,32 @@
|
|||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/needle": {
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/needle/-/needle-2.9.1.tgz",
|
||||
"integrity": "sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^3.2.6",
|
||||
"iconv-lite": "^0.4.4",
|
||||
"sax": "^1.2.4"
|
||||
},
|
||||
"bin": {
|
||||
"needle": "bin/needle"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4.4.x"
|
||||
}
|
||||
},
|
||||
"node_modules/needle/node_modules/debug": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
|
||||
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||
|
|
@ -35115,6 +35270,108 @@
|
|||
"integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/node-pre-gyp": {
|
||||
"version": "0.17.0",
|
||||
"resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.17.0.tgz",
|
||||
"integrity": "sha512-abzZt1hmOjkZez29ppg+5gGqdPLUuJeAEwVPtHYEJgx0qzttCbcKFpxrCQn2HYbwCv2c+7JwH4BgEzFkUGpn4A==",
|
||||
"deprecated": "Please upgrade to @mapbox/node-pre-gyp: the non-scoped node-pre-gyp package is deprecated and only the @mapbox scoped package will recieve updates in the future",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"detect-libc": "^1.0.3",
|
||||
"mkdirp": "^0.5.5",
|
||||
"needle": "^2.5.2",
|
||||
"nopt": "^4.0.3",
|
||||
"npm-packlist": "^1.4.8",
|
||||
"npmlog": "^4.1.2",
|
||||
"rc": "^1.2.8",
|
||||
"rimraf": "^2.7.1",
|
||||
"semver": "^5.7.1",
|
||||
"tar": "^4.4.13"
|
||||
},
|
||||
"bin": {
|
||||
"node-pre-gyp": "bin/node-pre-gyp"
|
||||
}
|
||||
},
|
||||
"node_modules/node-pre-gyp/node_modules/detect-libc": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
||||
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"detect-libc": "bin/detect-libc.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/node-pre-gyp/node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.1.1",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/node-pre-gyp/node_modules/mkdirp": {
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
||||
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.6"
|
||||
},
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
}
|
||||
},
|
||||
"node_modules/node-pre-gyp/node_modules/nopt": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz",
|
||||
"integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"abbrev": "1",
|
||||
"osenv": "^0.1.4"
|
||||
},
|
||||
"bin": {
|
||||
"nopt": "bin/nopt.js"
|
||||
}
|
||||
},
|
||||
"node_modules/node-pre-gyp/node_modules/rimraf": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
||||
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"glob": "^7.1.3"
|
||||
},
|
||||
"bin": {
|
||||
"rimraf": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/node-pre-gyp/node_modules/semver": {
|
||||
"version": "5.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
||||
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver"
|
||||
}
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.18",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
|
||||
|
|
@ -35324,6 +35581,32 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/npm-bundled": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz",
|
||||
"integrity": "sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"npm-normalize-package-bin": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/npm-normalize-package-bin": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz",
|
||||
"integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/npm-packlist": {
|
||||
"version": "1.4.8",
|
||||
"resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz",
|
||||
"integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"ignore-walk": "^3.0.1",
|
||||
"npm-bundled": "^1.0.1",
|
||||
"npm-normalize-package-bin": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/npm-run-path": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
|
||||
|
|
@ -39756,6 +40039,12 @@
|
|||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"node_modules/sax": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
|
||||
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/saxes": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
|
||||
|
|
@ -40996,6 +41285,28 @@
|
|||
"streamx": "^2.15.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tar/node_modules/minipass": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
|
||||
"integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.1.2",
|
||||
"yallist": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tar/node_modules/mkdirp": {
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
||||
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.6"
|
||||
},
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
}
|
||||
},
|
||||
"node_modules/temp-dir": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz",
|
||||
|
|
@ -43535,6 +43846,43 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/wrtc": {
|
||||
"version": "0.4.7",
|
||||
"resolved": "https://registry.npmjs.org/wrtc/-/wrtc-0.4.7.tgz",
|
||||
"integrity": "sha512-P6Hn7VT4lfSH49HxLHcHhDq+aFf/jd9dPY7lDHeFhZ22N3858EKuwm2jmnlPzpsRGEPaoF6XwkcxY5SYnt4f/g==",
|
||||
"bundleDependencies": [
|
||||
"node-pre-gyp"
|
||||
],
|
||||
"hasInstallScript": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"node-pre-gyp": "^0.13.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^8.11.2 || >=10.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"domexception": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/wrtc/node_modules/domexception": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz",
|
||||
"integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==",
|
||||
"deprecated": "Use your platform's native DOMException instead",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"webidl-conversions": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/wrtc/node_modules/webidl-conversions": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
|
||||
"integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==",
|
||||
"license": "BSD-2-Clause",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue