Source: sfu-extended.js

/**
 * @namespace FlashphonerSFUExtended
 */


const ws = require("./ws");
const messaging = require("./messaging");
const roomApi = require("./room");
const constants = require("./constants");
const SFU_STATE = constants.SFU_STATE;
const SFU_EVENT = constants.SFU_EVENT;
const SFU_INTERNAL = constants.SFU_INTERNAL_API;
const sfu = {};
let rooms = {};
let connection = ws.createConnection();
let server;
const callbacks = {};

let state = SFU_STATE.NEW;
let connectionConfig;
let user;
let im;
let pendingUserList = [];
let pendingUserCalendar = [];


function setupConnection(connection) {
    connection.onMessage = function(name, msg){
        switch (name) {
            case SFU_INTERNAL.DEFAULT_METHOD:
                //filter messages
                if (msg[0].type === SFU_INTERNAL.MESSAGE) {
                    notify(SFU_EVENT.MESSAGE, msg[0].message);
                } else if (msg[0].type === SFU_INTERNAL.MESSAGE_STATE) {
                    im.onMessageState(msg);
                } else if (msg[0].type === SFU_INTERNAL.USER_LIST) {
                    while (pendingUserList.length > 0) {
                        const promise = pendingUserList.pop();
                        promise.resolve(msg[0].list);
                    }
                    notify(SFU_EVENT.USER_LIST, msg[0].list);
                } else if (msg[0].type === SFU_INTERNAL.USER_CALENDAR) {
                    while (pendingUserCalendar.length > 0) {
                        const promise = pendingUserCalendar.pop();
                        promise.resolve(msg[0].calendar);
                    }
                    notify(SFU_EVENT.USER_CALENDAR, msg[0].calendar);
                } else if (msg[0].roomName && msg[0].roomName.length > 0) {
                    //room event
                    const room = rooms[msg[0].roomName];
                    if (room) {
                        room.processEvent(msg[0]);
                    }
                } else {
                    notify(msg[0].type, msg[0]);
                }
                break;
            case "failed":
                notify(constants.SFU_EVENT.FAILED, msg[0]);
                break;
        }
    };

    connection.onClose = function(e) {
        state = SFU_STATE.DISCONNECTED;
        disconnect();
        notify(SFU_EVENT.DISCONNECTED, e);
    };

    connection.onError = function(e) {
        state = SFU_STATE.FAILED;
        notify(SFU_STATE.FAILED, e);
    };

    im = messaging.create({
        connection: connection
    });
}
/**
 * Connect to server.
 *
 * @param {Object} options SFU options
 * @param {String} options.url Server url
 * @param {String} options.username Username
 * @param {String} options.password Password
 * @param {String=} options.nickname Participant's nickname
 * @returns {Promise<void|Error>} Promise which resolves upon connect
 * @throws {TypeError} Error if no options provided
 * @memberof FlashphonerSFUExtended
 */
const connect = function(options) {
    if (!options) {
        throw new TypeError("No options provided");
    }
    setupConnection(connection);
    server = new URL(options.url).hostname;
    connectionConfig = {
        url: options.url,
        appName: SFU_INTERNAL.Z_APP,
        custom: {
            username: options.username,
            password: options.password,
            nickname: options.nickname
        }
    };
    return new Promise(function(resolve, reject) {
        if (state !== SFU_STATE.NEW && state !== SFU_STATE.DISCONNECTED && state !== SFU_STATE.FAILED) {
            reject(new Error("Can't connect with the state " + state));
            return;
        }
        connection.connect(connectionConfig).then(function (connectionConfig) {
            user = Object.freeze({
                username: connectionConfig[0].sipLogin,
                nickname: connectionConfig[0].sipVisibleName
            });
            state = SFU_STATE.AUTHENTICATED;
            notify(SFU_EVENT.CONNECTED);
            resolve();
        }, function (e) {
            state = SFU_STATE.FAILED;
            notify(SFU_EVENT.FAILED, e);
            reject(e);
        });
    });
};

