From 39ded555a46e6c8651a27628d066223523fd48d9 Mon Sep 17 00:00:00 2001 From: balazskreith Date: Sat, 2 Nov 2024 08:09:14 +0200 Subject: [PATCH] HA update --- .../templates/mediaserver.yaml | 15 + media-server/configs/local-2.yaml | 51 ++ media-server/package.json | 6 +- media-server/src/Server.ts | 57 +- .../client-listeners/ClientMessageListener.ts | 4 +- ...ClientMonitorSampleNotificationListener.ts | 96 +-- .../ConnectTransportRequestListener.ts | 10 +- .../ControlConsumerNotificationListener.ts | 6 +- .../ControlProducerNotificationListener.ts | 6 +- .../ControlTransportNotificationListener.ts | 9 +- .../CreateProducerRequestListener.ts | 85 ++- .../CreateTransportRequestListener.ts | 82 ++- .../JoinCallRequestListener.ts | 89 ++- .../ObserverRequestListener.ts | 1 - media-server/src/common/ClientContext.ts | 10 +- media-server/src/common/utils.ts | 25 +- media-server/src/config.ts | 26 +- .../ClientMonitorSampleListener.ts | 67 ++ .../ConnectPipeTransportListener.ts | 52 ++ .../ConsumeMediaProducerListener.ts | 97 +++ .../CreatePipeTransportListener.ts | 39 ++ .../GetClientProducersListener.ts | 34 + .../PipeMediaProducerToListener.ts | 60 ++ .../PipedMediaConsumerClosedListener.ts | 26 + .../src/intervals/CloseClientInterval.ts | 27 + media-server/src/main.ts | 159 +++-- .../protocols/HamokServiceEventProtocol.ts | 54 ++ media-server/src/protocols/MessageProtocol.ts | 3 + media-server/src/services/HamokService.ts | 519 ++++++++++++++ .../src/services/IntervalTrackerService.ts | 39 ++ media-server/src/services/MediasoupService.ts | 253 ++++++- media-server/yarn.lock | 631 +++++++++--------- webapp/src/actions/actions.ts | 23 +- webapp/src/components/Countdown/Countdown.tsx | 109 +++ webapp/src/components/Countdown/style.css | 120 ++++ webapp/src/stores/LocalClientStore.tsx | 3 + webapp/src/stores/callConfigStore.tsx | 7 +- webapp/src/utils/Connection.tsx | 17 +- webapp/src/utils/MessageProtocol.ts | 3 + webapp/src/views/Join.tsx | 21 +- webapp/src/views/VideoCall.tsx | 3 + 41 files changed, 2320 insertions(+), 624 deletions(-) create mode 100644 media-server/configs/local-2.yaml create mode 100644 media-server/src/hamok-listeners/ClientMonitorSampleListener.ts create mode 100644 media-server/src/hamok-listeners/ConnectPipeTransportListener.ts create mode 100644 media-server/src/hamok-listeners/ConsumeMediaProducerListener.ts create mode 100644 media-server/src/hamok-listeners/CreatePipeTransportListener.ts create mode 100644 media-server/src/hamok-listeners/GetClientProducersListener.ts create mode 100644 media-server/src/hamok-listeners/PipeMediaProducerToListener.ts create mode 100644 media-server/src/hamok-listeners/PipedMediaConsumerClosedListener.ts create mode 100644 media-server/src/intervals/CloseClientInterval.ts create mode 100644 media-server/src/protocols/HamokServiceEventProtocol.ts create mode 100644 media-server/src/services/HamokService.ts create mode 100644 media-server/src/services/IntervalTrackerService.ts create mode 100644 webapp/src/components/Countdown/Countdown.tsx create mode 100644 webapp/src/components/Countdown/style.css diff --git a/charts/webrtc-observer-org/templates/mediaserver.yaml b/charts/webrtc-observer-org/templates/mediaserver.yaml index 27cef2d..43f39c7 100644 --- a/charts/webrtc-observer-org/templates/mediaserver.yaml +++ b/charts/webrtc-observer-org/templates/mediaserver.yaml @@ -87,6 +87,21 @@ data: port: 9081 serverIp: {{ .Values.publicIP }} announcedIp: {{ .Values.publicIP }} + maxTransportsPerRouter: 6 + maxProducerPerClients: 2 + maxClientLifeTimeInMins: 15 + hamok: + clientsMap: + mapId: "webrtc-observer-clients-map" + eventEmitter: + emitterId: "webrtc-observer-media-service-events" + roomsMap: + mapId: "webrtc-observer-rooms-map" + redis: + host: "redis" + port: 6379 + redisChannelId: "webrtc-observer-hamok-message-channel" + devMode: false mediasoup: numberOfWorkers: 2 workerSettings: diff --git a/media-server/configs/local-2.yaml b/media-server/configs/local-2.yaml new file mode 100644 index 0000000..eb0c543 --- /dev/null +++ b/media-server/configs/local-2.yaml @@ -0,0 +1,51 @@ +server: + port: 9081 + serverIp: "127.0.0.1" + # announcedIp: "127.0.0.1" + +hamok: + clientsMap: + mapId: "webrtc-observer-clients-map" + eventEmitter: + emitterId: "webrtc-observer-media-service-events" + roomsMap: + mapId: "webrtc-observer-rooms-map" + redis: + host: "localhost" + port: 6379 + redisChannelId: "webrtc-observer-hamok-message-channel" + devMode: true + +# stunnerAuthUrl: "http://stunner-auth.stunner-system:8088?service=turn" +maxTransportsPerRouter: 6 +maxProducerPerClients: 2 +maxClientLifeTimeInMins: 15 + +mediasoup: + numberOfWorkers: 1 + workerSettings: + logLevel: "warn" + logTags: + - "info" + - "ice" + - "dtls" + - "rtp" + - "srtp" + - "rtcp" + rtcMinPort: 42000 + rtcMaxPort: 43000 + mediaCodecs: + - kind: "audio" + mimeType: "audio/opus" + clockRate: 48000 + channels: 2 + - kind: "video" + mimeType: "video/VP8" + clockRate: 90000 + webRtcServerSettings: + - listenInfos: + - ip: "127.0.0.1" + # ip: "0.0.0.0" + protocol: "udp" + # announcedAddress: "127.0.0.1" + port: 5001 diff --git a/media-server/package.json b/media-server/package.json index da88055..ade8fce 100644 --- a/media-server/package.json +++ b/media-server/package.json @@ -7,6 +7,7 @@ "build": "tsc --build", "clean": "tsc --build --clean", "dev": "nodemon -x ts-node src/main.ts | pino-pretty", + "dev:2": "CONFIG_PATH=./configs/local-2.yaml nodemon --ignore ./sfu-local-files -x ts-node src/main.ts | pino-pretty", "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { @@ -32,6 +33,8 @@ "@observertc/sfu-monitor-js": "^2.0.0", "@types/ws": "^8.2.2", "events": "^3.3.0", + "hamok": "^2.6.1-76ef244.0", + "ioredis": "^5.4.1", "jsonwebtoken": "^9.0.0", "kafkajs": "^2.2.4", "mediasoup": "^3.14.7", @@ -45,11 +48,12 @@ "@tsconfig/node20": "^1.0.2", "@types/events": "^3.0.0", "@types/jest": "^29.5.1", + "@types/node": "^22.8.4", "@types/pino": "^7.0.5", "@types/uuid": "^10.0.0", "@types/yaml": "^1.9.7", - "pino-pretty": "^11.2.2", "nodemon": "^3.0.1", + "pino-pretty": "^11.2.2", "ts-jest": "^29.1.0", "ts-node": "^10.9.1", "ts-node-dev": "^2.0.0", diff --git a/media-server/src/Server.ts b/media-server/src/Server.ts index 6a9afed..552e901 100644 --- a/media-server/src/Server.ts +++ b/media-server/src/Server.ts @@ -1,9 +1,9 @@ import * as http from 'http'; -import { WebSocketServer } from 'ws'; +import { WebSocketServer, WebSocket } from 'ws'; import { createLogger } from "./common/logger"; import url from 'url'; import { ClientContext } from './common/ClientContext'; -import { ClientMessageContext } from './client-listeners/ClientMessageListener'; +import { ClientMessageContext, ClientMessageListener } from './client-listeners/ClientMessageListener'; import { ClientMessage } from './protocols/MessageProtocol'; import { EventEmitter } from 'events'; @@ -49,7 +49,11 @@ export class Server extends EventEmitter { private _state: ServerState = 'idle'; private _httpServer?: http.Server; private _wsServer?: WebSocketServer; - + + public readonly clients = new Map(); + public readonly messageListeners = new Map(); + public createClientContext?: (base: Pick & { callId?: string }) => Promise; + public constructor( public readonly config: ServerConfig, ) { @@ -139,38 +143,53 @@ export class Server extends EventEmitter { }); wsServer.on('connection', async (ws, req) => { + if (!this.createClientContext) { + logger.error(`createClientContext is not set`); + return ws.close(4001, 'Server error'); + } + // console.warn("\n\n", url.parse(req.url, true).query, "\n\n"); const query = url.parse(req.url ?? '', true).query; const clientId = query.clientId as string; - const schemaVersion = query.schemaVersion as string; + // const schemaVersion = query.schemaVersion as string; const send = (message: ClientMessage) => { const data = JSON.stringify(message); ws.send(data); } - const clientContext: ClientContext = { - userId: query.userId as string, - clientId, - schemaVersion, - webSocket: ws, - send, - mediaProducers: new Set(), - mediaConsumers: new Set(), - } + const clientContext = await this.createClientContext({ + callId: typeof query.callId === 'string' ? query.callId : undefined, + webSocket: ws, + clientId, + send, + userId: typeof query.userId === 'string' ? query.userId : 'unknown-user', + }); this.emit('newclient', clientContext); - ws.on('message', data => { + ws.on('message', async data => { const message = JSON.parse(data.toString()); const messageContext: ClientMessageContext = { - clientId, + client: clientContext, message, send, - get callId() { - return clientContext.routerId; - }, } - this.emit('newmessage', messageContext); + const listener = this.messageListeners.get(message.type); + + if (!listener) return logger.warn(`No listener found for message type ${message.type}`); + + try { + await listener(messageContext); + } catch (err) { + logger.error(`Error occurred while processing message: %o`, err); + } + // this.emit('newmessage', messageContext); + }); + + clientContext.webSocket.once('close', () => { + this.clients.delete(clientId); }); + this.clients.set(clientId, clientContext); + logger.info(`Websocket connection is requested from ${req.socket.remoteAddress}, query:`, query); }); wsServer.on('error', error => { diff --git a/media-server/src/client-listeners/ClientMessageListener.ts b/media-server/src/client-listeners/ClientMessageListener.ts index 75e8a5d..2ca191d 100644 --- a/media-server/src/client-listeners/ClientMessageListener.ts +++ b/media-server/src/client-listeners/ClientMessageListener.ts @@ -1,8 +1,8 @@ +import { ClientContext } from "../common/ClientContext" import { ClientMessage } from "../protocols/MessageProtocol" export type ClientMessageContext = { - readonly callId?: string, - clientId: string, + client: ClientContext, message: ClientMessage, send: (message: ClientMessage) => void, } diff --git a/media-server/src/client-listeners/ClientMonitorSampleNotificationListener.ts b/media-server/src/client-listeners/ClientMonitorSampleNotificationListener.ts index a0c363c..87a7df1 100644 --- a/media-server/src/client-listeners/ClientMonitorSampleNotificationListener.ts +++ b/media-server/src/client-listeners/ClientMonitorSampleNotificationListener.ts @@ -5,109 +5,41 @@ import { ClientMessageContext } from "./ClientMessageListener"; import { Kafka, Producer } from "kafkajs"; import { SampleMessage } from "../protocols/SampleMessage"; import { MainEmitter } from "../common/MainEmitter"; +import { HamokService } from "../services/HamokService"; const logger = createLogger('ClientMonitorSampleNotificatinListener'); export type ClientMonitorSampleNotificatinListenerContext = { - clients: Map, - observer: Observer, - mainEmitter: MainEmitter + // clients: Map, + // observer: Observer, + // mainEmitter: MainEmitter + hamokService: HamokService, } export function createClientMonitorSampleNotificatinListener(listenerContext: ClientMonitorSampleNotificatinListenerContext) { const { - observer, - clients, - mainEmitter, + hamokService, } = listenerContext; const result = async (messageContext: ClientMessageContext) => { const { + client, message: notification, } = messageContext; - const client = clients.get(messageContext.clientId); + if (notification.type !== 'client-monitor-sample-notification') { return console.warn(`Invalid message type ${notification.type}`); - } else if (!client) { - return console.warn(`Client ${messageContext.clientId} not found`); - } else if (!client.decoder) { - return console.warn(`Client ${messageContext.clientId} decoder not found`); - } else if (!client.routerId) { - return console.warn(`Client ${messageContext.clientId} routerId not found`); - } - - let observedCall = observer.observedCalls.get(client.routerId); - if (!observedCall) { - observedCall = observer.createObservedCall({ - roomId: client.routerId, - callId: client.routerId, - serviceId: 'demo-service', - appData: { - - }, - }); - - observedCall.once('close', () => { - // once it is closed - }); } - - let observedClient = observedCall.clients.get(client.clientId); - if (!observedClient) { - const newObservedClient = observedCall.createClient({ - clientId: client.clientId, - userId: client.userId, - mediaUnitId: 'webapp', - appData: { - - }, - }); - - client.webSocket.once('close', () => { - newObservedClient.close(); - if (newObservedClient.call.clients.size === 0) { - newObservedClient.call.close(); - } - }); - observedClient = newObservedClient; - } - - const sample = client.decoder.decodeFromBase64(notification.sample); - - observedClient.accept(sample); - - mainEmitter.emit('sample', { + + hamokService.publishClientSample({ + callId: client.callId, clientId: client.clientId, - callId: client.routerId, - mediaUnitId: 'webapp', - roomId: client.routerId, sampleInBase64: notification.sample, - serviceId: 'demo-service', + mediaUnitId: 'webapp', + roomId: client.roomId, + serviceId: 'webrtc-observer', userId: client.userId, }); }; - - - - // const consumer = kafka.consumer({ - // groupId: 'client-monitor-sample-notification', - // }); - - // consumer.connect().then(() => { - // consumer.subscribe({ topic: 'client-sample' }).then(() => { - // consumer.run({ - // eachMessage: async ({ topic, partition, message, heartbeat, pause }) => { - // console.log({ - // topic, - // partition, - // key: message.key?.toString(), - // value: message.value?.toString(), - // headers: message.headers, - // }) - // }, - // }); - // }); - // }); - return result; } \ No newline at end of file diff --git a/media-server/src/client-listeners/ConnectTransportRequestListener.ts b/media-server/src/client-listeners/ConnectTransportRequestListener.ts index c0def81..822ce6e 100644 --- a/media-server/src/client-listeners/ConnectTransportRequestListener.ts +++ b/media-server/src/client-listeners/ConnectTransportRequestListener.ts @@ -7,30 +7,22 @@ import { ClientMessageContext } from "./ClientMessageListener"; const logger = createLogger('ConnectTransportRequestListener'); export type ConnectTransportRequestListenerContext = { - mediasoupService: MediasoupService; - clients: Map; } export function createConnectTransportRequestListener(listenerContext: ConnectTransportRequestListenerContext) { const { - mediasoupService, - clients, } = listenerContext; const result = async (messageContext: ClientMessageContext) => { const { + client, message: request, } = messageContext; - const client = clients.get(messageContext.clientId); if (request.type !== 'connect-transport-request') { return console.warn(`Invalid message type ${request.type}`); - } else if (!client) { - return console.warn(`Client ${messageContext.clientId} not found`); } - logger.debug(`Transport ${request.transportId} attempt to connect`); - let response: ConnectTransportResponsePayload | undefined; let error: string | undefined; diff --git a/media-server/src/client-listeners/ControlConsumerNotificationListener.ts b/media-server/src/client-listeners/ControlConsumerNotificationListener.ts index 27cc548..d676b4b 100644 --- a/media-server/src/client-listeners/ControlConsumerNotificationListener.ts +++ b/media-server/src/client-listeners/ControlConsumerNotificationListener.ts @@ -7,25 +7,23 @@ const logger = createLogger('ControlConsumerNotificationListener'); export type ControlConsumerNotificationListenerContext = { mediasoupService: MediasoupService, - clients: Map, } export function createControlConsumerNotificationListener(listenerContext: ControlConsumerNotificationListenerContext): ClientMessageListener { const { mediasoupService, - clients, } = listenerContext; const result = async (messageContext: ClientMessageContext) => { const { message: request, + client, } = messageContext; - const client = clients.get(messageContext.clientId); if (request.type !== 'control-consumer-notification') { return console.warn(`Invalid message type ${request.type}`); } else if (!client?.mediaConsumers.has(request.consumerId)) { - return console.warn(`Consumer ${request.consumerId} not found for client ${messageContext.clientId}`); + return console.warn(`Consumer ${request.consumerId} not found for client ${client.clientId}`); } const consumer = mediasoupService.mediaConsumers.get(request.consumerId); diff --git a/media-server/src/client-listeners/ControlProducerNotificationListener.ts b/media-server/src/client-listeners/ControlProducerNotificationListener.ts index 5c0d60a..86b2514 100644 --- a/media-server/src/client-listeners/ControlProducerNotificationListener.ts +++ b/media-server/src/client-listeners/ControlProducerNotificationListener.ts @@ -7,25 +7,23 @@ const logger = createLogger('ControlProducerNotificationListener'); export type ControlProducerNotificationListenerContext = { mediasoupService: MediasoupService, - clients: Map, } export function createControlProducerNotificationListener(listenerContext: ControlProducerNotificationListenerContext): ClientMessageListener { const { mediasoupService, - clients, } = listenerContext; const result = async (messageContext: ClientMessageContext) => { const { message: request, + client, } = messageContext; - const client = clients.get(messageContext.clientId); if (request.type !== 'control-producer-notification') { return console.warn(`Invalid message type ${request.type}`); } else if (!client?.mediaProducers.has(request.producerId)) { - return console.warn(`Producer ${request.producerId} not found for client ${messageContext.clientId}`); + return console.warn(`Producer ${request.producerId} not found for client ${client.clientId}`); } const producer = mediasoupService.mediaProducers.get(request.producerId); diff --git a/media-server/src/client-listeners/ControlTransportNotificationListener.ts b/media-server/src/client-listeners/ControlTransportNotificationListener.ts index d7ad0f2..f0d7b91 100644 --- a/media-server/src/client-listeners/ControlTransportNotificationListener.ts +++ b/media-server/src/client-listeners/ControlTransportNotificationListener.ts @@ -6,24 +6,19 @@ import { ClientMessageContext } from "./ClientMessageListener"; const logger = createLogger('CreateProducerNotificationListener'); export type CreateControlTransportNotificationListenerContext = { - mediasoupService: MediasoupService, - clients: Map, } export function createControlTransportNotificationListener(listenerContext: CreateControlTransportNotificationListenerContext) { const { - mediasoupService, - clients, } = listenerContext; const result = async (messageContext: ClientMessageContext) => { const { message: notification, + client, } = messageContext; - const client = clients.get(messageContext.clientId); + if (notification.type !== 'control-transport-notification') { return console.warn(`Invalid message type ${notification.type}`); - } else if (!client) { - return console.warn(`Client ${messageContext.clientId} not found`); } try { diff --git a/media-server/src/client-listeners/CreateProducerRequestListener.ts b/media-server/src/client-listeners/CreateProducerRequestListener.ts index 637d674..1500ddf 100644 --- a/media-server/src/client-listeners/CreateProducerRequestListener.ts +++ b/media-server/src/client-listeners/CreateProducerRequestListener.ts @@ -1,6 +1,7 @@ import { ClientContext } from "../common/ClientContext"; import { createLogger } from "../common/logger"; import { ConsumerCreatedNotification, CreateProducerResponsePayload, Response } from "../protocols/MessageProtocol"; +import { HamokService } from "../services/HamokService"; import { MediasoupService } from "../services/MediasoupService" import { ClientMessageContext } from "./ClientMessageListener"; import * as mediasoup from 'mediasoup'; @@ -9,29 +10,27 @@ const logger = createLogger('CreateProducerRequestListener'); export type CreateProducerRequestListenerContext = { mediasoupService: MediasoupService, - clients: Map, + hamokService: HamokService, maxProducerPerClients: number, } export function createCreateProducerRequestListener(listenerContext: CreateProducerRequestListenerContext) { const { - mediasoupService, - clients, + hamokService, } = listenerContext; const result = async (messageContext: ClientMessageContext) => { const { message: request, + client, } = messageContext; - const client = clients.get(messageContext.clientId); + if (request.type !== 'create-producer-request') { return console.warn(`Invalid message type ${request.type}`); - } else if (!client) { - return console.warn(`Client ${messageContext.clientId} not found`); - } else if (!client.routerId) { - return console.warn(`Client ${messageContext.clientId} routerId not found`); + }else if (!client.routerId) { + return console.warn(`Client ${client.clientId} routerId not found`); } else if (client.sndTransport === undefined) { - return console.warn(`Client ${messageContext.clientId} has no sending transport`); + return console.warn(`Client ${client.clientId} has no sending transport`); } else if (client.mediaProducers.size >= listenerContext.maxProducerPerClients) { return messageContext.send(new Response( request.requestId, @@ -40,62 +39,60 @@ export function createCreateProducerRequestListener(listenerContext: CreateProdu )); } + let mediaProducer: mediasoup.types.Producer | undefined; let response: CreateProducerResponsePayload | undefined; let error: string | undefined; try { - - - const producer = await client.sndTransport.produce({ + const newproducer = await client.sndTransport.produce({ kind: request.kind, rtpParameters: request.rtpParameters, }); - producer.observer.once('close', () => { - client.mediaProducers.delete(producer.id); + newproducer.observer.once('close', () => { + client.mediaProducers.delete(newproducer.id); }); - client.mediaProducers.add(producer.id); + client.mediaProducers.add(newproducer.id); response = { - producerId: producer.id, + producerId: newproducer.id, }; - for (const consumingClient of clients.values()) { - if (client.clientId === consumingClient.clientId) continue; - if (client.routerId !== consumingClient.routerId) continue; - - if (!consumingClient) continue; - try { - const consumer = await mediasoupService.consumeMediaProducer(producer.id, consumingClient); - - consumingClient.send(new ConsumerCreatedNotification( - consumer.id, - producer.id, - consumer.kind, - consumer.rtpParameters, - { - producerId: producer.id, - paused: producer.paused, - }, - { - clientId: client.clientId, - userId: client.userId, - } - )) - } catch (err) { - logger.error(`Error occurred while trying to consume media producer ${producer.id}`, err); - } - } - + mediaProducer = newproducer; } catch (err) { error = `${err}`; } - messageContext.send(new Response( request.requestId, response, error )); + + if (mediaProducer) { + // setInterval(async () => { + // const stats = await mediaProducer.getStats(); + // console.log('Producer stats', stats); + // }, 5000); + try { + for (const [consumingClientId, consumingClient] of hamokService.clients.entries()) { + if (consumingClientId === client.clientId) continue; + if (consumingClient.roomId !== client.roomId) continue; + if (consumingClient.callId !== client.callId) continue; + if (!consumingClient.routerId) continue; + + hamokService.consumeMediaProducer({ + consumingClientId: consumingClient.clientId, + producingClientId: client.clientId, + producingUserId: client.userId, + mediaProducerId: mediaProducer.id, + producingRouterId: client.routerId, + }); + } + } catch (err) { + logger.error(`Error occurred while trying to consume media producer ${mediaProducer.id}`, err); + } + } + }; return result; } \ No newline at end of file diff --git a/media-server/src/client-listeners/CreateTransportRequestListener.ts b/media-server/src/client-listeners/CreateTransportRequestListener.ts index 6b51cb1..3a9e944 100644 --- a/media-server/src/client-listeners/CreateTransportRequestListener.ts +++ b/media-server/src/client-listeners/CreateTransportRequestListener.ts @@ -4,33 +4,41 @@ import { createLogger } from "../common/logger"; import { ConsumerCreatedNotification, CreateTransportResponsePayload, Response } from "../protocols/MessageProtocol"; import { MediasoupService } from "../services/MediasoupService"; import * as mediasoup from 'mediasoup'; +import { ClientMessageContext } from "./ClientMessageListener"; +import { HamokService } from "../services/HamokService"; const logger = createLogger('CreateTransportRequestListener'); export type CreateTransportRequestListenerContext = { server: Server, mediasoupService: MediasoupService; - clients: Map; + hamokService: HamokService, }; export function createCreateTransportRequestListener(listenerContext: CreateTransportRequestListenerContext) { const { server, mediasoupService, - clients, + hamokService, } = listenerContext; - const result = async (messageContext: any) => { + const result = async (messageContext: ClientMessageContext) => { const { + client, message: request, + send, } = messageContext; - const client = clients.get(messageContext.clientId); if (request.type !== 'create-transport-request') { return console.warn(`Invalid message type ${request.type}`); - } else if (!client) { - return console.warn(`Client ${messageContext.clientId} not found`); + } else if (!client.routerId) { + return send(new Response( + request.requestId, + undefined, + `Client ${client.clientId} has not joined a call yet` + )); } + const { role, requestId } = request; const router = mediasoupService.routers.get(client.routerId ?? ''); const worker = mediasoupService.workers.get(router?.appData.workerPid ?? 0); @@ -76,11 +84,15 @@ export function createCreateTransportRequestListener(listenerContext: CreateTran transport.observer.once('close', () => { if (role === 'producing') client.sndTransport = undefined; - else if (role === 'consuming') client.rcvTransport = undefined - }) + else if (role === 'consuming') { + client.rcvTransport = undefined; + } + }); if (role === 'producing') client.sndTransport = transport; - else if (role === 'consuming') client.rcvTransport = transport; + else if (role === 'consuming') { + client.rcvTransport = transport; + } logger.debug(`Transport ${transport.id} iceCandidates: %s`, JSON.stringify(transport.iceCandidates, null, 2)) @@ -103,39 +115,25 @@ export function createCreateTransportRequestListener(listenerContext: CreateTran if (response && role === 'consuming') { - for (const producingClient of clients.values()) { - if (client.clientId === producingClient.clientId) continue; - if (client.routerId !== producingClient.routerId) continue; - - if (!producingClient) continue; - - for (const mediaProducerId of producingClient.mediaProducers) { - const mediaProducer = mediasoupService.mediaProducers.get(mediaProducerId); - if (!mediaProducer) { - logger.warn(`Media producer ${mediaProducerId} not found in mediasoupService, though the client ${producingClient.clientId} has it`); - continue; - } - try { - - const consumer = await mediasoupService.consumeMediaProducer(mediaProducerId, client); - - client.send(new ConsumerCreatedNotification( - consumer.id, - mediaProducerId, - consumer.kind, - consumer.rtpParameters, - { - producerId: mediaProducer.id, - paused: mediaProducer.paused, - }, - { - clientId: client.clientId, - userId: client.userId, - } - )) - } catch (err) { - logger.error(`Error occurred while trying to consume media producer ${mediaProducerId}`, err); - } + + for (const [producingClientId, producingClient] of hamokService.clients.entries()) { + if (producingClientId === client.clientId) continue; + if (producingClient.roomId !== client.roomId) continue; + if (producingClient.callId !== client.callId) continue; + if (!producingClient.routerId) continue; + + const mediaProducerIds = (await hamokService.getClientProducers({ + clientId: producingClientId, + })).mediaProducerIds; + + for (const mediaProducerId of mediaProducerIds) { + hamokService.consumeMediaProducer({ + mediaProducerId, + producingClientId: producingClient.clientId, + producingUserId: producingClient.userId, + consumingClientId: client.clientId, + producingRouterId: producingClient.routerId, + }); } } } diff --git a/media-server/src/client-listeners/JoinCallRequestListener.ts b/media-server/src/client-listeners/JoinCallRequestListener.ts index b6474bd..bd81699 100644 --- a/media-server/src/client-listeners/JoinCallRequestListener.ts +++ b/media-server/src/client-listeners/JoinCallRequestListener.ts @@ -1,6 +1,7 @@ import { ClientContext } from "../common/ClientContext"; import { createLogger } from "../common/logger"; import { JoinCallResponsePayload, Response } from "../protocols/MessageProtocol"; +import { HamokService } from "../services/HamokService"; import { MediasoupService, RouterAppData } from "../services/MediasoupService" import { ClientMessageContext } from "./ClientMessageListener"; import * as mediasoup from 'mediasoup'; @@ -9,8 +10,10 @@ const logger = createLogger('JoinCallRequestListener'); export type JoinCallRequestListenerContext = { mediasoupService: MediasoupService; - clients: Map; + hamokService: HamokService, maxTransportsPerRouter: number; + clientMaxLifetimeInMs: number; + stunnerAuthUrl?: string; } type TurnServerConfig = { @@ -23,61 +26,101 @@ type TurnServerConfig = { export function createJoinCallRequestListener(listenerContext: JoinCallRequestListenerContext) { const { mediasoupService, - clients, + hamokService, + stunnerAuthUrl, + clientMaxLifetimeInMs, } = listenerContext; const result = async (messageContext: ClientMessageContext) => { - const { + const { message: request, + client, } = messageContext; - const client = clients.get(messageContext.clientId); + if (request.type !== 'join-call-request') { return console.warn(`Invalid message type ${request.type}`); - } else if (!client) { - return console.warn(`Client ${messageContext.clientId} not found`); + } else if (client.routerId) { + return messageContext.send(new Response( + request.requestId, + undefined, + `Client ${client.clientId} already joined a call` + )); } - logger.debug(`Client ${client.clientId} joining call ${request.callId}. request: %o`, request); + if (request.callId) { + const ongoingCall = hamokService.calls.get(request.callId); + if (!ongoingCall) { + return messageContext.send(new Response( + request.requestId, + undefined, + `Call ${request.callId} not found` + )); + } + client.callId = request.callId; + client.roomId = ongoingCall.roomId; + + logger.info(`Client ${client.clientId} changed room and call to ${client.callId}, room: ${client.roomId}.`); + } + + logger.debug(`Client ${client.clientId} (${client.userId}) joining call ${client.callId}, room: ${client.roomId}. request: %o`, request); let closeClient = false; let response: JoinCallResponsePayload | undefined; let error: string | undefined; try { - let router: mediasoup.types.Router | undefined; + let router = [ ...mediasoupService.routers.values() ].find(router => router.appData.callId === client.callId); - if (request.callId) { - router = mediasoupService.calls.get(request.callId); - if (!router) throw new Error(`Call with id ${request.callId} does not exist`); - } else { - router = await mediasoupService.createRouter(); + if (!router) { + router = await mediasoupService.createRouter(client.callId); } + client.routerId = router.id; + if (listenerContext.maxTransportsPerRouter <= router.appData.transports.size) { closeClient = true; throw new Error(`Max transports per router reached`); } - client.routerId = router.id; - - const turnResponse = await (await fetch(`http://stunner-auth.stunner-system:8088?service=turn`)).json(); - const turnConfig = (await turnResponse) as TurnServerConfig; + let turnConfig: TurnServerConfig | undefined; + try { + if (stunnerAuthUrl) { + const turnResponse = await (await fetch(stunnerAuthUrl)).json(); + turnConfig = (await turnResponse) as TurnServerConfig; - logger.info(`Turn response: %o`, turnConfig); + logger.info(`Turn response: %o`, turnConfig); + } + } catch (err) { + logger.error(`Failed to fetch turn server config: %o`, err); + turnConfig = undefined; + } - // http://stunner-auth.stunner-system:8088?service=turn + if (!turnConfig) { + logger.warn(`No turn server config available`); + } + + await hamokService.joinClient({ + callId: client.callId, + clientId: client.clientId, + roomId: client.roomId, + routerId: router.id, + userId: client.userId, + }) response = { - callId: router.id, + callId: client.callId, rtpCapabilities: router.rtpCapabilities, - iceServers: [{ + iceServers: turnConfig ? [{ credential: turnConfig.password, credentialType: 'password', urls: turnConfig.uris, username: turnConfig.username, - }], + }] : [], + clientCreatedServerTimestamp: client.created, + innerServerIp: mediasoupService.announcedAddress, + clientMaxLifetimeInMs, }; - + logger.info(`Client ${client.clientId} joined call ${router.id}`); } catch (err) { diff --git a/media-server/src/client-listeners/ObserverRequestListener.ts b/media-server/src/client-listeners/ObserverRequestListener.ts index ebacf23..79643a5 100644 --- a/media-server/src/client-listeners/ObserverRequestListener.ts +++ b/media-server/src/client-listeners/ObserverRequestListener.ts @@ -12,7 +12,6 @@ const logger = createLogger('ClientMonitorSampleNotificatinListener'); export type ClientMonitorSampleNotificatinListenerContext = { observer: Observer, - clients: Map; } export function createObserverRequestListener(listenerContext: ClientMonitorSampleNotificatinListenerContext) { diff --git a/media-server/src/common/ClientContext.ts b/media-server/src/common/ClientContext.ts index b8f613f..433d3a4 100644 --- a/media-server/src/common/ClientContext.ts +++ b/media-server/src/common/ClientContext.ts @@ -7,17 +7,19 @@ import { ClientSampleDecoder } from '@observertc/samples-decoder'; const logger = createLogger('Client'); export type ClientContext = { - routerId?: string, + // routerId?: string, + created: number, + roomId: string, + callId: string, + clientId: string, sndTransport?: mediasoup.types.WebRtcTransport; rcvTransport?: mediasoup.types.WebRtcTransport; - clientId: string, - schemaVersion?: string, userId: string, webSocket: WebSocket, mediaProducers: Set; mediaConsumers: Set; send: (message: ClientMessage) => void; - decoder?: ClientSampleDecoder, + routerId?: string, } /** diff --git a/media-server/src/common/utils.ts b/media-server/src/common/utils.ts index 4fb8cee..f8f5b73 100644 --- a/media-server/src/common/utils.ts +++ b/media-server/src/common/utils.ts @@ -202,6 +202,25 @@ const famousMovies: string[] = [ import { v4 as uuidv4 } from 'uuid'; -export function createRandomCallId() { - return `${famousMovies[Math.floor(Math.random() * famousMovies.length)]}-${uuidv4().substring(0, 8)}`; -} \ No newline at end of file +export function createRandomCallId(): [roomId: string, callId: string] { + const roomId = famousMovies[Math.floor(Math.random() * famousMovies.length)]; + return [roomId, `${roomId}-${uuidv4().substring(0, 8)}`]; +} + +export function cmpUuids(uuid1: string, uuid2: string): number { + const uuid1Parts = uuid1.split('-'); + const uuid2Parts = uuid2.split('-'); + + for (let i = 0; i < 5; i++) { + const uuid1Part = parseInt(uuid1Parts[i], 16); + const uuid2Part = parseInt(uuid2Parts[i], 16); + + if (uuid1Part < uuid2Part) { + return -1; + } else if (uuid1Part > uuid2Part) { + return 1; + } + } + + return 0; +} diff --git a/media-server/src/config.ts b/media-server/src/config.ts index 29f24a9..1689a38 100644 --- a/media-server/src/config.ts +++ b/media-server/src/config.ts @@ -2,13 +2,17 @@ import fs from 'fs'; import YAML from 'yaml'; import type { MediasoupServiceConfig } from './services/MediasoupService'; import type { ServerConfig } from './Server'; +import type { HamokServiceConfig } from './services/HamokService'; export type Config = { configPath?: string; server: ServerConfig, + stunnerAuthUrl?: string; mediasoup: MediasoupServiceConfig; maxTransportsPerRouter: number; maxProducerPerClients: number; + maxClientLifeTimeInMins: number; + hamok: HamokServiceConfig; }; const getDefaultConfig: () => Config = () => { @@ -18,8 +22,28 @@ const getDefaultConfig: () => Config = () => { serverIp: '127.0.0.1', // announcedIp: '127.0.0.1', }, + + hamok: { + clientsMap: { + mapId: 'webrtc-observer-clients-map', + }, + eventEmitter: { + emitterId: 'webrtc-observer-media-service-events', + }, + roomsMap: { + mapId: 'webrtc-observer-rooms-map', + }, + redis: { + host: 'localhost', + port: 6379, + }, + redisChannelId: 'webrtc-observer-hamok-message-channel', + devMode: true, + }, + // stunnerAuthUrl: 'http://stunner-auth.stunner-system:8088?service=turn', maxTransportsPerRouter: 6, - maxProducerPerClients: 1, + maxProducerPerClients: 2, + maxClientLifeTimeInMins: 15, mediasoup: { numberOfWorkers: 1, workerSettings: { diff --git a/media-server/src/hamok-listeners/ClientMonitorSampleListener.ts b/media-server/src/hamok-listeners/ClientMonitorSampleListener.ts new file mode 100644 index 0000000..613e033 --- /dev/null +++ b/media-server/src/hamok-listeners/ClientMonitorSampleListener.ts @@ -0,0 +1,67 @@ +import { ObservedClient, Observer } from "@observertc/observer-js"; +import { createLogger } from "../common/logger"; +import { MainEmitter } from "../common/MainEmitter"; +import { HamokServiceEventMap } from "../services/HamokService"; +import { ClientSampleDecoder as LatestClientSampleDecoder, schemaVersion as latestSchemaVersion } from "@observertc/samples-decoder"; + +const logger = createLogger('ClientMonitorSampleNotificatinListener'); + +export type ClientMonitorSampleNotificatinListenerContext = { + observer: Observer, + mainEmitter: MainEmitter +} + +type ObservedClientAppData = { + decoder: LatestClientSampleDecoder, +} + +export function createClientMonitorSampleListener(listenerContext: ClientMonitorSampleNotificatinListenerContext) { + const { + observer, + mainEmitter, + } = listenerContext; + const result = async (...options: HamokServiceEventMap['client-sample']) => { + const [{ + callId, + clientId, + mediaUnitId, + userId, + sampleInBase64, + }] = options; + + let observedCall = observer.observedCalls.get(callId); + + if (!observedCall) return; // not for this media-server + + let observedClient = observedCall.clients.get(clientId) as ObservedClient | undefined; + + if (!observedClient) { + const newObservedClient = observedCall.createClient({ + clientId, + userId, + mediaUnitId, + appData: { + decoder: new LatestClientSampleDecoder(), + }, + }); + + observedClient = newObservedClient; + } + + const sample = observedClient.appData.decoder.decodeFromBase64(sampleInBase64); + + observedClient.accept(sample); + + // mainEmitter.emit('sample', { + // clientId, + // callId, + // mediaUnitId: 'webapp', + // roomId, + // sampleInBase64: sample, + // serviceId: 'demo-service', + // userId, + // }); + }; + + return result; +} \ No newline at end of file diff --git a/media-server/src/hamok-listeners/ConnectPipeTransportListener.ts b/media-server/src/hamok-listeners/ConnectPipeTransportListener.ts new file mode 100644 index 0000000..657d5c7 --- /dev/null +++ b/media-server/src/hamok-listeners/ConnectPipeTransportListener.ts @@ -0,0 +1,52 @@ +import { createLogger } from "../common/logger"; +import { HamokServiceEventMap } from "../services/HamokService"; +import { MediasoupService } from "../services/MediasoupService"; + +const logger = createLogger('ConnectPipeTransportListener'); + +export type CreateConnectPipeTransportListenerContext = { + mediasoupService: MediasoupService, +} + +export function createConnectPipeTransportListener(listenerContext: CreateConnectPipeTransportListenerContext) { + const { + mediasoupService, + } = listenerContext; + + const result = async (...options: HamokServiceEventMap['connect-pipe-transport-request']) => { + const [{ + dstRouterId, + srcRouterId, + ip, + port, + }, respond] = options; + + if (!mediasoupService.routers.has(srcRouterId)) return; + + logger.debug(`Requested to connect pipe transport from router ${srcRouterId} to router ${dstRouterId}, ip: ${ip}, port: ${port}`); + + let error: string | undefined; + try { + const transport = await mediasoupService.getOrCreatePipeTransport(srcRouterId, dstRouterId); + + if (!transport.appData.connecting) { + logger.debug(`Connecting pipe transport from router ${srcRouterId} to router ${dstRouterId}, ip: ${ip}, port: ${port}`); + transport.appData.connecting = transport.connect({ + ip, + port, + }) + } + + await transport.appData.connecting; + + transport.appData.connected = true; + + } catch (err) { + error = `Failed to create pipe transport: ${err}`; + } + + respond(error) + }; + + return result; +} \ No newline at end of file diff --git a/media-server/src/hamok-listeners/ConsumeMediaProducerListener.ts b/media-server/src/hamok-listeners/ConsumeMediaProducerListener.ts new file mode 100644 index 0000000..7c11da6 --- /dev/null +++ b/media-server/src/hamok-listeners/ConsumeMediaProducerListener.ts @@ -0,0 +1,97 @@ +import { createLogger } from "../common/logger"; +import { ConsumerCreatedNotification } from "../protocols/MessageProtocol"; +import { Server } from "../Server"; +import { HamokServiceEventMap } from "../services/HamokService"; +import { MediasoupService } from "../services/MediasoupService"; +import mediasoup from 'mediasoup'; + +const logger = createLogger('ConsumeMediaProducerListener'); + +export type ConsumeMediaProducerListenerContext = { + mediasoupService: MediasoupService, + server: Server, +} + +export function createConsumeMediaProducerListener(listenerContext: ConsumeMediaProducerListenerContext) { + const { + mediasoupService, + server, + } = listenerContext; + + const result = async (...options: HamokServiceEventMap['consume-media-producer']) => { + const [{ + producingClientId, + producingUserId, + producingRouterId, + mediaProducerId, + consumingClientId, + }] = options; + + const consumingClient = server.clients.get(consumingClientId); + + if (!consumingClient || !consumingClient.routerId) return; + + logger.debug(`Client ${consumingClient.clientId} (${consumingClient.userId}) consuming media producer ${mediaProducerId} from client ${producingClientId} (${producingUserId}). consumingRouterId: ${consumingClient.routerId}, producingRouter: ${producingRouterId}`); + + try { + let mediaProducer: mediasoup.types.Producer | undefined; + + if (producingRouterId !== consumingClient.routerId) { + logger.debug(`Piping media producer ${mediaProducerId} from router ${producingRouterId} to router ${consumingClient.routerId}`); + + mediaProducer = await mediasoupService.getOrCreatePipedMediaProducer({ + localRouterId: consumingClient.routerId, + mediaProducerId, + remoteRouterId: producingRouterId, + }); + } else { + mediaProducer = mediasoupService.mediaProducers.get(mediaProducerId); + } + + + if (!mediaProducer) { + return logger.warn(`Media producer ${mediaProducerId} not found in mediasoupService`); + } + + const consumer = await mediasoupService.consumeMediaProducer(mediaProducerId, consumingClient); + + for (const existingConsumerId of consumingClient.mediaConsumers) { + const existingConsumer = mediasoupService.mediaConsumers.get(existingConsumerId); + + if (existingConsumer?.producerId === mediaProducerId) { + logger.warn(`Consumer ${existingConsumerId} already consuming media producer ${mediaProducerId} for client ${consumingClientId}. Closing newly created consumer ${consumer.id}`); + return consumer.close(); + } + } + + logger.debug(`Created consumer ${consumer.id} consuming media producer ${mediaProducerId} for client ${consumingClientId} (${consumingClient.userId}). kind: ${consumer.kind}, paused: ${consumer.paused}`); + + consumer.observer.once('close', () => { + consumingClient.mediaConsumers.delete(consumer.id); + }) + consumingClient.mediaConsumers.add(consumer.id); + + consumingClient.send(new ConsumerCreatedNotification( + consumer.id, + mediaProducerId, + consumer.kind, + consumer.rtpParameters, + { + producerId: mediaProducer.id, + paused: mediaProducer.paused, + }, + { + clientId: producingClientId, + userId: producingUserId ?? 'unknown', + } + )) + + } catch (err) { + logger.warn(`Failed to create pipe transport: ${err}`); + } + + + }; + + return result; +} \ No newline at end of file diff --git a/media-server/src/hamok-listeners/CreatePipeTransportListener.ts b/media-server/src/hamok-listeners/CreatePipeTransportListener.ts new file mode 100644 index 0000000..0659af5 --- /dev/null +++ b/media-server/src/hamok-listeners/CreatePipeTransportListener.ts @@ -0,0 +1,39 @@ +import { createLogger } from "../common/logger"; +import { HamokServiceEventMap } from "../services/HamokService"; +import { MediasoupService } from "../services/MediasoupService"; + +const logger = createLogger('CreatePipeTransportListener'); + +export type CreatePipeTransportListenerContext = { + mediasoupService: MediasoupService, +} + +export function createPipeTransportListener(listenerContext: CreatePipeTransportListenerContext) { + const { + mediasoupService, + } = listenerContext; + + const result = async (...options: HamokServiceEventMap['create-pipe-transport-request']) => { + const [{ + dstRouterId, + srcRouterId, + }, respond] = options; + + if (!mediasoupService.routers.has(srcRouterId)) return; + + try { + const transport = await mediasoupService.getOrCreatePipeTransport(srcRouterId, dstRouterId); + + respond({ + ip: transport.tuple.localIp, + port: transport.tuple.localPort, + }); + } catch (err) { + respond(undefined, `${err}`) + } + + + }; + + return result; +} \ No newline at end of file diff --git a/media-server/src/hamok-listeners/GetClientProducersListener.ts b/media-server/src/hamok-listeners/GetClientProducersListener.ts new file mode 100644 index 0000000..ce21ca3 --- /dev/null +++ b/media-server/src/hamok-listeners/GetClientProducersListener.ts @@ -0,0 +1,34 @@ +import { createLogger } from "../common/logger"; +import { Server } from "../Server"; +import { HamokServiceEventMap } from "../services/HamokService"; +import { MediasoupService } from "../services/MediasoupService"; + +const logger = createLogger('GetClientProducers'); + +export type CreateGetClientProducersListenerContext = { + mediasoupService: MediasoupService, + server: Server, +} + +export function createGetClientProducersListener(listenerContext: CreateGetClientProducersListenerContext) { + const { + mediasoupService, + server, + } = listenerContext; + + const result = async (...options: HamokServiceEventMap['get-client-producers-request']) => { + const [{ + clientId, + }, respond] = options; + + const client = server.clients.get(clientId); + + if (!client || !client.routerId) return; + + respond({ + mediaProducerIds: Array.from(client.mediaProducers), + }) + }; + + return result; +} \ No newline at end of file diff --git a/media-server/src/hamok-listeners/PipeMediaProducerToListener.ts b/media-server/src/hamok-listeners/PipeMediaProducerToListener.ts new file mode 100644 index 0000000..2fcd42e --- /dev/null +++ b/media-server/src/hamok-listeners/PipeMediaProducerToListener.ts @@ -0,0 +1,60 @@ +import { createLogger } from "../common/logger"; +import { HamokService, HamokServiceEventMap } from "../services/HamokService"; +import { MediasoupService } from "../services/MediasoupService"; + +const logger = createLogger('PipeMediaProducerToRemoteRouterListener'); + +export type PipeMediaProducerToListenerContext = { + mediasoupService: MediasoupService, + hamokService: HamokService +} + +export function createPipeMediaProducerToListener(listenerContext: PipeMediaProducerToListenerContext) { + const { + mediasoupService, + hamokService, + } = listenerContext; + + const result = async (...options: HamokServiceEventMap['pipe-media-producer-to']) => { + const [{ + dstRouterId, + srcRouterId, + mediaProducerId, + }, respond] = options; + + if (!mediasoupService.routers.has(srcRouterId)) return; + + logger.debug(`Requested to pipe media producer ${mediaProducerId} from router ${srcRouterId} to router ${dstRouterId}`); + + try { + logger.debug(`Consuming media producer ${mediaProducerId} from router ${srcRouterId} to forward traffic to router ${dstRouterId}`); + + const consumer = await mediasoupService.getOrCreatePipedMediaConsumer({ + srcRouterId, + dstRouterId, + mediaProducerId, + }); + + consumer.observer.once('close', () => { + + // notify the other peers about the close + hamokService.publishPipeMediaConsumerClosed({ + mediaProducerId: consumer.producerId, + }) + }); + + respond({ + kind: consumer.kind, + rtpParameters: consumer.rtpParameters, + id: consumer.producerId, + }) + + } catch (err) { + respond(undefined, `${err}`) + } + + + }; + + return result; +} \ No newline at end of file diff --git a/media-server/src/hamok-listeners/PipedMediaConsumerClosedListener.ts b/media-server/src/hamok-listeners/PipedMediaConsumerClosedListener.ts new file mode 100644 index 0000000..cee6599 --- /dev/null +++ b/media-server/src/hamok-listeners/PipedMediaConsumerClosedListener.ts @@ -0,0 +1,26 @@ +import { createLogger } from "../common/logger"; +import { Server } from "../Server"; +import { HamokServiceEventMap } from "../services/HamokService"; +import { MediasoupService } from "../services/MediasoupService"; + +const logger = createLogger('GetClientProducers'); + +export type PipedMediaConsumerClosedListenerContext = { + mediasoupService: MediasoupService, +} + +export function createPipedMediaConsumerClosedListener(listenerContext: PipedMediaConsumerClosedListenerContext) { + const { + mediasoupService, + } = listenerContext; + + const result = async (...options: HamokServiceEventMap['piped-media-consumer-closed']) => { + const [{ + mediaProducerId, + }] = options; + + mediasoupService.closePipedMediaProducer(mediaProducerId) + }; + + return result; +} \ No newline at end of file diff --git a/media-server/src/intervals/CloseClientInterval.ts b/media-server/src/intervals/CloseClientInterval.ts new file mode 100644 index 0000000..ec23f65 --- /dev/null +++ b/media-server/src/intervals/CloseClientInterval.ts @@ -0,0 +1,27 @@ +import { createLogger } from "../common/logger"; +import { Server } from "../Server"; + +const logger = createLogger('CloseClientInterval'); + +export type CloseClientIntervalContext = { + server: Server, + maxClientLifeTimeInMins: number, +} + +export function createCloseClientInterval(context: CloseClientIntervalContext) { + const { server, maxClientLifeTimeInMins } = context; + const ONE_MIN = 60000; + const maxClientLifeTimeInMs = maxClientLifeTimeInMins * ONE_MIN; + + return () => setInterval(() => { + const now = Date.now(); + for (const client of server.clients.values()) { + const elapsedTimeInMs = now - client.created; + + if (elapsedTimeInMs > maxClientLifeTimeInMs) { + logger.debug(`Client ${client.clientId} has been alive for ${elapsedTimeInMs}ms, closing connection`); + client.webSocket.close(); + } + } + }, ONE_MIN); +} \ No newline at end of file diff --git a/media-server/src/main.ts b/media-server/src/main.ts index 57701ca..efb5170 100644 --- a/media-server/src/main.ts +++ b/media-server/src/main.ts @@ -1,8 +1,6 @@ import { Server } from "./Server"; import process from'process'; import { createLogger } from "./common/logger"; -import { ClientMessage } from "./protocols/MessageProtocol"; -import { ClientMessageListener } from "./client-listeners/ClientMessageListener"; import { config, getConfigString } from "./config"; import { createCreateTransportRequestListener } from "./client-listeners/CreateTransportRequestListener"; import { MediasoupService } from "./services/MediasoupService"; @@ -14,120 +12,190 @@ import { createObserver } from "@observertc/observer-js"; import { createJoinCallRequestListener } from "./client-listeners/JoinCallRequestListener"; import { createControlTransportNotificationListener } from "./client-listeners/ControlTransportNotificationListener"; import { createConnectTransportRequestListener } from "./client-listeners/ConnectTransportRequestListener"; -import { ClientSampleDecoder as LatestClientSampleDecoder, schemaVersion as latestSchemaVersion } from "@observertc/samples-decoder"; import { createClientMonitorSampleNotificatinListener } from "./client-listeners/ClientMonitorSampleNotificationListener"; import { createObserverRequestListener } from "./client-listeners/ObserverRequestListener"; import { MainEmitter } from "./common/MainEmitter"; import { createObservedCallLogMonitor } from "./observer-listeners/ObservedCallLogMonitor"; +import { HamokService } from "./services/HamokService"; +import { createPipeTransportListener } from "./hamok-listeners/CreatePipeTransportListener"; +import { createConnectPipeTransportListener } from "./hamok-listeners/ConnectPipeTransportListener"; +import { createRandomCallId } from "./common/utils"; +import { createConsumeMediaProducerListener } from "./hamok-listeners/ConsumeMediaProducerListener"; +import { createPipeMediaProducerToListener } from "./hamok-listeners/PipeMediaProducerToListener"; +import { createPipedMediaConsumerClosedListener } from "./hamok-listeners/PipedMediaConsumerClosedListener"; +import { createGetClientProducersListener } from "./hamok-listeners/GetClientProducersListener"; +import { IntervalTrackerService } from "./services/IntervalTrackerService"; +import { createCloseClientInterval } from "./intervals/CloseClientInterval"; const logger = createLogger('main'); -const clients = new Map(); -const server = new Server(config.server); const mediasoupService = new MediasoupService(config.mediasoup); +const hamokService = new HamokService(config.hamok); const mainEmitter = new MainEmitter(); +const intervalTrackerService = new IntervalTrackerService(); const observer = createObserver({ maxCollectingTimeInMs: 1000, maxReports: 100, defaultMediaUnitId: 'webapp', defaultServiceId: 'demo-service', }); -const listeners = new Map() +const server = new Server(config.server); + +server.messageListeners .set('join-call-request', createJoinCallRequestListener({ + hamokService, mediasoupService, - clients, maxTransportsPerRouter: config.maxTransportsPerRouter, + stunnerAuthUrl: config.stunnerAuthUrl, + clientMaxLifetimeInMs: config.maxClientLifeTimeInMins * 60 * 1000, })) .set('connect-transport-request', createConnectTransportRequestListener({ mediasoupService, - clients, })) .set( 'control-consumer-notification', createControlConsumerNotificationListener({ - clients, mediasoupService, }) ) .set( 'control-producer-notification', createControlProducerNotificationListener({ - clients, mediasoupService, }) ) .set( 'control-transport-notification', createControlTransportNotificationListener({ - clients, mediasoupService, }) ) .set( 'create-producer-request', createCreateProducerRequestListener({ - clients, - mediasoupService, + hamokService, maxProducerPerClients: config.maxProducerPerClients, + mediasoupService, }) ) .set( 'create-transport-request', createCreateTransportRequestListener({ + hamokService, mediasoupService, server, - clients, }) ) .set( 'client-monitor-sample-notification', createClientMonitorSampleNotificatinListener({ - observer, - clients, - mainEmitter, + hamokService, }) ) .set('observer-request', createObserverRequestListener({ observer, - clients, }) ) ; +hamokService + .on('call-inserted-by-this-instance', call => { + if (observer.observedCalls.has(call.callId)) { + logger.warn(`Call ${call.callId} already observed`); + return; + } + observer.createObservedCall({ + roomId: call.roomId, + callId: call.callId, + serviceId: 'demo-service', + appData: {}, + }); + }) + .on('call-ended', call => { + const routers = [...mediasoupService.routers.values()].filter(router => router.appData.callId === call.callId); + routers.forEach(router => router.close()); + + observer.observedCalls.get(call.callId)?.close(); + + }) + .on('client-left', (callId, clientId) => { + observer.observedCalls.get(callId)?.clients.get(clientId)?.close(); + }) + .on('client-sample', message => { + + }) + .on('create-pipe-transport-request', createPipeTransportListener({ + mediasoupService, + })) + .on('connect-pipe-transport-request', createConnectPipeTransportListener({ + mediasoupService + })) + .on('pipe-media-producer-to', createPipeMediaProducerToListener({ + mediasoupService, + hamokService, + })) + .on('consume-media-producer', createConsumeMediaProducerListener({ + mediasoupService, + server, + })) + .on('piped-media-consumer-closed', createPipedMediaConsumerClosedListener({ + mediasoupService, + })) + .on('get-client-producers-request', createGetClientProducersListener({ + mediasoupService, + server, + })) + ; + +intervalTrackerService.addInterval('closing-clients', createCloseClientInterval({ + server, + maxClientLifeTimeInMins: config.maxClientLifeTimeInMins, +})) + observer.on('newcall', createObservedCallLogMonitor()); +mediasoupService.connectRemotePipeTransport = (options) => hamokService.connectRemotePipeTransport(options); +mediasoupService.createRemotePipeTransport = (options) => hamokService.createRemotePipeTransport(options); +mediasoupService.pipeRemoteMediaProducerTo = (options) => hamokService.pipeMediaProducerTo(options); + +server.createClientContext = async ({ clientId, webSocket, send, callId, userId }): Promise => { + let roomId: string | undefined; + + if (callId) { + const ongoingCall = hamokService.calls.get(callId); + if (!ongoingCall) { + throw new Error(`Call ${callId} not found`); + } + roomId = ongoingCall.roomId; + } else { + [ roomId, callId ] = createRandomCallId(); + } + + if (!roomId || !callId) throw new Error(`Invalid roomId or callId`); + + return { + created: Date.now(), + clientId, + webSocket, + send, + callId, + roomId, + userId, + mediaProducers: new Set(), + mediaConsumers: new Set(), + }; +} + server .on('newclient', client => { client.webSocket.once('close', () => { client.sndTransport?.close(); client.rcvTransport?.close(); - clients.delete(client.clientId) - logger.info(`Client disconnected with id ${client.clientId}`); + + hamokService.leaveClient(client.clientId).catch(err => logger.warn(`Error occurred while trying to leave client ${client.clientId} %o`, err)); }); - let decoder: LatestClientSampleDecoder | undefined; - switch (client.schemaVersion) { - case latestSchemaVersion: - decoder = new LatestClientSampleDecoder(); - logger.debug(`Client ${client.clientId} uses schema version ${client.schemaVersion}`); - break; - default: { - logger.warn(`Unsupported schema version ${client.schemaVersion}`); - } - } - client.decoder = decoder; - clients.set(client.clientId, client); logger.info(`New client connected with id ${client.clientId}`); }) - .on('newmessage', messageContext => { - const listener = listeners.get(messageContext.message.type); - if (!listener) { - logger.warn(`No listener found for message type ${messageContext.message.type}`); - return; - } - listener(messageContext)?.catch(err => { - logger.error(`Error occurred while processing message`, err); - }); - }) async function main(): Promise { let stopped = false; @@ -136,10 +204,14 @@ async function main(): Promise { stopped = true; logger.info("Stopping server"); + intervalTrackerService.stop(); + await Promise.allSettled([ server.stop(), mediasoupService.stop(), + hamokService.stop(), ]); + process.exit(0); }); @@ -152,8 +224,11 @@ async function main(): Promise { logger.warn("Error loading module %o", ex); } + await hamokService.start(); await mediasoupService.start(); await server.start(); + + intervalTrackerService.start(); } diff --git a/media-server/src/protocols/HamokServiceEventProtocol.ts b/media-server/src/protocols/HamokServiceEventProtocol.ts new file mode 100644 index 0000000..6b4b6b8 --- /dev/null +++ b/media-server/src/protocols/HamokServiceEventProtocol.ts @@ -0,0 +1,54 @@ +import * as mediasoup from 'mediasoup'; + +export type HamokServiceCreatePipeTransportResponsePayload = { + ip: string; + port: number; +};export type HamokServiceCreatePipeTransportRequestPayload = { + srcRouterId: string; + dstRouterId: string; +}; +export type HamokServiceClientSampleEventPayload = { + clientId: string; + callId: string; + mediaUnitId: string; + roomId: string; + sampleInBase64: string; + serviceId: string; + userId?: string; + schemaVersion?: string; +}; + +export type HamokServicePipeMediaConsumerClosedEventPayload = { + mediaProducerId: string; +}; + +export type HamokServiceConnectPipeTransportRequestPayload = { + srcRouterId: string; + dstRouterId: string; + ip: string; + port: number; +}; +export type HamokServicePipeMediaProducerToRequestPayload = { + srcRouterId: string; + dstRouterId: string; + mediaProducerId: string; +}; +export type HamokServiceConnectPipeTransportResponsePayload = void; +export type HamokServiceResponseField = T extends void ? ((err?: string) => void) : ((payload?: T, error?: string) => void); +export type HamokServiceConsumeMediaProducerEventPayload = { + producingRouterId: string; + mediaProducerId: string; + consumingClientId: string; + producingClientId: string; + producingUserId?: string; +}; +export type HamokServiceGetClientProducersRequestPayload = { + clientId: string; +}; +export type HamokServiceGetClientProducersResponsePayload = { + mediaProducerIds: string[]; +}; + +export type HamokServicePipeMediaProducerToResponsePayload = mediasoup.types.ProducerOptions; + + diff --git a/media-server/src/protocols/MessageProtocol.ts b/media-server/src/protocols/MessageProtocol.ts index 6519cef..80ab7ca 100644 --- a/media-server/src/protocols/MessageProtocol.ts +++ b/media-server/src/protocols/MessageProtocol.ts @@ -66,6 +66,9 @@ export type JoinCallResponsePayload = { urls: string | string[]; username: string; }[], + clientCreatedServerTimestamp: number, + clientMaxLifetimeInMs?: number, + innerServerIp: string, } export class CreateProducerRequest { diff --git a/media-server/src/services/HamokService.ts b/media-server/src/services/HamokService.ts new file mode 100644 index 0000000..80fc189 --- /dev/null +++ b/media-server/src/services/HamokService.ts @@ -0,0 +1,519 @@ +import { EventEmitter } from "events" +import { addHamokLogTransport, Hamok, HamokEmitter, HamokEmitterBuilderConfig, HamokMap, HamokMapBuilderConfig, HamokMessage, setHamokLogLevel } from "hamok" +import Redis, { RedisOptions } from "ioredis" +import { v4 as uuid } from "uuid" +import { createLogger } from "../common/logger"; +import { + HamokServiceClientSampleEventPayload, + HamokServiceConnectPipeTransportRequestPayload, + HamokServiceConnectPipeTransportResponsePayload, + HamokServiceConsumeMediaProducerEventPayload, + HamokServiceCreatePipeTransportRequestPayload, + HamokServiceCreatePipeTransportResponsePayload, + HamokServiceGetClientProducersRequestPayload, + HamokServiceGetClientProducersResponsePayload, + HamokServicePipeMediaConsumerClosedEventPayload, + HamokServicePipeMediaProducerToRequestPayload, + HamokServicePipeMediaProducerToResponsePayload, + HamokServiceResponseField +} from "../protocols/HamokServiceEventProtocol"; + +const logger = createLogger('HamokService'); + +export type HamokServiceEventMap = { + 'call-created': [call: OngoingCall], + 'call-inserted-by-this-instance': [call: OngoingCall], + 'call-ended': [call: OngoingCall], + 'client-left': [callId: string, clientId: string], + + // emitter events + 'client-sample': [payload: HamokServiceClientSampleEventPayload], + 'create-pipe-transport-request': [ + payload: HamokServiceCreatePipeTransportRequestPayload, + resolve: HamokServiceResponseField, + ], + 'connect-pipe-transport-request': [ + payload: HamokServiceConnectPipeTransportRequestPayload, + resolve: HamokServiceResponseField + ], + 'pipe-media-producer-to': [ + payload: HamokServicePipeMediaProducerToRequestPayload, + resolve: HamokServiceResponseField, + ], + 'consume-media-producer': [ + payload: HamokServiceConsumeMediaProducerEventPayload + ], + 'get-client-producers-request': [ + options: HamokServiceGetClientProducersRequestPayload, + resolve: HamokServiceResponseField + ], + 'piped-media-consumer-closed': [ + payload: HamokServicePipeMediaConsumerClosedEventPayload + ], +} + + +type HamokEmitterEventMap = { + 'client-sample': [message: HamokServiceClientSampleEventPayload], + 'create-pipe-transport-request': [ + requestId: string, + payload: HamokServiceCreatePipeTransportRequestPayload + ], + 'connect-pipe-transport-request': [ + requestId: string, + payload: HamokServiceConnectPipeTransportRequestPayload + ], + 'consume-media-producer': [ + payload: HamokServiceConsumeMediaProducerEventPayload + ], + 'get-client-producers-request': [ + requestId: string, + payload: HamokServiceGetClientProducersRequestPayload + ], + 'pipe-media-producer-to': [ + requestId: string, + payload: HamokServicePipeMediaProducerToRequestPayload + ], + 'piped-media-consumer-closed': [ + payload: HamokServicePipeMediaConsumerClosedEventPayload + ], + 'response': [ + requestId: string, + response: { + error?: string, + payload?: unknown + } + ], +} + +export type OngoingCall = { + roomId: string; + callId: string; +} + +export type ActiveClient = { + roomId: string, + callId: string, + clientId: string, + userId: string, + routerId: string, +} + + +export type HamokServiceConfig = { + redis: RedisOptions, + redisChannelId: string, + roomsMap: Pick, 'mapId'>, + clientsMap: Pick, 'mapId'>, + eventEmitter: Pick, 'emitterId'>, + devMode?: boolean, +} + +type HamokAppData = { +} + + +interface HamokRedisMessageChannel extends EventEmitter<{ 'message': [HamokMessage] }>{ + send(message: HamokMessage): void; + close(): void; +} + +interface PendingRequest { + resolve(data: T): void; + reject(error: string): void; + timer: ReturnType; +} + +export class HamokService extends EventEmitter { + + public run = false; + + public readonly hamok: Hamok; + public readonly calls: HamokMap; + public readonly clients: HamokMap; + public readonly eventEmitter: HamokEmitter; + + private readonly _pendingRequests = new Map>(); + private _redisConnected = false; + private readonly _channel: HamokRedisMessageChannel; + private _devHamok?: Hamok; + + constructor( + config: HamokServiceConfig + ) { + super(); + + this.hamok = new Hamok({ + appData: { + } + }); + this._channel = this._createRedisClients(config.redis, config.redisChannelId); + this.calls = this.hamok.createMap(config.roomsMap); + this.clients = this.hamok.createMap(config.clientsMap); + this.eventEmitter = this.hamok.createEmitter(config.eventEmitter); + + this.calls.on('remove', (key, value) => this.emit('call-ended', value)); + this.calls.on('insert', (key, value) => this.emit('call-created', value)); + this.clients.on('remove', (key, value) => this.emit('client-left', value.callId, key)); + + this._remotePeerJoined = this._remotePeerJoined.bind(this); + this._remotePeerLeft = this._remotePeerLeft.bind(this); + this._leaderChanged = this._leaderChanged.bind(this); + + if (config.devMode) { + this._devHamok = new Hamok({ + appData: { + }, + onlyFollower: true, + }); + } + } + + public async joinClient(activeClient: ActiveClient): Promise { + + await this._getOrCreateCall({ + callId: activeClient.callId, + roomId: activeClient.roomId + }); + + const clientAlreadyExists = await this.clients.insert(activeClient.clientId, activeClient); + + if (clientAlreadyExists) throw new Error(`Client ${activeClient.clientId} already exists`); + } + + public async updateClient(clientId: string, update: Partial, enforced = false): Promise { + const activeClient = this.clients.get(clientId); + + if (!activeClient) throw new Error(`Client ${update.clientId} not found`); + + const updatedActiveClient = { ...activeClient, ...update }; + + if (enforced) return this.clients.set(clientId, updatedActiveClient).then(() => true); + else return this.clients.updateIf(clientId, updatedActiveClient, activeClient); + } + + public async leaveClient(clientId: string): Promise { + const removedClient = this.clients.get(clientId); + + if (!removedClient) return; + + await this.clients.remove(clientId); + await this.hamok.waitUntilCommitHead(); + + let counter = 0; + for (const [, activeClient] of this.clients.entries()) { + if (activeClient.callId === removedClient.callId) { + counter++; + } + } + + if (counter < 1) { + await this.calls.remove(removedClient.callId); + } + } + + public publishClientSample(payload: HamokServiceClientSampleEventPayload): void { + this.eventEmitter.notify('client-sample', payload); + } + + public publishPipeMediaConsumerClosed(payload: HamokServicePipeMediaConsumerClosedEventPayload): void { + this.eventEmitter.notify('piped-media-consumer-closed', payload); + } + + public async getClientProducers(payload: HamokServiceGetClientProducersRequestPayload): Promise<{ mediaProducerIds: string[] }> { + const [requestId, promise] = this._createPendingRequest({ type: 'get-client-producers-request', payload }); + + this.eventEmitter.notify('get-client-producers-request', requestId, payload); + + return promise.then(payload => payload as { mediaProducerIds: string[] }); + } + + public consumeMediaProducer(payload: HamokServiceConsumeMediaProducerEventPayload): void { + this.eventEmitter.notify('consume-media-producer', payload); + } + + public async createRemotePipeTransport(payload: HamokServiceCreatePipeTransportRequestPayload): Promise<{ ip: string, port: number }> { + const [requestId, promise] = this._createPendingRequest({ type: 'create-pipe-transport-request', payload }); + + this.eventEmitter.notify('create-pipe-transport-request', requestId, payload); + + return promise.then(payload => payload as { ip: string, port: number }); + } + + public async connectRemotePipeTransport(payload: HamokServiceConnectPipeTransportRequestPayload): Promise { + const [requestId, promise] = this._createPendingRequest({ type: 'connect-pipe-transport-request', payload }); + + this.eventEmitter.notify('connect-pipe-transport-request', requestId, payload); + + await promise; + } + + public async pipeMediaProducerTo(payload: HamokServicePipeMediaProducerToRequestPayload): Promise { + const [requestId, promise] = this._createPendingRequest({ type: 'pipe-media-producer-to', payload }); + + this.eventEmitter.notify('pipe-media-producer-to', requestId, payload); + + return promise.then(payload => payload as HamokServicePipeMediaProducerToResponsePayload); + } + + private async _getOrCreateCall(ongoingCall: Pick): Promise { + const call = this.calls.get(ongoingCall.callId); + + if (call) return call; + const newCall: OngoingCall = { + ...ongoingCall, + } + + const alreadyExistingCall = await this.calls.insert(ongoingCall.callId, newCall); + + if (alreadyExistingCall) { + return alreadyExistingCall; + } + + this.emit('call-inserted-by-this-instance', newCall); + + return newCall; + } + + + public async start() { + if (this.run) return; + this.run = true; + + logger.info('Starting HamokService'); + + setHamokLogLevel('info'); + addHamokLogTransport({ + target: 'pino-pretty', + options: { destination: 0 } // use 2 for stderr + }) + + this.hamok.on('message', msg => this._channel.send(msg)); + this._channel.on('message', msg => this.hamok.accept(msg)); + this.hamok.on('remote-peer-joined', this._remotePeerJoined); + this.hamok.on('remote-peer-left', this._remotePeerLeft); + this.hamok.on('leader-changed', this._leaderChanged); + this.hamok.on('error', (err) => logger.error('Hamok error', err)); + + if (this._devHamok) { + // for local try purpose, because hamok cannot work without at least 2 instance + this._devHamok.on('message', msg => this._channel.send(msg)); + this._channel.on('message', msg => this._devHamok?.accept(msg)); + this.hamok.once('close', () => this._devHamok?.close()); + + this._devHamok.join({ + fetchRemotePeerTimeoutInMs: 2000, + maxRetry: 50, + }).catch(err => logger.error('Failed to join dev hamok', err)); + } + + await this.hamok.join({ + fetchRemotePeerTimeoutInMs: 2000, + maxRetry: 50, + }); + + logger.debug('Hamok joined'); + + const createNonVoidResponseCallback = (requestId: string): HamokServiceResponseField => ((payload?: T, error?: string) => { + this.eventEmitter.notify('response', requestId, { + error, + payload, + }); + }) as HamokServiceResponseField; + const createVoidResponseCallback = (requestId: string): HamokServiceResponseField => ((err?: string) => { + this.eventEmitter.notify('response', requestId, { + error: err, + payload: err ? undefined : {} + }); + }) as HamokServiceResponseField; + + await this.eventEmitter.subscribe('client-sample', (message) => { + this.emit('client-sample', message); + }); + logger.debug('Subscribed to client-sample channel'); + + await this.eventEmitter.subscribe('create-pipe-transport-request', (requestId, payload) => { + this.emit('create-pipe-transport-request', payload, createNonVoidResponseCallback(requestId)); + }); + logger.debug('Subscribed to create-pipe-transport-request channel'); + + await this.eventEmitter.subscribe('connect-pipe-transport-request', (requestId, payload) => { + this.emit('connect-pipe-transport-request', payload, createVoidResponseCallback(requestId)); + }); + logger.debug('Subscribed to connect-pipe-transport-request channel'); + + await this.eventEmitter.subscribe('pipe-media-producer-to', (requestId, payload) => { + this.emit('pipe-media-producer-to', payload, createNonVoidResponseCallback(requestId)); + }); + logger.debug('Subscribed to pipe-media-producer-to channel'); + + await this.eventEmitter.subscribe('get-client-producers-request', (requestId, payload) => { + this.emit('get-client-producers-request', payload, createNonVoidResponseCallback(requestId)); + }); + logger.debug('Subscribed to get-client-producers-request channel'); + + await this.eventEmitter.subscribe('consume-media-producer', (payload) => { + this.emit('consume-media-producer', payload); + }); + logger.debug('Subscribed to consume-media-producer channel'); + + await this.eventEmitter.subscribe('piped-media-consumer-closed', (payload) => { + this.emit('piped-media-consumer-closed', payload); + }); + logger.debug('Subscribed to piped-media-consumer-closed channel'); + + await this.eventEmitter.subscribe('response', (requestId, response) => { + const pendingRequest = this._pendingRequests.get(requestId); + + if (!pendingRequest) return; + + clearTimeout(pendingRequest.timer); + if (response.error) { + pendingRequest.reject(response.error); + } else { + pendingRequest.resolve(response.payload); + } + }); + logger.debug('Subscribed to response channel'); + + logger.info('HamokService started'); + } + + public async stop() { + if (!this.run) return; + this.run = false; + + logger.info('Stopping HamokService'); + this.hamok.off('remote-peer-joined', this._remotePeerJoined); + this.hamok.off('remote-peer-left', this._remotePeerLeft); + this.hamok.off('leader', this._leaderChanged); + + return this.hamok.close(); + } + + private _createPendingRequest(debugInfo: Record): [requestId: string, promise: Promise] { + const requestId = uuid(); + + return [ + requestId, + new Promise((_resolve, _reject) => { + const timer = setTimeout(() => { + this._pendingRequests.delete(requestId); + _reject('Request timed out. debugInfo: ' + JSON.stringify(debugInfo)); + }, 5000); + + this._pendingRequests.set(requestId, { + resolve: data => { + this._pendingRequests.delete(requestId); + _resolve(data); + }, + reject: error => { + this._pendingRequests.delete(requestId); + _reject(error); + }, + timer + }); + }) + ] + } + + private _createRedisClients( + config: RedisOptions, + channelId: string, + ): HamokRedisMessageChannel { + const redisPublisher = new Redis(config); + const redisSubscriber = new Redis(config); + const channel = new class extends EventEmitter<{ 'message': [message: HamokMessage] }> implements HamokRedisMessageChannel{ + send(message: HamokMessage): void { + // logger.debug(`Sending message to channel ${channelId}:`, message); + const data = Buffer.from(message.toBinary()); + redisPublisher.publish(channelId, data).catch((error: Error) => { + logger.error('Failed to publish to the redis channel', error); + }); + } + close(): void { + redisSubscriber.unsubscribe(channelId, (err) => { + if (err) { + logger.error('Failed to unsubscribe from the redis channel', err); + } + }); + } + + }(); + + redisPublisher.on('reconnecting', () => logger.warn('Reconnecting')); + redisPublisher.on('ready', () => logger.info('Ready')); + redisPublisher.on('end', () => logger.warn('Connection closed (ended)')); + redisSubscriber.on('error', (err) => logger.error('Error occurred', err)); + redisSubscriber.on('connect', () => void 0); + redisSubscriber.on('reconnecting', () => void 0); + redisSubscriber.on('ready', () => void 0); + redisSubscriber.on('end', () => void 0); + + redisPublisher.on('error', (err) => { + if (!this._redisConnected) { + // ignore the errors + return; + } + if (err?.name === 'ECONNREFUSED') { + logger.warn('Disconnected due to error', err); + this._redisConnected = false; + } else { + logger.warn('Error occurred', err); + } + }); + redisPublisher.on('connect', () => { + if (this._redisConnected) { + return; + } + this._redisConnected = true; + + redisSubscriber.subscribe(channelId, (err, count) => { + if (err) { + return logger.error("Failed to subscribe: %s", err.message); + } + + logger.debug( + `Subscribed successfully to channel ${channelId}! This client is currently subscribed to ${count} channels.` + ); + }).catch(err => { + logger.error('Failed to subscribe to the channel', err); + + channel.close(); + }); + logger.info('Connected'); + }); + + redisSubscriber.on('messageBuffer', (redisChannel, data) => { + // the only channel we subscribe to is the channelId... + try { + const message = HamokMessage.fromBinary(data); + channel.emit('message', message); + } catch (err) { + logger.error('Failed to parse message %o', err); + } + }); + + return channel; + } + + private _remotePeerJoined(peerId: string) { + logger.info('Remote peer joined', peerId); + } + + private _remotePeerLeft(peerId: string) { + logger.info('Remote peer left', peerId); + + if (this.hamok.leader) { + // if this node is the leader and another peer left, maybe we need to do something with some stuffs? + } + } + + private async _leaderChanged(leaderId: string | undefined) { + if (leaderId !== this.hamok.localPeerId) { + return; + } + // if this node is the leader, we need to do something with some stuffs? + } + +} diff --git a/media-server/src/services/IntervalTrackerService.ts b/media-server/src/services/IntervalTrackerService.ts new file mode 100644 index 0000000..2294cb2 --- /dev/null +++ b/media-server/src/services/IntervalTrackerService.ts @@ -0,0 +1,39 @@ +import { createLogger } from "../common/logger"; + +const logger = createLogger('IntervalTrackerService'); + +export class IntervalTrackerService { + + private _running = false; + private readonly _queue: [name: string, (() => ReturnType)][] = []; + public readonly intervals = new Map>(); + + public addInterval(name: string, interval: () => ReturnType) { + this._queue.push([name, interval]); + } + + public start() { + if (this._running) { + return; + } + this._running = true; + + for (const [name, interval] of this._queue) { + if (this.intervals.has(name)) { + logger.warn(`Interval ${name} already exists, clearing it`); + + clearInterval(this.intervals.get(name)!); + } + this.intervals.set(name, interval()); + } + } + + public stop() { + if (!this._running) { + return; + } + this._running = false; + this.intervals.forEach((interval) => clearInterval(interval)); + this.intervals.clear(); + } +} \ No newline at end of file diff --git a/media-server/src/services/MediasoupService.ts b/media-server/src/services/MediasoupService.ts index 7327584..87633c1 100644 --- a/media-server/src/services/MediasoupService.ts +++ b/media-server/src/services/MediasoupService.ts @@ -4,10 +4,16 @@ import { ClientContext } from '../common/ClientContext'; import { ControlConsumerNotification } from '../protocols/MessageProtocol'; import dns from 'dns'; import os from 'os'; -import { createRandomCallId } from '../common/utils'; +import { cmpUuids } from '../common/utils'; +import { WebRtcTransport } from 'mediasoup/node/lib/types'; +import { EventEmitter } from 'stream'; const logger = createLogger('MediasoupService'); +export type MediasoupServiceEventMap = { + 'new-webrtc-media-consumer': [ consumer: mediasoup.types.Consumer ], +} + export type MediasoupServiceConfig = { numberOfWorkers: number; workerSettings: mediasoup.types.WorkerSettings; @@ -33,35 +39,63 @@ type ProducerAppData = { routerId: string; transportId: string; remoteClosed?: boolean; + piped?: boolean; } type ConsumerAppData = { remoteClosed?: boolean; } -export class MediasoupService { +export type PipeTransportAppData = { + remoteRouterId: string; + connecting?: Promise; + connected: boolean; +} + +type PipedMediaProducerAppData = { + srcRouterId: string; + routerId: string; +} + +type PipedMediaConsumerAppData = { + srcRouterId: string; + dstRouterId: string; +} + +export class MediasoupService extends EventEmitter { private _run = false; public readonly workers = new Map>(); - public readonly calls = new Map>(); public readonly routers = new Map>(); public readonly transports = new Map(); public readonly mediaProducers = new Map>(); public readonly mediaConsumers = new Map>(); public readonly dataProducers = new Map(); public readonly dataConsumers = new Map(); + + private readonly _pipedMediaProducer = new Map>>(); + private readonly _pipedMediaConsumer = new Map>(); + private readonly _pipes = new Map>>(); + private _announcedAddress?: string; public constructor( public readonly config: MediasoupServiceConfig ) { - + super(); } + + public createRemotePipeTransport?: (options: { srcRouterId: string, dstRouterId: string }) => Promise<{ ip: string, port: number }>; + public connectRemotePipeTransport?: (options: { srcRouterId: string, dstRouterId: string, ip: string, port: number }) => Promise; + public pipeRemoteMediaProducerTo?: (options: { mediaProducerId: string, srcRouterId: string, dstRouterId: string }) => Promise; public async start() { if (this._run) return; this._run = true; const webrtcServerListeningPorts = new Set(); - const address = await dns.promises.lookup(os.hostname()); + + logger.info('os.hostname(): %o', os.hostname()); + + const address = await dns.promises.lookup(os.hostname()).catch(err => (logger.error('Error occurred while trying to get hostname', err), { address: undefined })); this._announcedAddress = address.address; @@ -123,14 +157,22 @@ export class MediasoupService { } } - public async createRouter(): Promise> { + public get announcedAddress() { + return this._announcedAddress ?? '127.0.0.1'; + } + + public closePipedMediaProducer(mediaProducerId: string) { + this._pipedMediaProducer.get(mediaProducerId)?.then(producer => producer.close()); + } + + public async createRouter(callId: string): Promise> { if (!this.workers) throw new Error('Worker is not started'); const worker = [...this.workers.values()][Math.floor(Math.random() * this.workers.size)]; const newrouter = await worker.createRouter({ mediaCodecs: this.config.mediaCodecs, appData: { - callId: createRandomCallId(), + callId, workerPid: worker.pid, dataConsumers: new Map(), dataProducers: new Map(), @@ -144,21 +186,150 @@ export class MediasoupService { newrouter.observer.once('close', () => { newrouter.observer.off('newtransport', addTransport); - this.calls.delete(newrouter.appData.callId); this.routers.delete(newrouter.id); logger.info(`Router ${newrouter.id} closed`); }) newrouter.observer.on('newtransport', addTransport); - this.calls.set(newrouter.appData.callId, newrouter); this.routers.set(newrouter.id, newrouter); logger.info(`Router ${newrouter.id} created`); return newrouter; } + public async getOrCreatePipedMediaProducer(options: { mediaProducerId: string, localRouterId: string, remoteRouterId: string }): Promise { + const existingMediaProducer = this._pipedMediaProducer.get(options.mediaProducerId); + const localRouter = this.routers.get(options.localRouterId); + + if (existingMediaProducer) return existingMediaProducer; + else if (!localRouter) throw new Error(`Router ${options.localRouterId} not found`); + + const pipedMediaProducer = (async () => { + if (!this.pipeRemoteMediaProducerTo) throw new Error('consumeRemoteProducer is not set'); + + const localPipeTransport = await this._pipeToRemoteRouter({ localRouterId: options.localRouterId, remoteRouterId: options.remoteRouterId }); + const producerOptions = await this.pipeRemoteMediaProducerTo({ + dstRouterId: options.localRouterId, + srcRouterId: options.remoteRouterId, + mediaProducerId: options.mediaProducerId, + }); + const mediaProducer = await localPipeTransport.produce({ + ...producerOptions, + appData: { + srcRouterId: options.remoteRouterId, + routerId: options.localRouterId, + } + }); + + logger.debug(`Piped media producer ${mediaProducer.id} is created from router ${options.remoteRouterId} to router ${options.localRouterId}. kind: ${mediaProducer.kind}, paused: ${mediaProducer.paused}`); + + mediaProducer.observer.once('close', () => { + this._pipedMediaProducer.delete(options.mediaProducerId); + + logger.debug(`Piped media producer ${mediaProducer.id} closed`); + }); + + return mediaProducer; + })(); + + this._pipedMediaProducer.set(options.mediaProducerId, pipedMediaProducer); + + logger.debug(`Piped media producer ${options.mediaProducerId} is creating from router ${options.remoteRouterId} to router ${options.localRouterId}`); + + return pipedMediaProducer; + } + + public async getOrCreatePipedMediaConsumer(options: { srcRouterId: string, dstRouterId: string, mediaProducerId: string }): Promise> { + let existingMediaConsumer = this._findExistingPipedMediaConsumer(options.srcRouterId, options.dstRouterId, options.mediaProducerId); + const srcRouter = this.routers.get(options.srcRouterId); + + if (existingMediaConsumer) return existingMediaConsumer; + else if (!srcRouter) throw new Error(`Router ${options.srcRouterId} not found`); + + const pipeTransport = await this.getOrCreatePipeTransport(options.srcRouterId, options.dstRouterId); + + if (!pipeTransport) throw new Error(`Pipe transport from ${options.srcRouterId} to ${options.dstRouterId} not found`); + + await pipeTransport.appData.connecting; + + const mediaProducer = this.mediaProducers.get(options.mediaProducerId); + if (!mediaProducer) throw new Error(`Media producer ${options.mediaProducerId} not found`); + + const consumer = await pipeTransport.consume({ + producerId: mediaProducer.id, + appData: { + dstRouterId: options.dstRouterId, + srcRouterId: options.srcRouterId, + } + }); + + existingMediaConsumer = this._findExistingPipedMediaConsumer(options.srcRouterId, options.dstRouterId, options.mediaProducerId); + + if (existingMediaConsumer) { + logger.warn(`Piped media consumer ${existingMediaConsumer.id} already created for media producer ${options.mediaProducerId} meanwhile creating new one`); + consumer.close(); + + return existingMediaConsumer; + } + + consumer.observer.once('close', () => { + this._pipedMediaConsumer.delete(options.mediaProducerId); + + logger.debug(`Piped media consumer ${consumer.id} closed for media producer ${options.mediaProducerId}`); + }); + this._pipedMediaConsumer.set(options.mediaProducerId, consumer); + + logger.debug(`Piped media consumer ${consumer.id} created for media producer ${options.mediaProducerId} from router ${options.srcRouterId} to router ${options.dstRouterId}. kind: ${consumer.kind}, paused: ${consumer.paused}`); + + return consumer; + } + + public getOrCreatePipeTransport(srcRouterId: string, dstRouterId: string): Promise> { + const srcRouter = this.routers.get(srcRouterId); + const existingPipe = this._pipes.get(dstRouterId); + + if (!srcRouter) { + throw new Error(`Router ${srcRouterId} or ${dstRouterId} not found`); + } else if (existingPipe) { + return existingPipe; + } + + const pipeTransport = (async () => { + try { + const result = await srcRouter.createPipeTransport({ + listenInfo: { + ip: this._announcedAddress ?? '127.0.0.1', + protocol: 'udp', + }, + appData: { + remoteRouterId: dstRouterId, + connecting: undefined, + connected: false, + } + }); + + result.observer.once('close', () => { + this._pipes.delete(dstRouterId); + }); + + logger.debug(`Pipe transport created. srcRouterId: ${srcRouterId}, dstRouter: ${dstRouterId}, listenInfo: %o`, result.tuple); + + return result; + } catch (err) { + logger.error(`Error occurred while creating pipe transport`, err); + this._pipes.delete(dstRouterId); + throw err; + } + + })(); + + this._pipes.set(dstRouterId, pipeTransport); + + return pipeTransport; + } + public async consumeMediaProducer(producerId: string, consumingClient: ClientContext): Promise { - const mediaProducer = this.mediaProducers.get(producerId); + const mediaProducer = this.mediaProducers.get(producerId) ?? (await this._pipedMediaProducer.get(producerId)); logger.info(`Attempt to consume media producer ${mediaProducer?.id} to client ${consumingClient.clientId}`); if (!mediaProducer || !consumingClient) { @@ -193,7 +364,6 @@ export class MediasoupService { consumer.observer.once('close', () => { mediaProducer.observer.off('pause', onProducerPause); mediaProducer.observer.off('resume', onProducerResume); - consumingClient.mediaConsumers.delete(consumer.id); if (!consumer.appData.remoteClosed) { consumingClient.send(new ControlConsumerNotification( @@ -204,13 +374,62 @@ export class MediasoupService { }); mediaProducer.observer.on('pause', onProducerPause); mediaProducer.observer.on('resume', onProducerResume); - consumingClient.mediaConsumers.add(consumer.id); return consumer; }; - private _addTransport = (router: mediasoup.types.Router, transport: mediasoup.types.Transport) => { + private async _pipeToRemoteRouter(options: { remoteRouterId: string, localRouterId: string }): Promise> { + if (!this.createRemotePipeTransport) throw new Error('createRemotePipeTransport is not set'); + else if (!this.connectRemotePipeTransport) throw new Error('connectRemotePipeTransport is not set'); + + const localPipeTransport = await this.getOrCreatePipeTransport(options.localRouterId, options.remoteRouterId); + + if (localPipeTransport.appData.connecting) { + await localPipeTransport.appData.connecting; + + return localPipeTransport; + } + + logger.debug(`Connecting pipe transports from ${options.localRouterId} to ${options.remoteRouterId}`); + + const { ip: remoteIp, port: remotePort } = await this.createRemotePipeTransport({ + srcRouterId: options.remoteRouterId, + dstRouterId: options.localRouterId, + }); + + logger.debug(`Remote Pipe transport from ${options.remoteRouterId} is created. Address: ${remoteIp}:${remotePort}. is local pipe transport connecting? ${localPipeTransport.appData.connecting !== undefined}`); + + if (localPipeTransport.appData.connecting) { + await localPipeTransport.appData.connecting; + + return localPipeTransport; + } + + localPipeTransport.appData.connecting = Promise.all([ + localPipeTransport.connect({ ip: remoteIp, port: remotePort }), + this.connectRemotePipeTransport({ + srcRouterId: options.remoteRouterId, + dstRouterId: options.localRouterId, + ip: localPipeTransport.tuple.localIp, + port: localPipeTransport.tuple.localPort, + }) + ]).then(() => void 0) + + await localPipeTransport.appData.connecting; + localPipeTransport.appData.connected = true; + + logger.debug(`Pipe transports are connected between ${options.localRouterId} to ${options.remoteRouterId}`); + + return localPipeTransport; + } + + private _addTransport = (router: mediasoup.types.Router, transport: mediasoup.types.Transport) => { + if (transport instanceof WebRtcTransport === false) { + // at the moment we only add webrtc transport + return; + } + const addProducer = (producer: mediasoup.types.Producer) => this._addProducer(router, transport, producer); const addConsumer = (consumer: mediasoup.types.Consumer) => this._addConsumer(router, transport, consumer); const addDataProducer = (dataProducer: mediasoup.types.DataProducer) => this._addDataProducer(router, transport, dataProducer); @@ -241,7 +460,6 @@ export class MediasoupService { this.transports.set(transport.id, transport); logger.info(`Transport ${transport.id} created on router ${router.id}`); - } private _addProducer = (router: mediasoup.types.Router, transport: mediasoup.types.Transport, producer: mediasoup.types.Producer) => { @@ -272,6 +490,9 @@ export class MediasoupService { router.appData.mediaConsumers.set(consumer.id, consumer); this.mediaConsumers.set(consumer.id, consumer); + if (transport instanceof WebRtcTransport) { + this.emit('new-webrtc-media-consumer', consumer); + } logger.info(`Consumer ${consumer.id} created on transport ${transport.id} on router ${router.id}`); } @@ -300,6 +521,8 @@ export class MediasoupService { logger.info(`Data consumer ${dataConsumer.id} created on transport ${transport.id} on router ${router.id}`); } - + private _findExistingPipedMediaConsumer(srcRouterId: string, dstRouterId: string, mediaProducerId: string): mediasoup.types.Consumer | undefined { + return [ ...this._pipedMediaConsumer.values() ].find(consumer => consumer.appData.srcRouterId === srcRouterId && consumer.appData.dstRouterId === dstRouterId && consumer.producerId === mediaProducerId); + } } \ No newline at end of file diff --git a/media-server/yarn.lock b/media-server/yarn.lock index 8f9a72c..2321b0f 100644 --- a/media-server/yarn.lock +++ b/media-server/yarn.lock @@ -3,48 +3,44 @@ "@babel/code-frame@^7.12.13": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465" - integrity sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA== + version "7.26.0" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.0.tgz" + integrity sha512-INCKxTtbXtcNbUZ3YXutwMpEleqttcswhAdee7dhuoVrD2cnuc3PqtERBtxkX5nziX9vnBL8WXmSGwv8CuPV6g== dependencies: - "@babel/highlight" "^7.24.7" - picocolors "^1.0.0" - -"@babel/helper-validator-identifier@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" - integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== - -"@babel/highlight@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.7.tgz#a05ab1df134b286558aae0ed41e6c5f731bf409d" - integrity sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw== - dependencies: - "@babel/helper-validator-identifier" "^7.24.7" - chalk "^2.4.2" + "@babel/helper-validator-identifier" "^7.25.9" js-tokens "^4.0.0" picocolors "^1.0.0" +"@babel/helper-validator-identifier@^7.25.9": + version "7.25.9" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz" + integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== + "@bufbuild/protobuf@1.1.1": version "1.1.1" - resolved "https://registry.yarnpkg.com/@bufbuild/protobuf/-/protobuf-1.1.1.tgz#ed2a8d25975ccc405393c96aaf707c1897e249d3" + resolved "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.1.1.tgz" integrity sha512-1dS2m3jabfW2XL2K6//41wZkO4XJOZtpe0bKLuta0BvK5LxNRyUBSSN5835n3bIFiNJKFWhDnzZEMrV7QVGFfQ== "@bufbuild/protobuf@^1.10.0", "@bufbuild/protobuf@^1.2.0": version "1.10.0" - resolved "https://registry.yarnpkg.com/@bufbuild/protobuf/-/protobuf-1.10.0.tgz#1a67ac889c2d464a3492b3e54c38f80517963b16" + resolved "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.0.tgz" integrity sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag== "@cspotcode/source-map-support@^0.8.0": version "0.8.1" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + resolved "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz" integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@ioredis/commands@^1.1.1": + version "1.2.0" + resolved "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz" + integrity sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg== + "@isaacs/cliui@^8.0.2": version "8.0.2" - resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + resolved "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz" integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== dependencies: string-width "^5.1.2" @@ -56,28 +52,28 @@ "@isaacs/fs-minipass@^4.0.0": version "4.0.1" - resolved "https://registry.yarnpkg.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz#2d59ae3ab4b38fb4270bfa23d30f8e2e86c7fe32" + resolved "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz" integrity sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w== dependencies: minipass "^7.0.4" "@jest/expect-utils@^29.7.0": version "29.7.0" - resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.7.0.tgz#023efe5d26a8a70f21677d0a1afc0f0a44e3a1c6" + resolved "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz" integrity sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA== dependencies: jest-get-type "^29.6.3" "@jest/schemas@^29.6.3": version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz" integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== dependencies: "@sinclair/typebox" "^0.27.8" "@jest/types@^29.6.3": version "29.6.3" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + resolved "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz" integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== dependencies: "@jest/schemas" "^29.6.3" @@ -89,17 +85,17 @@ "@jridgewell/resolve-uri@^3.0.3": version "3.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz" integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== "@jridgewell/sourcemap-codec@^1.4.10": version "1.5.0" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz" integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== "@jridgewell/trace-mapping@0.3.9": version "0.3.9" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz" integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== dependencies: "@jridgewell/resolve-uri" "^3.0.3" @@ -107,7 +103,7 @@ "@observertc/observer-js@^0.42.6": version "0.42.6" - resolved "https://registry.yarnpkg.com/@observertc/observer-js/-/observer-js-0.42.6.tgz#06d58aeb80817b09616ea337252f4a3423f2d662" + resolved "https://registry.npmjs.org/@observertc/observer-js/-/observer-js-0.42.6.tgz" integrity sha512-wKoBHUg0gydRMR/Y+J5e2cg03knrGsOVV99XOgnVIfjVC9IKnZdKuzltDxWfp8DvTLICzXFQNfw14s7/2v68Lw== dependencies: "@bufbuild/protobuf" "1.1.1" @@ -117,36 +113,36 @@ "@observertc/report-schemas-js@2.2.10": version "2.2.10" - resolved "https://registry.yarnpkg.com/@observertc/report-schemas-js/-/report-schemas-js-2.2.10.tgz#6ba5a7414ae4e47a5aa41321eae43b2def058427" + resolved "https://registry.npmjs.org/@observertc/report-schemas-js/-/report-schemas-js-2.2.10.tgz" integrity sha512-SdsJS2/dMpK/kSjbZBuflMMibHNh5af/Q0PC29sYkCj321wNDnNSzGlf2C/w3ohSxJZBUJToG4N/ZnHRGzbtKQ== "@observertc/sample-schemas-js@2.2.1-rc.0": version "2.2.1-rc.0" - resolved "https://registry.yarnpkg.com/@observertc/sample-schemas-js/-/sample-schemas-js-2.2.1-rc.0.tgz#db4555cc795616b0649c7d3bf516ca4f85981e37" + resolved "https://registry.npmjs.org/@observertc/sample-schemas-js/-/sample-schemas-js-2.2.1-rc.0.tgz" integrity sha512-pg+YeLi5rXx9ggXNbZe5QJ1vrpUdMxbw+Xfd0xG/B7Cf3sXcWQzDrIsts7on2jHcIY+rODV8WFaIMfmCHdIBVQ== "@observertc/sample-schemas-js@2.2.10": version "2.2.10" - resolved "https://registry.yarnpkg.com/@observertc/sample-schemas-js/-/sample-schemas-js-2.2.10.tgz#b7d6419cacdb79c700accd344cf110dbe93a5c5c" + resolved "https://registry.npmjs.org/@observertc/sample-schemas-js/-/sample-schemas-js-2.2.10.tgz" integrity sha512-pz9DDh7fd7i7c6ttLaCvUh1Uile5UP6dA3y3q+SJT0JOF7NDyGCPKGMTagOknR2s21p3U/XlLLcQIH4BoveOGg== "@observertc/samples-decoder@^2.2.12": version "2.2.12" - resolved "https://registry.yarnpkg.com/@observertc/samples-decoder/-/samples-decoder-2.2.12.tgz#da3883d9c08274bb60ac8e4beacd8b06e1b89983" + resolved "https://registry.npmjs.org/@observertc/samples-decoder/-/samples-decoder-2.2.12.tgz" integrity sha512-LEfcRLCKfAHUIFC9HCk/cN0ZWnPfv7SHpPZrIFkNGX0bkib5WSEu5Ju8koFqc2u7crFX6AXvyGuJBd2cm5OHJg== dependencies: "@bufbuild/protobuf" "^1.2.0" "@observertc/samples-encoder@^2.2.8": version "2.2.12" - resolved "https://registry.yarnpkg.com/@observertc/samples-encoder/-/samples-encoder-2.2.12.tgz#85345d1ba386cf300b1ba766f201dbab6a878670" + resolved "https://registry.npmjs.org/@observertc/samples-encoder/-/samples-encoder-2.2.12.tgz" integrity sha512-m0L+MZKSTzFT0+nfgro3mAwzioBw+4rDFyZgsRVWZ8MD9jInnXVneRegUlUQCrYIw0O0lWsLR7/UGo9Fj/GJaw== dependencies: "@bufbuild/protobuf" "^1.2.0" "@observertc/sfu-monitor-js@^2.0.0": version "2.0.3" - resolved "https://registry.yarnpkg.com/@observertc/sfu-monitor-js/-/sfu-monitor-js-2.0.3.tgz#3719cb2d60bced2dccbffc49829c1bf3b182af9c" + resolved "https://registry.npmjs.org/@observertc/sfu-monitor-js/-/sfu-monitor-js-2.0.3.tgz" integrity sha512-TFbLdgE8v5opo6KeueylakMqDXMhl7AuqBaUwcCGB5cMboA9ISl9BBC8hc7Yk3OaNDPlsFtnDmr11Npi2IJb6w== dependencies: "@observertc/sample-schemas-js" "2.2.1-rc.0" @@ -157,78 +153,78 @@ "@pkgjs/parseargs@^0.11.0": version "0.11.0" - resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== "@sinclair/typebox@^0.27.8": version "0.27.8" - resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz" integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== "@tsconfig/node10@^1.0.7": version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" + resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz" integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== "@tsconfig/node12@^1.0.7": version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + resolved "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz" integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== "@tsconfig/node14@^1.0.0": version "1.0.3" - resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + resolved "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz" integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== "@tsconfig/node16@^1.0.2": version "1.0.4" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz" integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== "@tsconfig/node20@^1.0.2": version "1.0.2" - resolved "https://registry.yarnpkg.com/@tsconfig/node20/-/node20-1.0.2.tgz#ada464d3f8dc6c991f64d38712573277862bb5c7" + resolved "https://registry.npmjs.org/@tsconfig/node20/-/node20-1.0.2.tgz" integrity sha512-pw0MmECiSTbBfIlT0x3iQLuJ8s3i2mwYoGxJ3vzqTNMdc4nO2VeqfCOQ/doGFa8iyPlqmBd98/5pBctWz7uN2A== "@types/debug@^4.1.12": version "4.1.12" - resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917" + resolved "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz" integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ== dependencies: "@types/ms" "*" "@types/events@^3.0.0": version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.3.tgz#a8ef894305af28d1fc6d2dfdfc98e899591ea529" + resolved "https://registry.npmjs.org/@types/events/-/events-3.0.3.tgz" integrity sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g== "@types/ini@^4.1.1": version "4.1.1" - resolved "https://registry.yarnpkg.com/@types/ini/-/ini-4.1.1.tgz#6984664a8cc74c3348f4049d0bf2b1ab2d061ca3" + resolved "https://registry.npmjs.org/@types/ini/-/ini-4.1.1.tgz" integrity sha512-MIyNUZipBTbyUNnhvuXJTY7B6qNI78meck9Jbv3wk0OgNwRyOOVEKDutAkOs1snB/tx0FafyR6/SN4Ps0hZPeg== "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": version "2.0.6" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7" + resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz" integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== "@types/istanbul-lib-report@*": version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz#53047614ae72e19fc0401d872de3ae2b4ce350bf" + resolved "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz" integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== dependencies: "@types/istanbul-lib-coverage" "*" "@types/istanbul-reports@^3.0.0": version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz#0f03e3d2f670fbdac586e34b433783070cc16f54" + resolved "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz" integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== dependencies: "@types/istanbul-lib-report" "*" "@types/jest@^29.5.1": version "29.5.12" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.12.tgz#7f7dc6eb4cf246d2474ed78744b05d06ce025544" + resolved "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz" integrity sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw== dependencies: expect "^29.0.0" @@ -236,130 +232,123 @@ "@types/ms@*": version "0.7.34" - resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.34.tgz#10964ba0dee6ac4cd462e2795b6bebd407303433" + resolved "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz" integrity sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g== -"@types/node@*": - version "22.5.4" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.5.4.tgz#83f7d1f65bc2ed223bdbf57c7884f1d5a4fa84e8" - integrity sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg== +"@types/node@*", "@types/node@^22.8.4": + version "22.8.4" + resolved "https://registry.npmjs.org/@types/node/-/node-22.8.4.tgz" + integrity sha512-SpNNxkftTJOPk0oN+y2bIqurEXHTA2AOZ3EJDDKeJ5VzkvvORSvmQXGQarcOzWV1ac7DCaPBEdMDxBsM+d8jWw== dependencies: - undici-types "~6.19.2" + undici-types "~6.19.8" "@types/pino@^7.0.5": version "7.0.5" - resolved "https://registry.yarnpkg.com/@types/pino/-/pino-7.0.5.tgz#1c84a81b924a6a9e263dbb581dffdbad7a3c60c4" + resolved "https://registry.npmjs.org/@types/pino/-/pino-7.0.5.tgz" integrity sha512-wKoab31pknvILkxAF8ss+v9iNyhw5Iu/0jLtRkUD74cNfOOLJNnqfFKAv0r7wVaTQxRZtWrMpGfShwwBjOcgcg== dependencies: pino "*" "@types/stack-utils@^2.0.0": version "2.0.3" - resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" + resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz" integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== "@types/strip-bom@^3.0.0": version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/strip-bom/-/strip-bom-3.0.0.tgz#14a8ec3956c2e81edb7520790aecf21c290aebd2" + resolved "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz" integrity sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ== "@types/strip-json-comments@0.0.30": version "0.0.30" - resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1" + resolved "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz" integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ== "@types/uuid@^10.0.0": version "10.0.0" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-10.0.0.tgz#e9c07fe50da0f53dc24970cca94d619ff03f6f6d" + resolved "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz" integrity sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ== "@types/uuid@^8.3.4": version "8.3.4" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" + resolved "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz" integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== "@types/ws@^8.2.2", "@types/ws@^8.5.3": version "8.5.12" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.12.tgz#619475fe98f35ccca2a2f6c137702d85ec247b7e" + resolved "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz" integrity sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ== dependencies: "@types/node" "*" "@types/yaml@^1.9.7": version "1.9.7" - resolved "https://registry.yarnpkg.com/@types/yaml/-/yaml-1.9.7.tgz#2331f36e0aac91311a63d33eb026c21687729679" + resolved "https://registry.npmjs.org/@types/yaml/-/yaml-1.9.7.tgz" integrity sha512-8WMXRDD1D+wCohjfslHDgICd2JtMATZU8CkhH8LVJqcJs6dyYj5TGptzP8wApbmEullGBSsCEzzap73DQ1HJaA== dependencies: yaml "*" "@types/yargs-parser@*": version "21.0.3" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" + resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz" integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== "@types/yargs@^17.0.8": version "17.0.33" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" + resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz" integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== dependencies: "@types/yargs-parser" "*" abort-controller@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + resolved "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz" integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== dependencies: event-target-shim "^5.0.0" acorn-walk@^8.1.1: version "8.3.4" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" + resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz" integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== dependencies: acorn "^8.11.0" acorn@^8.11.0, acorn@^8.4.1: version "8.12.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz" integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== ansi-regex@^5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== ansi-regex@^6.0.1: version "6.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz" integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== dependencies: color-convert "^2.0.1" ansi-styles@^5.0.0: version "5.2.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== ansi-styles@^6.1.0: version "6.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz" integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== anymatch@~3.1.2: version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz" integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== dependencies: normalize-path "^3.0.0" @@ -367,37 +356,37 @@ anymatch@~3.1.2: arg@^4.1.0: version "4.1.3" - resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + resolved "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz" integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== async@^3.2.3: version "3.2.6" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" + resolved "https://registry.npmjs.org/async/-/async-3.2.6.tgz" integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== atomic-sleep@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" + resolved "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz" integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== balanced-match@^1.0.0: version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== base64-js@^1.3.1: version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== binary-extensions@^2.0.0: version "2.3.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz" integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== brace-expansion@^1.1.7: version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== dependencies: balanced-match "^1.0.0" @@ -405,55 +394,46 @@ brace-expansion@^1.1.7: brace-expansion@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz" integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== dependencies: balanced-match "^1.0.0" braces@^3.0.3, braces@~3.0.2: version "3.0.3" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + resolved "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz" integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: fill-range "^7.1.1" bs-logger@^0.2.6: version "0.2.6" - resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + resolved "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz" integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== dependencies: fast-json-stable-stringify "2.x" buffer-equal-constant-time@1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + resolved "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz" integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== buffer-from@^1.0.0: version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== buffer@^6.0.3: version "6.0.3" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz" integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== dependencies: base64-js "^1.3.1" ieee754 "^1.2.1" -chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - chalk@^4.0.0, chalk@^4.0.2: version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== dependencies: ansi-styles "^4.1.0" @@ -461,7 +441,7 @@ chalk@^4.0.0, chalk@^4.0.2: chokidar@^3.5.1, chokidar@^3.5.2: version "3.6.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz" integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== dependencies: anymatch "~3.1.2" @@ -476,56 +456,49 @@ chokidar@^3.5.1, chokidar@^3.5.2: chownr@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-3.0.0.tgz#9855e64ecd240a9cc4267ce8a4aa5d24a1da15e4" + resolved "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz" integrity sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g== ci-info@^3.2.0: version "3.9.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz" integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" +cluster-key-slot@^1.1.0: + version "1.1.2" + resolved "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz" + integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA== color-convert@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== dependencies: color-name "~1.1.4" -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - color-name@~1.1.4: version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== colorette@^2.0.7: version "2.0.20" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + resolved "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz" integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== concat-map@0.0.1: version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== create-require@^1.1.0: version "1.1.1" - resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== cross-spawn@^7.0.0: version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== dependencies: path-key "^3.1.0" @@ -534,97 +507,97 @@ cross-spawn@^7.0.0: data-uri-to-buffer@^4.0.0: version "4.0.1" - resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e" + resolved "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz" integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== dateformat@^4.6.3: version "4.6.3" - resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-4.6.3.tgz#556fa6497e5217fedb78821424f8a1c22fa3f4b5" + resolved "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz" integrity sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA== debug@^4, debug@^4.3.4, debug@^4.3.7: version "4.3.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz" integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== dependencies: ms "^2.1.3" +denque@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz" + integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== + diff-sequences@^29.6.3: version "29.6.3" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz" integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== diff@^4.0.1: version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== dynamic-dedupe@^0.3.0: version "0.3.0" - resolved "https://registry.yarnpkg.com/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz#06e44c223f5e4e94d78ef9db23a6515ce2f962a1" + resolved "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz" integrity sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ== dependencies: xtend "^4.0.0" eastasianwidth@^0.2.0: version "0.2.0" - resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== ecdsa-sig-formatter@1.0.11: version "1.0.11" - resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + resolved "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz" integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== dependencies: safe-buffer "^5.0.1" ejs@^3.1.10: version "3.1.10" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" + resolved "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz" integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== dependencies: jake "^10.8.5" emoji-regex@^8.0.0: version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== emoji-regex@^9.2.2: version "9.2.2" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== end-of-stream@^1.1.0: version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== dependencies: once "^1.4.0" -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - escape-string-regexp@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== event-target-shim@^5.0.0: version "5.0.1" - resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== events@^3.3.0: version "3.3.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== expect@^29.0.0: version "29.7.0" - resolved "https://registry.yarnpkg.com/expect/-/expect-29.7.0.tgz#578874590dcb3214514084c08115d8aee61e11bc" + resolved "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz" integrity sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw== dependencies: "@jest/expect-utils" "^29.7.0" @@ -635,27 +608,27 @@ expect@^29.0.0: fast-copy@^3.0.2: version "3.0.2" - resolved "https://registry.yarnpkg.com/fast-copy/-/fast-copy-3.0.2.tgz#59c68f59ccbcac82050ba992e0d5c389097c9d35" + resolved "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz" integrity sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ== fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== fast-redact@^3.1.1: version "3.5.0" - resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.5.0.tgz#e9ea02f7e57d0cd8438180083e93077e496285e4" + resolved "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz" integrity sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A== fast-safe-stringify@^2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" + resolved "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz" integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== fetch-blob@^3.1.2, fetch-blob@^3.1.4: version "3.2.0" - resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" + resolved "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz" integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ== dependencies: node-domexception "^1.0.0" @@ -663,26 +636,26 @@ fetch-blob@^3.1.2, fetch-blob@^3.1.4: filelist@^1.0.4: version "1.0.4" - resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" + resolved "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz" integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== dependencies: minimatch "^5.0.1" fill-range@^7.1.1: version "7.1.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz" integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== dependencies: to-regex-range "^5.0.1" flatbuffers@^24.3.25: version "24.3.25" - resolved "https://registry.yarnpkg.com/flatbuffers/-/flatbuffers-24.3.25.tgz#e2f92259ba8aa53acd0af7844afb7c7eb95e7089" + resolved "https://registry.npmjs.org/flatbuffers/-/flatbuffers-24.3.25.tgz" integrity sha512-3HDgPbgiwWMI9zVB7VYBHaMrbOO7Gm0v+yD2FV/sCKj+9NDeVL7BOBYUuhWAQGKWOzBo8S9WdMvV0eixO233XQ== foreground-child@^3.1.0: version "3.3.0" - resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.0.tgz#0ac8644c06e431439f8561db8ecf29a7b5519c77" + resolved "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz" integrity sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg== dependencies: cross-spawn "^7.0.0" @@ -690,36 +663,36 @@ foreground-child@^3.1.0: formdata-polyfill@^4.0.10: version "4.0.10" - resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" + resolved "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz" integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== dependencies: fetch-blob "^3.1.2" fs.realpath@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== fsevents@~2.3.2: version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== function-bind@^1.1.2: version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== glob-parent@~5.1.2: version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" glob@^10.3.7: version "10.4.5" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" + resolved "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz" integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== dependencies: foreground-child "^3.1.0" @@ -731,7 +704,7 @@ glob@^10.3.7: glob@^7.1.3: version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== dependencies: fs.realpath "^1.0.0" @@ -743,52 +716,61 @@ glob@^7.1.3: graceful-fs@^4.2.9: version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== h264-profile-level-id@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/h264-profile-level-id/-/h264-profile-level-id-2.0.0.tgz#b7ea45badbac8f5dbb9583d34b06db09764f2535" + resolved "https://registry.npmjs.org/h264-profile-level-id/-/h264-profile-level-id-2.0.0.tgz" integrity sha512-X4CLryVbVA0CtjTExS4G5U1gb2Z4wa32AF8ukVmFuLdw2JRq2aHisor7SY5SYTUUrUSqq0KdPIO18sql6IWIQw== dependencies: "@types/debug" "^4.1.12" debug "^4.3.4" +hamok@^2.6.1-76ef244.0: + version "2.6.1-fee52c2.0" + resolved "https://registry.npmjs.org/hamok/-/hamok-2.6.1-fee52c2.0.tgz" + integrity sha512-XBAZUyycHPqsmkimCR1Jz1fbnH0D1PYXWqS7Icl4eB8Rue6hf8dO50Ga1Hhl7cKmSlOwfaLVQ7GfpVJOHSU7dQ== + dependencies: + "@bufbuild/protobuf" "^1.10.0" + pino "^9.3.2" + uuid "^9.0.0" + has-flag@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== has-flag@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== hasown@^2.0.2: version "2.0.2" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz" integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== dependencies: function-bind "^1.1.2" help-me@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/help-me/-/help-me-5.0.0.tgz#b1ebe63b967b74060027c2ac61f9be12d354a6f6" + resolved "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz" integrity sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg== ieee754@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== ignore-by-default@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" + resolved "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz" integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== inflight@^1.0.4: version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== dependencies: once "^1.3.0" @@ -796,58 +778,73 @@ inflight@^1.0.4: inherits@2: version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== ini@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/ini/-/ini-5.0.0.tgz#a7a4615339843d9a8ccc2d85c9d81cf93ffbc638" + resolved "https://registry.npmjs.org/ini/-/ini-5.0.0.tgz" integrity sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw== +ioredis@^5.4.1: + version "5.4.1" + resolved "https://registry.npmjs.org/ioredis/-/ioredis-5.4.1.tgz" + integrity sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA== + dependencies: + "@ioredis/commands" "^1.1.1" + cluster-key-slot "^1.1.0" + debug "^4.3.4" + denque "^2.1.0" + lodash.defaults "^4.2.0" + lodash.isarguments "^3.1.0" + redis-errors "^1.2.0" + redis-parser "^3.0.0" + standard-as-callback "^2.1.0" + is-binary-path@~2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz" integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== dependencies: binary-extensions "^2.0.0" is-core-module@^2.13.0: version "2.15.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.1.tgz#a7363a25bee942fefab0de13bf6aa372c82dcc37" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz" integrity sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ== dependencies: hasown "^2.0.2" is-extglob@^2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== is-fullwidth-code-point@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== dependencies: is-extglob "^2.1.1" is-number@^7.0.0: version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== isexe@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== jackspeak@^3.1.2: version "3.4.3" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" + resolved "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz" integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== dependencies: "@isaacs/cliui" "^8.0.2" @@ -856,7 +853,7 @@ jackspeak@^3.1.2: jake@^10.8.5: version "10.9.2" - resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f" + resolved "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz" integrity sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA== dependencies: async "^3.2.3" @@ -866,7 +863,7 @@ jake@^10.8.5: jest-diff@^29.7.0: version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a" + resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz" integrity sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw== dependencies: chalk "^4.0.0" @@ -876,12 +873,12 @@ jest-diff@^29.7.0: jest-get-type@^29.6.3: version "29.6.3" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz" integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== jest-matcher-utils@^29.7.0: version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz#ae8fec79ff249fd592ce80e3ee474e83a6c44f12" + resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz" integrity sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g== dependencies: chalk "^4.0.0" @@ -891,7 +888,7 @@ jest-matcher-utils@^29.7.0: jest-message-util@^29.7.0: version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3" + resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz" integrity sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w== dependencies: "@babel/code-frame" "^7.12.13" @@ -906,7 +903,7 @@ jest-message-util@^29.7.0: jest-util@^29.0.0, jest-util@^29.7.0: version "29.7.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + resolved "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz" integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== dependencies: "@jest/types" "^29.6.3" @@ -918,22 +915,22 @@ jest-util@^29.0.0, jest-util@^29.7.0: joycon@^3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03" + resolved "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz" integrity sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw== js-tokens@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== json5@^2.2.3: version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== jsonwebtoken@^9.0.0: version "9.0.2" - resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3" + resolved "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz" integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ== dependencies: jws "^3.2.2" @@ -949,7 +946,7 @@ jsonwebtoken@^9.0.0: jwa@^1.4.1: version "1.4.1" - resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + resolved "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz" integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== dependencies: buffer-equal-constant-time "1.0.1" @@ -958,7 +955,7 @@ jwa@^1.4.1: jws@^3.2.2: version "3.2.2" - resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + resolved "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz" integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== dependencies: jwa "^1.4.1" @@ -966,62 +963,72 @@ jws@^3.2.2: kafkajs@^2.2.4: version "2.2.4" - resolved "https://registry.yarnpkg.com/kafkajs/-/kafkajs-2.2.4.tgz#59e6e16459d87fdf8b64be73970ed5aa42370a5b" + resolved "https://registry.npmjs.org/kafkajs/-/kafkajs-2.2.4.tgz" integrity sha512-j/YeapB1vfPT2iOIUn/vxdyKEuhuY2PxMBvf5JWux6iSaukAccrMtXEY/Lb7OvavDhOWME589bpLrEdnVHjfjA== +lodash.defaults@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz" + integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== + lodash.includes@^4.3.0: version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + resolved "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz" integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== +lodash.isarguments@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz" + integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg== + lodash.isboolean@^3.0.3: version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + resolved "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz" integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== lodash.isinteger@^4.0.4: version "4.0.4" - resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + resolved "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz" integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== lodash.isnumber@^3.0.3: version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + resolved "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz" integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== lodash.isplainobject@^4.0.6: version "4.0.6" - resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + resolved "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz" integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== lodash.isstring@^4.0.1: version "4.0.1" - resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + resolved "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz" integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== lodash.memoize@^4.1.2: version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz" integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== lodash.once@^4.0.0: version "4.1.1" - resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + resolved "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz" integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== lru-cache@^10.2.0: version "10.4.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz" integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== make-error@^1.1.1, make-error@^1.3.6: version "1.3.6" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== mediasoup@^3.14.7: version "3.14.14" - resolved "https://registry.yarnpkg.com/mediasoup/-/mediasoup-3.14.14.tgz#88f8de1dc04d726e85990381dbb52cacaf46f38a" + resolved "https://registry.npmjs.org/mediasoup/-/mediasoup-3.14.14.tgz" integrity sha512-BagHzOgtPUPxpPAHSMdFKICjtDuLNHznuk3YUUDDTFIwyTOYIXeX7NRklzMRAiRRKA9luXmjCjTNwkMQ4sb0aA== dependencies: "@types/ini" "^4.1.1" @@ -1035,7 +1042,7 @@ mediasoup@^3.14.7: micromatch@^4.0.4: version "4.0.8" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: braces "^3.0.3" @@ -1043,38 +1050,38 @@ micromatch@^4.0.4: minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" minimatch@^5.0.1: version "5.1.6" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz" integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== dependencies: brace-expansion "^2.0.1" minimatch@^9.0.4: version "9.0.5" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz" integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== dependencies: brace-expansion "^2.0.1" minimist@^1.2.6: version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== "minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.4, minipass@^7.1.2: version "7.1.2" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" + resolved "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz" integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== minizlib@^3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-3.0.1.tgz#46d5329d1eb3c83924eff1d3b858ca0a31581012" + resolved "https://registry.npmjs.org/minizlib/-/minizlib-3.0.1.tgz" integrity sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg== dependencies: minipass "^7.0.4" @@ -1082,27 +1089,27 @@ minizlib@^3.0.1: mkdirp@^1.0.4: version "1.0.4" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== mkdirp@^3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50" + resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz" integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg== ms@^2.1.1, ms@^2.1.3: version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== node-domexception@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + resolved "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz" integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== node-fetch@^3.3.2: version "3.3.2" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.3.2.tgz#d1e889bacdf733b4ff3b2b243eb7a12866a0b78b" + resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz" integrity sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA== dependencies: data-uri-to-buffer "^4.0.0" @@ -1111,7 +1118,7 @@ node-fetch@^3.3.2: nodemon@^3.0.1: version "3.1.4" - resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-3.1.4.tgz#c34dcd8eb46a05723ccde60cbdd25addcc8725e4" + resolved "https://registry.npmjs.org/nodemon/-/nodemon-3.1.4.tgz" integrity sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ== dependencies: chokidar "^3.5.2" @@ -1127,62 +1134,62 @@ nodemon@^3.0.1: normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== on-exit-leak-free@^2.1.0: version "2.1.2" - resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz#fed195c9ebddb7d9e4c3842f93f281ac8dadd3b8" + resolved "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz" integrity sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA== once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" package-json-from-dist@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz#e501cd3094b278495eb4258d4c9f6d5ac3019f00" + resolved "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz" integrity sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw== path-is-absolute@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== path-key@^3.1.0: version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== path-parse@^1.0.7: version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-scurry@^1.11.1: version "1.11.1" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + resolved "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz" integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== dependencies: lru-cache "^10.2.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" picocolors@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59" - integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw== + version "1.1.1" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== pino-abstract-transport@^1.0.0, pino-abstract-transport@^1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz#97f9f2631931e242da531b5c66d3079c12c9d1b5" + resolved "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz" integrity sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q== dependencies: readable-stream "^4.0.0" @@ -1190,7 +1197,7 @@ pino-abstract-transport@^1.0.0, pino-abstract-transport@^1.2.0: pino-pretty@^11.2.2: version "11.2.2" - resolved "https://registry.yarnpkg.com/pino-pretty/-/pino-pretty-11.2.2.tgz#5e8ec69b31e90eb187715af07b1d29a544e60d39" + resolved "https://registry.npmjs.org/pino-pretty/-/pino-pretty-11.2.2.tgz" integrity sha512-2FnyGir8nAJAqD3srROdrF1J5BIcMT4nwj7hHSc60El6Uxlym00UbCCd8pYIterstVBFlMyF1yFV8XdGIPbj4A== dependencies: colorette "^2.0.7" @@ -1210,12 +1217,12 @@ pino-pretty@^11.2.2: pino-std-serializers@^7.0.0: version "7.0.0" - resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz#7c625038b13718dbbd84ab446bd673dc52259e3b" + resolved "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz" integrity sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA== pino@*, pino@^9.3.2: version "9.4.0" - resolved "https://registry.yarnpkg.com/pino/-/pino-9.4.0.tgz#e4600ff199efc744856a5b3b71c53e38998eae5a" + resolved "https://registry.npmjs.org/pino/-/pino-9.4.0.tgz" integrity sha512-nbkQb5+9YPhQRz/BeQmrWpEknAaqjpAqRK8NwJpmrX/JHu7JuZC5G1CeAwJDJfGes4h+YihC6in3Q2nGb+Y09w== dependencies: atomic-sleep "^1.0.0" @@ -1232,7 +1239,7 @@ pino@*, pino@^9.3.2: pretty-format@^29.0.0, pretty-format@^29.7.0: version "29.7.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" + resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz" integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== dependencies: "@jest/schemas" "^29.6.3" @@ -1241,22 +1248,22 @@ pretty-format@^29.0.0, pretty-format@^29.7.0: process-warning@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-4.0.0.tgz#581e3a7a1fb456c5f4fd239f76bce75897682d5a" + resolved "https://registry.npmjs.org/process-warning/-/process-warning-4.0.0.tgz" integrity sha512-/MyYDxttz7DfGMMHiysAsFE4qF+pQYAA8ziO/3NcRVrQ5fSk+Mns4QZA/oRPFzvcqNoVJXQNWNAsdwBXLUkQKw== process@^0.11.10: version "0.11.10" - resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz" integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== pstree.remy@^1.1.8: version "1.1.8" - resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" + resolved "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz" integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== pump@^3.0.0: version "3.0.2" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.2.tgz#836f3edd6bc2ee599256c924ffe0d88573ddcbf8" + resolved "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz" integrity sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw== dependencies: end-of-stream "^1.1.0" @@ -1264,17 +1271,17 @@ pump@^3.0.0: quick-format-unescaped@^4.0.3: version "4.0.4" - resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7" + resolved "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz" integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== react-is@^18.0.0: version "18.3.1" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== readable-stream@^4.0.0: version "4.5.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.5.2.tgz#9e7fc4c45099baeed934bff6eb97ba6cf2729e09" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz" integrity sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g== dependencies: abort-controller "^3.0.0" @@ -1285,19 +1292,31 @@ readable-stream@^4.0.0: readdirp@~3.6.0: version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz" integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== dependencies: picomatch "^2.2.1" real-require@^0.2.0: version "0.2.0" - resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.2.0.tgz#209632dea1810be2ae063a6ac084fee7e33fba78" + resolved "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz" integrity sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg== +redis-errors@^1.0.0, redis-errors@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz" + integrity sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w== + +redis-parser@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz" + integrity sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A== + dependencies: + redis-errors "^1.0.0" + resolve@^1.0.0: version "1.22.8" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== dependencies: is-core-module "^2.13.0" @@ -1306,77 +1325,77 @@ resolve@^1.0.0: rimraf@^2.6.1: version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== dependencies: glob "^7.1.3" rimraf@^5.0.5: version "5.0.10" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.10.tgz#23b9843d3dc92db71f96e1a2ce92e39fd2a8221c" + resolved "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz" integrity sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ== dependencies: glob "^10.3.7" safe-buffer@^5.0.1, safe-buffer@~5.2.0: version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== safe-stable-stringify@^2.3.1: version "2.5.0" - resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz#4ca2f8e385f2831c432a719b108a3bf7af42a1dd" + resolved "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz" integrity sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA== secure-json-parse@^2.4.0: version "2.7.0" - resolved "https://registry.yarnpkg.com/secure-json-parse/-/secure-json-parse-2.7.0.tgz#5a5f9cd6ae47df23dba3151edd06855d47e09862" + resolved "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz" integrity sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw== semver@^7.5.3, semver@^7.5.4, semver@^7.6.3: version "7.6.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + resolved "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== shebang-command@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== dependencies: shebang-regex "^3.0.0" shebang-regex@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== signal-exit@^4.0.1: version "4.1.0" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== simple-update-notifier@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz#d70b92bdab7d6d90dfd73931195a30b6e3d7cebb" + resolved "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz" integrity sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w== dependencies: semver "^7.5.3" slash@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== sonic-boom@^4.0.1: version "4.1.0" - resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-4.1.0.tgz#4f039663ba191fac5cfe4f1dc330faac079e4342" + resolved "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.1.0.tgz" integrity sha512-NGipjjRicyJJ03rPiZCJYjwlsuP2d1/5QUviozRXC7S3WdVWNK5e3Ojieb9CCyfhq2UC+3+SRd9nG3I2lPRvUw== dependencies: atomic-sleep "^1.0.0" source-map-support@^0.5.12: version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz" integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== dependencies: buffer-from "^1.0.0" @@ -1384,25 +1403,29 @@ source-map-support@^0.5.12: source-map@^0.6.0: version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== split2@^4.0.0: version "4.2.0" - resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" + resolved "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz" integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== stack-utils@^2.0.3: version "2.0.6" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + resolved "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz" integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== dependencies: escape-string-regexp "^2.0.0" +standard-as-callback@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz" + integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== + "string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0: - name string-width-cjs version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + 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" @@ -1411,7 +1434,7 @@ stack-utils@^2.0.3: string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + resolved "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz" integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== dependencies: eastasianwidth "^0.2.0" @@ -1420,68 +1443,67 @@ string-width@^5.0.1, string-width@^5.1.2: string_decoder@^1.3.0: version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== dependencies: safe-buffer "~5.2.0" "strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: - name strip-ansi-cjs version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: ansi-regex "^5.0.1" strip-ansi@^7.0.1: version "7.1.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz" integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== dependencies: ansi-regex "^6.0.1" strip-bom@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz" integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== strip-json-comments@^2.0.0: version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz" integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== strip-json-comments@^3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -supports-color@^5.3.0, supports-color@^5.5.0: +supports-color@^5.5.0: version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== dependencies: has-flag "^3.0.0" supports-color@^7.1.0: version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: has-flag "^4.0.0" supports-color@^9.4.0: version "9.4.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-9.4.0.tgz#17bfcf686288f531db3dea3215510621ccb55954" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz" integrity sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw== supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== tar@^7.4.3: version "7.4.3" - resolved "https://registry.yarnpkg.com/tar/-/tar-7.4.3.tgz#88bbe9286a3fcd900e94592cda7a22b192e80571" + resolved "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz" integrity sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw== dependencies: "@isaacs/fs-minipass" "^4.0.0" @@ -1493,31 +1515,31 @@ tar@^7.4.3: thread-stream@^3.0.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-3.1.0.tgz#4b2ef252a7c215064507d4ef70c05a5e2d34c4f1" + resolved "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz" integrity sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A== dependencies: real-require "^0.2.0" to-regex-range@^5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== dependencies: is-number "^7.0.0" touch@^3.1.0: version "3.1.1" - resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.1.tgz#097a23d7b161476435e5c1344a95c0f75b4a5694" + resolved "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz" integrity sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA== tree-kill@^1.2.2: version "1.2.2" - resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" + resolved "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz" integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== ts-jest@^29.1.0: version "29.2.5" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.2.5.tgz#591a3c108e1f5ebd013d3152142cb5472b399d63" + resolved "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz" integrity sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA== dependencies: bs-logger "^0.2.6" @@ -1532,7 +1554,7 @@ ts-jest@^29.1.0: ts-node-dev@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/ts-node-dev/-/ts-node-dev-2.0.0.tgz#bdd53e17ab3b5d822ef519928dc6b4a7e0f13065" + resolved "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-2.0.0.tgz" integrity sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w== dependencies: chokidar "^3.5.1" @@ -1548,7 +1570,7 @@ ts-node-dev@^2.0.0: ts-node@^10.4.0, ts-node@^10.9.1: version "10.9.2" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz" integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== dependencies: "@cspotcode/source-map-support" "^0.8.0" @@ -1567,7 +1589,7 @@ ts-node@^10.4.0, ts-node@^10.9.1: tsconfig@^7.0.0: version "7.0.0" - resolved "https://registry.yarnpkg.com/tsconfig/-/tsconfig-7.0.0.tgz#84538875a4dc216e5c4a5432b3a4dec3d54e91b7" + resolved "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz" integrity sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw== dependencies: "@types/strip-bom" "^3.0.0" @@ -1577,44 +1599,49 @@ tsconfig@^7.0.0: typescript@^5.1.6: version "5.6.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.2.tgz#d1de67b6bef77c41823f822df8f0b3bcff60a5a0" + resolved "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz" integrity sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw== undefsafe@^2.0.5: version "2.0.5" - resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" + resolved "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz" integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== -undici-types@~6.19.2: +undici-types@~6.19.8: version "6.19.8" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz" integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== uuid@^8.3.2: version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +uuid@^9.0.0: + version "9.0.1" + resolved "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + v8-compile-cache-lib@^3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + resolved "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== web-streams-polyfill@^3.0.3: version "3.3.3" - resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz#2073b91a2fdb1fbfbd401e7de0ac9f8214cecb4b" + resolved "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz" integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw== which@^2.0.1: version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== dependencies: ansi-styles "^4.0.0" @@ -1623,7 +1650,7 @@ which@^2.0.1: wrap-ansi@^8.1.0: version "8.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz" integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== dependencies: ansi-styles "^6.1.0" @@ -1632,35 +1659,35 @@ wrap-ansi@^8.1.0: wrappy@1: version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== ws@^8.8.0: version "8.18.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + resolved "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz" integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== xtend@^4.0.0: version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== yallist@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-5.0.0.tgz#00e2de443639ed0d78fd87de0d27469fbcffb533" + resolved "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz" integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw== yaml@*, yaml@^2.5.0: version "2.5.1" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.5.1.tgz#c9772aacf62cb7494a95b0c4f1fb065b563db130" + resolved "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz" integrity sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q== yargs-parser@^21.1.1: version "21.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== yn@3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz" integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== diff --git a/webapp/src/actions/actions.ts b/webapp/src/actions/actions.ts index 83528df..3ec4cd3 100644 --- a/webapp/src/actions/actions.ts +++ b/webapp/src/actions/actions.ts @@ -2,17 +2,18 @@ import { createEffect } from 'solid-js'; import { Connection, ConnectionConfig } from '../utils/Connection'; import { clientStore } from '../stores/LocalClientStore'; import { addAudioConsumerId, addVideoConsumerId, removeAudioConsumerId, removeVideoConsumerId } from '../stores/RemoteClientsStore'; +import { JoinCallResponsePayload } from '../utils/MessageProtocol'; // const logger = console; const MAX_BITRATE = 2000000; -let call: Connection; +let call: Connection | undefined; declare global { // eslint-disable-next-line no-unused-vars interface Window { - call: Connection; + call?: Connection; } } @@ -27,19 +28,23 @@ createEffect(() => { // } }); -export const joinToCall = async (config: ConnectionConfig): Promise => { +export const joinToCall = async (config: ConnectionConfig): Promise> => { try { if (!call) { call = new Connection(config); window.call = call; - - handleCall(); + call.once('close', () => { + call = undefined; + window.call = undefined; + }); + handleCall(call); } - await call.join(); + return call.join(); } catch (error) { console.error('joinToCall error', error); + call?.close(); throw error; // batch(() => { // setTestResults({ mediaConnection: ' @@ -48,10 +53,9 @@ export const joinToCall = async (config: ConnectionConfig): Promise => { } }; -export const handleCall = (): void => { +export const handleCall = (call: Connection): void => { call.on('newconsumer', (consumer) => { consumer.resume(); - console.warn('resumed consumer? ', consumer); if (consumer.kind === 'video') { addVideoConsumerId(consumer.id); @@ -70,6 +74,8 @@ export const handleCall = (): void => { }; export const produceMedia = async (): Promise => { + if (!call) throw new Error('produceMedia() call not initialized'); + let callError: string | undefined; const onError = (err: any) => (callError = `${err}`); @@ -89,6 +95,7 @@ export const produceMedia = async (): Promise => { { maxBitrate: MAX_BITRATE, scaleResolutionDownBy: 1 } ] }), + call.sndTransport?.produce({ track: micTrack }), ]); diff --git a/webapp/src/components/Countdown/Countdown.tsx b/webapp/src/components/Countdown/Countdown.tsx new file mode 100644 index 0000000..accc476 --- /dev/null +++ b/webapp/src/components/Countdown/Countdown.tsx @@ -0,0 +1,109 @@ +/* eslint-disable no-unused-vars */ +import { createSignal, onCleanup, onMount, Show } from 'solid-js'; +import './style.css'; + +type CountdownComponentProps = { + millis: number; + onZero?: () => void; +}; + +const Countdown = (props: CountdownComponentProps) => { + const [secondsLeft, setSecondsLeft] = createSignal(0); + const [isCounting, setIsCounting] = createSignal(false); + const [pie, setPie] = createSignal(0); + const [backgroundStyle, setBackgroundStyle] = createSignal(''); + + let interval: number | undefined; + + const calculatePie = (totalSec: number, currentSec: number) => { + return 100 - (currentSec / totalSec) * 100; + }; + + const startClock = (millis: number) => { + + const sec = Math.floor(millis / 1000); + setSecondsLeft(sec); + setPie(0); + setIsCounting(true); + + if (interval) clearInterval(interval); + + let currentSec = sec; + + interval = setInterval(() => { + currentSec -= 1; + setSecondsLeft(currentSec); + + const newPie = calculatePie(sec, currentSec); + setPie(newPie); + + // Update background gradient style for the clock + const half = 50; + const increment = 360 / (100 / 1); + const i = Math.floor(newPie) - 1; + + if (i < half) { + const nextdeg = 90 + increment * i + 'deg'; + // setBackgroundStyle({ + // backgroundImage: `linear-gradient(90deg, #feeff4 50%, transparent 50%, transparent), + // linear-gradient(${nextdeg}, #ec366b 50%, #feeff4 50%, #feeff4)`, + // }); + setBackgroundStyle(` + backgroundImage: linear-gradient(90deg, #feeff4 50%, transparent 50%, transparent), + linear-gradient(${nextdeg}, #ec366b 50%, #feeff4 50%, #feeff4) + `); + } else { + const nextdeg = -90 + increment * (i - half) + 'deg'; + // setBackgroundStyle({ + // backgroundImage: `linear-gradient(${nextdeg}, #ec366b 50%, transparent 50%, transparent), + // linear-gradient(270deg, #ec366b 50%, #feeff4 50%, #feeff4)`, + // }); + setBackgroundStyle(` + backgroundImage: linear-gradient(${nextdeg}, #ec366b 50%, transparent 50%, transparent), + linear-gradient(270deg, #ec366b 50%, #feeff4 50%, #feeff4) + `); + } + + if (currentSec <= 0) { + clearInterval(interval); + setIsCounting(false); + setBackgroundStyle(''); + props.onZero?.(); + } + }, 1000); + }; + + const stopClock = () => { + if (interval) clearInterval(interval); + setSecondsLeft(0); + setIsCounting(false); + setBackgroundStyle(''); + }; + + onCleanup(() => { + if (interval) clearInterval(interval); + }); + + onMount(() => { + startClock(props.millis); + }); + + return ( +
+
+ {style => ( +
+ + {secondsLeft() > 59 + ? Math.floor(secondsLeft() / 60) + ' min' + : secondsLeft() + ' sec'} + +
+ )} +
+
+
+ ); +}; + +export default Countdown; diff --git a/webapp/src/components/Countdown/style.css b/webapp/src/components/Countdown/style.css new file mode 100644 index 0000000..946a897 --- /dev/null +++ b/webapp/src/components/Countdown/style.css @@ -0,0 +1,120 @@ +body { + margin: 0; + padding: 0; + color: #444; + background: #ecf0f1; + font: 300 18px/18px Roboto, sans-serif; + } + *, + :after, + :before { + box-sizing: border-box; + } + .pull-left { + float: left; + } + .pull-right { + float: right; + } + .clearfix:after, + .clearfix:before { + content: ''; + display: table; + } + .clearfix:after { + clear: both; + display: block; + } + + .clock:before, + .count:after { + content: ''; + position: absolute; + } + .clock-wrap { + width: 180px; /* Smaller clock wrap */ + height: 180px; + position: relative; + border-radius: 40px; + background-color: #fff; + box-shadow: 0 0 15px rgba(0, 0, 0, 0.15); + } + .clock { + top: 50%; + left: 50%; + width: 140px; /* Smaller clock */ + height: 140px; + border-radius: 50%; + position: absolute; + transform: translate(-50%, -50%); + background-color: #587291; + } + .clock:before { + top: 50%; + left: 50%; + width: 100px; /* Adjusted for smaller size */ + height: 100px; + transform: translate(-50%, -50%); + border-radius: inherit; + background-color: #2f97c1; + box-shadow: 0 0 15px rgba(0, 0, 0, 0.15), 0 0 3px rgba(255, 255, 255, 0.75) inset; + } + .count { + width: 100%; + color: #fff; + height: 100%; + padding: 30px; /* Smaller padding */ + font-size: 28px; + font-weight: 500; + line-height: 40px; + position: absolute; + text-align: center; + } + .count:after { + width: 100%; + display: block; + font-size: 14px; + font-weight: 300; + line-height: 14px; + text-align: center; + position: relative; + } + .count.sec:after { + content: 'sec'; + } + .count.min:after { + content: 'min'; + } + .action { + max-width: 160px; + } + .action .input { + position: relative; + } + .action .input-num { + width: 100%; + border: none; + padding: 10px; + border-radius: 40px; + background-color: #ecf0f1; + color: #2f97c1; + } + .action .input-btn { + top: 0; + right: 0; + color: #fff; + border: none; + padding: 10px; + position: absolute; + border-radius: 40px; + background-color: #2f97c1; + text-transform: uppercase; + } + .tbl { + display: table; + width: 100%; + } + .tbl .col { + display: table-cell; + } + \ No newline at end of file diff --git a/webapp/src/stores/LocalClientStore.tsx b/webapp/src/stores/LocalClientStore.tsx index e8788a4..564805b 100644 --- a/webapp/src/stores/LocalClientStore.tsx +++ b/webapp/src/stores/LocalClientStore.tsx @@ -23,6 +23,9 @@ export type LocalClientStore = { selectedAudioDeviceId?: string; selectedVideoDeviceId?: string; mediaStream?: MediaStream; + clientCreatedServerTimestamp?: number; + clientMaxLifetimeInMs?: number; + innerServerIp?: string; } export const [clientStore, setClientStore] = createStore({ diff --git a/webapp/src/stores/callConfigStore.tsx b/webapp/src/stores/callConfigStore.tsx index 458d70f..65e1861 100644 --- a/webapp/src/stores/callConfigStore.tsx +++ b/webapp/src/stores/callConfigStore.tsx @@ -2,8 +2,9 @@ import { createStore } from 'solid-js/store'; import { ConnectionConfig } from '../utils/Connection'; import { v4 as uuid } from 'uuid'; -// const SERVER = undefined; -const SERVER = 'wss://media.webrtc-observer.org:443'; +const DEFAULT_PORT = (new URL(window.location.href)).searchParams.get('port') ?? '9080'; +const SERVER = undefined; +// const SERVER = 'wss://media.webrtc-observer.org:443'; const forceRelay = window.location.search.includes('forceRelay') ? true : undefined; // const forcedRelay = false; @@ -13,7 +14,7 @@ export const [ callConfig, setCallConfig ] = createStore({ collectingPeriodInMs: 1000, }, requestTimeoutInMs: 10000, - serverUri: import.meta.env.VITE_MEDIA_SERVER_HOST ?? SERVER ?? 'ws://localhost:9080', + serverUri: import.meta.env.VITE_MEDIA_SERVER_HOST ?? SERVER ?? `ws://localhost:${DEFAULT_PORT}`, clientId: uuid(), callId: undefined, forceRelay: forceRelay ?? import.meta.env.VITE_FORCE_RELAY === 'true', diff --git a/webapp/src/utils/Connection.tsx b/webapp/src/utils/Connection.tsx index 9585ed7..6bb825f 100644 --- a/webapp/src/utils/Connection.tsx +++ b/webapp/src/utils/Connection.tsx @@ -7,6 +7,7 @@ import { v4 as uuid } from 'uuid'; import { RtpCapabilities } from 'mediasoup-client/lib/RtpParameters'; import { ClientMessage, + JoinCallResponsePayload, NotificationMap, ObservedGetOngoingCallResponse, ObserverGetCallStatsResponse, @@ -88,7 +89,7 @@ export class Connection extends EventEmitter { // here for checking compatibility with client lib, no other use // private _deviceMonitor?: MediasoupStatsCollectorDeviceInterface - public async join() { + public async join(): Promise> { if (!this._device) { this._device = new Device(); // const deviceMonitor = this.monitor.collectors.addMediasoupDevice(this._device); @@ -122,13 +123,16 @@ export class Connection extends EventEmitter { this.sndTransport?.close(); this.rcvTransport?.close(); + const response = await this._request('join-call-request', { + callId: this.config.callId, + }); + + console.warn('response', response); const { iceServers, callId, rtpCapabilities: routerRtpCapabilities - } = await this._request('join-call-request', { - callId: this.config.callId, - }); + } = response; console.warn('iceServers', iceServers); @@ -147,6 +151,8 @@ export class Connection extends EventEmitter { this.rcvTransport = await this._createTransportProcess('createRecvTransport', routerRtpCapabilities, iceServers); this.emit('join'); + + return response; } public get callId() { @@ -193,6 +199,7 @@ export class Connection extends EventEmitter { try { switch (message.type) { case 'consumer-created-notification': { + // logger.debug('Consumer created notification received', message); await this._createMediaConsumer({ rtpParameters: message.rtpParameters, producerId: message.remoteProducerId, @@ -327,7 +334,7 @@ export class Connection extends EventEmitter { action: 'pause', }); const onResume = () =>{ - console.warn('resume'); + // console.warn('resume'); this._notify('control-consumer-notification', { consumerId: consumer.id, action: 'resume', diff --git a/webapp/src/utils/MessageProtocol.ts b/webapp/src/utils/MessageProtocol.ts index d870eae..7a8c579 100644 --- a/webapp/src/utils/MessageProtocol.ts +++ b/webapp/src/utils/MessageProtocol.ts @@ -67,6 +67,9 @@ export type JoinCallResponsePayload = { urls: string | string[]; username: string; }[], + clientCreatedServerTimestamp: number, + clientMaxLifetimeInMs?: number, + innerServerIp: string, } export class CreateProducerRequest { diff --git a/webapp/src/views/Join.tsx b/webapp/src/views/Join.tsx index 8f76ffc..df0d9e2 100644 --- a/webapp/src/views/Join.tsx +++ b/webapp/src/views/Join.tsx @@ -15,7 +15,10 @@ const Join: Component = () => { onMount(() => { updateClientMedia({}); - setCallConfig({ ...callConfig, }); + setCallConfig({ + ...callConfig, + userId: clientStore.userId, + }); }); return ( @@ -66,8 +69,20 @@ const Join: Component = () => { onClick={() => { setInProgress(true); joinToCall(JSON.parse(JSON.stringify(callConfig))) - .then(() => { - setClientStore({ ...clientStore, call: window.call }); + .then(({ clientCreatedServerTimestamp, innerServerIp, clientMaxLifetimeInMs }) => { + window.call?.once('close', () => { + setClientStore({ + ...clientStore, + call: undefined + }); + }); + setClientStore({ + ...clientStore, + call: window.call, + clientCreatedServerTimestamp, + innerServerIp, + clientMaxLifetimeInMs, + }); // setPage('videoCall'); }) .catch(err => setError(err ? `${err}` : 'Unknown error')) diff --git a/webapp/src/views/VideoCall.tsx b/webapp/src/views/VideoCall.tsx index 4c4edf5..6dc2f7c 100644 --- a/webapp/src/views/VideoCall.tsx +++ b/webapp/src/views/VideoCall.tsx @@ -9,6 +9,7 @@ import RemoteClientVideo from '../components/RemoteClientVideo'; import RemoteClientAudio from '../components/RemoteClientAudio'; import { clientStore } from '../stores/LocalClientStore'; import { writeClipboard } from '@solid-primitives/clipboard'; +import Countdown from '../components/Countdown/Countdown'; // import { setTestState } from '../signals/signals'; // import Button from '../components/Button'; @@ -51,12 +52,14 @@ const VideoCall: Component = () => { {copyBtnText()} + clientStore.call?.close()}/>
ClientId: {clientStore.call?.config.clientId}
UserId: {clientStore.userId}
+
Inner MediaServer IP: {clientStore.innerServerIp}
{/* */}