diff --git a/api/package.json b/api/package.json index 2685104a86..630273b8e6 100644 --- a/api/package.json +++ b/api/package.json @@ -82,6 +82,7 @@ "mongoose": "^8.9.5", "multer": "^1.4.5-lts.1", "nanoid": "^3.3.7", + "node-pre-gyp": "^0.17.0", "nodemailer": "^6.9.15", "ollama": "^0.5.0", "openai": "^4.47.1", @@ -102,6 +103,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/services/WebSocket/WebSocketServer.js b/api/server/services/WebSocket/WebSocketServer.js index 602e20851e..be79f3a9a1 100644 --- a/api/server/services/WebSocket/WebSocketServer.js +++ b/api/server/services/WebSocket/WebSocketServer.js @@ -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 = ''; } }; diff --git a/client/src/common/types.ts b/client/src/common/types.ts index 380ec573b8..97bd105644 100644 --- a/client/src/common/types.ts +++ b/client/src/common/types.ts @@ -48,6 +48,17 @@ export type AudioChunk = { }; }; +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; diff --git a/client/src/hooks/useCall.ts b/client/src/hooks/useCall.ts index 91dc2effb0..2890c9a601 100644 --- a/client/src/hooks/useCall.ts +++ b/client/src/hooks/useCall.ts @@ -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(null); const analyserRef = useRef(null); + const audioChunksRef = useRef([]); const silenceStartRef = useRef(null); const intervalRef = useRef(null); const webrtcServiceRef = useRef(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; diff --git a/client/src/hooks/useWebRTC.ts b/client/src/hooks/useWebRTC.ts index 436b1da1ea..0e951a531b 100644 --- a/client/src/hooks/useWebRTC.ts +++ b/client/src/hooks/useWebRTC.ts @@ -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(null); - const audioContextRef = useRef(null); - const analyserRef = useRef(null); - const silenceStartTime = useRef(null); - const isProcessingRef = useRef(false); + const webrtcServiceRef = useRef(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 }; }; diff --git a/client/src/hooks/useWebSocket.ts b/client/src/hooks/useWebSocket.ts index 4d070f499f..fc16ec13af 100644 --- a/client/src/hooks/useWebSocket.ts +++ b/client/src/hooks/useWebSocket.ts @@ -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(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) => { if (wsRef.current?.readyState === WebSocket.OPEN) { wsRef.current.send(JSON.stringify(message)); } diff --git a/client/src/services/WebRTC/WebRTCService.ts b/client/src/services/WebRTC/WebRTCService.ts index 6b7ec2bfd8..9f0564d078 100644 --- a/client/src/services/WebRTC/WebRTCService.ts +++ b/client/src/services/WebRTC/WebRTCService.ts @@ -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; } } diff --git a/package-lock.json b/package-lock.json index 58988c0295..d039c16f17 100644 --- a/package-lock.json +++ b/package-lock.json @@ -98,6 +98,7 @@ "mongoose": "^8.9.5", "multer": "^1.4.5-lts.1", "nanoid": "^3.3.7", + "node-pre-gyp": "^0.17.0", "nodemailer": "^6.9.15", "ollama": "^0.5.0", "openai": "^4.47.1", @@ -118,6 +119,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" @@ -17171,8 +17173,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", @@ -17379,6 +17380,21 @@ "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" }, + "node_modules/aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "node_modules/are-we-there-yet": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", + "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -18861,6 +18877,14 @@ "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==", + "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", @@ -19110,6 +19134,11 @@ "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==" + }, "node_modules/constants-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", @@ -19827,6 +19856,11 @@ "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==" + }, "node_modules/denque": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", @@ -22111,6 +22145,23 @@ "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==", + "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==", + "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", @@ -22167,6 +22218,65 @@ "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.", + "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==", + "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==", + "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==", + "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==", + "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", @@ -22833,6 +22943,11 @@ "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==" + }, "node_modules/hash-base": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", @@ -23485,6 +23600,14 @@ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", "dev": true }, + "node_modules/ignore-walk": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.4.tgz", + "integrity": "sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ==", + "dependencies": { + "minimatch": "^3.0.4" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -28118,6 +28241,23 @@ "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==", + "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==", + "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", @@ -28395,6 +28535,30 @@ "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==", + "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==", + "dependencies": { + "ms": "^2.1.1" + } + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -28511,6 +28675,101 @@ "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", + "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==", + "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", + "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==", + "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==", + "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", + "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==", + "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", @@ -28720,6 +28979,29 @@ "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==", + "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==" + }, + "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==", + "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", @@ -28732,6 +29014,18 @@ "node": ">=8" } }, + "node_modules/npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -28745,6 +29039,14 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/nwsapi": { "version": "2.2.7", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", @@ -29067,6 +29369,32 @@ "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==", "dev": true }, + "node_modules/os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, "node_modules/outvariant": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.0.tgz", @@ -33220,6 +33548,11 @@ "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==" + }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -33348,6 +33681,11 @@ "node": ">= 0.8.0" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -33540,8 +33878,7 @@ "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, "node_modules/simple-concat": { "version": "1.0.1", @@ -34496,6 +34833,23 @@ "node": ">=6" } }, + "node_modules/tar": { + "version": "4.4.19", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", + "integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==", + "dependencies": { + "chownr": "^1.1.4", + "fs-minipass": "^1.2.7", + "minipass": "^2.9.0", + "minizlib": "^1.3.3", + "mkdirp": "^0.5.5", + "safe-buffer": "^5.2.1", + "yallist": "^3.1.1" + }, + "engines": { + "node": ">=4.5" + } + }, "node_modules/tar-fs": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", @@ -34516,6 +34870,26 @@ "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==", + "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==", + "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", @@ -36411,6 +36785,40 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wide-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/wide-align/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/wide-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/winston": { "version": "3.11.0", "resolved": "https://registry.npmjs.org/winston/-/winston-3.11.0.tgz", @@ -37092,6 +37500,40 @@ "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, + "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", + "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==", + "optional": true + }, "node_modules/ws": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",