/**
 * Send message
 * @param {Object} msg Message
 * @param {String} msg.to Recipient's id
 * @param {String} msg.body Message body
 * @returns {Promise} Promise will resolve upon message delivery and reject if delivery was unsuccessful
 * @throws {Error} error if api isn't connected
 * @memberOf FlashphonerSFUExtended
 */
const sendMessage = function(msg) {
    if (state !== SFU_STATE.AUTHENTICATED) {
        throw new Error("Can't send message while in " + state + " state");
    }
    return im.sendMessage(msg);
};

/**
 * Fetch available user list from server
 * @returns {Promise<Array<FlashphonerSFUExtended.UserListEntry>>}
 * @throws {Error} error if api isn't connected
 * @memberOf FlashphonerSFUExtended

 */
const getUserList = function() {
    if (state !== SFU_STATE.AUTHENTICATED) {
        throw new Error("Can't get user list while in " + state + " state");
    }
    return new Promise(function (resolve, reject){
        if (state !== SFU_STATE.AUTHENTICATED) {
            reject(new Error("Can't get user list while in " + state + " state"));
            return;
        }
        if (pendingUserList.length > 0) {
            pendingUserList.push({resolve: resolve, reject: reject});
            return;
        } else {
            pendingUserList.push({resolve: resolve, reject: reject});
        }
        connection.send(constants.SFU_INTERNAL_API.GET_USER_LIST);
    });
};

/**
 * Fetch available calendar from server
 * @returns {Promise<Array<FlashphonerSFUExtended.UserCalendar>>}
 * @throws {Error} error if api isn't connected
 * @memberOf FlashphonerSFUExtended

 */
const getUserCalendar = function() {
    if (state !== SFU_STATE.AUTHENTICATED) {
        throw new Error("Can't get user calendar while in " + state + " state");
    }
    return new Promise(function (resolve, reject){
        if (state !== SFU_STATE.AUTHENTICATED) {
            reject(new Error("Can't get user calendar while in " + state + " state"));
            return;
        }
        if (pendingUserCalendar.length > 0) {
            pendingUserCalendar.push({resolve: resolve, reject: reject});
            return;
        } else {
            pendingUserCalendar.push({resolve: resolve, reject: reject});
        }
        connection.send(constants.SFU_INTERNAL_API.GET_USER_CALENDAR);
    });
};

/**
 * Add event to the calendar
 * @param event {FlashphonerSFUExtended.UserCalendarEvent}
 * @throws {Error} error if api isn't connected
 * @memberOf FlashphonerSFUExtended

 */
const addCalendarEvent = function(event) {
    if (state !== SFU_STATE.AUTHENTICATED) {
        throw new Error("Can't add calendar event while in " + state + " state");
    }
    connection.send(constants.SFU_INTERNAL_API.ADD_CALENDAR_EVENT, event);
};

/**
 * Remove event from the calendar
 * @param event {FlashphonerSFUExtended.UserCalendarEvent}
 * @throws {Error} error if api isn't connected
 * @memberOf FlashphonerSFUExtended

 */
const removeCalendarEvent = function(event) {
    if (state !== SFU_STATE.AUTHENTICATED) {
        throw new Error("Can't remove calendar event while in " + state + " state");
    }
    connection.send(constants.SFU_INTERNAL_API.REMOVE_CALENDAR_EVENT, event);
};

/**
 * Create room
 *
 * @param {Object} options Room options
 * @param {String} options.name Room name
 * @param {String} options.pin Room's pin
 * @param {Object} options.pc Peer connection
 * @returns {FlashphonerSFU.Room} Room
 * @throws {TypeError} Error if no options provided
 * @throws {Error} error if api isn't connected
 * @memberof FlashphonerSFUExtended
 */
