utils/helpers.js

import Skylink from '../index';
import logger from '../logger';
import MESSAGES from '../messages';
import mediaStreamHelpers from '../media-stream/helpers/index';
import SkylinkSignalingServer from '../server-communication/signaling-server/index';
import { BROWSER_AGENT } from '../constants';

/**
 * @namespace UtilHelpers
 * @description Util helper functions
 * @private
 */

/**
 * Function that tests if an object is empty.
 * @param {Object} obj
 * @return {boolean}
 * @memberOf UtilHelpers
 */
export const isEmptyObj = (obj) => {
  const keys = Object.keys(obj);
  return keys.length === 0;
};

/**
 * Function that tests if an array is empty.
 * @param {Array} array
 * @returns {boolean}
 * @memberOf UtilHelpers
 */
export const isEmptyArray = array => array.length === 0;

/**
 * Function that tests if a string is an empty string.
 * @param string
 * @returns {boolean}
 * @memberOf UtilHelpers
 */
export const isEmptyString = string => string === '';

/**
 * Function that tests if type is 'Object'.
 * @param {*} param
 * @returns {boolean}
 * @memberOf UtilHelpers
 */
export const isAObj = param => typeof param === 'object' && param !== null;

/**
 * Function that tests if type is 'Null'.
 * @param {*} param
 * @returns {boolean}
 * @memberOf UtilHelpers
 */
export const isNull = param => typeof param === 'object' && param == null;

/**
 * Function that tests if type is 'Number'.
 * @param {*} param
 * @returns {boolean}
 * @memberOf UtilHelpers
 */
export const isANumber = param => typeof param === 'number';

/**
 * Function that tests if type is 'Function'.
 * @param {*} param
 * @returns {boolean}
 * @memberOf UtilHelpers
 */
export const isAFunction = param => typeof param === 'function';

/**
 * Function that tests if type is 'Boolean'.
 * @param {Object|boolean}
 * @returns {boolean}
 * @memberOf UtilHelpers
 */
export const isABoolean = obj => typeof obj !== 'undefined' && typeof obj === 'boolean';

/**
 * Function that tests if type is 'String'.
 * @param {*} param
 * @returns {boolean}
 * @memberOf UtilHelpers
 */
export const isAString = param => typeof param === 'string';

/**
 * Function that tests if a param is null, undefined or a string.
 * @param param
 * @param paramName
 * @param methodName
 * @returns {boolean}
 * @memberOf UtilHelpers
 */
export const getParamValidity = (param, paramName, methodName) => {
  let proceed = true;
  if (param === null || typeof param === 'undefined' || !isAString(param)) {
    logger.log.ERROR(`${methodName}: ${paramName} is null, undefined or not a string.`);
    proceed = false;
  }
  return proceed;
};

/**
 * Function that returns the Skylink state.
 * @param {SkylinkRoom.id} rid
 * @returns {SkylinkState|null} state
 * @memberOf UtilHelpers
 */
export const getStateByRid = (rid) => {
  const proceed = getParamValidity(rid, 'roomId', 'getStateByRid');
  if (proceed) {
    const states = Skylink.getSkylinkState();
    const roomKeys = Object.keys(states);
    let roomState = null;
    for (let i = 0; i < roomKeys.length; i += 1) {
      const key = roomKeys[i];
      if (key === rid) {
        roomState = states[key];
        break;
      }
    }
    return roomState;
  }
  logger.log.ERROR(`getRoomStateByRid: ${MESSAGES.ROOM_STATE.NOT_FOUND} - ${rid}`);
  return null;
};

/**
 * Function that returns the Skylink state.
 * @param {String} roomkey - The room key.
 * @returns {SkylinkState}
 * @memberOf UtilHelpers
 */
export const getStateByKey = roomkey => getStateByRid(roomkey);

/**
 * Function that returns the room state.
 * @param {String} roomName - The room name.
 * @returns {SkylinkState|null} - The room state.
 * @memberOf UtilHelpers
 */
export const getRoomStateByName = (roomName) => {
  const proceed = getParamValidity(roomName, 'roomName', 'getRoomStateByName');
  let matchedRoomState = null;
  if (proceed) {
    const state = Skylink.getSkylinkState();
    const roomKeys = Object.keys(state);
    for (let i = 0; i < roomKeys.length; i += 1) {
      const roomState = state[roomKeys[i]];
      if (roomState.room.roomName.toLowerCase() === roomName.toLowerCase()) {
        matchedRoomState = roomState;
        break;
      }
    }
  }
  if (!matchedRoomState) {
    logger.log.ERROR(`getRoomStateByName: ${MESSAGES.ROOM_STATE.NOT_FOUND} - ${roomName}`);
  }
  return matchedRoomState;
};

/**
 * Function that checks the agent version compatibility.
 * @param {String} agentVer
 * @param {String} requiredVer
 * @returns {boolean}
 * @memberOf UtilHelpers
 */
export const isLowerThanVersion = (agentVer, requiredVer) => {
  const partsA = (agentVer || '').split('.');
  const partsB = (requiredVer || '').split('.');

  for (let i = 0; i < partsB.length; i += 1) {
    if ((partsA[i] || '0') < (partsB[i] || '0')) {
      return true;
    }
  }
  return false;
};

