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());
    });
};
import SoftphoneSessionHandler from './softphone-session-handler';
import { SessionTypes, SdkErrorTypes } from '../types/enums';
import { createAndEmitSdkError, logPendingSession } from '../utils';
import ScreenShareSessionHandler from './screen-share-session-handler';
import VideoSessionHandler from './video-session-handler';
import ScreenRecordingSessionHandler from './screen-recording-session-handler';
import LiveMonitoringSessionHandler from './live-monitoring-session-handler';
const sessionHandlersToConfigure = [
    SoftphoneSessionHandler,
    VideoSessionHandler,
    ScreenShareSessionHandler,
    ScreenRecordingSessionHandler,
    LiveMonitoringSessionHandler
];
export class SessionManager {
    constructor(sdk) {
        this.sdk = sdk;
        /* use conversationId to index to account for persistent connections */
        this.pendingSessions = [];
        this.sessionHandlers = sessionHandlersToConfigure.map((ClassDef) => new ClassDef(this.sdk, this));
    }
    log(level, message, details) {
        this.sdk.logger[level](message, details);
    }
    get webrtcSessions() {
        return this.sdk._streamingConnection.webrtcSessions;
    }
    addAllowedSessionType(sessionType) {
        return __awaiter(this, void 0, void 0, function* () {
            const handler = this.getSessionHandler({ sessionType });
            return handler.enableHandler();
        });
    }
    removeAllowedSessionType(sessionType) {
        return __awaiter(this, void 0, void 0, function* () {
            const handler = this.getSessionHandler({ sessionType });
            return handler.disableHandler();
        });
    }
    handleConversationUpdate(update) {
        const sessions = this.getAllSessions();
        /* let each enabled handler process updates */
        this.sessionHandlers
            .filter(handler => !handler.disabled)
            .forEach(handler => handler.handleConversationUpdate(update, sessions.filter(s => s.sessionType === handler.sessionType)));
    }
    handleConversationUpdateRaw(update) {
        /* let each enabled handler process updates */
        this.sessionHandlers
            .filter(handler => !handler.disabled)
            .forEach(handler => handler.handleConversationUpdateRaw(update));
    }
    getPendingSession(params) {
        const session = this.pendingSessions
            .find(s => {
            // if sessionId is provided use that exclusively
            if (params.sessionId) {
                return s.id === params.sessionId;
            }
            let matchesSessionType = true;
            if (params.sessionType) {
                matchesSessionType = s.sessionType === params.sessionType;
            }
            return matchesSessionType && s.conversationId === params.conversationId;
        });
        return session;
    }
    removePendingSession(params) {
        const pendingSession = this.getPendingSession(params);
        if (!pendingSession) {
            this.sdk.logger.warn('failed to find pendingSession to remove', params);
            return;
        }
        this.pendingSessions = this.pendingSessions.filter(s => s !== pendingSession);
    }
    getSession(params) {
        var _a;
        let sessionTypesToSearch;
        if (params.sessionType) {
            sessionTypesToSearch = [params.sessionType];
        }
        else {
            const sessionTypes = Object.assign({}, SessionTypes);
            if (!params.searchScreenRecordingSessions) {
                delete sessionTypes[SessionTypes.screenRecording];
            }
            sessionTypesToSearch = Object.values(sessionTypes);
        }
        let session = this.getAllSessions()
            .find((s) => sessionTypesToSearch.includes(s.sessionType) && s.conversationId === params.conversationId);
        // search fake/shared softphone sessions
        if (!session && sessionTypesToSearch.includes(SessionTypes.softphone)) {
            const softphoneHandler = this.getSessionHandler({ sessionType: SessionTypes.softphone });
            session = (_a = Object.values(softphoneHandler.conversations).find((c) => c.conversationId === params.conversationId)) === null || _a === void 0 ? void 0 : _a.session;
        }
        if (!session) {
            throw createAndEmitSdkError.call(this.sdk, SdkErrorTypes.session, 'Unable to find session', params);
        }
        return session;
    }
    getSessionBySessionId(sessionId) {
        return this.getAllSessions().find(s => s.id === sessionId);
    }
    getAllActiveSessions() {
        return this.webrtcSessions.getAllSessions()
            .filter((session) => session.state === 'active');
    }
    getAllActiveConversations() {
        return [].concat(...this.sessionHandlers.map(handler => handler.getActiveConversations()));
    }
    getAllSessions() {
        return this.webrtcSessions.getAllSessions();
    }
    getSessionHandler(params) {
        var _a;
        let handler;
        if (params.sessionType || ((_a = params.sessionInfo) === null || _a === void 0 ? void 0 : _a.sessionType)) {
            handler = this.sessionHandlers.find((handler) => { var _a; return handler.sessionType == (params.sessionType || ((_a = params.sessionInfo) === null || _a === void 0 ? void 0 : _a.sessionType)); });
        }
        else {
            const fromJid = (params.sessionInfo && params.sessionInfo.fromJid) || (params.jingleSession && params.jingleSession.peerID);
            if (!fromJid) {
                throw createAndEmitSdkError.call(this.sdk, SdkErrorTypes.generic, 'getSessionHandler was called without any identifying information', params);
            }
            handler = this.sessionHandlers.find((handler) => handler.shouldHandleSessionByJid(fromJid));
        }
        if (!handler) {
            throw createAndEmitSdkError.call(this.sdk, SdkErrorTypes.session, 'Failed to find session handler for session', params);
        }
        return handler;
    }
    startSession(startSessionParams) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!this.sdk.connected) {
                throw createAndEmitSdkError.call(this.sdk, SdkErrorTypes.session, 'A session cannot be started as streaming client is not yet connected', { sessionType: startSessionParams.sessionType });
            }
            const handler = this.getSessionHandler({ sessionType: startSessionParams.sessionType });
            if (handler.disabled) {
                throw createAndEmitSdkError.call(this.sdk, SdkErrorTypes.generic, 'Cannot start a session with a disabled session handler', { startSessionParams, allowedSessionTypes: this.sdk._config.allowedSessionTypes });
            }
            return handler.startSession(startSessionParams);
        });
    }
    /**
     * Update the outgoing media for a session.
     *
     * @param options for updating outgoing media
     */
    updateOutgoingMedia(options) {
        return __awaiter(this, void 0, void 0, function* () {
            const session = options.session || this.getSession({ conversationId: options.conversationId });
            const handler = this.getSessionHandler({ jingleSession: session });
            return handler.updateOutgoingMedia(session, options);
        });
    }
    updateOutgoingMediaForAllSessions(options) {
        return __awaiter(this, void 0, void 0, function* () {
            const opts = options || {
                videoDeviceId: this.sdk._config.defaults.videoDeviceId,
                audioDeviceId: this.sdk._config.defaults.audioDeviceId
            };
            const sessions = this.getAllActiveSessions();
            this.log('info', 'Updating outgoing deviceId(s) for all active sessions', {
                sessionInfos: sessions.map(s => ({ sessionId: s.id, conversationId: s.conversationId, sessionType: s.sessionType })),
                videoDeviceId: opts.videoDeviceId,
                audioDeviceId: opts.audioDeviceId
            });
            const promises = sessions.map(session => {
                return this.updateOutgoingMedia(Object.assign({ session }, opts));
            });
            yield Promise.all(promises);
        });
    }
    updateAudioVolume(volume) {
        const sessions = this.getAllActiveSessions();
        this.log('info', 'Updating volume for all active sessions', {
            sessionInfos: sessions.map(s => ({ sessionId: s.id, conversationId: s.conversationId })),
            volume
        });
        sessions.forEach((session) => {
            const handler = this.getSessionHandler({ jingleSession: session });
            handler.updateAudioVolume(session, volume);
        });
    }
    updateOutputDeviceForAllSessions(outputDeviceId) {
        return __awaiter(this, void 0, void 0, function* () {
            const sessions = this.getAllActiveSessions().filter(s => s.sessionType !== SessionTypes.acdScreenShare);
            const _outputDeviceId = this.sdk.media.getValidDeviceId('audiooutput', outputDeviceId, ...sessions) || '';
            const ids = sessions.map(s => ({ sessionId: s.id, conversationId: s.conversationId, sessionType: s.sessionType }));
            if (typeof outputDeviceId === 'string' && _outputDeviceId !== outputDeviceId) {
                this.log('warn', 'Output deviceId not found. Not updating output media', { sessions: ids, outputDeviceId });
                return;
            }
            this.log('info', 'Updating output deviceId for all active sessions', {
                sessions: ids,
                outputDeviceId: _outputDeviceId
            });
            const promises = sessions.map(session => {
                const handler = this.getSessionHandler({ jingleSession: session });
                return handler.updateOutputDevice(session, _outputDeviceId);
            });
            yield Promise.all(promises);
        });
    }
    addOrReplaceTrackOnSession(track, session) {
        return __awaiter(this, void 0, void 0, function* () {
            const handler = this.getSessionHandler({ jingleSession: session });
            yield handler.addReplaceTrackToSession(session, track);
        });
    }
    /**
     * Event handler for pending webrtc-sessions.
     * @param sessionInfo pending webrtc-session info
     */
    onPropose(sessionInfo) {
        return __awaiter(this, void 0, void 0, function* () {
            const handler = this.getSessionHandler({ sessionInfo });
            if (handler.disabled) {
                return;
            }
            logPendingSession(this.sdk.logger, 'onPendingSession', sessionInfo);
            const { conversationId, sessionType } = sessionInfo;
            const existingSession = this.getPendingSession({ conversationId, sessionType });
            if (existingSession) {
                if (existingSession.sessionId !== sessionInfo.sessionId) {
                    this.log('info', `found an existingSession matching propose's conversationId, updating existingSession.sessionId to match`, { existingSessionId: existingSession.sessionId, proposeSessionId: sessionInfo.sessionId, conversationId: sessionInfo.conversationId });
                    existingSession.sessionId = sessionInfo.sessionId;
                    existingSession.id = sessionInfo.id;
                    if (existingSession.accepted) {
                        this.log('info', `updated existingSession was already accepted, "proceeding" again`, { sessionId: sessionInfo.sessionId, conversationId: sessionInfo.conversationId });
                        return handler.proceedWithSession(existingSession);
                    }
                }
                logPendingSession(this.sdk.logger, 'duplicate session invitation, ignoring', sessionInfo);
                return;
            }
            const pendingSession = {
                id: sessionInfo.sessionId,
                sessionId: sessionInfo.sessionId,
                autoAnswer: sessionInfo.autoAnswer,
                conversationId: sessionInfo.conversationId,
                sessionType: handler.sessionType,
                originalRoomJid: sessionInfo.originalRoomJid,
                fromUserId: sessionInfo.fromUserId,
                toJid: sessionInfo.toJid,
                fromJid: sessionInfo.fromJid,
                privAnswerMode: sessionInfo.privAnswerMode,
                meetingId: sessionInfo.meetingId
            };
            this.pendingSessions.push(pendingSession);
            yield handler.handlePropose(pendingSession);
        });
    }
    /**
     * If we get this event from streaming-client, that means the session was
     *  canceled. We no longer care if it was a LA of 1 session or not
     *  because streaming-client will only emit events for actual sessions
     *  and not our psuedo-events we emit for LA of 1 conversation
     *  events.
     * @param sessionId session id canceled by streaming client
     */
    onCancelPendingSession(sessionId, conversationId) {
        const pendingSession = this.getPendingSession({ sessionId, conversationId });
        if (!pendingSession) {
            return;
        }
        this.sdk.emit('cancelPendingSession', { sessionId, conversationId: pendingSession.conversationId });
        this.removePendingSession(pendingSession);
    }
    /**
     * If we get this event from streaming-client, that means the session was
     *  canceled. We no longer care if it was a LA of 1 session or not
     *  because streaming-client will only emit events for actual sessions
     *  and not our psuedo-events we emit for LA of 1 conversation
     *  events.
     * @param sessionId session id canceled by streaming client
     */
    onHandledPendingSession(sessionId, conversationId) {
        const pendingSession = this.getPendingSession({ sessionId, conversationId });
        if (!pendingSession) {
            return;
        }
        this.sdk.emit('handledPendingSession', { sessionId, conversationId: pendingSession.conversationId });
        this.removePendingSession(pendingSession);
    }
    proceedWithSession(params) {
        return __awaiter(this, void 0, void 0, function* () {
            const pendingSession = this.getPendingSession(params);
            if (!pendingSession) {
                throw createAndEmitSdkError.call(this.sdk, SdkErrorTypes.session, 'Could not find a pendingSession matching accept params', { params });
            }
            const sessionHandler = this.getSessionHandler({ sessionType: pendingSession.sessionType });
            yield sessionHandler.proceedWithSession(pendingSession);
        });
    }
    rejectPendingSession(params) {
        return __awaiter(this, void 0, void 0, function* () {
            const pendingSession = this.getPendingSession(params);
            if (!pendingSession) {
                throw createAndEmitSdkError.call(this.sdk, SdkErrorTypes.session, 'Could not find a pendingSession', { params });
            }
            const sessionHandler = this.getSessionHandler({ sessionType: pendingSession.sessionType });
            yield sessionHandler.rejectPendingSession(pendingSession);
        });
    }
    onSessionInit(session) {
        return __awaiter(this, void 0, void 0, function* () {
            const sessionHandler = this.getSessionHandler({ jingleSession: session });
            if (sessionHandler.disabled) {
                return;
            }
            session.sessionType = sessionHandler.sessionType;
            return sessionHandler.handleSessionInit(session);
        });
    }
    acceptSession(params) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!params || !params.conversationId) {
                throw createAndEmitSdkError.call(this.sdk, SdkErrorTypes.invalid_options, 'A conversationId is required for acceptSession');
            }
            const session = this.getSession({ conversationId: params.conversationId, sessionType: params.sessionType, searchScreenRecordingSessions: true });
            if (session._alreadyAccepted) {
                this.log('info', 'acceptSession called for session that was already accepted, not accepting again.', { sessionId: session.id, conversationId: session.conversationId });
                return;
            }
            session._alreadyAccepted = true;
            const sessionHandler = this.getSessionHandler({ jingleSession: session });
            return sessionHandler.acceptSession(session, params);
        });
    }
    endSession(params) {
        return __awaiter(this, void 0, void 0, function* () {
            const conversationId = params.conversationId;
            if (!conversationId) {
                throw createAndEmitSdkError.call(this.sdk, SdkErrorTypes.session, 'Unable to end session: must provide a conversationId.');
            }
            const session = this.getSession(params);
            const sessionHandler = this.getSessionHandler({ jingleSession: session });
            return sessionHandler.endSession(conversationId, session, params.reason);
        });
    }
    forceTerminateSession(sessionId, reason) {
        return __awaiter(this, void 0, void 0, function* () {
            const session = this.getAllSessions().find((s) => s.id === sessionId);
            if (!session) {
                throw createAndEmitSdkError.call(this.sdk, SdkErrorTypes.generic, 'Failed to find session by sessionId', { sessionId });
            }
            const handler = this.getSessionHandler({ sessionType: session.sessionType });
            return handler.forceEndSession(session, reason);
        });
    }
    setVideoMute(params) {
        return __awaiter(this, void 0, void 0, function* () {
            const session = this.getSession({ conversationId: params.conversationId });
            const handler = this.getSessionHandler({ sessionType: session.sessionType });
            yield handler.setVideoMute(session, params);
        });
    }
    setAudioMute(params) {
        return __awaiter(this, void 0, void 0, function* () {
            const session = this.getSession({ conversationId: params.conversationId });
            const handler = this.getSessionHandler({ sessionType: session.sessionType });
            yield handler.setAudioMute(session, params);
        });
    }
    setConversationHeld(params) {
        return __awaiter(this, void 0, void 0, function* () {
            const session = this.getSession({ conversationId: params.conversationId });
            const handler = this.getSessionHandler({ sessionType: session.sessionType });
            yield handler.setConversationHeld(session, params);
        });
    }
    validateOutgoingMediaTracks() {
        return __awaiter(this, void 0, void 0, function* () {
            const sessions = this.getAllActiveSessions();
            const { videoDevices, audioDevices, outputDevices, hasOutputDeviceSupport } = this.sdk.media.getState();
            const updates = new Map();
            const promises = [];
            let updateOutputDeviceForAllSessions = false;
            /* find all sessions that ned to be updated */
            for (const session of sessions) {
                const trackIdsToIgnore = [];
                /* if we have a video session with a screenShareStream */
                if (session._screenShareStream) {
                    trackIdsToIgnore.push(...session._screenShareStream.getTracks().map((track) => track.id));
                }
                session.peerConnection.getSenders()
                    .filter((sender) => sender.track && !trackIdsToIgnore.includes(sender.track.id))
                    .map(s => s.track)
                    .forEach(track => {
                    /* senders won't be using output devices so we don't need to worry about those */
                    const deviceExists = !!(track.kind === 'video' ? videoDevices : audioDevices).find(d => d.label === track.label && d.kind.slice(0, 5) === track.kind);
                    if (deviceExists) {
                        this.log('debug', 'sessions outgoing track still has available device', { conversationId: session.conversationId, sessionId: session.id, sessionType: session.sessionType, kind: track.kind, deviceLabel: track.label });
                        return;
                    }
                    const currVal = updates.get(session.id) || {};
                    currVal[track.kind] = true;
                    updates.set(session.id, currVal);
                    this.log('info', 'session lost media device and will attempt to switch devices', { conversationId: session.conversationId, sessionId: session.id, sessionType: session.sessionType, kind: track.kind, deviceLabel: track.label });
                });
                /* check output device */
                if (hasOutputDeviceSupport && session._outputAudioElement) {
                    const deviceExists = outputDevices.find(d => d.deviceId === session._outputAudioElement.sinkId);
                    if (!deviceExists) {
                        updateOutputDeviceForAllSessions = true;
                        this.log('info', 'session lost output device and will attempt to switch device', { conversationId: session.conversationId, sessionId: session.id, kind: 'output', sessionType: session.sessionType });
                    }
                }
            }
            /* if there are not sessions to updated, log and we are done */
            if (!updates.size && !updateOutputDeviceForAllSessions) {
                this.log('debug', 'no active sessions have outgoing tracks that need to have the device updated', { sessionIds: sessions.map(s => s.id) });
                return;
            }
            /* update the sessions */
            for (const [sessionId, mediaToUpdate] of updates) {
                const jingleSession = this.getSessionBySessionId(sessionId);
                const opts = { session: jingleSession };
                const handler = this.getSessionHandler({ jingleSession });
                /* if our video needs to be updated */
                if (mediaToUpdate.video) {
                    /* if we have devices, let the sdk figure out which to switch to */
                    if (videoDevices.length) {
                        opts.videoDeviceId = true;
                    }
                    else {
                        this.log('warn', 'no available video devices to switch to. setting video to mute for session', { conversationId: jingleSession.conversationId, sessionId, kind: 'video', sessionType: jingleSession.sessionType });
                        promises.push(handler.setVideoMute(jingleSession, { mute: true, conversationId: jingleSession.conversationId }));
                    }
                }
                /* if our audio needs to be updated */
                if (mediaToUpdate.audio) {
                    if (audioDevices.length) {
                        opts.audioDeviceId = true;
                    }
                    else {
                        this.log('warn', 'no available audio devices to switch to. setting audio to mute for session', { conversationId: jingleSession.conversationId, sessionId, kind: 'audio', sessionType: jingleSession.sessionType });
                        promises.push(handler.setAudioMute(jingleSession, { mute: true, conversationId: jingleSession.conversationId }));
                        const senders = handler.getSendersByTrackType(jingleSession, 'audio')
                            .filter((sender) => sender.track);
                        senders.forEach((sender) => {
                            sender.track.stop();
                            promises.push(handler.removeMediaFromSession(jingleSession, sender));
                            jingleSession._outboundStream.removeTrack(sender.track);
                        });
                    }
                }
                /* update outgoing media */
                if (opts.videoDeviceId || opts.audioDeviceId) {
                    promises.push(this.sdk.updateOutgoingMedia(opts));
                }
            }
            /* if the output device needs to change, update all sessions */
            if (updateOutputDeviceForAllSessions) {
                promises.push(this.updateOutputDeviceForAllSessions(true));
            }
            return Promise.all(promises);
        });
    }
}
//# sourceMappingURL=session-manager.js.map