UNPKG

mediasoup

Version:

Cutting Edge WebRTC Video Conferencing

446 lines (445 loc) 16.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.WebRtcTransportImpl = void 0; exports.parseWebRtcTransportDumpResponse = parseWebRtcTransportDumpResponse; const Logger_1 = require("./Logger"); const enhancedEvents_1 = require("./enhancedEvents"); const Transport_1 = require("./Transport"); const fbsUtils = require("./fbsUtils"); const notification_1 = require("./fbs/notification"); const FbsRequest = require("./fbs/request"); const FbsTransport = require("./fbs/transport"); const FbsWebRtcTransport = require("./fbs/web-rtc-transport"); const dtls_state_1 = require("./fbs/web-rtc-transport/dtls-state"); const dtls_role_1 = require("./fbs/web-rtc-transport/dtls-role"); const fingerprint_algorithm_1 = require("./fbs/web-rtc-transport/fingerprint-algorithm"); const ice_state_1 = require("./fbs/web-rtc-transport/ice-state"); const ice_role_1 = require("./fbs/web-rtc-transport/ice-role"); const ice_candidate_type_1 = require("./fbs/web-rtc-transport/ice-candidate-type"); const ice_candidate_tcp_type_1 = require("./fbs/web-rtc-transport/ice-candidate-tcp-type"); const logger = new Logger_1.Logger('WebRtcTransport'); class WebRtcTransportImpl extends Transport_1.TransportImpl { // WebRtcTransport data. #data; constructor(options) { const observer = new enhancedEvents_1.EnhancedEventEmitter(); super(options, observer); logger.debug('constructor()'); const { data } = options; this.#data = { iceRole: data.iceRole, iceParameters: data.iceParameters, iceCandidates: data.iceCandidates, iceState: data.iceState, iceSelectedTuple: data.iceSelectedTuple, dtlsParameters: data.dtlsParameters, dtlsState: data.dtlsState, dtlsRemoteCert: data.dtlsRemoteCert, sctpParameters: data.sctpParameters, sctpState: data.sctpState, }; this.handleWorkerNotifications(); this.handleListenerError(); } get type() { return 'webrtc'; } get observer() { return super.observer; } get iceRole() { return this.#data.iceRole; } get iceParameters() { return this.#data.iceParameters; } get iceCandidates() { return this.#data.iceCandidates; } get iceState() { return this.#data.iceState; } get iceSelectedTuple() { return this.#data.iceSelectedTuple; } get dtlsParameters() { return this.#data.dtlsParameters; } get dtlsState() { return this.#data.dtlsState; } get dtlsRemoteCert() { return this.#data.dtlsRemoteCert; } get sctpParameters() { return this.#data.sctpParameters; } get sctpState() { return this.#data.sctpState; } close() { if (this.closed) { return; } this.#data.iceState = 'closed'; this.#data.iceSelectedTuple = undefined; this.#data.dtlsState = 'closed'; if (this.#data.sctpState) { this.#data.sctpState = 'closed'; } super.close(); } routerClosed() { if (this.closed) { return; } this.#data.iceState = 'closed'; this.#data.iceSelectedTuple = undefined; this.#data.dtlsState = 'closed'; if (this.#data.sctpState) { this.#data.sctpState = 'closed'; } super.routerClosed(); } listenServerClosed() { if (this.closed) { return; } this.#data.iceState = 'closed'; this.#data.iceSelectedTuple = undefined; this.#data.dtlsState = 'closed'; if (this.#data.sctpState) { this.#data.sctpState = 'closed'; } super.listenServerClosed(); } async dump() { logger.debug('dump()'); const response = await this.channel.request(FbsRequest.Method.TRANSPORT_DUMP, undefined, undefined, this.internal.transportId); /* Decode Response. */ const data = new FbsWebRtcTransport.DumpResponse(); response.body(data); return parseWebRtcTransportDumpResponse(data); } async getStats() { logger.debug('getStats()'); const response = await this.channel.request(FbsRequest.Method.TRANSPORT_GET_STATS, undefined, undefined, this.internal.transportId); /* Decode Response. */ const data = new FbsWebRtcTransport.GetStatsResponse(); response.body(data); return [parseGetStatsResponse(data)]; } async connect({ dtlsParameters, }) { logger.debug('connect()'); const requestOffset = createConnectRequest({ builder: this.channel.bufferBuilder, dtlsParameters, }); // Wait for response. const response = await this.channel.request(FbsRequest.Method.WEBRTCTRANSPORT_CONNECT, FbsRequest.Body.WebRtcTransport_ConnectRequest, requestOffset, this.internal.transportId); /* Decode Response. */ const data = new FbsWebRtcTransport.ConnectResponse(); response.body(data); // Update data. this.#data.dtlsParameters.role = dtlsRoleFromFbs(data.dtlsLocalRole()); } async restartIce() { logger.debug('restartIce()'); const response = await this.channel.request(FbsRequest.Method.TRANSPORT_RESTART_ICE, undefined, undefined, this.internal.transportId); /* Decode Response. */ const restartIceResponse = new FbsTransport.RestartIceResponse(); response.body(restartIceResponse); const iceParameters = { usernameFragment: restartIceResponse.usernameFragment(), password: restartIceResponse.password(), iceLite: restartIceResponse.iceLite(), }; this.#data.iceParameters = iceParameters; return iceParameters; } handleWorkerNotifications() { this.channel.on(this.internal.transportId, (event, data) => { switch (event) { case notification_1.Event.WEBRTCTRANSPORT_ICE_STATE_CHANGE: { const notification = new FbsWebRtcTransport.IceStateChangeNotification(); data.body(notification); const iceState = iceStateFromFbs(notification.iceState()); this.#data.iceState = iceState; this.safeEmit('icestatechange', iceState); // Emit observer event. this.observer.safeEmit('icestatechange', iceState); break; } case notification_1.Event.WEBRTCTRANSPORT_ICE_SELECTED_TUPLE_CHANGE: { const notification = new FbsWebRtcTransport.IceSelectedTupleChangeNotification(); data.body(notification); const iceSelectedTuple = (0, Transport_1.parseTuple)(notification.tuple()); this.#data.iceSelectedTuple = iceSelectedTuple; this.safeEmit('iceselectedtuplechange', iceSelectedTuple); // Emit observer event. this.observer.safeEmit('iceselectedtuplechange', iceSelectedTuple); break; } case notification_1.Event.WEBRTCTRANSPORT_DTLS_STATE_CHANGE: { const notification = new FbsWebRtcTransport.DtlsStateChangeNotification(); data.body(notification); const dtlsState = dtlsStateFromFbs(notification.dtlsState()); this.#data.dtlsState = dtlsState; if (dtlsState === 'connected') { this.#data.dtlsRemoteCert = notification.remoteCert(); } this.safeEmit('dtlsstatechange', dtlsState); // Emit observer event. this.observer.safeEmit('dtlsstatechange', dtlsState); break; } case notification_1.Event.TRANSPORT_SCTP_STATE_CHANGE: { const notification = new FbsTransport.SctpStateChangeNotification(); data.body(notification); const sctpState = (0, Transport_1.parseSctpState)(notification.sctpState()); this.#data.sctpState = sctpState; this.safeEmit('sctpstatechange', sctpState); // Emit observer event. this.observer.safeEmit('sctpstatechange', sctpState); break; } case notification_1.Event.TRANSPORT_TRACE: { const notification = new FbsTransport.TraceNotification(); data.body(notification); const trace = (0, Transport_1.parseTransportTraceEventData)(notification); this.safeEmit('trace', trace); // Emit observer event. this.observer.safeEmit('trace', trace); break; } default: { logger.error(`ignoring unknown event "${event}"`); } } }); } handleListenerError() { this.on('listenererror', (eventName, error) => { logger.error(`event listener threw an error [eventName:${eventName}]:`, error); }); } } exports.WebRtcTransportImpl = WebRtcTransportImpl; function iceStateFromFbs(fbsIceState) { switch (fbsIceState) { case ice_state_1.IceState.NEW: { return 'new'; } case ice_state_1.IceState.CONNECTED: { return 'connected'; } case ice_state_1.IceState.COMPLETED: { return 'completed'; } case ice_state_1.IceState.DISCONNECTED: { return 'disconnected'; } } } function iceRoleFromFbs(role) { switch (role) { case ice_role_1.IceRole.CONTROLLED: { return 'controlled'; } case ice_role_1.IceRole.CONTROLLING: { return 'controlling'; } } } function iceCandidateTypeFromFbs(type) { switch (type) { case ice_candidate_type_1.IceCandidateType.HOST: { return 'host'; } } } function iceCandidateTcpTypeFromFbs(type) { switch (type) { case ice_candidate_tcp_type_1.IceCandidateTcpType.PASSIVE: { return 'passive'; } } } function dtlsStateFromFbs(fbsDtlsState) { switch (fbsDtlsState) { case dtls_state_1.DtlsState.NEW: { return 'new'; } case dtls_state_1.DtlsState.CONNECTING: { return 'connecting'; } case dtls_state_1.DtlsState.CONNECTED: { return 'connected'; } case dtls_state_1.DtlsState.FAILED: { return 'failed'; } case dtls_state_1.DtlsState.CLOSED: { return 'closed'; } } } function dtlsRoleFromFbs(role) { switch (role) { case dtls_role_1.DtlsRole.AUTO: { return 'auto'; } case dtls_role_1.DtlsRole.CLIENT: { return 'client'; } case dtls_role_1.DtlsRole.SERVER: { return 'server'; } } } function fingerprintAlgorithmsFromFbs(algorithm) { switch (algorithm) { case fingerprint_algorithm_1.FingerprintAlgorithm.SHA1: { return 'sha-1'; } case fingerprint_algorithm_1.FingerprintAlgorithm.SHA224: { return 'sha-224'; } case fingerprint_algorithm_1.FingerprintAlgorithm.SHA256: { return 'sha-256'; } case fingerprint_algorithm_1.FingerprintAlgorithm.SHA384: { return 'sha-384'; } case fingerprint_algorithm_1.FingerprintAlgorithm.SHA512: { return 'sha-512'; } } } function fingerprintAlgorithmToFbs(algorithm) { switch (algorithm) { case 'sha-1': { return fingerprint_algorithm_1.FingerprintAlgorithm.SHA1; } case 'sha-224': { return fingerprint_algorithm_1.FingerprintAlgorithm.SHA224; } case 'sha-256': { return fingerprint_algorithm_1.FingerprintAlgorithm.SHA256; } case 'sha-384': { return fingerprint_algorithm_1.FingerprintAlgorithm.SHA384; } case 'sha-512': { return fingerprint_algorithm_1.FingerprintAlgorithm.SHA512; } default: { throw new TypeError(`invalid FingerprintAlgorithm: ${algorithm}`); } } } function dtlsRoleToFbs(role) { switch (role) { case 'auto': { return dtls_role_1.DtlsRole.AUTO; } case 'client': { return dtls_role_1.DtlsRole.CLIENT; } case 'server': { return dtls_role_1.DtlsRole.SERVER; } default: { throw new TypeError(`invalid DtlsRole: ${role}`); } } } function parseWebRtcTransportDumpResponse(binary) { // Retrieve BaseTransportDump. const baseTransportDump = (0, Transport_1.parseBaseTransportDump)(binary.base()); // Retrieve ICE candidates. const iceCandidates = fbsUtils.parseVector(binary, 'iceCandidates', parseIceCandidate); // Retrieve ICE parameters. const iceParameters = parseIceParameters(binary.iceParameters()); // Retrieve DTLS parameters. const dtlsParameters = parseDtlsParameters(binary.dtlsParameters()); return { ...baseTransportDump, sctpParameters: baseTransportDump.sctpParameters, sctpState: baseTransportDump.sctpState, iceRole: 'controlled', iceParameters: iceParameters, iceCandidates: iceCandidates, iceState: iceStateFromFbs(binary.iceState()), dtlsParameters: dtlsParameters, dtlsState: dtlsStateFromFbs(binary.dtlsState()), }; } function createConnectRequest({ builder, dtlsParameters, }) { // Serialize DtlsParameters. This can throw. const dtlsParametersOffset = serializeDtlsParameters(builder, dtlsParameters); return FbsWebRtcTransport.ConnectRequest.createConnectRequest(builder, dtlsParametersOffset); } function parseGetStatsResponse(binary) { const base = (0, Transport_1.parseBaseTransportStats)(binary.base()); return { ...base, type: 'webrtc-transport', iceRole: iceRoleFromFbs(binary.iceRole()), iceState: iceStateFromFbs(binary.iceState()), iceSelectedTuple: binary.iceSelectedTuple() ? (0, Transport_1.parseTuple)(binary.iceSelectedTuple()) : undefined, dtlsState: dtlsStateFromFbs(binary.dtlsState()), }; } function parseIceCandidate(binary) { return { foundation: binary.foundation(), priority: binary.priority(), ip: binary.address(), address: binary.address(), protocol: (0, Transport_1.parseProtocol)(binary.protocol()), port: binary.port(), type: iceCandidateTypeFromFbs(binary.type()), tcpType: binary.tcpType() === null ? undefined : iceCandidateTcpTypeFromFbs(binary.tcpType()), }; } function parseIceParameters(binary) { return { usernameFragment: binary.usernameFragment(), password: binary.password(), iceLite: binary.iceLite(), }; } function parseDtlsParameters(binary) { const fingerprints = []; for (let i = 0; i < binary.fingerprintsLength(); ++i) { const fbsFingerprint = binary.fingerprints(i); const fingerPrint = { algorithm: fingerprintAlgorithmsFromFbs(fbsFingerprint.algorithm()), value: fbsFingerprint.value(), }; fingerprints.push(fingerPrint); } return { fingerprints: fingerprints, role: binary.role() === null ? undefined : dtlsRoleFromFbs(binary.role()), }; } function serializeDtlsParameters(builder, dtlsParameters) { const fingerprints = []; for (const fingerprint of dtlsParameters.fingerprints) { const algorithm = fingerprintAlgorithmToFbs(fingerprint.algorithm); const valueOffset = builder.createString(fingerprint.value); const fingerprintOffset = FbsWebRtcTransport.Fingerprint.createFingerprint(builder, algorithm, valueOffset); fingerprints.push(fingerprintOffset); } const fingerprintsOffset = FbsWebRtcTransport.DtlsParameters.createFingerprintsVector(builder, fingerprints); const role = dtlsParameters.role !== undefined ? dtlsRoleToFbs(dtlsParameters.role) : FbsWebRtcTransport.DtlsRole.AUTO; return FbsWebRtcTransport.DtlsParameters.createDtlsParameters(builder, fingerprintsOffset, role); }