'use strict'; /** * Create a {@link Deferred}. * @returns {Deferred} */ function defer() { var deferred = {}; deferred.promise = new Promise(function(resolve, reject) { deferred.resolve = resolve; deferred.reject = reject; }); return deferred; } /** * Copy a method from a `source` prototype onto a `wrapper` prototype. Invoking * the method on the `wrapper` prototype will invoke the corresponding method * on an instance accessed by `target`. * @param {object} source * @param {object} wrapper * @param {string} target * @param {string} methodName * @returns {undefined} */ function delegateMethod(source, wrapper, target, methodName) { if (methodName in wrapper) { // Skip any methods already set. return; } else if (methodName.match(/^on[a-z]+$/)) { // Skip EventHandlers (these are handled in the constructor). return; } var type; try { type = typeof source[methodName]; } catch (error) { // NOTE(mroberts): Attempting to check the type of non-function members // on the prototype throws an error for some types. } if (type !== 'function') { // Skip non-function members. return; } /* eslint no-loop-func:0 */ wrapper[methodName] = function() { return this[target][methodName].apply(this[target], arguments); }; } /** * Copy methods from a `source` prototype onto a `wrapper` prototype. Invoking * the methods on the `wrapper` prototype will invoke the corresponding method * on an instance accessed by `target`. * @param {object} source * @param {object} wrapper * @param {string} target * @returns {undefined} */ function delegateMethods(source, wrapper, target) { for (var methodName in source) { delegateMethod(source, wrapper, target, methodName); } } /** * Finds the items in list1 that are not in list2. * @param {Array<*>|Map<*>|Set<*>} list1 * @param {Array<*>|Map<*>|Set<*>} list2 * @returns {Set} */ function difference(list1, list2) { list1 = Array.isArray(list1) ? new Set(list1) : new Set(list1.values()); list2 = Array.isArray(list2) ? new Set(list2) : new Set(list2.values()); var difference = new Set(); list1.forEach(function(item) { if (!list2.has(item)) { difference.add(item); } }); return difference; } /** * Map a list to an array of arrays, and return the flattened result. * @param {Array<*>|Set<*>|Map<*>} list * @param {function(*): Array<*>} mapFn * @returns Array<*> */ function flatMap(list, mapFn) { var listArray = list instanceof Map || list instanceof Set ? Array.from(list.values()) : list; return listArray.reduce(function(flattened, item) { var mapped = mapFn(item); return flattened.concat(mapped); }, []); } /** * Get the browser's user agent, if available. * @returns {?string} */ function getUserAgent() { return typeof navigator !== 'undefined' && typeof navigator.userAgent === 'string' ? navigator.userAgent : null; } /** * Guess the browser. * @param {string} [userAgent=navigator.userAgent] * @returns {?string} browser - "chrome", "firefox", "safari", or null */ function guessBrowser(userAgent) { if (typeof userAgent === 'undefined') { userAgent = getUserAgent(); } if (/Chrome|CriOS/.test(userAgent)) { return 'chrome'; } if (/Firefox|FxiOS/.test(userAgent)) { return 'firefox'; } if (/Safari/.test(userAgent)) { return 'safari'; } return null; } /** * Guess the browser version. * @param {string} [userAgent=navigator.userAgent] * @returns {?{major: number, minor: number}} */ function guessBrowserVersion(userAgent) { if (typeof userAgent === 'undefined') { userAgent = getUserAgent(); } var prefix = { chrome: 'Chrome|CriOS', firefox: 'Firefox|FxiOS', safari: 'Version' }[guessBrowser(userAgent)]; if (!prefix) { return null; } var regex = new RegExp('(' + prefix + ')/([^\\s]+)'); var match = (userAgent.match(regex) || [])[2]; if (!match) { return null; } var versions = match.split('.').map(Number); return { major: isNaN(versions[0]) ? null : versions[0], minor: isNaN(versions[1]) ? null : versions[1] }; } /** * Intercept an event that might otherwise be proxied on an EventTarget. * @param {EventTarget} target * @param {string} type * @returns {void} */ function interceptEvent(target, type) { var currentListener = null; Object.defineProperty(target, 'on' + type, { get: function() { return currentListener; }, set: function(newListener) { if (currentListener) { this.removeEventListener(type, currentListener); } if (typeof newListener === 'function') { currentListener = newListener; this.addEventListener(type, currentListener); } else { currentListener = null; } } }); } /** * This is a function for turning a Promise into the kind referenced in the * Legacy Interface Extensions section of the WebRTC spec. * @param {Promise<*>} promise * @param {function<*>} onSuccess * @param {function} onFailure * @returns {Promise} */ function legacyPromise(promise, onSuccess, onFailure) { if (onSuccess) { return promise.then(function(result) { onSuccess(result); }, function(error) { onFailure(error); }); } return promise; } /** * Make a unique ID. * @return {string} */ function makeUUID() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random() * 16 | 0; var v = c === 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } /** * For each property name on the `source` prototype, add getters and/or setters * to `wrapper` that proxy to `target`. * @param {object} source * @param {object} wrapper * @param {string} target * @returns {undefined} */ function proxyProperties(source, wrapper, target) { Object.getOwnPropertyNames(source).forEach(function(propertyName) { proxyProperty(source, wrapper, target, propertyName); }); } /** * For the property name on the `source` prototype, add a getter and/or setter * to `wrapper` that proxies to `target`. * @param {object} source * @param {object} wrapper * @param {string} target * @param {string} propertyName * @returns {undefined} */ function proxyProperty(source, wrapper, target, propertyName) { if (propertyName in wrapper) { // Skip any properties already set. return; } else if (propertyName.match(/^on[a-z]+$/)) { Object.defineProperty(wrapper, propertyName, { value: null, writable: true }); target.addEventListener(propertyName.slice(2), function() { wrapper.dispatchEvent.apply(wrapper, arguments); }); return; } Object.defineProperty(wrapper, propertyName, { enumerable: true, get: function() { return target[propertyName]; } }); } /** * Check whether native WebRTC APIs are supported. * @returns {boolean} */ function support() { return typeof navigator === 'object' && typeof navigator.mediaDevices === 'object' && typeof navigator.mediaDevices.getUserMedia === 'function' && typeof RTCPeerConnection === 'function'; } /** * @typedef {object} Deferred * @property {Promise} promise * @property {function} reject * @property {function} resolve */ exports.defer = defer; exports.delegateMethods = delegateMethods; exports.difference = difference; exports.flatMap = flatMap; exports.guessBrowser = guessBrowser; exports.guessBrowserVersion = guessBrowserVersion; exports.interceptEvent = interceptEvent; exports.legacyPromise = legacyPromise; exports.makeUUID = makeUUID; exports.proxyProperties = proxyProperties; exports.support = support;