UNPKG

sinch-rtc

Version:

RTC JavaScript/Web SDK

363 lines 17.9 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.CallClientInstance = void 0; const function_1 = require("fp-ts/lib/function"); const utils_1 = require("../utils"); const models_1 = require("../ocra/api/models"); const RealTimeScheduler_1 = require("../session/RealTimeScheduler"); const OutboundSession_1 = require("../session/OutboundSession"); const Errors_1 = require("../utils/Errors"); const mxp_1 = require("../mxp"); const models_2 = require("../mxp/models"); const OutboundCall_1 = require("./OutboundCall"); const PubSubClient_1 = require("./PubSubClient"); const MxpMessageChannel_1 = require("../mxp/MxpMessageChannel"); const InboundSession_1 = require("../session/InboundSession"); const InboundCall_1 = require("./InboundCall"); const Sdp_1 = require("../session/jsep/Sdp"); const rtc_1 = require("../rtc"); const constants_1 = require("../constants"); const ClientEventsCollector_1 = require("./reporting/ClientEventsCollector"); const ClientEvent_1 = require("./reporting/ClientEvent"); const MxpEventsCollector_1 = require("./reporting/MxpEventsCollector"); const models_3 = require("./models"); const CallQualityDataCollector_1 = require("./quality/CallQualityDataCollector"); const DefaultCallQualityWarningControllerFactory_1 = require("./quality/DefaultCallQualityWarningControllerFactory"); const MxpClientEvent_1 = require("../mxp/MxpClientEvent"); class CallClientInstance { constructor(userId, applicationKey, rtcConfig, fetchIceServers, callInitiator, rtcProfile, incomingCallEvent, pushSender, callReporter, webRtcStatsCollector, mediaManager, useRelayIceCandidatesOnly, features, instanceId, callerIdentifier) { this.userId = userId; this.applicationKey = applicationKey; this.rtcConfig = rtcConfig; this.fetchIceServers = fetchIceServers; this.callInitiator = callInitiator; this.rtcProfile = rtcProfile; this.incomingCallEvent = incomingCallEvent; this.pushSender = pushSender; this.callReporter = callReporter; this.webRtcStatsCollector = webRtcStatsCollector; this.mediaManager = mediaManager; this.useRelayIceCandidatesOnly = useRelayIceCandidatesOnly; this.features = features; this.instanceId = instanceId; this.callerIdentifier = callerIdentifier; this.mxpChannels = new Map(); this.calls = new Map(); this.addListener = (listener) => { this.incomingCallEvent.addListener(listener); }; this.onBroadcastMessage = (broadcastMessage) => { const { message } = broadcastMessage; try { const decoded = this.mxpProtocol.decode(message, new models_2.SymmetricKey(this.rtcProfile.mxpEncryptionKey)); if (decoded !== null && decoded.message.method == models_2.Method.Invite) { this.onInvite({ message: decoded.message, pubsubTimeStamp: Date.now().toString(), }); } } catch (error) { // todo: log error logger.LogError("Failed to decode broadcast data", e); console.error(error); } }; this.onSignalMessage = (signalMessage) => { var _a; const { message } = signalMessage; const transport = mxp_1.ProtocolV10.getMXPDataParts(message); try { (_a = this.getMxpChannel(transport.sessionId)) === null || _a === void 0 ? void 0 : _a.handleInboundTransportMessage(signalMessage, this.mxpEventsCollector); } catch (error) { console.error(error); } }; this.sendOutboundMessage = (outboundMessage) => __awaiter(this, void 0, void 0, function* () { var _a, _b, _c; const { channelId, data } = outboundMessage; (_a = this.mxpEventsCollector) === null || _a === void 0 ? void 0 : _a.addSendingEventLogForMessage(outboundMessage); try { yield this.pubSubClient.publish(channelId, data); (_b = this.mxpEventsCollector) === null || _b === void 0 ? void 0 : _b.addSentEventLogForMessage(outboundMessage); } catch (error) { (_c = this.mxpEventsCollector) === null || _c === void 0 ? void 0 : _c.addErrorEventLogForMessage(outboundMessage); } }); this.localPeer = { userId: userId, instanceId: this.instanceId, channel: rtcProfile.mxpSignalChannel, deviceDescription: "js-sdk", // todo: fix device description cli: this.callerIdentifier, domain: models_1.Domain.Mxp, }; this.isMxpEventLogsEnabled = this.features.getFeatureFlag(constants_1.FeatureFlag.IncludeMxpEventLog); this.isTurnEnabled = this.features.getFeatureFlag(constants_1.FeatureFlag.UseTurn); if (rtcConfig.legacyStunUrls && !this.isTurnEnabled) { this.stunUrls = rtcConfig.legacyStunUrls; } this.pubSubClient = this.createPubSubClient(rtcProfile, rtcConfig); this.pubSubClient.onSignalMessage.add(this.onSignalMessage); this.pubSubClient.onBroadcastMessage.add(this.onBroadcastMessage); this.mxpProtocol = mxp_1.ProtocolV10Factory.create(); this.joinIceServersAndStunUrls = (0, function_1.flow)(rtc_1.toRTCIceServer, (iceServers) => this.stunUrls ? iceServers.concat([{ urls: this.stunUrls }]) : iceServers); this.getRTCIceServers = (0, utils_1.pipeAsync)(this.fetchIceServers, this.joinIceServersAndStunUrls); } removeEventListener(listener) { this.incomingCallEvent.removeListener(listener); } newOutboundCall(destination, video) { return __awaiter(this, void 0, void 0, function* () { const callId = this.newCallId(); this.mxpEventsCollector = this.createMxpEventsCollector(); const session = new OutboundSession_1.OutboundSession(this.localPeer, callId, new RealTimeScheduler_1.RealTimeScheduler()); const mediaController = yield this.mediaManager.createMediaController(true, video); const iceServers = yield this.getRTCIceServers(); const call = new OutboundCall_1.OutboundCall(destination, video, session, this.getOrCreateMxpChannel(session), this.pushSender, mediaController, this.callInitiator, this.useRelayIceCandidatesOnly, this.features, new ClientEventsCollector_1.ClientEventsCollector(), new CallQualityDataCollector_1.CallQualityDataCollector(), new DefaultCallQualityWarningControllerFactory_1.DefaultCallQualityWarningControllerFactory(), iceServers); this.addCallListener(call); this.storeCall(call); return call; }); } ensureCallNotExists(callId) { if (this.calls.has(callId)) { throw new Errors_1.InvalidOperationError(`Call already exists (${callId})`); } } hasActiveCall() { for (const call of this.calls.values()) { if (call.state != models_3.CallState.Ended) { return true; } } return false; } hasVideo(message) { return Sdp_1.Sdp.parse(message.getSdp().sdp).hasVideo(); } createMxpEventsCollector() { return this.isMxpEventLogsEnabled ? new MxpEventsCollector_1.MxpEventsCollector() : undefined; } newInboundCall(message) { return __awaiter(this, void 0, void 0, function* () { this.ensureCallNotExists(message.sessionId); this.mxpEventsCollector = this.createMxpEventsCollector(); const hasVideo = this.hasVideo(message); const session = new InboundSession_1.InboundSession(this.localPeer, message.sessionId, new RealTimeScheduler_1.RealTimeScheduler()); const mediaController = yield this.mediaManager.createMediaController(true, hasVideo); const origin = { identity: message.from.userId, domain: message.from.domain, }; const call = new InboundCall_1.InboundCall(hasVideo, origin, session, this.getOrCreateMxpChannel(session), mediaController, this.useRelayIceCandidatesOnly, new ClientEventsCollector_1.ClientEventsCollector(), new CallQualityDataCollector_1.CallQualityDataCollector(), new DefaultCallQualityWarningControllerFactory_1.DefaultCallQualityWarningControllerFactory(), this.onAcceptedWithoutTracks.bind(this)); call.remoteUserDisplayName = message.from.cli; this.addCallListener(call); this.storeCall(call); return call; }); } addCallListener(call) { const listener = { onCallEstablished: () => { this.webRtcStatsCollector.startStatsCollection(call); call.emitClientEvent(MxpClientEvent_1.MxpClientEvent.EnteredEstablished); }, onCallEnded: (call) => __awaiter(this, void 0, void 0, function* () { var _a; call.performCallEndedCleanup(); this.cancelSubscription(); this.webRtcStatsCollector.stopStatsCollection(); const webRtcStats = this.webRtcStatsCollector.getStats(); const connectionInfos = this.webRtcStatsCollector.getConnectionInfos(); yield this.callReporter.submitCallReport(call, webRtcStats, connectionInfos, call.callQualityCollector.callQualityData, call.clientEventsCollector.events, (_a = this.mxpEventsCollector) === null || _a === void 0 ? void 0 : _a.eventLog); this.webRtcStatsCollector.reset(); call.removeListener(listener); }), }; call.addListener(listener); } storeCall(call) { this.calls.set(call.id, call); } newCallId() { return utils_1.StringHelper.UUID(); } getMxpChannel(callId) { return this.mxpChannels.get(callId); } getOrCreateMxpChannel(session) { if (this.mxpChannels.has(session.id)) { return this.mxpChannels.get(session.id); } else { const mxpChannel = this.createMxpChannel(session); mxpChannel.onOutboundMessage.add(this.sendOutboundMessage); return mxpChannel; } } createMxpChannel(session) { if (this.mxpChannels.has(session.id)) throw new Errors_1.InvalidOperationError("MXP message channel already exists"); const channel = new MxpMessageChannel_1.MxpMessageChannel(session.id, session.localPeer.instanceId, this.mxpProtocol); this.mxpChannels.set(session.id, channel); return channel; } startSubscription() { this.pubSubClient.subscribe(); } static isMxpCall(call) { var _a; return models_1.Domain.Mxp == ((_a = call.destination) === null || _a === void 0 ? void 0 : _a.domain); } call(destination, video, headers) { return __awaiter(this, void 0, void 0, function* () { var _a; this.startSubscription(); const call = yield this.newOutboundCall(destination, video); if (headers) { call.setHeaders(headers); } call.p2p = CallClientInstance.isMxpCall(call); call.clientEventsCollector.addEvent(ClientEvent_1.ClientEvent.createClientEventType(ClientEvent_1.ClientEventName.SDK_API_CALL_USER_START)); yield call.start(); (_a = this.getMxpChannel(call.id)) === null || _a === void 0 ? void 0 : _a.processPendingInbound(); return call; }); } cancelSubscription() { this.pubSubClient.cancel(); } onInvite(invite) { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d; let ris = null; const { message } = invite; if (!message.isAddressedToInstance(this.instanceId)) { console.log("Received MXP Invite not addressed for this instance"); return; } this.startSubscription(); const call = yield this.newInboundCall(message); (_a = this.mxpEventsCollector) === null || _a === void 0 ? void 0 : _a.addReceivedEventLogForMessage(message); if (!mxp_1.Message.tryExtractSessionKey(message, (key) => call.setMxpSignalingConfiguration(key, message.from.channel))) { // todo: handle failure return; } if ((_b = message.values) === null || _b === void 0 ? void 0 : _b.ph) call.setHeaders(JSON.parse((_c = message.values) === null || _c === void 0 ? void 0 : _c.ph)); if ((_d = message.values) === null || _d === void 0 ? void 0 : _d.ris) { ris = JSON.parse(message.values.ris); } call.setIceServers(this.joinIceServersAndStunUrls(ris || [])); yield call.start(); const channel = this.getMxpChannel(call.id); if (channel) { channel.processPendingInbound(); channel.handleInboundMessage(message); } else { throw new Errors_1.InvalidOperationError("Unable to get message channel"); } this.incomingCallEvent.onEvent(call); }); } createPubSubClient(rtcProfile, rtcConfig) { // todo: EnsureValidCurrentInstance(); return PubSubClient_1.PubSubClientFactory.create(rtcProfile.mxpBroadcastChannel, rtcProfile.mxpSignalChannel, { hostname: rtcConfig.mxpPubSubConfig.hostname, broadcastSubscribeKey: rtcConfig.mxpPubSubConfig.broadcastSubscribeKey, signalSubscribeKey: rtcConfig.mxpPubSubConfig.signalSubscribeKey, signalPublishKey: rtcConfig.mxpPubSubConfig.signalPublishKey, }); } handleCallPushPayload(notificationResult) { this.pubSubClient.broadcastSubscribe((message) => { return (mxp_1.ProtocolV10.getMXPDataParts(message).sessionId == notificationResult.callNotificationResult.callId); }); } callUser(userId, video, headers) { return __awaiter(this, void 0, void 0, function* () { const call = yield this.call({ identity: userId, domain: models_1.Domain.Mxp, type: models_1.DestinationTypeEnum.UserId, }, video, headers); return call; }); } callSip(destination, video, headers) { return __awaiter(this, void 0, void 0, function* () { const call = yield this.call({ identity: destination, domain: models_1.Domain.Sip, type: models_1.DestinationTypeEnum.Sip, }, video, headers); return call; }); } callConference(id, video, headers) { return __awaiter(this, void 0, void 0, function* () { const call = yield this.call({ identity: id, domain: models_1.Domain.Conference, type: models_1.DestinationTypeEnum.Conference, }, video, headers); return call; }); } callPhoneNumber(number, headers) { return __awaiter(this, void 0, void 0, function* () { const call = yield this.call({ identity: number, domain: models_1.Domain.Pstn, type: models_1.DestinationTypeEnum.Phonenumber, }, false, headers); return call; }); } getCall(id) { return this.calls.get(id); } set audioConstraints(constraints) { this.mediaManager.audioTrackConstraints = constraints !== null && constraints !== void 0 ? constraints : undefined; this.updateActiveCallTrackConstraints(); } set videoConstraints(constraints) { this.mediaManager.videoTrackConstraints = constraints !== null && constraints !== void 0 ? constraints : undefined; this.updateActiveCallTrackConstraints(); } updateActiveCallTrackConstraints() { return __awaiter(this, void 0, void 0, function* () { for (const call of this.calls.values()) { if (call.state != models_3.CallState.Ended) { call.updateMediaController(yield this.mediaManager.createMediaController(true, call.requestedVideo)); } } }); } onAcceptedWithoutTracks(call) { return __awaiter(this, void 0, void 0, function* () { const mediaConstroller = yield this.mediaManager.createMediaController(true, call.requestedVideo); if (!mediaConstroller.hasAnyTracks) { return Promise.reject(new Errors_1.InvalidOperationError("Could not get media tracks. Make sure you have granted required media permissions.")); } call.updateMediaController(mediaConstroller); }); } } exports.CallClientInstance = CallClientInstance; //# sourceMappingURL=CallClientInstance.js.map