Source: room-module.js

'use strict';
var SESSION_STATUS = require('./constants').SESSION_STATUS;
var STREAM_STATUS = require('./constants').STREAM_STATUS;
var Promise = require('promise-polyfill');
const { v1: uuid_v1 } = require('uuid');
var util = require('./util');
var ROOM_REST_APP = "roomApp";
var Flashphoner = require('./flashphoner-core');

/**
 * Room api based on core api
 *
 * @namespace roomApi
 */

/**
 * Initialize connection
 *
 * @param {Object} options session options
 * @param {String} options.urlServer Server address in form of [ws,wss]://host.domain:port
 * @param {String} options.username Username to login with
 * @param {String} options.token JWT Token
 * @param {String} options.appKey Application Key
 * @param {Integer=} options.timeout Connection timeout in milliseconds [0]
 * @param {Integer=} options.pingInterval Server ping interval in milliseconds [0]
 * @param {Integer=} options.receiveProbes A maximum subsequental pings received missing count [0]
 * @param {Integer=} options.probesInterval Interval to check subsequental pings received [0]
 * @returns {roomApi.RoomSession}
 * @memberof roomApi
 * @method connect
 */
var appSession = function (options) {
    /**
     * Represents connection to room api app
     *
     * @namespace roomApi.RoomSession
     */
    var callbacks = {};
    var rooms = {};
    var username_ = options.username;
    var exports;
    var roomHandlers = {};
    var session = Flashphoner.createSession({
        urlServer: options.urlServer,
        mediaOptions: options.mediaOptions,
        appKey: (options.appKey && options.appKey.length != 0) ? options.appKey : ROOM_REST_APP,
        custom: {
            login: options.username,
            token: options.token
        },
        timeout: options.timeout || 0,
        pingInterval: options.pingInterval || 0,
        receiveProbes: options.receiveProbes || 0,
        probesInterval: options.probesInterval || 0
    }).on(SESSION_STATUS.ESTABLISHED, function (session) {
        if (callbacks[session.status()]) {
            callbacks[session.status()](exports);
        }
    }).on(SESSION_STATUS.APP_DATA, function (data) {
        var payload = data.payload;
        if (!payload || !payload.roomName) {
            console.error("Received app data does not contain 'payload' or 'payload.roomName' field. Received data: " + JSON.stringify(data));
            return;
        }
        var roomName = payload.roomName;
        if (roomHandlers[roomName]) {
            roomHandlers[roomName](payload);
        } else {
            console.warn("Failed to find room with name " + roomName);
        }
    }).on(SESSION_STATUS.DISCONNECTED, sessionDied).on(SESSION_STATUS.FAILED, sessionDied);

    //teardown helper
    function sessionDied(session) {
        if (callbacks[session.status()]) {
            callbacks[session.status()](exports);
        }
    }

    /**
     * Disconnect session
     *
     * @memberof roomApi.RoomSession
     * @inner
     */
    var disconnect = function () {
        session.disconnect();
    };

    /**
     * Get session status
     *
     * @returns {string} One of {@link Flashphoner.constants.SESSION_STATUS}
     * @memberof roomApi.RoomSession
     * @inner
     */
    var status = function () {
        return session.status();
    };

    /**
     * Get session id
     *
     * @returns {string} session id
     * @memberof roomApi.RoomSession
     * @inner
     */
    var id = function () {
        return session.id();
    };

    /**
     * Get server address
     *
     * @returns {string} Server url
     * @memberof roomApi.RoomSession
     * @inner
     */
    var getServerUrl = function () {
        return session.getServerUrl();
    };

    /**
     * Get session username
     *
     * @returns {string} username
     * @memberof roomApi.RoomSession
     * @inner
     */
    var username = function () {
        return username_;
    };

    /**
     * Get rooms
     *
     * @returns {roomApi.Room[]}
     * @memberof roomApi.RoomSession
     * @inner
     */
    var getRooms = function () {
        return util.copyObjectToArray(rooms);
    };


    /**
     * Add session event callback.
     *
     * @param {string} event One of {@link Flashphoner.constants.SESSION_STATUS} events
     * @param {RoomSession~eventCallback} callback Callback function
     * @returns {roomApi.RoomSession} Room Session
     * @throws {TypeError} Error if event is not specified
     * @throws {Error} Error if callback is not a valid function
     * @memberof roomApi.RoomSession
     * @inner
     */
    var on = function (event, callback) {
        if (!event) {
            throw new Error("Event can't be null", "TypeError");
        }
        if (!callback || typeof callback !== 'function') {
            throw new Error("Callback needs to be a valid function");
        }
        callbacks[event] = callback;
        return exports;
    };

    /**
     * Join room
     *
     * @param {Object} options Room options
     * @param {String} options.name Room name
     * @param {Boolean} options.record Record
     * @returns {roomApi.Room}
     * @memberof roomApi.RoomSession
     * @inner
     */
    var join = function (options) {
        /**
         * Room
         *
         * @namespace roomApi.Room
         */
        var room = {};
        var name_ = options.name;
        var record_ = options.record;
        var participants = {};
        var callbacks = {};
        var stateStreams = {};
        roomHandlers[name_] = function (data) {
            /**
             * Room participant
             *
             * @namespace roomApi.Room.Participant
             */
            var participant;
            if (data.name == "STATE") {
                if (data.info) {
                    for (var i = 0; i < data.info.length; i++) {
                        participantFromState(data.info[i]);
                    }
                    stateStreams = {};
                }
                if (callbacks["STATE"]) {
                    callbacks["STATE"](room);
                }
            } else if (data.name == "JOINED") {
                participants[data.info] = {
                    streams: {},
                    name: function () {
                        return data.info;
                    },
                    sendMessage: attachSendMessage(data.info),
                    getStreams: function () {
                        return util.copyObjectToArray(this.streams);
                    }
                };
                if (callbacks["JOINED"]) {
                    callbacks["JOINED"](participants[data.info]);
                }
            } else if (data.name == "LEFT") {
                participant = participants[data.info];
                delete participants[data.info];
                if (callbacks["LEFT"]) {
                    callbacks["LEFT"](participant);
                }
            } else if (data.name == "PUBLISHED") {
                participant = participants[data.info.login];
                participant.streams[data.info.name] = {
                    play: play(data.info.name),
                    stop: stop(data.info.name),
                    id: id(data.info.name),
                    streamName: function () {
                        return data.info.name
                    }
                };
                if (callbacks["PUBLISHED"]) {
                    callbacks["PUBLISHED"](participant);
                }
            } else if (data.name == "FAILED" || data.name == "UNPUBLISHED") {
                participant = participants[data.info.login];
                if (participant != null)
                    delete participant.streams[data.info.name];
            } else if (data.name == "MESSAGE") {
                if (callbacks["MESSAGE"]) {
                    callbacks["MESSAGE"]({
                        from: participants[data.info.from],
                        text: data.info.text
                    });
                }
            }
        };

        //participant creation helper
        function participantFromState(state) {
            var participant = {};

            if (state.hasOwnProperty("login")) {
                var login = state.login;
                var streamName = state.name;
                stateStreams[streamName] = {
                    /**
                     * Play participant stream
                     *
                     * @param {HTMLElement} display Div element stream should be displayed in
                     * @returns {Stream} Local stream object
                     * @memberof roomApi.Room.Participant.Stream
                     * @inner
                     */
                    play: play(streamName),
                    /**
                     * Stop participant stream
                     *
                     * @memberof roomApi.Room.Participant.Stream
                     * @inner
                     */
                    stop: stop(streamName),
                    /**
                     * Get participant stream id
                     *
                     * @returns {String} Stream id
                     * @memberof roomApi.Room.Participant.Stream
                     * @inner
                     */
                    id: id(streamName),
                    /**
                     * Get participant stream name
                     *
                     * @returns {String} Stream name
                     * @memberof roomApi.Room.Participant.Stream
                     * @inner
                     */
                    streamName: function () {
                        return streamName
                    }
                };

                if (participants[login] != null) {
                    participant = participants[login];
                } else {
                    participant = {
                        streams: {},
                        /**
                         * Get participant name
                         *
                         * @returns {String} Participant name
                         * @memberof roomApi.Room.Participant
                         * @inner
                         */
                        name: function () {
                            return login;
                        },
                        /**
                         * Send message to participant
                         *
                         * @param {String} message Message to send
                         * @param {Function} error Error callback
                         * @memberof roomApi.Room.Participant
                         * @inner
                         */
                        sendMessage: attachSendMessage(login),
                        /**
                         * Get participant streams
                         *
                         * @returns {Array<roomApi.Room.Participant.Stream>} Streams
                         * @memberof roomApi.Room.Participant
                         * @inner
                         */
                        getStreams: function () {
                            return util.copyObjectToArray(this.streams);
                        }
                    };
                    participants[participant.name()] = participant;
                }
                /**
                 * Room participant
                 *
                 * @namespace roomApi.Room.Participant.Stream
                 */
            } else {
                participant = {
                    streams: {},
                    name: function () {
                        return state;
                    },
                    sendMessage: attachSendMessage(state),
                    getStreams: function () {
                        return util.copyObjectToArray(this.streams);
                    }
                }
            }
            if (Object.keys(stateStreams).length != 0) {
                for (var k in stateStreams) {
                    if (stateStreams.hasOwnProperty(k)) {
                        participant.streams[k] = stateStreams[k];
                        delete stateStreams[k];
                    }
                }
            }
            participants[participant.name()] = participant;
            return participant;
        }

        /**
         * Get room name
         *
         * @returns {String} Room name
         * @memberof roomApi.Room
         * @inner
         */
        var name = function () {
            return name_;
        };

        /**
         * Leave room
         *
         * @returns {Promise<room>}
         * @memberof roomApi.Room
         * @inner
         */
        var leave = function () {
            return new Promise(function (resolve, reject) {
                sendAppCommand("leave", {name: name_}).then(function () {
                    cleanUp();
                    resolve(room);
                }, function () {
                    cleanUp();
                    reject(room);
                });

                function cleanUp() {
                    //clear streams
                    var streams = session.getStreams();
                    for (var i = 0; i < streams.length; i++) {
                        if (streams[i].name().indexOf(name_ + "-" + username_) !== -1 && streams[i].status() != STREAM_STATUS.UNPUBLISHED) {
                            streams[i].stop();
                        } else if (streams[i].name().indexOf(name_) !== -1 && streams[i].status() != STREAM_STATUS.STOPPED) {
                            streams[i].stop();
                        }
                    }
                    delete roomHandlers[name_];
                    delete rooms[name_];
                }
            });
        };

        /**
         * Publish stream inside room
         *
         * @param {Object} options Stream options
         * @param {string=} options.name Stream name
         * @param {Object=} options.constraints Stream constraints
         * @param {Boolean=} options.record Enable stream recording
         * @param {Boolean=} options.cacheLocalResources Display will contain local video after stream release
         * @param {HTMLElement} options.display Div element stream should be displayed in
         * @returns {Stream}
         * @memberof roomApi.Room
         * @inner
         */
        var publish = function (options) {
            options.name = (options.name) ? (name_ + "-" + username_ + "-" + uuid_v1().substr(0, 4) + "-" + options.name) : (name_ + "-" + username_ + "-" + uuid_v1().substr(0, 4));
            options.cacheLocalResources = (typeof options.cacheLocalResources === "boolean") ? options.cacheLocalResources : true;
            options.custom = {name: name_};
            var stream = session.createStream(options);
            stream.publish();
            return stream;
        };

        /**
         * Add room event callback.
         *
         * @param {string} event One of {@link roomApi.events} events
         * @param {roomApi.Room~eventCallback} callback Callback function
         * @returns {roomApi.Room} room
         * @throws {TypeError} Error if event is not specified
         * @throws {Error} Error if callback is not a valid function
         * @memberof roomApi.Room
         * @inner
         */
        var on = function (event, callback) {
            if (!event) {
                throw new Error("Event can't be null", "TypeError");
            }
            if (!callback || typeof callback !== 'function') {
                throw new Error("Callback needs to be a valid function");
            }
            callbacks[event] = callback;
            return room;
        };

        /**
         * Get participants
         *
         * @returns {roomApi.Room.Participant}
         * @memberof roomApi.Room
         * @inner
         */
        var getParticipants = function () {
            return util.copyObjectToArray(participants);
        };

        //participant helpers
        function play(streamName) {
            // Pass stream options to play #WCS-3445
            return function (display, options = {}) {
                var streamOptions = {
                    ...options,
                    name: streamName,
                    display: display,
                    custom: {name: name_}
                };
                var stream = session.createStream(streamOptions);
                stream.play();
                return stream;
            }
        }

        function stop(streamName) {
            return function () {
                var streams = session.getStreams();
                for (var i = 0; i < streams.length; i++) {
                    if (streams[i].name() == streamName && streams[i].status() != STREAM_STATUS.UNPUBLISHED) {
                        streams[i].stop();
                    }
                }
            }
        }

        function id(streamName) {
            return function () {
                var streams = session.getStreams();
                for (var i = 0; i < streams.length; i++) {
                    if (streams[i].name() == streamName)
                        return streams[i].id();
                }
            }
        }

        function attachSendMessage(recipientName) {
            return function (text, error) {
                var message = {
                    roomConfig: {
                        name: name_
                    },
                    to: recipientName,
                    text: text
                };
                sendAppCommand("sendMessage", message).then(function () {
                }, function () {
                    if (error) {
                        error();
                    }
                });
            }
        }

        //sendData helper
        function sendAppCommand(commandName, data) {
            var command = {
                command: commandName,
                options: data
            };
            return session.sendData(command);
        }

        sendAppCommand("join", {name: name_, record: record_}).then(function () {
        }, function (info) {
            if (callbacks["FAILED"]) {
                callbacks["FAILED"](room, info.info);
            }
        });
        room.name = name;
        room.leave = leave;
        room.publish = publish;
        room.getParticipants = getParticipants;
        room.on = on;
        rooms[name_] = room;
        return room;
    };


    exports = {
        disconnect: disconnect,
        id: id,
        getServerUrl: getServerUrl,
        username: username,
        status: status,
        getRooms: getRooms,
        join: join,
        on: on
    };
    return exports;
};

var events = {
    STATE: "STATE",
    JOINED: "JOINED",
    LEFT: "LEFT",
    PUBLISHED: "PUBLISHED",
    MESSAGE: "MESSAGE",
    FAILED: "FAILED"
};

module.exports = {
    connect: appSession,
    events: events,
    sdk: Flashphoner    
};