/**
 * Disconnects from the signaling server.
 * @memberOf UtilHelpers
 */
export const disconnect = () => {
  try {
    new SkylinkSignalingServer().socket.disconnect();
  } catch (error) {
    logger.log.ERROR(error);
  }
};

/**
 * Function that generates an <a href="https://en.wikipedia.org/wiki/Universally_unique_identifier">UUID</a> (Unique ID).
 * @returns {String} Returns a generated UUID (Unique ID).
 * @memberOf UtilHelpers
 */
export const generateUUID = () => {
  /* eslint-disable no-bitwise */
  let d = new Date().getTime();
  const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
    const r = (d + Math.random() * 16) % 16 | 0;
    d = Math.floor(d / 16);
    return (c === 'x' ? r : (r && 0x7 | 0x8)).toString(16);
  });
  return uuid;
};

/**
 * Function that returns the getUserMedia stream when the user had not joined a room (is stateless)
 * @param {Object} options
 * @returns {Promise}
 * @memberOf UtilHelpers
 */
export const statelessGetUserMedia = options => new Promise((resolve, reject) => {
  const { navigator } = window;
  if (!options || !isAObj(options)) {
    reject(new Error(`${MESSAGES.MEDIA_STREAM.ERRORS.INVALID_GUM_OPTIONS} ${options}`));
  }

  navigator.mediaDevices.getUserMedia(options).then((stream) => {
    const streams = mediaStreamHelpers.splitAudioAndVideoStream(stream);
    resolve(streams);
  }).catch((error) => {
    reject(error);
  });
});

/**
 * Function that always returns are rejected Promise.
 * @param {String} errorMsg
 * @returns {Promise}
 * @memberOf UtilHelpers
 */
export const rejectPromise = errorMsg => new Promise((resolve, reject) => {
  reject(new Error(errorMsg));
});

/**
 * Function that updates the replaced state of the streams
 * @param {MediaStream} replacedStream
 * @param {MediaStream} newStream
 * @param {SkylinkState} state
 * @param {boolean} isReplaced
 * @memberOf UtilHelpers
 */
export const updateReplacedStreamInState = (replacedStream, newStream, state, isReplaced) => {
  const { streams, room } = state;
  const streamObjs = Object.values(streams.userMedia);
  for (let i = 0; i < streamObjs.length; i += 1) {
    if (streamObjs[i].id === replacedStream.id) {
      streamObjs[i].isReplaced = isReplaced;
      streamObjs[i].newStream = newStream;
    }
  }

  Skylink.setSkylinkState(state, room.id);
};

/**
 * Function that checks if the peerId exists on the peerConnection
 * @param {SkylinkRoom} room
 * @param {String} peerId
 * @returns {boolean}
 * @memberOf UtilHelpers
 */
export const isValidPeerId = (room, peerId) => {
  const state = Skylink.getSkylinkState(room.id);

  const { peerConnections } = state;
  const peerIds = Object.keys(peerConnections);

  let isValid = false;
  peerIds.forEach((validPeerId) => {
    if (validPeerId === peerId) {
      isValid = true;
    }
  });

  return isValid;
};

/**
 * Function that checks if a media stream has an audio track.
 * @param {MediaStream} stream
 * @returns {boolean}
 * @memberOf UtilHelpers
 */
export const hasAudioTrack = stream => stream.getAudioTracks().length > 0;

/**
 * Function that checks if a media stream has a video track.
 * @param {MediaStream} stream
 * @returns {boolean}
 * @memberOf UtilHelpers
 */
export const hasVideoTrack = stream => stream.getVideoTracks().length > 0;

/**
 * Function that checks the browser agent.
 * @param {String} agent
 * @returns {boolean}
 * @memberOf UtilHelpers
 */
export const isAgent = (agent) => {
  const { AdapterJS } = window;
  switch (agent) {
    case BROWSER_AGENT.CHROME:
      return AdapterJS.webrtcDetectedBrowser === BROWSER_AGENT.CHROME;
    case BROWSER_AGENT.SAFARI:
      return AdapterJS.webrtcDetectedBrowser === BROWSER_AGENT.SAFARI;
    case BROWSER_AGENT.FIREFOX:
      return AdapterJS.webrtcDetectedBrowser === BROWSER_AGENT.FIREFOX;
    default:
      logger.log.DEBUG(MESSAGES.UTILS.INVALID_BROWSER_AGENT);
      return false;
  }
};

/**
 * Function that checks the browser version.
 * @param {number} version
 * @returns {boolean}
 * @memberOf UtilHelpers
 */
export const isVersion = (version) => {
  const { AdapterJS } = window;
  return AdapterJS.webrtcDetectedVersion === version;
};

/**
 * Function that generates a timestamp in UNIX format.
 * @returns {number}
 * @memberOf UtilHelpers
 */
export const generateUNIXTimeStamp = () => Math.round(new Date().getTime() / 1000);

/**
 * Function that parses UNIX timestamp and returns timestamp in ISO string.
 * @param timestamp
 * @returns {string}
 * @memberOf UtilHelpers
 */
export const parseUNIXTimeStamp = timestamp => new Date(timestamp).toISOString();

/**
 * Function that generates a timestamp in ISO string format.
 * @returns {string}
 * @memberOf UtilHelpers
 */
export const generateISOStringTimesStamp = () => new Date().toISOString();