sinch-rtc
Version:
RTC JavaScript/Web SDK
363 lines • 17.9 kB
JavaScript
"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