const room = function(options) {
    if (!options) {
        throw new TypeError("No options provided");
    }
    if (state !== SFU_STATE.AUTHENTICATED) {
        throw new Error("Can't create room while in " + state + " state");
    }
    const opt = {
        connection: connection,
        name: options.name,
        pin: options.pin,
        pc: options.pc
    };
    const exports = roomApi.room(opt);
    rooms[options.name] = exports;
    const cleanup = function() {
        rooms[options.name].pc().close();
        rooms[options.name].pc().dispatchEvent(new Event("connectionstatechange"));
        delete rooms[options.name];
    };
    exports.on(constants.SFU_ROOM_EVENT.LEFT, function(participant){
        if (participant.name === user.nickname) {
            cleanup();
        }
    }).on(constants.SFU_ROOM_EVENT.EVICTED, function(participant) {
        if (participant.name === user.nickname) {
            cleanup();
        }
    }).on(constants.SFU_ROOM_EVENT.FAILED, cleanup
    ).on(constants.SFU_ROOM_EVENT.OPERATION_FAILED, function(e) {
        if (constants.SFU_OPERATIONS.ROOM_JOIN === e.operation) {
            cleanup();
        }
    });
    return exports;
};

/**
 * FlashphonerSFUExtended event callback.
 *
 * @callback FlashphonerSFUExtended~eventCallback
 * @param {FlashphonerSFUExtended} sdk instance FlashphonerSFUExtended
 */

/**
 * Add session event callback.
 *
 * @param {String} event One of {@link FlashphonerSFU.SFU_EVENT} events
 * @param {FlashphonerSFUExtended~eventCallback} callback Callback function
 * @returns {FlashphonerSFUExtended} SDK instance callback was attached to
 * @throws {TypeError} Error if event is not specified
 * @throws {Error} Error if callback is not a valid function
 * @memberOf FlashphonerSFUExtended
 */
const on = function (event, callback) {
    if (!event) {
        throw new TypeError("Event can't be null");
    }
    if (!callback || typeof callback !== "function") {
        throw new Error("Callback needs to be a valid function");
    }
    if (!callbacks[event]) {
        callbacks[event] = [];
    }
    callbacks[event].push(callback);
    return sfu;
};

const notify = function(event, msg) {
    if (callbacks[event]) {
        for (const callback of callbacks[event]) {
            callback(msg);
        }
    }
};

/**
 * Disconnect sfu from the server
 * @memberOf FlashphonerSFUExtended
 */
const disconnect = function() {
    for (const [key, value] of Object.entries(rooms)) {
        value.leaveRoom();
    }
    user = undefined;
    pendingUserList = [];
    pendingUserCalendar = [];
    connection.close();
    connection = ws.createConnection();
    state = SFU_STATE.DISCONNECTED;
    rooms = {};
};

sfu.on = on;
sfu.connect = connect;
sfu.sendMessage = sendMessage;
sfu.getUserList = getUserList;
sfu.getUserCalendar = getUserCalendar;
sfu.addCalendarEvent = addCalendarEvent;
sfu.removeCalendarEvent = removeCalendarEvent;
sfu.room = room;


/**
 * @typedef {Object} SFUUser
 * @property username {String} username
 * @property nickname {String} nickname
 * @memberOf FlashphonerSFUExtended
 */

/**
 * Get current user
 * @returns {FlashphonerSFUExtended.SFUUser} Returns current logged in user
 * @memberOf FlashphonerSFUExtended
 */
sfu.user = function(){
    return user;
};
/**
 * Get hostname of the server in use
 * @returns {String} Current server's hostname
 * @memberOf FlashphonerSFUExtended
 */
sfu.server = function() {
    return server;
};
sfu.disconnect = disconnect;

/**
 * Get sfu state
 * @returns {FlashphonerSFU.SFU_STATE} Current sfu state
 */
sfu.state = function() {
    return state;
};

sfu.constants = constants;


module.exports = sfu;