diff --git a/openvidu-browser/package.json b/openvidu-browser/package.json index d100429a..05ca356a 100644 --- a/openvidu-browser/package.json +++ b/openvidu-browser/package.json @@ -10,20 +10,20 @@ }, "description": "OpenVidu Browser", "devDependencies": { - "@types/node": "14.14.32", + "@types/node": "15.12.2", "@types/platform": "1.3.3", "browserify": "17.0.0", - "grunt": "1.3.0", - "grunt-cli": "1.3.2", + "grunt": "1.4.1", + "grunt-cli": "1.4.3", "grunt-contrib-copy": "1.0.0", "grunt-contrib-sass": "2.0.0", - "grunt-contrib-uglify": "5.0.0", + "grunt-contrib-uglify": "5.0.1", "grunt-contrib-watch": "1.1.0", "grunt-postcss": "0.9.0", "grunt-string-replace": "1.3.1", "grunt-ts": "6.0.0-beta.22", - "terser": "5.6.0", - "tsify": "5.0.2", + "terser": "5.7.0", + "tsify": "5.0.4", "tslint": "6.1.3", "typedoc": "0.19.2", "typescript": "4.0.7" @@ -38,9 +38,9 @@ "scripts": { "browserify": "VERSION=${VERSION:-dev}; mkdir -p static/js/ && cd src && ../node_modules/browserify/bin/cmd.js Main.ts -p [ tsify ] --exclude kurento-browser-extensions --debug -o ../static/js/openvidu-browser-$VERSION.js -v", "browserify-prod": "VERSION=${VERSION:-dev}; mkdir -p static/js/ && cd src && ../node_modules/browserify/bin/cmd.js --debug Main.ts -p [ tsify ] --exclude kurento-browser-extensions | ../node_modules/terser/bin/terser --source-map content=inline --output ../static/js/openvidu-browser-$VERSION.min.js", - "build": "cd src/OpenVidu && ./../../node_modules/typescript/bin/tsc && cd ../.. && ./node_modules/typescript/bin/tsc --declaration src/index.ts --outDir ./lib --sourceMap --lib dom,es5,es2015.promise,scripthost", + "build": "cd src/OpenVidu && ./../../node_modules/typescript/bin/tsc && cd ../.. && ./node_modules/typescript/bin/tsc --declaration src/index.ts --outDir ./lib --sourceMap --target es5 --lib dom,es5,es2015.promise,scripthost", "docs": "./generate-docs.sh" }, "types": "lib/index.d.ts", - "version": "2.17.0" + "version": "2.18.0" } diff --git a/openvidu-browser/src/OpenVidu/OpenVidu.ts b/openvidu-browser/src/OpenVidu/OpenVidu.ts index e4f0fa49..181a6ce3 100644 --- a/openvidu-browser/src/OpenVidu/OpenVidu.ts +++ b/openvidu-browser/src/OpenVidu/OpenVidu.ts @@ -67,6 +67,7 @@ let platform: PlatformUtils; export class OpenVidu { private jsonRpcClient: any; + private masterNodeHasCrashed = false; /** * @hidden @@ -104,6 +105,10 @@ export class OpenVidu { * @hidden */ finalUserId: string; + /** + * @hidden + */ + mediaServer: string; /** * @hidden */ @@ -744,7 +749,8 @@ export class OpenVidu { onconnected: onConnectSucces, ondisconnect: this.disconnectCallback.bind(this), onreconnecting: this.reconnectingCallback.bind(this), - onreconnected: this.reconnectedCallback.bind(this) + onreconnected: this.reconnectedCallback.bind(this), + ismasternodecrashed: this.isMasterNodeCrashed.bind(this) }, rpc: { requestTimeout: 10000, @@ -761,12 +767,30 @@ export class OpenVidu { networkQualityLevelChanged: this.session.onNetworkQualityLevelChangedChanged.bind(this.session), filterEventDispatched: this.session.onFilterEventDispatched.bind(this.session), iceCandidate: this.session.recvIceCandidate.bind(this.session), - mediaError: this.session.onMediaError.bind(this.session) + mediaError: this.session.onMediaError.bind(this.session), + masterNodeCrashedNotification: this.onMasterNodeCrashedNotification.bind(this) } }; this.jsonRpcClient = new RpcBuilder.clients.JsonRpcClient(config); } + /** + * @hidden + */ + onMasterNodeCrashedNotification(response): void { + console.error('Master Node has crashed'); + this.masterNodeHasCrashed = true; + this.session.onLostConnection("nodeCrashed"); + this.jsonRpcClient.close(4103, "Master Node has crashed"); + } + + /** + * @hidden + */ + getWsReadyState(): number { + return this.jsonRpcClient.getReadyState(); + } + /** * @hidden */ @@ -1009,10 +1033,14 @@ export class OpenVidu { if (!!this.session.connection) { this.sendRequest('connect', { sessionId: this.session.connection.rpcSessionId }, (error, response) => { if (!!error) { - logger.error(error); - logger.warn('Websocket was able to reconnect to OpenVidu Server, but your Connection was already destroyed due to timeout. You are no longer a participant of the Session and your media streams have been destroyed'); - this.session.onLostConnection("networkDisconnect"); - this.jsonRpcClient.close(4101, "Reconnection fault"); + if (this.isMasterNodeCrashed()) { + logger.warn('Master Node has crashed!'); + } else { + logger.error(error); + logger.warn('Websocket was able to reconnect to OpenVidu Server, but your Connection was already destroyed due to timeout. You are no longer a participant of the Session and your media streams have been destroyed'); + this.session.onLostConnection("networkDisconnect"); + this.jsonRpcClient.close(4101, "Reconnection fault"); + } } else { this.jsonRpcClient.resetPing(); this.session.onRecoveredConnection(); @@ -1030,6 +1058,10 @@ export class OpenVidu { } } + private isMasterNodeCrashed() { + return this.masterNodeHasCrashed; + } + private isRoomAvailable(): boolean { if (this.session !== undefined && this.session instanceof Session) { return true; diff --git a/openvidu-browser/src/OpenVidu/Publisher.ts b/openvidu-browser/src/OpenVidu/Publisher.ts index b063c949..c46e24be 100644 --- a/openvidu-browser/src/OpenVidu/Publisher.ts +++ b/openvidu-browser/src/OpenVidu/Publisher.ts @@ -86,10 +86,6 @@ export class Publisher extends StreamManager { * @hidden */ screenShareResizeInterval: NodeJS.Timer; - /** - * @hidden - */ - IEAdapter: any; /** * @hidden diff --git a/openvidu-browser/src/OpenVidu/Session.ts b/openvidu-browser/src/OpenVidu/Session.ts index be678ba5..6a0d2a7f 100644 --- a/openvidu-browser/src/OpenVidu/Session.ts +++ b/openvidu-browser/src/OpenVidu/Session.ts @@ -197,7 +197,7 @@ export class Session extends EventDispatcher { * #### Events dispatched * * The [[Session]] object of the local participant will dispatch a `sessionDisconnected` event. - * This event will automatically unsubscribe the leaving participant from every Subscriber object of the session (this includes closing the WebRTCPeer connection and disposing all MediaStreamTracks) + * This event will automatically unsubscribe the leaving participant from every Subscriber object of the session (this includes closing the RTCPeerConnection and disposing all MediaStreamTracks) * and also deletes any HTML video element associated to each Subscriber (only those [created by OpenVidu Browser](/en/stable/cheatsheet/manage-videos/#let-openvidu-take-care-of-the-video-players)). * For every video removed, each Subscriber object will dispatch a `videoElementDestroyed` event. * Call `event.preventDefault()` upon event `sessionDisconnected` to avoid this behavior and take care of disposing and cleaning all the Subscriber objects yourself. @@ -210,7 +210,7 @@ export class Session extends EventDispatcher { * or/and `Session.disconnect()` in the previous session). See [[StreamEvent]] and [[VideoElementEvent]] to learn more. * * The [[Session]] object of every other participant connected to the session will dispatch a `streamDestroyed` event if the disconnected participant was publishing. - * This event will automatically unsubscribe the Subscriber object from the session (this includes closing the WebRTCPeer connection and disposing all MediaStreamTracks) + * This event will automatically unsubscribe the Subscriber object from the session (this includes closing the RTCPeerConnection and disposing all MediaStreamTracks) * and also deletes any HTML video element associated to that Subscriber (only those [created by OpenVidu Browser](/en/stable/cheatsheet/manage-videos/#let-openvidu-take-care-of-the-video-players)). * For every video removed, the Subscriber object will dispatch a `videoElementDestroyed` event. * Call `event.preventDefault()` upon event `streamDestroyed` to avoid this default behavior and take care of disposing and cleaning the Subscriber object yourself. @@ -437,7 +437,7 @@ export class Session extends EventDispatcher { * Call `event.preventDefault()` upon event `streamDestroyed` if you want to clean the Publisher object on your own or re-publish it in a different Session. * * The [[Session]] object of every other participant connected to the session will dispatch a `streamDestroyed` event. - * This event will automatically unsubscribe the Subscriber object from the session (this includes closing the WebRTCPeer connection and disposing all MediaStreamTracks) and + * This event will automatically unsubscribe the Subscriber object from the session (this includes closing the RTCPeerConnection and disposing all MediaStreamTracks) and * delete any HTML video element associated to it (only those [created by OpenVidu Browser](/en/stable/cheatsheet/manage-videos/#let-openvidu-take-care-of-the-video-players)). * For every video removed, the Subscriber object will dispatch a `videoElementDestroyed` event. * Call `event.preventDefault()` upon event `streamDestroyed` to avoid this default behavior and take care of disposing and cleaning the Subscriber object on your own. @@ -781,7 +781,7 @@ export class Session extends EventDispatcher { onParticipantLeft(msg): void { if (this.remoteConnections.size > 0) { - this.getRemoteConnection(msg.connectionId).then(connection => { + this.getRemoteConnection(msg.connectionId, 'onParticipantLeft').then(connection => { if (!!connection.stream) { const stream = connection.stream; @@ -823,7 +823,7 @@ export class Session extends EventDispatcher { // Get the existing Connection created on 'onParticipantJoined' for // existing participants or create a new one for new participants let connection: Connection; - this.getRemoteConnection(response.id) + this.getRemoteConnection(response.id, 'onParticipantPublished') .then(con => { // Update existing Connection @@ -848,7 +848,7 @@ export class Session extends EventDispatcher { // Your stream has been forcedly unpublished from the session this.stopPublisherStream(msg.reason); } else { - this.getRemoteConnection(msg.connectionId) + this.getRemoteConnection(msg.connectionId, 'onParticipantUnpublished') .then(connection => { @@ -965,7 +965,7 @@ export class Session extends EventDispatcher { // Your stream has been forcedly changed (filter feature) callback(this.connection); } else { - this.getRemoteConnection(msg.connectionId) + this.getRemoteConnection(msg.connectionId, 'onStreamPropertyChanged') .then(connection => { callback(connection); }) @@ -1411,7 +1411,7 @@ export class Session extends EventDispatcher { }); } - private getRemoteConnection(connectionId: string): Promise { + private getRemoteConnection(connectionId: string, operation: string): Promise { return new Promise((resolve, reject) => { const connection = this.remoteConnections.get(connectionId); if (!!connection) { @@ -1419,9 +1419,8 @@ export class Session extends EventDispatcher { resolve(connection); } else { // Remote connection not found. Reject with OpenViduError - const errorMessage = 'Remote connection ' + connectionId + " unknown when 'onParticipantLeft'. " + + const errorMessage = 'Remote connection ' + connectionId + " unknown when '" + operation + "'. " + 'Existing remote connections: ' + JSON.stringify(this.remoteConnections.keys()); - reject(new OpenViduError(OpenViduErrorName.GENERIC_ERROR, errorMessage)); } }); @@ -1487,6 +1486,7 @@ export class Session extends EventDispatcher { } this.openvidu.role = opts.role; this.openvidu.finalUserId = opts.finalUserId; + this.openvidu.mediaServer = opts.mediaServer; this.capabilities = { subscribe: true, publish: this.openvidu.role !== 'SUBSCRIBER', diff --git a/openvidu-browser/src/OpenVidu/Stream.ts b/openvidu-browser/src/OpenVidu/Stream.ts index e2533915..f771be33 100644 --- a/openvidu-browser/src/OpenVidu/Stream.ts +++ b/openvidu-browser/src/OpenVidu/Stream.ts @@ -214,6 +214,10 @@ export class Stream { * @hidden */ ee = new EventEmitter(); + /** + * @hidden + */ + reconnectionEventEmitter: EventEmitter | undefined; /** @@ -277,6 +281,19 @@ export class Stream { } + /** + * Recreates the media connection with the server. This entails the disposal of the previous RTCPeerConnection and the re-negotiation + * of a new one, that will apply the same properties. + * + * This method can be useful in those situations were there the media connection breaks and OpenVidu is not able to recover on its own + * for any kind of unanticipated reason (see [Automatic reconnection](/en/latest/advanced-features/automatic-reconnection/)). + * + * @returns A Promise (to which you can optionally subscribe to) that is resolved if the reconnection operation was successful and rejected with an Error object if not + */ + public reconnect(): Promise { + return this.reconnectStream('API'); + } + /** * Applies an audio/video filter to the stream. * @@ -461,11 +478,13 @@ export class Stream { * @hidden */ disposeWebRtcPeer(): void { + let webrtcId; if (!!this.webRtcPeer) { this.webRtcPeer.dispose(); - this.stopWebRtcStats(); + webrtcId = this.webRtcPeer.id; } - logger.info((!!this.outboundStreamOpts ? 'Outbound ' : 'Inbound ') + "WebRTCPeer from 'Stream' with id [" + this.streamId + '] is now closed'); + this.stopWebRtcStats(); + logger.info((!!this.outboundStreamOpts ? 'Outbound ' : 'Inbound ') + "RTCPeerConnection with id [" + webrtcId + "] from 'Stream' with id [" + this.streamId + '] is now closed'); } /** @@ -774,7 +793,7 @@ export class Stream { return false; } if (this.isLocal() && !!this.session.openvidu.advancedConfiguration.forceMediaReconnectionAfterNetworkDrop) { - logger.warn('OpenVidu Browser advanced configuration option "forceMediaReconnectionAfterNetworkDrop" is enabled. Stream ' + this.streamId + ' will force a reconnection'); + logger.warn(`OpenVidu Browser advanced configuration option "forceMediaReconnectionAfterNetworkDrop" is enabled. Stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}) will force a reconnection`); return true; } const iceConnectionState: RTCIceConnectionState = this.getRTCPeerConnection().iceConnectionState; @@ -802,10 +821,42 @@ export class Stream { initWebRtcPeerSend(reconnect: boolean): Promise { return new Promise((resolve, reject) => { - if (!reconnect) { + if (reconnect) { + if (this.reconnectionEventEmitter == undefined) { + // There is no ongoing reconnection + this.reconnectionEventEmitter = new EventEmitter(); + } else { + // Ongoing reconnection + console.warn(`Trying to reconnect stream ${this.streamId} (Publisher) but an ongoing reconnection process is active. Waiting for response...`); + this.reconnectionEventEmitter.once('success', () => { + resolve(); + }); + this.reconnectionEventEmitter.once('error', error => { + reject(error); + }); + return; + } + } else { + // MediaStream will already have hark events for reconnected streams this.initHarkEvents(); // Init hark events for the local stream } + const finalResolve = () => { + if (reconnect) { + this.reconnectionEventEmitter?.emitEvent('success'); + delete this.reconnectionEventEmitter; + } + resolve(); + } + + const finalReject = error => { + if (reconnect) { + this.reconnectionEventEmitter?.emitEvent('error', [error]); + delete this.reconnectionEventEmitter; + } + reject(error); + } + const successOfferCallback = (sdpOfferParam) => { logger.debug('Sending SDP offer to publish as ' + this.streamId, sdpOfferParam); @@ -839,9 +890,9 @@ export class Stream { this.session.openvidu.sendRequest(method, params, (error, response) => { if (error) { if (error.code === 401) { - reject(new OpenViduError(OpenViduErrorName.OPENVIDU_PERMISSION_DENIED, "You don't have permissions to publish")); + finalReject(new OpenViduError(OpenViduErrorName.OPENVIDU_PERMISSION_DENIED, "You don't have permissions to publish")); } else { - reject('Error on publishVideo: ' + JSON.stringify(error)); + finalReject('Error on publishVideo: ' + JSON.stringify(error)); } } else { this.webRtcPeer.processRemoteAnswer(response.sdpAnswer) @@ -861,10 +912,11 @@ export class Stream { } this.initWebRtcStats(); logger.info("'Publisher' (" + this.streamId + ") successfully " + (reconnect ? "reconnected" : "published") + " to session"); - resolve(); + + finalResolve(); }) .catch(error => { - reject(error); + finalReject(error); }); } }); @@ -876,8 +928,8 @@ export class Stream { video: this.hasVideo, }, simulcast: false, - onicecandidate: this.connection.sendIceCandidate.bind(this.connection), - onexception: (exceptionName: ExceptionEventName, message: string, data?: any) => { this.session.emitEvent('exception', [new ExceptionEvent(this.session, exceptionName, this, message, data)]) }, + onIceCandidate: this.connection.sendIceCandidate.bind(this.connection), + onIceConnectionStateException: (exceptionName: ExceptionEventName, message: string, data?: any) => { this.session.emitEvent('exception', [new ExceptionEvent(this.session, exceptionName, this, message, data)]) }, iceServers: this.getIceServersConf(), mediaStream: this.mediaStream, }; @@ -896,10 +948,10 @@ export class Stream { .then(() => { successOfferCallback(sdpOffer.sdp); }).catch(error => { - reject(new Error('(publish) SDP process local offer error: ' + JSON.stringify(error))); + finalReject(new Error('(publish) SDP process local offer error: ' + JSON.stringify(error))); }); }).catch(error => { - reject(new Error('(publish) SDP create offer error: ' + JSON.stringify(error))); + finalReject(new Error('(publish) SDP create offer error: ' + JSON.stringify(error))); }); }); } @@ -909,20 +961,87 @@ export class Stream { */ initWebRtcPeerReceive(reconnect: boolean): Promise { return new Promise((resolve, reject) => { + + if (reconnect) { + if (this.reconnectionEventEmitter == undefined) { + // There is no ongoing reconnection + this.reconnectionEventEmitter = new EventEmitter(); + } else { + // Ongoing reconnection + console.warn(`Trying to reconnect stream ${this.streamId} (Subscriber) but an ongoing reconnection process is active. Waiting for response...`); + this.reconnectionEventEmitter.once('success', () => { + resolve(); + }); + this.reconnectionEventEmitter.once('error', error => { + reject(error); + }); + return; + } + } + + const finalResolve = () => { + logger.info("'Subscriber' (" + this.streamId + ") successfully " + (reconnect ? "reconnected" : "subscribed")); + this.remotePeerSuccessfullyEstablished(reconnect); + this.initWebRtcStats(); + if (reconnect) { + this.reconnectionEventEmitter?.emitEvent('success'); + delete this.reconnectionEventEmitter; + } + resolve(); + } + + const finalReject = error => { + if (reconnect) { + this.reconnectionEventEmitter?.emitEvent('error', [error]); + delete this.reconnectionEventEmitter; + } + reject(error); + } + + if (this.session.openvidu.mediaServer === 'mediasoup') { + + // Server initiates negotiation + + this.initWebRtcPeerReceiveFromServer(reconnect) + .then(() => finalResolve()) + .catch(error => finalReject(error)); + + } else { + + // Client initiates negotiation + + this.initWebRtcPeerReceiveFromClient(reconnect) + .then(() => finalResolve()) + .catch(error => finalReject(error)); + + } + }); + } + + /** + * @hidden + */ + initWebRtcPeerReceiveFromClient(reconnect: boolean): Promise { + return new Promise((resolve, reject) => { + this.completeWebRtcPeerReceive(reconnect).then(response => { + this.webRtcPeer.processRemoteAnswer(response.sdpAnswer) + .then(() => resolve()).catch(error => reject(error)); + }).catch(error => reject(error)); + }); + } + + /** + * @hidden + */ + initWebRtcPeerReceiveFromServer(reconnect: boolean): Promise { + return new Promise((resolve, reject) => { + // Server initiates negotiation this.session.openvidu.sendRequest('prepareReceiveVideoFrom', { sender: this.streamId, reconnect }, (error, response) => { if (error) { reject(new Error('Error on prepareReceiveVideoFrom: ' + JSON.stringify(error))); } else { - this.completeWebRtcPeerReceive(response.sdpOffer, reconnect) - .then(() => { - logger.info("'Subscriber' (" + this.streamId + ") successfully " + (reconnect ? "reconnected" : "subscribed")); - this.remotePeerSuccessfullyEstablished(reconnect); - this.initWebRtcStats(); - resolve(); - }) - .catch(error => { - reject(error); - }); + this.completeWebRtcPeerReceive(reconnect, response.sdpOffer) + .then(() => resolve()).catch(error => reject(error)); } }); }); @@ -931,25 +1050,29 @@ export class Stream { /** * @hidden */ - completeWebRtcPeerReceive(sdpOffer: string, reconnect: boolean): Promise { + completeWebRtcPeerReceive(reconnect: boolean, sdpOfferByServer?: string): Promise { return new Promise((resolve, reject) => { logger.debug("'Session.subscribe(Stream)' called"); - const successAnswerCallback = (sdpAnswer) => { - logger.debug('Sending SDP answer to subscribe to ' - + this.streamId, sdpAnswer); + const sendSdpToServer = (sdpString: string) => { + + logger.debug(`Sending local SDP ${(!!sdpOfferByServer ? 'answer' : 'offer')} to subscribe to ${this.streamId}`, sdpString); const method = reconnect ? 'reconnectStream' : 'receiveVideoFrom'; const params = {}; params[reconnect ? 'stream' : 'sender'] = this.streamId; - params[reconnect ? 'sdpString' : 'sdpAnswer'] = sdpAnswer; + if (!!sdpOfferByServer) { + params[reconnect ? 'sdpString' : 'sdpAnswer'] = sdpString; + } else { + params['sdpOffer'] = sdpString; + } this.session.openvidu.sendRequest(method, params, (error, response) => { if (error) { reject(new Error('Error on ' + method + ' : ' + JSON.stringify(error))); } else { - resolve(); + resolve(response); } }); }; @@ -960,29 +1083,47 @@ export class Stream { video: this.hasVideo, }, simulcast: false, - onicecandidate: this.connection.sendIceCandidate.bind(this.connection), - onexception: (exceptionName: ExceptionEventName, message: string, data?: any) => { this.session.emitEvent('exception', [new ExceptionEvent(this.session, exceptionName, this, message, data)]) }, + onIceCandidate: this.connection.sendIceCandidate.bind(this.connection), + onIceConnectionStateException: (exceptionName: ExceptionEventName, message: string, data?: any) => { this.session.emitEvent('exception', [new ExceptionEvent(this.session, exceptionName, this, message, data)]) }, iceServers: this.getIceServersConf(), }; + if (reconnect) { + this.disposeWebRtcPeer(); + } + this.webRtcPeer = new WebRtcPeerRecvonly(config); this.webRtcPeer.addIceConnectionStateChangeListener(this.streamId); - this.webRtcPeer.processRemoteOffer(sdpOffer) - .then(() => { + + if (!!sdpOfferByServer) { + + this.webRtcPeer.processRemoteOffer(sdpOfferByServer).then(() => { this.webRtcPeer.createAnswer().then(sdpAnswer => { - this.webRtcPeer.processLocalAnswer(sdpAnswer) - .then(() => { - successAnswerCallback(sdpAnswer.sdp); - }).catch(error => { - reject(new Error('(subscribe) SDP process local answer error: ' + JSON.stringify(error))); - }); + this.webRtcPeer.processLocalAnswer(sdpAnswer).then(() => { + sendSdpToServer(sdpAnswer.sdp!); + }).catch(error => { + reject(new Error('(subscribe) SDP process local answer error: ' + JSON.stringify(error))); + }); }).catch(error => { reject(new Error('(subscribe) SDP create answer error: ' + JSON.stringify(error))); }); - }) - .catch(error => { + }).catch(error => { reject(new Error('(subscribe) SDP process remote offer error: ' + JSON.stringify(error))); }); + + } else { + + this.webRtcPeer.createOffer().then(sdpOffer => { + this.webRtcPeer.processLocalOffer(sdpOffer).then(() => { + sendSdpToServer(sdpOffer.sdp!); + }).catch(error => { + reject(new Error('(subscribe) SDP process local offer error: ' + JSON.stringify(error))); + }); + }).catch(error => { + reject(new Error('(subscribe) SDP create offer error: ' + JSON.stringify(error))); + }); + + } }); } @@ -1048,6 +1189,137 @@ export class Stream { } } + private onIceConnectionStateExceptionHandler(exceptionName: ExceptionEventName, message: string, data?: any): void { + switch (exceptionName) { + case ExceptionEventName.ICE_CONNECTION_FAILED: + this.onIceConnectionFailed(); + break; + case ExceptionEventName.ICE_CONNECTION_DISCONNECTED: + this.onIceConnectionDisconnected(); + break; + } + this.session.emitEvent('exception', [new ExceptionEvent(this.session, exceptionName, this, message, data)]); + } + + private onIceConnectionFailed() { + // Immediately reconnect, as this is a terminal error + logger.log(`[ICE_CONNECTION_FAILED] Handling ICE_CONNECTION_FAILED event. Reconnecting stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')})`); + this.reconnectStreamAndLogResultingIceConnectionState(ExceptionEventName.ICE_CONNECTION_FAILED); + } + + private onIceConnectionDisconnected() { + // Wait to see if the ICE connection is able to reconnect + logger.log(`[ICE_CONNECTION_DISCONNECTED] Handling ICE_CONNECTION_DISCONNECTED event. Waiting for ICE to be restored and reconnect stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}) if not possible`); + const timeout = this.session.openvidu.advancedConfiguration.iceConnectionDisconnectedExceptionTimeout || 4000; + this.awaitWebRtcPeerConnectionState(timeout).then(state => { + switch (state) { + case 'failed': + // Do nothing, as an ICE_CONNECTION_FAILED event will have already raised + logger.warn(`[ICE_CONNECTION_DISCONNECTED] ICE connection of stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}) is now failed after ICE_CONNECTION_DISCONNECTED`); + break; + case 'connected': + case 'completed': + logger.log(`[ICE_CONNECTION_DISCONNECTED] ICE connection of stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}) automatically restored after ICE_CONNECTION_DISCONNECTED. Current ICE connection state: ${state}`); + break; + case 'closed': + case 'checking': + case 'new': + case 'disconnected': + // Rest of states + logger.warn(`[ICE_CONNECTION_DISCONNECTED] ICE connection of stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}) couldn't be restored after ICE_CONNECTION_DISCONNECTED event. Current ICE connection state after ${timeout} ms: ${state}`); + this.reconnectStreamAndLogResultingIceConnectionState(ExceptionEventName.ICE_CONNECTION_DISCONNECTED); + break; + } + }); + } + + private async reconnectStreamAndLogResultingIceConnectionState(event: string) { + try { + const finalIceStateAfterReconnection = await this.reconnectStreamAndReturnIceConnectionState(event); + switch (finalIceStateAfterReconnection) { + case 'connected': + case 'completed': + logger.log(`[${event}] Stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}) successfully reconnected after ${event}. Current ICE connection state: ${finalIceStateAfterReconnection}`); + break; + default: + logger.error(`[${event}] Stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}) failed to reconnect after ${event}. Current ICE connection state: ${finalIceStateAfterReconnection}`); + break; + } + } catch (error) { + logger.error(`[${event}] Error reconnecting stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}) after ${event}: ${error}`); + } + } + + private async reconnectStreamAndReturnIceConnectionState(event: string): Promise { + logger.log(`[${event}] Reconnecting stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}) after event ${event}`); + try { + await this.reconnectStream(event); + const timeout = this.session.openvidu.advancedConfiguration.iceConnectionDisconnectedExceptionTimeout || 4000; + return this.awaitWebRtcPeerConnectionState(timeout); + } catch (error) { + logger.warn(`[${event}] Error reconnecting stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}). Reason: ${error}`); + return this.awaitWebRtcPeerConnectionState(1); + } + } + + private async reconnectStream(event: string) { + const isWsConnected = await this.isWebsocketConnected(event, 3000); + if (isWsConnected) { + // There is connection to openvidu-server. The RTCPeerConnection is the only one broken + logger.log(`[${event}] Trying to reconnect stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}) and the websocket is opened`); + if (this.isLocal()) { + return this.initWebRtcPeerSend(true); + } else { + return this.initWebRtcPeerReceive(true); + } + } else { + // There is no connection to openvidu-server. Nothing can be done. The automatic reconnection + // feature should handle a possible reconnection of RTCPeerConnection in case network comes back + const errorMsg = `[${event}] Trying to reconnect stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}) but the websocket wasn't opened`; + logger.error(errorMsg); + throw Error(errorMsg); + } + } + + private isWebsocketConnected(event: string, msResponseTimeout: number): Promise { + return new Promise((resolve, reject) => { + const wsReadyState = this.session.openvidu.getWsReadyState(); + if (wsReadyState === 1) { + const responseTimeout = setTimeout(() => { + console.warn(`[${event}] Websocket timeout of ${msResponseTimeout}ms`); + resolve(false); + }, msResponseTimeout); + this.session.openvidu.sendRequest('echo', {}, (error, response) => { + clearTimeout(responseTimeout); + if (!!error) { + console.warn(`[${event}] Websocket 'echo' returned error: ${error}`); + resolve(false); + } else { + resolve(true); + } + }); + } else { + console.warn(`[${event}] Websocket readyState is ${wsReadyState}`); + resolve(false); + } + }); + } + + private async awaitWebRtcPeerConnectionState(timeout: number): Promise { + let state = this.getRTCPeerConnection().iceConnectionState; + const interval = 150; + const intervals = Math.ceil(timeout / interval); + for (let i = 0; i < intervals; i++) { + state = this.getRTCPeerConnection().iceConnectionState; + if (state === 'connected' || state === 'completed') { + break; + } + // Sleep + await new Promise((resolve) => setTimeout(resolve, interval)); + } + return state; + } + private initWebRtcStats(): void { this.webRtcStats = new WebRtcStats(this); this.webRtcStats.initWebRtcStats(); diff --git a/openvidu-browser/src/OpenVidu/StreamManager.ts b/openvidu-browser/src/OpenVidu/StreamManager.ts index d6036998..f1ef2313 100644 --- a/openvidu-browser/src/OpenVidu/StreamManager.ts +++ b/openvidu-browser/src/OpenVidu/StreamManager.ts @@ -16,6 +16,7 @@ */ import { Stream } from './Stream'; +import { Subscriber } from './Subscriber'; import { EventDispatcher } from './EventDispatcher'; import { StreamManagerVideo } from '../OpenViduInternal/Interfaces/Public/StreamManagerVideo'; import { Event } from '../OpenViduInternal/Events/Event'; @@ -24,6 +25,7 @@ import { VideoElementEvent } from '../OpenViduInternal/Events/VideoElementEvent' import { VideoInsertMode } from '../OpenViduInternal/Enums/VideoInsertMode'; import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger'; import { PlatformUtils } from '../OpenViduInternal/Utils/Platform'; +import { ExceptionEvent, ExceptionEventName } from '../OpenViduInternal/Events/ExceptionEvent'; /** * @hidden @@ -90,19 +92,23 @@ export class StreamManager extends EventDispatcher { /** * @hidden */ - firstVideoElement?: StreamManagerVideo; + protected firstVideoElement?: StreamManagerVideo; /** * @hidden */ - lazyLaunchVideoElementCreatedEvent = false; - /** - * @hidden - */ - element: HTMLElement; + protected element: HTMLElement; /** * @hidden */ protected canPlayListener: EventListener; + /** + * @hidden + */ + private streamPlayingEventExceptionTimeout?: NodeJS.Timeout; + /** + * @hidden + */ + private lazyLaunchVideoElementCreatedEvent = false; /** * @hidden @@ -138,7 +144,11 @@ export class StreamManager extends EventDispatcher { } this.canPlayListener = () => { - if (this.stream.isLocal()) { + this.deactivateStreamPlayingEventExceptionTimeout(); + if (this.remote) { + logger.info("Remote 'Stream' with id [" + this.stream.streamId + '] video is now playing'); + this.ee.emitEvent('videoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'videoPlaying')]); + } else { if (!this.stream.displayMyRemote()) { logger.info("Your local 'Stream' with id [" + this.stream.streamId + '] video is now playing'); this.ee.emitEvent('videoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'videoPlaying')]); @@ -146,9 +156,6 @@ export class StreamManager extends EventDispatcher { logger.info("Your own remote 'Stream' with id [" + this.stream.streamId + '] video is now playing'); this.ee.emitEvent('remoteVideoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'remoteVideoPlaying')]); } - } else { - logger.info("Remote 'Stream' with id [" + this.stream.streamId + '] video is now playing'); - this.ee.emitEvent('videoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'videoPlaying')]); } this.ee.emitEvent('streamPlaying', [new StreamManagerEvent(this, 'streamPlaying', undefined)]); }; @@ -274,7 +281,7 @@ export class StreamManager extends EventDispatcher { this.initializeVideoProperties(video); - if (this.stream.isLocal() && this.stream.displayMyRemote()) { + if (!this.remote && this.stream.displayMyRemote()) { if (video.srcObject !== this.stream.getMediaStream()) { video.srcObject = this.stream.getMediaStream(); } @@ -305,7 +312,7 @@ export class StreamManager extends EventDispatcher { id: video.id, canplayListenerAdded: false }); - + logger.info('New video element associated to ', this); return returnNumber; @@ -386,7 +393,7 @@ export class StreamManager extends EventDispatcher { * - `interval`: (number) how frequently the analyser polls the audio stream to check if speaking has started/stopped or audio volume has changed. Default **100** (ms) * - `threshold`: (number) the volume at which _publisherStartSpeaking_, _publisherStopSpeaking_ events will be fired. Default **-50** (dB) */ - updatePublisherSpeakingEventsOptions(publisherSpeakingEventsOptions): void { + updatePublisherSpeakingEventsOptions(publisherSpeakingEventsOptions: { interval?: number, threshold?: number }): void { const currentHarkOptions = !!this.stream.harkOptions ? this.stream.harkOptions : (this.stream.session.openvidu.advancedConfiguration.publisherSpeakingEventsOptions || {}); const newInterval = (typeof publisherSpeakingEventsOptions.interval === 'number') ? publisherSpeakingEventsOptions.interval : ((typeof currentHarkOptions.interval === 'number') ? currentHarkOptions.interval : 100); @@ -408,7 +415,7 @@ export class StreamManager extends EventDispatcher { * @hidden */ initializeVideoProperties(video: HTMLVideoElement): void { - if (!(this.stream.isLocal() && this.stream.displayMyRemote())) { + if (!(!this.remote && this.stream.displayMyRemote())) { // Avoid setting the MediaStream into the srcObject if remote subscription before publishing if (video.srcObject !== this.stream.getMediaStream()) { // If srcObject already set don't do it again @@ -492,6 +499,7 @@ export class StreamManager extends EventDispatcher { */ addPlayEventToFirstVideo() { if ((!!this.videos[0]) && (!!this.videos[0].video) && (!this.videos[0].canplayListenerAdded)) { + this.activateStreamPlayingEventExceptionTimeout(); this.videos[0].video.addEventListener('canplay', this.canPlayListener); this.videos[0].canplayListenerAdded = true; } @@ -533,6 +541,7 @@ export class StreamManager extends EventDispatcher { */ removeSrcObject(streamManagerVideo: StreamManagerVideo) { streamManagerVideo.video.srcObject = null; + this.deactivateStreamPlayingEventExceptionTimeout(); } /* Private methods */ @@ -557,4 +566,28 @@ export class StreamManager extends EventDispatcher { video.style.webkitTransform = 'unset'; } + private activateStreamPlayingEventExceptionTimeout() { + if (!this.remote) { + // ExceptionEvent NO_STREAM_PLAYING_EVENT is only for subscribers + return; + } + if (this.streamPlayingEventExceptionTimeout != null) { + // The timeout is already activated + return; + } + // Trigger ExceptionEvent NO_STREAM_PLAYING_EVENT if after timeout there is no 'canplay' event + const msTimeout = this.stream.session.openvidu.advancedConfiguration.noStreamPlayingEventExceptionTimeout || 4000; + this.streamPlayingEventExceptionTimeout = setTimeout(() => { + const msg = 'StreamManager of Stream ' + this.stream.streamId + ' (' + (this.remote ? 'Subscriber' : 'Publisher') + ') did not trigger "streamPlaying" event in ' + msTimeout + ' ms'; + logger.warn(msg); + this.stream.session.emitEvent('exception', [new ExceptionEvent(this.stream.session, ExceptionEventName.NO_STREAM_PLAYING_EVENT, (this) as Subscriber, msg)]); + delete this.streamPlayingEventExceptionTimeout; + }, msTimeout); + } + + private deactivateStreamPlayingEventExceptionTimeout() { + clearTimeout(this.streamPlayingEventExceptionTimeout as any); + delete this.streamPlayingEventExceptionTimeout; + } + } \ No newline at end of file diff --git a/openvidu-browser/src/OpenViduInternal/Events/ConnectionEvent.ts b/openvidu-browser/src/OpenViduInternal/Events/ConnectionEvent.ts index 60609a26..05fc02dc 100644 --- a/openvidu-browser/src/OpenViduInternal/Events/ConnectionEvent.ts +++ b/openvidu-browser/src/OpenViduInternal/Events/ConnectionEvent.ts @@ -39,6 +39,7 @@ export class ConnectionEvent extends Event { * - "forceDisconnectByServer": the remote user has been evicted from the Session by the application * - "sessionClosedByServer": the Session has been closed by the application * - "networkDisconnect": the remote user network connection has dropped + * - "nodeCrashed": a node has crashed in the server side * * For `connectionCreated` event an empty string */ diff --git a/openvidu-browser/src/OpenViduInternal/Events/Event.ts b/openvidu-browser/src/OpenViduInternal/Events/Event.ts index 5d87dfd6..61c743a3 100644 --- a/openvidu-browser/src/OpenViduInternal/Events/Event.ts +++ b/openvidu-browser/src/OpenViduInternal/Events/Event.ts @@ -60,14 +60,14 @@ export abstract class Event { /** * Prevents the default behavior of the event. The following events have a default behavior: * - * - `sessionDisconnected`: dispatched by [[Session]] object, automatically unsubscribes the leaving participant from every Subscriber object of the session (this includes closing the WebRTCPeer connection and disposing all MediaStreamTracks) + * - `sessionDisconnected`: dispatched by [[Session]] object, automatically unsubscribes the leaving participant from every Subscriber object of the session (this includes closing the RTCPeerConnection and disposing all MediaStreamTracks) * and also deletes any HTML video element associated to each Subscriber (only those created by OpenVidu Browser, either by passing a valid parameter as `targetElement` in method [[Session.subscribe]] or * by calling [[Subscriber.createVideoElement]]). For every video removed, each Subscriber object will also dispatch a `videoElementDestroyed` event. * * - `streamDestroyed`: * - If dispatched by a [[Publisher]] (*you* have unpublished): automatically stops all media tracks and deletes any HTML video element associated to it (only those created by OpenVidu Browser, either by passing a valid parameter as `targetElement` * in method [[OpenVidu.initPublisher]] or by calling [[Publisher.createVideoElement]]). For every video removed, the Publisher object will also dispatch a `videoElementDestroyed` event. - * - If dispatched by [[Session]] (*other user* has unpublished): automatically unsubscribes the proper Subscriber object from the session (this includes closing the WebRTCPeer connection and disposing all MediaStreamTracks) + * - If dispatched by [[Session]] (*other user* has unpublished): automatically unsubscribes the proper Subscriber object from the session (this includes closing the RTCPeerConnection and disposing all MediaStreamTracks) * and also deletes any HTML video element associated to that Subscriber (only those created by OpenVidu Browser, either by passing a valid parameter as `targetElement` in method [[Session.subscribe]] or * by calling [[Subscriber.createVideoElement]]). For every video removed, the Subscriber object will also dispatch a `videoElementDestroyed` event. */ diff --git a/openvidu-browser/src/OpenViduInternal/Events/ExceptionEvent.ts b/openvidu-browser/src/OpenViduInternal/Events/ExceptionEvent.ts index 8c086fa7..5ec9b4e2 100644 --- a/openvidu-browser/src/OpenViduInternal/Events/ExceptionEvent.ts +++ b/openvidu-browser/src/OpenViduInternal/Events/ExceptionEvent.ts @@ -17,6 +17,7 @@ import { Session } from '../../OpenVidu/Session'; import { Stream } from '../../OpenVidu/Stream'; +import { Subscriber } from '../../OpenVidu/Subscriber'; import { Event } from './Event'; @@ -36,7 +37,9 @@ export enum ExceptionEventName { * The [ICE connection state](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/iceConnectionState) * of an [RTCPeerConnection](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection) reached `failed` status. * - * This is a terminal error that won't have any kind of possible recovery. + * This is a terminal error that won't have any kind of possible recovery. If the client is still connected to OpenVidu Server, + * then an automatic reconnection process of the media stream is immediately performed. If the ICE connection has broken due to + * a total network drop, then no automatic reconnection process will be possible. * * [[ExceptionEvent]] objects with this [[ExceptionEvent.name]] will have as [[ExceptionEvent.origin]] property a [[Stream]] object. */ @@ -46,18 +49,45 @@ export enum ExceptionEventName { * The [ICE connection state](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/iceConnectionState) * of an [RTCPeerConnection](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection) reached `disconnected` status. * - * This is not a terminal error, and it is possible for the ICE connection to be reconnected. + * This is not a terminal error, and it is possible for the ICE connection to be reconnected. If the client is still connected to + * OpenVidu Server and after certain timeout the ICE connection has not reached a success or terminal status, then an automatic + * reconnection process of the media stream is performed. If the ICE connection has broken due to a total network drop, then no + * automatic reconnection process will be possible. + * + * You can customize the timeout for the reconnection attempt with property [[OpenViduAdvancedConfiguration.iceConnectionDisconnectedExceptionTimeout]], + * which by default is 4000 milliseconds. * * [[ExceptionEvent]] objects with this [[ExceptionEvent.name]] will have as [[ExceptionEvent.origin]] property a [[Stream]] object. */ - ICE_CONNECTION_DISCONNECTED = 'ICE_CONNECTION_DISCONNECTED' + ICE_CONNECTION_DISCONNECTED = 'ICE_CONNECTION_DISCONNECTED', + /** + * A [[Subscriber]] object has not fired event `streamPlaying` after certain timeout. `streamPlaying` event belongs to [[StreamManagerEvent]] + * category. It wraps Web API native event [canplay](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canplay_event). + * + * OpenVidu Browser can take care of the video players (see [here](/en/latest/cheatsheet/manage-videos/#let-openvidu-take-care-of-the-video-players)), + * or you can take care of video players on your own (see [here](/en/latest/cheatsheet/manage-videos/#you-take-care-of-the-video-players)). + * Either way, whenever a [[Subscriber]] object is commanded to attach its [[Stream]] to a video element, it is supposed to fire `streamPlaying` + * event shortly after. If it does not, then we can safely assume that something wrong has happened while playing the remote video and the + * application may be notified through this specific ExceptionEvent. + * + * The timeout can be configured with property [[OpenViduAdvancedConfiguration.noStreamPlayingEventExceptionTimeout]]. By default it is 4000 milliseconds. + * + * This is just an informative exception. It only means that a remote Stream that is supposed to be playing by a video player has not done so + * in a reasonable time. But the lack of the event can be caused by multiple reasons. If a Subscriber is not playing its Stream, the origin + * of the problem could be located at the Publisher side. Or may be caused by a transient network problem. But it also could be a problem with + * autoplay permissions. Bottom line, the cause can be very varied, and depending on the application the lack of the event could even be expected. + * + * [[ExceptionEvent]] objects with this [[ExceptionEvent.name]] will have as [[ExceptionEvent.origin]] property a [[Subscriber]] object. + */ + NO_STREAM_PLAYING_EVENT = 'NO_STREAM_PLAYING_EVENT' } /** * Defines event `exception` dispatched by [[Session]] object. * - * This event acts as a global handler for asynchronous errors that may be triggered for multiple reasons and from multiple origins. + * This event acts as a global handler for asynchronous errors that may be triggered for multiple reasons and from multiple origins. To see the different + * types of exceptions go to [[ExceptionEventName]]. */ export class ExceptionEvent extends Event { @@ -70,8 +100,9 @@ export class ExceptionEvent extends Event { * Object affected by the exception. Depending on the [[ExceptionEvent.name]] property: * - [[Session]]: `ICE_CANDIDATE_ERROR` * - [[Stream]]: `ICE_CONNECTION_FAILED`, `ICE_CONNECTION_DISCONNECTED` + * - [[Subscriber]]: `NO_STREAM_PLAYING_EVENT` */ - origin: Session | Stream; + origin: Session | Stream | Subscriber; /** * Informative description of the exception @@ -86,7 +117,7 @@ export class ExceptionEvent extends Event { /** * @hidden */ - constructor(session: Session, name: ExceptionEventName, origin: Session | Stream, message: string, data?: any) { + constructor(session: Session, name: ExceptionEventName, origin: Session | Stream | Subscriber, message: string, data?: any) { super(false, session, 'exception'); this.name = name; this.origin = origin; diff --git a/openvidu-browser/src/OpenViduInternal/Events/RecordingEvent.ts b/openvidu-browser/src/OpenViduInternal/Events/RecordingEvent.ts index 9117cd65..2ec063a9 100644 --- a/openvidu-browser/src/OpenViduInternal/Events/RecordingEvent.ts +++ b/openvidu-browser/src/OpenViduInternal/Events/RecordingEvent.ts @@ -46,7 +46,7 @@ export class RecordingEvent extends Event { * - "recordingStoppedByServer": the recording has been gracefully stopped by the application * - "sessionClosedByServer": the Session has been closed by the application * - "automaticStop": see [Automatic stop of recordings](/en/stable/advanced-features/recording/#automatic-stop-of-recordings) - * - "mediaServerDisconnect": OpenVidu Media Node has crashed or lost its connection. A new Media Node instance is active and the recording has been stopped (no media streams are available in the new Media Node) + * - "nodeCrashed": a node has crashed in the server side * * For 'recordingStarted' empty string */ diff --git a/openvidu-browser/src/OpenViduInternal/Events/SessionDisconnectedEvent.ts b/openvidu-browser/src/OpenViduInternal/Events/SessionDisconnectedEvent.ts index f6f26c6b..d60470c9 100644 --- a/openvidu-browser/src/OpenViduInternal/Events/SessionDisconnectedEvent.ts +++ b/openvidu-browser/src/OpenViduInternal/Events/SessionDisconnectedEvent.ts @@ -39,6 +39,8 @@ export class SessionDisconnectedEvent extends Event { * Session object will always have previously dispatched a `reconnecting` event. If the reconnection process succeeds, * Session object will dispatch a `reconnected` event. If it fails, Session object will dispatch a SessionDisconnectedEvent * with reason "networkDisconnect" + * - "nodeCrashed": a node has crashed in the server side. You can use this reason to ask your application's backend to reconnect + * to a new session to replace the crashed one */ reason: string; diff --git a/openvidu-browser/src/OpenViduInternal/Events/StreamEvent.ts b/openvidu-browser/src/OpenViduInternal/Events/StreamEvent.ts index 749e2532..7e76ec29 100644 --- a/openvidu-browser/src/OpenViduInternal/Events/StreamEvent.ts +++ b/openvidu-browser/src/OpenViduInternal/Events/StreamEvent.ts @@ -48,7 +48,7 @@ export class StreamEvent extends Event { * - "forceDisconnectByServer": the user has been evicted from the Session by the application * - "sessionClosedByServer": the Session has been closed by the application * - "networkDisconnect": the user's network connection has dropped - * - "mediaServerDisconnect": OpenVidu Media Node has crashed or lost its connection. A new Media Node instance is active and no media streams are available in the Media Node + * - "nodeCrashed": a node has crashed in the server side * * For 'streamCreated' empty string */ diff --git a/openvidu-browser/src/OpenViduInternal/Events/StreamManagerEvent.ts b/openvidu-browser/src/OpenViduInternal/Events/StreamManagerEvent.ts index 78225898..ddfe9e77 100644 --- a/openvidu-browser/src/OpenViduInternal/Events/StreamManagerEvent.ts +++ b/openvidu-browser/src/OpenViduInternal/Events/StreamManagerEvent.ts @@ -21,7 +21,10 @@ import { StreamManager } from '../../OpenVidu/StreamManager'; /** * Defines the following events: * - `streamPlaying`: dispatched by [[StreamManager]] ([[Publisher]] and [[Subscriber]]) whenever its media stream starts playing (one of its videos has media - * and has begun to play). This event will be dispatched when these 3 conditions are met 1) The StreamManager has no video associated in the DOM 2) It is associated to one video 3) That video starts playing + * and has begun to play). This event will be dispatched when these 3 conditions are met: + * 1. The StreamManager has no video associated in the DOM + * 2. It is associated to one video + * 3. That video starts playing. Internally the expected Web API event is [HTMLMediaElement.canplay](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canplay_event) * - `streamAudioVolumeChange`: dispatched by [[StreamManager]] ([[Publisher]] and [[Subscriber]]) when the volume of its Stream's audio track * changes. Only applies if [[Stream.hasAudio]] is `true`. The frequency this event is fired with is defined by property `interval` of * [[OpenViduAdvancedConfiguration.publisherSpeakingEventsOptions]] (default 100ms) diff --git a/openvidu-browser/src/OpenViduInternal/Interfaces/Private/LocalConnectionOptions.ts b/openvidu-browser/src/OpenViduInternal/Interfaces/Private/LocalConnectionOptions.ts index 028410cc..58721ef0 100644 --- a/openvidu-browser/src/OpenViduInternal/Interfaces/Private/LocalConnectionOptions.ts +++ b/openvidu-browser/src/OpenViduInternal/Interfaces/Private/LocalConnectionOptions.ts @@ -31,4 +31,5 @@ export interface LocalConnectionOptions { turnUsername: string; turnCredential: string; version: string; + mediaServer: string; } \ No newline at end of file diff --git a/openvidu-browser/src/OpenViduInternal/Interfaces/Public/OpenViduAdvancedConfiguration.ts b/openvidu-browser/src/OpenViduInternal/Interfaces/Public/OpenViduAdvancedConfiguration.ts index 1233f1fa..3ec845db 100644 --- a/openvidu-browser/src/OpenViduInternal/Interfaces/Public/OpenViduAdvancedConfiguration.ts +++ b/openvidu-browser/src/OpenViduInternal/Interfaces/Public/OpenViduAdvancedConfiguration.ts @@ -39,7 +39,10 @@ export interface OpenViduAdvancedConfiguration { * * This sets the global default configuration that will affect all streams, but you can later customize these values for each specific stream by calling [[StreamManager.updatePublisherSpeakingEventsOptions]] */ - publisherSpeakingEventsOptions?: any; + publisherSpeakingEventsOptions?: { + interval?: number; + threshold?: number; + }; /** * Determines the automatic reconnection process policy. Whenever the client's network drops, OpenVidu Browser starts a reconnection process with OpenVidu Server. After network is recovered, OpenVidu Browser automatically @@ -52,4 +55,20 @@ export interface OpenViduAdvancedConfiguration { */ forceMediaReconnectionAfterNetworkDrop?: boolean; -} + /** + * The milliseconds that must elapse after triggering [[ExceptionEvent]] of name [`ICE_CONNECTION_DISCONNECTED`](/en/latest/api/openvidu-browser/enums/exceptioneventname.html#ice_connection_disconnected) to perform an automatic reconnection process of the affected media stream. + * This automatic reconnection process can only take place if the client still has network connection to OpenVidu Server. If the ICE connection has broken because of a total network drop, + * then no reconnection process will be possible at all. + * + * Default to `4000`. + */ + iceConnectionDisconnectedExceptionTimeout?: number; + + /** + * The milliseconds that must elapse for the [[ExceptionEvent]] of name [`NO_STREAM_PLAYING_EVENT`](/en/latest/api/openvidu-browser/enums/exceptioneventname.html#no_stream_playing_event) to be fired. + * + * Default to `4000`. + */ + noStreamPlayingEventExceptionTimeout?: number; + +} \ No newline at end of file diff --git a/openvidu-browser/src/OpenViduInternal/KurentoUtils/kurento-jsonrpc/clients/jsonrpcclient.js b/openvidu-browser/src/OpenViduInternal/KurentoUtils/kurento-jsonrpc/clients/jsonrpcclient.js index 9a626f05..8f0463ca 100644 --- a/openvidu-browser/src/OpenViduInternal/KurentoUtils/kurento-jsonrpc/clients/jsonrpcclient.js +++ b/openvidu-browser/src/OpenViduInternal/KurentoUtils/kurento-jsonrpc/clients/jsonrpcclient.js @@ -270,6 +270,10 @@ function JsonRpcClient(configuration) { pingNextNum = 0; usePing(); } + + this.getReadyState = function () { + return ws.getReadyState(); + } } diff --git a/openvidu-browser/src/OpenViduInternal/KurentoUtils/kurento-jsonrpc/clients/transports/webSocketWithReconnection.js b/openvidu-browser/src/OpenViduInternal/KurentoUtils/kurento-jsonrpc/clients/transports/webSocketWithReconnection.js index 940a4107..4850077f 100644 --- a/openvidu-browser/src/OpenViduInternal/KurentoUtils/kurento-jsonrpc/clients/transports/webSocketWithReconnection.js +++ b/openvidu-browser/src/OpenViduInternal/KurentoUtils/kurento-jsonrpc/clients/transports/webSocketWithReconnection.js @@ -66,8 +66,12 @@ function WebSocketWithReconnection(config) { if (closing) { Logger.debug("Connection closed by user"); } else { - Logger.debug("Connection closed unexpectecly. Reconnecting..."); - reconnect(MAX_RETRIES, 1); + if (config.ismasternodecrashed()) { + Logger.error("Master Node has crashed. Stopping reconnection process"); + } else { + Logger.debug("Connection closed unexpectedly. Reconnecting..."); + reconnect(MAX_RETRIES, 1); + } } } else { Logger.debug("Close callback from previous websocket. Ignoring it"); @@ -147,6 +151,10 @@ function WebSocketWithReconnection(config) { }; registerMessageHandler(); }; + + this.getReadyState = () => { + return ws.readyState; + } } module.exports = WebSocketWithReconnection; \ No newline at end of file diff --git a/openvidu-browser/src/OpenViduInternal/WebRtcPeer/WebRtcPeer.ts b/openvidu-browser/src/OpenViduInternal/WebRtcPeer/WebRtcPeer.ts index cce334b3..2d610fb1 100644 --- a/openvidu-browser/src/OpenViduInternal/WebRtcPeer/WebRtcPeer.ts +++ b/openvidu-browser/src/OpenViduInternal/WebRtcPeer/WebRtcPeer.ts @@ -37,18 +37,19 @@ export interface WebRtcPeerConfiguration { video: boolean }; simulcast: boolean; - onicecandidate: (event: RTCIceCandidate) => void; - onexception: (exceptionName: ExceptionEventName, message: string, data?: any) => void; - iceServers: RTCIceServer[] | undefined; + onIceCandidate: (event: RTCIceCandidate) => void; + onIceConnectionStateException: (exceptionName: ExceptionEventName, message: string, data?: any) => void; + + iceServers?: RTCIceServer[]; mediaStream?: MediaStream | null; mode?: 'sendonly' | 'recvonly' | 'sendrecv'; id?: string; } export class WebRtcPeer { - public pc: RTCPeerConnection; - public remoteCandidatesQueue: RTCIceCandidate[] = []; - public localCandidatesQueue: RTCIceCandidate[] = []; + pc: RTCPeerConnection; + remoteCandidatesQueue: RTCIceCandidate[] = []; + localCandidatesQueue: RTCIceCandidate[] = []; // Same as WebRtcPeerConfiguration but without optional fields. protected configuration: Required; @@ -61,10 +62,15 @@ export class WebRtcPeer { this.configuration = { ...configuration, - iceServers: (!!configuration.iceServers && configuration.iceServers.length > 0) ? configuration.iceServers : freeice(), - mediaStream: !!configuration.mediaStream - ? configuration.mediaStream - : null, + iceServers: + !!configuration.iceServers && + configuration.iceServers.length > 0 + ? configuration.iceServers + : freeice(), + mediaStream: + configuration.mediaStream !== undefined + ? configuration.mediaStream + : null, mode: !!configuration.mode ? configuration.mode : "sendrecv", id: !!configuration.id ? configuration.id : this.generateUniqueId(), }; @@ -74,7 +80,7 @@ export class WebRtcPeer { this.pc.addEventListener('icecandidate', (event: RTCPeerConnectionIceEvent) => { if (event.candidate != null) { const candidate: RTCIceCandidate = event.candidate; - this.configuration.onicecandidate(candidate); + this.configuration.onIceCandidate(candidate); if (candidate.candidate !== '') { this.localCandidatesQueue.push({ candidate: candidate.candidate }); } @@ -91,6 +97,10 @@ export class WebRtcPeer { }); } + get id(): string { + return this.configuration.id; + } + /** * This method frees the resources used by WebRtcPeer */ @@ -310,12 +320,12 @@ export class WebRtcPeer { // Possible network disconnection const msg1 = 'IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "disconnected". Possible network disconnection'; logger.warn(msg1); - this.configuration.onexception(ExceptionEventName.ICE_CONNECTION_DISCONNECTED, msg1); + this.configuration.onIceConnectionStateException(ExceptionEventName.ICE_CONNECTION_DISCONNECTED, msg1); break; case 'failed': const msg2 = 'IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') to "failed"'; logger.error(msg2); - this.configuration.onexception(ExceptionEventName.ICE_CONNECTION_FAILED, msg2); + this.configuration.onIceConnectionStateException(ExceptionEventName.ICE_CONNECTION_FAILED, msg2); break; case 'closed': logger.log('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "closed"'); diff --git a/openvidu-client/src/main/java/io/openvidu/client/internal/ProtocolElements.java b/openvidu-client/src/main/java/io/openvidu/client/internal/ProtocolElements.java index ba3468e6..46929012 100644 --- a/openvidu-client/src/main/java/io/openvidu/client/internal/ProtocolElements.java +++ b/openvidu-client/src/main/java/io/openvidu/client/internal/ProtocolElements.java @@ -139,6 +139,8 @@ public class ProtocolElements { // ENDTODO public static final String VIDEODATA_METHOD = "videoData"; + + public static final String ECHO_METHOD = "echo"; // ---------------------------- SERVER RESPONSES & EVENTS ----------------- @@ -150,6 +152,7 @@ public class ProtocolElements { public static final String PARTICIPANTJOINED_VALUE_PARAM = "value"; public static final String PARTICIPANTJOINED_SESSION_PARAM = "session"; public static final String PARTICIPANTJOINED_VERSION_PARAM = "version"; + public static final String PARTICIPANTJOINED_MEDIASERVER_PARAM = "mediaServer"; public static final String PARTICIPANTJOINED_RECORD_PARAM = "record"; public static final String PARTICIPANTJOINED_ROLE_PARAM = "role"; public static final String PARTICIPANTJOINED_COTURNIP_PARAM = "coturnIp"; diff --git a/openvidu-java-client/pom.xml b/openvidu-java-client/pom.xml index e60f7a7a..89e19768 100644 --- a/openvidu-java-client/pom.xml +++ b/openvidu-java-client/pom.xml @@ -10,7 +10,7 @@ openvidu-java-client - 2.17.0 + 2.18.0 jar OpenVidu Java Client diff --git a/openvidu-node-client/package-lock.json b/openvidu-node-client/package-lock.json index 2c4b0522..657c79e6 100644 --- a/openvidu-node-client/package-lock.json +++ b/openvidu-node-client/package-lock.json @@ -1,4342 +1,8 @@ { "name": "openvidu-node-client", - "version": "2.17.0", - "lockfileVersion": 2, + "version": "2.18.0", + "lockfileVersion": 1, "requires": true, - "packages": { - "": { - "version": "2.17.0", - "license": "Apache-2.0", - "dependencies": { - "axios": "0.21.1", - "buffer": "6.0.3" - }, - "devDependencies": { - "@types/node": "14.14.37", - "grunt": "1.3.0", - "grunt-cli": "1.4.2", - "grunt-contrib-copy": "1.0.0", - "grunt-contrib-sass": "2.0.0", - "grunt-contrib-uglify": "5.0.1", - "grunt-contrib-watch": "1.1.0", - "grunt-postcss": "0.9.0", - "grunt-string-replace": "1.3.1", - "grunt-ts": "6.0.0-beta.22", - "ts-node": "9.1.1", - "tslint": "6.1.3", - "typedoc": "0.19.2", - "typescript": "3.8.3" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.12.13" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", - "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", - "dev": true - }, - "node_modules/@babel/highlight": { - "version": "7.13.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz", - "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.12.11", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@types/node": { - "version": "14.14.37", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.37.tgz", - "integrity": "sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw==", - "dev": true, - "license": "MIT" - }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, - "node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "node_modules/anymatch/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", - "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-slice": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "dev": true, - "dependencies": { - "lodash": "^4.17.14" - } - }, - "node_modules/async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", - "dev": true - }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true, - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/axios": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", - "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", - "dependencies": { - "follow-redirects": "^1.10.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "dependencies": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "optional": true, - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, - "node_modules/body": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/body/-/body-5.1.0.tgz", - "integrity": "sha1-5LoM5BCkaTYyM2dgnstOZVMSUGk=", - "dev": true, - "dependencies": { - "continuable-cache": "^0.3.1", - "error": "^7.0.0", - "raw-body": "~1.1.0", - "safe-json-parse": "~1.0.1" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "node_modules/builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", - "integrity": "sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=", - "dev": true - }, - "node_modules/cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "dependencies": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "deprecated": "Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependencies.", - "dev": true, - "dependencies": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - }, - "optionalDependencies": { - "fsevents": "^1.2.7" - } - }, - "node_modules/class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "dependencies": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-descriptor/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "dependencies": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "node_modules/colors": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", - "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", - "dev": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "node_modules/continuable-cache": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz", - "integrity": "sha1-vXJ6f67XfnH/OYWskzUakSczrQ8=", - "dev": true - }, - "node_modules/copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/csproj2ts": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/csproj2ts/-/csproj2ts-1.1.0.tgz", - "integrity": "sha512-sk0RTT51t4lUNQ7UfZrqjQx7q4g0m3iwNA6mvyh7gLsgQYvwKzfdyoAgicC9GqJvkoIkU0UmndV9c7VZ8pJ45Q==", - "dev": true, - "dependencies": { - "es6-promise": "^4.1.1", - "lodash": "^4.17.4", - "semver": "^5.4.1", - "xml2js": "^0.4.19" - } - }, - "node_modules/csproj2ts/node_modules/es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "dev": true - }, - "node_modules/dargs": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/dargs/-/dargs-6.1.0.tgz", - "integrity": "sha512-5dVBvpBLBnPwSsYXqfybFyehMmC/EenKEcf23AhCTgTf48JFBbmJKqoZBsERDnjL0FyiVTYWdFsRfTLHxLyKdQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/dateformat": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", - "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/detect-file": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/detect-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", - "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", - "dev": true, - "dependencies": { - "repeating": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/detect-newline": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", - "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "dev": true - }, - "node_modules/error": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/error/-/error-7.2.1.tgz", - "integrity": "sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA==", - "dev": true, - "dependencies": { - "string-template": "~0.2.1" - } - }, - "node_modules/es6-promise": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-0.1.2.tgz", - "integrity": "sha1-8RLCn+paCZhTn8tqL9IUQ9KPBfc=", - "dev": true - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/eventemitter2": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", - "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=", - "dev": true - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "dependencies": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-descriptor/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", - "dev": true, - "dependencies": { - "homedir-polyfill": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "dependencies": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/faye-websocket": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", - "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", - "dev": true, - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, - "dependencies": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/file-sync-cmp": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/file-sync-cmp/-/file-sync-cmp-0.1.1.tgz", - "integrity": "sha1-peeo/7+kk7Q7kju9TKiaU7Y7YSs=", - "dev": true - }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true, - "optional": true - }, - "node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/findup-sync": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.3.0.tgz", - "integrity": "sha1-N5MKpdgWt3fANEXhlmzGeQpMCxY=", - "dev": true, - "dependencies": { - "glob": "~5.0.0" - }, - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/findup-sync/node_modules/glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", - "dev": true, - "dependencies": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/fined": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", - "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", - "dev": true, - "dependencies": { - "expand-tilde": "^2.0.2", - "is-plain-object": "^2.0.3", - "object.defaults": "^1.1.0", - "object.pick": "^1.2.0", - "parse-filepath": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/flagged-respawn": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", - "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/follow-redirects": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz", - "integrity": "sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", - "dev": true, - "dependencies": { - "for-in": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "dependencies": { - "map-cache": "^0.2.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "node_modules/fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/gaze": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", - "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", - "dev": true, - "dependencies": { - "globule": "^1.0.0" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/getobject": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", - "integrity": "sha1-BHpEl4n6Fg0Bj1SG7ZEyC27HiFw=", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dev": true, - "dependencies": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - } - }, - "node_modules/glob-parent/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/global-modules": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", - "dev": true, - "dependencies": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/global-prefix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", - "dev": true, - "dependencies": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/globule": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.2.tgz", - "integrity": "sha512-7IDTQTIu2xzXkT+6mlluidnWo+BypnbSoEVVQCGfzqnl5Ik8d3e1d4wycb8Rj9tWW+Z39uPWsdlquqiqPCd/pA==", - "dev": true, - "dependencies": { - "glob": "~7.1.1", - "lodash": "~4.17.10", - "minimatch": "~3.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "dev": true - }, - "node_modules/grunt": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.3.0.tgz", - "integrity": "sha512-6ILlMXv11/4cxuhSMfSU+SfvbxrPuqZrAtLN64+tZpQ3DAKfSQPQHRbTjSbdtxfyQhGZPtN0bDZJ/LdCM5WXXA==", - "dev": true, - "dependencies": { - "dateformat": "~3.0.3", - "eventemitter2": "~0.4.13", - "exit": "~0.1.2", - "findup-sync": "~0.3.0", - "glob": "~7.1.6", - "grunt-cli": "~1.3.2", - "grunt-known-options": "~1.1.0", - "grunt-legacy-log": "~3.0.0", - "grunt-legacy-util": "~2.0.0", - "iconv-lite": "~0.4.13", - "js-yaml": "~3.14.0", - "minimatch": "~3.0.4", - "mkdirp": "~1.0.4", - "nopt": "~3.0.6", - "rimraf": "~3.0.2" - }, - "bin": { - "grunt": "bin/grunt" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/grunt-cli": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.4.2.tgz", - "integrity": "sha512-wsu6BZh7KCnfeaSkDrKIAvOlqGKxNRTZjc8xfZlvxCByQIqUfZ31kh5uHpPnhQ4NdVgvaWaVxa1LUbVU80nACw==", - "dev": true, - "dependencies": { - "grunt-known-options": "~1.1.1", - "interpret": "~1.1.0", - "liftup": "~3.0.1", - "nopt": "~4.0.1", - "v8flags": "~3.2.0" - }, - "bin": { - "grunt": "bin/grunt" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/grunt-cli/node_modules/nopt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", - "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", - "dev": true, - "dependencies": { - "abbrev": "1", - "osenv": "^0.1.4" - }, - "bin": { - "nopt": "bin/nopt.js" - } - }, - "node_modules/grunt-contrib-copy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-copy/-/grunt-contrib-copy-1.0.0.tgz", - "integrity": "sha1-cGDGWB6QS4qw0A8HbgqPbj58NXM=", - "dev": true, - "dependencies": { - "chalk": "^1.1.1", - "file-sync-cmp": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-contrib-sass": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-sass/-/grunt-contrib-sass-2.0.0.tgz", - "integrity": "sha512-RxZ3dlZZTX4YBPu2zMu84NPYgJ2AYAlIdEqlBaixNVyLNbgvJBGUr5Gi0ec6IiOQbt/I/z7uZVN9HsRxgznIRw==", - "dev": true, - "dependencies": { - "async": "^2.6.1", - "chalk": "^2.4.1", - "cross-spawn": "^6.0.5", - "dargs": "^6.0.0", - "which": "^1.3.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/grunt-contrib-sass/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/grunt-contrib-sass/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/grunt-contrib-sass/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/grunt-contrib-uglify": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/grunt-contrib-uglify/-/grunt-contrib-uglify-5.0.1.tgz", - "integrity": "sha512-T/aXZ4WIpAtoswZqb6HROKg7uq9QbKwl+lUuOwK4eoFj3tFv9/a/oMyd3/qvetV29Pbf8P1YYda1gDwZppr60A==", - "dev": true, - "dependencies": { - "chalk": "^2.4.1", - "maxmin": "^2.1.0", - "uglify-js": "^3.13.3", - "uri-path": "^1.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/grunt-contrib-uglify/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/grunt-contrib-uglify/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/grunt-contrib-uglify/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/grunt-contrib-watch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-watch/-/grunt-contrib-watch-1.1.0.tgz", - "integrity": "sha512-yGweN+0DW5yM+oo58fRu/XIRrPcn3r4tQx+nL7eMRwjpvk+rQY6R8o94BPK0i2UhTg9FN21hS+m8vR8v9vXfeg==", - "dev": true, - "dependencies": { - "async": "^2.6.0", - "gaze": "^1.1.0", - "lodash": "^4.17.10", - "tiny-lr": "^1.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-known-options": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-1.1.1.tgz", - "integrity": "sha512-cHwsLqoighpu7TuYj5RonnEuxGVFnztcUqTqp5rXFGYL4OuPFofwC4Ycg7n9fYwvK6F5WbYgeVOwph9Crs2fsQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-legacy-log": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-3.0.0.tgz", - "integrity": "sha512-GHZQzZmhyq0u3hr7aHW4qUH0xDzwp2YXldLPZTCjlOeGscAOWWPftZG3XioW8MasGp+OBRIu39LFx14SLjXRcA==", - "dev": true, - "dependencies": { - "colors": "~1.1.2", - "grunt-legacy-log-utils": "~2.1.0", - "hooker": "~0.2.3", - "lodash": "~4.17.19" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/grunt-legacy-log-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.1.0.tgz", - "integrity": "sha512-lwquaPXJtKQk0rUM1IQAop5noEpwFqOXasVoedLeNzaibf/OPWjKYvvdqnEHNmU+0T0CaReAXIbGo747ZD+Aaw==", - "dev": true, - "dependencies": { - "chalk": "~4.1.0", - "lodash": "~4.17.19" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/grunt-legacy-log-utils/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/grunt-legacy-log-utils/node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/grunt-legacy-log-utils/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/grunt-legacy-log-utils/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/grunt-legacy-log-utils/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/grunt-legacy-log-utils/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/grunt-legacy-util": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-2.0.0.tgz", - "integrity": "sha512-ZEmYFB44bblwPE2oz3q3ygfF6hseQja9tx8I3UZIwbUik32FMWewA+d1qSFicMFB+8dNXDkh35HcDCWlpRsGlA==", - "dev": true, - "dependencies": { - "async": "~1.5.2", - "exit": "~0.1.1", - "getobject": "~0.1.0", - "hooker": "~0.2.3", - "lodash": "~4.17.20", - "underscore.string": "~3.3.5", - "which": "~1.3.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/grunt-legacy-util/node_modules/async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - }, - "node_modules/grunt-postcss": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/grunt-postcss/-/grunt-postcss-0.9.0.tgz", - "integrity": "sha512-lglLcVaoOIqH0sFv7RqwUKkEFGQwnlqyAKbatxZderwZGV1nDyKHN7gZS9LUiTx1t5GOvRBx0BEalHMyVwFAIA==", - "dev": true, - "dependencies": { - "chalk": "^2.1.0", - "diff": "^3.0.0", - "postcss": "^6.0.11" - }, - "engines": { - "node": ">= 0.12.0" - }, - "peerDependencies": { - "grunt": ">=0.4.5" - } - }, - "node_modules/grunt-postcss/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/grunt-postcss/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/grunt-postcss/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/grunt-string-replace": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/grunt-string-replace/-/grunt-string-replace-1.3.1.tgz", - "integrity": "sha1-YzoDvHhIKg4OH5339kWBH8H7sWI=", - "dev": true, - "dependencies": { - "async": "^2.0.0", - "chalk": "^1.0.0" - }, - "bin": { - "grunt-string-replace": "bin/grunt-string-replace" - }, - "engines": { - "node": ">= 0.10.0", - "npm": ">= 1.4.15" - } - }, - "node_modules/grunt-ts": { - "version": "6.0.0-beta.22", - "resolved": "https://registry.npmjs.org/grunt-ts/-/grunt-ts-6.0.0-beta.22.tgz", - "integrity": "sha512-g9e+ZImQ7W38dfpwhp0+GUltXWidy3YGPfIA/IyGL5HMv6wmVmMMoSgscI5swhs2HSPf8yAvXAAJbwrouijoRg==", - "dev": true, - "dependencies": { - "chokidar": "^2.0.4", - "csproj2ts": "^1.1.0", - "detect-indent": "^4.0.0", - "detect-newline": "^2.1.0", - "es6-promise": "~0.1.1", - "jsmin2": "^1.2.1", - "lodash": "~4.17.10", - "ncp": "0.5.1", - "rimraf": "2.2.6", - "semver": "^5.3.0", - "strip-bom": "^2.0.0" - }, - "engines": { - "node": ">= 0.8.0" - }, - "peerDependencies": { - "grunt": "^1.0.0 || ^0.4.0", - "typescript": ">=1" - } - }, - "node_modules/grunt-ts/node_modules/rimraf": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.6.tgz", - "integrity": "sha1-xZWXVpsU2VatKcrMQr3d9fDqT0w=", - "dev": true, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/grunt/node_modules/grunt-cli": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.3.2.tgz", - "integrity": "sha512-8OHDiZZkcptxVXtMfDxJvmN7MVJNE8L/yIcPb4HB7TlyFD1kDvjHrb62uhySsU14wJx9ORMnTuhRMQ40lH/orQ==", - "dev": true, - "dependencies": { - "grunt-known-options": "~1.1.0", - "interpret": "~1.1.0", - "liftoff": "~2.5.0", - "nopt": "~4.0.1", - "v8flags": "~3.1.1" - }, - "bin": { - "grunt": "bin/grunt" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/grunt/node_modules/grunt-cli/node_modules/nopt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", - "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", - "dev": true, - "dependencies": { - "abbrev": "1", - "osenv": "^0.1.4" - }, - "bin": { - "nopt": "bin/nopt.js" - } - }, - "node_modules/grunt/node_modules/v8flags": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.3.tgz", - "integrity": "sha512-amh9CCg3ZxkzQ48Mhcb8iX7xpAfYJgePHxWMQCBWECpOSqJUXgY26ncA61UTV0BkPqfhcy6mzwCIoP4ygxpW8w==", - "dev": true, - "dependencies": { - "homedir-polyfill": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/gzip-size": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-3.0.0.tgz", - "integrity": "sha1-VGGI6b3DN/Zzdy+BZgRks4nc5SA=", - "dev": true, - "dependencies": { - "duplexer": "^0.1.1" - }, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "dependencies": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/highlight.js": { - "version": "10.7.2", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.2.tgz", - "integrity": "sha512-oFLl873u4usRM9K63j4ME9u3etNF0PLiJhSQ8rdfuL51Wn3zkD6drf9ZW0dOzjnZI22YYG24z30JcmfCZjMgYg==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/homedir-polyfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", - "dev": true, - "dependencies": { - "parse-passwd": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hooker": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", - "integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/http-parser-js": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz", - "integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==", - "dev": true - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, - "node_modules/interpret": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", - "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", - "dev": true - }, - "node_modules/is-absolute": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", - "dev": true, - "dependencies": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true, - "dependencies": { - "binary-extensions": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "node_modules/is-core-module": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", - "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-descriptor/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-finite": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", - "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", - "dev": true, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-relative": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", - "dev": true, - "dependencies": { - "is-unc-path": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-unc-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", - "dev": true, - "dependencies": { - "unc-path-regex": "^0.1.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsmin2": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/jsmin2/-/jsmin2-1.2.1.tgz", - "integrity": "sha1-iPvi+/dfCpH2YCD9mBzWk/S/5X4=", - "dev": true - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/liftoff": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-2.5.0.tgz", - "integrity": "sha1-IAkpG7Mc6oYbvxCnwVooyvdcMew=", - "dev": true, - "dependencies": { - "extend": "^3.0.0", - "findup-sync": "^2.0.0", - "fined": "^1.0.1", - "flagged-respawn": "^1.0.0", - "is-plain-object": "^2.0.4", - "object.map": "^1.0.0", - "rechoir": "^0.6.2", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/liftoff/node_modules/findup-sync": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", - "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", - "dev": true, - "dependencies": { - "detect-file": "^1.0.0", - "is-glob": "^3.1.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/liftoff/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/liftoff/node_modules/rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", - "dev": true, - "dependencies": { - "resolve": "^1.1.6" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/liftup": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/liftup/-/liftup-3.0.1.tgz", - "integrity": "sha512-yRHaiQDizWSzoXk3APcA71eOI/UuhEkNN9DiW2Tt44mhYzX4joFoCZlxsSOF7RyeLlfqzFLQI1ngFq3ggMPhOw==", - "dev": true, - "dependencies": { - "extend": "^3.0.2", - "findup-sync": "^4.0.0", - "fined": "^1.2.0", - "flagged-respawn": "^1.0.1", - "is-plain-object": "^2.0.4", - "object.map": "^1.0.1", - "rechoir": "^0.7.0", - "resolve": "^1.19.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/liftup/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/liftup/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/liftup/node_modules/findup-sync": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-4.0.0.tgz", - "integrity": "sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==", - "dev": true, - "dependencies": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.0", - "micromatch": "^4.0.2", - "resolve-dir": "^1.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/liftup/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/liftup/node_modules/micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/liftup/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/livereload-js": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.4.0.tgz", - "integrity": "sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw==", - "dev": true - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/lunr": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", - "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", - "dev": true - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "node_modules/make-iterator": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", - "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/make-iterator/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "dependencies": { - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/marked": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/marked/-/marked-1.2.9.tgz", - "integrity": "sha512-H8lIX2SvyitGX+TRdtS06m1jHMijKN/XjfH6Ooii9fvxMlh8QdqBfBDkGUpMWH2kQNrtixjzYUa3SH8ROTgRRw==", - "dev": true, - "bin": { - "marked": "bin/marked" - }, - "engines": { - "node": ">= 8.16.2" - } - }, - "node_modules/maxmin": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/maxmin/-/maxmin-2.1.0.tgz", - "integrity": "sha1-TTsiCQPZXu5+t6x/qGTnLcCaMWY=", - "dev": true, - "dependencies": { - "chalk": "^1.0.0", - "figures": "^1.0.1", - "gzip-size": "^3.0.0", - "pretty-bytes": "^3.0.0" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/micromatch/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "node_modules/mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "dependencies": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mixin-deep/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/nan": { - "version": "2.14.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", - "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", - "dev": true, - "optional": true - }, - "node_modules/nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nanomatch/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ncp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/ncp/-/ncp-0.5.1.tgz", - "integrity": "sha1-dDmFMW49tFkoG1hxaehFc1oFQ58=", - "dev": true, - "bin": { - "ncp": "bin/ncp" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "node_modules/nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", - "dev": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "dependencies": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-descriptor/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", - "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "dependencies": { - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.defaults": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", - "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", - "dev": true, - "dependencies": { - "array-each": "^1.0.1", - "array-slice": "^1.0.0", - "for-own": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", - "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", - "dev": true, - "dependencies": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "dev": true, - "dependencies": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "node_modules/parse-filepath": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", - "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", - "dev": true, - "dependencies": { - "is-absolute": "^1.0.0", - "map-cache": "^0.2.0", - "path-root": "^0.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "dev": true - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "node_modules/path-root": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", - "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", - "dev": true, - "dependencies": { - "path-root-regex": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-root-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", - "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "dependencies": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/postcss/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pretty-bytes": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-3.0.1.tgz", - "integrity": "sha1-J9AAjXeAY6C0gRuzXHnxvV1fvM8=", - "dev": true, - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/qs": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", - "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", - "dev": true, - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/raw-body": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz", - "integrity": "sha1-HQJ8K/oRasxmI7yo8AAWVyqH1CU=", - "dev": true, - "dependencies": { - "bytes": "1", - "string_decoder": "0.10" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/raw-body/node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - }, - "node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/rechoir": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.0.tgz", - "integrity": "sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q==", - "dev": true, - "dependencies": { - "resolve": "^1.9.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "dependencies": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regex-not/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regex-not/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "node_modules/repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "dev": true, - "dependencies": { - "is-finite": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", - "dev": true, - "dependencies": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-dir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", - "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", - "dev": true, - "dependencies": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "deprecated": "https://github.com/lydell/resolve-url#deprecated", - "dev": true - }, - "node_modules/ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/safe-json-parse": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz", - "integrity": "sha1-PnZyPjjf3aE8mx0poeB//uSzC1c=", - "dev": true - }, - "node_modules/safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "dependencies": { - "ret": "~0.1.10" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true - }, - "node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/shelljs": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", - "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", - "dev": true, - "dependencies": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - }, - "bin": { - "shjs": "bin/shjs" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/shelljs/node_modules/rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", - "dev": true, - "dependencies": { - "resolve": "^1.1.6" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "dependencies": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "dependencies": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "dependencies": { - "kind-of": "^3.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-descriptor/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, - "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "dev": true - }, - "node_modules/split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "dependencies": { - "extend-shallow": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/split-string/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/split-string/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "node_modules/static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "dependencies": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-descriptor/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string-template": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", - "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=", - "dev": true - }, - "node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "dependencies": { - "is-utf8": "^0.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/tiny-lr": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-1.1.1.tgz", - "integrity": "sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA==", - "dev": true, - "dependencies": { - "body": "^5.1.0", - "debug": "^3.1.0", - "faye-websocket": "~0.10.0", - "livereload-js": "^2.3.0", - "object-assign": "^4.1.0", - "qs": "^6.4.0" - } - }, - "node_modules/tiny-lr/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/tiny-lr/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "dependencies": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ts-node": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", - "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", - "dev": true, - "dependencies": { - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.17", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "typescript": ">=2.7" - } - }, - "node_modules/ts-node/node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/tslint": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", - "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", - "deprecated": "TSLint has been deprecated in favor of ESLint. Please see https://github.com/palantir/tslint/issues/4534 for more information.", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "builtin-modules": "^1.1.1", - "chalk": "^2.3.0", - "commander": "^2.12.1", - "diff": "^4.0.1", - "glob": "^7.1.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.3", - "resolve": "^1.3.2", - "semver": "^5.3.0", - "tslib": "^1.13.0", - "tsutils": "^2.29.0" - }, - "bin": { - "tslint": "bin/tslint" - }, - "engines": { - "node": ">=4.8.0" - }, - "peerDependencies": { - "typescript": ">=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev || >= 4.0.0-dev" - } - }, - "node_modules/tslint/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/tslint/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/tslint/node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/tslint/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/tslint/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/tsutils": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", - "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "peerDependencies": { - "typescript": ">=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev" - } - }, - "node_modules/typedoc": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.19.2.tgz", - "integrity": "sha512-oDEg1BLEzi1qvgdQXc658EYgJ5qJLVSeZ0hQ57Eq4JXy6Vj2VX4RVo18qYxRWz75ifAaYuYNBUCnbhjd37TfOg==", - "dev": true, - "dependencies": { - "fs-extra": "^9.0.1", - "handlebars": "^4.7.6", - "highlight.js": "^10.2.0", - "lodash": "^4.17.20", - "lunr": "^2.3.9", - "marked": "^1.1.1", - "minimatch": "^3.0.0", - "progress": "^2.0.3", - "semver": "^7.3.2", - "shelljs": "^0.8.4", - "typedoc-default-themes": "^0.11.4" - }, - "bin": { - "typedoc": "bin/typedoc" - }, - "engines": { - "node": ">= 10.0.0" - }, - "peerDependencies": { - "typescript": "3.9.x || 4.0.x" - } - }, - "node_modules/typedoc-default-themes": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/typedoc-default-themes/-/typedoc-default-themes-0.11.4.tgz", - "integrity": "sha512-Y4Lf+qIb9NTydrexlazAM46SSLrmrQRqWiD52593g53SsmUFioAsMWt8m834J6qsp+7wHRjxCXSZeiiW5cMUdw==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/typedoc/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/typescript": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", - "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/uglify-js": { - "version": "3.13.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.3.tgz", - "integrity": "sha512-otIc7O9LyxpUcQoXzj2hL4LPWKklO6LJWoJUzNa8A17Xgi4fOeDC8FBDOLHnC/Slo1CQgsZMcM6as0M76BZaig==", - "dev": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/unc-path-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/underscore.string": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.5.tgz", - "integrity": "sha512-g+dpmgn+XBneLmXXo+sGlW5xQEt4ErkS3mgeN2GFbremYeMBSJKr9Wf2KJplQVaiPY/f7FN6atosWYNm9ovrYg==", - "dev": true, - "dependencies": { - "sprintf-js": "^1.0.3", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": "*" - } - }, - "node_modules/union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "dependencies": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "dependencies": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "dependencies": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "dependencies": { - "isarray": "1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "dev": true, - "engines": { - "node": ">=4", - "yarn": "*" - } - }, - "node_modules/uri-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/uri-path/-/uri-path-1.0.0.tgz", - "integrity": "sha1-l0fwGDWJM8Md4PzP2C0TjmcmLjI=", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "deprecated": "Please see https://github.com/lydell/urix#deprecated", - "dev": true - }, - "node_modules/use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "node_modules/v8flags": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", - "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", - "dev": true, - "dependencies": { - "homedir-polyfill": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "dev": true, - "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "node_modules/xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "dev": true, - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "engines": { - "node": ">=6" - } - } - }, "dependencies": { "@babel/code-frame": { "version": "7.12.13", @@ -7269,6 +2935,12 @@ } } }, + "string-template": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", + "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=", + "dev": true + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -7278,12 +2950,6 @@ "safe-buffer": "~5.1.0" } }, - "string-template": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", - "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=", - "dev": true - }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", diff --git a/openvidu-node-client/package.json b/openvidu-node-client/package.json index 1c669a00..e6091b77 100644 --- a/openvidu-node-client/package.json +++ b/openvidu-node-client/package.json @@ -33,5 +33,5 @@ "docs": "./generate-docs.sh" }, "typings": "lib/index.d.ts", - "version": "2.17.0" + "version": "2.18.0" } diff --git a/openvidu-node-client/src/OpenVidu.ts b/openvidu-node-client/src/OpenVidu.ts index 672786f9..b29aaad5 100644 --- a/openvidu-node-client/src/OpenVidu.ts +++ b/openvidu-node-client/src/OpenVidu.ts @@ -206,10 +206,10 @@ export class OpenVidu { // The request was made but no response was received // `error.request` is an instance of XMLHttpRequest in the browser and an instance of // http.ClientRequest in node.js - console.error(error.request); + reject(error.request); } else { // Something happened in setting up the request that triggered an Error - console.error('Error', error.message); + reject(error.message); } }); }); @@ -259,10 +259,10 @@ export class OpenVidu { } else if (error.request) { // The request was made but no response was received `error.request` is an instance of XMLHttpRequest // in the browser and an instance of http.ClientRequest in node.js - console.error(error.request); + reject(new Error(error.request)); } else { // Something happened in setting up the request that triggered an Error - console.error('Error', error.message); + reject(new Error(error.message)); } }); }); @@ -304,10 +304,10 @@ export class OpenVidu { // The request was made but no response was received // `error.request` is an instance of XMLHttpRequest in the browser and an instance of // http.ClientRequest in node.js - console.error(error.request); + reject(new Error(error.request)); } else { // Something happened in setting up the request that triggered an Error - console.error('Error', error.message); + reject(new Error(error.message)); } }); }); @@ -352,10 +352,10 @@ export class OpenVidu { // The request was made but no response was received // `error.request` is an instance of XMLHttpRequest in the browser and an instance of // http.ClientRequest in node.js - console.error(error.request); + reject(new Error(error.request)); } else { // Something happened in setting up the request that triggered an Error - console.error('Error', error.message); + reject(new Error(error.message)); } }); }); @@ -398,10 +398,10 @@ export class OpenVidu { // The request was made but no response was received // `error.request` is an instance of XMLHttpRequest in the browser and an instance of // http.ClientRequest in node.js - console.error(error.request); + reject(new Error(error.request)); } else { // Something happened in setting up the request that triggered an Error - console.error('Error', error.message); + reject(new Error(error.message)); } }); }); @@ -479,11 +479,9 @@ export class OpenVidu { // The request was made but no response was received // `error.request` is an instance of XMLHttpRequest in the browser and an instance of // http.ClientRequest in node.js - console.error(error.request); reject(error); } else { // Something happened in setting up the request that triggered an Error - console.error('Error', error.message); reject(new Error(error.message)); } }); @@ -649,10 +647,10 @@ export class OpenVidu { // The request was made but no response was received // `error.request` is an instance of XMLHttpRequest in the browser and an instance of // http.ClientRequest in node.js - console.error(error.request); + reject(new Error(error.request)); } else { // Something happened in setting up the request that triggered an Error - console.error('Error', error.message); + reject(new Error(error.message)); } }); }); @@ -668,7 +666,6 @@ export class OpenVidu { try { url = new URL(this.hostname); } catch (error) { - console.error('URL format incorrect', error); throw new Error('URL format incorrect: ' + error); } this.host = url.protocol + '//' + url.host; diff --git a/openvidu-node-client/src/Session.ts b/openvidu-node-client/src/Session.ts index 7a2fa28d..79d70843 100644 --- a/openvidu-node-client/src/Session.ts +++ b/openvidu-node-client/src/Session.ts @@ -511,11 +511,9 @@ export class Session { // The request was made but no response was received // `error.request` is an instance of XMLHttpRequest in the browser and an instance of // http.ClientRequest in node.js - console.error(error.request); reject(new Error(error.request)); } else { // Something happened in setting up the request that triggered an Error - console.error('Error', error.message); reject(new Error(error.message)); } }); @@ -642,11 +640,9 @@ export class Session { // The request was made but no response was received // `error.request` is an instance of XMLHttpRequest in the browser and an instance of // http.ClientRequest in node.js - console.error(error.request); reject(new Error(error.request)); } else { // Something happened in setting up the request that triggered an Error - console.error('Error', error.message); reject(new Error(error.message)); } } diff --git a/openvidu-server/deployments/ce/docker-compose/.env b/openvidu-server/deployments/ce/docker-compose/.env index 29eb479e..4da4efac 100644 --- a/openvidu-server/deployments/ce/docker-compose/.env +++ b/openvidu-server/deployments/ce/docker-compose/.env @@ -128,10 +128,12 @@ OPENVIDU_STREAMS_VIDEO_MIN_SEND_BANDWIDTH=300 # All sessions of OpenVidu will try to force this codec. If OPENVIDU_STREAMS_ALLOW_TRANSCODING=true # when a codec can not be forced, transcoding will be allowed +# Values: VP8, H264, NONE # Default value is VP8 # OPENVIDU_STREAMS_FORCED_VIDEO_CODEC=VP8 # Allow transcoding if codec specified in OPENVIDU_STREAMS_FORCED_VIDEO_CODEC can not be applied +# Values: true | false # Default value is false # OPENVIDU_STREAMS_ALLOW_TRANSCODING=false diff --git a/openvidu-server/deployments/ce/docker-compose/docker-compose.override.yml b/openvidu-server/deployments/ce/docker-compose/docker-compose.override.yml index b2af84ce..706adbb9 100644 --- a/openvidu-server/deployments/ce/docker-compose/docker-compose.override.yml +++ b/openvidu-server/deployments/ce/docker-compose/docker-compose.override.yml @@ -9,11 +9,11 @@ services: # # Default Application # - # Openvidu-Call Version: 2.17.0 + # Openvidu-Call Version: 2.18.0 # # -------------------------------------------------------------- app: - image: openvidu/openvidu-call:2.18.0-beta8 + image: openvidu/openvidu-call:2.18.0 restart: on-failure network_mode: host environment: diff --git a/openvidu-server/deployments/ce/docker-compose/docker-compose.yml b/openvidu-server/deployments/ce/docker-compose/docker-compose.yml index 83eb0f2d..d7b0692d 100644 --- a/openvidu-server/deployments/ce/docker-compose/docker-compose.yml +++ b/openvidu-server/deployments/ce/docker-compose/docker-compose.yml @@ -11,7 +11,7 @@ # # This file will be overridden when update OpenVidu Platform # -# Openvidu Version: 2.17.0 +# Openvidu Version: 2.18.0 # # Installation Mode: On Premises # @@ -22,7 +22,7 @@ version: '3.1' services: openvidu-server: - image: openvidu/openvidu-server:2.18.0-dev2 + image: openvidu/openvidu-server:2.18.0 restart: on-failure network_mode: host entrypoint: ['/usr/local/bin/entrypoint.sh'] @@ -65,7 +65,7 @@ services: max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}" redis: - image: openvidu/openvidu-redis:2.0.0 + image: openvidu/openvidu-redis:3.0.0 restart: always network_mode: host environment: @@ -75,7 +75,7 @@ services: max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}" coturn: - image: openvidu/openvidu-coturn:4.0.0-dev2 + image: openvidu/openvidu-coturn:4.0.0 restart: on-failure network_mode: host environment: @@ -96,7 +96,7 @@ services: max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}" nginx: - image: openvidu/openvidu-proxy:6.0.0-dev1 + image: openvidu/openvidu-proxy:7.0.0-dev1 restart: on-failure network_mode: host volumes: diff --git a/openvidu-server/deployments/pro/docker-compose/aws-asg-openvidu-server-pro/.env b/openvidu-server/deployments/pro/docker-compose/aws-asg-openvidu-server-pro/.env index 5e48f206..7e049fa8 100644 --- a/openvidu-server/deployments/pro/docker-compose/aws-asg-openvidu-server-pro/.env +++ b/openvidu-server/deployments/pro/docker-compose/aws-asg-openvidu-server-pro/.env @@ -243,10 +243,12 @@ OPENVIDU_STREAMS_VIDEO_MIN_SEND_BANDWIDTH=300 # All sessions of OpenVidu will try to force this codec. If OPENVIDU_STREAMS_ALLOW_TRANSCODING=true # when a codec can not be forced, transcoding will be allowed +# Values: VP8, H264, NONE # Default value is VP8 # OPENVIDU_STREAMS_FORCED_VIDEO_CODEC=VP8 # Allow transcoding if codec specified in OPENVIDU_STREAMS_FORCED_VIDEO_CODEC can not be applied +# Values: true | false # Default value is false # OPENVIDU_STREAMS_ALLOW_TRANSCODING=false diff --git a/openvidu-server/deployments/pro/docker-compose/aws-asg-openvidu-server-pro/docker-compose.override.yml b/openvidu-server/deployments/pro/docker-compose/aws-asg-openvidu-server-pro/docker-compose.override.yml index b2af84ce..706adbb9 100644 --- a/openvidu-server/deployments/pro/docker-compose/aws-asg-openvidu-server-pro/docker-compose.override.yml +++ b/openvidu-server/deployments/pro/docker-compose/aws-asg-openvidu-server-pro/docker-compose.override.yml @@ -9,11 +9,11 @@ services: # # Default Application # - # Openvidu-Call Version: 2.17.0 + # Openvidu-Call Version: 2.18.0 # # -------------------------------------------------------------- app: - image: openvidu/openvidu-call:2.18.0-beta8 + image: openvidu/openvidu-call:2.18.0 restart: on-failure network_mode: host environment: diff --git a/openvidu-server/deployments/pro/docker-compose/aws-asg-openvidu-server-pro/docker-compose.yml b/openvidu-server/deployments/pro/docker-compose/aws-asg-openvidu-server-pro/docker-compose.yml index 3003d227..36caee31 100644 --- a/openvidu-server/deployments/pro/docker-compose/aws-asg-openvidu-server-pro/docker-compose.yml +++ b/openvidu-server/deployments/pro/docker-compose/aws-asg-openvidu-server-pro/docker-compose.yml @@ -11,7 +11,7 @@ # # This file will be overridden when update OpenVidu Platform # -# Openvidu Version: 2.17.0 +# Openvidu Version: 2.18.0 # # Installation Mode: On Premises # @@ -22,7 +22,7 @@ version: '3.1' services: openvidu-server: - image: openvidu/openvidu-server-pro:2.18.0-beta17 + image: openvidu/openvidu-server-pro:2.18.0 restart: on-failure network_mode: host entrypoint: ['/usr/local/bin/entrypoint.sh'] @@ -76,7 +76,7 @@ services: max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}" redis: - image: openvidu/openvidu-redis:2.0.0 + image: openvidu/openvidu-redis:3.0.0 restart: always network_mode: host environment: @@ -86,7 +86,7 @@ services: max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}" coturn: - image: openvidu/openvidu-coturn:4.0.0-dev2 + image: openvidu/openvidu-coturn:4.0.0 restart: on-failure network_mode: host environment: diff --git a/openvidu-server/deployments/pro/docker-compose/media-node/docker-compose.yml b/openvidu-server/deployments/pro/docker-compose/media-node/docker-compose.yml index eb714d1e..5262520a 100644 --- a/openvidu-server/deployments/pro/docker-compose/media-node/docker-compose.yml +++ b/openvidu-server/deployments/pro/docker-compose/media-node/docker-compose.yml @@ -6,7 +6,7 @@ # # This docker-compose file coordinates all services of OpenVidu CE Platform. # -# Openvidu Version: 2.17.0 +# Openvidu Version: 2.18.0 # # Installation Mode: On Premises # @@ -16,7 +16,7 @@ version: '3.1' services: media-node-controller: - image: openvidu/media-node-controller:4.0.0-dev1 + image: openvidu/media-node-controller:4.0.0 restart: always ulimits: core: -1 diff --git a/openvidu-server/deployments/pro/docker-compose/openvidu-server-pro/.env b/openvidu-server/deployments/pro/docker-compose/openvidu-server-pro/.env index 892602c2..c55f4853 100644 --- a/openvidu-server/deployments/pro/docker-compose/openvidu-server-pro/.env +++ b/openvidu-server/deployments/pro/docker-compose/openvidu-server-pro/.env @@ -243,10 +243,12 @@ OPENVIDU_STREAMS_VIDEO_MIN_SEND_BANDWIDTH=300 # All sessions of OpenVidu will try to force this codec. If OPENVIDU_STREAMS_ALLOW_TRANSCODING=true # when a codec can not be forced, transcoding will be allowed +# Values: VP8, H264, NONE # Default value is VP8 # OPENVIDU_STREAMS_FORCED_VIDEO_CODEC=VP8 # Allow transcoding if codec specified in OPENVIDU_STREAMS_FORCED_VIDEO_CODEC can not be applied +# Values: true | false # Default value is false # OPENVIDU_STREAMS_ALLOW_TRANSCODING=false diff --git a/openvidu-server/deployments/pro/docker-compose/openvidu-server-pro/cluster/aws/openvidu_autodiscover.sh b/openvidu-server/deployments/pro/docker-compose/openvidu-server-pro/cluster/aws/openvidu_autodiscover.sh index 8689f3b7..09e8abb8 100644 --- a/openvidu-server/deployments/pro/docker-compose/openvidu-server-pro/cluster/aws/openvidu_autodiscover.sh +++ b/openvidu-server/deployments/pro/docker-compose/openvidu-server-pro/cluster/aws/openvidu_autodiscover.sh @@ -7,12 +7,12 @@ DEBUG=${DEBUG:-false} OUTPUT=$(mktemp -t openvidu-autodiscover-XXX --suffix .json) -docker run --rm amazon/aws-cli:${AWS_CLI_DOCKER_TAG} ec2 describe-instances \ +docker run --rm amazon/aws-cli:"${AWS_CLI_DOCKER_TAG}" ec2 describe-instances \ --output text \ --filters "Name=instance-state-name,Values=running" \ "Name=tag:ov-cluster-member,Values=kms" \ "Name=tag:ov-stack-name,Values=${AWS_STACK_NAME}" \ "Name=tag:ov-stack-region,Values=${AWS_DEFAULT_REGION}" \ - --query 'Reservations[*].Instances[*].{id:InstanceId,ip:PrivateIpAddress}' > ${OUTPUT} + --query 'Reservations[*].Instances[*].{id:InstanceId,ip:PrivateIpAddress}' > "${OUTPUT}" -cat ${OUTPUT} | jq --raw-input --slurp 'split("\n") | map(split("\t")) | .[0:-1] | map( { "id": .[0], "ip": .[1] } )' +cat "${OUTPUT}" | jq --raw-input --slurp 'split("\n") | map(split("\t")) | .[0:-1] | map( { "id": .[0], "ip": .[1] } )' diff --git a/openvidu-server/deployments/pro/docker-compose/openvidu-server-pro/cluster/aws/openvidu_drop.sh b/openvidu-server/deployments/pro/docker-compose/openvidu-server-pro/cluster/aws/openvidu_drop.sh index 8d585480..68dfd393 100644 --- a/openvidu-server/deployments/pro/docker-compose/openvidu-server-pro/cluster/aws/openvidu_drop.sh +++ b/openvidu-server/deployments/pro/docker-compose/openvidu-server-pro/cluster/aws/openvidu_drop.sh @@ -8,4 +8,4 @@ DEBUG=${DEBUG:-false} ID=$1 [ -z "${ID}" ] && { echo "Must provide instance ID"; exit 1; } -docker run --rm amazon/aws-cli:${AWS_CLI_DOCKER_TAG} ec2 terminate-instances --instance-ids ${ID} --output json +docker run --rm amazon/aws-cli:"${AWS_CLI_DOCKER_TAG}" ec2 terminate-instances --instance-ids "${ID}" --output json diff --git a/openvidu-server/deployments/pro/docker-compose/openvidu-server-pro/cluster/aws/openvidu_launch_kms.sh b/openvidu-server/deployments/pro/docker-compose/openvidu-server-pro/cluster/aws/openvidu_launch_kms.sh index 72f23e33..7f464b5c 100644 --- a/openvidu-server/deployments/pro/docker-compose/openvidu-server-pro/cluster/aws/openvidu_launch_kms.sh +++ b/openvidu-server/deployments/pro/docker-compose/openvidu-server-pro/cluster/aws/openvidu_launch_kms.sh @@ -23,7 +23,7 @@ exit_on_error () { "UnauthorizedOperation") MSG_COD=$(cat ${ERROUTPUT} | awk -F: '{ print $3 }') - MSG_DEC=$(docker run --rm amazon/aws-cli:${AWS_CLI_DOCKER_TAG} sts decode-authorization-message --encoded-message ${MSG_COD}) + MSG_DEC=$(docker run --rm amazon/aws-cli:"${AWS_CLI_DOCKER_TAG}" sts decode-authorization-message --encoded-message "${MSG_COD}") echo -e "Unauthorized " $(cat ${MSG_DEC}) >&2 exit 1 @@ -43,21 +43,21 @@ if [[ -n "${CUSTOM_VOLUME_SIZE}" ]]; then AWS_VOLUME_SIZE="${CUSTOM_VOLUME_SIZE}" fi -docker run --rm amazon/aws-cli:${AWS_CLI_DOCKER_TAG} ec2 run-instances \ - --image-id ${AWS_IMAGE_ID} --count 1 \ - --instance-type ${AWS_INSTANCE_TYPE} \ - --key-name ${AWS_KEY_NAME} \ - --subnet-id ${AWS_SUBNET_ID} \ +docker run --rm amazon/aws-cli:"${AWS_CLI_DOCKER_TAG}" ec2 run-instances \ + --image-id "${AWS_IMAGE_ID}" --count 1 \ + --instance-type "${AWS_INSTANCE_TYPE}" \ + --key-name "${AWS_KEY_NAME}" \ + --subnet-id "${AWS_SUBNET_ID}" \ --tag-specifications "ResourceType=instance,Tags=[{Key='Name',Value='Kurento Media Server'},{Key='ov-cluster-member',Value='kms'},{Key='ov-stack-name',Value='${AWS_STACK_NAME}'},{Key='ov-stack-region',Value='${AWS_DEFAULT_REGION}'}]" \ --iam-instance-profile Name="OpenViduInstanceProfile-${AWS_STACK_NAME}-${AWS_DEFAULT_REGION}" \ --block-device-mappings "DeviceName=/dev/sda1,Ebs={DeleteOnTermination=True,VolumeType='gp2',VolumeSize='${AWS_VOLUME_SIZE}'}" \ - --security-group-ids ${AWS_SECURITY_GROUP} > ${OUTPUT} 2> ${ERROUTPUT} + --security-group-ids "${AWS_SECURITY_GROUP}" > "${OUTPUT}" 2> "${ERROUTPUT}" -docker run --rm amazon/aws-cli:${AWS_CLI_DOCKER_TAG} ec2 wait instance-running --instance-ids $(cat ${OUTPUT} | jq --raw-output ' .Instances[] | .InstanceId') +docker run --rm amazon/aws-cli:"${AWS_CLI_DOCKER_TAG}" ec2 wait instance-running --instance-ids $(cat ${OUTPUT} | jq --raw-output ' .Instances[] | .InstanceId') # Generating the output -KMS_IP=$(cat ${OUTPUT} | jq --raw-output ' .Instances[] | .NetworkInterfaces[0] | .PrivateIpAddress') -KMS_ID=$(cat ${OUTPUT} | jq --raw-output ' .Instances[] | .InstanceId') +KMS_IP=$(cat "${OUTPUT}" | jq --raw-output ' .Instances[] | .NetworkInterfaces[0] | .PrivateIpAddress') +KMS_ID=$(cat "${OUTPUT}" | jq --raw-output ' .Instances[] | .InstanceId') jq -n \ --arg id "${KMS_ID}" \ diff --git a/openvidu-server/deployments/pro/docker-compose/openvidu-server-pro/docker-compose.override.yml b/openvidu-server/deployments/pro/docker-compose/openvidu-server-pro/docker-compose.override.yml index b2af84ce..706adbb9 100644 --- a/openvidu-server/deployments/pro/docker-compose/openvidu-server-pro/docker-compose.override.yml +++ b/openvidu-server/deployments/pro/docker-compose/openvidu-server-pro/docker-compose.override.yml @@ -9,11 +9,11 @@ services: # # Default Application # - # Openvidu-Call Version: 2.17.0 + # Openvidu-Call Version: 2.18.0 # # -------------------------------------------------------------- app: - image: openvidu/openvidu-call:2.18.0-beta8 + image: openvidu/openvidu-call:2.18.0 restart: on-failure network_mode: host environment: diff --git a/openvidu-server/deployments/pro/docker-compose/openvidu-server-pro/docker-compose.yml b/openvidu-server/deployments/pro/docker-compose/openvidu-server-pro/docker-compose.yml index 5f6e8343..7b5899a6 100644 --- a/openvidu-server/deployments/pro/docker-compose/openvidu-server-pro/docker-compose.yml +++ b/openvidu-server/deployments/pro/docker-compose/openvidu-server-pro/docker-compose.yml @@ -11,7 +11,7 @@ # # This file will be overridden when update OpenVidu Platform # -# Openvidu Version: 2.17.0 +# Openvidu Version: 2.18.0 # # Installation Mode: On Premises # @@ -22,7 +22,7 @@ version: '3.1' services: openvidu-server: - image: openvidu/openvidu-server-pro:2.18.0-beta17 + image: openvidu/openvidu-server-pro:2.18.0 restart: on-failure network_mode: host entrypoint: ['/usr/local/bin/entrypoint.sh'] @@ -52,7 +52,7 @@ services: max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}" redis: - image: openvidu/openvidu-redis:2.0.0 + image: openvidu/openvidu-redis:3.0.0 restart: always network_mode: host environment: @@ -62,7 +62,7 @@ services: max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}" coturn: - image: openvidu/openvidu-coturn:4.0.0-dev2 + image: openvidu/openvidu-coturn:4.0.0 restart: on-failure network_mode: host environment: @@ -84,7 +84,7 @@ services: max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}" nginx: - image: openvidu/openvidu-proxy:6.0.0-dev1 + image: openvidu/openvidu-proxy:7.0.0-dev1 restart: on-failure network_mode: host volumes: diff --git a/openvidu-server/docker/openvidu-proxy/default_nginx_conf/ce/default.conf b/openvidu-server/docker/openvidu-proxy/default_nginx_conf/ce/default.conf index 60769f42..6b8fbea8 100644 --- a/openvidu-server/docker/openvidu-proxy/default_nginx_conf/ce/default.conf +++ b/openvidu-server/docker/openvidu-proxy/default_nginx_conf/ce/default.conf @@ -1,3 +1,5 @@ +{xframe_options} + {app_upstream} upstream openviduserver { diff --git a/openvidu-server/docker/openvidu-proxy/default_nginx_conf/global/xframe_sameorigin.conf b/openvidu-server/docker/openvidu-proxy/default_nginx_conf/global/xframe_sameorigin.conf new file mode 100644 index 00000000..66d024c7 --- /dev/null +++ b/openvidu-server/docker/openvidu-proxy/default_nginx_conf/global/xframe_sameorigin.conf @@ -0,0 +1 @@ +add_header X-Frame-Options SAMEORIGIN; \ No newline at end of file diff --git a/openvidu-server/docker/openvidu-proxy/default_nginx_conf/pro/default.conf b/openvidu-server/docker/openvidu-proxy/default_nginx_conf/pro/default.conf index 1d84535e..a2eb5914 100644 --- a/openvidu-server/docker/openvidu-proxy/default_nginx_conf/pro/default.conf +++ b/openvidu-server/docker/openvidu-proxy/default_nginx_conf/pro/default.conf @@ -1,4 +1,5 @@ -add_header X-Frame-Options SAMEORIGIN; +{xframe_options} + add_header X-Content-Type-Options nosniff; add_header X-XSS-Protection "1; mode=block"; diff --git a/openvidu-server/docker/openvidu-proxy/entrypoint.sh b/openvidu-server/docker/openvidu-proxy/entrypoint.sh index cfe9f389..39bd14af 100755 --- a/openvidu-server/docker/openvidu-proxy/entrypoint.sh +++ b/openvidu-server/docker/openvidu-proxy/entrypoint.sh @@ -47,6 +47,7 @@ CERTIFICATES_CONF="${CERTIFICATES_LIVE_FOLDER}/certificates.conf" [ -z "${PUBLIC_IP}" ] && export PUBLIC_IP=auto-ipv4 [ -z "${ALLOWED_ACCESS_TO_DASHBOARD}" ] && export ALLOWED_ACCESS_TO_DASHBOARD=all [ -z "${ALLOWED_ACCESS_TO_RESTAPI}" ] && export ALLOWED_ACCESS_TO_RESTAPI=all +[ -z "${XFRAME_SAMEORIGIN}" ] && export XFRAME_SAMEORIGIN=false # Show input enviroment variables printf "\n =======================================" @@ -228,6 +229,12 @@ elif [[ "${WITH_APP}" == "false" ]]; then sed -e '/{app_config}/{r default_nginx_conf/global/app_config_default.conf' -e 'd}' -i /etc/nginx/conf.d/* fi +if [[ "${XFRAME_SAMEORIGIN}" == "true" ]]; then + sed -e '/{xframe_options}/{r default_nginx_conf/global/xframe_sameorigin.conf' -e 'd}' -i /etc/nginx/conf.d/* +elif [[ "${XFRAME_SAMEORIGIN}" == "false" ]]; then + sed -i '/{xframe_options}/d' /etc/nginx/conf.d/* +fi + if [[ "${SUPPORT_DEPRECATED_API}" == "true" ]]; then sed -e '/{deprecated_api_ce}/{r default_nginx_conf/global/ce/deprecated_api_ce.conf' -e 'd}' -i /etc/nginx/conf.d/* sed -e '/{deprecated_api_pro}/{r default_nginx_conf/global/pro/deprecated_api_pro.conf' -e 'd}' -i /etc/nginx/conf.d/* diff --git a/openvidu-server/docker/openvidu-recording/ubuntu-16-04.Dockerfile b/openvidu-server/docker/openvidu-recording/ubuntu-16-04.Dockerfile index 267e386a..accab36d 100644 --- a/openvidu-server/docker/openvidu-recording/ubuntu-16-04.Dockerfile +++ b/openvidu-server/docker/openvidu-recording/ubuntu-16-04.Dockerfile @@ -4,7 +4,7 @@ MAINTAINER info@openvidu.io ARG CHROME_VERSION # Install Chrome -RUN apt-get update && apt-get -y upgrade && apt-get install -y wget sudo +RUN apt-get update && apt-get -y upgrade && apt-get install -y wget sudo fonts-noto RUN wget http://dl.google.com/linux/deb/pool/main/g/google-chrome-stable/google-chrome-stable_${CHROME_VERSION}_amd64.deb \ && apt install -y ./google-chrome-stable_${CHROME_VERSION}_amd64.deb \ && rm google-chrome-stable_${CHROME_VERSION}_amd64.deb \ diff --git a/openvidu-server/docker/openvidu-recording/ubuntu-20-04.Dockerfile b/openvidu-server/docker/openvidu-recording/ubuntu-20-04.Dockerfile index 2eab38fa..8fa18ab6 100644 --- a/openvidu-server/docker/openvidu-recording/ubuntu-20-04.Dockerfile +++ b/openvidu-server/docker/openvidu-recording/ubuntu-20-04.Dockerfile @@ -14,6 +14,7 @@ RUN apt-get update && apt-get -y upgrade && apt-get install -y \ pulseaudio \ xvfb \ jq \ + fonts-noto \ && rm -rf /var/lib/apt/lists/* # Install chrome diff --git a/openvidu-server/pom.xml b/openvidu-server/pom.xml index f365e27e..f5c02cc7 100644 --- a/openvidu-server/pom.xml +++ b/openvidu-server/pom.xml @@ -12,7 +12,7 @@ jar OpenVidu Server - 2.17.0 + 2.18.0 OpenVidu Server https://openvidu.io diff --git a/openvidu-server/src/dashboard/package-lock.json b/openvidu-server/src/dashboard/package-lock.json index db086a25..53494126 100644 --- a/openvidu-server/src/dashboard/package-lock.json +++ b/openvidu-server/src/dashboard/package-lock.json @@ -7183,6 +7183,11 @@ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, + "jsnlog": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/jsnlog/-/jsnlog-2.30.0.tgz", + "integrity": "sha512-o3ROQVkhek+dkc7/9TXlB4TNtxUpYsRLOBJHZYk3Vy0B5zRBmfv9tyr56PrjcgEXuy06ARgfLTANY0+ImhzzGA==" + }, "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", @@ -8728,14 +8733,15 @@ } }, "openvidu-browser": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/openvidu-browser/-/openvidu-browser-2.17.0.tgz", - "integrity": "sha512-wholLLrs2R1Rf05+QPOH4eIO9LaKO9UvoinGNHU9WoP32L9cU6WeqL0sH3gHhUdiYPqGwTNRPjC3qk/ltjUbDA==", + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/openvidu-browser/-/openvidu-browser-2.18.0.tgz", + "integrity": "sha512-a9HPAG8p2vG9XGThPUbZWPI5sO1OFmrxKQdCZM3RLmasXQa2Lwj7X1Aicsznb7RtDBg/lD8NdNwAQjm4ZDt2Nw==", "requires": { "freeice": "2.2.2", "hark": "1.2.3", + "jsnlog": "2.30.0", "platform": "1.3.6", - "uuid": "8.3.1", + "uuid": "8.3.2", "wolfy87-eventemitter": "5.2.9" } }, @@ -13579,9 +13585,9 @@ "dev": true }, "uuid": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", - "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==" + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" }, "validate-npm-package-name": { "version": "3.0.0", diff --git a/openvidu-server/src/dashboard/package.json b/openvidu-server/src/dashboard/package.json index e07a11ca..82639913 100644 --- a/openvidu-server/src/dashboard/package.json +++ b/openvidu-server/src/dashboard/package.json @@ -1,55 +1,55 @@ { - "dependencies": { - "@angular/animations": "11.2.4", - "@angular/cdk": "11.2.3", - "@angular/common": "11.2.4", - "@angular/compiler": "11.2.4", - "@angular/core": "11.2.4", - "@angular/flex-layout": "11.0.0-beta.33", - "@angular/forms": "11.2.4", - "@angular/material": "11.2.3", - "@angular/platform-browser": "11.2.4", - "@angular/platform-browser-dynamic": "11.2.4", - "@angular/router": "11.2.4", - "core-js": "3.9.1", - "jquery": "3.6.0", - "openvidu-browser": "2.17.0", - "rxjs": "6.6.6", - "tslib": "2.1.0", - "zone.js": "0.11.4" - }, - "devDependencies": { - "@angular-devkit/build-angular": "0.1102.3", - "@angular/cli": "11.2.3", - "@angular/compiler-cli": "11.2.4", - "@angular/language-service": "11.2.4", - "@types/jasmine": "3.6.6", - "@types/node": "14.14.32", - "codelyzer": "6.0.1", - "jasmine-core": "3.6.0", - "jasmine-spec-reporter": "6.0.0", - "karma": "6.1.2", - "karma-chrome-launcher": "3.1.0", - "karma-coverage-istanbul-reporter": "3.0.3", - "karma-jasmine": "4.0.1", - "karma-jasmine-html-reporter": "1.5.4", - "protractor": "7.0.0", - "ts-node": "9.1.1", - "tslint": "6.1.3", - "typescript": "4.1.5" - }, - "license": "Apache-2.0", - "name": "frontend", - "private": true, - "scripts": { - "build": "./node_modules/@angular/cli/bin/ng build --base-href /dashboard/ --output-path ../main/resources/static/dashboard", - "build-prod": "./node_modules/@angular/cli/bin/ng build --prod --base-href /dashboard/ --output-path ../main/resources/static/dashboard", - "e2e": "ng e2e", - "lint": "ng lint", - "ng": "ng", - "postinstall": "ngcc --properties es2015 browser module main --first-only --create-ivy-entry-points", - "start": "ng serve", - "test": "ng test" - }, - "version": "0.0.0" + "dependencies": { + "@angular/animations": "11.2.4", + "@angular/cdk": "11.2.3", + "@angular/common": "11.2.4", + "@angular/compiler": "11.2.4", + "@angular/core": "11.2.4", + "@angular/flex-layout": "11.0.0-beta.33", + "@angular/forms": "11.2.4", + "@angular/material": "11.2.3", + "@angular/platform-browser": "11.2.4", + "@angular/platform-browser-dynamic": "11.2.4", + "@angular/router": "11.2.4", + "core-js": "3.9.1", + "jquery": "3.6.0", + "openvidu-browser": "2.18.0", + "rxjs": "6.6.6", + "tslib": "2.1.0", + "zone.js": "0.11.4" + }, + "devDependencies": { + "@angular-devkit/build-angular": "0.1102.3", + "@angular/cli": "11.2.3", + "@angular/compiler-cli": "11.2.4", + "@angular/language-service": "11.2.4", + "@types/jasmine": "3.6.6", + "@types/node": "14.14.32", + "codelyzer": "6.0.1", + "jasmine-core": "3.6.0", + "jasmine-spec-reporter": "6.0.0", + "karma": "6.1.2", + "karma-chrome-launcher": "3.1.0", + "karma-coverage-istanbul-reporter": "3.0.3", + "karma-jasmine": "4.0.1", + "karma-jasmine-html-reporter": "1.5.4", + "protractor": "7.0.0", + "ts-node": "9.1.1", + "tslint": "6.1.3", + "typescript": "4.1.5" + }, + "license": "Apache-2.0", + "name": "frontend", + "private": true, + "scripts": { + "build": "./node_modules/@angular/cli/bin/ng build --base-href /dashboard/ --output-path ../main/resources/static/dashboard", + "build-prod": "./node_modules/@angular/cli/bin/ng build --prod --base-href /dashboard/ --output-path ../main/resources/static/dashboard", + "e2e": "ng e2e", + "lint": "ng lint", + "ng": "ng", + "postinstall": "ngcc --properties es2015 browser module main --first-only --create-ivy-entry-points", + "start": "ng serve", + "test": "ng test" + }, + "version": "0.0.0" } diff --git a/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduConfig.java b/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduConfig.java index b3993b29..29fc25a6 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduConfig.java +++ b/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduConfig.java @@ -61,6 +61,7 @@ import io.openvidu.java.client.VideoCodec; import io.openvidu.server.OpenViduServer; import io.openvidu.server.cdr.CDREventName; import io.openvidu.server.config.Dotenv.DotenvFormatException; +import io.openvidu.server.core.MediaServer; import io.openvidu.server.recording.RecordingNotification; import io.openvidu.server.rest.RequestMappings; @@ -246,6 +247,10 @@ public class OpenviduConfig { return false; } + public MediaServer getMediaServer() { + return MediaServer.kurento; + } + public String getOpenViduRecordingPath() { return this.openviduRecordingPath; } diff --git a/openvidu-server/src/main/java/io/openvidu/server/core/EndReason.java b/openvidu-server/src/main/java/io/openvidu/server/core/EndReason.java index 161db6f5..5efba5ac 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/core/EndReason.java +++ b/openvidu-server/src/main/java/io/openvidu/server/core/EndReason.java @@ -19,8 +19,114 @@ package io.openvidu.server.core; public enum EndReason { - unsubscribe, unpublish, disconnect, forceUnpublishByUser, forceUnpublishByServer, forceDisconnectByUser, - forceDisconnectByServer, lastParticipantLeft, recordingStoppedByServer, sessionClosedByServer, networkDisconnect, - mediaServerDisconnect, mediaServerReconnect, nodeCrashed, openviduServerStopped, automaticStop + /** + * A user called the RPC operation to unsubscribe from a remote stream. Applies + * to webrtcConnectionDestroyed + */ + unsubscribe, + + /** + * A user called the RPC operation to unpublish a local stream. Applies to + * webrtcConnectionDestroyed + */ + unpublish, + + /** + * A user called the RPC operation to leave the session. Applies to + * webrtcConnectionDestroyed and participantLeft. Can trigger other events with + * lastParticipantLeft + */ + disconnect, + + /** + * A user called the RPC operation to force the unpublishing of a remote stream. + * Applies to webrtcConnectionDestroyed + */ + forceUnpublishByUser, + + /** + * The server application called the REST operation to force the unpublishing of + * a user's stream. Applies to webrtcConnectionDestroyed + */ + forceUnpublishByServer, + + /** + * A user called the RPC operation to force the disconnection of a remote user. + * Applies to webrtcConnectionDestroyed and participantLeft. Can trigger other + * events with lastParticipantLeft + */ + forceDisconnectByUser, + + /** + * The server application called the REST operation to force the disconnection + * of a user. Applies to webrtcConnectionDestroyed and participantLeft. Can + * trigger other events with lastParticipantLeft + */ + forceDisconnectByServer, + + /** + * The last participant left the session, which caused the session to be closed. + * Applies to webrtcConnectionDestroyed, participantLeft, recordingStatusChanged + * and sessionDestroyed. Can be triggered from other events with other end + * reasons (disconnect, forceDisconnectByUser, forceDisconnectByServer, + * networkDisconnect) + */ + lastParticipantLeft, + + /** + * The server application called the REST operation to stop a recording. Applies + * to recordingStatusChanged + */ + recordingStoppedByServer, + + /** + * The server application called the REST operation to close a session. Applies + * to webrtcConnectionDestroyed, participantLeft, recordingStatusChanged and + * sessionDestroyed + */ + sessionClosedByServer, + + /** + * A user left the session because of a network disconnection. Applies to + * webrtcConnectionDestroyed and participantLeft. Can trigger other events with + * lastParticipantLeft + */ + networkDisconnect, + + /** + * A media server disconnected. This is reserved for Media Nodes being + * gracefully removed from an OpenVidu Pro cluster. Applies to + * webrtcConnectionDestroyed, participantLeft, recordingStatusChanged and + * sessionDestroyed + */ + mediaServerDisconnect, + + /** + * A media server disconnected, but was able to reconnect again. Nevertheless + * all of the media endpoints were destroyed in the process. Applies to + * webrtcConnectionDestroyed and recordingStatusChanged + */ + mediaServerReconnect, + + /** + * A node has crashed. For now this means a Media Node has crashed. Applies to + * webrtcConnectionDestroyed, participantLeft, recordingStatusChanged and + * sessionDestroyed + */ + nodeCrashed, + + /** + * OpenVidu Server has gracefully stopped. This is reserved for OpenVidu Pro + * restart operation. Applies to webrtcConnectionDestroyed, participantLeft, + * recordingStatusChanged and sessionDestroyed + */ + openviduServerStopped, + + /** + * A recording has been stopped automatically + * (https://docs.openvidu.io/en/latest/advanced-features/recording/#automatic-stop-of-recordings). + * Applies to recordingStatusChanged + */ + automaticStop } diff --git a/openvidu-server/src/main/java/io/openvidu/server/core/MediaServer.java b/openvidu-server/src/main/java/io/openvidu/server/core/MediaServer.java new file mode 100644 index 00000000..3f398843 --- /dev/null +++ b/openvidu-server/src/main/java/io/openvidu/server/core/MediaServer.java @@ -0,0 +1,7 @@ +package io.openvidu.server.core; + +public enum MediaServer { + + kurento, mediasoup + +} diff --git a/openvidu-server/src/main/java/io/openvidu/server/core/SessionEventsHandler.java b/openvidu-server/src/main/java/io/openvidu/server/core/SessionEventsHandler.java index f041792b..3a856145 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/core/SessionEventsHandler.java +++ b/openvidu-server/src/main/java/io/openvidu/server/core/SessionEventsHandler.java @@ -153,15 +153,18 @@ public class SessionEventsHandler { ProtocolElements.PARTICIPANTJOINED_METHOD, notifParams); } } + result.addProperty(ProtocolElements.PARTICIPANTJOINED_USER_PARAM, participant.getParticipantPublicId()); result.addProperty(ProtocolElements.PARTICIPANTJOINED_FINALUSERID_PARAM, participant.getFinalUserId()); result.addProperty(ProtocolElements.PARTICIPANTJOINED_CREATEDAT_PARAM, participant.getActiveAt()); result.addProperty(ProtocolElements.PARTICIPANTJOINED_METADATA_PARAM, participant.getFullMetadata()); result.add(ProtocolElements.PARTICIPANTJOINED_VALUE_PARAM, resultArray); - result.addProperty(ProtocolElements.PARTICIPANTJOINED_SESSION_PARAM, participant.getSessionId()); result.addProperty(ProtocolElements.PARTICIPANTJOINED_VERSION_PARAM, openviduBuildConfig.getOpenViduServerVersion()); + result.addProperty(ProtocolElements.PARTICIPANTJOINED_MEDIASERVER_PARAM, + this.openviduConfig.getMediaServer().name()); + if (participant.getToken() != null) { result.addProperty(ProtocolElements.PARTICIPANTJOINED_RECORD_PARAM, participant.getToken().record()); if (participant.getToken().getRole() != null) { @@ -638,6 +641,10 @@ public class SessionEventsHandler { rpcNotificationService.sendResponse(participant.getParticipantPrivateId(), transactionId, new JsonObject()); } + public void onEcho(String participantPrivateId, Integer transactionId) { + rpcNotificationService.sendResponse(participantPrivateId, transactionId, new JsonObject()); + } + /** * This handler must be called before cleaning any sessions or recordings hosted * by the crashed Media Node diff --git a/openvidu-server/src/main/java/io/openvidu/server/core/SessionManager.java b/openvidu-server/src/main/java/io/openvidu/server/core/SessionManager.java index 3d46cfd0..eb33cb8c 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/core/SessionManager.java +++ b/openvidu-server/src/main/java/io/openvidu/server/core/SessionManager.java @@ -116,15 +116,8 @@ public abstract class SessionManager { public abstract void prepareSubscription(Participant participant, String senderPublicId, boolean reconnect, Integer id); - // TODO: REMOVE ON 2.18.0 - public abstract void subscribe(Participant participant, String senderName, String sdpAnwser, Integer transactionId, - boolean is2180); - // END TODO - - // TODO: UNCOMMENT ON 2.18.0 - // public abstract void subscribe(Participant participant, String senderName, - // String sdpAnwser, Integer transactionId); - // END TODO + public abstract void subscribe(Participant participant, String senderName, String sdpString, Integer transactionId, + boolean initByServer); public abstract void unsubscribe(Participant participant, String senderName, Integer transactionId); @@ -180,13 +173,11 @@ public abstract class SessionManager { public abstract Participant publishIpcam(Session session, MediaOptions mediaOptions, ConnectionProperties connectionProperties) throws Exception; - public abstract void reconnectStream(Participant participant, String streamId, String sdpOffer, + public abstract void reconnectPublisher(Participant participant, String streamId, String sdpOffer, Integer transactionId); - // TODO: REMOVE ON 2.18.0 - public abstract void reconnectStream2170(Participant participant, String streamId, String sdpOffer, - Integer transactionId); - // END TODO + public abstract void reconnectSubscriber(Participant participant, String streamId, String sdpString, + Integer transactionId, boolean initByServer); public abstract String getParticipantPrivateIdFromStreamId(String sessionId, String streamId) throws OpenViduException; @@ -194,6 +185,10 @@ public abstract class SessionManager { public abstract void onVideoData(Participant participant, Integer transactionId, Integer height, Integer width, Boolean videoActive, Boolean audioActive); + public void onEcho(String participantPrivateId, Integer requestId) { + sessionEventsHandler.onEcho(participantPrivateId, requestId); + } + /** * Returns a Session given its id * @@ -308,8 +303,10 @@ public abstract class SessionManager { } public Session storeSessionNotActive(String sessionId, SessionProperties sessionProperties) { - Session sessionNotActive = new Session(sessionId, sessionProperties, openviduConfig, recordingManager); - return this.storeSessionNotActive(sessionNotActive); + Session sessionNotActive = this + .storeSessionNotActive(new Session(sessionId, sessionProperties, openviduConfig, recordingManager)); + sessionEventsHandler.onSessionCreated(sessionNotActive); + return sessionNotActive; } public Session storeSessionNotActive(Session sessionNotActive) { diff --git a/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoParticipant.java b/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoParticipant.java index 6f68b914..79ed1b39 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoParticipant.java +++ b/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoParticipant.java @@ -24,7 +24,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.Lock; import java.util.function.Function; import org.apache.commons.lang3.RandomStringUtils; @@ -220,240 +220,100 @@ public class KurentoParticipant extends Participant { } KurentoParticipant kSender = (KurentoParticipant) sender; + if (kSender.streaming && kSender.getPublisher() != null) { - if (kSender.streaming && kSender.getPublisher() != null - && kSender.getPublisher().closingLock.readLock().tryLock()) { - - try { - log.debug("PARTICIPANT {}: Creating a subscriber endpoint to user {}", this.getParticipantPublicId(), - senderName); - - SubscriberEndpoint subscriber = getNewOrExistingSubscriber(senderName); - + final Lock closingReadLock = kSender.getPublisher().closingLock.readLock(); + if (closingReadLock.tryLock()) { try { - CountDownLatch subscriberLatch = new CountDownLatch(1); - Endpoint oldMediaEndpoint = subscriber.createEndpoint(subscriberLatch); + + SubscriberEndpoint subscriber = initializeSubscriberEndpoint(kSender); try { - if (!subscriberLatch.await(KurentoSession.ASYNC_LATCH_TIMEOUT, TimeUnit.SECONDS)) { - throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, - "Timeout reached when creating subscriber endpoint"); - } - } catch (InterruptedException e) { - throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, - "Interrupted when creating subscriber endpoint: " + e.getMessage()); - } - if (oldMediaEndpoint != null) { - log.warn( - "PARTICIPANT {}: Two threads are trying to create at " - + "the same time a subscriber endpoint for user {}", - this.getParticipantPublicId(), senderName); + String sdpOffer = subscriber.prepareSubscription(kSender.getPublisher()); + log.trace("PARTICIPANT {}: Subscribing SdpOffer is {}", this.getParticipantPublicId(), + sdpOffer); + log.info("PARTICIPANT {}: offer prepared to receive media from {} in room {}", + this.getParticipantPublicId(), senderName, this.session.getSessionId()); + return sdpOffer; + } catch (KurentoServerException e) { + log.error("Exception preparing subscriber endpoint for user {}: {}", + this.getParticipantPublicId(), e.getMessage()); + this.subscribers.remove(senderName); + releaseSubscriberEndpoint(senderName, (KurentoParticipant) sender, subscriber, null, false); return null; } - if (subscriber.getEndpoint() == null) { - throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, - "Unable to create subscriber endpoint"); - } - - String subscriberEndpointName = calculateSubscriberEndpointName(kSender); - - subscriber.setEndpointName(subscriberEndpointName); - subscriber.getEndpoint().setName(subscriberEndpointName); - subscriber.setStreamId(kSender.getPublisherStreamId()); - - endpointConfig.addEndpointListeners(subscriber, "subscriber"); - - } catch (OpenViduException e) { - this.subscribers.remove(senderName); - throw e; + } finally { + closingReadLock.unlock(); } - - log.debug("PARTICIPANT {}: Created subscriber endpoint for user {}", this.getParticipantPublicId(), - senderName); - try { - String sdpOffer = subscriber.prepareSubscription(kSender.getPublisher()); - log.trace("PARTICIPANT {}: Subscribing SdpOffer is {}", this.getParticipantPublicId(), sdpOffer); - log.info("PARTICIPANT {}: offer prepared to receive media from {} in room {}", - this.getParticipantPublicId(), senderName, this.session.getSessionId()); - return sdpOffer; - } catch (KurentoServerException e) { - log.error("Exception preparing subscriber endpoint for user {}: {}", this.getParticipantPublicId(), - e.getMessage()); - this.subscribers.remove(senderName); - releaseSubscriberEndpoint(senderName, (KurentoParticipant) sender, subscriber, null, false); - return null; - } - } finally { - kSender.getPublisher().closingLock.readLock().unlock(); } - } else { - log.error( - "PublisherEndpoint of participant {} of session {} is closed. Participant {} couldn't subscribe to it ", - senderName, sender.getSessionId(), this.participantPublicId); - throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, - "Unable to create subscriber endpoint. Publisher endpoint of participant " + senderName - + "is closed"); } + log.error( + "PublisherEndpoint of participant {} of session {} is closed. Participant {} couldn't subscribe to it ", + senderName, sender.getSessionId(), this.participantPublicId); + throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, + "Unable to create subscriber endpoint. Publisher endpoint of participant " + senderName + "is closed"); } - public void receiveMediaFrom2180(Participant sender, String sdpAnswer, boolean silent) { + public String receiveMedia(Participant sender, String sdpString, boolean silent, boolean initByServer) { final String senderName = sender.getParticipantPublicId(); - log.info("PARTICIPANT {}: Request to receive media from {} in room {}", this.getParticipantPublicId(), senderName, this.session.getSessionId()); - log.trace("PARTICIPANT {}: SdpAnswer for {} is {}", this.getParticipantPublicId(), senderName, sdpAnswer); + log.trace("PARTICIPANT {}: Sdp string for {} is {}", this.getParticipantPublicId(), senderName, sdpString); if (senderName.equals(this.getParticipantPublicId())) { log.warn("PARTICIPANT {}: trying to configure loopback by subscribing", this.getParticipantPublicId()); throw new OpenViduException(Code.USER_NOT_STREAMING_ERROR_CODE, "Can loopback only when publishing media"); } - KurentoParticipant kSender = (KurentoParticipant) sender; + if (kSender.streaming && kSender.getPublisher() != null) { - if (kSender.streaming && kSender.getPublisher() != null - && kSender.getPublisher().closingLock.readLock().tryLock()) { - - try { - final SubscriberEndpoint subscriber = getSubscriber(senderName); - if (subscriber.getEndpoint() == null) { - throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, "Unable to create subscriber endpoint"); - } - + final Lock closingReadLock = kSender.getPublisher().closingLock.readLock(); + if (closingReadLock.tryLock()) { try { - subscriber.subscribe(sdpAnswer, kSender.getPublisher()); - log.info("PARTICIPANT {}: Is now receiving video from {} in room {}", this.getParticipantPublicId(), - senderName, this.session.getSessionId()); - if (!silent - && !ProtocolElements.RECORDER_PARTICIPANT_PUBLICID.equals(this.getParticipantPublicId())) { - endpointConfig.getCdr().recordNewSubscriber(this, sender.getPublisherStreamId(), - sender.getParticipantPublicId(), subscriber.createdAt()); - } - } catch (KurentoServerException e) { - // TODO Check object status when KurentoClient sets this info in the object - if (e.getCode() == 40101) { - log.warn( - "Publisher endpoint was already released when trying to connect a subscriber endpoint to it", - e); - } else { - log.error("Exception connecting subscriber endpoint to publisher endpoint", e); - } - this.subscribers.remove(senderName); - releaseSubscriberEndpoint(senderName, (KurentoParticipant) sender, subscriber, null, false); - } - } finally { - kSender.getPublisher().closingLock.readLock().unlock(); - } - } else { - log.error( - "PublisherEndpoint of participant {} of session {} is closed. Participant {} couldn't subscribe to it ", - senderName, sender.getSessionId(), this.participantPublicId); - throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, - "Unable to create subscriber endpoint. Publisher endpoint of participant " + senderName - + "is closed"); - } - } + // If initialized by server SubscriberEndpoint was created on + // prepareReceiveMediaFrom. If initialized by client must be created now + final SubscriberEndpoint subscriber = initByServer ? getSubscriber(senderName) + : initializeSubscriberEndpoint(kSender); - public String receiveMediaFrom2170(Participant sender, String sdpOffer, boolean silent) { - final String senderName = sender.getParticipantPublicId(); - - log.info("PARTICIPANT {}: Request to receive media from {} in room {}", this.getParticipantPublicId(), - senderName, this.session.getSessionId()); - log.trace("PARTICIPANT {}: SdpOffer for {} is {}", this.getParticipantPublicId(), senderName, sdpOffer); - - if (senderName.equals(this.getParticipantPublicId())) { - log.warn("PARTICIPANT {}: trying to configure loopback by subscribing", this.getParticipantPublicId()); - throw new OpenViduException(Code.USER_NOT_STREAMING_ERROR_CODE, "Can loopback only when publishing media"); - } - - KurentoParticipant kSender = (KurentoParticipant) sender; - - if (kSender.streaming && kSender.getPublisher() != null - && kSender.getPublisher().closingLock.readLock().tryLock()) { - - try { - log.debug("PARTICIPANT {}: Creating a subscriber endpoint to user {}", this.getParticipantPublicId(), - senderName); - - SubscriberEndpoint subscriber = getNewOrExistingSubscriber(senderName); - - try { - CountDownLatch subscriberLatch = new CountDownLatch(1); - Endpoint oldMediaEndpoint = subscriber.createEndpoint(subscriberLatch); - - try { - if (!subscriberLatch.await(KurentoSession.ASYNC_LATCH_TIMEOUT, TimeUnit.SECONDS)) { - throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, - "Timeout reached when creating subscriber endpoint"); - } - } catch (InterruptedException e) { - throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, - "Interrupted when creating subscriber endpoint: " + e.getMessage()); - } - if (oldMediaEndpoint != null) { - log.warn( - "PARTICIPANT {}: Two threads are trying to create at " - + "the same time a subscriber endpoint for user {}", - this.getParticipantPublicId(), senderName); - return null; - } if (subscriber.getEndpoint() == null) { throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, "Unable to create subscriber endpoint"); } - - String subscriberEndpointName = calculateSubscriberEndpointName(kSender); - - subscriber.setEndpointName(subscriberEndpointName); - subscriber.getEndpoint().setName(subscriberEndpointName); - subscriber.setStreamId(kSender.getPublisherStreamId()); - - endpointConfig.addEndpointListeners(subscriber, "subscriber"); - - } catch (OpenViduException e) { - this.subscribers.remove(senderName); - throw e; + try { + String sdpAnswer = subscriber.subscribe(sdpString, kSender.getPublisher()); + log.info("PARTICIPANT {}: Is now receiving video from {} in room {}", + this.getParticipantPublicId(), senderName, this.session.getSessionId()); + if (!silent && !ProtocolElements.RECORDER_PARTICIPANT_PUBLICID + .equals(this.getParticipantPublicId())) { + endpointConfig.getCdr().recordNewSubscriber(this, sender.getPublisherStreamId(), + sender.getParticipantPublicId(), subscriber.createdAt()); + } + return sdpAnswer; + } catch (KurentoServerException e) { + // TODO Check object status when KurentoClient sets this info in the object + if (e.getCode() == 40101) { + log.warn( + "Publisher endpoint was already released when trying to connect a subscriber endpoint to it", + e); + } else { + log.error("Exception connecting subscriber endpoint to publisher endpoint", e); + } + this.subscribers.remove(senderName); + releaseSubscriberEndpoint(senderName, (KurentoParticipant) sender, subscriber, null, false); + return null; + } + } finally { + closingReadLock.unlock(); } - log.debug("PARTICIPANT {}: Created subscriber endpoint for user {}", this.getParticipantPublicId(), - senderName); - try { - String sdpAnswer = subscriber.subscribe(sdpOffer, kSender.getPublisher()); - log.trace("PARTICIPANT {}: Subscribing SdpAnswer is {}", this.getParticipantPublicId(), sdpAnswer); - log.info("PARTICIPANT {}: Is now receiving video from {} in room {}", this.getParticipantPublicId(), - senderName, this.session.getSessionId()); - - if (!silent - && !ProtocolElements.RECORDER_PARTICIPANT_PUBLICID.equals(this.getParticipantPublicId())) { - endpointConfig.getCdr().recordNewSubscriber(this, sender.getPublisherStreamId(), - sender.getParticipantPublicId(), subscriber.createdAt()); - } - - return sdpAnswer; - } catch (KurentoServerException e) { - // TODO Check object status when KurentoClient sets this info in the object - if (e.getCode() == 40101) { - log.warn( - "Publisher endpoint was already released when trying to connect a subscriber endpoint to it", - e); - } else { - log.error("Exception connecting subscriber endpoint to publisher endpoint", e); - } - this.subscribers.remove(senderName); - releaseSubscriberEndpoint(senderName, (KurentoParticipant) sender, subscriber, null, false); - return null; - } - } finally { - kSender.getPublisher().closingLock.readLock().unlock(); } - } else { - log.error( - "PublisherEndpoint of participant {} of session {} is closed. Participant {} couldn't subscribe to it ", - senderName, sender.getSessionId(), this.participantPublicId); - throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, - "Unable to create subscriber endpoint. Publisher endpoint of participant " + senderName - + "is closed"); } + log.error( + "PublisherEndpoint of participant {} of session {} is closed. Participant {} couldn't subscribe to it ", + senderName, sender.getSessionId(), this.participantPublicId); + throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, + "Unable to create subscriber endpoint. Publisher endpoint of participant " + senderName + "is closed"); } public void cancelReceivingMedia(KurentoParticipant senderKurentoParticipant, EndReason reason, boolean silent) { @@ -461,7 +321,8 @@ public class KurentoParticipant extends Participant { final PublisherEndpoint pub = senderKurentoParticipant.publisher; if (pub != null) { try { - if (pub.closingLock.writeLock().tryLock(15, TimeUnit.SECONDS)) { + final Lock closingWriteLock = pub.closingLock.writeLock(); + if (closingWriteLock.tryLock(15, TimeUnit.SECONDS)) { try { log.info("PARTICIPANT {}: cancel receiving media from {}", this.getParticipantPublicId(), senderName); @@ -478,7 +339,7 @@ public class KurentoParticipant extends Participant { this.getParticipantPublicId(), senderName, this.session.getSessionId()); } } finally { - pub.closingLock.writeLock().unlock(); + closingWriteLock.unlock(); } } else { log.error( @@ -585,15 +446,66 @@ public class KurentoParticipant extends Participant { return this.getParticipantPublicId() + "_" + senderParticipant.getPublisherStreamId(); } + private SubscriberEndpoint initializeSubscriberEndpoint(Participant kSender) { + + String senderName = kSender.getParticipantPublicId(); + + log.debug("PARTICIPANT {}: Creating a subscriber endpoint to user {}", this.getParticipantPublicId(), + senderName); + + SubscriberEndpoint subscriber = getNewOrExistingSubscriber(senderName); + + try { + CountDownLatch subscriberLatch = new CountDownLatch(1); + Endpoint oldMediaEndpoint = subscriber.createEndpoint(subscriberLatch); + + try { + if (!subscriberLatch.await(KurentoSession.ASYNC_LATCH_TIMEOUT, TimeUnit.SECONDS)) { + throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, + "Timeout reached when creating subscriber endpoint"); + } + } catch (InterruptedException e) { + throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, + "Interrupted when creating subscriber endpoint: " + e.getMessage()); + } + if (oldMediaEndpoint != null) { + log.warn( + "PARTICIPANT {}: Two threads are trying to create at " + + "the same time a subscriber endpoint for user {}", + this.getParticipantPublicId(), senderName); + return null; + } + if (subscriber.getEndpoint() == null) { + throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, "Unable to create subscriber endpoint"); + } + + String subscriberEndpointName = calculateSubscriberEndpointName(kSender); + + subscriber.setEndpointName(subscriberEndpointName); + subscriber.getEndpoint().setName(subscriberEndpointName); + subscriber.setStreamId(kSender.getPublisherStreamId()); + + endpointConfig.addEndpointListeners(subscriber, "subscriber"); + + } catch (OpenViduException e) { + this.subscribers.remove(senderName); + throw e; + } + + log.debug("PARTICIPANT {}: Created subscriber endpoint for user {}", this.getParticipantPublicId(), senderName); + + return subscriber; + } + private void releasePublisherEndpoint(EndReason reason, Long kmsDisconnectionTime) { if (publisher != null && publisher.getEndpoint() != null) { - final ReadWriteLock closingLock = publisher.closingLock; try { - if (closingLock.writeLock().tryLock(15, TimeUnit.SECONDS)) { + final Lock closingWriteLock = publisher.closingLock.writeLock(); + if (closingWriteLock.tryLock(15, TimeUnit.SECONDS)) { try { this.releasePublisherEndpointAux(reason, kmsDisconnectionTime); } finally { - closingLock.writeLock().unlock(); + closingWriteLock.unlock(); } } } catch (InterruptedException e) { diff --git a/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoSessionManager.java b/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoSessionManager.java index 918e85c7..317638fe 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoSessionManager.java +++ b/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoSessionManager.java @@ -31,6 +31,8 @@ import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.annotation.PreDestroy; + import org.apache.commons.lang3.RandomStringUtils; import org.kurento.client.GenericMediaElement; import org.kurento.client.IceCandidate; @@ -65,6 +67,7 @@ import io.openvidu.server.core.EndReason; import io.openvidu.server.core.FinalUser; import io.openvidu.server.core.IdentifierPrefixes; import io.openvidu.server.core.MediaOptions; +import io.openvidu.server.core.MediaServer; import io.openvidu.server.core.Participant; import io.openvidu.server.core.Session; import io.openvidu.server.core.SessionManager; @@ -111,9 +114,13 @@ public class KurentoSessionManager extends SessionManager { if (sessionNotActive == null && this.isInsecureParticipant(participant.getParticipantPrivateId())) { // Insecure user directly call joinRoom RPC method, without REST API use - sessionNotActive = new Session(sessionId, new SessionProperties.Builder() - .mediaMode(MediaMode.ROUTED).recordingMode(RecordingMode.ALWAYS).build(), openviduConfig, - recordingManager); + SessionProperties.Builder builder = new SessionProperties.Builder().mediaMode(MediaMode.ROUTED) + .recordingMode(RecordingMode.ALWAYS); + // forcedVideoCodec to NONE if mediasoup + if (MediaServer.mediasoup.equals(openviduConfig.getMediaServer())) { + builder.forcedVideoCodec(VideoCodec.NONE); + } + sessionNotActive = new Session(sessionId, builder.build(), openviduConfig, recordingManager); } try { @@ -385,7 +392,7 @@ public class KurentoSessionManager extends SessionManager { // Modify sdp if forced codec is defined if (forcedVideoCodec != VideoCodec.NONE && !participant.isIpcam()) { kurentoOptions.sdpOffer = sdpMunging.forceCodec(kurentoOptions.sdpOffer, participant, true, false, - isTranscodingAllowed, forcedVideoCodec, false); + isTranscodingAllowed, forcedVideoCodec); CDR.log(new WebrtcDebugEvent(participant, streamId, WebrtcDebugEventIssuer.client, WebrtcDebugEventOperation.publish, WebrtcDebugEventType.sdpOfferMunged, kurentoOptions.sdpOffer)); } @@ -573,7 +580,7 @@ public class KurentoSessionManager extends SessionManager { // Modify server's SDPOffer if forced codec is defined if (forcedVideoCodec != VideoCodec.NONE && !participant.isIpcam()) { sdpOffer = sdpMunging.forceCodec(sdpOffer, participant, false, false, isTranscodingAllowed, - forcedVideoCodec, true); + forcedVideoCodec); CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.server, WebrtcDebugEventOperation.subscribe, WebrtcDebugEventType.sdpOfferMunged, sdpOffer)); @@ -594,12 +601,12 @@ public class KurentoSessionManager extends SessionManager { } @Override - public void subscribe(Participant participant, String senderName, String sdpAnswer, Integer transactionId, - boolean is2180) { + public void subscribe(Participant participant, String senderName, String sdpString, Integer transactionId, + boolean initByServer) { Session session = null; try { - log.debug("Request [SUBSCRIBE] remoteParticipant={} sdpAnswer={} ({})", senderName, sdpAnswer, + log.debug("Request [SUBSCRIBE] remoteParticipant={} sdpString={} ({})", senderName, sdpString, participant.getParticipantPublicId()); KurentoParticipant kParticipant = (KurentoParticipant) participant; @@ -625,48 +632,44 @@ public class KurentoSessionManager extends SessionManager { String subscriberEndpointName = kParticipant.calculateSubscriberEndpointName(senderParticipant); - // TODO: REMOVE ON 2.18.0 - if (is2180) { + if (initByServer) { + + // Server initiated negotiation. sdpString is the SDP Answer of the client - // Client's SDPAnswer to the server's SDPOffer CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.client, - WebrtcDebugEventOperation.subscribe, WebrtcDebugEventType.sdpAnswer, sdpAnswer)); + WebrtcDebugEventOperation.subscribe, WebrtcDebugEventType.sdpAnswer, sdpString)); - kParticipant.receiveMediaFrom2180(senderParticipant, sdpAnswer, false); + kParticipant.receiveMedia(senderParticipant, sdpString, false, true); sessionEventsHandler.onSubscribe(participant, session, transactionId, null); + } else { + // Client initiated negotiation. sdpString is the SDP Offer of the client + boolean isTranscodingAllowed = session.getSessionProperties().isTranscodingAllowed(); VideoCodec forcedVideoCodec = session.getSessionProperties().forcedVideoCodec(); + String sdpOffer = sdpString; // Modify sdp if forced codec is defined if (forcedVideoCodec != VideoCodec.NONE && !participant.isIpcam()) { - sdpAnswer = sdpMunging.forceCodec(sdpAnswer, participant, false, false, isTranscodingAllowed, - forcedVideoCodec, false); + sdpOffer = sdpMunging.forceCodec(sdpString, participant, false, false, isTranscodingAllowed, + forcedVideoCodec); CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.client, - WebrtcDebugEventOperation.subscribe, WebrtcDebugEventType.sdpOfferMunged, sdpAnswer)); + WebrtcDebugEventOperation.subscribe, WebrtcDebugEventType.sdpOfferMunged, sdpOffer)); } - String finalSdpAnswer = kParticipant.receiveMediaFrom2170(senderParticipant, sdpAnswer, false); - if (finalSdpAnswer == null) { + String sdpAnswer = kParticipant.receiveMedia(senderParticipant, sdpOffer, false, false); + if (sdpAnswer == null) { throw new OpenViduException(Code.MEDIA_SDP_ERROR_CODE, "Unable to generate SDP answer when subscribing '" + participant.getParticipantPublicId() + "' to '" + senderName + "'"); } CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.server, - WebrtcDebugEventOperation.subscribe, WebrtcDebugEventType.sdpAnswer, finalSdpAnswer)); - sessionEventsHandler.onSubscribe(participant, session, finalSdpAnswer, transactionId, null); + WebrtcDebugEventOperation.subscribe, WebrtcDebugEventType.sdpAnswer, sdpAnswer)); + sessionEventsHandler.onSubscribe(participant, session, sdpAnswer, transactionId, null); } - // END TODO - - // TODO: UNCOMMENT ON 2.18.0 -// CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.client, -// WebrtcDebugEventOperation.subscribe, WebrtcDebugEventType.sdpAnswer, sdpAnswer)); -// String remoteSdpAnswer = kParticipant.receiveMediaFrom(senderParticipant, sdpAnswer, false); -// sessionEventsHandler.onSubscribe(participant, session, transactionId, null); - // END TODO } catch (OpenViduException e) { log.error("PARTICIPANT {}: Error subscribing to {}", participant.getParticipantPublicId(), senderName, e); @@ -774,7 +777,6 @@ public class KurentoSessionManager extends SessionManager { log.info("No session '{}' exists yet. Created one on KMS '{}' with ip '{}'", session.getSessionId(), kms.getId(), kms.getIp()); - sessionEventsHandler.onSessionCreated(session); return session; } @@ -1176,184 +1178,127 @@ public class KurentoSessionManager extends SessionManager { return kParticipant; } - // TODO: REMOVE ON 2.18.0 @Override - public void reconnectStream2170(Participant participant, String streamId, String sdpOffer, Integer transactionId) { + public void reconnectPublisher(Participant participant, String streamId, String sdpString, Integer transactionId) { KurentoParticipant kParticipant = (KurentoParticipant) participant; KurentoSession kSession = kParticipant.getSession(); - boolean isPublisher = streamId.equals(participant.getPublisherStreamId()); + reconnectPublisher(kSession, kParticipant, streamId, sdpString, transactionId); + } + + @Override + public void reconnectSubscriber(Participant participant, String streamId, String sdpString, Integer transactionId, + boolean initByServer) { + KurentoParticipant kParticipant = (KurentoParticipant) participant; + KurentoSession kSession = kParticipant.getSession(); + reconnectSubscriber(kSession, kParticipant, streamId, sdpString, transactionId, initByServer); + } + + private String mungeSdpOffer(Session kSession, Participant participant, String sdpOffer, boolean isPublisher) { boolean isTranscodingAllowed = kSession.getSessionProperties().isTranscodingAllowed(); VideoCodec forcedVideoCodec = kSession.getSessionProperties().forcedVideoCodec(); - - boolean sdpOfferHasBeenMunged = false; - String originalSdpOffer = sdpOffer; - // Modify sdp if forced codec is defined if (forcedVideoCodec != VideoCodec.NONE && !participant.isIpcam()) { - sdpOfferHasBeenMunged = true; - sdpOffer = sdpMunging.forceCodec(sdpOffer, participant, isPublisher, true, isTranscodingAllowed, - forcedVideoCodec, false); + return sdpMunging.forceCodec(sdpOffer, participant, isPublisher, true, isTranscodingAllowed, + forcedVideoCodec); } + return null; + } - if (isPublisher) { + private void reconnectPublisher(KurentoSession kSession, KurentoParticipant kParticipant, String streamId, + String sdpOffer, Integer transactionId) { - CDR.log(new WebrtcDebugEvent(participant, streamId, WebrtcDebugEventIssuer.client, - WebrtcDebugEventOperation.reconnectPublisher, WebrtcDebugEventType.sdpOffer, originalSdpOffer)); - if (sdpOfferHasBeenMunged) { - CDR.log(new WebrtcDebugEvent(participant, streamId, WebrtcDebugEventIssuer.client, - WebrtcDebugEventOperation.reconnectPublisher, WebrtcDebugEventType.sdpOfferMunged, sdpOffer)); - } + String sdpOfferMunged = mungeSdpOffer(kSession, kParticipant, sdpOffer, true); - // Reconnect publisher - final KurentoMediaOptions kurentoOptions = (KurentoMediaOptions) kParticipant.getPublisher() - .getMediaOptions(); + CDR.log(new WebrtcDebugEvent(kParticipant, streamId, WebrtcDebugEventIssuer.client, + WebrtcDebugEventOperation.reconnectPublisher, WebrtcDebugEventType.sdpOffer, sdpOffer)); + if (sdpOfferMunged != null) { + sdpOffer = sdpOfferMunged; + CDR.log(new WebrtcDebugEvent(kParticipant, streamId, WebrtcDebugEventIssuer.client, + WebrtcDebugEventOperation.reconnectPublisher, WebrtcDebugEventType.sdpOfferMunged, sdpOffer)); + } + // Reconnect publisher + final KurentoMediaOptions kurentoOptions = (KurentoMediaOptions) kParticipant.getPublisher().getMediaOptions(); + // 1) Disconnect broken PublisherEndpoint from its PassThrough + PublisherEndpoint publisher = kParticipant.getPublisher(); + final PassThrough passThru = publisher.disconnectFromPassThrough(); + // 2) Destroy the broken PublisherEndpoint and nothing else + publisher.cancelStatsLoop.set(true); + kParticipant.releaseElement(kParticipant.getParticipantPublicId(), publisher.getEndpoint()); + // 3) Create a new PublisherEndpoint connecting it to the previous PassThrough + kParticipant.resetPublisherEndpoint(kurentoOptions, passThru); + kParticipant.createPublishingEndpoint(kurentoOptions, streamId); + String sdpAnswer = kParticipant.publishToRoom(sdpOffer, kurentoOptions.doLoopback, true); + log.debug("SDP Answer for publishing reconnection PARTICIPANT {}: {}", kParticipant.getParticipantPublicId(), + sdpAnswer); + CDR.log(new WebrtcDebugEvent(kParticipant, streamId, WebrtcDebugEventIssuer.server, + WebrtcDebugEventOperation.reconnectPublisher, WebrtcDebugEventType.sdpAnswer, sdpAnswer)); + sessionEventsHandler.onPublishMedia(kParticipant, kParticipant.getPublisherStreamId(), + kParticipant.getPublisher().createdAt(), kSession.getSessionId(), kurentoOptions, sdpAnswer, + new HashSet(), transactionId, null); + } - // 1) Disconnect broken PublisherEndpoint from its PassThrough - PublisherEndpoint publisher = kParticipant.getPublisher(); - final PassThrough passThru = publisher.disconnectFromPassThrough(); + private void reconnectSubscriber(KurentoSession kSession, KurentoParticipant kParticipant, String streamId, + String sdpString, Integer transactionId, boolean initByServer) { - // 2) Destroy the broken PublisherEndpoint and nothing else - publisher.cancelStatsLoop.set(true); - kParticipant.releaseElement(participant.getParticipantPublicId(), publisher.getEndpoint()); + String senderPrivateId = kSession.getParticipantPrivateIdFromStreamId(streamId); + if (senderPrivateId != null) { - // 3) Create a new PublisherEndpoint connecting it to the previous PassThrough - kParticipant.resetPublisherEndpoint(kurentoOptions, passThru); - kParticipant.createPublishingEndpoint(kurentoOptions, streamId); - String sdpAnswer = kParticipant.publishToRoom(sdpOffer, kurentoOptions.doLoopback, true); - log.debug("SDP Answer for publishing reconnection PARTICIPANT {}: {}", participant.getParticipantPublicId(), - sdpAnswer); + KurentoParticipant sender = (KurentoParticipant) kSession.getParticipantByPrivateId(senderPrivateId); + String subscriberEndpointName = kParticipant.calculateSubscriberEndpointName(sender); - CDR.log(new WebrtcDebugEvent(participant, streamId, WebrtcDebugEventIssuer.server, - WebrtcDebugEventOperation.reconnectPublisher, WebrtcDebugEventType.sdpAnswer, sdpAnswer)); + if (initByServer) { - sessionEventsHandler.onPublishMedia(participant, participant.getPublisherStreamId(), - kParticipant.getPublisher().createdAt(), kSession.getSessionId(), kurentoOptions, sdpAnswer, - new HashSet(), transactionId, null); + // Server initiated negotiation - } else { + final String sdpAnswer = sdpString; - // Reconnect subscriber - String senderPrivateId = kSession.getParticipantPrivateIdFromStreamId(streamId); - if (senderPrivateId != null) { + CDR.log(new WebrtcDebugEvent(kParticipant, subscriberEndpointName, WebrtcDebugEventIssuer.client, + WebrtcDebugEventOperation.reconnectSubscriber, WebrtcDebugEventType.sdpAnswer, sdpAnswer)); - KurentoParticipant sender = (KurentoParticipant) kSession.getParticipantByPrivateId(senderPrivateId); - String subscriberEndpointName = kParticipant.calculateSubscriberEndpointName(sender); + kParticipant.receiveMedia(sender, sdpAnswer, true, true); - CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.client, - WebrtcDebugEventOperation.reconnectSubscriber, WebrtcDebugEventType.sdpOffer, - originalSdpOffer)); - if (sdpOfferHasBeenMunged) { - CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.client, + log.debug("SDP Answer for subscribing reconnection PARTICIPANT {}: {}", + kParticipant.getParticipantPublicId(), sdpAnswer); + + sessionEventsHandler.onSubscribe(kParticipant, kSession, sdpAnswer, transactionId, null); + + } else { + + // Client initiated negotiation + + String sdpOffer = sdpString; + + CDR.log(new WebrtcDebugEvent(kParticipant, subscriberEndpointName, WebrtcDebugEventIssuer.client, + WebrtcDebugEventOperation.reconnectSubscriber, WebrtcDebugEventType.sdpOffer, sdpOffer)); + + String sdpOfferMunged = mungeSdpOffer(kSession, kParticipant, sdpOffer, false); + if (sdpOfferMunged != null) { + sdpOffer = sdpOfferMunged; + CDR.log(new WebrtcDebugEvent(kParticipant, subscriberEndpointName, WebrtcDebugEventIssuer.client, WebrtcDebugEventOperation.reconnectSubscriber, WebrtcDebugEventType.sdpOfferMunged, sdpOffer)); } - String sdpAnswer = kParticipant.receiveMediaFrom2170(sender, sdpOffer, true); + kParticipant.cancelReceivingMedia(sender, null, true); + String sdpAnswer = kParticipant.receiveMedia(sender, sdpOffer, true, false); if (sdpAnswer == null) { throw new OpenViduException(Code.MEDIA_SDP_ERROR_CODE, "Unable to generate SDP answer when reconnecting subscriber to '" + streamId + "'"); } log.debug("SDP Answer for subscribing reconnection PARTICIPANT {}: {}", - participant.getParticipantPublicId(), sdpAnswer); + kParticipant.getParticipantPublicId(), sdpAnswer); - CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.server, + CDR.log(new WebrtcDebugEvent(kParticipant, subscriberEndpointName, WebrtcDebugEventIssuer.server, WebrtcDebugEventOperation.reconnectSubscriber, WebrtcDebugEventType.sdpAnswer, sdpAnswer)); - sessionEventsHandler.onSubscribe(participant, kSession, sdpAnswer, transactionId, null); - } else { - throw new OpenViduException(Code.USER_NOT_STREAMING_ERROR_CODE, - "Stream '" + streamId + "' does not exist in Session '" + kSession.getSessionId() + "'"); + sessionEventsHandler.onSubscribe(kParticipant, kSession, sdpAnswer, transactionId, null); + } - } - } - // END TODO - - @Override - public void reconnectStream(Participant participant, String streamId, String sdpOfferOrAnswer, - Integer transactionId) { - KurentoParticipant kParticipant = (KurentoParticipant) participant; - KurentoSession kSession = kParticipant.getSession(); - boolean isPublisher = streamId.equals(participant.getPublisherStreamId()); - - if (isPublisher) { - - // Reconnect publisher - - String sdpOffer = sdpOfferOrAnswer; - - boolean isTranscodingAllowed = kSession.getSessionProperties().isTranscodingAllowed(); - VideoCodec forcedVideoCodec = kSession.getSessionProperties().forcedVideoCodec(); - - boolean sdpOfferHasBeenMunged = false; - final String originalSdpOffer = sdpOffer; - - // Modify sdp if forced codec is defined - if (forcedVideoCodec != VideoCodec.NONE && !participant.isIpcam()) { - sdpOfferHasBeenMunged = true; - sdpOffer = sdpMunging.forceCodec(sdpOffer, participant, isPublisher, true, isTranscodingAllowed, - forcedVideoCodec, false); - } - - CDR.log(new WebrtcDebugEvent(participant, streamId, WebrtcDebugEventIssuer.client, - WebrtcDebugEventOperation.reconnectPublisher, WebrtcDebugEventType.sdpOffer, originalSdpOffer)); - if (sdpOfferHasBeenMunged) { - CDR.log(new WebrtcDebugEvent(participant, streamId, WebrtcDebugEventIssuer.client, - WebrtcDebugEventOperation.reconnectPublisher, WebrtcDebugEventType.sdpOfferMunged, sdpOffer)); - } - - // Reconnect publisher - final KurentoMediaOptions kurentoOptions = (KurentoMediaOptions) kParticipant.getPublisher() - .getMediaOptions(); - - // 1) Disconnect broken PublisherEndpoint from its PassThrough - PublisherEndpoint publisher = kParticipant.getPublisher(); - final PassThrough passThru = publisher.disconnectFromPassThrough(); - - // 2) Destroy the broken PublisherEndpoint and nothing else - publisher.cancelStatsLoop.set(true); - kParticipant.releaseElement(participant.getParticipantPublicId(), publisher.getEndpoint()); - - // 3) Create a new PublisherEndpoint connecting it to the previous PassThrough - kParticipant.resetPublisherEndpoint(kurentoOptions, passThru); - kParticipant.createPublishingEndpoint(kurentoOptions, streamId); - String sdpAnswer = kParticipant.publishToRoom(sdpOffer, kurentoOptions.doLoopback, true); - log.debug("SDP Answer for publishing reconnection PARTICIPANT {}: {}", participant.getParticipantPublicId(), - sdpAnswer); - - CDR.log(new WebrtcDebugEvent(participant, streamId, WebrtcDebugEventIssuer.server, - WebrtcDebugEventOperation.reconnectPublisher, WebrtcDebugEventType.sdpAnswer, sdpAnswer)); - - sessionEventsHandler.onPublishMedia(participant, participant.getPublisherStreamId(), - kParticipant.getPublisher().createdAt(), kSession.getSessionId(), kurentoOptions, sdpAnswer, - new HashSet(), transactionId, null); } else { - - // Reconnect subscriber - - final String sdpAnswer = sdpOfferOrAnswer; - - String senderPrivateId = kSession.getParticipantPrivateIdFromStreamId(streamId); - if (senderPrivateId != null) { - - KurentoParticipant sender = (KurentoParticipant) kSession.getParticipantByPrivateId(senderPrivateId); - String subscriberEndpointName = kParticipant.calculateSubscriberEndpointName(sender); - - CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.client, - WebrtcDebugEventOperation.reconnectSubscriber, WebrtcDebugEventType.sdpAnswer, sdpAnswer)); - - kParticipant.receiveMediaFrom2180(sender, sdpAnswer, true); - - log.debug("SDP Answer for subscribing reconnection PARTICIPANT {}: {}", - participant.getParticipantPublicId(), sdpAnswer); - - sessionEventsHandler.onSubscribe(participant, kSession, sdpAnswer, transactionId, null); - - } else { - throw new OpenViduException(Code.USER_NOT_STREAMING_ERROR_CODE, - "Stream '" + streamId + "' does not exist in Session '" + kSession.getSessionId() + "'"); - } + throw new OpenViduException(Code.USER_NOT_STREAMING_ERROR_CODE, + "Stream '" + streamId + "' does not exist in Session '" + kSession.getSessionId() + "'"); } } @@ -1444,4 +1389,11 @@ public class KurentoSessionManager extends SessionManager { return lessLoadedKms; } + @PreDestroy + @Override + public void close() { + super.close(); + this.kmsManager.closeAllKurentoClients(); + } + } diff --git a/openvidu-server/src/main/java/io/openvidu/server/kurento/endpoint/SubscriberEndpoint.java b/openvidu-server/src/main/java/io/openvidu/server/kurento/endpoint/SubscriberEndpoint.java index 31c7fea7..7a0b9f61 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/kurento/endpoint/SubscriberEndpoint.java +++ b/openvidu-server/src/main/java/io/openvidu/server/kurento/endpoint/SubscriberEndpoint.java @@ -58,31 +58,24 @@ public class SubscriberEndpoint extends MediaEndpoint { offerOptions.setOfferToReceiveVideo(publisher.getMediaOptions().hasVideo()); String sdpOffer = generateOffer(offerOptions); + gatherCandidates(); return sdpOffer; } - public synchronized String subscribe(String sdpAnswer, PublisherEndpoint publisher) { - // TODO: REMOVE ON 2.18.0 - if (this.createdAt == null) { - // 2.17.0 + public synchronized String subscribe(String sdpString, PublisherEndpoint publisher) { + if (this.publisherStreamId == null) { + // Client initiated negotiation registerOnIceCandidateEventListener(publisher.getOwner().getParticipantPublicId()); this.createdAt = System.currentTimeMillis(); - String realSdpAnswer = processOffer(sdpAnswer); + String realSdpAnswer = processOffer(sdpString); gatherCandidates(); publisher.connect(this.getEndpoint(), false); this.publisherStreamId = publisher.getStreamId(); return realSdpAnswer; } else { - // 2.18.0 - processAnswer(sdpAnswer); - gatherCandidates(); - return null; + // Server initiated negotiation + return processAnswer(sdpString); } - // END TODO - - // TODO: UNCOMMENT ON 2.18.0 - // processAnswer(sdpAnswer); - // END TODO } @Override diff --git a/openvidu-server/src/main/java/io/openvidu/server/kurento/kms/KmsManager.java b/openvidu-server/src/main/java/io/openvidu/server/kurento/kms/KmsManager.java index dae5cb19..dc3f0e21 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/kurento/kms/KmsManager.java +++ b/openvidu-server/src/main/java/io/openvidu/server/kurento/kms/KmsManager.java @@ -31,7 +31,6 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; import org.apache.commons.lang3.RandomStringUtils; import org.kurento.jsonrpc.client.JsonRpcWSConnectionListener; @@ -343,8 +342,7 @@ public abstract class KmsManager { @PostConstruct protected abstract void postConstructInitKurentoClients(); - @PreDestroy - public void close() { + public void closeAllKurentoClients() { log.info("Closing all KurentoClients"); this.kmss.values().forEach(kms -> { if (kms.getKurentoClientReconnectTimer() != null) { diff --git a/openvidu-server/src/main/java/io/openvidu/server/recording/Recording.java b/openvidu-server/src/main/java/io/openvidu/server/recording/Recording.java index 871c909a..790defeb 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/recording/Recording.java +++ b/openvidu-server/src/main/java/io/openvidu/server/recording/Recording.java @@ -78,12 +78,18 @@ public class Recording { RecordingProperties.Builder builder = new RecordingProperties.Builder().name(json.get("name").getAsString()) .outputMode(outputMode).hasAudio(hasAudio).hasVideo(hasVideo); if (RecordingUtils.IS_COMPOSED(outputMode) && hasVideo) { - builder.resolution(json.get("resolution").getAsString()); - builder.frameRate(json.get("frameRate").getAsInt()); - RecordingLayout recordingLayout = RecordingLayout.valueOf(json.get("recordingLayout").getAsString()); - builder.recordingLayout(recordingLayout); - if (RecordingLayout.CUSTOM.equals(recordingLayout)) { - builder.customLayout(json.get("customLayout").getAsString()); + if (json.has("resolution")) { + builder.resolution(json.get("resolution").getAsString()); + } + if (json.has("frameRate")) { + builder.frameRate(json.get("frameRate").getAsInt()); + } + if (json.has("recordingLayout")) { + RecordingLayout recordingLayout = RecordingLayout.valueOf(json.get("recordingLayout").getAsString()); + builder.recordingLayout(recordingLayout); + if (RecordingLayout.CUSTOM.equals(recordingLayout) && json.has("customLayout")) { + builder.customLayout(json.get("customLayout").getAsString()); + } } } this.recordingProperties = builder.build(); diff --git a/openvidu-server/src/main/java/io/openvidu/server/recording/service/RecordingManager.java b/openvidu-server/src/main/java/io/openvidu/server/recording/service/RecordingManager.java index 47b4e6de..94484203 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/recording/service/RecordingManager.java +++ b/openvidu-server/src/main/java/io/openvidu/server/recording/service/RecordingManager.java @@ -64,6 +64,7 @@ import io.openvidu.java.client.RecordingProperties; import io.openvidu.server.cdr.CallDetailRecord; import io.openvidu.server.config.OpenviduConfig; import io.openvidu.server.core.EndReason; +import io.openvidu.server.core.MediaServer; import io.openvidu.server.core.Participant; import io.openvidu.server.core.Session; import io.openvidu.server.core.SessionEventsHandler; @@ -768,6 +769,8 @@ public class RecordingManager { // Check Kurento Media Server write permissions in recording path if (this.kmsManager.getKmss().isEmpty()) { log.warn("No KMSs were defined in KMS_URIS array. Recording path check aborted"); + } else if (MediaServer.mediasoup.equals(openviduConfig.getMediaServer())) { + log.warn("Using mediasoup. Recording path check aborted"); } else { Kms kms = null; try { diff --git a/openvidu-server/src/main/java/io/openvidu/server/rest/CertificateRestController.java b/openvidu-server/src/main/java/io/openvidu/server/rest/CertificateRestController.java index 542249fa..1ea9dbd4 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/rest/CertificateRestController.java +++ b/openvidu-server/src/main/java/io/openvidu/server/rest/CertificateRestController.java @@ -17,12 +17,12 @@ package io.openvidu.server.rest; +import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; -@RestController +@Controller @CrossOrigin @RequestMapping(RequestMappings.ACCEPT_CERTIFICATE) public class CertificateRestController { diff --git a/openvidu-server/src/main/java/io/openvidu/server/rest/SessionRestController.java b/openvidu-server/src/main/java/io/openvidu/server/rest/SessionRestController.java index c39195b9..1574514f 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/rest/SessionRestController.java +++ b/openvidu-server/src/main/java/io/openvidu/server/rest/SessionRestController.java @@ -65,6 +65,7 @@ import io.openvidu.java.client.VideoCodec; import io.openvidu.server.config.OpenviduConfig; import io.openvidu.server.core.EndReason; import io.openvidu.server.core.IdentifierPrefixes; +import io.openvidu.server.core.MediaServer; import io.openvidu.server.core.Participant; import io.openvidu.server.core.Session; import io.openvidu.server.core.SessionManager; @@ -120,7 +121,7 @@ public class SessionRestController { } Session sessionNotActive = sessionManager.storeSessionNotActive(sessionId, sessionProperties); - log.info("New session {} initialized {}", sessionId, this.sessionManager.getSessionsWithNotActive().stream() + log.info("New session {} created {}", sessionId, this.sessionManager.getSessionsWithNotActive().stream() .map(Session::getSessionId).collect(Collectors.toList()).toString()); return new ResponseEntity<>(sessionNotActive.toJson(false, false).toString(), RestUtils.getResponseHeaders(), @@ -756,10 +757,15 @@ public class SessionRestController { } builder = builder.customSessionId(customSessionId); } - if (forcedVideoCodec != null) { - builder = builder.forcedVideoCodec(VideoCodec.valueOf(forcedVideoCodec)); + // forcedVideoCodec to NONE if mediasoup + if (MediaServer.mediasoup.equals(openviduConfig.getMediaServer())) { + builder = builder.forcedVideoCodec(VideoCodec.NONE); } else { - builder = builder.forcedVideoCodec(openviduConfig.getOpenviduForcedCodec()); + if (forcedVideoCodec != null) { + builder = builder.forcedVideoCodec(VideoCodec.valueOf(forcedVideoCodec)); + } else { + builder = builder.forcedVideoCodec(openviduConfig.getOpenviduForcedCodec()); + } } if (allowTranscoding != null) { builder = builder.allowTranscoding(allowTranscoding); diff --git a/openvidu-server/src/main/java/io/openvidu/server/rpc/RpcHandler.java b/openvidu-server/src/main/java/io/openvidu/server/rpc/RpcHandler.java index 21598e3c..faa3a81d 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/rpc/RpcHandler.java +++ b/openvidu-server/src/main/java/io/openvidu/server/rpc/RpcHandler.java @@ -172,6 +172,9 @@ public class RpcHandler extends DefaultJsonRpcHandler { case ProtocolElements.VIDEODATA_METHOD: updateVideoData(rpcConnection, request); break; + case ProtocolElements.ECHO_METHOD: + echo(rpcConnection, request); + break; default: log.error("Unrecognized request {}", request); break; @@ -355,18 +358,7 @@ public class RpcHandler extends DefaultJsonRpcHandler { String senderStreamId = getStringParam(request, ProtocolElements.RECEIVEVIDEO_SENDER_PARAM); String senderPublicId = parseSenderPublicIdFromStreamId(senderStreamId); - boolean reconnect = false; - - // TODO: REMOVE ON 2.18.0 - if (request.getParams().has(ProtocolElements.PREPARERECEIVEVIDEO_RECONNECT_PARAM)) { - reconnect = getBooleanParam(request, ProtocolElements.PREPARERECEIVEVIDEO_RECONNECT_PARAM); - } - // END TODO - - // TODO: UNCOMMENT ON 2.18.0 - // boolean reconnect = getBooleanParam(request, - // ProtocolElements.PREPARERECEIVEVIDEO_RECONNECT_PARAM); - // END TODO + boolean reconnect = getBooleanParam(request, ProtocolElements.PREPARERECEIVEVIDEO_RECONNECT_PARAM); sessionManager.prepareSubscription(participant, senderPublicId, reconnect, request.getId()); } @@ -382,28 +374,15 @@ public class RpcHandler extends DefaultJsonRpcHandler { String senderStreamId = getStringParam(request, ProtocolElements.RECEIVEVIDEO_SENDER_PARAM); String senderPublicId = parseSenderPublicIdFromStreamId(senderStreamId); - // TODO: REMOVE ON 2.18.0 if (request.getParams().has(ProtocolElements.RECEIVEVIDEO_SDPOFFER_PARAM)) { - // 2.17.0: initiative held by browser when subscribing - // The request comes with an SDPOffer + // Client initiated negotiation (comes with SDP Offer) String sdpOffer = getStringParam(request, ProtocolElements.RECEIVEVIDEO_SDPOFFER_PARAM); sessionManager.subscribe(participant, senderPublicId, sdpOffer, request.getId(), false); } else if (request.getParams().has(ProtocolElements.RECEIVEVIDEO_SDPANSWER_PARAM)) { - // 2.18.0: initiative held by server when subscribing - // This is the final call after prepareReceiveVidoFrom, comes with SDPAnswer + // Server initiated negotiation (comes with SDP Answer) String sdpAnswer = getStringParam(request, ProtocolElements.RECEIVEVIDEO_SDPANSWER_PARAM); sessionManager.subscribe(participant, senderPublicId, sdpAnswer, request.getId(), true); } - // END TODO - - // TODO: UNCOMMENT ON 2.18.0 - /* - * String sdpAnswer = getStringParam(request, - * ProtocolElements.RECEIVEVIDEO_SDPANSWER_PARAM); - * sessionManager.subscribe(participant, senderPublicId, sdpAnswer, - * request.getId()); - */ - // END TODO } private void unsubscribeFromVideo(RpcConnection rpcConnection, Request request) { @@ -667,40 +646,28 @@ public class RpcHandler extends DefaultJsonRpcHandler { } catch (OpenViduException e) { return; } + String streamId = getStringParam(request, ProtocolElements.RECONNECTSTREAM_STREAM_PARAM); + boolean isPublisher = streamId.equals(participant.getPublisherStreamId()); - // TODO: REMOVE ON 2.18.0 + String sdpString = null; if (request.getParams().has(ProtocolElements.RECONNECTSTREAM_SDPOFFER_PARAM)) { - // 2.17.0 - try { - String sdpOffer = getStringParam(request, ProtocolElements.RECONNECTSTREAM_SDPOFFER_PARAM); - sessionManager.reconnectStream(participant, streamId, sdpOffer, request.getId()); - } catch (OpenViduException e) { - this.notificationService.sendErrorResponse(participant.getParticipantPrivateId(), request.getId(), - new JsonObject(), e); - } + sdpString = getStringParam(request, ProtocolElements.RECONNECTSTREAM_SDPOFFER_PARAM); } else if (request.getParams().has(ProtocolElements.RECONNECTSTREAM_SDPSTRING_PARAM)) { - // 2.18.0 - String sdpString = getStringParam(request, ProtocolElements.RECONNECTSTREAM_SDPSTRING_PARAM); - try { - sessionManager.reconnectStream(participant, streamId, sdpString, request.getId()); - } catch (OpenViduException e) { - this.notificationService.sendErrorResponse(participant.getParticipantPrivateId(), request.getId(), - new JsonObject(), e); - } + sdpString = getStringParam(request, ProtocolElements.RECONNECTSTREAM_SDPSTRING_PARAM); } - // END TODO - // TODO: UNCOMMENT ON 2.18.0 - /* - * String sdpString = getStringParam(request, - * ProtocolElements.RECONNECTSTREAM_SDPSTRING_PARAM); try { - * sessionManager.reconnectStream(participant, streamId, sdpString, - * request.getId()); } catch (OpenViduException e) { - * this.notificationService.sendErrorResponse(participant. - * getParticipantPrivateId(), request.getId(), new JsonObject(), e); } - */ - // END TODO + try { + if (isPublisher) { + sessionManager.reconnectPublisher(participant, streamId, sdpString, request.getId()); + } else { + boolean initByServer = request.getParams().has(ProtocolElements.RECONNECTSTREAM_SDPSTRING_PARAM); + sessionManager.reconnectSubscriber(participant, streamId, sdpString, request.getId(), initByServer); + } + } catch (OpenViduException e) { + this.notificationService.sendErrorResponse(participant.getParticipantPrivateId(), request.getId(), + new JsonObject(), e); + } } private void updateVideoData(RpcConnection rpcConnection, Request request) { @@ -717,6 +684,10 @@ public class RpcHandler extends DefaultJsonRpcHandler { } } + private void echo(RpcConnection rpcConnection, Request request) { + sessionManager.onEcho(rpcConnection.getParticipantPrivateId(), request.getId()); + } + public void leaveRoomAfterConnClosed(String participantPrivateId, EndReason reason) { try { sessionManager.evictParticipant(this.sessionManager.getParticipant(participantPrivateId), null, null, diff --git a/openvidu-server/src/main/java/io/openvidu/server/utils/SDPMunging.java b/openvidu-server/src/main/java/io/openvidu/server/utils/SDPMunging.java index 1ed6c319..ea2c5260 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/utils/SDPMunging.java +++ b/openvidu-server/src/main/java/io/openvidu/server/utils/SDPMunging.java @@ -68,7 +68,7 @@ public class SDPMunging { * ordering of formats. Browsers (tested with Chrome 84) honor this change and * use the first codec provided in the answer, so this operation actually works. */ - public String setCodecPreference(VideoCodec codec, String sdp, boolean applyHeavyMunging) throws OpenViduException { + public String setCodecPreference(VideoCodec codec, String sdp) throws OpenViduException { String codecStr = codec.name(); log.info("[setCodecPreference] codec: {}", codecStr); @@ -156,9 +156,8 @@ public class SDPMunging { lines[sl] = newLine.toString().trim(); } - if (applyHeavyMunging) { - lines = cleanLinesWithRemovedCodecs(unusedCodecPts, lines); - } + lines = cleanLinesWithRemovedCodecs(unusedCodecPts, lines); + return String.join("\r\n", lines) + "\r\n"; } @@ -166,8 +165,7 @@ public class SDPMunging { * Return a SDP modified to force a specific codec */ public String forceCodec(String sdp, Participant participant, boolean isPublisher, boolean isReconnecting, - boolean isTranscodingAllowed, VideoCodec forcedVideoCodec, boolean applyHeavyMunging) - throws OpenViduException { + boolean isTranscodingAllowed, VideoCodec forcedVideoCodec) throws OpenViduException { try { if (supportedVideoCodecs.contains(forcedVideoCodec)) { String mungedSdpOffer; @@ -178,7 +176,7 @@ public class SDPMunging { participant.getParticipantPublicId(), participant.getSessionId(), isPublisher, !isPublisher, isReconnecting, sdp); - mungedSdpOffer = this.setCodecPreference(forcedVideoCodec, sdp, applyHeavyMunging); + mungedSdpOffer = this.setCodecPreference(forcedVideoCodec, sdp); log.debug( "PARTICIPANT '{}' in Session '{}'. Is Publisher: '{}'. Is Subscriber: '{}'." diff --git a/openvidu-server/src/test/java/io/openvidu/server/test/unit/SDPMungingTest.java b/openvidu-server/src/test/java/io/openvidu/server/test/unit/SDPMungingTest.java index 294fe24f..6f7f6546 100644 --- a/openvidu-server/src/test/java/io/openvidu/server/test/unit/SDPMungingTest.java +++ b/openvidu-server/src/test/java/io/openvidu/server/test/unit/SDPMungingTest.java @@ -88,7 +88,7 @@ public class SDPMungingTest { private void initTestsSetCodecPrevalence(VideoCodec codec, String sdpNameFile) throws IOException { this.oldSdp = getSdpFile(sdpNameFile); - this.newSdp = this.sdpMungin.setCodecPreference(codec, oldSdp, false); + this.newSdp = this.sdpMungin.setCodecPreference(codec, oldSdp); this.forceCodecPayloads = new ArrayList<>(); // Get all Payload-Type for video Codec diff --git a/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduEventManager.java b/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduEventManager.java index be49e3b1..35306c1d 100644 --- a/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduEventManager.java +++ b/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduEventManager.java @@ -344,6 +344,13 @@ public class OpenViduEventManager { return dimension; } + public void stopVideoTracksOfVideoElement(WebElement videoElement, String parentSelector) { + String script = "return (document.querySelector('" + parentSelector + (parentSelector.isEmpty() ? "" : " ") + + "#" + videoElement.getAttribute("id") + + "').srcObject.getVideoTracks().forEach(track => track.stop()))"; + ((JavascriptExecutor) driver).executeScript(script); + } + private boolean hasAudioTracks(WebElement videoElement, String parentSelector) { String script = "return ((document.querySelector('" + parentSelector + (parentSelector.isEmpty() ? "" : " ") + "#" + videoElement.getAttribute("id") + "').srcObject.getAudioTracks().length > 0)" diff --git a/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduTestAppE2eTest.java b/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduTestAppE2eTest.java index 6544ea1f..1da51f1f 100644 --- a/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduTestAppE2eTest.java +++ b/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduTestAppE2eTest.java @@ -481,6 +481,38 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestAppE2eTest { gracefullyLeaveParticipants(4); } + @Test + @DisplayName("ExceptionEvent test") + void exceptionEventTest() throws Exception { + + setupBrowser("chrome"); + + log.info("ExceptionEvent test"); + + user.getDriver().findElement(By.id("add-user-btn")).click(); + user.getDriver().findElement(By.cssSelector("#openvidu-instance-0 .send-audio-checkbox")).click(); + user.getDriver().findElement(By.cssSelector("#openvidu-instance-0 .join-btn")).click(); + + user.getEventManager().waitUntilEventReaches("streamCreated", 1); + user.getEventManager().waitUntilEventReaches("streamPlaying", 1); + + // Stop video track + WebElement video = user.getDriver().findElement(By.cssSelector("#openvidu-instance-0 video")); + this.user.getEventManager().stopVideoTracksOfVideoElement(video, "#openvidu-instance-0"); + + user.getDriver().findElement(By.id("add-user-btn")).click(); + user.getDriver().findElement(By.cssSelector("#openvidu-instance-1 .publish-checkbox")).click(); + user.getDriver().findElement(By.cssSelector("#openvidu-instance-1 .join-btn")).click(); + + user.getEventManager().waitUntilEventReaches("exception", 1); + + Assert.assertTrue("Wrong ExceptionEvent type", user.getDriver() + .findElement(By.cssSelector("#openvidu-instance-1 .mat-expansion-panel:last-child .event-content")) + .getAttribute("textContent").equals("NO_STREAM_PLAYING_EVENT")); + + gracefullyLeaveParticipants(2); + } + @Test @DisplayName("Subscribe Unsubscribe") void subscribeUnsubscribeTest() throws Exception { diff --git a/openvidu-testapp/package.json b/openvidu-testapp/package.json index 959cbc9c..ffbf4b76 100644 --- a/openvidu-testapp/package.json +++ b/openvidu-testapp/package.json @@ -1,49 +1,49 @@ { - "dependencies": { - "@angular/animations": "8.2.14", - "@angular/cdk": "8.2.3", - "@angular/common": "8.2.14", - "@angular/compiler": "8.2.14", - "@angular/core": "8.2.14", - "@angular/flex-layout": "8.0.0-beta.27", - "@angular/forms": "8.2.14", - "@angular/http": "7.2.15", - "@angular/material": "8.2.3", - "@angular/platform-browser": "8.2.14", - "@angular/platform-browser-dynamic": "8.2.14", - "@angular/router": "8.2.14", - "colormap": "2.3.1", - "core-js": "3.4.7", - "hammerjs": "2.0.8", - "json-stringify-safe": "^5.0.1", - "openvidu-browser": "2.17.0", - "openvidu-node-client": "2.17.0", - "rxjs": "6.5.3", - "zone.js": "0.10.2" - }, - "devDependencies": { - "@angular-devkit/build-angular": "0.803.20", - "@angular/cli": "8.3.20", - "@angular/compiler-cli": "8.2.14", - "@angular/language-service": "8.2.14", - "@types/jasmine": "3.5.0", - "@types/jasminewd2": "2.0.8", - "@types/node": "12.12.14", - "codelyzer": "5.2.0", - "ts-node": "8.5.4", - "tslint": "5.20.1", - "typescript": "3.5.3" - }, - "license": "Apache-2.0", - "name": "openvidu-testapp", - "private": true, - "scripts": { - "build": "ng build", - "e2e": "ng e2e", - "lint": "ng lint", - "ng": "ng", - "start": "ng serve", - "test": "ng test" - }, - "version": "2.17.0" -} + "dependencies": { + "@angular/animations": "8.2.14", + "@angular/cdk": "8.2.3", + "@angular/common": "8.2.14", + "@angular/compiler": "8.2.14", + "@angular/core": "8.2.14", + "@angular/flex-layout": "8.0.0-beta.27", + "@angular/forms": "8.2.14", + "@angular/http": "7.2.15", + "@angular/material": "8.2.3", + "@angular/platform-browser": "8.2.14", + "@angular/platform-browser-dynamic": "8.2.14", + "@angular/router": "8.2.14", + "colormap": "2.3.1", + "core-js": "3.4.7", + "hammerjs": "2.0.8", + "json-stringify-safe": "^5.0.1", + "openvidu-browser": "2.18.0", + "openvidu-node-client": "2.18.0", + "rxjs": "6.5.3", + "zone.js": "0.10.2" + }, + "devDependencies": { + "@angular-devkit/build-angular": "0.803.20", + "@angular/cli": "8.3.20", + "@angular/compiler-cli": "8.2.14", + "@angular/language-service": "8.2.14", + "@types/jasmine": "3.5.0", + "@types/jasminewd2": "2.0.8", + "@types/node": "12.12.14", + "codelyzer": "5.2.0", + "ts-node": "8.5.4", + "tslint": "5.20.1", + "typescript": "3.5.3" + }, + "license": "Apache-2.0", + "name": "openvidu-testapp", + "private": true, + "scripts": { + "build": "ng build", + "e2e": "ng e2e", + "lint": "ng lint", + "ng": "ng", + "start": "ng serve", + "test": "ng test" + }, + "version": "2.18.0" +} \ No newline at end of file diff --git a/openvidu-testapp/src/app/components/openvidu-instance/openvidu-instance.component.ts b/openvidu-testapp/src/app/components/openvidu-instance/openvidu-instance.component.ts index ef28c0c5..72bd61e3 100644 --- a/openvidu-testapp/src/app/components/openvidu-instance/openvidu-instance.component.ts +++ b/openvidu-testapp/src/app/components/openvidu-instance/openvidu-instance.component.ts @@ -6,7 +6,7 @@ import { import { OpenVidu, Session, Subscriber, Publisher, Event, StreamEvent, ConnectionEvent, SessionDisconnectedEvent, SignalEvent, RecordingEvent, - PublisherSpeakingEvent, PublisherProperties, StreamPropertyChangedEvent, ConnectionPropertyChangedEvent, OpenViduError, NetworkQualityLevelChangedEvent + PublisherSpeakingEvent, PublisherProperties, StreamPropertyChangedEvent, ConnectionPropertyChangedEvent, OpenViduError, NetworkQualityLevelChangedEvent, ExceptionEvent } from 'openvidu-browser'; import { OpenVidu as OpenViduAPI, @@ -138,7 +138,8 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy { publisherStartSpeaking: false, publisherStopSpeaking: false, reconnecting: true, - reconnected: true + reconnected: true, + exception: true }; // Session properties dialog @@ -247,8 +248,9 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy { signal: false, publisherStartSpeaking: true, publisherStopSpeaking: true, - reconnecting: true, - reconnected: true + reconnecting: false, + reconnected: false, + exception: false }, true); this.session.connect(token, this.clientData) @@ -508,6 +510,15 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy { } } + if (this.sessionEvents.exception !== oldValues.exception || firstTime) { + this.session.off('exception'); + if (this.sessionEvents.exception) { + this.session.on('exception', (event: ExceptionEvent) => { + this.updateEventList('exception', event.name, event); + }); + } + } + } syncInitPublisher() { @@ -665,7 +676,8 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy { publisherStartSpeaking: this.sessionEvents.publisherStartSpeaking, publisherStopSpeaking: this.sessionEvents.publisherStopSpeaking, reconnecting: this.sessionEvents.reconnecting, - reconnected: this.sessionEvents.reconnected + reconnected: this.sessionEvents.reconnected, + exception: this.sessionEvents.exception }; const dialogRef = this.dialog.open(EventsDialogComponent, { @@ -699,7 +711,8 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy { publisherStartSpeaking: result.publisherStartSpeaking, publisherStopSpeaking: result.publisherStopSpeaking, reconnecting: result.reconnecting, - reconnected: result.reconnected + reconnected: result.reconnected, + exception: result.exception }; document.getElementById('session-events-btn-' + this.index).classList.remove('cdk-program-focused'); }); diff --git a/pom.xml b/pom.xml index d44be9a6..23b67636 100644 --- a/pom.xml +++ b/pom.xml @@ -62,7 +62,7 @@ 4.2.2 - 2.17.0 + 2.18.0 1.1.0 1.1.0