UNPKG

sinch-rtc

Version:

RTC JavaScript/Web SDK

264 lines 11.4 kB
"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