sinch-rtc
Version:
RTC JavaScript/Web SDK
264 lines • 11.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.OutboundSession = void 0;
const models_1 = require("../mxp/models");
const SessionStateBase_1 = require("./SessionStateBase");
const mxp_1 = require("../mxp");
const utils_1 = require("../utils");
const EstablishedState_1 = require("./EstablishedState");
const TerminatedState_1 = require("./TerminatedState");
const TerminationCause_1 = require("./TerminationCause");
const SessionState_1 = require("./SessionState");
const Session_1 = require("./Session");
const fsm_1 = require("./fsm");
const Direction_1 = require("./Direction");
const TransitionSource_1 = require("./fsm/TransitionSource");
const TimerEventType_1 = require("./fsm/TimerEventType");
class OutboundState extends SessionStateBase_1.SessionStateBase {
constructor(state, session) {
super(state, session);
}
respondToInboundDeny(message) {
if (message.method != models_1.Method.Deny) {
return;
}
this.sendOutboundMessage(this.Session.newMessage()
.method(models_1.Method.Cancel)
.values({
deny: mxp_1.HeaderBooleanValue.Yes,
})
.build());
}
handleACKWindowRelevantMessage(message) {
if (message.method != models_1.Method.Deny && message.method != models_1.Method.Error) {
console.error("Only DENY and ERROR messages are ACK window relevant");
return;
}
this.Session.removeProgressingInstance(message.from.instanceId);
if (this.Session.shouldHandleACKRelevantMessageNow) {
return this.handleACKWindowRelevantMessageNow(message);
}
/**
* Force handling MXP DENY immediately if "denyall" header is NOT set to "NO".
*/
if (message.method == models_1.Method.Deny && !message.isDoNotDenyAllMessage()) {
return this.handleACKWindowRelevantMessageNow(message);
}
if (!this.Session.isAckWindowClosed) {
this.Session.addACKWindowDelayedMessage(message);
}
}
handleACKWindowRelevantMessageNow(message) {
switch (message.method) {
case models_1.Method.Deny:
this.respondToInboundDeny(message);
this.terminateBasedOnMessage(message, TerminationCause_1.TerminationCause.Denied);
return true;
case models_1.Method.Error:
this.terminateBasedOnMessage(message, TerminationCause_1.TerminationCause.Error);
return true;
}
}
}
class OutboundCreatedState extends OutboundState {
constructor(session) {
super(SessionState_1.SessionState.Created, session);
}
}
class OutboundInitiatingState extends OutboundState {
constructor(session) {
super(SessionState_1.SessionState.Initiating, session);
this.inviteEmitted = false;
this.onLocalSessionDescription = (sd) => {
if (this.Session.sessionKey && sd && !this.inviteEmitted)
this.emitInvite(sd, this.Session.sessionKey);
};
this.onSessionKey = () => {
if (this.Session.sessionKey &&
this.Session.localDescription &&
!this.inviteEmitted)
this.emitInvite(this.Session.localDescription, this.Session.sessionKey);
};
}
onEnter(_) {
this.Session.scheduleTimeout(this, this.timeoutRelativeToStart(this.CallACKWindowTimeoutMs), TimerEventType_1.TimerEventType.ACKWindowEnd);
this.Session.scheduleTimeout(this, this.CallProgressTimeoutMs, TimerEventType_1.TimerEventType.Termination);
this.Session.onSessionKey.add(this.onSessionKey);
}
onExit(_) {
this.Session.onSessionKey.remove(this.onSessionKey);
}
emitInvite(localOffer, key) {
this.sendOutboundMessage(this.buildInvite(localOffer, key));
this.inviteEmitted = true;
}
buildInvite(offer, sessionKey) {
return this.Session.newMessage()
.method(models_1.Method.Invite)
.body(models_1.Body.sdp(offer.asJson()))
.values({ key: utils_1.Base64Helper.encode(sessionKey.key) })
.build();
}
handleInboundMessage(message) {
switch (message.method) {
case models_1.Method.Ack:
this.Session.addProgressingInstance(message.from.instanceId);
this.transition(SessionState_1.SessionState.Progress, TransitionSource_1.TransitionSource.RemotePeerMessage);
this.Session.emitJsepEvent(message);
return true;
case models_1.Method.Join:
// out of order join, join before ack
this.Session.addProgressingInstance(message.from.instanceId);
this.Session.addPendingInboundMessage(message);
this.transition(SessionState_1.SessionState.Progress, TransitionSource_1.TransitionSource.RemotePeerMessage);
return true;
case models_1.Method.PeerEvent:
this.Session.addProgressingInstance(message.from.instanceId);
this.Session.emitJsepEvent(message);
return true;
case models_1.Method.Deny:
case models_1.Method.Error:
this.handleACKWindowRelevantMessage(message);
return true;
}
// TODO: Handle out-of-order DENY? If ACK was re-ordered or dropped?
return false;
}
onWillTerminate(source) {
if (source === TransitionSource_1.TransitionSource.RemotePeerMessage) {
return;
}
switch (this.terminationCause) {
case TerminationCause_1.TerminationCause.Error:
this.sendError(this.Session.error);
return;
case TerminationCause_1.TerminationCause.None:
this.terminationCause = TerminationCause_1.TerminationCause.Canceled;
this.sendOutboundMessage(this.Session.newMessage().method(models_1.Method.Cancel).build());
return;
}
}
}
class OutboundProgressState extends OutboundState {
constructor(session) {
super(SessionState_1.SessionState.Progress, session);
}
onEnter(_) {
this.Session.scheduleTimeout(this, this.timeoutRelativeToStart(this.CallSetupTimeoutMs), TimerEventType_1.TimerEventType.Termination);
this.Session.scheduleTimeout(this, this.timeoutRelativeToStart(this.CallACKWindowTimeoutMs), TimerEventType_1.TimerEventType.ACKWindowEnd);
}
handleInboundMessage(message) {
switch (message.method) {
case models_1.Method.Join:
this.sendJoined(message);
this.Session.emitJsepEvent(message);
this.transition(SessionState_1.SessionState.Established, TransitionSource_1.TransitionSource.RemotePeerMessage);
break;
case models_1.Method.PeerEvent:
case models_1.Method.Ack:
this.Session.addProgressingInstance(message.from.instanceId);
this.Session.emitJsepEvent(message);
break;
case models_1.Method.Deny:
case models_1.Method.Error:
this.handleACKWindowRelevantMessage(message);
break;
default:
return false;
}
return true;
}
onExit(t) {
super.onExit(t);
this.Session.closeACKWindowDroppingAllPendingMessages();
}
onWillTerminate(source) {
if (source === TransitionSource_1.TransitionSource.RemotePeerMessage) {
return;
}
switch (this.terminationCause) {
case TerminationCause_1.TerminationCause.Error:
this.sendError(this.Session.error);
return;
case TerminationCause_1.TerminationCause.None:
this.terminationCause = TerminationCause_1.TerminationCause.Canceled;
this.sendOutboundMessage(this.Session.newMessage().method(models_1.Method.Cancel).build());
return;
}
}
}
class OutboundSession extends Session_1.Session {
get isAckWindowClosed() {
return this._isAckWindowClosed;
}
get instancesToACK() {
return this._instancesToACK;
}
set instancesToACK(value) {
this._instancesToACK = value;
}
get shouldExpectMoreProgressingPeers() {
return this.instancesToACK.size > 0 && !this.isAckWindowClosed;
}
get shouldHandleACKRelevantMessageNow() {
return (!this.shouldExpectMoreProgressingPeers && !this.hasProgressingInstances);
}
get hasProgressingInstances() {
return this.progressingInstances.size > 0;
}
constructor(localPeer, id, scheduler) {
super(localPeer, id, Direction_1.Direction.Outbound, scheduler);
this.ackWindowDelayedMessages = new Array();
this.progressingInstances = new Set();
this._instancesToACK = new Set();
this._isAckWindowClosed = false;
const created = new OutboundCreatedState(this);
const initiating = new OutboundInitiatingState(this);
const progress = new OutboundProgressState(this);
const established = new EstablishedState_1.EstablishedState(this);
const terminated = new TerminatedState_1.TerminatedState(this);
this.setInitialState(created);
const t = this.transitions;
t.add(SessionState_1.SessionState.Created, SessionState_1.SessionState.Terminated, terminated);
t.add(SessionState_1.SessionState.Created, SessionState_1.SessionState.Initiating, initiating);
t.add(SessionState_1.SessionState.Initiating, SessionState_1.SessionState.Progress, progress);
t.add(SessionState_1.SessionState.Initiating, SessionState_1.SessionState.Terminated, terminated);
t.add(SessionState_1.SessionState.Progress, SessionState_1.SessionState.Terminated, terminated);
t.add(SessionState_1.SessionState.Progress, SessionState_1.SessionState.Established, established);
t.add(SessionState_1.SessionState.Established, SessionState_1.SessionState.Terminated, terminated);
}
configure(sessionKey) {
if (sessionKey.key.sigBytes <= 0)
throw new fsm_1.ArgumentError("Invalid key (empty)", (0, utils_1.nameof)(sessionKey));
if (null != this.sessionKey)
throw new fsm_1.InvalidOperationError("Session encryption key already set");
this.sessionKey = sessionKey;
}
onACKWindowTimeout(source, _) {
this._isAckWindowClosed = true;
if (source == this.fsmState) {
this.applyAckWindowDelayedMessages();
}
}
addProgressingInstance(instanceId) {
this.progressingInstances.add(instanceId);
}
removeProgressingInstance(instanceId) {
this.progressingInstances.delete(instanceId);
this.instancesToACK.delete(instanceId);
}
addACKWindowDelayedMessage(m) {
this.ackWindowDelayedMessages.push(m);
}
closeACKWindowDroppingAllPendingMessages() {
this.ackWindowDelayedMessages.splice(0);
this._isAckWindowClosed = true;
}
applyAckWindowDelayedMessages() {
this.ackWindowDelayedMessages.splice(0).forEach((m) => {
this.onInboundMessage(m);
});
}
}
exports.OutboundSession = OutboundSession;
//# sourceMappingURL=OutboundSession.js.map