));\n } else {\n createDebug.names.push(new RegExp('^' + namespaces + '
));\n }\n }\n\n for (i = 0; i \u003C createDebug.instances.length; i++) {\n const instance = createDebug.instances[i];\n instance.enabled = createDebug.enabled(instance.namespace);\n }\n }\n\n \u002F**\n * Disable debug output.\n *\n * @return {String} namespaces\n * @api public\n *\u002F\n function disable() {\n const namespaces = [\n ...createDebug.names.map(toNamespace),\n ...createDebug.skips.map(toNamespace).map(namespace =\u003E '-' + namespace)\n ].join(',');\n createDebug.enable('');\n return namespaces;\n }\n\n \u002F**\n * Returns true if the given mode name is enabled, false otherwise.\n *\n * @param {String} name\n * @return {Boolean}\n * @api public\n *\u002F\n function enabled(name) {\n if (name[name.length - 1] === '*') {\n return true;\n }\n\n let i;\n let len;\n\n for (i = 0, len = createDebug.skips.length; i \u003C len; i++) {\n if (createDebug.skips[i].test(name)) {\n return false;\n }\n }\n\n for (i = 0, len = createDebug.names.length; i \u003C len; i++) {\n if (createDebug.names[i].test(name)) {\n return true;\n }\n }\n\n return false;\n }\n\n \u002F**\n * Convert regexp to namespace\n *\n * @param {RegExp} regxep\n * @return {String} namespace\n * @api private\n *\u002F\n function toNamespace(regexp) {\n return regexp.toString()\n .substring(2, regexp.toString().length - 2)\n .replace(\u002F\\.\\*\\?$\u002F, '*');\n }\n\n \u002F**\n * Coerce `val`.\n *\n * @param {Mixed} val\n * @return {Mixed}\n * @api private\n *\u002F\n function coerce(val) {\n if (val instanceof Error) {\n return val.stack || val.message;\n }\n return val;\n }\n\n createDebug.enable(createDebug.load());\n\n return createDebug;\n }\n\n var common = setup;\n\n var browser = createCommonjsModule(function (module, exports) {\n \u002F* eslint-env browser *\u002F\n\n \u002F**\n * This is the web browser implementation of `debug()`.\n *\u002F\n\n exports.log = log;\n exports.formatArgs = formatArgs;\n exports.save = save;\n exports.load = load;\n exports.useColors = useColors;\n exports.storage = localstorage();\n\n \u002F**\n * Colors.\n *\u002F\n\n exports.colors = [\n '#0000CC',\n '#0000FF',\n '#0033CC',\n '#0033FF',\n '#0066CC',\n '#0066FF',\n '#0099CC',\n '#0099FF',\n '#00CC00',\n '#00CC33',\n '#00CC66',\n '#00CC99',\n '#00CCCC',\n '#00CCFF',\n '#3300CC',\n '#3300FF',\n '#3333CC',\n '#3333FF',\n '#3366CC',\n '#3366FF',\n '#3399CC',\n '#3399FF',\n '#33CC00',\n '#33CC33',\n '#33CC66',\n '#33CC99',\n '#33CCCC',\n '#33CCFF',\n '#6600CC',\n '#6600FF',\n '#6633CC',\n '#6633FF',\n '#66CC00',\n '#66CC33',\n '#9900CC',\n '#9900FF',\n '#9933CC',\n '#9933FF',\n '#99CC00',\n '#99CC33',\n '#CC0000',\n '#CC0033',\n '#CC0066',\n '#CC0099',\n '#CC00CC',\n '#CC00FF',\n '#CC3300',\n '#CC3333',\n '#CC3366',\n '#CC3399',\n '#CC33CC',\n '#CC33FF',\n '#CC6600',\n '#CC6633',\n '#CC9900',\n '#CC9933',\n '#CCCC00',\n '#CCCC33',\n '#FF0000',\n '#FF0033',\n '#FF0066',\n '#FF0099',\n '#FF00CC',\n '#FF00FF',\n '#FF3300',\n '#FF3333',\n '#FF3366',\n '#FF3399',\n '#FF33CC',\n '#FF33FF',\n '#FF6600',\n '#FF6633',\n '#FF9900',\n '#FF9933',\n '#FFCC00',\n '#FFCC33'\n ];\n\n \u002F**\n * Currently only WebKit-based Web Inspectors, Firefox \u003E= v31,\n * and the Firebug extension (any Firefox version) are known\n * to support \"%c\" CSS customizations.\n *\n * TODO: add a `localStorage` variable to explicitly enable\u002Fdisable colors\n *\u002F\n\n \u002F\u002F eslint-disable-next-line complexity\n function useColors() {\n \u002F\u002F NB: In an Electron preload script, document will be defined but not fully\n \u002F\u002F initialized. Since we know we're in Chrome, we'll just detect this case\n \u002F\u002F explicitly\n if (typeof window !== 'undefined' && window.process && (window.process.type === 'renderer' || window.process.__nwjs)) {\n return true;\n }\n\n \u002F\u002F Internet Explorer and Edge do not support colors.\n if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(\u002F(edge|trident)\\\u002F(\\d+)\u002F)) {\n return false;\n }\n\n \u002F\u002F Is webkit? http:\u002F\u002Fstackoverflow.com\u002Fa\u002F16459606\u002F376773\n \u002F\u002F document is undefined in react-native: https:\u002F\u002Fgithub.com\u002Ffacebook\u002Freact-native\u002Fpull\u002F1632\n return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) ||\n \u002F\u002F Is firebug? http:\u002F\u002Fstackoverflow.com\u002Fa\u002F398120\u002F376773\n (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) ||\n \u002F\u002F Is firefox \u003E= v31?\n \u002F\u002F https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FTools\u002FWeb_Console#Styling_messages\n (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(\u002Ffirefox\\\u002F(\\d+)\u002F) && parseInt(RegExp.$1, 10) \u003E= 31) ||\n \u002F\u002F Double check webkit in userAgent just in case we are in a worker\n (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(\u002Fapplewebkit\\\u002F(\\d+)\u002F));\n }\n\n \u002F**\n * Colorize log arguments if enabled.\n *\n * @api public\n *\u002F\n\n function formatArgs(args) {\n args[0] = (this.useColors ? '%c' : '') +\n this.namespace +\n (this.useColors ? ' %c' : ' ') +\n args[0] +\n (this.useColors ? '%c ' : ' ') +\n '+' + module.exports.humanize(this.diff);\n\n if (!this.useColors) {\n return;\n }\n\n const c = 'color: ' + this.color;\n args.splice(1, 0, c, 'color: inherit');\n\n \u002F\u002F The final \"%c\" is somewhat tricky, because there could be other\n \u002F\u002F arguments passed either before or after the %c, so we need to\n \u002F\u002F figure out the correct index to insert the CSS into\n let index = 0;\n let lastC = 0;\n args[0].replace(\u002F%[a-zA-Z%]\u002Fg, match =\u003E {\n if (match === '%%') {\n return;\n }\n index++;\n if (match === '%c') {\n \u002F\u002F We only are interested in the *last* %c\n \u002F\u002F (the user may have provided their own)\n lastC = index;\n }\n });\n\n args.splice(lastC, 0, c);\n }\n\n \u002F**\n * Invokes `console.log()` when available.\n * No-op when `console.log` is not a \"function\".\n *\n * @api public\n *\u002F\n function log(...args) {\n \u002F\u002F This hackery is required for IE8\u002F9, where\n \u002F\u002F the `console.log` function doesn't have 'apply'\n return typeof console === 'object' &&\n console.log &&\n console.log(...args);\n }\n\n \u002F**\n * Save `namespaces`.\n *\n * @param {String} namespaces\n * @api private\n *\u002F\n function save(namespaces) {\n try {\n if (namespaces) {\n exports.storage.setItem('debug', namespaces);\n } else {\n exports.storage.removeItem('debug');\n }\n } catch (error) {\n \u002F\u002F Swallow\n \u002F\u002F XXX (@Qix-) should we be logging these?\n }\n }\n\n \u002F**\n * Load `namespaces`.\n *\n * @return {String} returns the previously persisted debug modes\n * @api private\n *\u002F\n function load() {\n let r;\n try {\n r = exports.storage.getItem('debug');\n } catch (error) {\n \u002F\u002F Swallow\n \u002F\u002F XXX (@Qix-) should we be logging these?\n }\n\n \u002F\u002F If debug isn't set in LS, and we're in Electron, try to load $DEBUG\n if (!r && typeof process !== 'undefined' && 'env' in process) {\n r = process.env.DEBUG;\n }\n\n return r;\n }\n\n \u002F**\n * Localstorage attempts to return the localstorage.\n *\n * This is necessary because safari throws\n * when a user disables cookies\u002Flocalstorage\n * and you attempt to access it.\n *\n * @return {LocalStorage}\n * @api private\n *\u002F\n\n function localstorage() {\n try {\n \u002F\u002F TVMLKit (Apple TV JS Runtime) does not have a window object, just localStorage in the global context\n \u002F\u002F The Browser also has localStorage in the global context.\n return localStorage;\n } catch (error) {\n \u002F\u002F Swallow\n \u002F\u002F XXX (@Qix-) should we be logging these?\n }\n }\n\n module.exports = common(exports);\n\n const {formatters} = module.exports;\n\n \u002F**\n * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.\n *\u002F\n\n formatters.j = function (v) {\n try {\n return JSON.stringify(v);\n } catch (error) {\n return '[UnexpectedJSONParseError]: ' + error.message;\n }\n };\n });\n var browser_1 = browser.log;\n var browser_2 = browser.formatArgs;\n var browser_3 = browser.save;\n var browser_4 = browser.load;\n var browser_5 = browser.useColors;\n var browser_6 = browser.storage;\n var browser_7 = browser.colors;\n\n \u002F**\n * Module dependencies.\n *\u002F\n\n\n var debug = browser('socket.io-client:url');\n\n \u002F**\n * Module exports.\n *\u002F\n\n var url_1 = url;\n\n \u002F**\n * URL parser.\n *\n * @param {String} url\n * @param {Object} An object meant to mimic window.location.\n * Defaults to window.location.\n * @api public\n *\u002F\n\n function url (uri, loc) {\n var obj = uri;\n\n \u002F\u002F default to window.location\n loc = loc || (typeof location !== 'undefined' && location);\n if (null == uri) uri = loc.protocol + '\u002F\u002F' + loc.host;\n\n \u002F\u002F relative path support\n if ('string' === typeof uri) {\n if ('\u002F' === uri.charAt(0)) {\n if ('\u002F' === uri.charAt(1)) {\n uri = loc.protocol + uri;\n } else {\n uri = loc.host + uri;\n }\n }\n\n if (!\u002F^(https?|wss?):\\\u002F\\\u002F\u002F.test(uri)) {\n debug('protocol-less url %s', uri);\n if ('undefined' !== typeof loc) {\n uri = loc.protocol + '\u002F\u002F' + uri;\n } else {\n uri = 'https:\u002F\u002F' + uri;\n }\n }\n\n \u002F\u002F parse\n debug('parse %s', uri);\n obj = parseuri(uri);\n }\n\n \u002F\u002F make sure we treat `localhost:80` and `localhost` equally\n if (!obj.port) {\n if (\u002F^(http|ws)$\u002F.test(obj.protocol)) {\n obj.port = '80';\n } else if (\u002F^(http|ws)s$\u002F.test(obj.protocol)) {\n obj.port = '443';\n }\n }\n\n obj.path = obj.path || '\u002F';\n\n var ipv6 = obj.host.indexOf(':') !== -1;\n var host = ipv6 ? '[' + obj.host + ']' : obj.host;\n\n \u002F\u002F define unique id\n obj.id = obj.protocol + ':\u002F\u002F' + host + ':' + obj.port;\n \u002F\u002F define href\n obj.href = obj.protocol + ':\u002F\u002F' + host + (loc && loc.port === obj.port ? '' : (':' + obj.port));\n\n return obj;\n }\n\n \u002F**\n * Helpers.\n *\u002F\n\n var s$1 = 1000;\n var m$1 = s$1 * 60;\n var h$1 = m$1 * 60;\n var d$1 = h$1 * 24;\n var y$1 = d$1 * 365.25;\n\n \u002F**\n * Parse or format the given `val`.\n *\n * Options:\n *\n * - `long` verbose formatting [false]\n *\n * @param {String|Number} val\n * @param {Object} [options]\n * @throws {Error} throw an error if val is not a non-empty string or a number\n * @return {String|Number}\n * @api public\n *\u002F\n\n var ms$1 = function(val, options) {\n options = options || {};\n var type = typeof val;\n if (type === 'string' && val.length \u003E 0) {\n return parse$2(val);\n } else if (type === 'number' && isNaN(val) === false) {\n return options.long ? fmtLong$1(val) : fmtShort$1(val);\n }\n throw new Error(\n 'val is not a non-empty string or a valid number. val=' +\n JSON.stringify(val)\n );\n };\n\n \u002F**\n * Parse the given `str` and return milliseconds.\n *\n * @param {String} str\n * @return {Number}\n * @api private\n *\u002F\n\n function parse$2(str) {\n str = String(str);\n if (str.length \u003E 100) {\n return;\n }\n var match = \u002F^((?:\\d+)?\\.?\\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$\u002Fi.exec(\n str\n );\n if (!match) {\n return;\n }\n var n = parseFloat(match[1]);\n var type = (match[2] || 'ms').toLowerCase();\n switch (type) {\n case 'years':\n case 'year':\n case 'yrs':\n case 'yr':\n case 'y':\n return n * y$1;\n case 'days':\n case 'day':\n case 'd':\n return n * d$1;\n case 'hours':\n case 'hour':\n case 'hrs':\n case 'hr':\n case 'h':\n return n * h$1;\n case 'minutes':\n case 'minute':\n case 'mins':\n case 'min':\n case 'm':\n return n * m$1;\n case 'seconds':\n case 'second':\n case 'secs':\n case 'sec':\n case 's':\n return n * s$1;\n case 'milliseconds':\n case 'millisecond':\n case 'msecs':\n case 'msec':\n case 'ms':\n return n;\n default:\n return undefined;\n }\n }\n\n \u002F**\n * Short format for `ms`.\n *\n * @param {Number} ms\n * @return {String}\n * @api private\n *\u002F\n\n function fmtShort$1(ms) {\n if (ms \u003E= d$1) {\n return Math.round(ms \u002F d$1) + 'd';\n }\n if (ms \u003E= h$1) {\n return Math.round(ms \u002F h$1) + 'h';\n }\n if (ms \u003E= m$1) {\n return Math.round(ms \u002F m$1) + 'm';\n }\n if (ms \u003E= s$1) {\n return Math.round(ms \u002F s$1) + 's';\n }\n return ms + 'ms';\n }\n\n \u002F**\n * Long format for `ms`.\n *\n * @param {Number} ms\n * @return {String}\n * @api private\n *\u002F\n\n function fmtLong$1(ms) {\n return plural$1(ms, d$1, 'day') ||\n plural$1(ms, h$1, 'hour') ||\n plural$1(ms, m$1, 'minute') ||\n plural$1(ms, s$1, 'second') ||\n ms + ' ms';\n }\n\n \u002F**\n * Pluralization helper.\n *\u002F\n\n function plural$1(ms, n, name) {\n if (ms \u003C n) {\n return;\n }\n if (ms \u003C n * 1.5) {\n return Math.floor(ms \u002F n) + ' ' + name;\n }\n return Math.ceil(ms \u002F n) + ' ' + name + 's';\n }\n\n var debug$1 = createCommonjsModule(function (module, exports) {\n \u002F**\n * This is the common logic for both the Node.js and web browser\n * implementations of `debug()`.\n *\n * Expose `debug()` as the module.\n *\u002F\n\n exports = module.exports = createDebug.debug = createDebug['default'] = createDebug;\n exports.coerce = coerce;\n exports.disable = disable;\n exports.enable = enable;\n exports.enabled = enabled;\n exports.humanize = ms$1;\n\n \u002F**\n * Active `debug` instances.\n *\u002F\n exports.instances = [];\n\n \u002F**\n * The currently active debug mode names, and names to skip.\n *\u002F\n\n exports.names = [];\n exports.skips = [];\n\n \u002F**\n * Map of special \"%n\" handling functions, for the debug \"format\" argument.\n *\n * Valid key names are a single, lower or upper-case letter, i.e. \"n\" and \"N\".\n *\u002F\n\n exports.formatters = {};\n\n \u002F**\n * Select a color.\n * @param {String} namespace\n * @return {Number}\n * @api private\n *\u002F\n\n function selectColor(namespace) {\n var hash = 0, i;\n\n for (i in namespace) {\n hash = ((hash \u003C\u003C 5) - hash) + namespace.charCodeAt(i);\n hash |= 0; \u002F\u002F Convert to 32bit integer\n }\n\n return exports.colors[Math.abs(hash) % exports.colors.length];\n }\n\n \u002F**\n * Create a debugger with the given `namespace`.\n *\n * @param {String} namespace\n * @return {Function}\n * @api public\n *\u002F\n\n function createDebug(namespace) {\n\n var prevTime;\n\n function debug() {\n \u002F\u002F disabled?\n if (!debug.enabled) return;\n\n var self = debug;\n\n \u002F\u002F set `diff` timestamp\n var curr = +new Date();\n var ms = curr - (prevTime || curr);\n self.diff = ms;\n self.prev = prevTime;\n self.curr = curr;\n prevTime = curr;\n\n \u002F\u002F turn the `arguments` into a proper Array\n var args = new Array(arguments.length);\n for (var i = 0; i \u003C args.length; i++) {\n args[i] = arguments[i];\n }\n\n args[0] = exports.coerce(args[0]);\n\n if ('string' !== typeof args[0]) {\n \u002F\u002F anything else let's inspect with %O\n args.unshift('%O');\n }\n\n \u002F\u002F apply any `formatters` transformations\n var index = 0;\n args[0] = args[0].replace(\u002F%([a-zA-Z%])\u002Fg, function(match, format) {\n \u002F\u002F if we encounter an escaped % then don't increase the array index\n if (match === '%%') return match;\n index++;\n var formatter = exports.formatters[format];\n if ('function' === typeof formatter) {\n var val = args[index];\n match = formatter.call(self, val);\n\n \u002F\u002F now we need to remove `args[index]` since it's inlined in the `format`\n args.splice(index, 1);\n index--;\n }\n return match;\n });\n\n \u002F\u002F apply env-specific formatting (colors, etc.)\n exports.formatArgs.call(self, args);\n\n var logFn = debug.log || exports.log || console.log.bind(console);\n logFn.apply(self, args);\n }\n\n debug.namespace = namespace;\n debug.enabled = exports.enabled(namespace);\n debug.useColors = exports.useColors();\n debug.color = selectColor(namespace);\n debug.destroy = destroy;\n\n \u002F\u002F env-specific initialization logic for debug instances\n if ('function' === typeof exports.init) {\n exports.init(debug);\n }\n\n exports.instances.push(debug);\n\n return debug;\n }\n\n function destroy () {\n var index = exports.instances.indexOf(this);\n if (index !== -1) {\n exports.instances.splice(index, 1);\n return true;\n } else {\n return false;\n }\n }\n\n \u002F**\n * Enables a debug mode by namespaces. This can include modes\n * separated by a colon and wildcards.\n *\n * @param {String} namespaces\n * @api public\n *\u002F\n\n function enable(namespaces) {\n exports.save(namespaces);\n\n exports.names = [];\n exports.skips = [];\n\n var i;\n var split = (typeof namespaces === 'string' ? namespaces : '').split(\u002F[\\s,]+\u002F);\n var len = split.length;\n\n for (i = 0; i \u003C len; i++) {\n if (!split[i]) continue; \u002F\u002F ignore empty strings\n namespaces = split[i].replace(\u002F\\*\u002Fg, '.*?');\n if (namespaces[0] === '-') {\n exports.skips.push(new RegExp('^' + namespaces.substr(1) + '
));\n } else {\n exports.names.push(new RegExp('^' + namespaces + '
));\n }\n }\n\n for (i = 0; i \u003C exports.instances.length; i++) {\n var instance = exports.instances[i];\n instance.enabled = exports.enabled(instance.namespace);\n }\n }\n\n \u002F**\n * Disable debug output.\n *\n * @api public\n *\u002F\n\n function disable() {\n exports.enable('');\n }\n\n \u002F**\n * Returns true if the given mode name is enabled, false otherwise.\n *\n * @param {String} name\n * @return {Boolean}\n * @api public\n *\u002F\n\n function enabled(name) {\n if (name[name.length - 1] === '*') {\n return true;\n }\n var i, len;\n for (i = 0, len = exports.skips.length; i \u003C len; i++) {\n if (exports.skips[i].test(name)) {\n return false;\n }\n }\n for (i = 0, len = exports.names.length; i \u003C len; i++) {\n if (exports.names[i].test(name)) {\n return true;\n }\n }\n return false;\n }\n\n \u002F**\n * Coerce `val`.\n *\n * @param {Mixed} val\n * @return {Mixed}\n * @api private\n *\u002F\n\n function coerce(val) {\n if (val instanceof Error) return val.stack || val.message;\n return val;\n }\n });\n var debug_1 = debug$1.coerce;\n var debug_2 = debug$1.disable;\n var debug_3 = debug$1.enable;\n var debug_4 = debug$1.enabled;\n var debug_5 = debug$1.humanize;\n var debug_6 = debug$1.instances;\n var debug_7 = debug$1.names;\n var debug_8 = debug$1.skips;\n var debug_9 = debug$1.formatters;\n\n var browser$1 = createCommonjsModule(function (module, exports) {\n \u002F**\n * This is the web browser implementation of `debug()`.\n *\n * Expose `debug()` as the module.\n *\u002F\n\n exports = module.exports = debug$1;\n exports.log = log;\n exports.formatArgs = formatArgs;\n exports.save = save;\n exports.load = load;\n exports.useColors = useColors;\n exports.storage = 'undefined' != typeof chrome\n && 'undefined' != typeof chrome.storage\n ? chrome.storage.local\n : localstorage();\n\n \u002F**\n * Colors.\n *\u002F\n\n exports.colors = [\n '#0000CC', '#0000FF', '#0033CC', '#0033FF', '#0066CC', '#0066FF', '#0099CC',\n '#0099FF', '#00CC00', '#00CC33', '#00CC66', '#00CC99', '#00CCCC', '#00CCFF',\n '#3300CC', '#3300FF', '#3333CC', '#3333FF', '#3366CC', '#3366FF', '#3399CC',\n '#3399FF', '#33CC00', '#33CC33', '#33CC66', '#33CC99', '#33CCCC', '#33CCFF',\n '#6600CC', '#6600FF', '#6633CC', '#6633FF', '#66CC00', '#66CC33', '#9900CC',\n '#9900FF', '#9933CC', '#9933FF', '#99CC00', '#99CC33', '#CC0000', '#CC0033',\n '#CC0066', '#CC0099', '#CC00CC', '#CC00FF', '#CC3300', '#CC3333', '#CC3366',\n '#CC3399', '#CC33CC', '#CC33FF', '#CC6600', '#CC6633', '#CC9900', '#CC9933',\n '#CCCC00', '#CCCC33', '#FF0000', '#FF0033', '#FF0066', '#FF0099', '#FF00CC',\n '#FF00FF', '#FF3300', '#FF3333', '#FF3366', '#FF3399', '#FF33CC', '#FF33FF',\n '#FF6600', '#FF6633', '#FF9900', '#FF9933', '#FFCC00', '#FFCC33'\n ];\n\n \u002F**\n * Currently only WebKit-based Web Inspectors, Firefox \u003E= v31,\n * and the Firebug extension (any Firefox version) are known\n * to support \"%c\" CSS customizations.\n *\n * TODO: add a `localStorage` variable to explicitly enable\u002Fdisable colors\n *\u002F\n\n function useColors() {\n \u002F\u002F NB: In an Electron preload script, document will be defined but not fully\n \u002F\u002F initialized. Since we know we're in Chrome, we'll just detect this case\n \u002F\u002F explicitly\n if (typeof window !== 'undefined' && window.process && window.process.type === 'renderer') {\n return true;\n }\n\n \u002F\u002F Internet Explorer and Edge do not support colors.\n if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(\u002F(edge|trident)\\\u002F(\\d+)\u002F)) {\n return false;\n }\n\n \u002F\u002F is webkit? http:\u002F\u002Fstackoverflow.com\u002Fa\u002F16459606\u002F376773\n \u002F\u002F document is undefined in react-native: https:\u002F\u002Fgithub.com\u002Ffacebook\u002Freact-native\u002Fpull\u002F1632\n return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) ||\n \u002F\u002F is firebug? http:\u002F\u002Fstackoverflow.com\u002Fa\u002F398120\u002F376773\n (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) ||\n \u002F\u002F is firefox \u003E= v31?\n \u002F\u002F https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FTools\u002FWeb_Console#Styling_messages\n (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(\u002Ffirefox\\\u002F(\\d+)\u002F) && parseInt(RegExp.$1, 10) \u003E= 31) ||\n \u002F\u002F double check webkit in userAgent just in case we are in a worker\n (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(\u002Fapplewebkit\\\u002F(\\d+)\u002F));\n }\n\n \u002F**\n * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.\n *\u002F\n\n exports.formatters.j = function(v) {\n try {\n return JSON.stringify(v);\n } catch (err) {\n return '[UnexpectedJSONParseError]: ' + err.message;\n }\n };\n\n\n \u002F**\n * Colorize log arguments if enabled.\n *\n * @api public\n *\u002F\n\n function formatArgs(args) {\n var useColors = this.useColors;\n\n args[0] = (useColors ? '%c' : '')\n + this.namespace\n + (useColors ? ' %c' : ' ')\n + args[0]\n + (useColors ? '%c ' : ' ')\n + '+' + exports.humanize(this.diff);\n\n if (!useColors) return;\n\n var c = 'color: ' + this.color;\n args.splice(1, 0, c, 'color: inherit');\n\n \u002F\u002F the final \"%c\" is somewhat tricky, because there could be other\n \u002F\u002F arguments passed either before or after the %c, so we need to\n \u002F\u002F figure out the correct index to insert the CSS into\n var index = 0;\n var lastC = 0;\n args[0].replace(\u002F%[a-zA-Z%]\u002Fg, function(match) {\n if ('%%' === match) return;\n index++;\n if ('%c' === match) {\n \u002F\u002F we only are interested in the *last* %c\n \u002F\u002F (the user may have provided their own)\n lastC = index;\n }\n });\n\n args.splice(lastC, 0, c);\n }\n\n \u002F**\n * Invokes `console.log()` when available.\n * No-op when `console.log` is not a \"function\".\n *\n * @api public\n *\u002F\n\n function log() {\n \u002F\u002F this hackery is required for IE8\u002F9, where\n \u002F\u002F the `console.log` function doesn't have 'apply'\n return 'object' === typeof console\n && console.log\n && Function.prototype.apply.call(console.log, console, arguments);\n }\n\n \u002F**\n * Save `namespaces`.\n *\n * @param {String} namespaces\n * @api private\n *\u002F\n\n function save(namespaces) {\n try {\n if (null == namespaces) {\n exports.storage.removeItem('debug');\n } else {\n exports.storage.debug = namespaces;\n }\n } catch(e) {}\n }\n\n \u002F**\n * Load `namespaces`.\n *\n * @return {String} returns the previously persisted debug modes\n * @api private\n *\u002F\n\n function load() {\n var r;\n try {\n r = exports.storage.debug;\n } catch(e) {}\n\n \u002F\u002F If debug isn't set in LS, and we're in Electron, try to load $DEBUG\n if (!r && typeof process !== 'undefined' && 'env' in process) {\n r = process.env.DEBUG;\n }\n\n return r;\n }\n\n \u002F**\n * Enable namespaces listed in `localStorage.debug` initially.\n *\u002F\n\n exports.enable(load());\n\n \u002F**\n * Localstorage attempts to return the localstorage.\n *\n * This is necessary because safari throws\n * when a user disables cookies\u002Flocalstorage\n * and you attempt to access it.\n *\n * @return {LocalStorage}\n * @api private\n *\u002F\n\n function localstorage() {\n try {\n return window.localStorage;\n } catch (e) {}\n }\n });\n var browser_1$1 = browser$1.log;\n var browser_2$1 = browser$1.formatArgs;\n var browser_3$1 = browser$1.save;\n var browser_4$1 = browser$1.load;\n var browser_5$1 = browser$1.useColors;\n var browser_6$1 = browser$1.storage;\n var browser_7$1 = browser$1.colors;\n\n var componentEmitter = createCommonjsModule(function (module) {\n \u002F**\n * Expose `Emitter`.\n *\u002F\n\n {\n module.exports = Emitter;\n }\n\n \u002F**\n * Initialize a new `Emitter`.\n *\n * @api public\n *\u002F\n\n function Emitter(obj) {\n if (obj) return mixin(obj);\n }\n \u002F**\n * Mixin the emitter properties.\n *\n * @param {Object} obj\n * @return {Object}\n * @api private\n *\u002F\n\n function mixin(obj) {\n for (var key in Emitter.prototype) {\n obj[key] = Emitter.prototype[key];\n }\n return obj;\n }\n\n \u002F**\n * Listen on the given `event` with `fn`.\n *\n * @param {String} event\n * @param {Function} fn\n * @return {Emitter}\n * @api public\n *\u002F\n\n Emitter.prototype.on =\n Emitter.prototype.addEventListener = function(event, fn){\n this._callbacks = this._callbacks || {};\n (this._callbacks['
+ event] = this._callbacks['
+ event] || [])\n .push(fn);\n return this;\n };\n\n \u002F**\n * Adds an `event` listener that will be invoked a single\n * time then automatically removed.\n *\n * @param {String} event\n * @param {Function} fn\n * @return {Emitter}\n * @api public\n *\u002F\n\n Emitter.prototype.once = function(event, fn){\n function on() {\n this.off(event, on);\n fn.apply(this, arguments);\n }\n\n on.fn = fn;\n this.on(event, on);\n return this;\n };\n\n \u002F**\n * Remove the given callback for `event` or all\n * registered callbacks.\n *\n * @param {String} event\n * @param {Function} fn\n * @return {Emitter}\n * @api public\n *\u002F\n\n Emitter.prototype.off =\n Emitter.prototype.removeListener =\n Emitter.prototype.removeAllListeners =\n Emitter.prototype.removeEventListener = function(event, fn){\n this._callbacks = this._callbacks || {};\n\n \u002F\u002F all\n if (0 == arguments.length) {\n this._callbacks = {};\n return this;\n }\n\n \u002F\u002F specific event\n var callbacks = this._callbacks['
+ event];\n if (!callbacks) return this;\n\n \u002F\u002F remove all handlers\n if (1 == arguments.length) {\n delete this._callbacks['
+ event];\n return this;\n }\n\n \u002F\u002F remove specific handler\n var cb;\n for (var i = 0; i \u003C callbacks.length; i++) {\n cb = callbacks[i];\n if (cb === fn || cb.fn === fn) {\n callbacks.splice(i, 1);\n break;\n }\n }\n return this;\n };\n\n \u002F**\n * Emit `event` with the given args.\n *\n * @param {String} event\n * @param {Mixed} ...\n * @return {Emitter}\n *\u002F\n\n Emitter.prototype.emit = function(event){\n this._callbacks = this._callbacks || {};\n var args = [].slice.call(arguments, 1)\n , callbacks = this._callbacks['
+ event];\n\n if (callbacks) {\n callbacks = callbacks.slice(0);\n for (var i = 0, len = callbacks.length; i \u003C len; ++i) {\n callbacks[i].apply(this, args);\n }\n }\n\n return this;\n };\n\n \u002F**\n * Return array of callbacks for `event`.\n *\n * @param {String} event\n * @return {Array}\n * @api public\n *\u002F\n\n Emitter.prototype.listeners = function(event){\n this._callbacks = this._callbacks || {};\n return this._callbacks['
+ event] || [];\n };\n\n \u002F**\n * Check if this emitter has `event` handlers.\n *\n * @param {String} event\n * @return {Boolean}\n * @api public\n *\u002F\n\n Emitter.prototype.hasListeners = function(event){\n return !! this.listeners(event).length;\n };\n });\n\n var toString$1 = {}.toString;\n\n var isarray = Array.isArray || function (arr) {\n return toString$1.call(arr) == '[object Array]';\n };\n\n var isBuffer = isBuf;\n\n var withNativeBuffer = typeof Buffer === 'function' && typeof Buffer.isBuffer === 'function';\n var withNativeArrayBuffer = typeof ArrayBuffer === 'function';\n\n var isView = function (obj) {\n return typeof ArrayBuffer.isView === 'function' ? ArrayBuffer.isView(obj) : (obj.buffer instanceof ArrayBuffer);\n };\n\n \u002F**\n * Returns true if obj is a buffer or an arraybuffer.\n *\n * @api private\n *\u002F\n\n function isBuf(obj) {\n return (withNativeBuffer && Buffer.isBuffer(obj)) ||\n (withNativeArrayBuffer && (obj instanceof ArrayBuffer || isView(obj)));\n }\n\n \u002F*global Blob,File*\u002F\n\n \u002F**\n * Module requirements\n *\u002F\n\n\n\n var toString$2 = Object.prototype.toString;\n var withNativeBlob = typeof Blob === 'function' || (typeof Blob !== 'undefined' && toString$2.call(Blob) === '[object BlobConstructor]');\n var withNativeFile = typeof File === 'function' || (typeof File !== 'undefined' && toString$2.call(File) === '[object FileConstructor]');\n\n \u002F**\n * Replaces every Buffer | ArrayBuffer in packet with a numbered placeholder.\n * Anything with blobs or files should be fed through removeBlobs before coming\n * here.\n *\n * @param {Object} packet - socket.io event packet\n * @return {Object} with deconstructed packet and list of buffers\n * @api public\n *\u002F\n\n var deconstructPacket = function(packet) {\n var buffers = [];\n var packetData = packet.data;\n var pack = packet;\n pack.data = _deconstructPacket(packetData, buffers);\n pack.attachments = buffers.length; \u002F\u002F number of binary 'attachments'\n return {packet: pack, buffers: buffers};\n };\n\n function _deconstructPacket(data, buffers) {\n if (!data) return data;\n\n if (isBuffer(data)) {\n var placeholder = { _placeholder: true, num: buffers.length };\n buffers.push(data);\n return placeholder;\n } else if (isarray(data)) {\n var newData = new Array(data.length);\n for (var i = 0; i \u003C data.length; i++) {\n newData[i] = _deconstructPacket(data[i], buffers);\n }\n return newData;\n } else if (typeof data === 'object' && !(data instanceof Date)) {\n var newData = {};\n for (var key in data) {\n newData[key] = _deconstructPacket(data[key], buffers);\n }\n return newData;\n }\n return data;\n }\n\n \u002F**\n * Reconstructs a binary packet from its placeholder packet and buffers\n *\n * @param {Object} packet - event packet with placeholders\n * @param {Array} buffers - binary buffers to put in placeholder positions\n * @return {Object} reconstructed packet\n * @api public\n *\u002F\n\n var reconstructPacket = function(packet, buffers) {\n packet.data = _reconstructPacket(packet.data, buffers);\n packet.attachments = undefined; \u002F\u002F no longer useful\n return packet;\n };\n\n function _reconstructPacket(data, buffers) {\n if (!data) return data;\n\n if (data && data._placeholder) {\n return buffers[data.num]; \u002F\u002F appropriate buffer (should be natural order anyway)\n } else if (isarray(data)) {\n for (var i = 0; i \u003C data.length; i++) {\n data[i] = _reconstructPacket(data[i], buffers);\n }\n } else if (typeof data === 'object') {\n for (var key in data) {\n data[key] = _reconstructPacket(data[key], buffers);\n }\n }\n\n return data;\n }\n\n \u002F**\n * Asynchronously removes Blobs or Files from data via\n * FileReader's readAsArrayBuffer method. Used before encoding\n * data as msgpack. Calls callback with the blobless data.\n *\n * @param {Object} data\n * @param {Function} callback\n * @api private\n *\u002F\n\n var removeBlobs = function(data, callback) {\n function _removeBlobs(obj, curKey, containingObject) {\n if (!obj) return obj;\n\n \u002F\u002F convert any blob\n if ((withNativeBlob && obj instanceof Blob) ||\n (withNativeFile && obj instanceof File)) {\n pendingBlobs++;\n\n \u002F\u002F async filereader\n var fileReader = new FileReader();\n fileReader.onload = function() { \u002F\u002F this.result == arraybuffer\n if (containingObject) {\n containingObject[curKey] = this.result;\n }\n else {\n bloblessData = this.result;\n }\n\n \u002F\u002F if nothing pending its callback time\n if(! --pendingBlobs) {\n callback(bloblessData);\n }\n };\n\n fileReader.readAsArrayBuffer(obj); \u002F\u002F blob -\u003E arraybuffer\n } else if (isarray(obj)) { \u002F\u002F handle array\n for (var i = 0; i \u003C obj.length; i++) {\n _removeBlobs(obj[i], i, obj);\n }\n } else if (typeof obj === 'object' && !isBuffer(obj)) { \u002F\u002F and object\n for (var key in obj) {\n _removeBlobs(obj[key], key, obj);\n }\n }\n }\n\n var pendingBlobs = 0;\n var bloblessData = data;\n _removeBlobs(bloblessData);\n if (!pendingBlobs) {\n callback(bloblessData);\n }\n };\n\n var binary = {\n deconstructPacket: deconstructPacket,\n reconstructPacket: reconstructPacket,\n removeBlobs: removeBlobs\n };\n\n var socket_ioParser = createCommonjsModule(function (module, exports) {\n \u002F**\n * Module dependencies.\n *\u002F\n\n var debug = browser$1('socket.io-parser');\n\n\n\n\n\n \u002F**\n * Protocol version.\n *\n * @api public\n *\u002F\n\n exports.protocol = 4;\n\n \u002F**\n * Packet types.\n *\n * @api public\n *\u002F\n\n exports.types = [\n 'CONNECT',\n 'DISCONNECT',\n 'EVENT',\n 'ACK',\n 'ERROR',\n 'BINARY_EVENT',\n 'BINARY_ACK'\n ];\n\n \u002F**\n * Packet type `connect`.\n *\n * @api public\n *\u002F\n\n exports.CONNECT = 0;\n\n \u002F**\n * Packet type `disconnect`.\n *\n * @api public\n *\u002F\n\n exports.DISCONNECT = 1;\n\n \u002F**\n * Packet type `event`.\n *\n * @api public\n *\u002F\n\n exports.EVENT = 2;\n\n \u002F**\n * Packet type `ack`.\n *\n * @api public\n *\u002F\n\n exports.ACK = 3;\n\n \u002F**\n * Packet type `error`.\n *\n * @api public\n *\u002F\n\n exports.ERROR = 4;\n\n \u002F**\n * Packet type 'binary event'\n *\n * @api public\n *\u002F\n\n exports.BINARY_EVENT = 5;\n\n \u002F**\n * Packet type `binary ack`. For acks with binary arguments.\n *\n * @api public\n *\u002F\n\n exports.BINARY_ACK = 6;\n\n \u002F**\n * Encoder constructor.\n *\n * @api public\n *\u002F\n\n exports.Encoder = Encoder;\n\n \u002F**\n * Decoder constructor.\n *\n * @api public\n *\u002F\n\n exports.Decoder = Decoder;\n\n \u002F**\n * A socket.io Encoder instance\n *\n * @api public\n *\u002F\n\n function Encoder() {}\n\n var ERROR_PACKET = exports.ERROR + '\"encode error\"';\n\n \u002F**\n * Encode a packet as a single string if non-binary, or as a\n * buffer sequence, depending on packet type.\n *\n * @param {Object} obj - packet object\n * @param {Function} callback - function to handle encodings (likely engine.write)\n * @return Calls callback with Array of encodings\n * @api public\n *\u002F\n\n Encoder.prototype.encode = function(obj, callback){\n debug('encoding packet %j', obj);\n\n if (exports.BINARY_EVENT === obj.type || exports.BINARY_ACK === obj.type) {\n encodeAsBinary(obj, callback);\n } else {\n var encoding = encodeAsString(obj);\n callback([encoding]);\n }\n };\n\n \u002F**\n * Encode packet as string.\n *\n * @param {Object} packet\n * @return {String} encoded\n * @api private\n *\u002F\n\n function encodeAsString(obj) {\n\n \u002F\u002F first is type\n var str = '' + obj.type;\n\n \u002F\u002F attachments if we have them\n if (exports.BINARY_EVENT === obj.type || exports.BINARY_ACK === obj.type) {\n str += obj.attachments + '-';\n }\n\n \u002F\u002F if we have a namespace other than `\u002F`\n \u002F\u002F we append it followed by a comma `,`\n if (obj.nsp && '\u002F' !== obj.nsp) {\n str += obj.nsp + ',';\n }\n\n \u002F\u002F immediately followed by the id\n if (null != obj.id) {\n str += obj.id;\n }\n\n \u002F\u002F json data\n if (null != obj.data) {\n var payload = tryStringify(obj.data);\n if (payload !== false) {\n str += payload;\n } else {\n return ERROR_PACKET;\n }\n }\n\n debug('encoded %j as %s', obj, str);\n return str;\n }\n\n function tryStringify(str) {\n try {\n return JSON.stringify(str);\n } catch(e){\n return false;\n }\n }\n\n \u002F**\n * Encode packet as 'buffer sequence' by removing blobs, and\n * deconstructing packet into object with placeholders and\n * a list of buffers.\n *\n * @param {Object} packet\n * @return {Buffer} encoded\n * @api private\n *\u002F\n\n function encodeAsBinary(obj, callback) {\n\n function writeEncoding(bloblessData) {\n var deconstruction = binary.deconstructPacket(bloblessData);\n var pack = encodeAsString(deconstruction.packet);\n var buffers = deconstruction.buffers;\n\n buffers.unshift(pack); \u002F\u002F add packet info to beginning of data list\n callback(buffers); \u002F\u002F write all the buffers\n }\n\n binary.removeBlobs(obj, writeEncoding);\n }\n\n \u002F**\n * A socket.io Decoder instance\n *\n * @return {Object} decoder\n * @api public\n *\u002F\n\n function Decoder() {\n this.reconstructor = null;\n }\n\n \u002F**\n * Mix in `Emitter` with Decoder.\n *\u002F\n\n componentEmitter(Decoder.prototype);\n\n \u002F**\n * Decodes an encoded packet string into packet JSON.\n *\n * @param {String} obj - encoded packet\n * @return {Object} packet\n * @api public\n *\u002F\n\n Decoder.prototype.add = function(obj) {\n var packet;\n if (typeof obj === 'string') {\n packet = decodeString(obj);\n if (exports.BINARY_EVENT === packet.type || exports.BINARY_ACK === packet.type) { \u002F\u002F binary packet's json\n this.reconstructor = new BinaryReconstructor(packet);\n\n \u002F\u002F no attachments, labeled binary but no binary data to follow\n if (this.reconstructor.reconPack.attachments === 0) {\n this.emit('decoded', packet);\n }\n } else { \u002F\u002F non-binary full packet\n this.emit('decoded', packet);\n }\n } else if (isBuffer(obj) || obj.base64) { \u002F\u002F raw binary data\n if (!this.reconstructor) {\n throw new Error('got binary data when not reconstructing a packet');\n } else {\n packet = this.reconstructor.takeBinaryData(obj);\n if (packet) { \u002F\u002F received final buffer\n this.reconstructor = null;\n this.emit('decoded', packet);\n }\n }\n } else {\n throw new Error('Unknown type: ' + obj);\n }\n };\n\n \u002F**\n * Decode a packet String (JSON data)\n *\n * @param {String} str\n * @return {Object} packet\n * @api private\n *\u002F\n\n function decodeString(str) {\n var i = 0;\n \u002F\u002F look up type\n var p = {\n type: Number(str.charAt(0))\n };\n\n if (null == exports.types[p.type]) {\n return error('unknown packet type ' + p.type);\n }\n\n \u002F\u002F look up attachments if type binary\n if (exports.BINARY_EVENT === p.type || exports.BINARY_ACK === p.type) {\n var buf = '';\n while (str.charAt(++i) !== '-') {\n buf += str.charAt(i);\n if (i == str.length) break;\n }\n if (buf != Number(buf) || str.charAt(i) !== '-') {\n throw new Error('Illegal attachments');\n }\n p.attachments = Number(buf);\n }\n\n \u002F\u002F look up namespace (if any)\n if ('\u002F' === str.charAt(i + 1)) {\n p.nsp = '';\n while (++i) {\n var c = str.charAt(i);\n if (',' === c) break;\n p.nsp += c;\n if (i === str.length) break;\n }\n } else {\n p.nsp = '\u002F';\n }\n\n \u002F\u002F look up id\n var next = str.charAt(i + 1);\n if ('' !== next && Number(next) == next) {\n p.id = '';\n while (++i) {\n var c = str.charAt(i);\n if (null == c || Number(c) != c) {\n --i;\n break;\n }\n p.id += str.charAt(i);\n if (i === str.length) break;\n }\n p.id = Number(p.id);\n }\n\n \u002F\u002F look up json data\n if (str.charAt(++i)) {\n var payload = tryParse(str.substr(i));\n var isPayloadValid = payload !== false && (p.type === exports.ERROR || isarray(payload));\n if (isPayloadValid) {\n p.data = payload;\n } else {\n return error('invalid payload');\n }\n }\n\n debug('decoded %s as %j', str, p);\n return p;\n }\n\n function tryParse(str) {\n try {\n return JSON.parse(str);\n } catch(e){\n return false;\n }\n }\n\n \u002F**\n * Deallocates a parser's resources\n *\n * @api public\n *\u002F\n\n Decoder.prototype.destroy = function() {\n if (this.reconstructor) {\n this.reconstructor.finishedReconstruction();\n }\n };\n\n \u002F**\n * A manager of a binary event's 'buffer sequence'. Should\n * be constructed whenever a packet of type BINARY_EVENT is\n * decoded.\n *\n * @param {Object} packet\n * @return {BinaryReconstructor} initialized reconstructor\n * @api private\n *\u002F\n\n function BinaryReconstructor(packet) {\n this.reconPack = packet;\n this.buffers = [];\n }\n\n \u002F**\n * Method to be called when binary data received from connection\n * after a BINARY_EVENT packet.\n *\n * @param {Buffer | ArrayBuffer} binData - the raw binary data received\n * @return {null | Object} returns null if more binary data is expected or\n * a reconstructed packet object if all buffers have been received.\n * @api private\n *\u002F\n\n BinaryReconstructor.prototype.takeBinaryData = function(binData) {\n this.buffers.push(binData);\n if (this.buffers.length === this.reconPack.attachments) { \u002F\u002F done with buffer list\n var packet = binary.reconstructPacket(this.reconPack, this.buffers);\n this.finishedReconstruction();\n return packet;\n }\n return null;\n };\n\n \u002F**\n * Cleans up binary packet reconstruction variables.\n *\n * @api private\n *\u002F\n\n BinaryReconstructor.prototype.finishedReconstruction = function() {\n this.reconPack = null;\n this.buffers = [];\n };\n\n function error(msg) {\n return {\n type: exports.ERROR,\n data: 'parser error: ' + msg\n };\n }\n });\n var socket_ioParser_1 = socket_ioParser.protocol;\n var socket_ioParser_2 = socket_ioParser.types;\n var socket_ioParser_3 = socket_ioParser.CONNECT;\n var socket_ioParser_4 = socket_ioParser.DISCONNECT;\n var socket_ioParser_5 = socket_ioParser.EVENT;\n var socket_ioParser_6 = socket_ioParser.ACK;\n var socket_ioParser_7 = socket_ioParser.ERROR;\n var socket_ioParser_8 = socket_ioParser.BINARY_EVENT;\n var socket_ioParser_9 = socket_ioParser.BINARY_ACK;\n var socket_ioParser_10 = socket_ioParser.Encoder;\n var socket_ioParser_11 = socket_ioParser.Decoder;\n\n var hasCors = createCommonjsModule(function (module) {\n \u002F**\n * Module exports.\n *\n * Logic borrowed from Modernizr:\n *\n * - https:\u002F\u002Fgithub.com\u002FModernizr\u002FModernizr\u002Fblob\u002Fmaster\u002Ffeature-detects\u002Fcors.js\n *\u002F\n\n try {\n module.exports = typeof XMLHttpRequest !== 'undefined' &&\n 'withCredentials' in new XMLHttpRequest();\n } catch (err) {\n \u002F\u002F if XMLHttp support is disabled in IE then it will throw\n \u002F\u002F when trying to create\n module.exports = false;\n }\n });\n\n \u002F\u002F browser shim for xmlhttprequest module\n\n\n\n var xmlhttprequest = function (opts) {\n var xdomain = opts.xdomain;\n\n \u002F\u002F scheme must be same when usign XDomainRequest\n \u002F\u002F http:\u002F\u002Fblogs.msdn.com\u002Fb\u002Fieinternals\u002Farchive\u002F2010\u002F05\u002F13\u002Fxdomainrequest-restrictions-limitations-and-workarounds.aspx\n var xscheme = opts.xscheme;\n\n \u002F\u002F XDomainRequest has a flow of not sending cookie, therefore it should be disabled as a default.\n \u002F\u002F https:\u002F\u002Fgithub.com\u002FAutomattic\u002Fengine.io-client\u002Fpull\u002F217\n var enablesXDR = opts.enablesXDR;\n\n \u002F\u002F XMLHttpRequest can be disabled on IE\n try {\n if ('undefined' !== typeof XMLHttpRequest && (!xdomain || hasCors)) {\n return new XMLHttpRequest();\n }\n } catch (e) { }\n\n \u002F\u002F Use XDomainRequest for IE8 if enablesXDR is true\n \u002F\u002F because loading bar keeps flashing when using jsonp-polling\n \u002F\u002F https:\u002F\u002Fgithub.com\u002Fyujiosaka\u002Fsocke.io-ie8-loading-example\n try {\n if ('undefined' !== typeof XDomainRequest && !xscheme && enablesXDR) {\n return new XDomainRequest();\n }\n } catch (e) { }\n\n if (!xdomain) {\n try {\n return new self[['Active'].concat('Object').join('X')]('Microsoft.XMLHTTP');\n } catch (e) { }\n }\n };\n\n \u002F**\n * Gets the keys for an object.\n *\n * @return {Array} keys\n * @api private\n *\u002F\n\n var keys = Object.keys || function keys (obj){\n var arr = [];\n var has = Object.prototype.hasOwnProperty;\n\n for (var i in obj) {\n if (has.call(obj, i)) {\n arr.push(i);\n }\n }\n return arr;\n };\n\n \u002F* global Blob File *\u002F\n\n \u002F*\n * Module requirements.\n *\u002F\n\n\n\n var toString$3 = Object.prototype.toString;\n var withNativeBlob$1 = typeof Blob === 'function' ||\n typeof Blob !== 'undefined' && toString$3.call(Blob) === '[object BlobConstructor]';\n var withNativeFile$1 = typeof File === 'function' ||\n typeof File !== 'undefined' && toString$3.call(File) === '[object FileConstructor]';\n\n \u002F**\n * Module exports.\n *\u002F\n\n var hasBinary2 = hasBinary;\n\n \u002F**\n * Checks for binary data.\n *\n * Supports Buffer, ArrayBuffer, Blob and File.\n *\n * @param {Object} anything\n * @api public\n *\u002F\n\n function hasBinary (obj) {\n if (!obj || typeof obj !== 'object') {\n return false;\n }\n\n if (isarray(obj)) {\n for (var i = 0, l = obj.length; i \u003C l; i++) {\n if (hasBinary(obj[i])) {\n return true;\n }\n }\n return false;\n }\n\n if ((typeof Buffer === 'function' && Buffer.isBuffer && Buffer.isBuffer(obj)) ||\n (typeof ArrayBuffer === 'function' && obj instanceof ArrayBuffer) ||\n (withNativeBlob$1 && obj instanceof Blob) ||\n (withNativeFile$1 && obj instanceof File)\n ) {\n return true;\n }\n\n \u002F\u002F see: https:\u002F\u002Fgithub.com\u002FAutomattic\u002Fhas-binary\u002Fpull\u002F4\n if (obj.toJSON && typeof obj.toJSON === 'function' && arguments.length === 1) {\n return hasBinary(obj.toJSON(), true);\n }\n\n for (var key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key) && hasBinary(obj[key])) {\n return true;\n }\n }\n\n return false;\n }\n\n \u002F**\n * An abstraction for slicing an arraybuffer even when\n * ArrayBuffer.prototype.slice is not supported\n *\n * @api public\n *\u002F\n\n var arraybuffer_slice = function(arraybuffer, start, end) {\n var bytes = arraybuffer.byteLength;\n start = start || 0;\n end = end || bytes;\n\n if (arraybuffer.slice) { return arraybuffer.slice(start, end); }\n\n if (start \u003C 0) { start += bytes; }\n if (end \u003C 0) { end += bytes; }\n if (end \u003E bytes) { end = bytes; }\n\n if (start \u003E= bytes || start \u003E= end || bytes === 0) {\n return new ArrayBuffer(0);\n }\n\n var abv = new Uint8Array(arraybuffer);\n var result = new Uint8Array(end - start);\n for (var i = start, ii = 0; i \u003C end; i++, ii++) {\n result[ii] = abv[i];\n }\n return result.buffer;\n };\n\n var after_1 = after;\n\n function after(count, callback, err_cb) {\n var bail = false;\n err_cb = err_cb || noop;\n proxy.count = count;\n\n return (count === 0) ? callback() : proxy\n\n function proxy(err, result) {\n if (proxy.count \u003C= 0) {\n throw new Error('after called too many times')\n }\n --proxy.count;\n\n \u002F\u002F after first error, rest are passed to err_cb\n if (err) {\n bail = true;\n callback(err);\n \u002F\u002F future error callbacks will go to error handler\n callback = err_cb;\n } else if (proxy.count === 0 && !bail) {\n callback(null, result);\n }\n }\n }\n\n function noop() {}\n\n \u002F*! https:\u002F\u002Fmths.be\u002Futf8js v2.1.2 by @mathias *\u002F\n\n var stringFromCharCode = String.fromCharCode;\n\n \u002F\u002F Taken from https:\u002F\u002Fmths.be\u002Fpunycode\n function ucs2decode(string) {\n var output = [];\n var counter = 0;\n var length = string.length;\n var value;\n var extra;\n while (counter \u003C length) {\n value = string.charCodeAt(counter++);\n if (value \u003E= 0xD800 && value \u003C= 0xDBFF && counter \u003C length) {\n \u002F\u002F high surrogate, and there is a next character\n extra = string.charCodeAt(counter++);\n if ((extra & 0xFC00) == 0xDC00) { \u002F\u002F low surrogate\n output.push(((value & 0x3FF) \u003C\u003C 10) + (extra & 0x3FF) + 0x10000);\n } else {\n \u002F\u002F unmatched surrogate; only append this code unit, in case the next\n \u002F\u002F code unit is the high surrogate of a surrogate pair\n output.push(value);\n counter--;\n }\n } else {\n output.push(value);\n }\n }\n return output;\n }\n\n \u002F\u002F Taken from https:\u002F\u002Fmths.be\u002Fpunycode\n function ucs2encode(array) {\n var length = array.length;\n var index = -1;\n var value;\n var output = '';\n while (++index \u003C length) {\n value = array[index];\n if (value \u003E 0xFFFF) {\n value -= 0x10000;\n output += stringFromCharCode(value \u003E\u003E\u003E 10 & 0x3FF | 0xD800);\n value = 0xDC00 | value & 0x3FF;\n }\n output += stringFromCharCode(value);\n }\n return output;\n }\n\n function checkScalarValue(codePoint, strict) {\n if (codePoint \u003E= 0xD800 && codePoint \u003C= 0xDFFF) {\n if (strict) {\n throw Error(\n 'Lone surrogate U+' + codePoint.toString(16).toUpperCase() +\n ' is not a scalar value'\n );\n }\n return false;\n }\n return true;\n }\n \u002F*--------------------------------------------------------------------------*\u002F\n\n function createByte(codePoint, shift) {\n return stringFromCharCode(((codePoint \u003E\u003E shift) & 0x3F) | 0x80);\n }\n\n function encodeCodePoint(codePoint, strict) {\n if ((codePoint & 0xFFFFFF80) == 0) { \u002F\u002F 1-byte sequence\n return stringFromCharCode(codePoint);\n }\n var symbol = '';\n if ((codePoint & 0xFFFFF800) == 0) { \u002F\u002F 2-byte sequence\n symbol = stringFromCharCode(((codePoint \u003E\u003E 6) & 0x1F) | 0xC0);\n }\n else if ((codePoint & 0xFFFF0000) == 0) { \u002F\u002F 3-byte sequence\n if (!checkScalarValue(codePoint, strict)) {\n codePoint = 0xFFFD;\n }\n symbol = stringFromCharCode(((codePoint \u003E\u003E 12) & 0x0F) | 0xE0);\n symbol += createByte(codePoint, 6);\n }\n else if ((codePoint & 0xFFE00000) == 0) { \u002F\u002F 4-byte sequence\n symbol = stringFromCharCode(((codePoint \u003E\u003E 18) & 0x07) | 0xF0);\n symbol += createByte(codePoint, 12);\n symbol += createByte(codePoint, 6);\n }\n symbol += stringFromCharCode((codePoint & 0x3F) | 0x80);\n return symbol;\n }\n\n function utf8encode(string, opts) {\n opts = opts || {};\n var strict = false !== opts.strict;\n\n var codePoints = ucs2decode(string);\n var length = codePoints.length;\n var index = -1;\n var codePoint;\n var byteString = '';\n while (++index \u003C length) {\n codePoint = codePoints[index];\n byteString += encodeCodePoint(codePoint, strict);\n }\n return byteString;\n }\n\n \u002F*--------------------------------------------------------------------------*\u002F\n\n function readContinuationByte() {\n if (byteIndex \u003E= byteCount) {\n throw Error('Invalid byte index');\n }\n\n var continuationByte = byteArray[byteIndex] & 0xFF;\n byteIndex++;\n\n if ((continuationByte & 0xC0) == 0x80) {\n return continuationByte & 0x3F;\n }\n\n \u002F\u002F If we end up here, it’s not a continuation byte\n throw Error('Invalid continuation byte');\n }\n\n function decodeSymbol(strict) {\n var byte1;\n var byte2;\n var byte3;\n var byte4;\n var codePoint;\n\n if (byteIndex \u003E byteCount) {\n throw Error('Invalid byte index');\n }\n\n if (byteIndex == byteCount) {\n return false;\n }\n\n \u002F\u002F Read first byte\n byte1 = byteArray[byteIndex] & 0xFF;\n byteIndex++;\n\n \u002F\u002F 1-byte sequence (no continuation bytes)\n if ((byte1 & 0x80) == 0) {\n return byte1;\n }\n\n \u002F\u002F 2-byte sequence\n if ((byte1 & 0xE0) == 0xC0) {\n byte2 = readContinuationByte();\n codePoint = ((byte1 & 0x1F) \u003C\u003C 6) | byte2;\n if (codePoint \u003E= 0x80) {\n return codePoint;\n } else {\n throw Error('Invalid continuation byte');\n }\n }\n\n \u002F\u002F 3-byte sequence (may include unpaired surrogates)\n if ((byte1 & 0xF0) == 0xE0) {\n byte2 = readContinuationByte();\n byte3 = readContinuationByte();\n codePoint = ((byte1 & 0x0F) \u003C\u003C 12) | (byte2 \u003C\u003C 6) | byte3;\n if (codePoint \u003E= 0x0800) {\n return checkScalarValue(codePoint, strict) ? codePoint : 0xFFFD;\n } else {\n throw Error('Invalid continuation byte');\n }\n }\n\n \u002F\u002F 4-byte sequence\n if ((byte1 & 0xF8) == 0xF0) {\n byte2 = readContinuationByte();\n byte3 = readContinuationByte();\n byte4 = readContinuationByte();\n codePoint = ((byte1 & 0x07) \u003C\u003C 0x12) | (byte2 \u003C\u003C 0x0C) |\n (byte3 \u003C\u003C 0x06) | byte4;\n if (codePoint \u003E= 0x010000 && codePoint \u003C= 0x10FFFF) {\n return codePoint;\n }\n }\n\n throw Error('Invalid UTF-8 detected');\n }\n\n var byteArray;\n var byteCount;\n var byteIndex;\n function utf8decode(byteString, opts) {\n opts = opts || {};\n var strict = false !== opts.strict;\n\n byteArray = ucs2decode(byteString);\n byteCount = byteArray.length;\n byteIndex = 0;\n var codePoints = [];\n var tmp;\n while ((tmp = decodeSymbol(strict)) !== false) {\n codePoints.push(tmp);\n }\n return ucs2encode(codePoints);\n }\n\n var utf8 = {\n version: '2.1.2',\n encode: utf8encode,\n decode: utf8decode\n };\n\n var base64Arraybuffer = createCommonjsModule(function (module, exports) {\n \u002F*\n * base64-arraybuffer\n * https:\u002F\u002Fgithub.com\u002Fniklasvh\u002Fbase64-arraybuffer\n *\n * Copyright (c) 2012 Niklas von Hertzen\n * Licensed under the MIT license.\n *\u002F\n (function(){\n\n var chars = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+\u002F\";\n\n \u002F\u002F Use a lookup table to find the index.\n var lookup = new Uint8Array(256);\n for (var i = 0; i \u003C chars.length; i++) {\n lookup[chars.charCodeAt(i)] = i;\n }\n\n exports.encode = function(arraybuffer) {\n var bytes = new Uint8Array(arraybuffer),\n i, len = bytes.length, base64 = \"\";\n\n for (i = 0; i \u003C len; i+=3) {\n base64 += chars[bytes[i] \u003E\u003E 2];\n base64 += chars[((bytes[i] & 3) \u003C\u003C 4) | (bytes[i + 1] \u003E\u003E 4)];\n base64 += chars[((bytes[i + 1] & 15) \u003C\u003C 2) | (bytes[i + 2] \u003E\u003E 6)];\n base64 += chars[bytes[i + 2] & 63];\n }\n\n if ((len % 3) === 2) {\n base64 = base64.substring(0, base64.length - 1) + \"=\";\n } else if (len % 3 === 1) {\n base64 = base64.substring(0, base64.length - 2) + \"==\";\n }\n\n return base64;\n };\n\n exports.decode = function(base64) {\n var bufferLength = base64.length * 0.75,\n len = base64.length, i, p = 0,\n encoded1, encoded2, encoded3, encoded4;\n\n if (base64[base64.length - 1] === \"=\") {\n bufferLength--;\n if (base64[base64.length - 2] === \"=\") {\n bufferLength--;\n }\n }\n\n var arraybuffer = new ArrayBuffer(bufferLength),\n bytes = new Uint8Array(arraybuffer);\n\n for (i = 0; i \u003C len; i+=4) {\n encoded1 = lookup[base64.charCodeAt(i)];\n encoded2 = lookup[base64.charCodeAt(i+1)];\n encoded3 = lookup[base64.charCodeAt(i+2)];\n encoded4 = lookup[base64.charCodeAt(i+3)];\n\n bytes[p++] = (encoded1 \u003C\u003C 2) | (encoded2 \u003E\u003E 4);\n bytes[p++] = ((encoded2 & 15) \u003C\u003C 4) | (encoded3 \u003E\u003E 2);\n bytes[p++] = ((encoded3 & 3) \u003C\u003C 6) | (encoded4 & 63);\n }\n\n return arraybuffer;\n };\n })();\n });\n var base64Arraybuffer_1 = base64Arraybuffer.encode;\n var base64Arraybuffer_2 = base64Arraybuffer.decode;\n\n \u002F**\n * Create a blob builder even when vendor prefixes exist\n *\u002F\n\n var BlobBuilder = typeof BlobBuilder !== 'undefined' ? BlobBuilder :\n typeof WebKitBlobBuilder !== 'undefined' ? WebKitBlobBuilder :\n typeof MSBlobBuilder !== 'undefined' ? MSBlobBuilder :\n typeof MozBlobBuilder !== 'undefined' ? MozBlobBuilder : \n false;\n\n \u002F**\n * Check if Blob constructor is supported\n *\u002F\n\n var blobSupported = (function() {\n try {\n var a = new Blob(['hi']);\n return a.size === 2;\n } catch(e) {\n return false;\n }\n })();\n\n \u002F**\n * Check if Blob constructor supports ArrayBufferViews\n * Fails in Safari 6, so we need to map to ArrayBuffers there.\n *\u002F\n\n var blobSupportsArrayBufferView = blobSupported && (function() {\n try {\n var b = new Blob([new Uint8Array([1,2])]);\n return b.size === 2;\n } catch(e) {\n return false;\n }\n })();\n\n \u002F**\n * Check if BlobBuilder is supported\n *\u002F\n\n var blobBuilderSupported = BlobBuilder\n && BlobBuilder.prototype.append\n && BlobBuilder.prototype.getBlob;\n\n \u002F**\n * Helper function that maps ArrayBufferViews to ArrayBuffers\n * Used by BlobBuilder constructor and old browsers that didn't\n * support it in the Blob constructor.\n *\u002F\n\n function mapArrayBufferViews(ary) {\n return ary.map(function(chunk) {\n if (chunk.buffer instanceof ArrayBuffer) {\n var buf = chunk.buffer;\n\n \u002F\u002F if this is a subarray, make a copy so we only\n \u002F\u002F include the subarray region from the underlying buffer\n if (chunk.byteLength !== buf.byteLength) {\n var copy = new Uint8Array(chunk.byteLength);\n copy.set(new Uint8Array(buf, chunk.byteOffset, chunk.byteLength));\n buf = copy.buffer;\n }\n\n return buf;\n }\n\n return chunk;\n });\n }\n\n function BlobBuilderConstructor(ary, options) {\n options = options || {};\n\n var bb = new BlobBuilder();\n mapArrayBufferViews(ary).forEach(function(part) {\n bb.append(part);\n });\n\n return (options.type) ? bb.getBlob(options.type) : bb.getBlob();\n }\n function BlobConstructor(ary, options) {\n return new Blob(mapArrayBufferViews(ary), options || {});\n }\n if (typeof Blob !== 'undefined') {\n BlobBuilderConstructor.prototype = Blob.prototype;\n BlobConstructor.prototype = Blob.prototype;\n }\n\n var blob = (function() {\n if (blobSupported) {\n return blobSupportsArrayBufferView ? Blob : BlobConstructor;\n } else if (blobBuilderSupported) {\n return BlobBuilderConstructor;\n } else {\n return undefined;\n }\n })();\n\n var browser$2 = createCommonjsModule(function (module, exports) {\n \u002F**\n * Module dependencies.\n *\u002F\n\n\n\n\n\n\n\n var base64encoder;\n if (typeof ArrayBuffer !== 'undefined') {\n base64encoder = base64Arraybuffer;\n }\n\n \u002F**\n * Check if we are running an android browser. That requires us to use\n * ArrayBuffer with polling transports...\n *\n * http:\u002F\u002Fghinda.net\u002Fjpeg-blob-ajax-android\u002F\n *\u002F\n\n var isAndroid = typeof navigator !== 'undefined' && \u002FAndroid\u002Fi.test(navigator.userAgent);\n\n \u002F**\n * Check if we are running in PhantomJS.\n * Uploading a Blob with PhantomJS does not work correctly, as reported here:\n * https:\u002F\u002Fgithub.com\u002Fariya\u002Fphantomjs\u002Fissues\u002F11395\n * @type boolean\n *\u002F\n var isPhantomJS = typeof navigator !== 'undefined' && \u002FPhantomJS\u002Fi.test(navigator.userAgent);\n\n \u002F**\n * When true, avoids using Blobs to encode payloads.\n * @type boolean\n *\u002F\n var dontSendBlobs = isAndroid || isPhantomJS;\n\n \u002F**\n * Current protocol version.\n *\u002F\n\n exports.protocol = 3;\n\n \u002F**\n * Packet types.\n *\u002F\n\n var packets = exports.packets = {\n open: 0 \u002F\u002F non-ws\n , close: 1 \u002F\u002F non-ws\n , ping: 2\n , pong: 3\n , message: 4\n , upgrade: 5\n , noop: 6\n };\n\n var packetslist = keys(packets);\n\n \u002F**\n * Premade error packet.\n *\u002F\n\n var err = { type: 'error', data: 'parser error' };\n\n \u002F**\n * Create a blob api even for blob builder when vendor prefixes exist\n *\u002F\n\n\n\n \u002F**\n * Encodes a packet.\n *\n * \u003Cpacket type id\u003E [ \u003Cdata\u003E ]\n *\n * Example:\n *\n * 5hello world\n * 3\n * 4\n *\n * Binary is encoded in an identical principle\n *\n * @api private\n *\u002F\n\n exports.encodePacket = function (packet, supportsBinary, utf8encode, callback) {\n if (typeof supportsBinary === 'function') {\n callback = supportsBinary;\n supportsBinary = false;\n }\n\n if (typeof utf8encode === 'function') {\n callback = utf8encode;\n utf8encode = null;\n }\n\n var data = (packet.data === undefined)\n ? undefined\n : packet.data.buffer || packet.data;\n\n if (typeof ArrayBuffer !== 'undefined' && data instanceof ArrayBuffer) {\n return encodeArrayBuffer(packet, supportsBinary, callback);\n } else if (typeof blob !== 'undefined' && data instanceof blob) {\n return encodeBlob(packet, supportsBinary, callback);\n }\n\n \u002F\u002F might be an object with { base64: true, data: dataAsBase64String }\n if (data && data.base64) {\n return encodeBase64Object(packet, callback);\n }\n\n \u002F\u002F Sending data as a utf-8 string\n var encoded = packets[packet.type];\n\n \u002F\u002F data fragment is optional\n if (undefined !== packet.data) {\n encoded += utf8encode ? utf8.encode(String(packet.data), { strict: false }) : String(packet.data);\n }\n\n return callback('' + encoded);\n\n };\n\n function encodeBase64Object(packet, callback) {\n \u002F\u002F packet data is an object { base64: true, data: dataAsBase64String }\n var message = 'b' + exports.packets[packet.type] + packet.data.data;\n return callback(message);\n }\n\n \u002F**\n * Encode packet helpers for binary types\n *\u002F\n\n function encodeArrayBuffer(packet, supportsBinary, callback) {\n if (!supportsBinary) {\n return exports.encodeBase64Packet(packet, callback);\n }\n\n var data = packet.data;\n var contentArray = new Uint8Array(data);\n var resultBuffer = new Uint8Array(1 + data.byteLength);\n\n resultBuffer[0] = packets[packet.type];\n for (var i = 0; i \u003C contentArray.length; i++) {\n resultBuffer[i+1] = contentArray[i];\n }\n\n return callback(resultBuffer.buffer);\n }\n\n function encodeBlobAsArrayBuffer(packet, supportsBinary, callback) {\n if (!supportsBinary) {\n return exports.encodeBase64Packet(packet, callback);\n }\n\n var fr = new FileReader();\n fr.onload = function() {\n exports.encodePacket({ type: packet.type, data: fr.result }, supportsBinary, true, callback);\n };\n return fr.readAsArrayBuffer(packet.data);\n }\n\n function encodeBlob(packet, supportsBinary, callback) {\n if (!supportsBinary) {\n return exports.encodeBase64Packet(packet, callback);\n }\n\n if (dontSendBlobs) {\n return encodeBlobAsArrayBuffer(packet, supportsBinary, callback);\n }\n\n var length = new Uint8Array(1);\n length[0] = packets[packet.type];\n var blob$1 = new blob([length.buffer, packet.data]);\n\n return callback(blob$1);\n }\n\n \u002F**\n * Encodes a packet with binary data in a base64 string\n *\n * @param {Object} packet, has `type` and `data`\n * @return {String} base64 encoded message\n *\u002F\n\n exports.encodeBase64Packet = function(packet, callback) {\n var message = 'b' + exports.packets[packet.type];\n if (typeof blob !== 'undefined' && packet.data instanceof blob) {\n var fr = new FileReader();\n fr.onload = function() {\n var b64 = fr.result.split(',')[1];\n callback(message + b64);\n };\n return fr.readAsDataURL(packet.data);\n }\n\n var b64data;\n try {\n b64data = String.fromCharCode.apply(null, new Uint8Array(packet.data));\n } catch (e) {\n \u002F\u002F iPhone Safari doesn't let you apply with typed arrays\n var typed = new Uint8Array(packet.data);\n var basic = new Array(typed.length);\n for (var i = 0; i \u003C typed.length; i++) {\n basic[i] = typed[i];\n }\n b64data = String.fromCharCode.apply(null, basic);\n }\n message += btoa(b64data);\n return callback(message);\n };\n\n \u002F**\n * Decodes a packet. Changes format to Blob if requested.\n *\n * @return {Object} with `type` and `data` (if any)\n * @api private\n *\u002F\n\n exports.decodePacket = function (data, binaryType, utf8decode) {\n if (data === undefined) {\n return err;\n }\n \u002F\u002F String data\n if (typeof data === 'string') {\n if (data.charAt(0) === 'b') {\n return exports.decodeBase64Packet(data.substr(1), binaryType);\n }\n\n if (utf8decode) {\n data = tryDecode(data);\n if (data === false) {\n return err;\n }\n }\n var type = data.charAt(0);\n\n if (Number(type) != type || !packetslist[type]) {\n return err;\n }\n\n if (data.length \u003E 1) {\n return { type: packetslist[type], data: data.substring(1) };\n } else {\n return { type: packetslist[type] };\n }\n }\n\n var asArray = new Uint8Array(data);\n var type = asArray[0];\n var rest = arraybuffer_slice(data, 1);\n if (blob && binaryType === 'blob') {\n rest = new blob([rest]);\n }\n return { type: packetslist[type], data: rest };\n };\n\n function tryDecode(data) {\n try {\n data = utf8.decode(data, { strict: false });\n } catch (e) {\n return false;\n }\n return data;\n }\n\n \u002F**\n * Decodes a packet encoded in a base64 string\n *\n * @param {String} base64 encoded message\n * @return {Object} with `type` and `data` (if any)\n *\u002F\n\n exports.decodeBase64Packet = function(msg, binaryType) {\n var type = packetslist[msg.charAt(0)];\n if (!base64encoder) {\n return { type: type, data: { base64: true, data: msg.substr(1) } };\n }\n\n var data = base64encoder.decode(msg.substr(1));\n\n if (binaryType === 'blob' && blob) {\n data = new blob([data]);\n }\n\n return { type: type, data: data };\n };\n\n \u002F**\n * Encodes multiple messages (payload).\n *\n * \u003Clength\u003E:data\n *\n * Example:\n *\n * 11:hello world2:hi\n *\n * If any contents are binary, they will be encoded as base64 strings. Base64\n * encoded strings are marked with a b before the length specifier\n *\n * @param {Array} packets\n * @api private\n *\u002F\n\n exports.encodePayload = function (packets, supportsBinary, callback) {\n if (typeof supportsBinary === 'function') {\n callback = supportsBinary;\n supportsBinary = null;\n }\n\n var isBinary = hasBinary2(packets);\n\n if (supportsBinary && isBinary) {\n if (blob && !dontSendBlobs) {\n return exports.encodePayloadAsBlob(packets, callback);\n }\n\n return exports.encodePayloadAsArrayBuffer(packets, callback);\n }\n\n if (!packets.length) {\n return callback('0:');\n }\n\n function setLengthHeader(message) {\n return message.length + ':' + message;\n }\n\n function encodeOne(packet, doneCallback) {\n exports.encodePacket(packet, !isBinary ? false : supportsBinary, false, function(message) {\n doneCallback(null, setLengthHeader(message));\n });\n }\n\n map(packets, encodeOne, function(err, results) {\n return callback(results.join(''));\n });\n };\n\n \u002F**\n * Async array map using after\n *\u002F\n\n function map(ary, each, done) {\n var result = new Array(ary.length);\n var next = after_1(ary.length, done);\n\n var eachWithIndex = function(i, el, cb) {\n each(el, function(error, msg) {\n result[i] = msg;\n cb(error, result);\n });\n };\n\n for (var i = 0; i \u003C ary.length; i++) {\n eachWithIndex(i, ary[i], next);\n }\n }\n\n \u002F*\n * Decodes data when a payload is maybe expected. Possible binary contents are\n * decoded from their base64 representation\n *\n * @param {String} data, callback method\n * @api public\n *\u002F\n\n exports.decodePayload = function (data, binaryType, callback) {\n if (typeof data !== 'string') {\n return exports.decodePayloadAsBinary(data, binaryType, callback);\n }\n\n if (typeof binaryType === 'function') {\n callback = binaryType;\n binaryType = null;\n }\n\n var packet;\n if (data === '') {\n \u002F\u002F parser error - ignoring payload\n return callback(err, 0, 1);\n }\n\n var length = '', n, msg;\n\n for (var i = 0, l = data.length; i \u003C l; i++) {\n var chr = data.charAt(i);\n\n if (chr !== ':') {\n length += chr;\n continue;\n }\n\n if (length === '' || (length != (n = Number(length)))) {\n \u002F\u002F parser error - ignoring payload\n return callback(err, 0, 1);\n }\n\n msg = data.substr(i + 1, n);\n\n if (length != msg.length) {\n \u002F\u002F parser error - ignoring payload\n return callback(err, 0, 1);\n }\n\n if (msg.length) {\n packet = exports.decodePacket(msg, binaryType, false);\n\n if (err.type === packet.type && err.data === packet.data) {\n \u002F\u002F parser error in individual packet - ignoring payload\n return callback(err, 0, 1);\n }\n\n var ret = callback(packet, i + n, l);\n if (false === ret) return;\n }\n\n \u002F\u002F advance cursor\n i += n;\n length = '';\n }\n\n if (length !== '') {\n \u002F\u002F parser error - ignoring payload\n return callback(err, 0, 1);\n }\n\n };\n\n \u002F**\n * Encodes multiple messages (payload) as binary.\n *\n * \u003C1 = binary, 0 = string\u003E\u003Cnumber from 0-9\u003E\u003Cnumber from 0-9\u003E[...]\u003Cnumber\n * 255\u003E\u003Cdata\u003E\n *\n * Example:\n * 1 3 255 1 2 3, if the binary contents are interpreted as 8 bit integers\n *\n * @param {Array} packets\n * @return {ArrayBuffer} encoded payload\n * @api private\n *\u002F\n\n exports.encodePayloadAsArrayBuffer = function(packets, callback) {\n if (!packets.length) {\n return callback(new ArrayBuffer(0));\n }\n\n function encodeOne(packet, doneCallback) {\n exports.encodePacket(packet, true, true, function(data) {\n return doneCallback(null, data);\n });\n }\n\n map(packets, encodeOne, function(err, encodedPackets) {\n var totalLength = encodedPackets.reduce(function(acc, p) {\n var len;\n if (typeof p === 'string'){\n len = p.length;\n } else {\n len = p.byteLength;\n }\n return acc + len.toString().length + len + 2; \u002F\u002F string\u002Fbinary identifier + separator = 2\n }, 0);\n\n var resultArray = new Uint8Array(totalLength);\n\n var bufferIndex = 0;\n encodedPackets.forEach(function(p) {\n var isString = typeof p === 'string';\n var ab = p;\n if (isString) {\n var view = new Uint8Array(p.length);\n for (var i = 0; i \u003C p.length; i++) {\n view[i] = p.charCodeAt(i);\n }\n ab = view.buffer;\n }\n\n if (isString) { \u002F\u002F not true binary\n resultArray[bufferIndex++] = 0;\n } else { \u002F\u002F true binary\n resultArray[bufferIndex++] = 1;\n }\n\n var lenStr = ab.byteLength.toString();\n for (var i = 0; i \u003C lenStr.length; i++) {\n resultArray[bufferIndex++] = parseInt(lenStr[i]);\n }\n resultArray[bufferIndex++] = 255;\n\n var view = new Uint8Array(ab);\n for (var i = 0; i \u003C view.length; i++) {\n resultArray[bufferIndex++] = view[i];\n }\n });\n\n return callback(resultArray.buffer);\n });\n };\n\n \u002F**\n * Encode as Blob\n *\u002F\n\n exports.encodePayloadAsBlob = function(packets, callback) {\n function encodeOne(packet, doneCallback) {\n exports.encodePacket(packet, true, true, function(encoded) {\n var binaryIdentifier = new Uint8Array(1);\n binaryIdentifier[0] = 1;\n if (typeof encoded === 'string') {\n var view = new Uint8Array(encoded.length);\n for (var i = 0; i \u003C encoded.length; i++) {\n view[i] = encoded.charCodeAt(i);\n }\n encoded = view.buffer;\n binaryIdentifier[0] = 0;\n }\n\n var len = (encoded instanceof ArrayBuffer)\n ? encoded.byteLength\n : encoded.size;\n\n var lenStr = len.toString();\n var lengthAry = new Uint8Array(lenStr.length + 1);\n for (var i = 0; i \u003C lenStr.length; i++) {\n lengthAry[i] = parseInt(lenStr[i]);\n }\n lengthAry[lenStr.length] = 255;\n\n if (blob) {\n var blob$1 = new blob([binaryIdentifier.buffer, lengthAry.buffer, encoded]);\n doneCallback(null, blob$1);\n }\n });\n }\n\n map(packets, encodeOne, function(err, results) {\n return callback(new blob(results));\n });\n };\n\n \u002F*\n * Decodes data when a payload is maybe expected. Strings are decoded by\n * interpreting each byte as a key code for entries marked to start with 0. See\n * description of encodePayloadAsBinary\n *\n * @param {ArrayBuffer} data, callback method\n * @api public\n *\u002F\n\n exports.decodePayloadAsBinary = function (data, binaryType, callback) {\n if (typeof binaryType === 'function') {\n callback = binaryType;\n binaryType = null;\n }\n\n var bufferTail = data;\n var buffers = [];\n\n while (bufferTail.byteLength \u003E 0) {\n var tailArray = new Uint8Array(bufferTail);\n var isString = tailArray[0] === 0;\n var msgLength = '';\n\n for (var i = 1; ; i++) {\n if (tailArray[i] === 255) break;\n\n \u002F\u002F 310 = char length of Number.MAX_VALUE\n if (msgLength.length \u003E 310) {\n return callback(err, 0, 1);\n }\n\n msgLength += tailArray[i];\n }\n\n bufferTail = arraybuffer_slice(bufferTail, 2 + msgLength.length);\n msgLength = parseInt(msgLength);\n\n var msg = arraybuffer_slice(bufferTail, 0, msgLength);\n if (isString) {\n try {\n msg = String.fromCharCode.apply(null, new Uint8Array(msg));\n } catch (e) {\n \u002F\u002F iPhone Safari doesn't let you apply to typed arrays\n var typed = new Uint8Array(msg);\n msg = '';\n for (var i = 0; i \u003C typed.length; i++) {\n msg += String.fromCharCode(typed[i]);\n }\n }\n }\n\n buffers.push(msg);\n bufferTail = arraybuffer_slice(bufferTail, msgLength);\n }\n\n var total = buffers.length;\n buffers.forEach(function(buffer, i) {\n callback(exports.decodePacket(buffer, binaryType, true), i, total);\n });\n };\n });\n var browser_1$2 = browser$2.protocol;\n var browser_2$2 = browser$2.packets;\n var browser_3$2 = browser$2.encodePacket;\n var browser_4$2 = browser$2.encodeBase64Packet;\n var browser_5$2 = browser$2.decodePacket;\n var browser_6$2 = browser$2.decodeBase64Packet;\n var browser_7$2 = browser$2.encodePayload;\n var browser_8 = browser$2.decodePayload;\n var browser_9 = browser$2.encodePayloadAsArrayBuffer;\n var browser_10 = browser$2.encodePayloadAsBlob;\n var browser_11 = browser$2.decodePayloadAsBinary;\n\n \u002F**\n * Module dependencies.\n *\u002F\n\n\n\n\n \u002F**\n * Module exports.\n *\u002F\n\n var transport = Transport;\n\n \u002F**\n * Transport abstract constructor.\n *\n * @param {Object} options.\n * @api private\n *\u002F\n\n function Transport (opts) {\n this.path = opts.path;\n this.hostname = opts.hostname;\n this.port = opts.port;\n this.secure = opts.secure;\n this.query = opts.query;\n this.timestampParam = opts.timestampParam;\n this.timestampRequests = opts.timestampRequests;\n this.readyState = '';\n this.agent = opts.agent || false;\n this.socket = opts.socket;\n this.enablesXDR = opts.enablesXDR;\n this.withCredentials = opts.withCredentials;\n\n \u002F\u002F SSL options for Node.js client\n this.pfx = opts.pfx;\n this.key = opts.key;\n this.passphrase = opts.passphrase;\n this.cert = opts.cert;\n this.ca = opts.ca;\n this.ciphers = opts.ciphers;\n this.rejectUnauthorized = opts.rejectUnauthorized;\n this.forceNode = opts.forceNode;\n\n \u002F\u002F results of ReactNative environment detection\n this.isReactNative = opts.isReactNative;\n\n \u002F\u002F other options for Node.js client\n this.extraHeaders = opts.extraHeaders;\n this.localAddress = opts.localAddress;\n }\n\n \u002F**\n * Mix in `Emitter`.\n *\u002F\n\n componentEmitter(Transport.prototype);\n\n \u002F**\n * Emits an error.\n *\n * @param {String} str\n * @return {Transport} for chaining\n * @api public\n *\u002F\n\n Transport.prototype.onError = function (msg, desc) {\n var err = new Error(msg);\n err.type = 'TransportError';\n err.description = desc;\n this.emit('error', err);\n return this;\n };\n\n \u002F**\n * Opens the transport.\n *\n * @api public\n *\u002F\n\n Transport.prototype.open = function () {\n if ('closed' === this.readyState || '' === this.readyState) {\n this.readyState = 'opening';\n this.doOpen();\n }\n\n return this;\n };\n\n \u002F**\n * Closes the transport.\n *\n * @api private\n *\u002F\n\n Transport.prototype.close = function () {\n if ('opening' === this.readyState || 'open' === this.readyState) {\n this.doClose();\n this.onClose();\n }\n\n return this;\n };\n\n \u002F**\n * Sends multiple packets.\n *\n * @param {Array} packets\n * @api private\n *\u002F\n\n Transport.prototype.send = function (packets) {\n if ('open' === this.readyState) {\n this.write(packets);\n } else {\n throw new Error('Transport not open');\n }\n };\n\n \u002F**\n * Called upon open\n *\n * @api private\n *\u002F\n\n Transport.prototype.onOpen = function () {\n this.readyState = 'open';\n this.writable = true;\n this.emit('open');\n };\n\n \u002F**\n * Called with data.\n *\n * @param {String} data\n * @api private\n *\u002F\n\n Transport.prototype.onData = function (data) {\n var packet = browser$2.decodePacket(data, this.socket.binaryType);\n this.onPacket(packet);\n };\n\n \u002F**\n * Called with a decoded packet.\n *\u002F\n\n Transport.prototype.onPacket = function (packet) {\n this.emit('packet', packet);\n };\n\n \u002F**\n * Called upon close.\n *\n * @api private\n *\u002F\n\n Transport.prototype.onClose = function () {\n this.readyState = 'closed';\n this.emit('close');\n };\n\n \u002F**\n * Compiles a querystring\n * Returns string representation of the object\n *\n * @param {Object}\n * @api private\n *\u002F\n\n var encode = function (obj) {\n var str = '';\n\n for (var i in obj) {\n if (obj.hasOwnProperty(i)) {\n if (str.length) str += '&';\n str += encodeURIComponent(i) + '=' + encodeURIComponent(obj[i]);\n }\n }\n\n return str;\n };\n\n \u002F**\n * Parses a simple querystring into an object\n *\n * @param {String} qs\n * @api private\n *\u002F\n\n var decode = function(qs){\n var qry = {};\n var pairs = qs.split('&');\n for (var i = 0, l = pairs.length; i \u003C l; i++) {\n var pair = pairs[i].split('=');\n qry[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);\n }\n return qry;\n };\n\n var parseqs = {\n encode: encode,\n decode: decode\n };\n\n var componentInherit = function(a, b){\n var fn = function(){};\n fn.prototype = b.prototype;\n a.prototype = new fn;\n a.prototype.constructor = a;\n };\n\n var alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_'.split('')\n , length = 64\n , map = {}\n , seed = 0\n , i$1 = 0\n , prev;\n\n \u002F**\n * Return a string representing the specified number.\n *\n * @param {Number} num The number to convert.\n * @returns {String} The string representation of the number.\n * @api public\n *\u002F\n function encode$1(num) {\n var encoded = '';\n\n do {\n encoded = alphabet[num % length] + encoded;\n num = Math.floor(num \u002F length);\n } while (num \u003E 0);\n\n return encoded;\n }\n\n \u002F**\n * Return the integer value specified by the given string.\n *\n * @param {String} str The string to convert.\n * @returns {Number} The integer value represented by the string.\n * @api public\n *\u002F\n function decode$1(str) {\n var decoded = 0;\n\n for (i$1 = 0; i$1 \u003C str.length; i$1++) {\n decoded = decoded * length + map[str.charAt(i$1)];\n }\n\n return decoded;\n }\n\n \u002F**\n * Yeast: A tiny growing id generator.\n *\n * @returns {String} A unique id.\n * @api public\n *\u002F\n function yeast() {\n var now = encode$1(+new Date());\n\n if (now !== prev) return seed = 0, prev = now;\n return now +'.'+ encode$1(seed++);\n }\n\n \u002F\u002F\n \u002F\u002F Map each character to its index.\n \u002F\u002F\n for (; i$1 \u003C length; i$1++) map[alphabet[i$1]] = i$1;\n\n \u002F\u002F\n \u002F\u002F Expose the `yeast`, `encode` and `decode` functions.\n \u002F\u002F\n yeast.encode = encode$1;\n yeast.decode = decode$1;\n var yeast_1 = yeast;\n\n \u002F**\n * Module dependencies.\n *\u002F\n\n\n\n\n\n\n var debug$2 = browser('engine.io-client:polling');\n\n \u002F**\n * Module exports.\n *\u002F\n\n var polling = Polling;\n\n \u002F**\n * Is XHR2 supported?\n *\u002F\n\n var hasXHR2 = (function () {\n var XMLHttpRequest = xmlhttprequest;\n var xhr = new XMLHttpRequest({ xdomain: false });\n return null != xhr.responseType;\n })();\n\n \u002F**\n * Polling interface.\n *\n * @param {Object} opts\n * @api private\n *\u002F\n\n function Polling (opts) {\n var forceBase64 = (opts && opts.forceBase64);\n if (!hasXHR2 || forceBase64) {\n this.supportsBinary = false;\n }\n transport.call(this, opts);\n }\n\n \u002F**\n * Inherits from Transport.\n *\u002F\n\n componentInherit(Polling, transport);\n\n \u002F**\n * Transport name.\n *\u002F\n\n Polling.prototype.name = 'polling';\n\n \u002F**\n * Opens the socket (triggers polling). We write a PING message to determine\n * when the transport is open.\n *\n * @api private\n *\u002F\n\n Polling.prototype.doOpen = function () {\n this.poll();\n };\n\n \u002F**\n * Pauses polling.\n *\n * @param {Function} callback upon buffers are flushed and transport is paused\n * @api private\n *\u002F\n\n Polling.prototype.pause = function (onPause) {\n var self = this;\n\n this.readyState = 'pausing';\n\n function pause () {\n debug$2('paused');\n self.readyState = 'paused';\n onPause();\n }\n\n if (this.polling || !this.writable) {\n var total = 0;\n\n if (this.polling) {\n debug$2('we are currently polling - waiting to pause');\n total++;\n this.once('pollComplete', function () {\n debug$2('pre-pause polling complete');\n --total || pause();\n });\n }\n\n if (!this.writable) {\n debug$2('we are currently writing - waiting to pause');\n total++;\n this.once('drain', function () {\n debug$2('pre-pause writing complete');\n --total || pause();\n });\n }\n } else {\n pause();\n }\n };\n\n \u002F**\n * Starts polling cycle.\n *\n * @api public\n *\u002F\n\n Polling.prototype.poll = function () {\n debug$2('polling');\n this.polling = true;\n this.doPoll();\n this.emit('poll');\n };\n\n \u002F**\n * Overloads onData to detect payloads.\n *\n * @api private\n *\u002F\n\n Polling.prototype.onData = function (data) {\n var self = this;\n debug$2('polling got data %s', data);\n var callback = function (packet, index, total) {\n \u002F\u002F if its the first message we consider the transport open\n if ('opening' === self.readyState) {\n self.onOpen();\n }\n\n \u002F\u002F if its a close packet, we close the ongoing requests\n if ('close' === packet.type) {\n self.onClose();\n return false;\n }\n\n \u002F\u002F otherwise bypass onData and handle the message\n self.onPacket(packet);\n };\n\n \u002F\u002F decode payload\n browser$2.decodePayload(data, this.socket.binaryType, callback);\n\n \u002F\u002F if an event did not trigger closing\n if ('closed' !== this.readyState) {\n \u002F\u002F if we got data we're not polling\n this.polling = false;\n this.emit('pollComplete');\n\n if ('open' === this.readyState) {\n this.poll();\n } else {\n debug$2('ignoring poll - transport state \"%s\"', this.readyState);\n }\n }\n };\n\n \u002F**\n * For polling, send a close packet.\n *\n * @api private\n *\u002F\n\n Polling.prototype.doClose = function () {\n var self = this;\n\n function close () {\n debug$2('writing close packet');\n self.write([{ type: 'close' }]);\n }\n\n if ('open' === this.readyState) {\n debug$2('transport open - closing');\n close();\n } else {\n \u002F\u002F in case we're trying to close while\n \u002F\u002F handshaking is in progress (GH-164)\n debug$2('transport not open - deferring close');\n this.once('open', close);\n }\n };\n\n \u002F**\n * Writes a packets payload.\n *\n * @param {Array} data packets\n * @param {Function} drain callback\n * @api private\n *\u002F\n\n Polling.prototype.write = function (packets) {\n var self = this;\n this.writable = false;\n var callbackfn = function () {\n self.writable = true;\n self.emit('drain');\n };\n\n browser$2.encodePayload(packets, this.supportsBinary, function (data) {\n self.doWrite(data, callbackfn);\n });\n };\n\n \u002F**\n * Generates uri for connection.\n *\n * @api private\n *\u002F\n\n Polling.prototype.uri = function () {\n var query = this.query || {};\n var schema = this.secure ? 'https' : 'http';\n var port = '';\n\n \u002F\u002F cache busting is forced\n if (false !== this.timestampRequests) {\n query[this.timestampParam] = yeast_1();\n }\n\n if (!this.supportsBinary && !query.sid) {\n query.b64 = 1;\n }\n\n query = parseqs.encode(query);\n\n \u002F\u002F avoid port if default for schema\n if (this.port && (('https' === schema && Number(this.port) !== 443) ||\n ('http' === schema && Number(this.port) !== 80))) {\n port = ':' + this.port;\n }\n\n \u002F\u002F prepend ? to query\n if (query.length) {\n query = '?' + query;\n }\n\n var ipv6 = this.hostname.indexOf(':') !== -1;\n return schema + ':\u002F\u002F' + (ipv6 ? '[' + this.hostname + ']' : this.hostname) + port + this.path + query;\n };\n\n \u002F* global attachEvent *\u002F\n\n \u002F**\n * Module requirements.\n *\u002F\n\n\n\n\n\n var debug$3 = browser('engine.io-client:polling-xhr');\n\n \u002F**\n * Module exports.\n *\u002F\n\n var pollingXhr = XHR;\n var Request_1 = Request;\n\n \u002F**\n * Empty function\n *\u002F\n\n function empty$1 () {}\n\n \u002F**\n * XHR Polling constructor.\n *\n * @param {Object} opts\n * @api public\n *\u002F\n\n function XHR (opts) {\n polling.call(this, opts);\n this.requestTimeout = opts.requestTimeout;\n this.extraHeaders = opts.extraHeaders;\n\n if (typeof location !== 'undefined') {\n var isSSL = 'https:' === location.protocol;\n var port = location.port;\n\n \u002F\u002F some user agents have empty `location.port`\n if (!port) {\n port = isSSL ? 443 : 80;\n }\n\n this.xd = (typeof location !== 'undefined' && opts.hostname !== location.hostname) ||\n port !== opts.port;\n this.xs = opts.secure !== isSSL;\n }\n }\n\n \u002F**\n * Inherits from Polling.\n *\u002F\n\n componentInherit(XHR, polling);\n\n \u002F**\n * XHR supports binary\n *\u002F\n\n XHR.prototype.supportsBinary = true;\n\n \u002F**\n * Creates a request.\n *\n * @param {String} method\n * @api private\n *\u002F\n\n XHR.prototype.request = function (opts) {\n opts = opts || {};\n opts.uri = this.uri();\n opts.xd = this.xd;\n opts.xs = this.xs;\n opts.agent = this.agent || false;\n opts.supportsBinary = this.supportsBinary;\n opts.enablesXDR = this.enablesXDR;\n opts.withCredentials = this.withCredentials;\n\n \u002F\u002F SSL options for Node.js client\n opts.pfx = this.pfx;\n opts.key = this.key;\n opts.passphrase = this.passphrase;\n opts.cert = this.cert;\n opts.ca = this.ca;\n opts.ciphers = this.ciphers;\n opts.rejectUnauthorized = this.rejectUnauthorized;\n opts.requestTimeout = this.requestTimeout;\n\n \u002F\u002F other options for Node.js client\n opts.extraHeaders = this.extraHeaders;\n\n return new Request(opts);\n };\n\n \u002F**\n * Sends data.\n *\n * @param {String} data to send.\n * @param {Function} called upon flush.\n * @api private\n *\u002F\n\n XHR.prototype.doWrite = function (data, fn) {\n var isBinary = typeof data !== 'string' && data !== undefined;\n var req = this.request({ method: 'POST', data: data, isBinary: isBinary });\n var self = this;\n req.on('success', fn);\n req.on('error', function (err) {\n self.onError('xhr post error', err);\n });\n this.sendXhr = req;\n };\n\n \u002F**\n * Starts a poll cycle.\n *\n * @api private\n *\u002F\n\n XHR.prototype.doPoll = function () {\n debug$3('xhr poll');\n var req = this.request();\n var self = this;\n req.on('data', function (data) {\n self.onData(data);\n });\n req.on('error', function (err) {\n self.onError('xhr poll error', err);\n });\n this.pollXhr = req;\n };\n\n \u002F**\n * Request constructor\n *\n * @param {Object} options\n * @api public\n *\u002F\n\n function Request (opts) {\n this.method = opts.method || 'GET';\n this.uri = opts.uri;\n this.xd = !!opts.xd;\n this.xs = !!opts.xs;\n this.async = false !== opts.async;\n this.data = undefined !== opts.data ? opts.data : null;\n this.agent = opts.agent;\n this.isBinary = opts.isBinary;\n this.supportsBinary = opts.supportsBinary;\n this.enablesXDR = opts.enablesXDR;\n this.withCredentials = opts.withCredentials;\n this.requestTimeout = opts.requestTimeout;\n\n \u002F\u002F SSL options for Node.js client\n this.pfx = opts.pfx;\n this.key = opts.key;\n this.passphrase = opts.passphrase;\n this.cert = opts.cert;\n this.ca = opts.ca;\n this.ciphers = opts.ciphers;\n this.rejectUnauthorized = opts.rejectUnauthorized;\n\n \u002F\u002F other options for Node.js client\n this.extraHeaders = opts.extraHeaders;\n\n this.create();\n }\n\n \u002F**\n * Mix in `Emitter`.\n *\u002F\n\n componentEmitter(Request.prototype);\n\n \u002F**\n * Creates the XHR object and sends the request.\n *\n * @api private\n *\u002F\n\n Request.prototype.create = function () {\n var opts = { agent: this.agent, xdomain: this.xd, xscheme: this.xs, enablesXDR: this.enablesXDR };\n\n \u002F\u002F SSL options for Node.js client\n opts.pfx = this.pfx;\n opts.key = this.key;\n opts.passphrase = this.passphrase;\n opts.cert = this.cert;\n opts.ca = this.ca;\n opts.ciphers = this.ciphers;\n opts.rejectUnauthorized = this.rejectUnauthorized;\n\n var xhr = this.xhr = new xmlhttprequest(opts);\n var self = this;\n\n try {\n debug$3('xhr open %s: %s', this.method, this.uri);\n xhr.open(this.method, this.uri, this.async);\n try {\n if (this.extraHeaders) {\n xhr.setDisableHeaderCheck && xhr.setDisableHeaderCheck(true);\n for (var i in this.extraHeaders) {\n if (this.extraHeaders.hasOwnProperty(i)) {\n xhr.setRequestHeader(i, this.extraHeaders[i]);\n }\n }\n }\n } catch (e) {}\n\n if ('POST' === this.method) {\n try {\n if (this.isBinary) {\n xhr.setRequestHeader('Content-type', 'application\u002Foctet-stream');\n } else {\n xhr.setRequestHeader('Content-type', 'text\u002Fplain;charset=UTF-8');\n }\n } catch (e) {}\n }\n\n try {\n xhr.setRequestHeader('Accept', '*\u002F*');\n } catch (e) {}\n\n \u002F\u002F ie6 check\n if ('withCredentials' in xhr) {\n xhr.withCredentials = this.withCredentials;\n }\n\n if (this.requestTimeout) {\n xhr.timeout = this.requestTimeout;\n }\n\n if (this.hasXDR()) {\n xhr.onload = function () {\n self.onLoad();\n };\n xhr.onerror = function () {\n self.onError(xhr.responseText);\n };\n } else {\n xhr.onreadystatechange = function () {\n if (xhr.readyState === 2) {\n try {\n var contentType = xhr.getResponseHeader('Content-Type');\n if (self.supportsBinary && contentType === 'application\u002Foctet-stream' || contentType === 'application\u002Foctet-stream; charset=UTF-8') {\n xhr.responseType = 'arraybuffer';\n }\n } catch (e) {}\n }\n if (4 !== xhr.readyState) return;\n if (200 === xhr.status || 1223 === xhr.status) {\n self.onLoad();\n } else {\n \u002F\u002F make sure the `error` event handler that's user-set\n \u002F\u002F does not throw in the same tick and gets caught here\n setTimeout(function () {\n self.onError(typeof xhr.status === 'number' ? xhr.status : 0);\n }, 0);\n }\n };\n }\n\n debug$3('xhr data %s', this.data);\n xhr.send(this.data);\n } catch (e) {\n \u002F\u002F Need to defer since .create() is called directly fhrom the constructor\n \u002F\u002F and thus the 'error' event can only be only bound *after* this exception\n \u002F\u002F occurs. Therefore, also, we cannot throw here at all.\n setTimeout(function () {\n self.onError(e);\n }, 0);\n return;\n }\n\n if (typeof document !== 'undefined') {\n this.index = Request.requestsCount++;\n Request.requests[this.index] = this;\n }\n };\n\n \u002F**\n * Called upon successful response.\n *\n * @api private\n *\u002F\n\n Request.prototype.onSuccess = function () {\n this.emit('success');\n this.cleanup();\n };\n\n \u002F**\n * Called if we have data.\n *\n * @api private\n *\u002F\n\n Request.prototype.onData = function (data) {\n this.emit('data', data);\n this.onSuccess();\n };\n\n \u002F**\n * Called upon error.\n *\n * @api private\n *\u002F\n\n Request.prototype.onError = function (err) {\n this.emit('error', err);\n this.cleanup(true);\n };\n\n \u002F**\n * Cleans up house.\n *\n * @api private\n *\u002F\n\n Request.prototype.cleanup = function (fromError) {\n if ('undefined' === typeof this.xhr || null === this.xhr) {\n return;\n }\n \u002F\u002F xmlhttprequest\n if (this.hasXDR()) {\n this.xhr.onload = this.xhr.onerror = empty$1;\n } else {\n this.xhr.onreadystatechange = empty$1;\n }\n\n if (fromError) {\n try {\n this.xhr.abort();\n } catch (e) {}\n }\n\n if (typeof document !== 'undefined') {\n delete Request.requests[this.index];\n }\n\n this.xhr = null;\n };\n\n \u002F**\n * Called upon load.\n *\n * @api private\n *\u002F\n\n Request.prototype.onLoad = function () {\n var data;\n try {\n var contentType;\n try {\n contentType = this.xhr.getResponseHeader('Content-Type');\n } catch (e) {}\n if (contentType === 'application\u002Foctet-stream' || contentType === 'application\u002Foctet-stream; charset=UTF-8') {\n data = this.xhr.response || this.xhr.responseText;\n } else {\n data = this.xhr.responseText;\n }\n } catch (e) {\n this.onError(e);\n }\n if (null != data) {\n this.onData(data);\n }\n };\n\n \u002F**\n * Check if it has XDomainRequest.\n *\n * @api private\n *\u002F\n\n Request.prototype.hasXDR = function () {\n return typeof XDomainRequest !== 'undefined' && !this.xs && this.enablesXDR;\n };\n\n \u002F**\n * Aborts the request.\n *\n * @api public\n *\u002F\n\n Request.prototype.abort = function () {\n this.cleanup();\n };\n\n \u002F**\n * Aborts pending requests when unloading the window. This is needed to prevent\n * memory leaks (e.g. when using IE) and to ensure that no spurious error is\n * emitted.\n *\u002F\n\n Request.requestsCount = 0;\n Request.requests = {};\n\n if (typeof document !== 'undefined') {\n if (typeof attachEvent === 'function') {\n attachEvent('onunload', unloadHandler);\n } else if (typeof addEventListener === 'function') {\n var terminationEvent = 'onpagehide' in self ? 'pagehide' : 'unload';\n addEventListener(terminationEvent, unloadHandler, false);\n }\n }\n\n function unloadHandler () {\n for (var i in Request.requests) {\n if (Request.requests.hasOwnProperty(i)) {\n Request.requests[i].abort();\n }\n }\n }\n pollingXhr.Request = Request_1;\n\n \u002F**\n * Module requirements.\n *\u002F\n\n\n\n\n \u002F**\n * Module exports.\n *\u002F\n\n var pollingJsonp = JSONPPolling;\n\n \u002F**\n * Cached regular expressions.\n *\u002F\n\n var rNewline = \u002F\\n\u002Fg;\n var rEscapedNewline = \u002F\\\\n\u002Fg;\n\n \u002F**\n * Global JSONP callbacks.\n *\u002F\n\n var callbacks;\n\n \u002F**\n * Noop.\n *\u002F\n\n function empty$2 () { }\n\n \u002F**\n * Until https:\u002F\u002Fgithub.com\u002Ftc39\u002Fproposal-global is shipped.\n *\u002F\n function glob () {\n return typeof self !== 'undefined' ? self\n : typeof window !== 'undefined' ? window\n : typeof commonjsGlobal !== 'undefined' ? commonjsGlobal : {};\n }\n\n \u002F**\n * JSONP Polling constructor.\n *\n * @param {Object} opts.\n * @api public\n *\u002F\n\n function JSONPPolling (opts) {\n polling.call(this, opts);\n\n this.query = this.query || {};\n\n \u002F\u002F define global callbacks array if not present\n \u002F\u002F we do this here (lazily) to avoid unneeded global pollution\n if (!callbacks) {\n \u002F\u002F we need to consider multiple engines in the same page\n var global = glob();\n callbacks = global.___eio = (global.___eio || []);\n }\n\n \u002F\u002F callback identifier\n this.index = callbacks.length;\n\n \u002F\u002F add callback to jsonp global\n var self = this;\n callbacks.push(function (msg) {\n self.onData(msg);\n });\n\n \u002F\u002F append to query string\n this.query.j = this.index;\n\n \u002F\u002F prevent spurious errors from being emitted when the window is unloaded\n if (typeof addEventListener === 'function') {\n addEventListener('beforeunload', function () {\n if (self.script) self.script.onerror = empty$2;\n }, false);\n }\n }\n\n \u002F**\n * Inherits from Polling.\n *\u002F\n\n componentInherit(JSONPPolling, polling);\n\n \u002F*\n * JSONP only supports binary as base64 encoded strings\n *\u002F\n\n JSONPPolling.prototype.supportsBinary = false;\n\n \u002F**\n * Closes the socket.\n *\n * @api private\n *\u002F\n\n JSONPPolling.prototype.doClose = function () {\n if (this.script) {\n this.script.parentNode.removeChild(this.script);\n this.script = null;\n }\n\n if (this.form) {\n this.form.parentNode.removeChild(this.form);\n this.form = null;\n this.iframe = null;\n }\n\n polling.prototype.doClose.call(this);\n };\n\n \u002F**\n * Starts a poll cycle.\n *\n * @api private\n *\u002F\n\n JSONPPolling.prototype.doPoll = function () {\n var self = this;\n var script = document.createElement('script');\n\n if (this.script) {\n this.script.parentNode.removeChild(this.script);\n this.script = null;\n }\n\n script.async = true;\n script.src = this.uri();\n script.onerror = function (e) {\n self.onError('jsonp poll error', e);\n };\n\n var insertAt = document.getElementsByTagName('script')[0];\n if (insertAt) {\n insertAt.parentNode.insertBefore(script, insertAt);\n } else {\n (document.head || document.body).appendChild(script);\n }\n this.script = script;\n\n var isUAgecko = 'undefined' !== typeof navigator && \u002Fgecko\u002Fi.test(navigator.userAgent);\n\n if (isUAgecko) {\n setTimeout(function () {\n var iframe = document.createElement('iframe');\n document.body.appendChild(iframe);\n document.body.removeChild(iframe);\n }, 100);\n }\n };\n\n \u002F**\n * Writes with a hidden iframe.\n *\n * @param {String} data to send\n * @param {Function} called upon flush.\n * @api private\n *\u002F\n\n JSONPPolling.prototype.doWrite = function (data, fn) {\n var self = this;\n\n if (!this.form) {\n var form = document.createElement('form');\n var area = document.createElement('textarea');\n var id = this.iframeId = 'eio_iframe_' + this.index;\n var iframe;\n\n form.className = 'socketio';\n form.style.position = 'absolute';\n form.style.top = '-1000px';\n form.style.left = '-1000px';\n form.target = id;\n form.method = 'POST';\n form.setAttribute('accept-charset', 'utf-8');\n area.name = 'd';\n form.appendChild(area);\n document.body.appendChild(form);\n\n this.form = form;\n this.area = area;\n }\n\n this.form.action = this.uri();\n\n function complete () {\n initIframe();\n fn();\n }\n\n function initIframe () {\n if (self.iframe) {\n try {\n self.form.removeChild(self.iframe);\n } catch (e) {\n self.onError('jsonp polling iframe removal error', e);\n }\n }\n\n try {\n \u002F\u002F ie6 dynamic iframes with target=\"\" support (thanks Chris Lambacher)\n var html = '\u003Ciframe src=\"javascript:0\" name=\"' + self.iframeId + '\"\u003E';\n iframe = document.createElement(html);\n } catch (e) {\n iframe = document.createElement('iframe');\n iframe.name = self.iframeId;\n iframe.src = 'javascript:0';\n }\n\n iframe.id = self.iframeId;\n\n self.form.appendChild(iframe);\n self.iframe = iframe;\n }\n\n initIframe();\n\n \u002F\u002F escape \\n to prevent it from being converted into \\r\\n by some UAs\n \u002F\u002F double escaping is required for escaped new lines because unescaping of new lines can be done safely on server-side\n data = data.replace(rEscapedNewline, '\\\\\\n');\n this.area.value = data.replace(rNewline, '\\\\n');\n\n try {\n this.form.submit();\n } catch (e) {}\n\n if (this.iframe.attachEvent) {\n this.iframe.onreadystatechange = function () {\n if (self.iframe.readyState === 'complete') {\n complete();\n }\n };\n } else {\n this.iframe.onload = complete;\n }\n };\n\n var _nodeResolve_empty = {};\n\n var _nodeResolve_empty$1 = \u002F*#__PURE__*\u002FObject.freeze({\n __proto__: null,\n 'default': _nodeResolve_empty\n });\n\n var require$1 = getCjsExportFromNamespace(_nodeResolve_empty$1);\n\n \u002F**\n * Module dependencies.\n *\u002F\n\n\n\n\n\n\n var debug$4 = browser('engine.io-client:websocket');\n\n var BrowserWebSocket, NodeWebSocket;\n\n if (typeof WebSocket !== 'undefined') {\n BrowserWebSocket = WebSocket;\n } else if (typeof self !== 'undefined') {\n BrowserWebSocket = self.WebSocket || self.MozWebSocket;\n }\n\n if (typeof window === 'undefined') {\n try {\n NodeWebSocket = require$1;\n } catch (e) { }\n }\n\n \u002F**\n * Get either the `WebSocket` or `MozWebSocket` globals\n * in the browser or try to resolve WebSocket-compatible\n * interface exposed by `ws` for Node-like environment.\n *\u002F\n\n var WebSocketImpl = BrowserWebSocket || NodeWebSocket;\n\n \u002F**\n * Module exports.\n *\u002F\n\n var websocket = WS;\n\n \u002F**\n * WebSocket transport constructor.\n *\n * @api {Object} connection options\n * @api public\n *\u002F\n\n function WS (opts) {\n var forceBase64 = (opts && opts.forceBase64);\n if (forceBase64) {\n this.supportsBinary = false;\n }\n this.perMessageDeflate = opts.perMessageDeflate;\n this.usingBrowserWebSocket = BrowserWebSocket && !opts.forceNode;\n this.protocols = opts.protocols;\n if (!this.usingBrowserWebSocket) {\n WebSocketImpl = NodeWebSocket;\n }\n transport.call(this, opts);\n }\n\n \u002F**\n * Inherits from Transport.\n *\u002F\n\n componentInherit(WS, transport);\n\n \u002F**\n * Transport name.\n *\n * @api public\n *\u002F\n\n WS.prototype.name = 'websocket';\n\n \u002F*\n * WebSockets support binary\n *\u002F\n\n WS.prototype.supportsBinary = true;\n\n \u002F**\n * Opens socket.\n *\n * @api private\n *\u002F\n\n WS.prototype.doOpen = function () {\n if (!this.check()) {\n \u002F\u002F let probe timeout\n return;\n }\n\n var uri = this.uri();\n var protocols = this.protocols;\n var opts = {\n agent: this.agent,\n perMessageDeflate: this.perMessageDeflate\n };\n\n \u002F\u002F SSL options for Node.js client\n opts.pfx = this.pfx;\n opts.key = this.key;\n opts.passphrase = this.passphrase;\n opts.cert = this.cert;\n opts.ca = this.ca;\n opts.ciphers = this.ciphers;\n opts.rejectUnauthorized = this.rejectUnauthorized;\n if (this.extraHeaders) {\n opts.headers = this.extraHeaders;\n }\n if (this.localAddress) {\n opts.localAddress = this.localAddress;\n }\n\n try {\n this.ws =\n this.usingBrowserWebSocket && !this.isReactNative\n ? protocols\n ? new WebSocketImpl(uri, protocols)\n : new WebSocketImpl(uri)\n : new WebSocketImpl(uri, protocols, opts);\n } catch (err) {\n return this.emit('error', err);\n }\n\n if (this.ws.binaryType === undefined) {\n this.supportsBinary = false;\n }\n\n if (this.ws.supports && this.ws.supports.binary) {\n this.supportsBinary = true;\n this.ws.binaryType = 'nodebuffer';\n } else {\n this.ws.binaryType = 'arraybuffer';\n }\n\n this.addEventListeners();\n };\n\n \u002F**\n * Adds event listeners to the socket\n *\n * @api private\n *\u002F\n\n WS.prototype.addEventListeners = function () {\n var self = this;\n\n this.ws.onopen = function () {\n self.onOpen();\n };\n this.ws.onclose = function () {\n self.onClose();\n };\n this.ws.onmessage = function (ev) {\n self.onData(ev.data);\n };\n this.ws.onerror = function (e) {\n self.onError('websocket error', e);\n };\n };\n\n \u002F**\n * Writes data to socket.\n *\n * @param {Array} array of packets.\n * @api private\n *\u002F\n\n WS.prototype.write = function (packets) {\n var self = this;\n this.writable = false;\n\n \u002F\u002F encodePacket efficient as it uses WS framing\n \u002F\u002F no need for encodePayload\n var total = packets.length;\n for (var i = 0, l = total; i \u003C l; i++) {\n (function (packet) {\n browser$2.encodePacket(packet, self.supportsBinary, function (data) {\n if (!self.usingBrowserWebSocket) {\n \u002F\u002F always create a new object (GH-437)\n var opts = {};\n if (packet.options) {\n opts.compress = packet.options.compress;\n }\n\n if (self.perMessageDeflate) {\n var len = 'string' === typeof data ? Buffer.byteLength(data) : data.length;\n if (len \u003C self.perMessageDeflate.threshold) {\n opts.compress = false;\n }\n }\n }\n\n \u002F\u002F Sometimes the websocket has already been closed but the browser didn't\n \u002F\u002F have a chance of informing us about it yet, in that case send will\n \u002F\u002F throw an error\n try {\n if (self.usingBrowserWebSocket) {\n \u002F\u002F TypeError is thrown when passing the second argument on Safari\n self.ws.send(data);\n } else {\n self.ws.send(data, opts);\n }\n } catch (e) {\n debug$4('websocket closed before onclose event');\n }\n\n --total || done();\n });\n })(packets[i]);\n }\n\n function done () {\n self.emit('flush');\n\n \u002F\u002F fake drain\n \u002F\u002F defer to next tick to allow Socket to clear writeBuffer\n setTimeout(function () {\n self.writable = true;\n self.emit('drain');\n }, 0);\n }\n };\n\n \u002F**\n * Called upon close\n *\n * @api private\n *\u002F\n\n WS.prototype.onClose = function () {\n transport.prototype.onClose.call(this);\n };\n\n \u002F**\n * Closes socket.\n *\n * @api private\n *\u002F\n\n WS.prototype.doClose = function () {\n if (typeof this.ws !== 'undefined') {\n this.ws.close();\n }\n };\n\n \u002F**\n * Generates uri for connection.\n *\n * @api private\n *\u002F\n\n WS.prototype.uri = function () {\n var query = this.query || {};\n var schema = this.secure ? 'wss' : 'ws';\n var port = '';\n\n \u002F\u002F avoid port if default for schema\n if (this.port && (('wss' === schema && Number(this.port) !== 443) ||\n ('ws' === schema && Number(this.port) !== 80))) {\n port = ':' + this.port;\n }\n\n \u002F\u002F append timestamp to URI\n if (this.timestampRequests) {\n query[this.timestampParam] = yeast_1();\n }\n\n \u002F\u002F communicate binary support capabilities\n if (!this.supportsBinary) {\n query.b64 = 1;\n }\n\n query = parseqs.encode(query);\n\n \u002F\u002F prepend ? to query\n if (query.length) {\n query = '?' + query;\n }\n\n var ipv6 = this.hostname.indexOf(':') !== -1;\n return schema + ':\u002F\u002F' + (ipv6 ? '[' + this.hostname + ']' : this.hostname) + port + this.path + query;\n };\n\n \u002F**\n * Feature detection for WebSocket.\n *\n * @return {Boolean} whether this transport is available.\n * @api public\n *\u002F\n\n WS.prototype.check = function () {\n return !!WebSocketImpl && !('__initialize' in WebSocketImpl && this.name === WS.prototype.name);\n };\n\n \u002F**\n * Module dependencies\n *\u002F\n\n\n\n\n\n\n \u002F**\n * Export transports.\n *\u002F\n\n var polling_1 = polling$1;\n var websocket_1 = websocket;\n\n \u002F**\n * Polling transport polymorphic constructor.\n * Decides on xhr vs jsonp based on feature detection.\n *\n * @api private\n *\u002F\n\n function polling$1 (opts) {\n var xhr;\n var xd = false;\n var xs = false;\n var jsonp = false !== opts.jsonp;\n\n if (typeof location !== 'undefined') {\n var isSSL = 'https:' === location.protocol;\n var port = location.port;\n\n \u002F\u002F some user agents have empty `location.port`\n if (!port) {\n port = isSSL ? 443 : 80;\n }\n\n xd = opts.hostname !== location.hostname || port !== opts.port;\n xs = opts.secure !== isSSL;\n }\n\n opts.xdomain = xd;\n opts.xscheme = xs;\n xhr = new xmlhttprequest(opts);\n\n if ('open' in xhr && !opts.forceJSONP) {\n return new pollingXhr(opts);\n } else {\n if (!jsonp) throw new Error('JSONP disabled');\n return new pollingJsonp(opts);\n }\n }\n\n var transports = {\n polling: polling_1,\n websocket: websocket_1\n };\n\n var indexOf = [].indexOf;\n\n var indexof = function(arr, obj){\n if (indexOf) return arr.indexOf(obj);\n for (var i = 0; i \u003C arr.length; ++i) {\n if (arr[i] === obj) return i;\n }\n return -1;\n };\n\n \u002F**\n * Module dependencies.\n *\u002F\n\n\n\n var debug$5 = browser('engine.io-client:socket');\n\n\n\n\n\n \u002F**\n * Module exports.\n *\u002F\n\n var socket = Socket;\n\n \u002F**\n * Socket constructor.\n *\n * @param {String|Object} uri or options\n * @param {Object} options\n * @api public\n *\u002F\n\n function Socket (uri, opts) {\n if (!(this instanceof Socket)) return new Socket(uri, opts);\n\n opts = opts || {};\n\n if (uri && 'object' === typeof uri) {\n opts = uri;\n uri = null;\n }\n\n if (uri) {\n uri = parseuri(uri);\n opts.hostname = uri.host;\n opts.secure = uri.protocol === 'https' || uri.protocol === 'wss';\n opts.port = uri.port;\n if (uri.query) opts.query = uri.query;\n } else if (opts.host) {\n opts.hostname = parseuri(opts.host).host;\n }\n\n this.secure = null != opts.secure ? opts.secure\n : (typeof location !== 'undefined' && 'https:' === location.protocol);\n\n if (opts.hostname && !opts.port) {\n \u002F\u002F if no port is specified manually, use the protocol default\n opts.port = this.secure ? '443' : '80';\n }\n\n this.agent = opts.agent || false;\n this.hostname = opts.hostname ||\n (typeof location !== 'undefined' ? location.hostname : 'localhost');\n this.port = opts.port || (typeof location !== 'undefined' && location.port\n ? location.port\n : (this.secure ? 443 : 80));\n this.query = opts.query || {};\n if ('string' === typeof this.query) this.query = parseqs.decode(this.query);\n this.upgrade = false !== opts.upgrade;\n this.path = (opts.path || '\u002Fengine.io').replace(\u002F\\\u002F$\u002F, '') + '\u002F';\n this.forceJSONP = !!opts.forceJSONP;\n this.jsonp = false !== opts.jsonp;\n this.forceBase64 = !!opts.forceBase64;\n this.enablesXDR = !!opts.enablesXDR;\n this.withCredentials = false !== opts.withCredentials;\n this.timestampParam = opts.timestampParam || 't';\n this.timestampRequests = opts.timestampRequests;\n this.transports = opts.transports || ['polling', 'websocket'];\n this.transportOptions = opts.transportOptions || {};\n this.readyState = '';\n this.writeBuffer = [];\n this.prevBufferLen = 0;\n this.policyPort = opts.policyPort || 843;\n this.rememberUpgrade = opts.rememberUpgrade || false;\n this.binaryType = null;\n this.onlyBinaryUpgrades = opts.onlyBinaryUpgrades;\n this.perMessageDeflate = false !== opts.perMessageDeflate ? (opts.perMessageDeflate || {}) : false;\n\n if (true === this.perMessageDeflate) this.perMessageDeflate = {};\n if (this.perMessageDeflate && null == this.perMessageDeflate.threshold) {\n this.perMessageDeflate.threshold = 1024;\n }\n\n \u002F\u002F SSL options for Node.js client\n this.pfx = opts.pfx || null;\n this.key = opts.key || null;\n this.passphrase = opts.passphrase || null;\n this.cert = opts.cert || null;\n this.ca = opts.ca || null;\n this.ciphers = opts.ciphers || null;\n this.rejectUnauthorized = opts.rejectUnauthorized === undefined ? true : opts.rejectUnauthorized;\n this.forceNode = !!opts.forceNode;\n\n \u002F\u002F detect ReactNative environment\n this.isReactNative = (typeof navigator !== 'undefined' && typeof navigator.product === 'string' && navigator.product.toLowerCase() === 'reactnative');\n\n \u002F\u002F other options for Node.js or ReactNative client\n if (typeof self === 'undefined' || this.isReactNative) {\n if (opts.extraHeaders && Object.keys(opts.extraHeaders).length \u003E 0) {\n this.extraHeaders = opts.extraHeaders;\n }\n\n if (opts.localAddress) {\n this.localAddress = opts.localAddress;\n }\n }\n\n \u002F\u002F set on handshake\n this.id = null;\n this.upgrades = null;\n this.pingInterval = null;\n this.pingTimeout = null;\n\n \u002F\u002F set on heartbeat\n this.pingIntervalTimer = null;\n this.pingTimeoutTimer = null;\n\n this.open();\n }\n\n Socket.priorWebsocketSuccess = false;\n\n \u002F**\n * Mix in `Emitter`.\n *\u002F\n\n componentEmitter(Socket.prototype);\n\n \u002F**\n * Protocol version.\n *\n * @api public\n *\u002F\n\n Socket.protocol = browser$2.protocol; \u002F\u002F this is an int\n\n \u002F**\n * Expose deps for legacy compatibility\n * and standalone browser access.\n *\u002F\n\n Socket.Socket = Socket;\n Socket.Transport = transport;\n Socket.transports = transports;\n Socket.parser = browser$2;\n\n \u002F**\n * Creates transport of the given type.\n *\n * @param {String} transport name\n * @return {Transport}\n * @api private\n *\u002F\n\n Socket.prototype.createTransport = function (name) {\n debug$5('creating transport \"%s\"', name);\n var query = clone(this.query);\n\n \u002F\u002F append engine.io protocol identifier\n query.EIO = browser$2.protocol;\n\n \u002F\u002F transport name\n query.transport = name;\n\n \u002F\u002F per-transport options\n var options = this.transportOptions[name] || {};\n\n \u002F\u002F session id if we already have one\n if (this.id) query.sid = this.id;\n\n var transport = new transports[name]({\n query: query,\n socket: this,\n agent: options.agent || this.agent,\n hostname: options.hostname || this.hostname,\n port: options.port || this.port,\n secure: options.secure || this.secure,\n path: options.path || this.path,\n forceJSONP: options.forceJSONP || this.forceJSONP,\n jsonp: options.jsonp || this.jsonp,\n forceBase64: options.forceBase64 || this.forceBase64,\n enablesXDR: options.enablesXDR || this.enablesXDR,\n withCredentials: options.withCredentials || this.withCredentials,\n timestampRequests: options.timestampRequests || this.timestampRequests,\n timestampParam: options.timestampParam || this.timestampParam,\n policyPort: options.policyPort || this.policyPort,\n pfx: options.pfx || this.pfx,\n key: options.key || this.key,\n passphrase: options.passphrase || this.passphrase,\n cert: options.cert || this.cert,\n ca: options.ca || this.ca,\n ciphers: options.ciphers || this.ciphers,\n rejectUnauthorized: options.rejectUnauthorized || this.rejectUnauthorized,\n perMessageDeflate: options.perMessageDeflate || this.perMessageDeflate,\n extraHeaders: options.extraHeaders || this.extraHeaders,\n forceNode: options.forceNode || this.forceNode,\n localAddress: options.localAddress || this.localAddress,\n requestTimeout: options.requestTimeout || this.requestTimeout,\n protocols: options.protocols || void (0),\n isReactNative: this.isReactNative\n });\n\n return transport;\n };\n\n function clone (obj) {\n var o = {};\n for (var i in obj) {\n if (obj.hasOwnProperty(i)) {\n o[i] = obj[i];\n }\n }\n return o;\n }\n\n \u002F**\n * Initializes transport to use and starts probe.\n *\n * @api private\n *\u002F\n Socket.prototype.open = function () {\n var transport;\n if (this.rememberUpgrade && Socket.priorWebsocketSuccess && this.transports.indexOf('websocket') !== -1) {\n transport = 'websocket';\n } else if (0 === this.transports.length) {\n \u002F\u002F Emit error on next tick so it can be listened to\n var self = this;\n setTimeout(function () {\n self.emit('error', 'No transports available');\n }, 0);\n return;\n } else {\n transport = this.transports[0];\n }\n this.readyState = 'opening';\n\n \u002F\u002F Retry with the next transport if the transport is disabled (jsonp: false)\n try {\n transport = this.createTransport(transport);\n } catch (e) {\n this.transports.shift();\n this.open();\n return;\n }\n\n transport.open();\n this.setTransport(transport);\n };\n\n \u002F**\n * Sets the current transport. Disables the existing one (if any).\n *\n * @api private\n *\u002F\n\n Socket.prototype.setTransport = function (transport) {\n debug$5('setting transport %s', transport.name);\n var self = this;\n\n if (this.transport) {\n debug$5('clearing existing transport %s', this.transport.name);\n this.transport.removeAllListeners();\n }\n\n \u002F\u002F set up transport\n this.transport = transport;\n\n \u002F\u002F set up transport listeners\n transport\n .on('drain', function () {\n self.onDrain();\n })\n .on('packet', function (packet) {\n self.onPacket(packet);\n })\n .on('error', function (e) {\n self.onError(e);\n })\n .on('close', function () {\n self.onClose('transport close');\n });\n };\n\n \u002F**\n * Probes a transport.\n *\n * @param {String} transport name\n * @api private\n *\u002F\n\n Socket.prototype.probe = function (name) {\n debug$5('probing transport \"%s\"', name);\n var transport = this.createTransport(name, { probe: 1 });\n var failed = false;\n var self = this;\n\n Socket.priorWebsocketSuccess = false;\n\n function onTransportOpen () {\n if (self.onlyBinaryUpgrades) {\n var upgradeLosesBinary = !this.supportsBinary && self.transport.supportsBinary;\n failed = failed || upgradeLosesBinary;\n }\n if (failed) return;\n\n debug$5('probe transport \"%s\" opened', name);\n transport.send([{ type: 'ping', data: 'probe' }]);\n transport.once('packet', function (msg) {\n if (failed) return;\n if ('pong' === msg.type && 'probe' === msg.data) {\n debug$5('probe transport \"%s\" pong', name);\n self.upgrading = true;\n self.emit('upgrading', transport);\n if (!transport) return;\n Socket.priorWebsocketSuccess = 'websocket' === transport.name;\n\n debug$5('pausing current transport \"%s\"', self.transport.name);\n self.transport.pause(function () {\n if (failed) return;\n if ('closed' === self.readyState) return;\n debug$5('changing transport and sending upgrade packet');\n\n cleanup();\n\n self.setTransport(transport);\n transport.send([{ type: 'upgrade' }]);\n self.emit('upgrade', transport);\n transport = null;\n self.upgrading = false;\n self.flush();\n });\n } else {\n debug$5('probe transport \"%s\" failed', name);\n var err = new Error('probe error');\n err.transport = transport.name;\n self.emit('upgradeError', err);\n }\n });\n }\n\n function freezeTransport () {\n if (failed) return;\n\n \u002F\u002F Any callback called by transport should be ignored since now\n failed = true;\n\n cleanup();\n\n transport.close();\n transport = null;\n }\n\n \u002F\u002F Handle any error that happens while probing\n function onerror (err) {\n var error = new Error('probe error: ' + err);\n error.transport = transport.name;\n\n freezeTransport();\n\n debug$5('probe transport \"%s\" failed because of error: %s', name, err);\n\n self.emit('upgradeError', error);\n }\n\n function onTransportClose () {\n onerror('transport closed');\n }\n\n \u002F\u002F When the socket is closed while we're probing\n function onclose () {\n onerror('socket closed');\n }\n\n \u002F\u002F When the socket is upgraded while we're probing\n function onupgrade (to) {\n if (transport && to.name !== transport.name) {\n debug$5('\"%s\" works - aborting \"%s\"', to.name, transport.name);\n freezeTransport();\n }\n }\n\n \u002F\u002F Remove all listeners on the transport and on self\n function cleanup () {\n transport.removeListener('open', onTransportOpen);\n transport.removeListener('error', onerror);\n transport.removeListener('close', onTransportClose);\n self.removeListener('close', onclose);\n self.removeListener('upgrading', onupgrade);\n }\n\n transport.once('open', onTransportOpen);\n transport.once('error', onerror);\n transport.once('close', onTransportClose);\n\n this.once('close', onclose);\n this.once('upgrading', onupgrade);\n\n transport.open();\n };\n\n \u002F**\n * Called when connection is deemed open.\n *\n * @api public\n *\u002F\n\n Socket.prototype.onOpen = function () {\n debug$5('socket open');\n this.readyState = 'open';\n Socket.priorWebsocketSuccess = 'websocket' === this.transport.name;\n this.emit('open');\n this.flush();\n\n \u002F\u002F we check for `readyState` in case an `open`\n \u002F\u002F listener already closed the socket\n if ('open' === this.readyState && this.upgrade && this.transport.pause) {\n debug$5('starting upgrade probes');\n for (var i = 0, l = this.upgrades.length; i \u003C l; i++) {\n this.probe(this.upgrades[i]);\n }\n }\n };\n\n \u002F**\n * Handles a packet.\n *\n * @api private\n *\u002F\n\n Socket.prototype.onPacket = function (packet) {\n if ('opening' === this.readyState || 'open' === this.readyState ||\n 'closing' === this.readyState) {\n debug$5('socket receive: type \"%s\", data \"%s\"', packet.type, packet.data);\n\n this.emit('packet', packet);\n\n \u002F\u002F Socket is live - any packet counts\n this.emit('heartbeat');\n\n switch (packet.type) {\n case 'open':\n this.onHandshake(JSON.parse(packet.data));\n break;\n\n case 'pong':\n this.setPing();\n this.emit('pong');\n break;\n\n case 'error':\n var err = new Error('server error');\n err.code = packet.data;\n this.onError(err);\n break;\n\n case 'message':\n this.emit('data', packet.data);\n this.emit('message', packet.data);\n break;\n }\n } else {\n debug$5('packet received with socket readyState \"%s\"', this.readyState);\n }\n };\n\n \u002F**\n * Called upon handshake completion.\n *\n * @param {Object} handshake obj\n * @api private\n *\u002F\n\n Socket.prototype.onHandshake = function (data) {\n this.emit('handshake', data);\n this.id = data.sid;\n this.transport.query.sid = data.sid;\n this.upgrades = this.filterUpgrades(data.upgrades);\n this.pingInterval = data.pingInterval;\n this.pingTimeout = data.pingTimeout;\n this.onOpen();\n \u002F\u002F In case open handler closes socket\n if ('closed' === this.readyState) return;\n this.setPing();\n\n \u002F\u002F Prolong liveness of socket on heartbeat\n this.removeListener('heartbeat', this.onHeartbeat);\n this.on('heartbeat', this.onHeartbeat);\n };\n\n \u002F**\n * Resets ping timeout.\n *\n * @api private\n *\u002F\n\n Socket.prototype.onHeartbeat = function (timeout) {\n clearTimeout(this.pingTimeoutTimer);\n var self = this;\n self.pingTimeoutTimer = setTimeout(function () {\n if ('closed' === self.readyState) return;\n self.onClose('ping timeout');\n }, timeout || (self.pingInterval + self.pingTimeout));\n };\n\n \u002F**\n * Pings server every `this.pingInterval` and expects response\n * within `this.pingTimeout` or closes connection.\n *\n * @api private\n *\u002F\n\n Socket.prototype.setPing = function () {\n var self = this;\n clearTimeout(self.pingIntervalTimer);\n self.pingIntervalTimer = setTimeout(function () {\n debug$5('writing ping packet - expecting pong within %sms', self.pingTimeout);\n self.ping();\n self.onHeartbeat(self.pingTimeout);\n }, self.pingInterval);\n };\n\n \u002F**\n * Sends a ping packet.\n *\n * @api private\n *\u002F\n\n Socket.prototype.ping = function () {\n var self = this;\n this.sendPacket('ping', function () {\n self.emit('ping');\n });\n };\n\n \u002F**\n * Called on `drain` event\n *\n * @api private\n *\u002F\n\n Socket.prototype.onDrain = function () {\n this.writeBuffer.splice(0, this.prevBufferLen);\n\n \u002F\u002F setting prevBufferLen = 0 is very important\n \u002F\u002F for example, when upgrading, upgrade packet is sent over,\n \u002F\u002F and a nonzero prevBufferLen could cause problems on `drain`\n this.prevBufferLen = 0;\n\n if (0 === this.writeBuffer.length) {\n this.emit('drain');\n } else {\n this.flush();\n }\n };\n\n \u002F**\n * Flush write buffers.\n *\n * @api private\n *\u002F\n\n Socket.prototype.flush = function () {\n if ('closed' !== this.readyState && this.transport.writable &&\n !this.upgrading && this.writeBuffer.length) {\n debug$5('flushing %d packets in socket', this.writeBuffer.length);\n this.transport.send(this.writeBuffer);\n \u002F\u002F keep track of current length of writeBuffer\n \u002F\u002F splice writeBuffer and callbackBuffer on `drain`\n this.prevBufferLen = this.writeBuffer.length;\n this.emit('flush');\n }\n };\n\n \u002F**\n * Sends a message.\n *\n * @param {String} message.\n * @param {Function} callback function.\n * @param {Object} options.\n * @return {Socket} for chaining.\n * @api public\n *\u002F\n\n Socket.prototype.write =\n Socket.prototype.send = function (msg, options, fn) {\n this.sendPacket('message', msg, options, fn);\n return this;\n };\n\n \u002F**\n * Sends a packet.\n *\n * @param {String} packet type.\n * @param {String} data.\n * @param {Object} options.\n * @param {Function} callback function.\n * @api private\n *\u002F\n\n Socket.prototype.sendPacket = function (type, data, options, fn) {\n if ('function' === typeof data) {\n fn = data;\n data = undefined;\n }\n\n if ('function' === typeof options) {\n fn = options;\n options = null;\n }\n\n if ('closing' === this.readyState || 'closed' === this.readyState) {\n return;\n }\n\n options = options || {};\n options.compress = false !== options.compress;\n\n var packet = {\n type: type,\n data: data,\n options: options\n };\n this.emit('packetCreate', packet);\n this.writeBuffer.push(packet);\n if (fn) this.once('flush', fn);\n this.flush();\n };\n\n \u002F**\n * Closes the connection.\n *\n * @api private\n *\u002F\n\n Socket.prototype.close = function () {\n if ('opening' === this.readyState || 'open' === this.readyState) {\n this.readyState = 'closing';\n\n var self = this;\n\n if (this.writeBuffer.length) {\n this.once('drain', function () {\n if (this.upgrading) {\n waitForUpgrade();\n } else {\n close();\n }\n });\n } else if (this.upgrading) {\n waitForUpgrade();\n } else {\n close();\n }\n }\n\n function close () {\n self.onClose('forced close');\n debug$5('socket closing - telling transport to close');\n self.transport.close();\n }\n\n function cleanupAndClose () {\n self.removeListener('upgrade', cleanupAndClose);\n self.removeListener('upgradeError', cleanupAndClose);\n close();\n }\n\n function waitForUpgrade () {\n \u002F\u002F wait for upgrade to finish since we can't send packets while pausing a transport\n self.once('upgrade', cleanupAndClose);\n self.once('upgradeError', cleanupAndClose);\n }\n\n return this;\n };\n\n \u002F**\n * Called upon transport error\n *\n * @api private\n *\u002F\n\n Socket.prototype.onError = function (err) {\n debug$5('socket error %j', err);\n Socket.priorWebsocketSuccess = false;\n this.emit('error', err);\n this.onClose('transport error', err);\n };\n\n \u002F**\n * Called upon transport close.\n *\n * @api private\n *\u002F\n\n Socket.prototype.onClose = function (reason, desc) {\n if ('opening' === this.readyState || 'open' === this.readyState || 'closing' === this.readyState) {\n debug$5('socket close with reason: \"%s\"', reason);\n var self = this;\n\n \u002F\u002F clear timers\n clearTimeout(this.pingIntervalTimer);\n clearTimeout(this.pingTimeoutTimer);\n\n \u002F\u002F stop event from firing again for transport\n this.transport.removeAllListeners('close');\n\n \u002F\u002F ensure transport won't stay open\n this.transport.close();\n\n \u002F\u002F ignore further transport communication\n this.transport.removeAllListeners();\n\n \u002F\u002F set ready state\n this.readyState = 'closed';\n\n \u002F\u002F clear session id\n this.id = null;\n\n \u002F\u002F emit close event\n this.emit('close', reason, desc);\n\n \u002F\u002F clean buffers after, so users can still\n \u002F\u002F grab the buffers on `close` event\n self.writeBuffer = [];\n self.prevBufferLen = 0;\n }\n };\n\n \u002F**\n * Filters upgrades, returning only those matching client transports.\n *\n * @param {Array} server upgrades\n * @api private\n *\n *\u002F\n\n Socket.prototype.filterUpgrades = function (upgrades) {\n var filteredUpgrades = [];\n for (var i = 0, j = upgrades.length; i \u003C j; i++) {\n if (~indexof(this.transports, upgrades[i])) filteredUpgrades.push(upgrades[i]);\n }\n return filteredUpgrades;\n };\n\n var lib = socket;\n\n \u002F**\n * Exports parser\n *\n * @api public\n *\n *\u002F\n var parser = browser$2;\n lib.parser = parser;\n\n var toArray_1 = toArray;\n\n function toArray(list, index) {\n var array = [];\n\n index = index || 0;\n\n for (var i = index || 0; i \u003C list.length; i++) {\n array[i - index] = list[i];\n }\n\n return array\n }\n\n \u002F**\n * Module exports.\n *\u002F\n\n var on_1 = on;\n\n \u002F**\n * Helper for subscriptions.\n *\n * @param {Object|EventEmitter} obj with `Emitter` mixin or `EventEmitter`\n * @param {String} event name\n * @param {Function} callback\n * @api public\n *\u002F\n\n function on (obj, ev, fn) {\n obj.on(ev, fn);\n return {\n destroy: function () {\n obj.removeListener(ev, fn);\n }\n };\n }\n\n \u002F**\n * Slice reference.\n *\u002F\n\n var slice = [].slice;\n\n \u002F**\n * Bind `obj` to `fn`.\n *\n * @param {Object} obj\n * @param {Function|String} fn or string\n * @return {Function}\n * @api public\n *\u002F\n\n var componentBind = function(obj, fn){\n if ('string' == typeof fn) fn = obj[fn];\n if ('function' != typeof fn) throw new Error('bind() requires a function');\n var args = slice.call(arguments, 2);\n return function(){\n return fn.apply(obj, args.concat(slice.call(arguments)));\n }\n };\n\n var socket$1 = createCommonjsModule(function (module, exports) {\n \u002F**\n * Module dependencies.\n *\u002F\n\n\n\n\n\n\n var debug = browser('socket.io-client:socket');\n\n\n\n \u002F**\n * Module exports.\n *\u002F\n\n module.exports = exports = Socket;\n\n \u002F**\n * Internal events (blacklisted).\n * These events can't be emitted by the user.\n *\n * @api private\n *\u002F\n\n var events = {\n connect: 1,\n connect_error: 1,\n connect_timeout: 1,\n connecting: 1,\n disconnect: 1,\n error: 1,\n reconnect: 1,\n reconnect_attempt: 1,\n reconnect_failed: 1,\n reconnect_error: 1,\n reconnecting: 1,\n ping: 1,\n pong: 1\n };\n\n \u002F**\n * Shortcut to `Emitter#emit`.\n *\u002F\n\n var emit = componentEmitter.prototype.emit;\n\n \u002F**\n * `Socket` constructor.\n *\n * @api public\n *\u002F\n\n function Socket (io, nsp, opts) {\n this.io = io;\n this.nsp = nsp;\n this.json = this; \u002F\u002F compat\n this.ids = 0;\n this.acks = {};\n this.receiveBuffer = [];\n this.sendBuffer = [];\n this.connected = false;\n this.disconnected = true;\n this.flags = {};\n if (opts && opts.query) {\n this.query = opts.query;\n }\n if (this.io.autoConnect) this.open();\n }\n\n \u002F**\n * Mix in `Emitter`.\n *\u002F\n\n componentEmitter(Socket.prototype);\n\n \u002F**\n * Subscribe to open, close and packet events\n *\n * @api private\n *\u002F\n\n Socket.prototype.subEvents = function () {\n if (this.subs) return;\n\n var io = this.io;\n this.subs = [\n on_1(io, 'open', componentBind(this, 'onopen')),\n on_1(io, 'packet', componentBind(this, 'onpacket')),\n on_1(io, 'close', componentBind(this, 'onclose'))\n ];\n };\n\n \u002F**\n * \"Opens\" the socket.\n *\n * @api public\n *\u002F\n\n Socket.prototype.open =\n Socket.prototype.connect = function () {\n if (this.connected) return this;\n\n this.subEvents();\n this.io.open(); \u002F\u002F ensure open\n if ('open' === this.io.readyState) this.onopen();\n this.emit('connecting');\n return this;\n };\n\n \u002F**\n * Sends a `message` event.\n *\n * @return {Socket} self\n * @api public\n *\u002F\n\n Socket.prototype.send = function () {\n var args = toArray_1(arguments);\n args.unshift('message');\n this.emit.apply(this, args);\n return this;\n };\n\n \u002F**\n * Override `emit`.\n * If the event is in `events`, it's emitted normally.\n *\n * @param {String} event name\n * @return {Socket} self\n * @api public\n *\u002F\n\n Socket.prototype.emit = function (ev) {\n if (events.hasOwnProperty(ev)) {\n emit.apply(this, arguments);\n return this;\n }\n\n var args = toArray_1(arguments);\n var packet = {\n type: (this.flags.binary !== undefined ? this.flags.binary : hasBinary2(args)) ? socket_ioParser.BINARY_EVENT : socket_ioParser.EVENT,\n data: args\n };\n\n packet.options = {};\n packet.options.compress = !this.flags || false !== this.flags.compress;\n\n \u002F\u002F event ack callback\n if ('function' === typeof args[args.length - 1]) {\n debug('emitting packet with ack id %d', this.ids);\n this.acks[this.ids] = args.pop();\n packet.id = this.ids++;\n }\n\n if (this.connected) {\n this.packet(packet);\n } else {\n this.sendBuffer.push(packet);\n }\n\n this.flags = {};\n\n return this;\n };\n\n \u002F**\n * Sends a packet.\n *\n * @param {Object} packet\n * @api private\n *\u002F\n\n Socket.prototype.packet = function (packet) {\n packet.nsp = this.nsp;\n this.io.packet(packet);\n };\n\n \u002F**\n * Called upon engine `open`.\n *\n * @api private\n *\u002F\n\n Socket.prototype.onopen = function () {\n debug('transport is open - connecting');\n\n \u002F\u002F write connect packet if necessary\n if ('\u002F' !== this.nsp) {\n if (this.query) {\n var query = typeof this.query === 'object' ? parseqs.encode(this.query) : this.query;\n debug('sending connect packet with query %s', query);\n this.packet({type: socket_ioParser.CONNECT, query: query});\n } else {\n this.packet({type: socket_ioParser.CONNECT});\n }\n }\n };\n\n \u002F**\n * Called upon engine `close`.\n *\n * @param {String} reason\n * @api private\n *\u002F\n\n Socket.prototype.onclose = function (reason) {\n debug('close (%s)', reason);\n this.connected = false;\n this.disconnected = true;\n delete this.id;\n this.emit('disconnect', reason);\n };\n\n \u002F**\n * Called with socket packet.\n *\n * @param {Object} packet\n * @api private\n *\u002F\n\n Socket.prototype.onpacket = function (packet) {\n var sameNamespace = packet.nsp === this.nsp;\n var rootNamespaceError = packet.type === socket_ioParser.ERROR && packet.nsp === '\u002F';\n\n if (!sameNamespace && !rootNamespaceError) return;\n\n switch (packet.type) {\n case socket_ioParser.CONNECT:\n this.onconnect();\n break;\n\n case socket_ioParser.EVENT:\n this.onevent(packet);\n break;\n\n case socket_ioParser.BINARY_EVENT:\n this.onevent(packet);\n break;\n\n case socket_ioParser.ACK:\n this.onack(packet);\n break;\n\n case socket_ioParser.BINARY_ACK:\n this.onack(packet);\n break;\n\n case socket_ioParser.DISCONNECT:\n this.ondisconnect();\n break;\n\n case socket_ioParser.ERROR:\n this.emit('error', packet.data);\n break;\n }\n };\n\n \u002F**\n * Called upon a server event.\n *\n * @param {Object} packet\n * @api private\n *\u002F\n\n Socket.prototype.onevent = function (packet) {\n var args = packet.data || [];\n debug('emitting event %j', args);\n\n if (null != packet.id) {\n debug('attaching ack callback to event');\n args.push(this.ack(packet.id));\n }\n\n if (this.connected) {\n emit.apply(this, args);\n } else {\n this.receiveBuffer.push(args);\n }\n };\n\n \u002F**\n * Produces an ack callback to emit with an event.\n *\n * @api private\n *\u002F\n\n Socket.prototype.ack = function (id) {\n var self = this;\n var sent = false;\n return function () {\n \u002F\u002F prevent double callbacks\n if (sent) return;\n sent = true;\n var args = toArray_1(arguments);\n debug('sending ack %j', args);\n\n self.packet({\n type: hasBinary2(args) ? socket_ioParser.BINARY_ACK : socket_ioParser.ACK,\n id: id,\n data: args\n });\n };\n };\n\n \u002F**\n * Called upon a server acknowlegement.\n *\n * @param {Object} packet\n * @api private\n *\u002F\n\n Socket.prototype.onack = function (packet) {\n var ack = this.acks[packet.id];\n if ('function' === typeof ack) {\n debug('calling ack %s with %j', packet.id, packet.data);\n ack.apply(this, packet.data);\n delete this.acks[packet.id];\n } else {\n debug('bad ack %s', packet.id);\n }\n };\n\n \u002F**\n * Called upon server connect.\n *\n * @api private\n *\u002F\n\n Socket.prototype.onconnect = function () {\n this.connected = true;\n this.disconnected = false;\n this.emit('connect');\n this.emitBuffered();\n };\n\n \u002F**\n * Emit buffered events (received and emitted).\n *\n * @api private\n *\u002F\n\n Socket.prototype.emitBuffered = function () {\n var i;\n for (i = 0; i \u003C this.receiveBuffer.length; i++) {\n emit.apply(this, this.receiveBuffer[i]);\n }\n this.receiveBuffer = [];\n\n for (i = 0; i \u003C this.sendBuffer.length; i++) {\n this.packet(this.sendBuffer[i]);\n }\n this.sendBuffer = [];\n };\n\n \u002F**\n * Called upon server disconnect.\n *\n * @api private\n *\u002F\n\n Socket.prototype.ondisconnect = function () {\n debug('server disconnect (%s)', this.nsp);\n this.destroy();\n this.onclose('io server disconnect');\n };\n\n \u002F**\n * Called upon forced client\u002Fserver side disconnections,\n * this method ensures the manager stops tracking us and\n * that reconnections don't get triggered for this.\n *\n * @api private.\n *\u002F\n\n Socket.prototype.destroy = function () {\n if (this.subs) {\n \u002F\u002F clean subscriptions to avoid reconnections\n for (var i = 0; i \u003C this.subs.length; i++) {\n this.subs[i].destroy();\n }\n this.subs = null;\n }\n\n this.io.destroy(this);\n };\n\n \u002F**\n * Disconnects the socket manually.\n *\n * @return {Socket} self\n * @api public\n *\u002F\n\n Socket.prototype.close =\n Socket.prototype.disconnect = function () {\n if (this.connected) {\n debug('performing disconnect (%s)', this.nsp);\n this.packet({ type: socket_ioParser.DISCONNECT });\n }\n\n \u002F\u002F remove socket from pool\n this.destroy();\n\n if (this.connected) {\n \u002F\u002F fire events\n this.onclose('io client disconnect');\n }\n return this;\n };\n\n \u002F**\n * Sets the compress flag.\n *\n * @param {Boolean} if `true`, compresses the sending data\n * @return {Socket} self\n * @api public\n *\u002F\n\n Socket.prototype.compress = function (compress) {\n this.flags.compress = compress;\n return this;\n };\n\n \u002F**\n * Sets the binary flag\n *\n * @param {Boolean} whether the emitted data contains binary\n * @return {Socket} self\n * @api public\n *\u002F\n\n Socket.prototype.binary = function (binary) {\n this.flags.binary = binary;\n return this;\n };\n });\n\n \u002F**\n * Expose `Backoff`.\n *\u002F\n\n var backo2 = Backoff;\n\n \u002F**\n * Initialize backoff timer with `opts`.\n *\n * - `min` initial timeout in milliseconds [100]\n * - `max` max timeout [10000]\n * - `jitter` [0]\n * - `factor` [2]\n *\n * @param {Object} opts\n * @api public\n *\u002F\n\n function Backoff(opts) {\n opts = opts || {};\n this.ms = opts.min || 100;\n this.max = opts.max || 10000;\n this.factor = opts.factor || 2;\n this.jitter = opts.jitter \u003E 0 && opts.jitter \u003C= 1 ? opts.jitter : 0;\n this.attempts = 0;\n }\n\n \u002F**\n * Return the backoff duration.\n *\n * @return {Number}\n * @api public\n *\u002F\n\n Backoff.prototype.duration = function(){\n var ms = this.ms * Math.pow(this.factor, this.attempts++);\n if (this.jitter) {\n var rand = Math.random();\n var deviation = Math.floor(rand * this.jitter * ms);\n ms = (Math.floor(rand * 10) & 1) == 0 ? ms - deviation : ms + deviation;\n }\n return Math.min(ms, this.max) | 0;\n };\n\n \u002F**\n * Reset the number of attempts.\n *\n * @api public\n *\u002F\n\n Backoff.prototype.reset = function(){\n this.attempts = 0;\n };\n\n \u002F**\n * Set the minimum duration\n *\n * @api public\n *\u002F\n\n Backoff.prototype.setMin = function(min){\n this.ms = min;\n };\n\n \u002F**\n * Set the maximum duration\n *\n * @api public\n *\u002F\n\n Backoff.prototype.setMax = function(max){\n this.max = max;\n };\n\n \u002F**\n * Set the jitter\n *\n * @api public\n *\u002F\n\n Backoff.prototype.setJitter = function(jitter){\n this.jitter = jitter;\n };\n\n \u002F**\n * Module dependencies.\n *\u002F\n\n\n\n\n\n\n\n var debug$6 = browser('socket.io-client:manager');\n\n\n\n \u002F**\n * IE6+ hasOwnProperty\n *\u002F\n\n var has = Object.prototype.hasOwnProperty;\n\n \u002F**\n * Module exports\n *\u002F\n\n var manager = Manager;\n\n \u002F**\n * `Manager` constructor.\n *\n * @param {String} engine instance or engine uri\u002Fopts\n * @param {Object} options\n * @api public\n *\u002F\n\n function Manager (uri, opts) {\n if (!(this instanceof Manager)) return new Manager(uri, opts);\n if (uri && ('object' === typeof uri)) {\n opts = uri;\n uri = undefined;\n }\n opts = opts || {};\n\n opts.path = opts.path || '\u002Fsocket.io';\n this.nsps = {};\n this.subs = [];\n this.opts = opts;\n this.reconnection(opts.reconnection !== false);\n this.reconnectionAttempts(opts.reconnectionAttempts || Infinity);\n this.reconnectionDelay(opts.reconnectionDelay || 1000);\n this.reconnectionDelayMax(opts.reconnectionDelayMax || 5000);\n this.randomizationFactor(opts.randomizationFactor || 0.5);\n this.backoff = new backo2({\n min: this.reconnectionDelay(),\n max: this.reconnectionDelayMax(),\n jitter: this.randomizationFactor()\n });\n this.timeout(null == opts.timeout ? 20000 : opts.timeout);\n this.readyState = 'closed';\n this.uri = uri;\n this.connecting = [];\n this.lastPing = null;\n this.encoding = false;\n this.packetBuffer = [];\n var _parser = opts.parser || socket_ioParser;\n this.encoder = new _parser.Encoder();\n this.decoder = new _parser.Decoder();\n this.autoConnect = opts.autoConnect !== false;\n if (this.autoConnect) this.open();\n }\n\n \u002F**\n * Propagate given event to sockets and emit on `this`\n *\n * @api private\n *\u002F\n\n Manager.prototype.emitAll = function () {\n this.emit.apply(this, arguments);\n for (var nsp in this.nsps) {\n if (has.call(this.nsps, nsp)) {\n this.nsps[nsp].emit.apply(this.nsps[nsp], arguments);\n }\n }\n };\n\n \u002F**\n * Update `socket.id` of all sockets\n *\n * @api private\n *\u002F\n\n Manager.prototype.updateSocketIds = function () {\n for (var nsp in this.nsps) {\n if (has.call(this.nsps, nsp)) {\n this.nsps[nsp].id = this.generateId(nsp);\n }\n }\n };\n\n \u002F**\n * generate `socket.id` for the given `nsp`\n *\n * @param {String} nsp\n * @return {String}\n * @api private\n *\u002F\n\n Manager.prototype.generateId = function (nsp) {\n return (nsp === '\u002F' ? '' : (nsp + '#')) + this.engine.id;\n };\n\n \u002F**\n * Mix in `Emitter`.\n *\u002F\n\n componentEmitter(Manager.prototype);\n\n \u002F**\n * Sets the `reconnection` config.\n *\n * @param {Boolean} true\u002Ffalse if it should automatically reconnect\n * @return {Manager} self or value\n * @api public\n *\u002F\n\n Manager.prototype.reconnection = function (v) {\n if (!arguments.length) return this._reconnection;\n this._reconnection = !!v;\n return this;\n };\n\n \u002F**\n * Sets the reconnection attempts config.\n *\n * @param {Number} max reconnection attempts before giving up\n * @return {Manager} self or value\n * @api public\n *\u002F\n\n Manager.prototype.reconnectionAttempts = function (v) {\n if (!arguments.length) return this._reconnectionAttempts;\n this._reconnectionAttempts = v;\n return this;\n };\n\n \u002F**\n * Sets the delay between reconnections.\n *\n * @param {Number} delay\n * @return {Manager} self or value\n * @api public\n *\u002F\n\n Manager.prototype.reconnectionDelay = function (v) {\n if (!arguments.length) return this._reconnectionDelay;\n this._reconnectionDelay = v;\n this.backoff && this.backoff.setMin(v);\n return this;\n };\n\n Manager.prototype.randomizationFactor = function (v) {\n if (!arguments.length) return this._randomizationFactor;\n this._randomizationFactor = v;\n this.backoff && this.backoff.setJitter(v);\n return this;\n };\n\n \u002F**\n * Sets the maximum delay between reconnections.\n *\n * @param {Number} delay\n * @return {Manager} self or value\n * @api public\n *\u002F\n\n Manager.prototype.reconnectionDelayMax = function (v) {\n if (!arguments.length) return this._reconnectionDelayMax;\n this._reconnectionDelayMax = v;\n this.backoff && this.backoff.setMax(v);\n return this;\n };\n\n \u002F**\n * Sets the connection timeout. `false` to disable\n *\n * @return {Manager} self or value\n * @api public\n *\u002F\n\n Manager.prototype.timeout = function (v) {\n if (!arguments.length) return this._timeout;\n this._timeout = v;\n return this;\n };\n\n \u002F**\n * Starts trying to reconnect if reconnection is enabled and we have not\n * started reconnecting yet\n *\n * @api private\n *\u002F\n\n Manager.prototype.maybeReconnectOnOpen = function () {\n \u002F\u002F Only try to reconnect if it's the first time we're connecting\n if (!this.reconnecting && this._reconnection && this.backoff.attempts === 0) {\n \u002F\u002F keeps reconnection from firing twice for the same reconnection loop\n this.reconnect();\n }\n };\n\n \u002F**\n * Sets the current transport `socket`.\n *\n * @param {Function} optional, callback\n * @return {Manager} self\n * @api public\n *\u002F\n\n Manager.prototype.open =\n Manager.prototype.connect = function (fn, opts) {\n debug$6('readyState %s', this.readyState);\n if (~this.readyState.indexOf('open')) return this;\n\n debug$6('opening %s', this.uri);\n this.engine = lib(this.uri, this.opts);\n var socket = this.engine;\n var self = this;\n this.readyState = 'opening';\n this.skipReconnect = false;\n\n \u002F\u002F emit `open`\n var openSub = on_1(socket, 'open', function () {\n self.onopen();\n fn && fn();\n });\n\n \u002F\u002F emit `connect_error`\n var errorSub = on_1(socket, 'error', function (data) {\n debug$6('connect_error');\n self.cleanup();\n self.readyState = 'closed';\n self.emitAll('connect_error', data);\n if (fn) {\n var err = new Error('Connection error');\n err.data = data;\n fn(err);\n } else {\n \u002F\u002F Only do this if there is no fn to handle the error\n self.maybeReconnectOnOpen();\n }\n });\n\n \u002F\u002F emit `connect_timeout`\n if (false !== this._timeout) {\n var timeout = this._timeout;\n debug$6('connect attempt will timeout after %d', timeout);\n\n \u002F\u002F set timer\n var timer = setTimeout(function () {\n debug$6('connect attempt timed out after %d', timeout);\n openSub.destroy();\n socket.close();\n socket.emit('error', 'timeout');\n self.emitAll('connect_timeout', timeout);\n }, timeout);\n\n this.subs.push({\n destroy: function () {\n clearTimeout(timer);\n }\n });\n }\n\n this.subs.push(openSub);\n this.subs.push(errorSub);\n\n return this;\n };\n\n \u002F**\n * Called upon transport open.\n *\n * @api private\n *\u002F\n\n Manager.prototype.onopen = function () {\n debug$6('open');\n\n \u002F\u002F clear old subs\n this.cleanup();\n\n \u002F\u002F mark as open\n this.readyState = 'open';\n this.emit('open');\n\n \u002F\u002F add new subs\n var socket = this.engine;\n this.subs.push(on_1(socket, 'data', componentBind(this, 'ondata')));\n this.subs.push(on_1(socket, 'ping', componentBind(this, 'onping')));\n this.subs.push(on_1(socket, 'pong', componentBind(this, 'onpong')));\n this.subs.push(on_1(socket, 'error', componentBind(this, 'onerror')));\n this.subs.push(on_1(socket, 'close', componentBind(this, 'onclose')));\n this.subs.push(on_1(this.decoder, 'decoded', componentBind(this, 'ondecoded')));\n };\n\n \u002F**\n * Called upon a ping.\n *\n * @api private\n *\u002F\n\n Manager.prototype.onping = function () {\n this.lastPing = new Date();\n this.emitAll('ping');\n };\n\n \u002F**\n * Called upon a packet.\n *\n * @api private\n *\u002F\n\n Manager.prototype.onpong = function () {\n this.emitAll('pong', new Date() - this.lastPing);\n };\n\n \u002F**\n * Called with data.\n *\n * @api private\n *\u002F\n\n Manager.prototype.ondata = function (data) {\n this.decoder.add(data);\n };\n\n \u002F**\n * Called when parser fully decodes a packet.\n *\n * @api private\n *\u002F\n\n Manager.prototype.ondecoded = function (packet) {\n this.emit('packet', packet);\n };\n\n \u002F**\n * Called upon socket error.\n *\n * @api private\n *\u002F\n\n Manager.prototype.onerror = function (err) {\n debug$6('error', err);\n this.emitAll('error', err);\n };\n\n \u002F**\n * Creates a new socket for the given `nsp`.\n *\n * @return {Socket}\n * @api public\n *\u002F\n\n Manager.prototype.socket = function (nsp, opts) {\n var socket = this.nsps[nsp];\n if (!socket) {\n socket = new socket$1(this, nsp, opts);\n this.nsps[nsp] = socket;\n var self = this;\n socket.on('connecting', onConnecting);\n socket.on('connect', function () {\n socket.id = self.generateId(nsp);\n });\n\n if (this.autoConnect) {\n \u002F\u002F manually call here since connecting event is fired before listening\n onConnecting();\n }\n }\n\n function onConnecting () {\n if (!~indexof(self.connecting, socket)) {\n self.connecting.push(socket);\n }\n }\n\n return socket;\n };\n\n \u002F**\n * Called upon a socket close.\n *\n * @param {Socket} socket\n *\u002F\n\n Manager.prototype.destroy = function (socket) {\n var index = indexof(this.connecting, socket);\n if (~index) this.connecting.splice(index, 1);\n if (this.connecting.length) return;\n\n this.close();\n };\n\n \u002F**\n * Writes a packet.\n *\n * @param {Object} packet\n * @api private\n *\u002F\n\n Manager.prototype.packet = function (packet) {\n debug$6('writing packet %j', packet);\n var self = this;\n if (packet.query && packet.type === 0) packet.nsp += '?' + packet.query;\n\n if (!self.encoding) {\n \u002F\u002F encode, then write to engine with result\n self.encoding = true;\n this.encoder.encode(packet, function (encodedPackets) {\n for (var i = 0; i \u003C encodedPackets.length; i++) {\n self.engine.write(encodedPackets[i], packet.options);\n }\n self.encoding = false;\n self.processPacketQueue();\n });\n } else { \u002F\u002F add packet to the queue\n self.packetBuffer.push(packet);\n }\n };\n\n \u002F**\n * If packet buffer is non-empty, begins encoding the\n * next packet in line.\n *\n * @api private\n *\u002F\n\n Manager.prototype.processPacketQueue = function () {\n if (this.packetBuffer.length \u003E 0 && !this.encoding) {\n var pack = this.packetBuffer.shift();\n this.packet(pack);\n }\n };\n\n \u002F**\n * Clean up transport subscriptions and packet buffer.\n *\n * @api private\n *\u002F\n\n Manager.prototype.cleanup = function () {\n debug$6('cleanup');\n\n var subsLength = this.subs.length;\n for (var i = 0; i \u003C subsLength; i++) {\n var sub = this.subs.shift();\n sub.destroy();\n }\n\n this.packetBuffer = [];\n this.encoding = false;\n this.lastPing = null;\n\n this.decoder.destroy();\n };\n\n \u002F**\n * Close the current socket.\n *\n * @api private\n *\u002F\n\n Manager.prototype.close =\n Manager.prototype.disconnect = function () {\n debug$6('disconnect');\n this.skipReconnect = true;\n this.reconnecting = false;\n if ('opening' === this.readyState) {\n \u002F\u002F `onclose` will not fire because\n \u002F\u002F an open event never happened\n this.cleanup();\n }\n this.backoff.reset();\n this.readyState = 'closed';\n if (this.engine) this.engine.close();\n };\n\n \u002F**\n * Called upon engine close.\n *\n * @api private\n *\u002F\n\n Manager.prototype.onclose = function (reason) {\n debug$6('onclose');\n\n this.cleanup();\n this.backoff.reset();\n this.readyState = 'closed';\n this.emit('close', reason);\n\n if (this._reconnection && !this.skipReconnect) {\n this.reconnect();\n }\n };\n\n \u002F**\n * Attempt a reconnection.\n *\n * @api private\n *\u002F\n\n Manager.prototype.reconnect = function () {\n if (this.reconnecting || this.skipReconnect) return this;\n\n var self = this;\n\n if (this.backoff.attempts \u003E= this._reconnectionAttempts) {\n debug$6('reconnect failed');\n this.backoff.reset();\n this.emitAll('reconnect_failed');\n this.reconnecting = false;\n } else {\n var delay = this.backoff.duration();\n debug$6('will wait %dms before reconnect attempt', delay);\n\n this.reconnecting = true;\n var timer = setTimeout(function () {\n if (self.skipReconnect) return;\n\n debug$6('attempting reconnect');\n self.emitAll('reconnect_attempt', self.backoff.attempts);\n self.emitAll('reconnecting', self.backoff.attempts);\n\n \u002F\u002F check again for the case socket closed in above events\n if (self.skipReconnect) return;\n\n self.open(function (err) {\n if (err) {\n debug$6('reconnect attempt error');\n self.reconnecting = false;\n self.reconnect();\n self.emitAll('reconnect_error', err.data);\n } else {\n debug$6('reconnect success');\n self.onreconnect();\n }\n });\n }, delay);\n\n this.subs.push({\n destroy: function () {\n clearTimeout(timer);\n }\n });\n }\n };\n\n \u002F**\n * Called upon successful reconnect.\n *\n * @api private\n *\u002F\n\n Manager.prototype.onreconnect = function () {\n var attempt = this.backoff.attempts;\n this.reconnecting = false;\n this.backoff.reset();\n this.updateSocketIds();\n this.emitAll('reconnect', attempt);\n };\n\n var lib$1 = createCommonjsModule(function (module, exports) {\n \u002F**\n * Module dependencies.\n *\u002F\n\n\n\n\n var debug = browser('socket.io-client');\n\n \u002F**\n * Module exports.\n *\u002F\n\n module.exports = exports = lookup;\n\n \u002F**\n * Managers cache.\n *\u002F\n\n var cache = exports.managers = {};\n\n \u002F**\n * Looks up an existing `Manager` for multiplexing.\n * If the user summons:\n *\n * `io('http:\u002F\u002Flocalhost\u002Fa');`\n * `io('http:\u002F\u002Flocalhost\u002Fb');`\n *\n * We reuse the existing instance based on same scheme\u002Fport\u002Fhost,\n * and we initialize sockets for each namespace.\n *\n * @api public\n *\u002F\n\n function lookup (uri, opts) {\n if (typeof uri === 'object') {\n opts = uri;\n uri = undefined;\n }\n\n opts = opts || {};\n\n var parsed = url_1(uri);\n var source = parsed.source;\n var id = parsed.id;\n var path = parsed.path;\n var sameNamespace = cache[id] && path in cache[id].nsps;\n var newConnection = opts.forceNew || opts['force new connection'] ||\n false === opts.multiplex || sameNamespace;\n\n var io;\n\n if (newConnection) {\n debug('ignoring socket cache for %s', source);\n io = manager(source, opts);\n } else {\n if (!cache[id]) {\n debug('new io instance for %s', source);\n cache[id] = manager(source, opts);\n }\n io = cache[id];\n }\n if (parsed.query && !opts.query) {\n opts.query = parsed.query;\n }\n return io.socket(parsed.path, opts);\n }\n\n \u002F**\n * Protocol version.\n *\n * @api public\n *\u002F\n\n exports.protocol = socket_ioParser.protocol;\n\n \u002F**\n * `connect`.\n *\n * @param {String} uri\n * @api public\n *\u002F\n\n exports.connect = lookup;\n\n \u002F**\n * Expose constructors for standalone build.\n *\n * @api public\n *\u002F\n\n exports.Manager = manager;\n exports.Socket = socket$1;\n });\n var lib_1 = lib$1.managers;\n var lib_2 = lib$1.protocol;\n var lib_3 = lib$1.connect;\n var lib_4 = lib$1.Manager;\n var lib_5 = lib$1.Socket;\n\n \u002F**\n * @class SharedState\n * @classdesc JavaScript Library for the flexcontrol SharedState\n * @param {string} url URL for the WS connection. if(!url) it tryes to connect to the server wich hosts the socket.io.js\n * @param {string} token the connection token\n * @param {Object} options\n * @param {boolean} [options.reconnection] if the Client should try to reconnect,Default = true\n * @param {boolean} [options.agentid] the AgentID to use, Default = random()\n * @param {boolean} [options.getOnInit] get all Keys from the Server on init(), Default = true\n * @param {boolean} [options.logStateInterval] logs the sharedState every 5sec to the console, Default = false\n * @param {boolean} [options.logToConsole] if things should get logged to console, Default = false\n * @param {boolean} [options.autoPresence] set presence to \"online\" on connect, Default = true\n * @returns {Object} SharedState\n * @author Andreas Bosl \u003Cbosl@irt.de\u003E\n * @copyright 2014 Institut für Rundfunktechnik GmbH, All rights reserved.\n *\n *\u002F\n var SharedState = function (url, options) {\n var _connection = null;\n\n var self = {};\n\n \u002F\u002F READY STATE for Shared State\n var STATE = Object.freeze({\n CONNECTING: \"connecting\",\n OPEN: \"open\",\n CLOSED: \"closed\"\n });\n\n \u002F\u002F Event Handlers\n var _callbacks = {\n 'change': [],\n 'remove': [],\n 'readystatechange': [],\n 'presence': []\n };\n\n var _sharedStates = {};\n\n var _presence = {};\n var _request = false;\n\n var _stateChanges = {};\n\n\n var _log = function (text, datagram) {\n if (options.logToConsole === true) {\n console.info(text, datagram);\n }\n };\n\n var _error = function (text, datagram) {\n if (options.logToConsole === true) {\n console.error(text, datagram);\n }\n };\n\n \u002F* \u003C!-- defaults *\u002F\n options = options || {};\n if (options instanceof String) {\n options = {};\n }\n if (options.reconnection !== false) {\n options.reconnection = true;\n }\n if (!options.agentid) {\n _log('SHAREDSTATE - agentID undefined, generating one for this session');\n options.agentid = (Math.random() * 999999999).toFixed(0);\n }\n if (options.getOnInit !== false) {\n options.getOnInit = true;\n }\n\n if (options.autoPresence !== false) {\n options.autoPresence = true;\n }\n if (options.autoClean !== true) {\n options.autoClean = false;\n }\n options.forceNew = true;\n options.multiplex = false;\n\n url = url || {};\n \u002F* defaults --\u003E *\u002F\n\n\n\n if (options.logStateInterval === true) {\n setInterval(function () {\n _log('SharedSate(' + url + '):', _sharedStates);\n }, 5000);\n }\n\n\n \u002F* \u003C!-- internal functions *\u002F\n var _init = function () {\n\n\n _connection = lib$1(url, options);\n _connection.on('connect', onConnect);\n _connection.on('disconnect', onDisconnect);\n\n _connection.on('joined', onJoined);\n _connection.on('status', onStatus);\n _connection.on('changeState', onChangeState);\n _connection.on('initState', onInitState);\n\n _connection.on('ssError', onError);\n readystate.set('connecting');\n\n\n if (_connection.connected === true) {\n onConnect();\n }\n\n };\n\n var onError = function (data) {\n _error('SharedState-error', data);\n };\n\n var onConnect = function () {\n if (_connection.connected === true) {\n readystate.set('connecting');\n var datagram = {\n agentID: options.agentid\n };\n if (options.userId) {\n datagram.userId = options.userId;\n }\n _sendDatagram('join', datagram);\n }\n };\n\n \u002F*\n Internal method for invoking callback handlers\n\n Handler is only supplied if on one specific callback is to used.\n This is helpful for supporting \"immediate events\", i.e. events given directly\n after handler is registered - on(\"change\", handler);\n\n If handler is not supplied, this means that all callbacks are to be fired.\n This function is also sensitive to whether an \"immediate event\" has already been fired\n or not. See callback registration below.\n *\u002F\n var _do_callbacks = function (what, e, handler) {\n if (!_callbacks.hasOwnProperty(what)) throw \"Unsupported event \" + what;\n var h;\n for (let i = 0; i \u003C _callbacks[what].length; i++) {\n h = _callbacks[what][i];\n if (handler === undefined) {\n \u002F\u002F all handlers to be invoked, except those with pending immeditate\n if (h._immediate_pending) {\n continue;\n }\n } else {\n \u002F\u002F only given handler to be called\n if (h === handler) handler._immediate_pending = false;\n else {\n continue;\n }\n }\n try {\n if (h._ctx) {\n h.call(h._ctx, e);\n } else {\n h.call(self, e);\n }\n } catch (e) {\n console.error(\"Error in \" + what + \": \" + h + \": \" + e);\n }\n }\n };\n \u002F* internal functions --\u003E *\u002F\n\n\n \u002F* \u003C!-- incoming socket functions *\u002F\n var onStatus = function (datagram) {\n _log('SHAREDSTATE - got \"status\"', datagram);\n for (var i = 0; i \u003C datagram.presence.length; i++) {\n if (datagram.presence[i].key && (JSON.stringify(_presence[datagram.presence[i].key]) != JSON.stringify(datagram.presence[i].value || !_presence[datagram.presence[i].key]))) {\n var presence = {\n key: datagram.presence[i].key,\n value: datagram.presence[i].value || undefined\n };\n _presence[datagram.presence[i].key] = datagram.presence[i].value;\n _do_callbacks('presence', presence);\n } else {\n _log('SHAREDSTATE - reveived \"presence\" already saved or something wrong', datagram.presence[i]);\n }\n\n }\n };\n\n var onDisconnect = function () {\n readystate.set('connecting');\n _log('SHAREDSTATE - got \"disconnected\"');\n };\n\n\n var onChangeState = function (datagram) {\n _log('SHAREDSTATE - got \"changeState\"', datagram);\n\n datagram = datagram || {};\n\n\n for (var i = 0; i \u003C datagram.length; i++) {\n if (datagram[i].type == 'set') {\n if (datagram[i].key && JSON.stringify(_sharedStates[datagram[i].key]) != JSON.stringify(datagram[i].value)) {\n var state = {\n key: datagram[i].key,\n value: datagram[i].value,\n type: 'add'\n };\n if (_sharedStates[datagram[i].key]) {\n state.type = 'update';\n }\n _sharedStates[datagram[i].key] = datagram[i].value;\n _do_callbacks('change', state);\n\n } else {\n _log('SHAREDSTATE - reveived \"set\" already saved or something wrong', datagram[i]);\n }\n } else if (datagram[i].type == 'remove') {\n if (datagram[i].key && _sharedStates[datagram[i].key]) {\n var state = {\n key: datagram[i].key,\n value: _sharedStates[datagram[i].key],\n type: 'delete'\n };\n delete _sharedStates[datagram[i].key];\n _do_callbacks('remove', state);\n }\n\n }\n\n }\n };\n\n var onJoined = function (datagram) {\n _log('SHAREDSTATE - got \"joined\"', datagram);\n if (datagram.agentID == options.agentid) {\n if (options.getOnInit === true) {\n var datagram = [];\n _sendDatagram('getInitState', datagram);\n } else {\n readystate.set('open');\n if (options.autoPresence === true) {\n setPresence(\"online\");\n }\n }\n\n\n }\n if (options.autoClean) {\n\n setInterval(function () {\n \u002F\u002F Autoclean - check for meta keys without online nodes\n for (var key in _sharedStates) {\n if (_sharedStates.hasOwnProperty(key)) {\n if (key.indexOf(\"__meta__\") == 0) {\n var agentid = key.substr(8);\n if (!_presence[agentid]) {\n _autoClean(agentid);\n }\n\n }\n }\n }\n }, 5000);\n }\n };\n\n\n\n var onInitState = function (datagram) {\n _log('INITSTATE', datagram);\n\n for (var i = 0, len = datagram.length; i \u003C len; i++) {\n if (datagram[i].type == 'set') {\n if (datagram[i].key && datagram[i].value && JSON.stringify(_sharedStates[datagram[i].key]) != JSON.stringify(datagram[i].value)) {\n var state = {\n key: datagram[i].key,\n value: datagram[i].value,\n type: 'add'\n };\n _sharedStates[datagram[i].key] = datagram[i].value;\n _do_callbacks('change', state);\n }\n }\n }\n readystate.set('open');\n if (options.autoPresence === true) {\n setPresence(\"online\");\n }\n\n };\n\n\n var _autoClean = function (agentid) {\n if (!options.autoClean) {\n return;\n }\n _log(\"*** Cleaning agent \", agentid);\n \u002F\u002F Go through the dataset and remove anything left from this node\n for (var key in _sharedStates) {\n if (_sharedStates.hasOwnProperty(key)) {\n if (key.indexOf(\"__\") == 0 && key.indexOf(\"__\" + agentid) \u003E -1) {\n self.removeItem(key);\n }\n }\n }\n };\n\n\n \u002F* incoming socket functions --\u003E *\u002F\n\n\n\n \u002F* \u003C!-- outgoing socket functions *\u002F\n var _sendDatagram = function (type, datagram) {\n _log('SHAREDSTATE - sending', datagram);\n _connection.emit(type, datagram);\n };\n \u002F* outgoing socket functions --\u003E *\u002F\n\n\n \u002F* \u003C!-- API functions *\u002F\n \u002F**\n * sets a key in the sharedState\n * @method setItem\n * @param {string} key the key to set\n * @param {Object} value the value to set\n * @param {string} [options] tbd\n * @returns {Object} SharedState\n * @memberof SharedState\n *\u002F\n var setItem = function (key, value, options) {\n if (_request) {\n var state = {\n type: 'set',\n key: key,\n value: value\n };\n\n _stateChanges[key] = state;\n } else {\n if (readystate.get() === STATE.OPEN) {\n if (key) {\n var datagram = [\n {\n type: 'set',\n key: key,\n value: value\n }\n ];\n _sendDatagram('changeState', datagram);\n } else {\n throw 'SHAREDSTATE - params with error - key:' + key + 'value:' + value;\n }\n } else {\n throw 'SHAREDSTATE - setItem not possible - connection status:' + readystate.get();\n }\n }\n\n\n return self;\n };\n\n \u002F**\n * removes a key from the sharedState\n * @method removeItem\n * @param {string} key the key to remove\n * @param {string} [options] tbd\n * @returns {Object} SharedState\n * @memberof SharedState\n *\u002F\n var removeItem = function (key, options) {\n if (_request) {\n var state = {\n type: 'remove',\n key: key\n };\n _stateChanges[key] = state;\n } else {\n if (readystate.get() == STATE.OPEN) {\n if (_sharedStates[key]) {\n var datagram = [\n {\n type: 'remove',\n key: key\n }\n ];\n _sendDatagram('changeState', datagram);\n } else {\n throw 'SHAREDSTATE - key with error - key:' + key;\n }\n } else {\n throw 'SHAREDSTATE - removeItem not possible - connection status:' + readystate.get();\n }\n }\n return self;\n };\n\n \u002F**\n * starts the request builder\n * @method request\n * @returns {Object} SharedState\n * @memberof SharedState\n *\u002F\n var request = function () {\n _request = true;\n return self;\n };\n\n \u002F**\n * stops the request builder and sends all changes\n * @method send\n * @returns {Object} SharedState\n * @memberof SharedState\n *\u002F\n var send = function () {\n if (readystate.get() == STATE.OPEN) {\n _request = false;\n if (Object.keys(_stateChanges).length \u003E 0) {\n var datagram = [];\n var keys = Object.keys(_stateChanges);\n for (var i = 0; i \u003C keys.length; i++) {\n datagram.push(_stateChanges[keys[i]]);\n }\n _sendDatagram('changeState', datagram);\n\n _stateChanges = {};\n }\n } else {\n throw 'SHAREDSTATE - send not possible - connection status:' + readystate.get();\n }\n return self;\n };\n\n \u002F**\n * returns a value of the given key\n * @method getItem\n * @param {string} key the key to remove\n * @param {string} [options] tbd\n * @returns {Object} data\n * @returns {Object} data.key the value of the key\n * @returns {Object} data.newValue the value of the key\n * @memberof SharedState\n *\u002F\n var getItem = function (key, options) {\n\n if (key === undefined || key === null) {\n var datagram = [];\n _sendDatagram('getState', datagram);\n return;\n } else {\n key = key + '';\n if (_sharedStates[key]) {\n return JSON.parse(JSON.stringify(_sharedStates[key]));\n }\n }\n return;\n };\n\n \u002F**\n * returns an Array of keys\n * @method keys\n * @returns {Array} keys\n * @memberof SharedState\n *\u002F\n var keys = function () {\n\n return Object.keys(_sharedStates);\n\n };\n\n\n \u002F*\n READYSTATE\n\n encapsulate protected property _readystate by wrapping\n getter and setter logic around it.\n Closure ensures that all state transfers must go through set function.\n Possibility to implement verification on all attempted state transferes\n Event\n\n *\u002F\n var readystate = function () {\n var _readystate = STATE[\"CONNECTING\"];\n \u002F\u002F accessors\n return {\n set: function (new_state) {\n \u002F\u002F check new state value\n let found = false;\n for (let key in STATE) {\n if (!STATE.hasOwnProperty(key)) continue;\n if (STATE[key] === new_state) found = true;\n }\n if (!found) throw \"Illegal state value \" + new_state;\n \u002F\u002F check state transition\n if (_readystate === STATE[\"CLOSED\"]) return; \u002F\u002F never leave final state\n \u002F\u002F perform state transition\n if (new_state !== _readystate) {\n _readystate = new_state;\n \u002F\u002F trigger events\n _do_callbacks(\"readystatechange\", new_state);\n }\n },\n get: function () {\n return _readystate;\n }\n };\n }();\n\n \u002F**\n * registers a function on event, function gets called immediatly\n * @method on\n * @param {string} what change || presence || readystatechange\n * @param {function} handler the function to call on event\n * @returns {Object} SharedState\n * @memberof SharedState\n *\u002F\n \u002F*\n register callback\n\n The complexity of this method arise from the fact that we are to give\n an \"immediate callback\" to the given handler.\n\n In addition, I do not want to do so directly within the on() method.\n\n As a programmer I would like to ensure that initialisation of an object\n is completed BEFORE the object needs to process any callbacks from the\n external world. This can be problematic if the object depends on events\n from multiple other objects. For example, the internal initialisation code\n needs to register handlers on external objects a and b.\n\n a.on(\"event\", internal_handler_a);\n b.on(\"event\", internal_handler_b);\n\n However, if object a gives an callback immediately within on, this callback\n will be processed BEFORE we have completed initialisation, i.e., any code\n subsequent to a.on).\n\n It is quite possible to make this be correct still, but I find nested handler\n invocation complicated to think about, and I prefer to avoid the problem.\n Therefore I like instead to make life easier by delaying \"immediate callbacks\"\n using\n\n setTimeout(_do_callbacks(\"event\", e, handler), 0);\n\n This however introduces two new problems. First, if you do :\n\n o.on(\"event\", handler);\n o.off(\"event\", handler);\n\n you will get the \"immediate callback\" after off(), which is not what you\n expect. This is avoided by checking that the given handler is indeed still\n registered when executing _do_callbacks(). Alternatively one could cancel the\n timeout within off().\n\n Second, with the handler included in _callbacks[what] it is possible to receive\n event callbacks before the delayed \"immediate callback\" is actually invoked.\n This breaks the expectation the the \"immediate callback\" is the first callback.\n This problem is avoided by flagging the callback handler with \".immediate_pending\"\n and dropping notifications that arrive before the \"immediate_callback has executed\".\n Note however that the effect of this dropped notification is not lost. The effects\n are taken into account when we calculate the \"initial state\" to be reported by the\n \"immediate callback\". Crucially, we do this not in the on() method, but when the\n delayed \"immediate callback\" actually is processed.\n *\u002F\n\n var on = function (what, handler, ctx) {\n if (!handler || typeof handler !== \"function\") throw \"Illegal handler\";\n if (!_callbacks.hasOwnProperty(what)) throw \"Unsupported event \" + what;\n if (ctx) {\n handler._ctx = ctx;\n }\n var index = _callbacks[what].indexOf(handler);\n if (index === -1) {\n \u002F\u002F register handler\n _callbacks[what].push(handler);\n \u002F\u002F flag handler\n handler._immediate_pending = true;\n \u002F\u002F do immediate callback\n setTimeout(function () {\n switch (what) {\n case 'change':\n var keys = Object.keys(_sharedStates);\n if (keys.length === 0) {\n handler._immediate_pending = false;\n } else {\n for (var i = 0, len = keys.length; i \u003C len; i++) {\n var state = {\n key: keys[i],\n value: _sharedStates[keys[i]],\n type: 'update'\n };\n _do_callbacks('change', state, handler);\n }\n }\n break;\n case 'presence':\n var keys = Object.keys(_presence);\n if (keys.length === 0) {\n handler._immediate_pending = false;\n } else {\n for (var i = 0, len = keys.length; i \u003C len; i++) {\n var presence = {\n key: keys[i],\n value: _presence[keys[i]]\n };\n _do_callbacks('presence', presence, handler);\n }\n }\n break;\n case 'remove':\n handler._immediate_pending = false;\n break;\n case 'readystatechange':\n _do_callbacks(\"readystatechange\", readystate.get(), handler);\n break;\n }\n }, 100);\n }\n return self;\n };\n\n \u002F**\n * deregisters a function on event\n * @method off\n * @param {string} what change || presence || readystatechange\n * @param {function} handler the function to call on event\n * @returns {Object} SharedState\n * @memberof SharedState\n *\u002F\n \u002F\u002F unregister callback\n var off = function (what, handler) {\n if (_callbacks[what] !== undefined) {\n var index = _callbacks[what].indexOf(handler);\n if (index \u003E -1) {\n _callbacks[what].splice(index, 1);\n }\n }\n return self;\n };\n\n\n \u002F**\n * sets the presence of the client ('connected' and 'disconnected' automatically set by server)\n * @method setPresence\n * @param {string} state the string to set the presence to\n * @returns {Object} SharedState\n * @memberof SharedState\n *\u002F\n var setPresence = function (state) {\n if (readystate.get() == STATE.OPEN) {\n if (state) {\n var datagram = {\n agentID: options.agentid,\n presence: state\n };\n _sendDatagram('changePresence', datagram);\n\n } else {\n throw 'SHAREDSTATE - params with error - state:' + state;\n }\n } else {\n throw 'SHAREDSTATE - send not possible - connection status:' + readystate.get();\n }\n return self;\n };\n \u002F* API functions --\u003E *\u002F\n\n\n \u002F* \u003C!-- public *\u002F\n self.__defineGetter__(\"readyState\", readystate.get);\n self.__defineGetter__(\"STATE\", function () {\n return STATE;\n });\n self.__defineGetter__(\"agentid\", function () {\n return options.agentid;\n });\n\n self.setItem = setItem;\n self.removeItem = removeItem;\n\n self.request = request;\n self.send = send;\n\n self.getItem = getItem;\n\n self.keys = keys;\n self.on = on;\n self.off = off;\n self.setPresence = setPresence;\n \u002F* public --\u003E *\u002F\n\n _init();\n\n return self;\n };\n\n \u002F\u002F The Application context is based on a shared state object\n var ApplicationContext = function (url, options) {\n function clone(obj) {\n if (null == obj || \"object\" != typeof obj) return obj;\n var copy = obj.constructor();\n for (var attr in obj) {\n if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);\n }\n return copy;\n }\n if (!AgentContext) {\n throw \"Missing flexcontrol agentContext, please load the correct js files\"\n }\n var _the_self = AgentContext();\n\n options = options || {};\n var self = {};\n var _agents = {}; \u002F\u002F We have a separate agent mapping of agent Context\n if (SharedState === undefined) {\n throw \"Missing flexcontrol shared state, please include the correct js files\";\n }\n\n var remoteAgentContext = function (__agentid) {\n var self = {};\n var _handlers = {\n \"agentchange\": []\n };\n self.agentid = __agentid;\n var _last_capabilities = [];\n\n _sharedstate.on(\"change\", function (e) {\n if (e.key.indexOf(\"__val__\" + self.agentid) == 0) {\n \u002F\u002F Updated values for my stuff\n if (_handlers[e.key] && _handlers[e.key].length \u003E 0) {\n for (var i = 0; i \u003C _handlers[e.key].length; i++) {\n try {\n _handlers[e.key][i].call(self.publicAPI, e);\n } catch (err) {\n console.log(\"Error in callback: \" + what);\n }\n }\n }\n }\n });\n\n var keys = function () {\n var meta = _sharedstate.getItem(\"__meta__\" + self.agentid);\n if (!meta) return [];\n return meta.keys;\n };\n\n var capabilities = function () {\n var meta = _sharedstate.getItem(\"__meta__\" + self.agentid);\n if (!meta) return [];\n return meta.capabilities;\n };\n\n var on = function (what, handler) {\n if (what == \"agentchange\") {\n _handlers[\"agentchange\"].push(handler);\n return;\n }\n\n if (keys().indexOf(what) == -1) {\n throw \"Unknown parameter \" + what;\n }\n var subscriptions = _sharedstate.getItem(\"__metasub__\" + _sharedstate.agentid) || [];\n var item = self.agentid + \"_\" + what;\n\n if (subscriptions.indexOf(item) == -1) {\n subscriptions.push(item);\n _sharedstate.setItem(\"__metasub__\" + _sharedstate.agentid, subscriptions);\n }\n \u002F\u002F Remember the handler\n if (_handlers[what] === undefined) {\n _handlers[what] = [];\n }\n _handlers[what].push(handler);\n\n \u002F\u002F check if we have a value already\n var value = _sharedstate.getItem('__val__' + item);\n if (value) {\n handler.call(self.publicAPI, what, value);\n }\n };\n\n var off = function (what, handler) {\n if (what == \"agentchange\") {\n _handlers[what].splice(_handlers[what].indexOf(handler), 1);\n return;\n }\n if (keys().indexOf(what) == -1) {\n throw \"Unknown parameter \" + what;\n }\n var subscriptions = clone(_sharedstate.getItem(\"__metasub__\" + _sharedstate.agentid));\n var item = self.agentid + \"_\" + what;\n\n if (subscriptions.indexOf(item) \u003E -1) {\n subscriptions.splice(subscriptions.indexOf(item), 1);\n _sharedstate.setItem(\"__metasub__\" + _sharedstate.agentid, subscriptions);\n } else {\n throw \"Not subscribed to \" + what;\n }\n \u002F\u002F TODO: Clean up _handlers[what]\n if (!_handlers[what]) {\n console.log(\"*** Warning: Missing handler for \" + what);\n return;\n\n }\n _handlers[what].splice(_handlers[what].indexOf(item), 1);\n };\n\n var update_meta = function (meta) {\n var diff = {\n \"added\": {},\n \"altered\": {},\n \"removed\": []\n };\n for (var capability in _last_capabilities) {\n if (meta.capabilities[capability] === undefined) {\n diff.removed.push(capability);\n }\n }\n for (var capability in meta.capabilities) {\n if (_last_capabilities[capability] === undefined) {\n diff.added[capability] = meta.capabilities[capability];\n } else if (_last_capabilities[capability] != meta.capabilities[capability]) {\n diff.altered[capability] = meta.capabilities[capability];\n }\n }\n _last_capabilities = meta.capabilities;\n\n for (var i = 0; i \u003C _handlers[\"agentchange\"].length; i++) {\n try {\n _handlers[\"agentchange\"][i].call(self.publicAPI, {\n diff: diff\n });\n } catch (err) {\n console.log(\"Error in meta-update\", err);\n }\n }\n };\n\n var update_value = function (_aid, _key, _value) {\n if (_handlers[_key] === undefined) {\n return;\n }\n for (var i = 0; i \u003C _handlers[_key].length; i++) {\n try {\n _handlers[_key][i].call(self.publicAPI, _key, _value);\n } catch (err) {\n console.log(\"Error in update: \", err);\n }\n }\n };\n\n var setItem = function (key, value) {\n _sharedstate.setItem(\"__val__\" + self.agentid + \"_\" + key, value);\n };\n var getItem = function (key) {\n return _sharedstate.getItem(\"__val__\" + self.agentid + \"_\" + key);\n };\n self.update_value = update_value;\n self.handlers = _handlers;\n self.update_meta = update_meta;\n\n let publicAPI = {};\n publicAPI.agentid = self.agentid;\n publicAPI.keys = keys;\n publicAPI.on = on;\n publicAPI.off = off;\n publicAPI.setItem = setItem;\n publicAPI.getItem = getItem;\n publicAPI.capabilities = capabilities;\n\n\n self.publicAPI = publicAPI;\n\n return self;\n };\n\n\n \u002F\u002F Handle updates\n var _sub_param2agent = []; \u002F\u002F Remote subscriptions, parameter -\u003E [agentid, ...]\n var _sub_agent2param = {}; \u002F\u002F Remote agent -\u003E [parameter, ...]\n var _sub_param2handler = {};\n var _currentAgentState = {}; \u002F\u002F For diffs\n var _last_capabilities = {}; \u002F\u002F For annouce diffs\n\n var _update_subscriptions = function (_aid2, _subs) {\n if (getAgent(_aid2) == undefined) {\n console.log(\"Subscription update for unknown node\", _aid2);\n setTimeout(()=\u003E{\n if (getAgent(_aid2) !== undefined){\n _update_subscriptions(_aid2, _subs);\n _subs = _subs || _sharedstate.getItem(\"__metasub__\" + _aid2);\n if (!_subs) {\n return;\n }\n var handlers = getAgent(_aid2).handlers;\n var thesubs = _sub_agent2param[_aid2] || [];\n for (var i = 0; i \u003C _subs.length; i++) {\n var target_agent = _subs[i].substr(0, _subs[i].indexOf(\"_\"));\n var target_param = _subs[i].substr(_subs[i].indexOf(\"_\") + 1);\n if (target_agent === _sharedstate.agentid) {\n \u002F\u002F See if this agent already subscribes to my parameter, otherwise add it\n if (thesubs.indexOf(target_param) \u003E -1) {\n \u002F\u002F Aready subscribed to this one\n continue;\n }\n if (_sub_param2agent[target_param] === undefined) {\n _sub_param2agent[target_param] = [];\n }\n _sub_param2agent[target_param].push(_aid2);\n if (!_sub_param2handler[target_param] || _sub_param2handler[target_param].length == 0) {\n _sub_param2handler[target_param] = function (e) {\n _sharedstate.setItem(\"__val__\" + _sharedstate.agentid + \"_\" + e.key, e.value);\n };\n }\n _the_self.on(target_param, _sub_param2handler[target_param]);\n }\n }\n \u002F\u002F see if there was an unsubscribe\n for (var i = 0; i \u003C thesubs.length; i++) {\n if (_sub_param2agent.indexOf(thesubs[i]) == -1) {\n var target_agent = thesubs[i].substr(0, thesubs[i].indexOf(\"_\"));\n var target_param = thesubs[i].substr(thesubs[i].indexOf(\"_\") + 1);\n if (target_agent != _sharedstate.agentid) {\n continue;\n }\n if (_sub_param2agent[target_param] != undefined) {\n _sub_param2agent[target_param].splice(_sub_param2agent[target_param].indexOf(thesubs[i]), 1);\n if (_sub_param2agent[target_param].length == 0) ;\n } else {\n console.log(\" *** Warning, unsub but don't have handler\");\n }\n \u002F\u002F TODO: Clean up if nobody uses it any more\n \u002F\u002F if _sub_param2agent[target_param].length == 0\n }\n }\n _sub_agent2param[_aid2] = _subs;\n }\n\n },1000);\n return;\n }\n _subs = _subs || _sharedstate.getItem(\"__metasub__\" + _aid2);\n if (!_subs) {\n return;\n }\n var handlers = getAgent(_aid2).handlers;\n var thesubs = _sub_agent2param[_aid2] || [];\n for (var i = 0; i \u003C _subs.length; i++) {\n var target_agent = _subs[i].substr(0, _subs[i].indexOf(\"_\"));\n var target_param = _subs[i].substr(_subs[i].indexOf(\"_\") + 1);\n if (target_agent === _sharedstate.agentid) {\n \u002F\u002F See if this agent already subscribes to my parameter, otherwise add it\n if (thesubs.indexOf(target_param) \u003E -1) {\n \u002F\u002F Aready subscribed to this one\n continue;\n }\n if (_sub_param2agent[target_param] === undefined) {\n _sub_param2agent[target_param] = [];\n }\n _sub_param2agent[target_param].push(_aid2);\n if (!_sub_param2handler[target_param] || _sub_param2handler[target_param].length == 0) {\n _sub_param2handler[target_param] = function (e) {\n _sharedstate.setItem(\"__val__\" + _sharedstate.agentid + \"_\" + e.key, e.value);\n };\n }\n _the_self.on(target_param, _sub_param2handler[target_param]);\n }\n }\n \u002F\u002F see if there was an unsubscribe\n for (var i = 0; i \u003C thesubs.length; i++) {\n if (_sub_param2agent.indexOf(thesubs[i]) == -1) {\n var target_agent = thesubs[i].substr(0, thesubs[i].indexOf(\"_\"));\n var target_param = thesubs[i].substr(thesubs[i].indexOf(\"_\") + 1);\n if (target_agent != _sharedstate.agentid) {\n continue;\n }\n if (_sub_param2agent[target_param] != undefined) {\n _sub_param2agent[target_param].splice(_sub_param2agent[target_param].indexOf(thesubs[i]), 1);\n if (_sub_param2agent[target_param].length == 0) ;\n } else {\n console.log(\" *** Warning, unsub but don't have handler\");\n }\n \u002F\u002F TODO: Clean up if nobody uses it any more\n \u002F\u002F if _sub_param2agent[target_param].length == 0\n }\n }\n _sub_agent2param[_aid2] = _subs;\n };\n\n var _update_value = function (_aid, _key, _val) {\n var ra = getAgent(_aid);\n if (ra) {\n ra.update_value(_aid, _key, _val);\n }\n };\n\n var _cbs = [];\n var _globalCbs = [];\n var on = function (what, handler) {\n if (what == \"agentchange\") {\n _cbs.push(handler);\n } else {\n if (!_globalCbs[what]) {\n _globalCbs[what] = [];\n }\n _globalCbs[what].push(handler);\n }\n\n };\n\n var off = function (what, handler) {\n if (what == \"agentchange\") {\n if (_cbs.indexOf(handler) == -1) {\n throw \"Handler not registered\";\n }\n _cbs.splice(_cbs.indexOf(handler), 1);\n } else {\n if (_globalCbs[what].indexOf(handler) == -1) {\n throw \"Handler not registered\";\n }\n _globalCbs[what].splice(_globalCbs[what].indexOf(handler), 1);\n }\n\n };\n\n\n var _doCallbacks = function (what, e) {\n if (what == \"agentchange\") {\n if (!e.agentContext) {\n delete _currentAgentState[e.agentid];\n console.log(_currentAgentState);\n } else if (_currentAgentState[e.agentid] == undefined) {\n _currentAgentState[e.agentid] = {\n capabilities: {},\n keys: []\n };\n }\n\n \u002F\u002F Create diff\n e.diff = {\n capabilities: {},\n keys: []\n };\n if (e.agentContext) {\n\n var cs = e.agentContext.capabilities();\n for (var c in cs) {\n if (cs.hasOwnProperty(c)) {\n if (!_currentAgentState[e.agentid].capabilities) {\n _currentAgentState[e.agentid].capabilities = {};\n }\n if (_currentAgentState[e.agentid].capabilities[c] != cs[c]) {\n e.diff.capabilities[c] = cs[c];\n }\n }\n }\n \u002F\u002F Also check keys\n var keys = e.agentContext.keys();\n for (var i = 0; i \u003C keys.length; i++) {\n if (_currentAgentState[e.agentid].keys.indexOf(keys[i]) == -1) {\n e.diff.keys.push(keys[i]);\n }\n }\n\n _currentAgentState[e.agentid] = {\n capabilities: cs,\n keys: keys\n };\n\n }\n for (var i = 0; i \u003C _cbs.length; i++) {\n try {\n _cbs[i].call(self, e);\n } catch (err) {\n console.log(\"Error in agentchange callback: \", err);\n console.log(\"cb:\", _cbs[i]);\n }\n }\n } else {\n for (var i = 0, len = _globalCbs[what].length; i \u003C len; i++) {\n try {\n _globalCbs[what][i].call(self, e);\n } catch (err) {\n console.log(\"Error in agentchange callback: \", err);\n console.log(\"cb:\", _globalCbs[what][i]);\n }\n }\n }\n\n };\n\n options.autoPresence = true;\n\n var __ready = false;\n var _sharedstate = SharedState(url, options)\n .on(\"readystatechange\", function (s) {\n if (s === _sharedstate.STATE.OPEN) {\n __ready = true;\n _sharedstate.setItem(\"__meta__\" + _sharedstate.agentid, meta);\n _sharedstate.setItem(\"__metasub__\" + _sharedstate.agentid, []);\n _sharedstate.setPresence(\"online\");\n console.log(_the_self.keys());\n \u002F\u002F Register\n var meta = {\n keys: _the_self.keys(),\n capabilities: _the_self.capabilities()\n };\n\n\n \u002F\u002F Also check for new parameters being added\n \u002F\u002F ABosl - moved here to only test if _sharedstate == open\n _the_self.on(\"keychange\", function (e) {\n \u002F\u002F Update my meta description too\n var meta = {\n keys: _the_self.keys(),\n capabilities: _the_self.capabilities()\n };\n _sharedstate.setItem(\"__meta__\" + _sharedstate.agentid, meta);\n });\n\n _sharedstate.on(\"presence\", function (event) {\n\n var _agentid = event.key;\n var _state = event.value;\n if (event.value == \"offline\") {\n if (_agents.hasOwnProperty(_agentid)) {\n \u002F\u002F Clean up this node\n\n _agents[_agentid] = null;\n delete _agents[_agentid];\n _doCallbacks(\"agentchange\", {\n agentid: _agentid,\n agentContext: null\n });\n\n }\n \u002F\u002F TODO: Also clean up other state that should be removed?\n \u002F*\n if (options.autoremove == true) {\n this.removeItem(\"__meta__\" + _agentid)\n this.removeItem(\"__metasub__\" + _agentid)\n this.delAgent(_agentid);\n }\n *\u002F\n\n } else {\n if (event.value == \"online\") {\n if (!_agents[_agentid]) {\n _agents[_agentid] = remoteAgentContext(_agentid);\n }\n\n var diff = {\n \"added\": {},\n \"altered\": {},\n \"removed\": []\n };\n var new_capabilities = _agents[_agentid].publicAPI.capabilities();\n if (_last_capabilities[_agentid] != undefined) {\n for (var capability in _last_capabilities[_agentid]) {\n console.log(capability);\n if (new_capabilities[capability] === undefined) {\n diff.removed.push(capability);\n }\n }\n } else {\n _last_capabilities[_agentid] = [];\n }\n\n for (var capability in new_capabilities) {\n if (_last_capabilities[_agentid][capability] === undefined) {\n diff.added[capability] = new_capabilities[capability];\n } else if (_last_capabilities[_agentid][capability] != new_capabilities[capability]) {\n diff.altered[capability] = new_capabilities[capability];\n }\n }\n\n _last_capabilities[_agentid] = new_capabilities;\n _update_subscriptions(_agentid);\n _doCallbacks(\"agentchange\", {\n agentid: _agentid,\n agentContext: _agents[_agentid].publicAPI,\n diff: diff\n });\n }\n\n }\n \u002F\u002F_agents[_agentid].\n });\n }\n })\n .on(\"change\", function (e) {\n if (e.key.indexOf(\"__meta__\") \u003E -1) {\n var _agentid = e.key.substr(8);\n var a = _agents[_agentid];\n if (a) {\n a.update_meta(e.value);\n _doCallbacks(\"agentchange\", {\n agentid: _agentid,\n agentContext: _agents[_agentid].publicAPI\n });\n } else {\n if (options.autoremove == true) {\n _sharedstate.removeItem(\"__meta__\" + _agentid);\n _sharedstate.removeItem(\"__metasub__\" + _agentid);\n }\n }\n } else if (e.key.indexOf(\"__metasub__\") \u003E -1) {\n var _agentid = e.key.substr(11);\n\n\n _update_subscriptions(_agentid, e.value);\n\n\n\n\n\n } else if (e.key.indexOf(\"__val__\") \u003E -1) {\n var _agentid = e.key.substr(7).split(\"_\")[0];\n var _key = e.key.substr(7).split(\"_\")[1];\n _update_value(_agentid, _key, e.value);\n } else if (e.key.indexOf('__global__') \u003E -1) {\n console.log('globalVar', e.key, e.value);\n var _what = e.key.substr(10);\n if (_globalCbs[_what]) {\n _doCallbacks(_what, {\n key: _what,\n value: e.value\n });\n }\n } else {\n console.log(\"Unknown Change:\" + JSON.stringify(e));\n }\n });\n\n\n\n\n var getAgent = function (aid) {\n return _agents[aid];\n };\n\n var getAgents = function () {\n if (_agents[_sharedstate.agentid] == undefined) {\n _agents[_sharedstate.agentid] = remoteAgentContext(_sharedstate.agentid);\n }\n var agents = {\n self: _agents[_sharedstate.agentid].publicAPI\n };\n for (var a in _agents) {\n if (a == _sharedstate.agentid) continue;\n if (_agents[_sharedstate.agentid]) {\n agents[a] = _agents[a].publicAPI;\n }\n }\n return agents;\n };\n var _ready = function (){\n\n return __ready;\n };\n var addAgent = function(agent) {\n \u002F\u002F TODO: Check argument!\n if (_agents[agent.agentid] != undefined) {\n throw new Error(\"Already have agent '\" + agent.agentid + \"'\");\n }\n _agents[agent.agentid] = agent;\n _doCallbacks(\"agentchange\", {\n agentid: agent.agentid,\n agentContext: agent,\n diff: {\"added\":agent.capabilities()}\n });\n };\n\n var removeAgent = function(agent) {\n if (_agents[agent.agentid] != agent) {\n throw new Error(\"Failed to remove agent '\" + agent.agentid + \"'\");\n }\n delete _agents[agent.agentid];\n _doCallbacks(\"agentchange\", {\n agentid: agent.agentid,\n agentContext: null\n });\n };\n\n\n var setItem = function (key, value) {\n if (key && value) {\n _sharedstate.setItem('__global__' + key, value);\n }\n };\n\n var getKeys = function () {\n var allKeys = _sharedstate.keys();\n var globalVars = [];\n for (var i = 0, len = allKeys.length; i \u003C len; i++) {\n if (allKeys[i].indexOf('__global__') \u003E -1) {\n globalVars.push(allKeys[i].substr(10));\n }\n }\n return globalVars;\n };\n\n var getItem = function (key) {\n return _sharedstate.getItem('__global__' + key);\n };\n\n \u002F\u002F API\n self.getAgents = getAgents;\n self.on = on;\n self.off = off;\n self.me = _the_self;\n self.ready = _ready;\n \u002F\u002FAPI for Global Variables\n self.setItem = setItem;\n self.getItem = getItem;\n self.keys = getKeys;\n\n \u002F\u002F API for locally discovered agents - should only be used for node discovery frameworks\n self.addAgent = addAgent;\n self.removeAgent = removeAgent;\n return self;\n };\n\n \u002F**\n *\n *\u002F\n var MappingService = function (url, options) {\n\n var _connection = null;\n var self = {};\n\n \u002F\u002F READY STATE for Shared State\n var STATE = Object.freeze({\n CONNECTING: \"connecting\",\n OPEN: \"open\",\n CLOSED: \"closed\"\n });\n\n if (typeof url === 'object') {\n var options = url;\n url = {};\n \n }\n\n \u002F\u002F Event Handlers\n var _callbacks = {\n 'readystatechange': []\n };\n \u002F* \u003C!-- defaults *\u002F\n if (!options) {\n var options = {};\n }\n\n if (!options.maxTimeout){\n options.maxTimeout = 2000;\n }\n\n options.forceNew = true;\n options.multiplex = false;\n\n var connectURL = url || {};\n \u002F* defaults --\u003E *\u002F\n\n var waitingUserPromises = [];\n var waitingGroupPromises = [];\n\n \u002F* \u003C!-- internal functions *\u002F\n var _init = function () {\n\n _connection = lib$1(connectURL, options);\n _connection.on('connect', onConnect);\n readystate.set('connecting');\n _connection.on('mapping', onMapping);\n if (_connection.connected === true) {\n onConnect();\n }\n\n };\n\n var onConnect = function () {\n readystate.set('open');\n };\n\n var onMapping = function (response) {\n var host = url;\n\n if (typeof url === 'object' || !url) {\n var host = window.location.protocol + '\u002F\u002F' + window.location.host + '\u002F';\n }\n\n if (!response.group) {\n var result = {};\n if (response.user) {\n result.user = host + response.user;\n }\n if (response.app) {\n result.app = host + response.app;\n }\n if (response.userApp) {\n result.userApp = host + response.userApp;\n }\n\n\n if (waitingUserPromises.length \u003E 0) {\n promise = waitingUserPromises.pop();\n promise(result);\n }\n } else {\n var result = {\n group: host + response.group\n };\n if (waitingGroupPromises.length \u003E 0) {\n let promise = waitingGroupPromises.pop();\n promise(result);\n }\n }\n };\n\n \u002F*\n Internal method for invoking callback handlers\n\n Handler is only supplied if on one specific callback is to used.\n This is helpful for supporting \"immediate events\", i.e. events given directly\n after handler is registered - on(\"change\", handler);\n\n If handler is not supplied, this means that all callbacks are to be fired.\n This function is also sensitive to whether an \"immediate event\" has already been fired\n or not. See callback registration below.\n *\u002F\n var _do_callbacks = function (what, e, handler) {\n if (!_callbacks.hasOwnProperty(what)) throw \"Unsupported event \" + what;\n var h;\n for (let i = 0; i \u003C _callbacks[what].length; i++) {\n h = _callbacks[what][i];\n if (handler === undefined) {\n \u002F\u002F all handlers to be invoked, except those with pending immeditate\n if (h._immediate_pending) {\n continue;\n }\n } else {\n \u002F\u002F only given handler to be called\n if (h === handler) handler._immediate_pending = false;\n else {\n continue;\n }\n }\n try {\n h.call(self, e);\n } catch (e) {\n _error(\"Error in \" + what + \": \" + h + \": \" + e);\n }\n }\n };\n\n \u002F*\n READYSTATE\n\n encapsulate protected property _readystate by wrapping\n getter and setter logic around it.\n Closure ensures that all state transfers must go through set function.\n Possibility to implement verification on all attempted state transferes\n Event\n\n *\u002F\n var readystate = function () {\n var _readystate = STATE[\"CONNECTING\"];\n \u002F\u002F accessors\n return {\n set: function (new_state) {\n \u002F\u002F check new state value\n let found = false;\n for (let key in STATE) {\n if (!STATE.hasOwnProperty(key)) continue;\n if (STATE[key] === new_state) found = true;\n }\n if (!found) throw \"Illegal state value \" + new_state;\n \u002F\u002F check state transition\n if (_readystate === STATE[\"CLOSED\"]) return; \u002F\u002F never leave final state\n \u002F\u002F perform state transition\n if (new_state !== _readystate) {\n _readystate = new_state;\n \u002F\u002F trigger events\n _do_callbacks(\"readystatechange\", new_state);\n }\n },\n get: function () {\n return _readystate;\n }\n };\n }();\n\n var getUserMapping = function (appId, scopeList) {\n if (appId && Array.isArray(scopeList)) {\n var request = {\n appId: appId\n };\n for (var i = 0, len = scopeList.length; i \u003C len; i++) {\n if (scopeList[i] === 'user') {\n request.user = true;\n }\n if (scopeList[i] === 'app') {\n request.app = true;\n }\n if (scopeList[i] === 'userApp') {\n request.userApp = true;\n }\n }\n if (options.userId) {\n request.userId = options.userId;\n }\n _connection.emit('getMapping', request);\n } else {\n throw 'appId or scopeList undefined';\n }\n return new Promise(function (fulfill, reject) {\n waitingUserPromises.push(function (data) {\n fulfill(data);\n });\n setTimeout(function () {\n reject({\n error: 'timeout-mappinservice'\n });\n }, options.maxTimeout);\n });\n };\n\n var getGroupMapping = function (groupId) {\n if (groupId) {\n var request = {\n groupId: groupId\n };\n _connection.emit('getMapping', request);\n } else {\n throw 'groupId undefined';\n }\n return new Promise(function (fulfill, reject) {\n waitingGroupPromises.push(function (data) {\n fulfill(data);\n });\n console.log(options.maxTimeout);\n setTimeout(function () {\n reject({\n error: 'timeout-mappinservice2'\n });\n }, options.maxTimeout);\n });\n };\n\n \u002F**\n * registers a function on event, function gets called immediatly\n * @method on\n * @param {string} what change || presence || readystatechange\n * @param {function} handler the function to call on event\n * @returns {Object} SharedState\n * @memberof SharedState\n *\u002F\n \u002F*\n register callback\n\n The complexity of this method arise from the fact that we are to give\n an \"immediate callback\" to the given handler.\n\n In addition, I do not want to do so directly within the on() method.\n\n As a programmer I would like to ensure that initialisation of an object\n is completed BEFORE the object needs to process any callbacks from the\n external world. This can be problematic if the object depends on events\n from multiple other objects. For example, the internal initialisation code\n needs to register handlers on external objects a and b.\n\n a.on(\"event\", internal_handler_a);\n b.on(\"event\", internal_handler_b);\n\n However, if object a gives an callback immediately within on, this callback\n will be processed BEFORE we have completed initialisation, i.e., any code\n subsequent to a.on).\n\n It is quite possible to make this be correct still, but I find nested handler\n invocation complicated to think about, and I prefer to avoid the problem.\n Therefore I like instead to make life easier by delaying \"immediate callbacks\"\n using\n\n setTimeout(_do_callbacks(\"event\", e, handler), 0);\n\n This however introduces two new problems. First, if you do :\n\n o.on(\"event\", handler);\n o.off(\"event\", handler);\n\n you will get the \"immediate callback\" after off(), which is not what you\n expect. This is avoided by checking that the given handler is indeed still\n registered when executing _do_callbacks(). Alternatively one could cancel the\n timeout within off().\n\n Second, with the handler included in _callbacks[what] it is possible to receive\n event callbacks before the delayed \"immediate callback\" is actually invoked.\n This breaks the expectation the the \"immediate callback\" is the first callback.\n This problem is avoided by flagging the callback handler with \".immediate_pending\"\n and dropping notifications that arrive before the \"immediate_callback has executed\".\n Note however that the effect of this dropped notification is not lost. The effects\n are taken into account when we calculate the \"initial state\" to be reported by the\n \"immediate callback\". Crucially, we do this not in the on() method, but when the\n delayed \"immediate callback\" actually is processed.\n *\u002F\n\n var on = function (what, handler) {\n if (!handler || typeof handler !== \"function\") throw \"Illegal handler\";\n if (!_callbacks.hasOwnProperty(what)) throw \"Unsupported event \" + what;\n var index = _callbacks[what].indexOf(handler);\n if (index === -1) {\n \u002F\u002F register handler\n _callbacks[what].push(handler);\n \u002F\u002F flag handler\n handler._immediate_pending = true;\n \u002F\u002F do immediate callback\n setTimeout(function () {\n switch (what) {\n case 'readystatechange':\n _do_callbacks(\"readystatechange\", readystate.get(), handler);\n break;\n }\n }, 0);\n }\n return self;\n };\n\n \u002F**\n * deregisters a function on event\n * @method off\n * @param {string} what change || presence || readystatechange\n * @param {function} handler the function to call on event\n * @returns {Object} SharedState\n * @memberof SharedState\n *\u002F\n \u002F\u002F unregister callback\n var off = function (what, handler) {\n if (_callbacks[what] !== undefined) {\n var index = _callbacks[what].indexOf(handler);\n if (index \u003E -1) {\n _callbacks[what].splice(index, 1);\n }\n }\n return self;\n };\n\n \u002F* API functions --\u003E *\u002F\n\n\n \u002F* \u003C!-- public *\u002F\n self.__defineGetter__(\"readyState\", readystate.get);\n self.__defineGetter__(\"STATE\", function () {\n return STATE;\n });\n\n\n self.getUserMapping = getUserMapping;\n self.getGroupMapping = getGroupMapping;\n\n self.on = on;\n self.off = off;\n\n \u002F* public --\u003E *\u002F\n\n _init();\n\n return self;\n };\n\n function DeviceProfile(url){\n\n let deviceProfile = {\n init:function(){\n this.setCapability(\"deviceProfile\", \"supported\");\n\n\n },\n on:function(){\n if (url) url +=\"checkDevice?agent=\"+navigator.userAgent;\n else url = \"https:\u002F\u002F\"+window.location.host+\"\u002FcheckDevice?agent=\"+navigator.userAgent;\n fetch(url).then((e)=\u003E{\n e.json().then((data)=\u003E{\n this.setItem('deviceProfile', data.deviceType);\n\n });\n\n });\n },\n off:function(){\n\n }\n };\n return {\"deviceProfile\":deviceProfile};\n }\n function TextData(element){\n\n let text = {\n init:function(){\n this.setCapability(\"textShare\", \"supported\");\n\n },\n on:function(){\n element.addEventListener('change',(txt)=\u003E{\n this.setItem('textShare', element.value);\n });\n \u002F\u002Fthis.setCapability(\"deviceProfile\", \"supported\");\n },\n off:function(){\n element.removeEventListener('change',(txt)=\u003E{\n this.setItem('textShare', element.value);\n });\n }\n };\n return {\"textShare\":text};\n }\n function MasterSlaveProfile(me){\n\n let ms = {\n init:function(){\n this.setCapability(\"masterSlave\", \"supported\");\n \u002F\u002F this.setItem('masterSlave', me.master || true);\n },\n on:function(){\n \u002F\u002F element.addEventListener('change',(txt)=\u003E{\n \u002F\u002F this.setItem('textShare', element.value);\n \u002F\u002F })\n this.setItem('masterSlave', (me.master === undefined || me.master === true)?true:false);\n\n },\n off:function(){\n\n }\n };\n return {\"masterSlave\":ms};\n }\n function UserProfile(me){\n\n let up = {\n init:function(){\n this.setCapability(\"userProfile\", \"supported\");\n \u002F\u002F this.setItem('masterSlave', me.master || true);\n },\n on:function(){\n \u002F\u002F element.addEventListener('change',(txt)=\u003E{\n \u002F\u002F this.setItem('textShare', element.value);\n \u002F\u002F })\n this.setItem('userProfile', me.options.profile || '');\n\n },\n off:function(){\n\n }\n };\n return {\"userProfile\":up};\n }\n function UserData(url){\n\n let userData = {\n init:function(){\n this.setCapability(\"userData\", \"supported\");\n\n\n },\n on:function(){\n this.setItem('userData', {});\n },\n off:function(){\n\n }\n };\n return {\"userData\":userData};\n }\n\n function KeyPress(){\n\n let ms = {\n init:function(){\n this.setCapability(\"keyPress\", \"supported\");\n \u002F\u002Fthis.setItem('keyPress', \"\");\n },\n on:function(){\n \u002F\u002F element.addEventListener('change',(txt)=\u003E{\n \u002F\u002F this.setItem('textShare', element.value);\n \u002F\u002F })\n document.addEventListener('keypress',(txt)=\u003E{\n this.setItem('keyPress', txt.key);\n });\n\n },\n off:function(){\n document.removeEventListener('keypress',(txt)=\u003E{\n this.setItem('keyPress', txt.key);\n });\n }\n };\n return {\"keyPress\":ms};\n }\n\n \u002F*! (C) WebReflection - Mit Style License *\u002F\n var EventTarget=function(){function o(e,t,i){n.value=i,r(e,t,n),n.value=null;}function u(e,t,n){var r;s.call(e,t)?r=e[t]:o(e,t,r=[]),i.call(r,n)\u003C0&&r.push(n);}function a(e,t,n){var r,i,o;if(s.call(e,t)){n.target=e,r=e[t].slice(0);for(o=0;o\u003Cr.length;o++)i=r[o],typeof i==\"function\"?i.call(e,n):typeof i.handleEvent==\"function\"&&i.handleEvent(n);}}function f(e,t,n){var r,o;s.call(e,t)&&(r=e[t],o=i.call(r,n),-1\u003Co&&(r.splice(o,1),r.length||delete e[t]));}var e=\"@@\",t={},n={configurable:!0,value:null},r=Object.defineProperty||function(t,n,r){t[n]=r.value;},i=[].indexOf||function(t){var n=this.length;while(n--&&this[n]!==t);return n},s=t.hasOwnProperty;return o(t,\"addEventListener\",function(n,r){u(this,e+n,r);}),o(t,\"dispatchEvent\",function(n){a(this,e+n.type,n);}),o(t,\"removeEventListener\",function(n,r){f(this,e+n,r);}),t};\n\n \u002F**\n * @file A State Vector is an object that describes uni-dimensional\n * motion in real time.\n *\n * The motion is described by four real numbers [p, v, a, t] representing\n * the position (p), velocity (v), and acceleration (a) at time (t).\n *\n * The time (t) is expressed in seconds. The position unit is entirely up to\n * the application. The velocity is in position units per second, and the\n * acceleration in position units per second squared.\n *\u002F\n\n \u002F\u002F Ensure \"define\" is defined in node.js in the absence of require.js\n \u002F\u002F See: https:\u002F\u002Fgithub.com\u002Fjrburke\u002Famdefine\n\n \u002F* Default constructor for a state vector\n *\n * @class\n * @param {Object} vector The initial motion vector\n * @param {Number} vector.position The initial position (0.0 if null)\n * @param {Number} vector.velocity The initial velocity (0.0 if null)\n * @param {Number} vector.acceleration The initial acceleration (0.0 if null)\n * @param {Number} vector.timestamp The initial time in seconds (now if null)\n *\u002F\n var StateVector = function (vector) {\n vector = vector || {};\n\n \u002F**\n * The position of the motion along its axis.\n *\n * The position unit may be anything.\n *\u002F\n this.position = vector.position || 0.0;\n\n \u002F**\n * The velocity of the motion in position units per second.\n *\u002F\n this.velocity = vector.velocity || 0.0;\n\n \u002F**\n * The acceleration of the motion in position units per second squared.\n *\u002F\n this.acceleration = vector.acceleration || 0.0;\n\n \u002F**\n * The local time in milliseconds when the position, velocity and\n * acceleration are evaluated.\n *\u002F\n this.timestamp = vector.timestamp || (Date.now() \u002F 1000.0);\n\n\n };\n\n\n \u002F**\n * Computes the position along the uni-dimensional axis at the given time\n *\n * @function\n * @param {Number} timestamp The reference time in seconds\n *\u002F\n StateVector.prototype.computePosition = function (timestamp) {\n var elapsed = timestamp - this.timestamp;\n var result = this.position +\n this.velocity * elapsed +\n 0.5 * this.acceleration * elapsed * elapsed;\n\n return result;\n };\n\n\n \u002F**\n * Computes the velocity along the uni-dimensional axis at the given time\n *\n * @function\n * @param {Number} timestamp The reference time in seconds\n *\u002F\n StateVector.prototype.computeVelocity = function (timestamp) {\n var elapsed = timestamp - this.timestamp;\n var result = this.velocity +\n this.acceleration * elapsed;\n return result;\n };\n\n\n \u002F**\n * Computes the acceleration along the uni-dimensional axis at the given time\n *\n * Note that this function merely exists for symmetry with computePosition and\n * computeAcceleration. In practice, this function merely returns the vector's\n * acceleration which is unaffected by time.\n *\n * @function\n * @param {Number} timestamp The reference time in seconds\n *\u002F\n StateVector.prototype.computeAcceleration = function (timestamp) {\n return this.acceleration;\n };\n\n\n \u002F**\n * Compares this vector with the specified vector for order. Returns a\n * negative integer, zero, or a positive integer as this vector is less than,\n * equal to, or greater than the specified object.\n *\n * Note that the notions of \"less than\" or \"greater than\" do not necessarily\n * mean much when comparing motions. In practice, the specified vector is\n * evaluated at the timestamp of this vector. Position is compared first.\n * If equal, velocity is compared next. If equal, acceleration is compared.\n *\n * TODO: the function probably returns differences in cases where it should\n * not because of the limited precision of floating numbers. Fix that.\n *\n * @function\n * @param {StateVector} vector The vector to compare\n * @returns {Integer} The comparison result\n *\u002F\n StateVector.prototype.compareTo = function (vector) {\n var timestamp = this.timestamp;\n var value = 0.0;\n\n value = vector.computePosition(timestamp);\n if (this.position \u003C value) {\n return -1;\n }\n else if (this.position \u003E value) {\n return 1;\n }\n\n value = vector.computeVelocity(timestamp);\n if (this.velocity \u003C value) {\n return -1;\n }\n else if (this.velocity \u003E value) {\n return 1;\n }\n\n value = vector.computeAcceleration(timestamp);\n if (this.acceleration \u003C value) {\n return -1;\n }\n else if (this.acceleration \u003E value) {\n return 1;\n }\n\n return 0;\n };\n\n\n \u002F**\n * Overrides toString to return a meaningful string serialization of the\n * object for logging\n *\n * @function\n * @returns {String} A human-readable serialization of the vector\n *\u002F\n StateVector.prototype.toString = function () {\n return '(position=' + this.position +\n ', velocity=' + this.velocity +\n ', acceleration=' + this.acceleration +\n ', timestamp=' + this.timestamp + ')';\n };\n\n \u002F**\n * @file A few useful utility functions\n *\u002F\n\n var Utils = {\n toString : Object.prototype.toString,\n\n \u002F**\n * Returns true when parameter is null\n *\n * @function\n * @param {*} obj Object to check\n * @returns {Boolean} true if object is null, false otherwise\n *\u002F\n isNull : function (obj) {\n return obj === null;\n },\n\n\n \u002F**\n * Returns true when parameter is a number\n *\n * @function\n * @param {*} obj Object to check\n * @returns {Boolean} true if object is a number, false otherwise\n *\u002F\n isNumber : function (obj) {\n return toString.call(obj) === '[object Number]';\n },\n\n\n \u002F**\n * Serialize object as a JSON string\n *\n * @function\n * @param {Object} obj The object to serialize as JSON string\n * @return {String} The serialized JSON string\n *\u002F\n stringify : function (obj) {\n return JSON.stringify(obj, null, 2);\n },\n getHostName:function(url){\n var a = document.createElement('a');\n a.href = url;\n return a.hostname;\n }\n\n\n \u002F\u002F Expose helper functions to the outer world\n\n };\n\n \u002F**\n * @file Defines an interval\n *\u002F\n\n \u002F**\n * Creates an interval\n *\n * @class\n * @param {Object} range The range\n * @param {Number} range.low Lower bound of the interval\n * @param {Number} range.high Higher bound of the interval\n * @param {Boolean} range.lowInclude Whether to include the lower bound\n * @param {Boolean} range.highInclude Whether to include the higher bound\n *\u002F\n var Interval = function (range) {\n range = range || {};\n\n this.low = range.low;\n this.lowInclude = range.lowInclude;\n this.high = range.high;\n this.highInclude = range.highInclude;\n\n \u002F\u002F Ensure low \u003C= high\n if (Utils.isNumber(this.low) &&\n Utils.isNumber(this.high) &&\n (this.low \u003E this.high)) {\n range.low = this.high;\n range.lowInclude = this.highInclude;\n this.high = this.low;\n this.highInclude = this.lowInclude;\n this.low = range.low;\n this.lowInclude = range.lowInclude;\n }\n\n\n };\n\n\n \u002F**\n * Returns true if interval covers the given value.\n *\n * @function\n * @param {Number} value Value to check\n * @returns {Boolean} true if interval covers the value\n *\u002F\n Interval.prototype.covers = function (value) {\n return (!this.low ||\n (this.low \u003C value) ||\n ((this.low === value) && this.lowInclude)) &&\n (!this.high ||\n (this.high \u003E value) ||\n ((this.high === value) && this.highInclude));\n };\n\n\n \u002F**\n * Returns true if low is equal to high\n *\n * @function\n * @returns true if low is equal to high\n *\u002F\n Interval.prototype.isSingular = function () {\n return this.low === this.high;\n };\n\n \u002F**\n * @file A timing provider object is a local object that interfaces with an\n * online timing service.\n *\n * This is an abstract base class that returns a dummy timing provider object.\n * Concrete implementations should derive this class or implement the\n * same interface. The constructor may be different in derived classes.\n *\u002F\n\n\n \u002F**\n * Creates a timing provider\n *\n * @class\n * @param {StateVector} vector The initial motion vector\n * @param {Interval} range The initial range if one is to be defined\n *\u002F\n var AbstractTimingProvider = function (vector, range) {\n this.range = new Interval(range);\n\n var currentVector = new StateVector(vector);\n var readyState = 'connecting';\n var self = this;\n Object.defineProperties(this, {\n readyState: {\n get: function () {\n return readyState;\n },\n set: function (state) {\n if (state !== readyState) {\n readyState = state;\n\n setTimeout(function () {\n \u002F\u002F Dispatch the event on next loop to give code that wants to\n \u002F\u002F listen to the initial change to \"open\" time to attach an event\n \u002F\u002F listener (local timing provider objects typically set the\n \u002F\u002F readyState property to \"open\" directly within the constructor)\n self.dispatchEvent({\n type: 'readystatechange',\n value: state\n });\n }, 0);\n }\n }\n },\n vector: {\n get: function () {\n return currentVector;\n },\n set: function (vector) {\n var previousVector = currentVector;\n currentVector = vector;\n if (previousVector.compareTo(currentVector) === 0) ;\n else {\n\n self.dispatchEvent({\n type: 'change',\n value: currentVector\n });\n }\n }\n }\n });\n\n };\n\n\n \u002F\u002F Timing providers implement EventTarget\n AbstractTimingProvider.prototype.addEventListener = EventTarget().addEventListener;\n AbstractTimingProvider.prototype.removeEventListener = EventTarget().removeEventListener;\n AbstractTimingProvider.prototype.dispatchEvent = EventTarget().dispatchEvent;\n\n\n \u002F**\n * Returns a new StateVector that represents the motion's position,\n * velocity and acceleration at the current local time.\n *\n * @function\n * @returns {StateVector} A new StateVector object that represents\n * the motion's position, velocity and acceleration at the current local\n * time.\n *\u002F\n AbstractTimingProvider.prototype.query = function () {\n var timestamp = Date.now() \u002F 1000.0;\n var currentVector = new StateVector({\n position: this.vector.computePosition(timestamp),\n velocity: this.vector.computeVelocity(timestamp),\n acceleration: this.vector.computeAcceleration(timestamp),\n timestamp: timestamp\n });\n\n return currentVector;\n };\n\n\n \u002F**\n * Sends an update command to the online timing service.\n *\n * @function\n * @param {Object} vector The new motion vector\n * @param {Number} vector.position The new motion position.\n * If null, the position at the current time is used.\n * @param {Number} vector.velocity The new velocity.\n * If null, the velocity at the current time is used.\n * @param {Number} vector.acceleration The new acceleration.\n * If null, the acceleration at the current time is used.\n * @returns {Promise} The promise to get an updated StateVector that\n * represents the updated motion on the server once the update command\n * has been processed by the server.\n * The promise is rejected if the connection with the online timing service\n * is not possible for some reason (no connection, timing object on the\n * server was deleted, timeout, permission issue).\n *\u002F\n AbstractTimingProvider.prototype.update = function (vector) {\n vector = new StateVector(vector || {});\n\n return new Promise(function (resolve, reject) {\n var err = new Error('Abstract \"update\" method called');\n\n reject(err);\n });\n };\n\n\n \u002F**\n * Closes the timing provider object, releasing any resource that the\n * object might use.\n *\n * Note that a closed timing provider object cannot be re-used.\n *\n * @function\n *\u002F\n AbstractTimingProvider.prototype.close = function () {\n if ((this.readyState === 'closing') ||\n (this.readyState === 'closed')) {\n return;\n }\n this.readyState = 'closing';\n this.readyState = 'closed';\n };\n\n \u002F**\n * @file A timing provider object associated with the local clock.\n *\n * Timing objects that are not associated with any other timing provider object\n * are automatically associated with an instance of that class.\n *\u002F\n\n\n \u002F**\n * Creates a timing provider\n *\n * @class\n *\u002F\n var LocalTimingProvider = function (vector, range) {\n AbstractTimingProvider.call(this, vector, range);\n this.readyState = 'open';\n\n };\n LocalTimingProvider.prototype = new AbstractTimingProvider();\n\n\n \u002F**\n * Sends an update command to the online timing service.\n *\n * @function\n * @param {Object} vector The new motion vector\n * @param {Number} vector.position The new motion position.\n * If null, the position at the current time is used.\n * @param {Number} vector.velocity The new velocity.\n * If null, the velocity at the current time is used.\n * @param {Number} vector.acceleration The new acceleration.\n * If null, the acceleration at the current time is used.\n * @returns {Promise} The promise to get an updated StateVector that\n * represents the updated motion on the server once the update command\n * has been processed by the server.\n * The promise is rejected if the connection with the online timing service\n * is not possible for some reason (no connection, timing object on the\n * server was deleted, timeout, permission issue).\n *\u002F\n LocalTimingProvider.prototype.update = function (vector) {\n vector = vector || {};\n\n var timestamp = Date.now() \u002F 1000.0;\n var newVector = {\n position: (Utils.isNull(vector.position) ?\n this.vector.computePosition(timestamp) :\n vector.position),\n velocity: (Utils.isNull(vector.velocity) ?\n this.vector.computeVelocity(timestamp) :\n vector.velocity),\n acceleration: (Utils.isNull(vector.acceleration) ?\n this.vector.computeAcceleration(timestamp) :\n vector.acceleration),\n timestamp: timestamp\n };\n this.vector = new StateVector(newVector);\n\n\n return new Promise(function (resolve, reject) {\n\n resolve(newVector);\n });\n };\n\n \u002F**\n * @file A timing object exposes methods to control a motion along some\n * uni-dimensional axis.\n *\n * A timing object is either in master mode whereby the motion is controlled\n * directly and associated with the local clock, or in slave mode whereby the\n * motion is synchronized with an online timing service.\n *\n * Question: What is the preferred way to create a timing object instance on\n * the server in a model where we plug a third-party library? Should a timing\n * object have a URN for instance?\n *\u002F\n\n\n \u002F**\n * Constructor of the timing object\n *\n * @class\n * @param {StateVector} vector The initial motion vector\n * @param {Interval} range The initial range if one is to be defined\n *\u002F\n var TimingObject = function (vector, range) {\n var self = this;\n\n \u002F**\n * Helper methods to start\u002Fstop dispatching time update events\n * (only triggered when the object is moving)\n *\n * Note that setInterval is bound to the event loop and thus\n * the precision of the timing update depends on the overall\n * event loop \"congestion\".\n *\n * TODO: note that the object cannot be garbage collected as long as\n * \"timeupdate\" events are being dispatched. Not sure that there is\n * much we can do here.\n *\n * TODO: the code triggers the first event after 200ms. Should it rather\n * trigger the first event right away?\n *\u002F\n var timeupdateInterval = null;\n var startDispatchingTimeUpdateEvents = function () {\n var frequency = 10;\n if (timeupdateInterval) { return; }\n\n timeupdateInterval = setInterval(function () {\n\n self.dispatchEvent({\n type: 'timeupdate'\n });\n }, Math.round(1000 \u002F frequency));\n };\n var stopDispatchingTimeUpdateEvents = function () {\n if (!timeupdateInterval) { return; }\n clearInterval(timeupdateInterval);\n timeupdateInterval = null;\n };\n\n\n \u002F**\n * All \"change\" events received from the timing provider will be\n * propagated on this object\n *\u002F\n var changeListener = function (evt) {\n var vector = evt.value || {\n velocity: 0.0,\n acceleration: 0.0\n };\n if ((vector.velocity !== 0.0) || (vector.acceleration !== 0.0)) {\n startDispatchingTimeUpdateEvents();\n }\n else {\n stopDispatchingTimeUpdateEvents();\n }\n self.dispatchEvent(evt);\n };\n var readystatechangeListener = function (evt) {\n if (evt.value === 'closed') {\n stopDispatchingTimeUpdateEvents();\n }\n self.dispatchEvent(evt);\n };\n\n\n \u002F**\n * Determine whether the timing object is managed locally or through\n * a third-party timing provider\n *\u002F\n var master = true;\n\n\n \u002F**\n * Timing provider object associated with this timing object,\n * along with a couple of internal helper functions to associate\u002Fdissociate\n * this timing object with a timing provider object.\n *\u002F\n var timingProvider = null;\n\n var associateWithTimingProvider = function (provider) {\n var previousProvider = timingProvider;\n dissociateFromTimingProvider(previousProvider);\n\n timingProvider = provider;\n provider.addEventListener('change', changeListener);\n provider.addEventListener('readystatechange', readystatechangeListener);\n if (previousProvider && (provider.readyState === 'open')) {\n if (previousProvider.vector.compareTo(provider.vector) !== 0) {\n changeListener({\n type: 'change',\n value: provider.query()\n });\n }\n }\n };\n\n var dissociateFromTimingProvider = function (provider) {\n if (provider) {\n provider.removeEventListener('change', changeListener);\n provider.removeEventListener('readystatechange', readystatechangeListener);\n }\n };\n\n\n \u002F**\n * Returns a new StateVector that represents the motion's position,\n * velocity and acceleration at the current local time.\n *\n * @function\n * @returns {StateVector} A new StateVector object that represents\n * the motion's position, velocity and acceleration at the current local\n * time.\n *\u002F\n this.query = function () {\n var vector = timingProvider.query();\n return vector;\n };\n\n\n \u002F**\n * Updates the internal motion.\n *\n * If the timing object is attached to the local clock, the update operation\n * happens synchronously. If not, the update operation is asynchronous as\n * the method then relays the command to the online timing service\n * associated with this timing object.\n *\n * The \"change\" event is triggered when the update operation has completed.\n *\n * @function\n * @param {Number} position The new motion position. If null, the position\n * at the current time is used.\n * @param {Number} velocity The new velocity. If null, the velocity at the\n * current time is used.\n * @param {Number} acceleration The new acceleration. If null, the\n * acceleration at the current time is used.\n *\u002F\n this.update = function (position, velocity, acceleration) {\n return timingProvider.update({\n position: position,\n velocity: velocity,\n acceleration: acceleration\n });\n };\n\n\n \u002F**\n * Returns true when the object is moving, in other words when velocity\n * or acceleration is different from 0.0, else False\n *\n * @function\n *\u002F\n this.isMoving = function () {\n\n var vector = timingProvider.query();\n var result = (vector.velocity !== 0.0) || (vector.acceleration !== 0.0);\n\n return result;\n };\n\n\n \u002F**\n * Define the \"srcObject\" and \"readyState\" properties\n *\u002F\n Object.defineProperties(this, {\n \u002F**\n * The readyState attribute returns the ready state of the underlying\n * timing provider object\n *\u002F\n readyState: {\n get: function () {\n return timingProvider.readyState;\n }\n },\n\n \u002F**\n * On getting, \"srcObject\" returns a pointer to the current timing\n * provider instance associated with the timing object, or null if\n * the timing object is managed locally.\n *\n * On setting, \"srcObject\" associates the timing object with the given\n * timing provider instance. If null, the timing object goes back to\n * being locally managed.\n *\u002F\n srcObject: {\n get: function () {\n \u002F\u002F Do not return anything if the timing object is managed locally\n if (master) {\n return null;\n }\n else {\n return timingProvider;\n }\n },\n set: function (provider) {\n var previousProvider = timingProvider;\n if (provider) {\n \u002F\u002F The caller wants to associate the timing object with a\n \u002F\u002F third-party timing provider.\n \u002F\u002F (this may trigger a \"change\" event if vector exposed by\n \u002F\u002F the timing provider is different from the one we used\n \u002F\u002F to have)\n master = false;\n associateWithTimingProvider(provider);\n\n }\n else {\n \u002F\u002F The caller wants to remove the association with a third-party\n \u002F\u002F timing provider. The object gets back to being locally managed\n if (master) ;\n else {\n \u002F\u002F Associate with a new local timing provider\n \u002F\u002F (this should not trigger a \"change\" event since\n \u002F\u002F vector does not change internally)\n master = true;\n provider = new LocalTimingProvider(\n previousProvider.query(),\n previousProvider.range\n );\n associateWithTimingProvider(provider);\n\n }\n }\n }\n },\n\n\n \u002F**\n * Returns the position evaluated at the time when the attribute is read\n *\u002F\n currentPosition: {\n get: function () {\n return timingProvider.query().position;\n }\n },\n\n \u002F**\n * Returns the velocity evaluated at the time when the attribute is read\n *\u002F\n currentVelocity: {\n get: function () {\n return timingProvider.query().velocity;\n }\n },\n\n \u002F**\n * Returns the acceleration evaluated at the time when the attribute is\n * read.\n *\u002F\n currentAcceleration: {\n get: function () {\n return timingProvider.query().acceleration;\n }\n }\n });\n\n \u002F\u002F TODO: implement \"range\"\n \u002F\u002F TODO: implement \"vector\", \"previousVector\" properties (is that needed?)\n \u002F\u002F TODO: implement on... event properties\n\n \u002F\u002F Newly created timing object instances are associated with a local timing\n \u002F\u002F provider to start with. Set the \"srcObject\" property to change that\n \u002F\u002F behavior afterwards.\n associateWithTimingProvider(new LocalTimingProvider(vector, range));\n\n };\n\n\n \u002F\u002F TimingObject implements EventTarget\n TimingObject.prototype.addEventListener = EventTarget().addEventListener;\n TimingObject.prototype.removeEventListener = EventTarget().removeEventListener;\n TimingObject.prototype.dispatchEvent = EventTarget().dispatchEvent;\n\n \u002F**\n * @file A synchronized clock converts a local timestamp into the corresponding\n * value of a reference clock it is synchronized with.\n *\n * This is an abstract base class that returns a dummy clock synchronized with\n * itself (but note readyState remains at \"connecting\", hence the class should\n * not be used directly)\n *\u002F\n\n \u002F**\n * Default constructor for a synchronized clock\n *\n * @class\n * @param {Number} initialSkew The initial clock skew\n * @param {Number} initialDelta The initial static delta\n *\u002F\n var AbstractSyncClock = function (initialSkew, initialDelta) {\n var self = this;\n\n \u002F**\n * The current estimation of the skew with the reference clock, in ms\n *\u002F\n var skew = initialSkew || 0.0;\n\n \u002F**\n * Some agreed fixed delta delay, in ms.\n *\u002F\n var delta = initialDelta || 0.0;\n\n \u002F**\n * The ready state of the synchronized clock\n *\u002F\n var readyState = 'connecting';\n\n \u002F**\n * Define the \"readyState\", \"skew\" and \"delta\" properties. Note that\n * setting these properties may trigger \"readystatechange\" and \"change\"\n * events.\n *\u002F\n Object.defineProperties(this, {\n readyState: {\n get: function () {\n return readyState;\n },\n set: function (state) {\n if (state !== readyState) {\n readyState = state;\n\n \u002F\u002F Dispatch the event on next loop to give code that wants to\n \u002F\u002F listen to the initial change to \"open\" time to attach an event\n \u002F\u002F listener (locally synchronized clocks typically set the\n \u002F\u002F readyState property to \"open\" directly within the constructor)\n setTimeout(function () {\n self.dispatchEvent({\n type: 'readystatechange',\n value: state\n });\n }, 0);\n }\n }\n },\n delta: {\n get: function () {\n return delta;\n },\n set: function (value) {\n var previousDelta = delta;\n delta = value;\n if (previousDelta === delta) ;\n else {\n\n self.dispatchEvent({\n type: 'change'\n });\n }\n }\n },\n skew: {\n get: function () {\n return skew;\n },\n set: function (value) {\n var previousSkew = skew;\n skew = value;\n if (readyState !== 'open') ;\n else if (previousSkew === skew) ;\n else {\n\n self.dispatchEvent({\n type: 'change'\n });\n }\n }\n }\n });\n };\n\n\n \u002F\u002F Synchronized clocks implement EventTarget\n AbstractSyncClock.prototype.addEventListener = EventTarget().addEventListener;\n AbstractSyncClock.prototype.removeEventListener = EventTarget().removeEventListener;\n AbstractSyncClock.prototype.dispatchEvent = EventTarget().dispatchEvent;\n\n\n \u002F**\n * Returns the time at the reference clock that corresponds to the local\n * time provided (both in milliseconds since 1 January 1970 00:00:00 UTC)\n *\n * @function\n * @param {Number} localTime The local time in milliseconds\n * @returns {Number} The corresponding time on the reference clock\n *\u002F\n AbstractSyncClock.prototype.getTime = function (localTime) {\n return localTime + this.skew - this.delta;\n };\n\n\n \u002F**\n * Returns the number of milliseconds elapsed since\n * 1 January 1970 00:00:00 UTC on the reference clock\n *\n * @function\n * @returns {Number} The current timestamp\n *\u002F\n AbstractSyncClock.prototype.now = function () {\n return this.getTime(Date.now());\n };\n\n \u002F**\n * Stops synchronization with the reference clock.\n *\n * In derived classes, this should typically be used to stop background\n * synchronization mechanisms.\n *\n * @function\n *\u002F\n AbstractSyncClock.prototype.close = function () {\n if ((this.readyState === 'closing') ||\n (this.readyState === 'closed')) {\n return;\n }\n this.readyState = 'closing';\n this.readyState = 'closed';\n };\n\n \u002F**\n * @file A clock synchronized with an online server clock over some WebSocket\n * communication channel.\n *\n * This clock has an initialization period during it sends a batch of \"sync\"\n * requets to the server to compute the minimum roundtrip duration and a\n * realistic threshold for that roundtrip duration.\n *\n * The threshold is used afterward to reject sync messages that spend too much\n * time in the network (or in the client or server waiting to be processed),\n * as these messages would otherwise lead to a poor skew estimate.\n *\n * Note that this clock is not necessarily monotonic.\n *\n * This implementation borrows idea from the Media State Vector\n * paper and\u002For the \"Probabilistic clock synchronization\" paper at:\n * http:\u002F\u002Fmotioncorporation.com\u002Fpublications\u002Fmediastatevector2012.pdf\n * http:\u002F\u002Fwww.cs.utexas.edu\u002Fusers\u002Florenzo\u002Fcorsi\u002Fcs380d\u002Fpapers\u002FCristian.pdf\n *\n * TODO: also consider building a monotonically increasing clock\n * (meaning one that cannot \"jump\" backward and jumps forward gradually)\n *\u002F\n var OPEN = 1;\n var CLOSED = 3;\n\n \u002F\u002F Number of exchanges to make with the server to compute the first skew\n var initialAttempts = 10;\n\n \u002F\u002F Interval between two exchanges during initialization (in ms)\n var initialInterval = 10;\n\n \u002F\u002F Maximum number of attempts before giving up\n var maxAttempts = 10;\n\n \u002F\u002F Interval between two attempts when the clock is open (in ms)\n var attemptInterval = 500;\n\n \u002F\u002F Interval between two synchronization batches (in ms)\n var batchInterval = 10000;\n\n \u002F\u002F Minimum roundtrip threshold (in ms)\n var minRoundtripThreshold = 5;\n\n\n \u002F**\n * Creates a Socket synchronization clock\n *\n * @class\n * @param {String} url The URL of the remote timing object for which we\n * want to synchronize the clock (only used to check permissions)\n * @param {WebSocket} socket A Web socket to use as communication channel.\n *\u002F\n var SocketSyncClock = function (url, socket) {\n \u002F\u002F Initialize the base class with default data\n AbstractSyncClock.call(this);\n\n var self = this;\n\n\n \u002F**\n * The Web Socket that will be used to exchange sync information with\n * the online server\n *\u002F\n this.socket = socket;\n\n\n \u002F**\n * Minimum round trip detected so far (in ms)\n *\u002F\n var roundtripMin = 1000;\n\n\n \u002F**\n * Current round trip threshold above which the \"sync\"\n * request is considered to be a failure (in ms)\n *\n * NB: this threshold must always be higher than the minimum round trip\n *\u002F\n var roundtripThreshold = 1000;\n\n\n \u002F**\n * Number of \"sync\" attempts in the current batch so far.\n * The clock will attempt up to maxAttempts attempts in a row\n * each time it wants to synchronize\n *\u002F\n var attempts = 0;\n\n\n \u002F**\n * Valid responses received from the server for the current batch\n *\u002F\n var initialSyncMessages = [];\n\n\n \u002F**\n * ID of the attempt response we are currently waiting for\n *\u002F\n var attemptId = null;\n\n\n \u002F**\n * The attempt timeout\n *\u002F\n var attemptTimeout = null;\n\n\n \u002F**\n * Timeout to detect when the server fails to respond in time\n *\u002F\n var timeoutTimeout = null;\n\n\n if (socket.readyState === OPEN) {\n\n sendSyncRequest();\n }\n else if (socket.readyState === CLOSED) {\n\n this.readyState = 'closed';\n }\n\n var errorHandler = function (err) {\n\n \u002F\u002F TODO: properly deal with network errors\n return true;\n };\n\n var openHandler = function () {\n\n sendSyncRequest();\n return true;\n };\n\n var closeHandler = function () {\n\n self.close();\n return true;\n };\n\n var messageHandler = function (evt) {\n var msg = null;\n var received = Date.now();\n var skew = 0;\n\n if (typeof evt.data !== 'string') {\n\n return true;\n }\n\n try {\n msg = JSON.parse(evt.data) || {};\n }\n catch (err) {\n\n return true;\n }\n\n if (msg.type !== 'sync') {\n\n return true;\n }\n\n if (!msg.client || !msg.server ||\n !Utils.isNumber(msg.client.sent) ||\n !Utils.isNumber(msg.server.received) ||\n !Utils.isNumber(msg.server.sent)) {\n\n return true;\n }\n\n if (msg.id !== attemptId) {\n\n return true;\n }\n\n \u002F\u002F Message is for us\n attempts += 1;\n\n \u002F\u002F Compute round trip duration\n var roundtripDuration = received - msg.client.sent;\n\n \u002F\u002F Check round trip duration\n if ((self.readyState !== 'connecting') &&\n (roundtripDuration \u003E roundtripThreshold)) {\n\n return false;\n }\n\n if (timeoutTimeout) {\n \u002F\u002F Cancel the timeout set to detect server timeouts.\n clearTimeout(timeoutTimeout);\n timeoutTimeout = null;\n }\n else {\n \u002F\u002F A timeout already occurred\n \u002F\u002F (should have normally be trapped by the check on round trip\n \u002F\u002F duration, but timeout scheduling and the event loop are not\n \u002F\u002F an exact science)\n\n return false;\n }\n\n \u002F\u002F During initialization, simply store the response,\n \u002F\u002F we'll process things afterwards\n if (self.readyState === 'connecting') {\n\n initialSyncMessages.push({\n received: received,\n roundtrip: roundtripDuration,\n msg: msg\n });\n if (attempts \u003E= initialAttempts) {\n initialize();\n scheduleNextBatch();\n }\n else {\n scheduleNextAttempt();\n }\n return false;\n }\n\n \u002F\u002F Adjust the minimum round trip and threshold if needed\n if (roundtripDuration \u003C roundtripMin) {\n roundtripThreshold = Math.ceil(\n roundtripThreshold * (roundtripDuration \u002F roundtripMin));\n if (roundtripThreshold \u003C minRoundtripThreshold) {\n roundtripThreshold = minRoundtripThreshold;\n }\n roundtripMin = roundtripDuration;\n }\n\n\n \u002F\u002F Sync message can be directly applied\n skew = ((msg.server.sent + msg.server.received) -\n (msg.client.sent + received)) \u002F 2.0;\n if (Math.abs(skew - self.skew) \u003C 1) {\n skew = self.skew;\n }\n else {\n skew = Math.round(skew);\n }\n\n \u002F\u002F Save the new skew\n \u002F\u002F (this triggers a \"change\" event if value changed)\n self.skew = skew;\n\n \u002F\u002F No need to schedule another attempt,\n \u002F\u002F let's simply schedule the next sync batch of attempts\n scheduleNextBatch();\n\n return false;\n };\n\n \u002F\u002F NB: calling \"addEventListener\" does not work in a Node.js environment\n \u002F\u002F because the WebSockets library used only supports basic \"onXXX\"\n \u002F\u002F constructs. The code below works around that limitation but note that\n \u002F\u002F only works provided the clock is associated with the socket *after* the\n \u002F\u002F timing provider object!\n var previousErrorHandler = this.socket.onerror;\n var previousOpenHandler = this.socket.onopen;\n var previousCloseHandler = this.socket.onclose;\n var previousMessageHandler = this.socket.onmessage;\n if (this.socket.addEventListener) {\n this.socket.addEventListener('error', errorHandler);\n this.socket.addEventListener('open', openHandler);\n this.socket.addEventListener('close', closeHandler);\n this.socket.addEventListener('message', messageHandler);\n }\n else {\n this.socket.onerror = function (evt) {\n if ( previousErrorHandler) {\n previousErrorHandler(evt);\n }\n };\n this.socket.onopen = function (evt) {\n var propagate = openHandler();\n if ( previousOpenHandler) {\n previousOpenHandler(evt);\n }\n };\n this.socket.onclose = function (evt) {\n var propagate = closeHandler();\n if ( previousCloseHandler) {\n previousCloseHandler(evt);\n }\n };\n this.socket.onmessage = function (evt) {\n var propagate = messageHandler(evt);\n if (propagate && previousMessageHandler) {\n previousMessageHandler(evt);\n }\n };\n }\n\n\n \u002F**\n * Helper function to send a \"sync\" request to the socket server\n *\u002F\n var sendSyncRequest = function () {\n\n attemptId = url + '#' + Date.now();\n self.socket.send(Utils.stringify({\n type: 'sync',\n id: attemptId,\n client: {\n sent: Date.now()\n }\n }));\n attemptTimeout = null;\n\n timeoutTimeout = setTimeout(function () {\n attempts += 1;\n timeoutTimeout = null;\n\n if (attempts \u003E= maxAttempts) {\n if (self.readyState === 'connecting') {\n initialize();\n }\n else {\n roundtripThreshold = Math.ceil(roundtripThreshold * 1.20);\n\n }\n scheduleNextBatch();\n }\n else {\n scheduleNextAttempt();\n }\n }, roundtripThreshold);\n };\n\n\n \u002F**\n * Helper function to schedule the next sync attempt\n *\n * @function\n *\u002F\n var scheduleNextAttempt = function () {\n var interval = (self.readyState === 'connecting') ?\n initialInterval :\n attemptInterval;\n if (timeoutTimeout) {\n clearTimeout(timeoutTimeout);\n timeoutTimeout = null;\n }\n if (attemptTimeout) {\n clearTimeout(attemptTimeout);\n attemptTimeout = null;\n }\n attemptTimeout = setTimeout(sendSyncRequest, interval);\n };\n\n\n \u002F**\n * Helper function to schedule the next batch of sync attempts\n *\n * @function\n *\u002F\n var scheduleNextBatch = function () {\n if (timeoutTimeout) {\n clearTimeout(timeoutTimeout);\n timeoutTimeout = null;\n }\n if (attemptTimeout) {\n clearTimeout(attemptTimeout);\n attemptTimeout = null;\n }\n attempts = 0;\n attemptTimeout = setTimeout(sendSyncRequest, batchInterval);\n };\n\n\n \u002F**\n * Helper function that computes the initial skew based on the\n * sync messages received so far and adjust the roundtrip threshold\n * accordingly.\n *\n * The function also sets the clock's ready state to \"open\".\n *\n * @function\n *\u002F\n var initialize = function () {\n var msg = null;\n var skew = null;\n var received = 0;\n var pos = 0;\n\n\n\n \u002F\u002F Sort messages received according to round trip\n initialSyncMessages.sort(function (a, b) {\n return a.roundtrip - b.roundtrip;\n });\n\n \u002F\u002F Use the first message to compute the initial skew\n if (initialSyncMessages.length \u003E 0) {\n msg = initialSyncMessages[0].msg;\n received = initialSyncMessages[0].received;\n roundtripMin = initialSyncMessages[0].roundtrip;\n\n if (Utils.isNumber(msg.delta)) {\n self.delta = msg.delta;\n }\n\n skew = ((msg.server.sent + msg.server.received) -\n (msg.client.sent + received)) \u002F 2.0;\n if (Math.abs(skew - self.skew) \u003C 1) {\n skew = self.skew;\n }\n else {\n skew = Math.round(skew);\n }\n self.skew = skew;\n }\n\n \u002F\u002F Adjust the threshold to preserve at least half of the sync messages\n \u002F\u002F that should have been received.\n pos = Math.ceil(initialAttempts \u002F 2) - 1;\n if (pos \u003E= initialSyncMessages.length) {\n pos = initialSyncMessages.length - 1;\n }\n if (pos \u003E= 0) {\n roundtripThreshold = initialSyncMessages[pos].roundtrip;\n }\n\n \u002F\u002F Ensure the threshold is not too low compared to the\n \u002F\u002F known minimum roundtrip duration\n if (roundtripThreshold \u003C roundtripMin * 1.30) {\n roundtripThreshold = Math.ceil(roundtripMin * 1.30);\n }\n if (roundtripThreshold \u003C minRoundtripThreshold) {\n roundtripThreshold = minRoundtripThreshold;\n }\n\n \u002F\u002F Clock is ready\n\n self.readyState = 'open';\n initialSyncMessages = [];\n };\n\n\n \u002F**\n * Method that stops the background synchronization\n *\u002F\n this.stopSync = function () {\n if (attemptTimeout) {\n clearTimeout(attemptTimeout);\n attemptTimeout = null;\n }\n if (timeoutTimeout) {\n clearTimeout(timeoutTimeout);\n timeoutTimeout = null;\n }\n };\n\n\n };\n SocketSyncClock.prototype = new AbstractSyncClock();\n\n\n \u002F**\n * Stops synchronizing the clock with the reference clock\n *\n * Note that a closed synchronized clock object cannot be re-used.\n *\n * @function\n *\u002F\n SocketSyncClock.prototype.close = function () {\n if ((this.readyState === 'closing') ||\n (this.readyState === 'closed')) {\n return;\n }\n this.readyState = 'closing';\n this.stopSync();\n this.socket = null;\n this.readyState = 'closed';\n };\n\n \u002F**\n * @file A timing provider object associated with an online timing server\n * using WebSockets.\n *\n * The socket timing provider object can send 3 different types of commands to\n * the WebSockets server:\n * - info: to retrieve the current media state vector (only done to initialize\n * the object to the right settings)\n * - update: to update the media state vector\n * - sync: to synchronize local clock with remote clock\n *\n * The socket timing provider object can receive 3 different types of responses:\n * - info: Information about the timing object on the server\n * - change: an update event, meaning the underlying vector was changed\n * - sync: response to the sync command\n *\n * The socket timing provider object does not handle the creation and deletion\n * of the online timing object it is associated with on the server. This should\n * be done in separate factory methods.\n *\n * The socket timing provider object computes an approximation of the skew\n * between the local clock and the server clock on a regular basis (several\n * times per minute). It adjusts the timestamp of change events received from\n * the server automatically based on that computation.\n *\n * The socket timing provider object tries to trigger change events only when\n * appropriate meaning it will queue events that it believes need to be\n * triggered in the future.\n *\u002F\n\n\n var W3CWebSocket = null;\n\n W3CWebSocket = window.WebSocket;\n var OPEN$1 = 1;\n var CLOSED$1 = 3;\n\n\n \u002F**\n * Creates a timing provider\n *\n * @class\n * @param {String} url The Web socket URL of the remote timing object\n * @param {WebSocket} socket An opened Web socket to use as communication\n * channel. The parameter is optional, the object will create the\n * communication channel if not given.\n * @param {AbstractSyncClock} clock A clock to use for synchronization with\n * the online server clock. If not given, a clock that uses the underlying\n * WebSocket will be created and used.\n *\u002F\n var SocketTimingProvider = function (url, socket, clock) {\n var self = this;\n\n \u002F**\n * The URL of the online object, it is used as\n * identifier in exchanges with the backend server\n *\u002F\n this.url = url;\n\n \u002F**\n * The current vector as returned by the server.\n *\n * Updating the property through the setter automatically updates\n * the exposed vector as well, converting the server timestamp into\n * a local timestamp based on the underlying synchronized clock's readings\n *\u002F\n var serverVector = null;\n Object.defineProperty(this, 'serverVector', {\n get: function () {\n return serverVector;\n },\n set: function (vector) {\n var now = Date.now();\n serverVector = vector;\n self.vector = new StateVector({\n position: vector.position,\n velocity: vector.velocity,\n acceleration: vector.acceleration,\n timestamp: vector.timestamp + (now - self.clock.getTime(now)) \u002F 1000.0\n });\n }\n });\n\n \u002F**\n * List of \"change\" events already received from the server but\n * whose estimated timestamps lie in the future\n *\u002F\n var pendingChanges = [];\n\n \u002F**\n * The ID of the timeout used to trigger the first of the remaining\n * pending change events to process\n *\u002F\n var pendingTimeoutId = null;\n\n \u002F**\n * Helper function that schedules the propagation of the next pending\n * change. Note that the function calls itself as long as there are\n * pending changes to schedule.\n *\n * The function should be called whenever the synchronized clock reports\n * changes on its skew evaluation, since that affects the time at which\n * pending changes need to be executed.\n *\u002F\n var scheduleNextPendingChange = function () {\n stopSchedulingPendingChanges();\n if (pendingChanges.length === 0) {\n return;\n }\n\n var now = Date.now();\n var vector = pendingChanges[0];\n var localTimestamp = (vector.timestamp * 1000.0) +\n now - self.clock.getTime(now);\n\n var applyNextPendingChange = function () {\n \u002F\u002F Since we cannot control when this function runs precisely,\n \u002F\u002F note we may have to skip over the first few changes. We'll\n \u002F\u002F only trigger the change that is closest to now\n\n var now = Date.now();\n var vector = pendingChanges.shift();\n var nextVector = null;\n var localTimestamp = 0.0;\n while (pendingChanges.length \u003E 0) {\n nextVector = pendingChanges[0];\n localTimestamp = nextVector.timestamp * 1000.0 +\n now - self.clock.getTime(now);\n if (localTimestamp \u003E now) {\n break;\n }\n vector = pendingChanges.shift();\n }\n\n self.serverVector = vector;\n scheduleNextPendingChange();\n };\n\n if (localTimestamp \u003E now) {\n pendingTimeoutId = setTimeout(\n applyNextPendingChange,\n localTimestamp - now);\n }\n else {\n applyNextPendingChange();\n }\n };\n\n\n \u002F**\n * Helper function that stops the pending changes scheduler\n *\n * @function\n *\u002F\n var stopSchedulingPendingChanges = function () {\n\n if (pendingTimeoutId) {\n clearTimeout(pendingTimeoutId);\n pendingTimeoutId = null;\n }\n };\n\n\n \u002F**\n * Helper function that processes the \"info\" message from the\n * socket server when the clock is ready.\n *\n * @function\n *\u002F\n var processInfoWhenPossible = function (msg) {\n \u002F\u002F This should really just happen during initialization\n if (self.readyState !== 'connecting') {\n\n return;\n }\n\n \u002F\u002F If clock is not yet ready, schedule processing for when it is\n \u002F\u002F (note that this function should only really be called once but\n \u002F\u002F not a big deal if we receive more than one info message from the\n \u002F\u002F server)\n if (self.clock.readyState !== 'open') {\n self.clock.addEventListener('readystatechange', function () {\n if (self.clock.readyState === 'open') {\n processInfoWhenPossible(msg);\n }\n });\n return;\n }\n\n if (self.clock.delta) {\n \u002F\u002F The info will be applied right away, but if the server imposes\n \u002F\u002F some delta to all clients (to improve synchronization), it\n \u002F\u002F should be applied to the timestamp received.\n msg.vector.timestamp -= (self.clock.delta \u002F 1000.0);\n }\n self.serverVector = new StateVector(msg.vector);\n\n \u002F\u002F TODO: set the range as well when feature is implemented\n\n \u002F\u002F The timing provider object should now be fully operational\n self.readyState = 'open';\n };\n\n\n \u002F\u002F Initialize the base class with default data\n AbstractTimingProvider.call(this);\n\n \u002F\u002F Connect to the Web socket\n if (socket) {\n this.socket = socket;\n this.socketProvided = true;\n }\n else {\n this.socket = new W3CWebSocket(url, 'echo-protocol');\n this.socketProvided = false;\n }\n\n this.socket.onerror = function (err) {\n console.log(\"error\",err);\n \u002F\u002F TODO: implement a connection recovery mechanism\n };\n\n this.socket.onopen = function () {\n console.warn(\"socket open\");\n self.socket.send(Utils.stringify({\n type: 'info',\n id: url\n }));\n };\n\n this.socket.onclose = function() {\n console.warn(\"socket closed\");\n self.close();\n };\n window.onbeforeunload = function() {\n self.onclose = function () {}; \u002F\u002F disable onclose handler first\n self.close();\n };\n\n this.socket.onmessage = function (evt) {\n var msg = null;\n var vector = null;\n var now = Date.now();\n var localTimestamp = 0;\n\n if (typeof evt.data === 'string') {\n try {\n msg = JSON.parse(evt.data) || {};\n }\n catch (err) {\n\n return;\n }\n\n if (msg.id !== url) {\n\n return;\n }\n\n switch (msg.type) {\n case 'info':\n \u002F\u002F Info received from the socket server but note that the clock may\n \u002F\u002F not yet be synchronized with that of the server, let's wait for\n \u002F\u002F that.\n\n processInfoWhenPossible(msg);\n break;\n\n case 'change':\n if (self.readyState !== 'open') {\n\n return;\n }\n\n \u002F\u002F TODO: not sure what to do when the server sends an update with\n \u002F\u002F a timestamp that lies in the past of the current vector we have,\n \u002F\u002F ignoring for now\n if (msg.vector.timestamp \u003C self.serverVector.timestamp) {\n\n return;\n }\n\n \u002F\u002F Create a new Media state vector from the one received\n vector = new StateVector(msg.vector);\n\n \u002F\u002F Determine whether the change event is to be applied now or to be\n \u002F\u002F queued up for later\n localTimestamp = vector.timestamp * 1000.0 +\n now - self.clock.getTime(now);\n if (localTimestamp \u003C now) {\n\n self.serverVector = vector;\n }\n else {\n\n pendingChanges.push(vector);\n pendingChanges.sort(function (a, b) {\n return a.timestamp - b.timestamp;\n });\n scheduleNextPendingChange();\n }\n break;\n }\n }\n };\n\n \u002F\u002F Create the clock\n if (clock) {\n this.clock = clock;\n }\n else {\n this.clock = new SocketSyncClock(url, this.socket);\n this.clock.addEventListener('change', function () {\n if (self.readyState !== 'open') {\n return;\n }\n\n scheduleNextPendingChange();\n });\n }\n\n\n\n\n \u002F\u002F Check the initial state of the socket connection\n if (this.socket.readyState === OPEN$1) {\n\n this.socket.send(Utils.stringify({\n type: 'info',\n id: url\n }));\n }\n else if (this.socket.readyState === CLOSED$1) {\n\n self.close();\n }\n\n };\n SocketTimingProvider.prototype = new AbstractTimingProvider();\n\n\n \u002F**\n * Sends an update command to the online timing service.\n *\n * @function\n * @param {Object} vector The new motion vector\n * @param {Number} vector.position The new motion position.\n * If null, the position at the current time is used.\n * @param {Number} vector.velocity The new velocity.\n * If null, the velocity at the current time is used.\n * @param {Number} vector.acceleration The new acceleration.\n * If null, the acceleration at the current time is used.\n * @returns {Promise} The promise to get an updated StateVector that\n * represents the updated motion on the server once the update command\n * has been processed by the server.\n * The promise is rejected if the connection with the online timing service\n * is not possible for some reason (no connection, timing object on the\n * server was deleted, timeout, permission issue).\n *\u002F\n SocketTimingProvider.prototype.update = function (vector) {\n vector = vector || {};\n\n\n if (this.readyState !== 'open') {\n return new Promise(function (resolve, reject) {\n\n reject(new Error('Underlying socket was closed'));\n });\n }\n this.socket.send(Utils.stringify({\n type: 'update',\n id: this.url,\n vector: vector\n }));\n\n return new Promise(function (resolve, reject) {\n \u002F\u002F TODO: To be able to resolve the promise, we would need to know\n \u002F\u002F when the server has received and processed the request. This\n \u002F\u002F requires an ack that does not yet exist. Also, should the promise\n \u002F\u002F only be resolved when the update is actually done (which may take\n \u002F\u002F place after some time and may actually not take place at all?)\n resolve();\n });\n };\n\n\n \u002F**\n * Closes the timing provider object, releasing any resource that the\n * object might use.\n *\n * Note that a closed timing provider object cannot be re-used.\n *\n * @function\n *\u002F\n SocketTimingProvider.prototype.close = function () {\n if ((this.readyState === 'closing') ||\n (this.readyState === 'closed')) {\n return;\n }\n this.readyState = 'closing';\n this.clock.close();\n if (!this.socketProvided && (this.socket.readyState !== CLOSED$1)) {\n this.socket.close();\n }\n this.socket = null;\n this.readyState = 'closed';\n };\n\n \u002F**\n * @file A timing media controller takes inputs from a timing object and\n * harnesses one or more HTML media elements (audio, video, media controller)\n * accordingly.\n *\n * A timing media controller exposes usual media element controls such as\n * \"play\", \"pause\" methods as well as \"currentTime\" and \"playbackRate\"\n * attributes. Internally, calling these methods or setting these attributes\n * update the timing object's state vector, which should in turn affect the\n * HTML media elements that the timing media controller harnesses.\n *\n * Said differently, commands sent to a timing media controller are not\n * directly applied to the HTML media elements under control. Everything goes\n * through the timing object to enable cross-device synchronization effects.\n *\n * TODO: add logic to handle buffering hiccups in media elements.\n * TODO: add logic to remove elements from the list of controlled elements.\n *\u002F\n\n\n \u002F**\n * Constructor of a timing media controller\n *\n * @class\n * @param {TimingObject} timing The timing object attached to the controller\n * @param {Object} options controller settings\n *\u002F\n var TimingMediaController = function (timing, options) {\n var self = this;\n options = options || {};\n\n if (!timing || (!timing instanceof TimingObject)) {\n throw new Error('No timing object provided');\n }\n\n \u002F**\n * The timing media controller's internal settings\n *\u002F\n var settings = {\n \u002F\u002F Media elements are considered in sync with the timing object if the\n \u002F\u002F difference between the position they report and the position of the\n \u002F\u002F timing object is below that threshold (in seconds).\n minDiff: options.minDiff || 0.040,\n\n \u002F\u002F Maximum delay for catching up (in seconds).\n \u002F\u002F If the code cannot meet the maxDelay constraint,\n \u002F\u002F it will have the media element directly seek to the right position.\n maxDelay: options.maxDelay || 0.8,\n\n \u002F\u002F Amortization period (in seconds).\n \u002F\u002F The amortization period is used when adjustments are made to\n \u002F\u002F the playback rate of the video.\n amortPeriod: options.amortPeriod || 1.0\n };\n\n\n \u002F**\n * The list of Media elements controlled by this timing media controller.\n *\n * For each media element, the controller maintains a state vector\n * representation of the element's position and velocity, a drift rate\n * to adjust the playback rate, whether we asked the media element to\n * seek or not, and whether there is an amortization period running for\n * the element\n *\n * {\n * vector: {},\n * driftRate: 0.0,\n * seeked: false,\n * amortization: false,\n * element: {}\n * }\n *\u002F\n var controlledElements = [];\n\n\n \u002F**\n * The timing object's state vector last time we checked it.\n * This variable is used in particular at the end of the amortization\n * period to compute the media element's drift rate\n *\u002F\n var timingVector = null;\n\n\n \u002F**\n * Pointer to the amortization period timeout.\n * The controller uses only one amortization period for all media elements\n * under control.\n *\u002F\n var amortTimeout = null;\n\n\n Object.defineProperties(this, {\n \u002F**\n * Report the state of the underlying timing object\n *\n * TODO: should that also take into account the state of the controlled\n * elements? Hard to find a proper definition though\n *\u002F\n readyState: {\n get: function () {\n return timingProvider.readyState;\n }\n },\n\n\n \u002F**\n * The currentTime attribute returns the position that all controlled\n * media elements should be at, in other words the position of the\n * timing media controller when this method is called.\n *\n * On setting, the timing object's state vector is updated with the\n * provided value, which will (asynchronously) affect all controlled\n * media elements.\n *\n * Note that getting \"currentTime\" right after setting it may not return\n * the value that was just set.\n *\u002F\n currentTime: {\n get: function () {\n return timing.currentPosition;\n },\n set: function (value) {\n timing.update(value, null);\n }\n },\n\n\n \u002F**\n * The current playback rate of the controller (controlled media elements\n * may have a slightly different playback rate since the role of the\n * controller is precisely to adjust their playback rate to ensure they\n * keep up with the controller's position.\n *\n * On setting, the timing object's state vector is updated with the\n * provided value, which will (asynchronously) affect all controlled\n * media elements.\n *\n * Note that getting \"playbackRate\" right after setting it may not return\n * the value that was just set.\n *\u002F\n playbackRate: {\n get: function () {\n return timing.currentVelocity;\n },\n set: function (value) {\n timing.update(null, value);\n }\n }\n });\n\n\n \u002F**\n * Start playing the controlled elements\n *\n * @function\n *\u002F\n this.play = function () {\n timing.update(null, 1.0);\n };\n\n\n \u002F**\n * Pause playback\n *\n * @function\n *\u002F\n this.pause = function () {\n timing.update(null, 0.0);\n\n };\n this.reset = function (vel) {\n timing.update(0.0,vel);\n };\n this.seek = function (time,vel) {\n timing.update(time, vel);\n };\n this.incPlayBacRate = function (){\n this.playbackRate+=0.3;\n };\n this.decPlayBacRate = function(){\n this.playbackRate-=0.3;\n };\n \u002F**\n * Add a media element to the list of elements controlled by this\n * controller\n *\n * @function\n * @param {MediaElement} element The media element to associate with the\n * controller.\n *\u002F\n this.addMediaElement = function (element,offset) {\n var found = false;\n if (element) {\n\n console.log(\"OFFSET\",offset\u002F1000);\n element._offset = offset\u002F1000;\n controlledElements.forEach(function (wrappedEl) {\n if (wrappedEl.element === element) {\n\n found = true;\n }\n });\n if (found) {\n return;\n }\n controlledElements.push({\n element: element,\n vector: null,\n driftRate: 0.0,\n seeked: false,\n amortization: false\n });\n }\n };\n this.removeMediaElement = function (element) {\n controlledElements = controlledElements.filter(function (wrappedEl) {\n if (wrappedEl.element === element) {\n return false;\n }\n else return true;\n });\n\n\n };\n\n \u002F**\n * Helper function that cancels a running amortization period\n *\u002F\n var cancelAmortizationPeriod = function () {\n if (!amortTimeout) {\n return;\n }\n clearTimeout(amortTimeout);\n amortTimeout = null;\n controlledElements.forEach(function (wrappedEl) {\n wrappedEl.amortization = false;\n wrappedEl.seeked = false;\n });\n };\n\n\n \u002F**\n * Helper function to stop the playback adjustment once the amortization\n * period is over.\n *\u002F\n var stopAmortizationPeriod = function () {\n var now = Date.now() \u002F 1000.0;\n amortTimeout = null;\n\n controlledElements.forEach(function (wrappedEl) {\n \u002F\u002F Nothing to do if element was not part of amortization period\n if (!wrappedEl.amortization) {\n return;\n }\n wrappedEl.amortization = false;\n\n \u002F\u002F Don't adjust playback rate and drift rate if video was seeked\n \u002F\u002F or if element was not part of that amortization period.\n if (wrappedEl.seeked) {\n\n wrappedEl.seeked = false;\n return;\n }\n\n \u002F\u002F Compute the difference between the position the video should be and\n \u002F\u002F the position it is reported to be at.\n var diff = wrappedEl.vector.computePosition(now) - wrappedEl.element.currentTime;\n\n \u002F\u002F Compute the new video drift rate\n wrappedEl.driftRate = 0.002;\n\n \u002F\u002F Switch back to the current vector's velocity,\n \u002F\u002F adjusted with the newly computed drift rate\n wrappedEl.vector.velocity = timingVector.velocity + wrappedEl.driftRate;\n wrappedEl.element.playbackRate = wrappedEl.vector.velocity;\n\n\n });\n };\n\n\n\n \u002F**\n * React to timing object's changes, harnessing the controlled\n * elements to align them with the timing object's position and velocity\n *\u002F\n var onTimingChange = function () {\n cancelAmortizationPeriod();\n controlElements();\n };\n\n\n \u002F**\n * Ensure media elements are aligned with the current timing object's\n * state vector\n *\u002F\n var controlElements = function () {\n \u002F\u002F Do not adjust anything during an amortization period\n if (amortTimeout) {\n return;\n }\n\n \u002F\u002F Get new readings from Timing object\n timingVector = timing.query();\n\n controlledElements.forEach(controlElement);\n\n var amortNeeded = false;\n controlledElements.forEach(function (wrappedEl) {\n if (wrappedEl.amortization) {\n amortNeeded = true;\n }\n });\n\n if (amortNeeded) {\n\n amortTimeout = setTimeout(stopAmortizationPeriod, settings.amortPeriod * 1000);\n }\n\n \u002F\u002F Queue a task to fire a simple event named \"timeupdate\"\n setTimeout(function () {\n self.dispatchEvent({\n type: 'timeupdate'\n }, 0);\n });\n };\n\n\n \u002F**\n * Ensure the given media element (wrapped in info structure) is aligned\n * with the current timing object's state vector\n *\u002F\n var controlElement = function (wrappedEl) {\n var element = wrappedEl.element;\n var diff = 0.0;\n var futurePos = 0.0;\n \u002F\u002F console.log(\"driftRate\",wrappedEl.driftRate);\n\n if ((timingVector.velocity === 0.0) &&\n (timingVector.acceleration === 0.0)) {\n \u002F\u002Flogger.info('stop element and seek to right position');\n element.pause();\n element.currentTime=timingVector.position;\n wrappedEl.vector = new StateVector(timingVector);\n }\n else if (element.paused ) {\n \u002F\u002F logger.info('play video');\n wrappedEl.vector = new StateVector({\n position: timingVector.position,\n velocity: timingVector.velocity + wrappedEl.driftRate,\n acceleration: 0.0,\n timestamp: timingVector.timestamp\n });\n\n wrappedEl.seeked = true;\n wrappedEl.amortization = true;\n element.currentTime = wrappedEl.vector.position-element._offset;\n element.playbackRate = wrappedEl.vector.velocity;\n element.play();\n\n\n }\n else {\n var vel = wrappedEl.vector ? wrappedEl.vector.velocity: 0.95;\n wrappedEl.vector = new StateVector({\n position: element.currentTime,\n velocity:vel,\n });\n diff = timingVector.position - wrappedEl.vector.position - element._offset;\n \u002F\u002F console.log(\"diff\",diff,\"vel\",wrappedEl.vector.velocity,\"timing vel\",timingVector.velocity,\"offset\",element._offset);\n if (Math.abs(diff) \u003C settings.minDiff) ;\n else if (Math.abs(diff) \u003E settings.maxDelay) {\n console.log(\"DIFF\",diff);\n wrappedEl.vector.position = timingVector.position;\n wrappedEl.vector.velocity = timingVector.velocity + wrappedEl.driftRate;\n wrappedEl.seeked = true;\n wrappedEl.amortization = false;\n element.currentTime = wrappedEl.vector.position-element._offset;\n element.playbackRate = wrappedEl.vector.velocity;\n }\n else {\n futurePos = timingVector.computePosition(\n timingVector.timestamp + settings.amortPeriod);\n wrappedEl.vector.velocity =\n wrappedEl.driftRate +\n (futurePos - wrappedEl.vector.position) \u002F settings.amortPeriod;\n wrappedEl.amortization = false;\n element.playbackRate=wrappedEl.vector.velocity;\n \u002F\u002F logger.info('new playbackrate={}', wrappedEl.vector.velocity);\n }\n }\n\n };\n\n\n \u002F**********************************************************************\n Listen to the timing object\n **********************************************************************\u002F\n\n timing.addEventListener('timeupdate', controlElements);\n timing.addEventListener('change', onTimingChange);\n\n\n timing.addEventListener('readystatechange', function (evt) {\n self.dispatchEvent(evt);\n });\n\n\n\n };\n\n\n \u002F\u002F TimingMediaController implements EventTarget\n TimingMediaController.prototype.addEventListener = EventTarget().addEventListener;\n TimingMediaController.prototype.removeEventListener = EventTarget().removeEventListener;\n TimingMediaController.prototype.dispatchEvent = EventTarget().dispatchEvent;\n\n class ObjectSync {\n constructor(){\n this.currentTime=0.0;\n this.playbackRate=1.0;\n this.paused=true;\n this.offset=0;\n }\n\n pause (){\n \u002F\u002F TODO pause\n console.log(\"TODO\");\n }\n play (){\n \u002F\u002F TODO play\n console.log(\"TODO\");\n }\n seekTo (time){\n \u002F\u002F TODO play\n console.log(\"TODO\");\n }\n setPlaybackRate(rate){\n this.playbackRate = rate;\n }\n incPlayBacRate(){\n this.playbackRate+=0.3;\n }\n decPlayBacRate(){\n this.playbackRate-=0.3;\n }\n }\n\n var Motion = function(url,channel){\n var controller = undefined;\n var timing = undefined;\n \u002F**********************************************************************\n Create the timing object associated with the online timing service\n **********************************************************************\u002F\n var start = function (){\n let domain = Utils.getHostName(url);\n var timingProvider = new SocketTimingProvider('wss:\u002F\u002F'+domain+':8080\u002F'+(channel || 'test'));\n\n timing = new TimingObject();\n timing.srcObject = timingProvider;\n controller = new TimingMediaController(timing);\n\n };\n var addMedia = function(media,offset){\n controller.addMediaElement(media,offset);\n controller.addEventListener('readystatechange', function (evt) {\n\n if (evt.value === 'open') {\n API.dispatchEvent({type:'motionReady',value:' '});\n }\n });\n\n return controller;\n };\n var removeMedia = function(media){\n controller.removeMediaElement(media);\n };\n \u002F**********************************************************************\n Enable commands when timing object is connected\n **********************************************************************\u002F\n var getController = function (){\n return controller;\n };\n var getTiming = function (){\n return timing;\n };\n\n var API = {\n addObject:addMedia,\n removeObject:removeMedia,\n getController:getController,\n start:start,\n ready:false,\n timing:getTiming,\n live:false,\n ObjectSync:ObjectSync,\n initTime:0\n };\n\n\n API.removeEventListener = EventTarget().removeEventListener;\n API.addEventListener = EventTarget().addEventListener;\n API.dispatchEvent = EventTarget().dispatchEvent;\n return API;\n };\n\n class User {\n\n constructor(agentid,name=\"\",profile,capacities){\n this.agentid = agentid;\n this.name = name;\n this.profile = profile;\n this.capacities = capacities;\n }\n\n\n }\n\n function UI (app) {\n var self = {};\n if (typeof document !== 'undefined')\n self.components = document.querySelector('orkestra-ui')?document.querySelector('orkestra-ui').children:[];\n else self.components =[];\n self.callbacks = [];\n app.timerObservable.subscribe(evt=\u003E{\n \u002F\u002F giving time to deploy rules to apply\n setTimeout(()=\u003E{\n runLayout(evt);\n\n },120);\n\n });\n app.userObservable.subscribe(chg=\u003E{\n runLayout(chg);\n });\n \n app.appObservable.subscribe(x =\u003E {\n if (x.key==\"layout\"){\n console.log(\"changing layout\");\n if (x.value)\n self.useLayout(x.value);\n else if(x.data) self.useLayout(x.data.value);\n }\n runLayout(x);\n });\n \n var runLayout = function (chg){\n \u002F\u002F clean style before\n Array.from(self.components).forEach((c)=\u003E{\n let status = c.style.display;\n c.style =\"\";\n c.className =\"\";\n c.style.display = status;\n });\n if (self.activeLayout){\n let _layout = self.callbacks.find((layout)=\u003E{ return layout.name === self.activeLayout});\n if (_layout) _layout.callback(chg,self.components);\n }\n else\n self.callbacks.forEach((cb)=\u003E{\n cb.callback(chg,self.components);\n });\n };\n self.subscribe = function (plugin){\n self.callbacks.push({name:plugin.name,callback:plugin()});\n console.log(\"registring UI\",plugin.name);\n };\n self.unsubscribe = function (plugin){\n console.log(\"unregistring UI\",plugin.name);\n };\n self.useLayout = function (name){\n self.activeLayout = name;\n };\n return {\n \"subscribe\":self.subscribe,\n \"components\":self.components,\n \"layouts\":self.callbacks,\n \"useLayout\":self.useLayout,\n\n\n }\n }\n\n var URI = {\n getUrlVar : function (name){\n var vars = {};\n var parts = window.location.href.replace(\u002F[?&]+([^=&]+)=([^&]*)\u002Fgi, function(m,key,value) {\n vars[key] = value;\n });\n return vars[name];\n },\n createToken:function(){\n var rand = function() {\n return Math.random().toString(36).substr(2); \u002F\u002F remove `0.`\n };\n\n var token = function() {\n return rand() + rand(); \u002F\u002F to make it longer\n };\n return token();\n },\n shortURL:function(server,url){\n var p1= new Promise((resolve,reject)=\u003E{\n fetch(server+'\u002Fapi\u002Fshorten',{\n method: 'POST', \u002F\u002F or 'PUT'\n body: JSON.stringify({url:url}),\n headers:{\n 'Content-Type': 'application\u002Fjson'\n }\n })\n .then((data)=\u003E{\n data.json().then((_data)=\u003E{\n console.log('Shorten:'+_data.shortUrl);\n resolve(JSON.parse('{\"response\":\"'+_data.shortUrl+'\"}'));\n });\n });\n\n });\n return p1;\n }\n\n\n };\n\n \u002F**\n * @fileoverview\n * - Using the 'QRCode for Javascript library'\n * - Fixed dataset of 'QRCode for Javascript library' for support full-spec.\n * - this library has no dependencies.\n *\n * @author davidshimjs\n * @see \u003Ca href=\"http:\u002F\u002Fwww.d-project.com\u002F\" target=\"_blank\"\u003Ehttp:\u002F\u002Fwww.d-project.com\u002F\u003C\u002Fa\u003E\n * @see \u003Ca href=\"http:\u002F\u002Fjeromeetienne.github.com\u002Fjquery-qrcode\u002F\" target=\"_blank\"\u003Ehttp:\u002F\u002Fjeromeetienne.github.com\u002Fjquery-qrcode\u002F\u003C\u002Fa\u003E\n *\u002F\n\n\n (function () {\n \u002F\u002F---------------------------------------------------------------------\n \u002F\u002F QRCode for JavaScript\n \u002F\u002F\n \u002F\u002F Copyright (c) 2009 Kazuhiko Arase\n \u002F\u002F\n \u002F\u002F URL: http:\u002F\u002Fwww.d-project.com\u002F\n \u002F\u002F\n \u002F\u002F Licensed under the MIT license:\n \u002F\u002F http:\u002F\u002Fwww.opensource.org\u002Flicenses\u002Fmit-license.php\n \u002F\u002F\n \u002F\u002F The word \"QR Code\" is registered trademark of\n \u002F\u002F DENSO WAVE INCORPORATED\n \u002F\u002F http:\u002F\u002Fwww.denso-wave.com\u002Fqrcode\u002Ffaqpatent-e.html\n \u002F\u002F\n \u002F\u002F---------------------------------------------------------------------\n function QR8bitByte(data) {\n this.mode = QRMode.MODE_8BIT_BYTE;\n this.data = data;\n this.parsedData = [];\n\n \u002F\u002F Added to support UTF-8 Characters\n for (var i = 0, l = this.data.length; i \u003C l; i++) {\n var byteArray = [];\n var code = this.data.charCodeAt(i);\n\n if (code \u003E 0x10000) {\n byteArray[0] = 0xF0 | ((code & 0x1C0000) \u003E\u003E\u003E 18);\n byteArray[1] = 0x80 | ((code & 0x3F000) \u003E\u003E\u003E 12);\n byteArray[2] = 0x80 | ((code & 0xFC0) \u003E\u003E\u003E 6);\n byteArray[3] = 0x80 | (code & 0x3F);\n } else if (code \u003E 0x800) {\n byteArray[0] = 0xE0 | ((code & 0xF000) \u003E\u003E\u003E 12);\n byteArray[1] = 0x80 | ((code & 0xFC0) \u003E\u003E\u003E 6);\n byteArray[2] = 0x80 | (code & 0x3F);\n } else if (code \u003E 0x80) {\n byteArray[0] = 0xC0 | ((code & 0x7C0) \u003E\u003E\u003E 6);\n byteArray[1] = 0x80 | (code & 0x3F);\n } else {\n byteArray[0] = code;\n }\n\n this.parsedData.push(byteArray);\n }\n\n this.parsedData = Array.prototype.concat.apply([], this.parsedData);\n\n if (this.parsedData.length != this.data.length) {\n this.parsedData.unshift(191);\n this.parsedData.unshift(187);\n this.parsedData.unshift(239);\n }\n }\n\n QR8bitByte.prototype = {\n getLength: function (buffer) {\n return this.parsedData.length;\n },\n write: function (buffer) {\n for (var i = 0, l = this.parsedData.length; i \u003C l; i++) {\n buffer.put(this.parsedData[i], 8);\n }\n }\n };\n\n function QRCodeModel(typeNumber, errorCorrectLevel) {\n this.typeNumber = typeNumber;\n this.errorCorrectLevel = errorCorrectLevel;\n this.modules = null;\n this.moduleCount = 0;\n this.dataCache = null;\n this.dataList = [];\n }\n\n QRCodeModel.prototype={addData:function(data){var newData=new QR8bitByte(data);this.dataList.push(newData);this.dataCache=null;},isDark:function(row,col){if(row\u003C0||this.moduleCount\u003C=row||col\u003C0||this.moduleCount\u003C=col){throw new Error(row+\",\"+col);}\n return this.modules[row][col];},getModuleCount:function(){return this.moduleCount;},make:function(){this.makeImpl(false,this.getBestMaskPattern());},makeImpl:function(test,maskPattern){this.moduleCount=this.typeNumber*4+17;this.modules=new Array(this.moduleCount);for(var row=0;row\u003Cthis.moduleCount;row++){this.modules[row]=new Array(this.moduleCount);for(var col=0;col\u003Cthis.moduleCount;col++){this.modules[row][col]=null;}}\n this.setupPositionProbePattern(0,0);this.setupPositionProbePattern(this.moduleCount-7,0);this.setupPositionProbePattern(0,this.moduleCount-7);this.setupPositionAdjustPattern();this.setupTimingPattern();this.setupTypeInfo(test,maskPattern);if(this.typeNumber\u003E=7){this.setupTypeNumber(test);}\n if(this.dataCache==null){this.dataCache=QRCodeModel.createData(this.typeNumber,this.errorCorrectLevel,this.dataList);}\n this.mapData(this.dataCache,maskPattern);},setupPositionProbePattern:function(row,col){for(var r=-1;r\u003C=7;r++){if(row+r\u003C=-1||this.moduleCount\u003C=row+r)continue;for(var c=-1;c\u003C=7;c++){if(col+c\u003C=-1||this.moduleCount\u003C=col+c)continue;if((0\u003C=r&&r\u003C=6&&(c==0||c==6))||(0\u003C=c&&c\u003C=6&&(r==0||r==6))||(2\u003C=r&&r\u003C=4&&2\u003C=c&&c\u003C=4)){this.modules[row+r][col+c]=true;}else {this.modules[row+r][col+c]=false;}}}},getBestMaskPattern:function(){var minLostPoint=0;var pattern=0;for(var i=0;i\u003C8;i++){this.makeImpl(true,i);var lostPoint=QRUtil.getLostPoint(this);if(i==0||minLostPoint\u003ElostPoint){minLostPoint=lostPoint;pattern=i;}}\n return pattern;},createMovieClip:function(target_mc,instance_name,depth){var qr_mc=target_mc.createEmptyMovieClip(instance_name,depth);var cs=1;this.make();for(var row=0;row\u003Cthis.modules.length;row++){var y=row*cs;for(var col=0;col\u003Cthis.modules[row].length;col++){var x=col*cs;var dark=this.modules[row][col];if(dark){qr_mc.beginFill(0,100);qr_mc.moveTo(x,y);qr_mc.lineTo(x+cs,y);qr_mc.lineTo(x+cs,y+cs);qr_mc.lineTo(x,y+cs);qr_mc.endFill();}}}\n return qr_mc;},setupTimingPattern:function(){for(var r=8;r\u003Cthis.moduleCount-8;r++){if(this.modules[r][6]!=null){continue;}\n this.modules[r][6]=(r%2==0);}\n for(var c=8;c\u003Cthis.moduleCount-8;c++){if(this.modules[6][c]!=null){continue;}\n this.modules[6][c]=(c%2==0);}},setupPositionAdjustPattern:function(){var pos=QRUtil.getPatternPosition(this.typeNumber);for(var i=0;i\u003Cpos.length;i++){for(var j=0;j\u003Cpos.length;j++){var row=pos[i];var col=pos[j];if(this.modules[row][col]!=null){continue;}\n for(var r=-2;r\u003C=2;r++){for(var c=-2;c\u003C=2;c++){if(r==-2||r==2||c==-2||c==2||(r==0&&c==0)){this.modules[row+r][col+c]=true;}else {this.modules[row+r][col+c]=false;}}}}}},setupTypeNumber:function(test){var bits=QRUtil.getBCHTypeNumber(this.typeNumber);for(var i=0;i\u003C18;i++){var mod=(!test&&((bits\u003E\u003Ei)&1)==1);this.modules[Math.floor(i\u002F3)][i%3+this.moduleCount-8-3]=mod;}\n for(var i=0;i\u003C18;i++){var mod=(!test&&((bits\u003E\u003Ei)&1)==1);this.modules[i%3+this.moduleCount-8-3][Math.floor(i\u002F3)]=mod;}},setupTypeInfo:function(test,maskPattern){var data=(this.errorCorrectLevel\u003C\u003C3)|maskPattern;var bits=QRUtil.getBCHTypeInfo(data);for(var i=0;i\u003C15;i++){var mod=(!test&&((bits\u003E\u003Ei)&1)==1);if(i\u003C6){this.modules[i][8]=mod;}else if(i\u003C8){this.modules[i+1][8]=mod;}else {this.modules[this.moduleCount-15+i][8]=mod;}}\n for(var i=0;i\u003C15;i++){var mod=(!test&&((bits\u003E\u003Ei)&1)==1);if(i\u003C8){this.modules[8][this.moduleCount-i-1]=mod;}else if(i\u003C9){this.modules[8][15-i-1+1]=mod;}else {this.modules[8][15-i-1]=mod;}}\n this.modules[this.moduleCount-8][8]=(!test);},mapData:function(data,maskPattern){var inc=-1;var row=this.moduleCount-1;var bitIndex=7;var byteIndex=0;for(var col=this.moduleCount-1;col\u003E0;col-=2){if(col==6)col--;while(true){for(var c=0;c\u003C2;c++){if(this.modules[row][col-c]==null){var dark=false;if(byteIndex\u003Cdata.length){dark=(((data[byteIndex]\u003E\u003E\u003EbitIndex)&1)==1);}\n var mask=QRUtil.getMask(maskPattern,row,col-c);if(mask){dark=!dark;}\n this.modules[row][col-c]=dark;bitIndex--;if(bitIndex==-1){byteIndex++;bitIndex=7;}}}\n row+=inc;if(row\u003C0||this.moduleCount\u003C=row){row-=inc;inc=-inc;break;}}}}};QRCodeModel.PAD0=0xEC;QRCodeModel.PAD1=0x11;QRCodeModel.createData=function(typeNumber,errorCorrectLevel,dataList){var rsBlocks=QRRSBlock.getRSBlocks(typeNumber,errorCorrectLevel);var buffer=new QRBitBuffer();for(var i=0;i\u003CdataList.length;i++){var data=dataList[i];buffer.put(data.mode,4);buffer.put(data.getLength(),QRUtil.getLengthInBits(data.mode,typeNumber));data.write(buffer);}\n var totalDataCount=0;for(var i=0;i\u003CrsBlocks.length;i++){totalDataCount+=rsBlocks[i].dataCount;}\n if(buffer.getLengthInBits()\u003EtotalDataCount*8){throw new Error(\"code length overflow. (\"\n +buffer.getLengthInBits()\n +\"\u003E\"\n +totalDataCount*8\n +\")\");}\n if(buffer.getLengthInBits()+4\u003C=totalDataCount*8){buffer.put(0,4);}\n while(buffer.getLengthInBits()%8!=0){buffer.putBit(false);}\n while(true){if(buffer.getLengthInBits()\u003E=totalDataCount*8){break;}\n buffer.put(QRCodeModel.PAD0,8);if(buffer.getLengthInBits()\u003E=totalDataCount*8){break;}\n buffer.put(QRCodeModel.PAD1,8);}\n return QRCodeModel.createBytes(buffer,rsBlocks);};QRCodeModel.createBytes=function(buffer,rsBlocks){var offset=0;var maxDcCount=0;var maxEcCount=0;var dcdata=new Array(rsBlocks.length);var ecdata=new Array(rsBlocks.length);for(var r=0;r\u003CrsBlocks.length;r++){var dcCount=rsBlocks[r].dataCount;var ecCount=rsBlocks[r].totalCount-dcCount;maxDcCount=Math.max(maxDcCount,dcCount);maxEcCount=Math.max(maxEcCount,ecCount);dcdata[r]=new Array(dcCount);for(var i=0;i\u003Cdcdata[r].length;i++){dcdata[r][i]=0xff&buffer.buffer[i+offset];}\n offset+=dcCount;var rsPoly=QRUtil.getErrorCorrectPolynomial(ecCount);var rawPoly=new QRPolynomial(dcdata[r],rsPoly.getLength()-1);var modPoly=rawPoly.mod(rsPoly);ecdata[r]=new Array(rsPoly.getLength()-1);for(var i=0;i\u003Cecdata[r].length;i++){var modIndex=i+modPoly.getLength()-ecdata[r].length;ecdata[r][i]=(modIndex\u003E=0)?modPoly.get(modIndex):0;}}\n var totalCodeCount=0;for(var i=0;i\u003CrsBlocks.length;i++){totalCodeCount+=rsBlocks[i].totalCount;}\n var data=new Array(totalCodeCount);var index=0;for(var i=0;i\u003CmaxDcCount;i++){for(var r=0;r\u003CrsBlocks.length;r++){if(i\u003Cdcdata[r].length){data[index++]=dcdata[r][i];}}}\n for(var i=0;i\u003CmaxEcCount;i++){for(var r=0;r\u003CrsBlocks.length;r++){if(i\u003Cecdata[r].length){data[index++]=ecdata[r][i];}}}\n return data;};var QRMode={MODE_NUMBER:1\u003C\u003C0,MODE_ALPHA_NUM:1\u003C\u003C1,MODE_8BIT_BYTE:1\u003C\u003C2,MODE_KANJI:1\u003C\u003C3};var QRErrorCorrectLevel={L:1,M:0,Q:3,H:2};var QRMaskPattern={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7};var QRUtil={PATTERN_POSITION_TABLE:[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],G15:(1\u003C\u003C10)|(1\u003C\u003C8)|(1\u003C\u003C5)|(1\u003C\u003C4)|(1\u003C\u003C2)|(1\u003C\u003C1)|(1\u003C\u003C0),G18:(1\u003C\u003C12)|(1\u003C\u003C11)|(1\u003C\u003C10)|(1\u003C\u003C9)|(1\u003C\u003C8)|(1\u003C\u003C5)|(1\u003C\u003C2)|(1\u003C\u003C0),G15_MASK:(1\u003C\u003C14)|(1\u003C\u003C12)|(1\u003C\u003C10)|(1\u003C\u003C4)|(1\u003C\u003C1),getBCHTypeInfo:function(data){var d=data\u003C\u003C10;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)\u003E=0){d^=(QRUtil.G15\u003C\u003C(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)));}\n return ((data\u003C\u003C10)|d)^QRUtil.G15_MASK;},getBCHTypeNumber:function(data){var d=data\u003C\u003C12;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)\u003E=0){d^=(QRUtil.G18\u003C\u003C(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)));}\n return (data\u003C\u003C12)|d;},getBCHDigit:function(data){var digit=0;while(data!=0){digit++;data\u003E\u003E\u003E=1;}\n return digit;},getPatternPosition:function(typeNumber){return QRUtil.PATTERN_POSITION_TABLE[typeNumber-1];},getMask:function(maskPattern,i,j){switch(maskPattern){case QRMaskPattern.PATTERN000:return (i+j)%2==0;case QRMaskPattern.PATTERN001:return i%2==0;case QRMaskPattern.PATTERN010:return j%3==0;case QRMaskPattern.PATTERN011:return (i+j)%3==0;case QRMaskPattern.PATTERN100:return (Math.floor(i\u002F2)+Math.floor(j\u002F3))%2==0;case QRMaskPattern.PATTERN101:return (i*j)%2+(i*j)%3==0;case QRMaskPattern.PATTERN110:return ((i*j)%2+(i*j)%3)%2==0;case QRMaskPattern.PATTERN111:return ((i*j)%3+(i+j)%2)%2==0;default:throw new Error(\"bad maskPattern:\"+maskPattern);}},getErrorCorrectPolynomial:function(errorCorrectLength){var a=new QRPolynomial([1],0);for(var i=0;i\u003CerrorCorrectLength;i++){a=a.multiply(new QRPolynomial([1,QRMath.gexp(i)],0));}\n return a;},getLengthInBits:function(mode,type){if(1\u003C=type&&type\u003C10){switch(mode){case QRMode.MODE_NUMBER:return 10;case QRMode.MODE_ALPHA_NUM:return 9;case QRMode.MODE_8BIT_BYTE:return 8;case QRMode.MODE_KANJI:return 8;default:throw new Error(\"mode:\"+mode);}}else if(type\u003C27){switch(mode){case QRMode.MODE_NUMBER:return 12;case QRMode.MODE_ALPHA_NUM:return 11;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 10;default:throw new Error(\"mode:\"+mode);}}else if(type\u003C41){switch(mode){case QRMode.MODE_NUMBER:return 14;case QRMode.MODE_ALPHA_NUM:return 13;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 12;default:throw new Error(\"mode:\"+mode);}}else {throw new Error(\"type:\"+type);}},getLostPoint:function(qrCode){var moduleCount=qrCode.getModuleCount();var lostPoint=0;for(var row=0;row\u003CmoduleCount;row++){for(var col=0;col\u003CmoduleCount;col++){var sameCount=0;var dark=qrCode.isDark(row,col);for(var r=-1;r\u003C=1;r++){if(row+r\u003C0||moduleCount\u003C=row+r){continue;}\n for(var c=-1;c\u003C=1;c++){if(col+c\u003C0||moduleCount\u003C=col+c){continue;}\n if(r==0&&c==0){continue;}\n if(dark==qrCode.isDark(row+r,col+c)){sameCount++;}}}\n if(sameCount\u003E5){lostPoint+=(3+sameCount-5);}}}\n for(var row=0;row\u003CmoduleCount-1;row++){for(var col=0;col\u003CmoduleCount-1;col++){var count=0;if(qrCode.isDark(row,col))count++;if(qrCode.isDark(row+1,col))count++;if(qrCode.isDark(row,col+1))count++;if(qrCode.isDark(row+1,col+1))count++;if(count==0||count==4){lostPoint+=3;}}}\n for(var row=0;row\u003CmoduleCount;row++){for(var col=0;col\u003CmoduleCount-6;col++){if(qrCode.isDark(row,col)&&!qrCode.isDark(row,col+1)&&qrCode.isDark(row,col+2)&&qrCode.isDark(row,col+3)&&qrCode.isDark(row,col+4)&&!qrCode.isDark(row,col+5)&&qrCode.isDark(row,col+6)){lostPoint+=40;}}}\n for(var col=0;col\u003CmoduleCount;col++){for(var row=0;row\u003CmoduleCount-6;row++){if(qrCode.isDark(row,col)&&!qrCode.isDark(row+1,col)&&qrCode.isDark(row+2,col)&&qrCode.isDark(row+3,col)&&qrCode.isDark(row+4,col)&&!qrCode.isDark(row+5,col)&&qrCode.isDark(row+6,col)){lostPoint+=40;}}}\n var darkCount=0;for(var col=0;col\u003CmoduleCount;col++){for(var row=0;row\u003CmoduleCount;row++){if(qrCode.isDark(row,col)){darkCount++;}}}\n var ratio=Math.abs(100*darkCount\u002FmoduleCount\u002FmoduleCount-50)\u002F5;lostPoint+=ratio*10;return lostPoint;}};var QRMath={glog:function(n){if(n\u003C1){throw new Error(\"glog(\"+n+\")\");}\n return QRMath.LOG_TABLE[n];},gexp:function(n){while(n\u003C0){n+=255;}\n while(n\u003E=256){n-=255;}\n return QRMath.EXP_TABLE[n];},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)};for(var i=0;i\u003C8;i++){QRMath.EXP_TABLE[i]=1\u003C\u003Ci;}\n for(var i=8;i\u003C256;i++){QRMath.EXP_TABLE[i]=QRMath.EXP_TABLE[i-4]^QRMath.EXP_TABLE[i-5]^QRMath.EXP_TABLE[i-6]^QRMath.EXP_TABLE[i-8];}\n for(var i=0;i\u003C255;i++){QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]]=i;}\n function QRPolynomial(num,shift){if(num.length==undefined){throw new Error(num.length+\"\u002F\"+shift);}\n var offset=0;while(offset\u003Cnum.length&&num[offset]==0){offset++;}\n this.num=new Array(num.length-offset+shift);for(var i=0;i\u003Cnum.length-offset;i++){this.num[i]=num[i+offset];}}\n QRPolynomial.prototype={get:function(index){return this.num[index];},getLength:function(){return this.num.length;},multiply:function(e){var num=new Array(this.getLength()+e.getLength()-1);for(var i=0;i\u003Cthis.getLength();i++){for(var j=0;j\u003Ce.getLength();j++){num[i+j]^=QRMath.gexp(QRMath.glog(this.get(i))+QRMath.glog(e.get(j)));}}\n return new QRPolynomial(num,0);},mod:function(e){if(this.getLength()-e.getLength()\u003C0){return this;}\n var ratio=QRMath.glog(this.get(0))-QRMath.glog(e.get(0));var num=new Array(this.getLength());for(var i=0;i\u003Cthis.getLength();i++){num[i]=this.get(i);}\n for(var i=0;i\u003Ce.getLength();i++){num[i]^=QRMath.gexp(QRMath.glog(e.get(i))+ratio);}\n return new QRPolynomial(num,0).mod(e);}};function QRRSBlock(totalCount,dataCount){this.totalCount=totalCount;this.dataCount=dataCount;}\n QRRSBlock.RS_BLOCK_TABLE=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]];QRRSBlock.getRSBlocks=function(typeNumber,errorCorrectLevel){var rsBlock=QRRSBlock.getRsBlockTable(typeNumber,errorCorrectLevel);if(rsBlock==undefined){throw new Error(\"bad rs block @ typeNumber:\"+typeNumber+\"\u002FerrorCorrectLevel:\"+errorCorrectLevel);}\n var length=rsBlock.length\u002F3;var list=[];for(var i=0;i\u003Clength;i++){var count=rsBlock[i*3+0];var totalCount=rsBlock[i*3+1];var dataCount=rsBlock[i*3+2];for(var j=0;j\u003Ccount;j++){list.push(new QRRSBlock(totalCount,dataCount));}}\n return list;};QRRSBlock.getRsBlockTable=function(typeNumber,errorCorrectLevel){switch(errorCorrectLevel){case QRErrorCorrectLevel.L:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+0];case QRErrorCorrectLevel.M:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+1];case QRErrorCorrectLevel.Q:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+2];case QRErrorCorrectLevel.H:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+3];default:return undefined;}};function QRBitBuffer(){this.buffer=[];this.length=0;}\n QRBitBuffer.prototype={get:function(index){var bufIndex=Math.floor(index\u002F8);return ((this.buffer[bufIndex]\u003E\u003E\u003E(7-index%8))&1)==1;},put:function(num,length){for(var i=0;i\u003Clength;i++){this.putBit(((num\u003E\u003E\u003E(length-i-1))&1)==1);}},getLengthInBits:function(){return this.length;},putBit:function(bit){var bufIndex=Math.floor(this.length\u002F8);if(this.buffer.length\u003C=bufIndex){this.buffer.push(0);}\n if(bit){this.buffer[bufIndex]|=(0x80\u003E\u003E\u003E(this.length%8));}\n this.length++;}};var QRCodeLimitLength=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]];\n\n function _isSupportCanvas() {\n return typeof CanvasRenderingContext2D != \"undefined\";\n }\n\n \u002F\u002F android 2.x doesn't support Data-URI spec\n function _getAndroid() {\n var android = false;\n var sAgent = navigator.userAgent;\n\n if (\u002Fandroid\u002Fi.test(sAgent)) { \u002F\u002F android\n android = true;\n var aMat = sAgent.toString().match(\u002Fandroid ([0-9]\\.[0-9])\u002Fi);\n\n if (aMat && aMat[1]) {\n android = parseFloat(aMat[1]);\n }\n }\n\n return android;\n }\n\n var svgDrawer = (function() {\n\n var Drawing = function (el, htOption) {\n this._el = el;\n this._htOption = htOption;\n };\n\n Drawing.prototype.draw = function (oQRCode) {\n var _htOption = this._htOption;\n var _el = this._el;\n var nCount = oQRCode.getModuleCount();\n var nWidth = Math.floor(_htOption.width \u002F nCount);\n var nHeight = Math.floor(_htOption.height \u002F nCount);\n\n this.clear();\n\n function makeSVG(tag, attrs) {\n var el = document.createElementNS('http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg', tag);\n for (var k in attrs)\n if (attrs.hasOwnProperty(k)) el.setAttribute(k, attrs[k]);\n return el;\n }\n\n var svg = makeSVG(\"svg\" , {'viewBox': '0 0 ' + String(nCount) + \" \" + String(nCount), 'width': '100%', 'height': '100%', 'fill': _htOption.colorLight});\n svg.setAttributeNS(\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fxmlns\u002F\", \"xmlns:xlink\", \"http:\u002F\u002Fwww.w3.org\u002F1999\u002Fxlink\");\n _el.appendChild(svg);\n\n svg.appendChild(makeSVG(\"rect\", {\"fill\": _htOption.colorLight, \"width\": \"100%\", \"height\": \"100%\"}));\n svg.appendChild(makeSVG(\"rect\", {\"fill\": _htOption.colorDark, \"width\": \"1\", \"height\": \"1\", \"id\": \"template\"}));\n\n for (var row = 0; row \u003C nCount; row++) {\n for (var col = 0; col \u003C nCount; col++) {\n if (oQRCode.isDark(row, col)) {\n var child = makeSVG(\"use\", {\"x\": String(col), \"y\": String(row)});\n child.setAttributeNS(\"http:\u002F\u002Fwww.w3.org\u002F1999\u002Fxlink\", \"href\", \"#template\");\n svg.appendChild(child);\n }\n }\n }\n };\n Drawing.prototype.clear = function () {\n while (this._el.hasChildNodes())\n this._el.removeChild(this._el.lastChild);\n };\n return Drawing;\n })();\n\n var useSVG = document.documentElement.tagName.toLowerCase() === \"svg\";\n\n \u002F\u002F Drawing in DOM by using Table tag\n var Drawing = useSVG ? svgDrawer : !_isSupportCanvas() ? (function () {\n var Drawing = function (el, htOption) {\n this._el = el;\n this._htOption = htOption;\n };\n\n \u002F**\n * Draw the QRCode\n *\n * @param {QRCode} oQRCode\n *\u002F\n Drawing.prototype.draw = function (oQRCode) {\n var _htOption = this._htOption;\n var _el = this._el;\n var nCount = oQRCode.getModuleCount();\n var nWidth = Math.floor(_htOption.width \u002F nCount);\n var nHeight = Math.floor(_htOption.height \u002F nCount);\n var aHTML = ['\u003Ctable style=\"border:0;border-collapse:collapse;\"\u003E'];\n\n for (var row = 0; row \u003C nCount; row++) {\n aHTML.push('\u003Ctr\u003E');\n\n for (var col = 0; col \u003C nCount; col++) {\n aHTML.push('\u003Ctd style=\"border:0;border-collapse:collapse;padding:0;margin:0;width:' + nWidth + 'px;height:' + nHeight + 'px;background-color:' + (oQRCode.isDark(row, col) ? _htOption.colorDark : _htOption.colorLight) + ';\"\u003E\u003C\u002Ftd\u003E');\n }\n\n aHTML.push('\u003C\u002Ftr\u003E');\n }\n\n aHTML.push('\u003C\u002Ftable\u003E');\n _el.innerHTML = aHTML.join('');\n\n \u002F\u002F Fix the margin values as real size.\n var elTable = _el.childNodes[0];\n var nLeftMarginTable = (_htOption.width - elTable.offsetWidth) \u002F 2;\n var nTopMarginTable = (_htOption.height - elTable.offsetHeight) \u002F 2;\n\n if (nLeftMarginTable \u003E 0 && nTopMarginTable \u003E 0) {\n elTable.style.margin = nTopMarginTable + \"px \" + nLeftMarginTable + \"px\";\n }\n };\n\n \u002F**\n * Clear the QRCode\n *\u002F\n Drawing.prototype.clear = function () {\n this._el.innerHTML = '';\n };\n\n return Drawing;\n })() : (function () { \u002F\u002F Drawing in Canvas\n function _onMakeImage() {\n this._elImage.src = this._elCanvas.toDataURL(\"image\u002Fpng\");\n this._elImage.style.display = \"block\";\n this._elCanvas.style.display = \"none\";\n }\n\n\n \u002F**\n * Check whether the user's browser supports Data URI or not\n *\n * @private\n * @param {Function} fSuccess Occurs if it supports Data URI\n * @param {Function} fFail Occurs if it doesn't support Data URI\n *\u002F\n function _safeSetDataURI(fSuccess, fFail) {\n var self = this;\n self._fFail = fFail;\n self._fSuccess = fSuccess;\n\n \u002F\u002F Check it just once\n if (self._bSupportDataURI === null) {\n var el = document.createElement(\"img\");\n var fOnError = function() {\n self._bSupportDataURI = false;\n\n if (self._fFail) {\n self._fFail.call(self);\n }\n };\n var fOnSuccess = function() {\n self._bSupportDataURI = true;\n\n if (self._fSuccess) {\n self._fSuccess.call(self);\n }\n };\n\n el.onabort = fOnError;\n el.onerror = fOnError;\n el.onload = fOnSuccess;\n el.src = \"data:image\u002Fgif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4\u002F\u002F8\u002Fw38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==\"; \u002F\u002F the Image contains 1px data.\n return;\n } else if (self._bSupportDataURI === true && self._fSuccess) {\n self._fSuccess.call(self);\n } else if (self._bSupportDataURI === false && self._fFail) {\n self._fFail.call(self);\n }\n }\n \u002F**\n * Drawing QRCode by using canvas\n *\n * @constructor\n * @param {HTMLElement} el\n * @param {Object} htOption QRCode Options\n *\u002F\n var Drawing = function (el, htOption) {\n this._bIsPainted = false;\n this._android = _getAndroid();\n\n this._htOption = htOption;\n this._elCanvas = document.createElement(\"canvas\");\n this._elCanvas.width = htOption.width;\n this._elCanvas.height = htOption.height;\n el.appendChild(this._elCanvas);\n this._el = el;\n this._oContext = this._elCanvas.getContext(\"2d\");\n this._bIsPainted = false;\n this._elImage = document.createElement(\"img\");\n this._elImage.alt = \"Scan me!\";\n this._elImage.style.display = \"none\";\n this._el.appendChild(this._elImage);\n this._bSupportDataURI = null;\n };\n\n \u002F**\n * Draw the QRCode\n *\n * @param {QRCode} oQRCode\n *\u002F\n Drawing.prototype.draw = function (oQRCode) {\n var _elImage = this._elImage;\n var _oContext = this._oContext;\n var _htOption = this._htOption;\n\n var nCount = oQRCode.getModuleCount();\n var nWidth = _htOption.width \u002F nCount;\n var nHeight = _htOption.height \u002F nCount;\n var nRoundedWidth = Math.round(nWidth);\n var nRoundedHeight = Math.round(nHeight);\n\n _elImage.style.display = \"none\";\n this.clear();\n\n for (var row = 0; row \u003C nCount; row++) {\n for (var col = 0; col \u003C nCount; col++) {\n var bIsDark = oQRCode.isDark(row, col);\n var nLeft = col * nWidth;\n var nTop = row * nHeight;\n _oContext.strokeStyle = bIsDark ? _htOption.colorDark : _htOption.colorLight;\n _oContext.lineWidth = 1;\n _oContext.fillStyle = bIsDark ? _htOption.colorDark : _htOption.colorLight;\n _oContext.fillRect(nLeft, nTop, nWidth, nHeight);\n\n \u002F\u002F 안티 앨리어싱 방지 처리\n _oContext.strokeRect(\n Math.floor(nLeft) + 0.5,\n Math.floor(nTop) + 0.5,\n nRoundedWidth,\n nRoundedHeight\n );\n\n _oContext.strokeRect(\n Math.ceil(nLeft) - 0.5,\n Math.ceil(nTop) - 0.5,\n nRoundedWidth,\n nRoundedHeight\n );\n }\n }\n\n this._bIsPainted = true;\n };\n\n \u002F**\n * Make the image from Canvas if the browser supports Data URI.\n *\u002F\n Drawing.prototype.makeImage = function () {\n if (this._bIsPainted) {\n _safeSetDataURI.call(this, _onMakeImage);\n }\n };\n\n \u002F**\n * Return whether the QRCode is painted or not\n *\n * @return {Boolean}\n *\u002F\n Drawing.prototype.isPainted = function () {\n return this._bIsPainted;\n };\n\n \u002F**\n * Clear the QRCode\n *\u002F\n Drawing.prototype.clear = function () {\n this._oContext.clearRect(0, 0, this._elCanvas.width, this._elCanvas.height);\n this._bIsPainted = false;\n };\n\n \u002F**\n * @private\n * @param {Number} nNumber\n *\u002F\n Drawing.prototype.round = function (nNumber) {\n if (!nNumber) {\n return nNumber;\n }\n\n return Math.floor(nNumber * 1000) \u002F 1000;\n };\n\n return Drawing;\n })();\n\n \u002F**\n * Get the type by string length\n *\n * @private\n * @param {String} sText\n * @param {Number} nCorrectLevel\n * @return {Number} type\n *\u002F\n function _getTypeNumber(sText, nCorrectLevel) {\n var nType = 1;\n var length = _getUTF8Length(sText);\n\n for (var i = 0, len = QRCodeLimitLength.length; i \u003C= len; i++) {\n var nLimit = 0;\n\n switch (nCorrectLevel) {\n case QRErrorCorrectLevel.L :\n nLimit = QRCodeLimitLength[i][0];\n break;\n case QRErrorCorrectLevel.M :\n nLimit = QRCodeLimitLength[i][1];\n break;\n case QRErrorCorrectLevel.Q :\n nLimit = QRCodeLimitLength[i][2];\n break;\n case QRErrorCorrectLevel.H :\n nLimit = QRCodeLimitLength[i][3];\n break;\n }\n\n if (length \u003C= nLimit) {\n break;\n } else {\n nType++;\n }\n }\n\n if (nType \u003E QRCodeLimitLength.length) {\n throw new Error(\"Too long data\");\n }\n\n return nType;\n }\n\n function _getUTF8Length(sText) {\n var replacedText = encodeURI(sText).toString().replace(\u002F\\%[0-9a-fA-F]{2}\u002Fg, 'a');\n return replacedText.length + (replacedText.length != sText ? 3 : 0);\n }\n\n \u002F**\n * @class QRCode\n * @constructor\n * @example\n * new QRCode(document.getElementById(\"test\"), \"http:\u002F\u002Fjindo.dev.naver.com\u002Fcollie\");\n *\n * @example\n * var oQRCode = new QRCode(\"test\", {\n * text : \"http:\u002F\u002Fnaver.com\",\n * width : 128,\n * height : 128\n * });\n *\n * oQRCode.clear(); \u002F\u002F Clear the QRCode.\n * oQRCode.makeCode(\"http:\u002F\u002Fmap.naver.com\"); \u002F\u002F Re-create the QRCode.\n *\n * @param {HTMLElement|String} el target element or 'id' attribute of element.\n * @param {Object|String} vOption\n * @param {String} vOption.text QRCode link data\n * @param {Number} [vOption.width=256]\n * @param {Number} [vOption.height=256]\n * @param {String} [vOption.colorDark=\"#000000\"]\n * @param {String} [vOption.colorLight=\"#ffffff\"]\n * @param {QRCode.CorrectLevel} [vOption.correctLevel=QRCode.CorrectLevel.H] [L|M|Q|H]\n *\u002F\n exports.QRCode = function (el, vOption) {\n this._htOption = {\n width : 256,\n height : 256,\n typeNumber : 4,\n colorDark : \"#000000\",\n colorLight : \"#ffffff\",\n correctLevel : QRErrorCorrectLevel.H\n };\n\n if (typeof vOption === 'string') {\n vOption\t= {\n text : vOption\n };\n }\n\n \u002F\u002F Overwrites options\n if (vOption) {\n for (var i in vOption) {\n this._htOption[i] = vOption[i];\n }\n }\n\n if (typeof el == \"string\") {\n el = document.querySelector(el);\n }\n\n if (this._htOption.useSVG) {\n Drawing = svgDrawer;\n }\n\n this._android = _getAndroid();\n this._el = el;\n this._oQRCode = null;\n this._oDrawing = new Drawing(this._el, this._htOption);\n\n if (this._htOption.text) {\n this.makeCode(this._htOption.text);\n }\n };\n\n \u002F**\n * Make the QRCode\n *\n * @param {String} sText link data\n *\u002F\n exports.QRCode.prototype.makeCode = function (sText) {\n this._oQRCode = new QRCodeModel(_getTypeNumber(sText, this._htOption.correctLevel), this._htOption.correctLevel);\n this._oQRCode.addData(sText);\n this._oQRCode.make();\n this._el.title = sText;\n this._oDrawing.draw(this._oQRCode);\n this.makeImage();\n };\n\n \u002F**\n * Make the Image from Canvas element\n * - It occurs automatically\n * - Android below 3 doesn't support Data-URI spec.\n *\n * @private\n *\u002F\n exports.QRCode.prototype.makeImage = function () {\n if (typeof this._oDrawing.makeImage == \"function\" && (!this._android || this._android \u003E= 3)) {\n this._oDrawing.makeImage();\n }\n };\n\n \u002F**\n * Clear the QRCode\n *\u002F\n exports.QRCode.prototype.clear = function () {\n this._oDrawing.clear();\n };\n\n \u002F**\n * @name QRCode.CorrectLevel\n *\u002F\n exports.QRCode.CorrectLevel = QRErrorCorrectLevel;\n })();\n\n const EVENT = {\n AGENT_CHANGE:'agent_change',\n AGENT_LEFT:'agent_left',\n AGENT_JOIN:'agent_join',\n CAP_CHANGE:'capability_change',\n TIMELINE_EVENT:'timeline_change'\n };\n\n class Orkestra {\n\n constructor(options){\n this.userObserver = new Subject();\n this.userPrivObserver = new Subject();\n this.timeObserver = new Subject();\n this.appObserver = new Subject();\n this.userPrivObservable =new Observable((observer) =\u003E { this.userPrivObserver.subscribe(observer);});\n this.userObservable =new Observable((observer) =\u003E { this.userObserver.subscribe(observer);});\n this.appObservable =new Observable((observer) =\u003E { this.appObserver.subscribe(observer);});\n this.timerObservable =new Observable((observer) =\u003E { this.timeObserver.subscribe(observer);});\n this.readyObservable =new Observable((observer) =\u003E { this.readyObserver = observer;});\n this.readyPrivObservable =new Observable((observer) =\u003E { this.readyPrivObserver = observer;});\n this.master = options.master;\n this.users = new Map();\n this.users_priv = new Map();\n this.appCtx = null;\n this.options = options;\n this.privateChannel = options.privateChannel;\n this.appData = new Object();\n this._ui = UI(this);\n \u002F\u002F Whether if private channel enable not listen for global user context changes\n if (this.privateChannel === true) {\n let user = new User (-1,\"me\",options.profile || \"slave\",{});\n this.users_priv.set(user.name,user);\n }\n if (this.master === true){\n let admin = new User(-1,\"me\",options.profile || \"master\",{});\n this.users.set(admin.name,admin);\n }\n else {\n if (this.master === undefined){\n let user = new User (-1,\"me\",options.profile || \"broadcast\",{});\n this.users.set(user.name,user);\n }\n else {\n let user = new User (-1,\"me\",options.profile || \"slave\",{});\n this.users.set(user.name,user);\n\n }\n }\n\n if (options.url){\n this.mappServ = MappingService(options.url,{maxTimeout:8000});\n this.privMappServ = MappingService(options.url,{maxTimeout:8000});\n }\n else\n this.mappServ = MappingService({maxTimeout:8000});\n this.setPublicConnection(options);\n if (this.privateChannel === true)\n this.setPrivateConnection(options);\n\n\n\n }\n setPublicConnection(options){\n this.mappServ.getGroupMapping(options.channel).then((channel)=\u003E{\n this.appCtx = ApplicationContext(channel.group,{autoPresence:true,autoClean:true,agentid:options.agentid});\n console.info(\"SharedState URL\",channel.group);\n console.info(\"Public connection init\",new Date());\n var init = setInterval(()=\u003E{\n\n\n if (this.appCtx.ready()){\n console.info(\"Public connection ready\",new Date());\n clearInterval(init);\n this.timerObservable.subscribe(evt=\u003E{\n this.rules && this.rules.forEach((r,i)=\u003E{\n if (r.options.listenEvent===\"timeline\"){\n let event_ = {type:EVENT.TIMELINE_EVENT,time:evt.target.currentTime,data:r.options.data};\n r.cb(event_,this._ui.components,this.users,this.options);\n }\n });\n });\n this.userObservable.subscribe(evt=\u003E{\n var d = [];\n this.rules && this.rules.forEach((r,i)=\u003E{\n d.push({priority:i,decision:r.cb(evt,this._ui.components,this.users,this.options)});\n });\n this.resolveConflict(d);\n });\n this.appObservable.subscribe(evt =\u003E {\n var d = [];\n this.rules && this.rules.forEach((r,i)=\u003E{\n d.push({priority:i,decision:r.cb(evt,this._ui.components,this.users,this.options)});\n });\n this.resolveConflict(d);\n });\n this.appCtx.on('agentchange',(chg)=\u003E{\n this.onAgentChange(chg);\n });\n if (this.privateChannel === false){\n this.appCtx.me.load(DeviceProfile(options.url));\n\n }\n this.appCtx.me.load(UserProfile(this));\n this.appCtx.me.load(MasterSlaveProfile(this));\n this.appCtx.me.load(UserData());\n\n this.readyObserver.next({status:'ready'});\n\n\n this.appCtx.keys().forEach((key)=\u003E{\n this.appCtx.on(key,this.onAppAttrChange.bind(this));\n });\n this.appCtx.keys().forEach(key=\u003E{\n this.appData[key] = this.appCtx.getItem(key);\n this.appObserver.next({key:key,value:this.appData[key]});\n });\n\n\n }\n },30);\n\n });\n }\n setPrivateConnection(options){\n this.privGroup = URI.getUrlVar('group');\n if (!this.privGroup){\n this.privGroup = URI.createToken();\n }\n this.privMappServ.getGroupMapping(options.channel+\"_\"+this.privGroup).then((channel)=\u003E{\n this.appCtxPriv = ApplicationContext(channel.group,{autoPresence:true,autoClean:true});\n console.info(\"SharedState URL\",channel.group);\n console.info(\"Private connection init\",new Date());\n var init = setInterval(()=\u003E{\n\n if (this.appCtxPriv.ready()){\n clearInterval(init);\n console.info(\"Private connection ready\",new Date());\n\n this.userPrivObservable.subscribe(evt=\u003E{\n var d = [];\n this.rules && this.rules.forEach((r,i)=\u003E{\n d.push({priority:i,decision:r.cb(evt,this._ui.components,this.users_priv,this.options)});\n });\n this.resolveConflict(d);\n });\n \u002F\u002F this.readyPrivObserver.next({status:'ready'});\n\n this.appCtxPriv.on('agentchange',(chg)=\u003E{\n this.onAgentPrivChange(chg);\n });\n this.appCtxPriv.me.load(DeviceProfile(options.url));\n this.appCtxPriv.me.load(MasterSlaveProfile(this));\n this.appCtxPriv.me.load(UserProfile(this));\n\n\n\n }\n },15);\n\n });\n }\n onAgentChange(chg){\n console.log(\"agent change\",chg);\n if (!this.userObserver){\n console.warn(\"There is not subscribed to receive agent change\");\n return -1;\n }\n if (chg.agentContext){\n if (!this.existsAgent(chg.agentid)){\n\n if (this.appCtx.getAgents().self.agentid === chg.agentid){\n let user = this.users.get(\"me\");\n user.agentid = chg.agentid;\n user.context = chg.agentContext;\n }\n else {\n this.users.set(chg.agentid, new User(chg.agentid,chg.agentid,'',{}));\n let user = this.users.get(chg.agentid);\n user.context = chg.agentContext;\n }\n this.userObserver.next({evt:EVENT.AGENT_JOIN,type:\"agentChange\",data:chg});\n\n }\n\n if (chg.diff.keys.length\u003E0)\n {\n\n\n this.enableInstrument(chg.diff.keys,chg.agentContext,chg,this.master);\n this.userObserver.next({evt:EVENT.AGENT_CHANGE,type:\"agentChange\",data:chg});\n\n }\n\n\n }\n else {\n this.users.delete(chg.agentid);\n this.userObserver.next({evt:EVENT.AGENT_LEFT,data:chg});\n\n }\n\n }\n onAgentPrivChange(chg){\n console.log(\"Priv agent change\",chg);\n if (!this.userPrivObserver){\n console.warn(\"There is not subscribed to receive agent change\");\n return -1;\n }\n if (chg.agentContext){\n if (!this.existsAgent(chg.agentid,true)){\n\n if (this.appCtxPriv.getAgents().self.agentid === chg.agentid){\n let user = this.users_priv.get(\"me\");\n user.agentid = chg.agentid;\n user.context = chg.agentContext;\n }\n else {\n this.users_priv.set(chg.agentid, new User(chg.agentid,chg.agentid,'',{}));\n let user = this.users_priv.get(chg.agentid);\n user.context = chg.agentContext;\n }\n this.userPrivObserver.next({evt:EVENT.AGENT_JOIN,type:\"agentChange\",data:chg});\n\n }\n\n if (chg.diff.keys.length\u003E0)\n {\n\n\n this.enablePrivInstrument(chg.diff.keys,chg.agentContext,chg,this.master);\n this.userPrivObserver.next({evt:EVENT.AGENT_CHANGE,type:\"agentChange\",data:chg});\n\n }\n\n\n }\n else {\n this.users_priv.delete(chg.agentid);\n this.userPrivObserver.next({evt:EVENT.AGENT_LEFT,data:chg});\n\n }\n\n }\n onAppAttrChange(chg){\n if (!this.appObserver) {\n console.warn(\"There is not subscribed to receive agent change\");\n return -1;\n }\n this.appData[chg.key] = chg.value;\n this.appObserver.next({type:\"appAttrChange\",key:chg.key,data:chg});\n }\n use(rule,options){\n this.rules = this.rules || [];\n this.rules.push({name:rule.name,cb:rule(),options:options});\n }\n ui(cb){\n if (cb)\n this._ui.subscribe(cb);\n else return this._ui;\n }\n\n updateUserData(user,key,value){\n let data = this.getUserData(user,\"userData\");\n if (data && typeof value !=\"string\")\n for (let d in value){\n data[d] = value[d];\n }\n else if (typeof value ===\"string\"){\n data[key]= value;\n }\n else data = value;\n if (user == this.users.get('me').agentid) this.users.get('me').context.setItem(\"userData\",data);\n\n else this.users.get(user).context.setItem(\"userData\",data);\n }\n\n setUserData(user,key,value){\n let data = this.getUserData(user,\"userData\");\n if (data)\n for (let d in value){\n if (!(d in data)) data[d] = value[d];\n }\n else data = value;\n if (user == this.users.get('me').agentid) this.users.get('me').context.setItem(key,data);\n\n else this.users.get(user).context.setItem(key,data);\n }\n getUserData(user,key){\n if (user == this.users.get('me').agentid) return this.users.get('me').context.getItem(key);\n\n else\n return this.users.get(user).context.getItem(key) || {};\n }\n\n getUsers(){\n return JSON.stringify(Array.from(this.users.entries()));\n }\n getPrivateUsers(){\n return JSON.stringify(Array.from(this.users_priv.entries()));\n }\n getAppData(){\n return JSON.stringify(this.appData);\n }\n getUsersByProfile(){\n \u002F\u002Fthis.appCtx.getAgents().self.agentid\n }\n data(name,script,args){\n var x = setInterval(()=\u003E{\n if (this.appCtx && this.appCtx.me){\n clearInterval(x);\n this.appCtx.me.load(script(args[0]));\n }\n },5);\n\n\n\n }\n existsAgent(agid,priv){\n if (priv && priv == true) return (this.users_priv.get(agid)!==undefined || (this.appCtxPriv.me.agentid === this.users_priv.get('me').agentid));\n return (this.users.get(agid)!==undefined || (this.appCtx.me.agentid === this.users.get('me').agentid));\n }\n setAppAttribute(key,value){\n if (this.appCtx.keys().indexOf(key)===-1) this.subscribe(key);\n this.appCtx.setItem(key,value);\n }\n getAppAttribute(key){\n this.appCtx.getItem(key);\n }\n subscribe(key){\n this.appCtx.on(key,this.onAppAttrChange.bind(this));\n }\n resolveConflict(deploys){\n var maxPriority = 0, result = null;\n\n for(var i=0; i\u003Cdeploys.length; i++){\n if(deploys[i].priority \u003E maxPriority) {\n maxPriority = deploys[i].priority;\n if (result === null)\n result = deploys[i].decision;\n else {\n result.forEach(function(c){\n deploys[i].decision.forEach(function(c2){\n if (c.cmp === c2.cmp){\n c.show = c2.show;\n }\n });\n });\n }\n }\n }\n if (result) result.forEach((c)=\u003E{\n if (c.show === true) document.querySelector('#'+c).style.display = \"block\";\n else document.querySelector('#'+c).style.display = \"none\";\n });\n }\n getUserObservable(){ return this.userObservable};\n me(){\n return this.appCtx.me;\n }\n getMyAgentId(){\n return this.users.get('me').agentid;\n }\n enableInstrument(cap,context,chg,master){\n cap.forEach((_cap)=\u003E{\n context.on(_cap,(k,v)=\u003E{\n if (context.agentid == this.appCtx.getAgents().self.agentid){\n let user = this.users.get('me');\n user[k] = v;\n console.log(\"me\",user);\n this.userObserver.next({evt:EVENT.CAP_CHANGE,data:{\"agentid\":context.agentid,\"key\":k,\"value\":v}});\n\n }\n });\n });\n if (master===true || master === undefined){\n cap.forEach((_cap)=\u003E{\n context.on(_cap,(k,v)=\u003E{\n\n let user = '';\n\n if (context.agentid == this.appCtx.getAgents().self.agentid)\n user =this.users.get('me');\n else\n user =this.users.get(context.agentid);\n user.capacities[k] = v;\n if (k === \"userProfile\" && v !== \"supported\") user.profile = v;\n\n this.userObserver.next({evt:EVENT.CAP_CHANGE,data:{\"agentid\":context.agentid,\"key\":k,\"value\":v}});\n });\n });\n }\n else {\n let ind = cap.indexOf(\"masterSlave\");\n if (ind !==-1){\n let user = '';\n context.on(cap[ind],(k,v)=\u003E{\n if (context.agentid == this.appCtx.getAgents().self.agentid)\n user =this.users.get('me');\n else\n user =this.users.get(context.agentid);\n user.capacities[k] = v;\n this.userObserver.next({evt:EVENT.CAP_CHANGE,data:{\"key\":k,\"value\":v}});\n if (k === \"masterSlave\" && v === true){\n\n context.keys().forEach((_cap)=\u003E{\n context.on(_cap,(k,v)=\u003E{\n let user = '';\n\n if (context.agentid == this.appCtx.getAgents().self.agentid)\n user =this.users.get('me');\n else\n user =this.users.get(context.agentid);\n user.capacities[k] = v;\n this.userObserver.next({evt:EVENT.CAP_CHANGE,data:{\"agentid\":context.agentid,\"key\":k,\"value\":v}});\n });\n });\n }\n if (k === \"userProfile\" && v !== \"supported\"){\n if (context.agentid == this.appCtx.getAgents().self.agentid)\n user =this.users.get('me');\n else\n user =this.users.get(context.agentid);\n user.profile = v;\n\n }\n if (k === \"userData\" && v !== \"supported\"){\n if (context.agentid == this.appCtx.getAgents().self.agentid)\n user =this.users.get('me');\n else\n user =this.users.get(context.agentid);\n user.data = v;\n\n }\n\n\n });\n }\n }\n }\n enablePrivInstrument(cap,context,chg,master){\n cap.forEach((_cap)=\u003E{\n context.on(_cap,(k,v)=\u003E{\n let user = '';\n\n if (context.agentid == this.appCtxPriv.getAgents().self.agentid)\n user =this.users_priv.get('me');\n else\n user =this.users_priv.get(context.agentid);\n user.capacities[k] = v;\n this.userPrivObserver.next({evt:EVENT.CAP_CHANGE,data:{\"agentid\":context.agentid,\"key\":k,\"value\":v}});\n });\n });\n\n\n }\n syncObjects(obj,channel){\n let motion = new Motion(this.options.url,channel);\n motion.start();\n let ctrl = motion.addObject(obj,0);\n return ctrl;\n }\n\n enableSequencer(obj,channel){\n let motion = new Motion(this.options.url,channel);\n motion.start();\n let ctrl = motion.addObject(obj,0);\n motion.addEventListener('motionReady',()=\u003E{\n console.log(\"MOTION SOCKET READY\");\n ctrl.addEventListener('timeupdate',(e)=\u003E{\n this.timeObserver.next(e);\n });\n });\n\n\n return ctrl;\n }\n enableQRcode(el){\n let server = this.options.url.slice(0, -1);\n URI.shortURL(server,location.protocol+\"\u002F\u002F\"+location.host+\"\u002F?master=\"+this.master+\"&group=\"+this.privGroup).then((_url)=\u003E{\n console.log(\"SHORTURL\",_url.response);\n let cont = document.createElement('div');\n document.querySelector(el).appendChild(cont);\n new exports.QRCode(cont,{\n text: _url.response,\n width: 128,\n height: 128,\n colorDark : \"#000000\",\n colorLight : \"#ffffff\",\n correctLevel : exports.QRCode.CorrectLevel.H\n });\n let anchor = document.createElement('a');\n anchor.href = _url.response;\n anchor.innerHTML = _url.response;\n cont.appendChild(anchor);\n });\n }\n }\n\n class WCUserTable extends HTMLElement {\n static get observedAttributes () {\n return ['users', 'no-headers'];\n }\n\n attributeChangedCallback (name, oldValue, newValue) {\n if (!this.__initialized) { return; }\n if (oldValue !== newValue) {\n if (name === 'no-headers') {\n this.noHeaders = newValue;\n } else {\n this[name] = newValue;\n }\n }\n }\n\n get users () { return this.getAttribute('users'); }\n set users (value) {\n this.setAttribute('users', value);\n this.render();\n }\n\n get value () { return this.__data; }\n set value (value) {\n this.setValue(value);\n }\n\n get noHeaders () { return this.hasAttribute('no-headers'); }\n set noHeaders (value) {\n const noHeaders = this.hasAttribute('no-headers');\n if (noHeaders) {\n this.setAttribute('no-headers', '');\n } else {\n this.removeAttribute('no-headers');\n }\n this.setNoHeaders(noHeaders);\n }\n\n constructor () {\n super();\n\n }\n\n async connectedCallback () {\n if (this.hasAttribute('no-headers')) {\n this.__headers = false;\n }\n\n if (this.hasAttribute('users')) {\n this.setUsers();\n }\n\n this.__initialized = true;\n }\n\n\n\n\n setValue (value) {\n this.__data = parse(value);\n this.render();\n }\n\n setNoHeaders (noHeaders) {\n this.__headers = !noHeaders;\n this.render();\n }\n\n render () {\n if (this.users === \"undefined\") return;\n if (!this.__table) this.__table = document.createElement('table');\n const div = document.createElement('h2');\n div.innerHTML = \"USERS DATA\";\n div.id =\"user_div\";\n this.__headers = [\"Id\",\"Name\",\"Profile\",\"Capacity\"];\n this.__table.innerHTML = \"\";\n if (this.__headers) {\n\n const thead = document.createElement('thead');\n const tr = document.createElement('tr');\n this.__headers.forEach(header =\u003E {\n const th = document.createElement('th');\n th.innerText = header;\n tr.appendChild(th);\n });\n thead.append(tr);\n this.__table.appendChild(thead);\n }\n\n const tbody = document.createElement('tbody');\n JSON.parse(this.users).forEach(row =\u003E {\n const tr = document.createElement('tr');\n for (let n in row[1])\n {\n const td = document.createElement('td');\n if (n==\"capacities\") td.innerText = JSON.stringify(row[1][n]);\n else td.innerText = row[1][n];\n tr.appendChild(td);\n }\n tbody.appendChild(tr);\n });\n this.__table.appendChild(tbody);\n\n \u002F\u002F this.removeChild(document.querySelector(\"#table\"));\n \u002F\u002Fthis.__table = table;\n\n \u002F\u002F console.log(\"HERE\",this.querySelector(\"#user_div\"));\n if (!this.querySelector(\"#user_div\")) this.appendChild(div);\n if (!this.querySelector(\"#table\")) this.appendChild(this.__table);\n\n\n\n }\n }\n\n customElements.define('user-table', WCUserTable);\n\n class WCAppTable extends HTMLElement {\n static get observedAttributes () {\n return ['datos', 'no-headers'];\n }\n\n attributeChangedCallback (name, oldValue, newValue) {\n if (!this.__initialized) { return; }\n if (oldValue !== newValue) {\n if (name === 'no-headers') {\n this.noHeaders = newValue;\n } else {\n this[name] = newValue;\n }\n }\n }\n\n get datos () { return this.getAttribute('datos'); }\n set datos (value) {\n this.setAttribute('datos', value);\n this.render();\n }\n\n get value () { return this.__data; }\n set value (value) {\n this.setValue(value);\n }\n\n get noHeaders () { return this.hasAttribute('no-headers'); }\n set noHeaders (value) {\n const noHeaders = this.hasAttribute('no-headers');\n if (noHeaders) {\n this.setAttribute('no-headers', '');\n } else {\n this.removeAttribute('no-headers');\n }\n this.setNoHeaders(noHeaders);\n }\n\n constructor () {\n super();\n\n }\n\n async connectedCallback () {\n if (this.hasAttribute('no-headers')) {\n this.__headers = false;\n }\n\n if (this.hasAttribute('datos')) {\n this.setUsers();\n }\n\n this.__initialized = true;\n }\n\n\n\n\n setValue (value) {\n this.__data = parse(value);\n this.render();\n }\n\n setNoHeaders (noHeaders) {\n this.__headers = !noHeaders;\n this.render();\n }\n\n render () {\n if (this.datos === \"undefined\") return;\n if (!this.__table) this.__table = document.createElement('table');\n const div = document.createElement('h2');\n div.innerHTML = \"APP DATA\";\n div.id =\"app_div\";\n this.__headers = [\"Key\",\"Value\"];\n this.__table.innerHTML=\"\";\n if (this.__headers) {\n const thead = document.createElement('thead');\n const tr = document.createElement('tr');\n this.__headers.forEach(header =\u003E {\n const th = document.createElement('th');\n th.innerText = header;\n tr.appendChild(th);\n });\n thead.append(tr);\n this.__table.appendChild(thead);\n }\n\n const tbody = document.createElement('tbody');\n var d = JSON.parse(this.datos);\n Object.keys(d).forEach(row =\u003E {\n const tr = document.createElement('tr');\n\n const td = document.createElement('td');\n td.innerText = row;\n tr.appendChild(td);\n const td1 = document.createElement('td');\n td1.innerText = JSON.stringify(d[row]);\n tr.appendChild(td1);\n\n tbody.appendChild(tr);\n });\n this.__table.id = \"table\";\n this.__table.appendChild(tbody);\n\n\n\n\n if (!this.querySelector(\"#app_div\")) this.appendChild(div);\n if (!this.querySelector(\"#table\")) this.appendChild(this.__table);\n\n }\n }\n\n customElements.define('app-table', WCAppTable);\n\n function Divided (){\n\n var render = function(ev,cmp){\n var setClasses = function(_cmp,N){\n document.querySelector('orkestra-ui').className=\"container\";\n console.log(\"N=\u003E\",N);\n if (N===9 || N==6){\n _cmp.className = \"item69\";\n }\n else if (N==4 || N==2 || N===3){\n _cmp.className = \"item24\";\n }\n else if (N==1){\n _cmp.className = \"item21\";\n }\n\n };\n var visible = Array.from(cmp).filter( (x,i,len) =\u003E\n {\n\n return x.style.display!=\"none\"\n });\n visible.forEach((cmp,o,len)=\u003E{\n setClasses(cmp,len.length);\n });\n\n\n };\n\n return render;\n\n }\n\n function Byname (){\n var deploy = function (evt,cmp,users,options){\n let decision = [];\n \u002F\u002F cmp[0].style.display=\"none\";\n for (let n in cmp){\n let c = cmp[n];\n if (!c.style) return;\n if ('master' === users.get('me').profile && c.nodeName===\"USER-TABLE\") c.style.display = 'block';\n else if ( c.nodeName===\"APP-TABLE\") c.style.display = 'block';\n else c.style.display = 'none';\n decision.push({cmp:c.id,show:c.style.display==\"block\"});\n }\n return decision;\n };\n return deploy;\n\n }\n\n function Bytimeline (){\n var self = {};\n self.name =\"bytimeline\";\n self.data = [];\n var deploy = function (evt,cmp,users,options){\n let decision = [];\n let tnpEv = {};\n let isForMe = false;\n if (evt.key===\"timeline\"){\n if (evt.type===\"appAttrChange\"){\n if (evt.data.value.applyTo=== users.get(\"me\").agentid || (!self.applyTo && evt.data.value.applyTo===\"all\")){\n self.data = evt.data.value.componentStatus;\n tnpEv[\"applyTo\"] = evt.data.value.applyTo;\n tnpEv[\"data\" ] = self.data;\n if (evt.data.value.applyTo!==\"all\") self.applyTo = evt.data.value.applyTo;\n isForMe = true;\n }\n }\n else {\n if (evt.value.applyTo=== users.get(\"me\").agentid || (!self.applyTo && evt.value.applyTo===\"all\")){\n self.data = evt.value.componentStatus;\n tnpEv[\"data\" ] = self.data;\n tnpEv[\"applyTo\"] = evt.applyTo;\n if( evt.value.applyTo!==\"all\") self.applyTo = evt.applyTo;\n isForMe = true;\n\n }\n\n }\n if (isForMe){\n tnpEv[\"time\"] = self.time;\n let cmps = getTimeEvent(tnpEv);\n for (let n=0;n\u003Ccmp.length;n++){\n let c = cmp[n];\n if (!c.style) continue;\n let index = cmps.map((x)=\u003E{return x.id}).indexOf(parseInt(c.id));\n if (index\u003E-1){\n c.input = cmps[index].source;\n c.style.display = 'block';\n }\n else\n c.style.display = 'none';\n decision.push({cmp:c.id,show:c.style.display==\"block\"});\n }\n\n }\n }\n\n\n \u002F* TODO :rewrite evt info is not clean *\u002F\n\n else if (evt.type==\"timeline_change\"){\n if (evt.data && typeof evt.data ==\"string\" ) tnpEv[\"data\"] = self.data;\n self.time = evt.time;\n tnpEv[\"time\"]= evt.time;\n let cmps = getTimeEvent(tnpEv);\n for (let n=0;n\u003Ccmp.length;n++){\n let c = cmp[n];\n if (!c.style) continue;\n let index = cmps.map((x)=\u003E{return x.id}).indexOf(parseInt(c.id));\n if (index\u003E-1){\n c.input = cmps[index].source;\n c.style.display = 'block';\n }\n else\n c.style.display = 'none';\n decision.push({cmp:c.id,show:c.style.display==\"block\"});\n }\n }\n return decision;\n };\n function getTimeEvent(evt){\n console.log(evt);\n let data = evt.data || [];\n let time = Math.round(evt.time);\n let inIntervalCmps = data.filter((d)=\u003E{\n if (d.slots[time] === 1) return true;\n return false;\n });\n if (inIntervalCmps.length \u003E0){\n inIntervalCmps = inIntervalCmps.map((c)=\u003E{\n return {id:c.id,source:c.source};\n });\n }\n return inIntervalCmps;\n }\n return deploy;\n\n }\n\n \u002F*\n * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n * Use of this source code is governed by a BSD-style license\n * that can be found in the LICENSE file in the root of the source\n * tree.\n *\u002F\n\n let logDisabled_ = true;\n let deprecationWarnings_ = true;\n\n \u002F**\n * Extract browser version out of the provided user agent string.\n *\n * @param {!string} uastring userAgent string.\n * @param {!string} expr Regular expression used as match criteria.\n * @param {!number} pos position in the version string to be returned.\n * @return {!number} browser version.\n *\u002F\n function extractVersion(uastring, expr, pos) {\n const match = uastring.match(expr);\n return match && match.length \u003E= pos && parseInt(match[pos], 10);\n }\n\n \u002F\u002F Wraps the peerconnection event eventNameToWrap in a function\n \u002F\u002F which returns the modified event object (or false to prevent\n \u002F\u002F the event).\n function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) {\n if (!window.RTCPeerConnection) {\n return;\n }\n const proto = window.RTCPeerConnection.prototype;\n const nativeAddEventListener = proto.addEventListener;\n proto.addEventListener = function(nativeEventName, cb) {\n if (nativeEventName !== eventNameToWrap) {\n return nativeAddEventListener.apply(this, arguments);\n }\n const wrappedCallback = (e) =\u003E {\n const modifiedEvent = wrapper(e);\n if (modifiedEvent) {\n cb(modifiedEvent);\n }\n };\n this._eventMap = this._eventMap || {};\n this._eventMap[cb] = wrappedCallback;\n return nativeAddEventListener.apply(this, [nativeEventName,\n wrappedCallback]);\n };\n\n const nativeRemoveEventListener = proto.removeEventListener;\n proto.removeEventListener = function(nativeEventName, cb) {\n if (nativeEventName !== eventNameToWrap || !this._eventMap\n || !this._eventMap[cb]) {\n return nativeRemoveEventListener.apply(this, arguments);\n }\n const unwrappedCb = this._eventMap[cb];\n delete this._eventMap[cb];\n return nativeRemoveEventListener.apply(this, [nativeEventName,\n unwrappedCb]);\n };\n\n Object.defineProperty(proto, 'on' + eventNameToWrap, {\n get() {\n return this['_on' + eventNameToWrap];\n },\n set(cb) {\n if (this['_on' + eventNameToWrap]) {\n this.removeEventListener(eventNameToWrap,\n this['_on' + eventNameToWrap]);\n delete this['_on' + eventNameToWrap];\n }\n if (cb) {\n this.addEventListener(eventNameToWrap,\n this['_on' + eventNameToWrap] = cb);\n }\n },\n enumerable: true,\n configurable: true\n });\n }\n\n function disableLog(bool) {\n if (typeof bool !== 'boolean') {\n return new Error('Argument type: ' + typeof bool +\n '. Please use a boolean.');\n }\n logDisabled_ = bool;\n return (bool) ? 'adapter.js logging disabled' :\n 'adapter.js logging enabled';\n }\n\n \u002F**\n * Disable or enable deprecation warnings\n * @param {!boolean} bool set to true to disable warnings.\n *\u002F\n function disableWarnings(bool) {\n if (typeof bool !== 'boolean') {\n return new Error('Argument type: ' + typeof bool +\n '. Please use a boolean.');\n }\n deprecationWarnings_ = !bool;\n return 'adapter.js deprecation warnings ' + (bool ? 'disabled' : 'enabled');\n }\n\n function log() {\n if (typeof window === 'object') {\n if (logDisabled_) {\n return;\n }\n if (typeof console !== 'undefined' && typeof console.log === 'function') {\n console.log.apply(console, arguments);\n }\n }\n }\n\n \u002F**\n * Shows a deprecation warning suggesting the modern and spec-compatible API.\n *\u002F\n function deprecated(oldMethod, newMethod) {\n if (!deprecationWarnings_) {\n return;\n }\n console.warn(oldMethod + ' is deprecated, please use ' + newMethod +\n ' instead.');\n }\n\n \u002F**\n * Browser detector.\n *\n * @return {object} result containing browser and version\n * properties.\n *\u002F\n function detectBrowser(window) {\n const {navigator} = window;\n\n \u002F\u002F Returned result object.\n const result = {browser: null, version: null};\n\n \u002F\u002F Fail early if it's not a browser\n if (typeof window === 'undefined' || !window.navigator) {\n result.browser = 'Not a browser.';\n return result;\n }\n\n if (navigator.mozGetUserMedia) { \u002F\u002F Firefox.\n result.browser = 'firefox';\n result.version = extractVersion(navigator.userAgent,\n \u002FFirefox\\\u002F(\\d+)\\.\u002F, 1);\n } else if (navigator.webkitGetUserMedia ||\n (window.isSecureContext === false && window.webkitRTCPeerConnection &&\n !window.RTCIceGatherer)) {\n \u002F\u002F Chrome, Chromium, Webview, Opera.\n \u002F\u002F Version matches Chrome\u002FWebRTC version.\n \u002F\u002F Chrome 74 removed webkitGetUserMedia on http as well so we need the\n \u002F\u002F more complicated fallback to webkitRTCPeerConnection.\n result.browser = 'chrome';\n result.version = extractVersion(navigator.userAgent,\n \u002FChrom(e|ium)\\\u002F(\\d+)\\.\u002F, 2);\n } else if (navigator.mediaDevices &&\n navigator.userAgent.match(\u002FEdge\\\u002F(\\d+).(\\d+)$\u002F)) { \u002F\u002F Edge.\n result.browser = 'edge';\n result.version = extractVersion(navigator.userAgent,\n \u002FEdge\\\u002F(\\d+).(\\d+)$\u002F, 2);\n } else if (window.RTCPeerConnection &&\n navigator.userAgent.match(\u002FAppleWebKit\\\u002F(\\d+)\\.\u002F)) { \u002F\u002F Safari.\n result.browser = 'safari';\n result.version = extractVersion(navigator.userAgent,\n \u002FAppleWebKit\\\u002F(\\d+)\\.\u002F, 1);\n result.supportsUnifiedPlan = window.RTCRtpTransceiver &&\n 'currentDirection' in window.RTCRtpTransceiver.prototype;\n } else { \u002F\u002F Default fallthrough: not supported.\n result.browser = 'Not a supported browser.';\n return result;\n }\n\n return result;\n }\n\n \u002F**\n * Checks if something is an object.\n *\n * @param {*} val The something you want to check.\n * @return true if val is an object, false otherwise.\n *\u002F\n function isObject$1(val) {\n return Object.prototype.toString.call(val) === '[object Object]';\n }\n\n \u002F**\n * Remove all empty objects and undefined values\n * from a nested object -- an enhanced and vanilla version\n * of Lodash's `compact`.\n *\u002F\n function compactObject(data) {\n if (!isObject$1(data)) {\n return data;\n }\n\n return Object.keys(data).reduce(function(accumulator, key) {\n const isObj = isObject$1(data[key]);\n const value = isObj ? compactObject(data[key]) : data[key];\n const isEmptyObject = isObj && !Object.keys(value).length;\n if (value === undefined || isEmptyObject) {\n return accumulator;\n }\n return Object.assign(accumulator, {[key]: value});\n }, {});\n }\n\n \u002F* iterates the stats graph recursively. *\u002F\n function walkStats(stats, base, resultSet) {\n if (!base || resultSet.has(base.id)) {\n return;\n }\n resultSet.set(base.id, base);\n Object.keys(base).forEach(name =\u003E {\n if (name.endsWith('Id')) {\n walkStats(stats, stats.get(base[name]), resultSet);\n } else if (name.endsWith('Ids')) {\n base[name].forEach(id =\u003E {\n walkStats(stats, stats.get(id), resultSet);\n });\n }\n });\n }\n\n \u002F* filter getStats for a sender\u002Freceiver track. *\u002F\n function filterStats(result, track, outbound) {\n const streamStatsType = outbound ? 'outbound-rtp' : 'inbound-rtp';\n const filteredResult = new Map();\n if (track === null) {\n return filteredResult;\n }\n const trackStats = [];\n result.forEach(value =\u003E {\n if (value.type === 'track' &&\n value.trackIdentifier === track.id) {\n trackStats.push(value);\n }\n });\n trackStats.forEach(trackStat =\u003E {\n result.forEach(stats =\u003E {\n if (stats.type === streamStatsType && stats.trackId === trackStat.id) {\n walkStats(result, stats, filteredResult);\n }\n });\n });\n return filteredResult;\n }\n\n \u002F*\n * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n * Use of this source code is governed by a BSD-style license\n * that can be found in the LICENSE file in the root of the source\n * tree.\n *\u002F\n const logging = log;\n\n function shimGetUserMedia(window) {\n const navigator = window && window.navigator;\n\n if (!navigator.mediaDevices) {\n return;\n }\n\n const browserDetails = detectBrowser(window);\n\n const constraintsToChrome_ = function(c) {\n if (typeof c !== 'object' || c.mandatory || c.optional) {\n return c;\n }\n const cc = {};\n Object.keys(c).forEach(key =\u003E {\n if (key === 'require' || key === 'advanced' || key === 'mediaSource') {\n return;\n }\n const r = (typeof c[key] === 'object') ? c[key] : {ideal: c[key]};\n if (r.exact !== undefined && typeof r.exact === 'number') {\n r.min = r.max = r.exact;\n }\n const oldname_ = function(prefix, name) {\n if (prefix) {\n return prefix + name.charAt(0).toUpperCase() + name.slice(1);\n }\n return (name === 'deviceId') ? 'sourceId' : name;\n };\n if (r.ideal !== undefined) {\n cc.optional = cc.optional || [];\n let oc = {};\n if (typeof r.ideal === 'number') {\n oc[oldname_('min', key)] = r.ideal;\n cc.optional.push(oc);\n oc = {};\n oc[oldname_('max', key)] = r.ideal;\n cc.optional.push(oc);\n } else {\n oc[oldname_('', key)] = r.ideal;\n cc.optional.push(oc);\n }\n }\n if (r.exact !== undefined && typeof r.exact !== 'number') {\n cc.mandatory = cc.mandatory || {};\n cc.mandatory[oldname_('', key)] = r.exact;\n } else {\n ['min', 'max'].forEach(mix =\u003E {\n if (r[mix] !== undefined) {\n cc.mandatory = cc.mandatory || {};\n cc.mandatory[oldname_(mix, key)] = r[mix];\n }\n });\n }\n });\n if (c.advanced) {\n cc.optional = (cc.optional || []).concat(c.advanced);\n }\n return cc;\n };\n\n const shimConstraints_ = function(constraints, func) {\n if (browserDetails.version \u003E= 61) {\n return func(constraints);\n }\n constraints = JSON.parse(JSON.stringify(constraints));\n if (constraints && typeof constraints.audio === 'object') {\n const remap = function(obj, a, b) {\n if (a in obj && !(b in obj)) {\n obj[b] = obj[a];\n delete obj[a];\n }\n };\n constraints = JSON.parse(JSON.stringify(constraints));\n remap(constraints.audio, 'autoGainControl', 'googAutoGainControl');\n remap(constraints.audio, 'noiseSuppression', 'googNoiseSuppression');\n constraints.audio = constraintsToChrome_(constraints.audio);\n }\n if (constraints && typeof constraints.video === 'object') {\n \u002F\u002F Shim facingMode for mobile & surface pro.\n let face = constraints.video.facingMode;\n face = face && ((typeof face === 'object') ? face : {ideal: face});\n const getSupportedFacingModeLies = browserDetails.version \u003C 66;\n\n if ((face && (face.exact === 'user' || face.exact === 'environment' ||\n face.ideal === 'user' || face.ideal === 'environment')) &&\n !(navigator.mediaDevices.getSupportedConstraints &&\n navigator.mediaDevices.getSupportedConstraints().facingMode &&\n !getSupportedFacingModeLies)) {\n delete constraints.video.facingMode;\n let matches;\n if (face.exact === 'environment' || face.ideal === 'environment') {\n matches = ['back', 'rear'];\n } else if (face.exact === 'user' || face.ideal === 'user') {\n matches = ['front'];\n }\n if (matches) {\n \u002F\u002F Look for matches in label, or use last cam for back (typical).\n return navigator.mediaDevices.enumerateDevices()\n .then(devices =\u003E {\n devices = devices.filter(d =\u003E d.kind === 'videoinput');\n let dev = devices.find(d =\u003E matches.some(match =\u003E\n d.label.toLowerCase().includes(match)));\n if (!dev && devices.length && matches.includes('back')) {\n dev = devices[devices.length - 1]; \u002F\u002F more likely the back cam\n }\n if (dev) {\n constraints.video.deviceId = face.exact ? {exact: dev.deviceId} :\n {ideal: dev.deviceId};\n }\n constraints.video = constraintsToChrome_(constraints.video);\n logging('chrome: ' + JSON.stringify(constraints));\n return func(constraints);\n });\n }\n }\n constraints.video = constraintsToChrome_(constraints.video);\n }\n logging('chrome: ' + JSON.stringify(constraints));\n return func(constraints);\n };\n\n const shimError_ = function(e) {\n if (browserDetails.version \u003E= 64) {\n return e;\n }\n return {\n name: {\n PermissionDeniedError: 'NotAllowedError',\n PermissionDismissedError: 'NotAllowedError',\n InvalidStateError: 'NotAllowedError',\n DevicesNotFoundError: 'NotFoundError',\n ConstraintNotSatisfiedError: 'OverconstrainedError',\n TrackStartError: 'NotReadableError',\n MediaDeviceFailedDueToShutdown: 'NotAllowedError',\n MediaDeviceKillSwitchOn: 'NotAllowedError',\n TabCaptureError: 'AbortError',\n ScreenCaptureError: 'AbortError',\n DeviceCaptureError: 'AbortError'\n }[e.name] || e.name,\n message: e.message,\n constraint: e.constraint || e.constraintName,\n toString() {\n return this.name + (this.message && ': ') + this.message;\n }\n };\n };\n\n const getUserMedia_ = function(constraints, onSuccess, onError) {\n shimConstraints_(constraints, c =\u003E {\n navigator.webkitGetUserMedia(c, onSuccess, e =\u003E {\n if (onError) {\n onError(shimError_(e));\n }\n });\n });\n };\n navigator.getUserMedia = getUserMedia_.bind(navigator);\n\n \u002F\u002F Even though Chrome 45 has navigator.mediaDevices and a getUserMedia\n \u002F\u002F function which returns a Promise, it does not accept spec-style\n \u002F\u002F constraints.\n if (navigator.mediaDevices.getUserMedia) {\n const origGetUserMedia = navigator.mediaDevices.getUserMedia.\n bind(navigator.mediaDevices);\n navigator.mediaDevices.getUserMedia = function(cs) {\n return shimConstraints_(cs, c =\u003E origGetUserMedia(c).then(stream =\u003E {\n if (c.audio && !stream.getAudioTracks().length ||\n c.video && !stream.getVideoTracks().length) {\n stream.getTracks().forEach(track =\u003E {\n track.stop();\n });\n throw new DOMException('', 'NotFoundError');\n }\n return stream;\n }, e =\u003E Promise.reject(shimError_(e))));\n };\n }\n }\n\n \u002F*\n * Copyright (c) 2018 The adapter.js project authors. All Rights Reserved.\n *\n * Use of this source code is governed by a BSD-style license\n * that can be found in the LICENSE file in the root of the source\n * tree.\n *\u002F\n function shimGetDisplayMedia(window, getSourceId) {\n if (window.navigator.mediaDevices &&\n 'getDisplayMedia' in window.navigator.mediaDevices) {\n return;\n }\n if (!(window.navigator.mediaDevices)) {\n return;\n }\n \u002F\u002F getSourceId is a function that returns a promise resolving with\n \u002F\u002F the sourceId of the screen\u002Fwindow\u002Ftab to be shared.\n if (typeof getSourceId !== 'function') {\n console.error('shimGetDisplayMedia: getSourceId argument is not ' +\n 'a function');\n return;\n }\n window.navigator.mediaDevices.getDisplayMedia =\n function getDisplayMedia(constraints) {\n return getSourceId(constraints)\n .then(sourceId =\u003E {\n const widthSpecified = constraints.video && constraints.video.width;\n const heightSpecified = constraints.video &&\n constraints.video.height;\n const frameRateSpecified = constraints.video &&\n constraints.video.frameRate;\n constraints.video = {\n mandatory: {\n chromeMediaSource: 'desktop',\n chromeMediaSourceId: sourceId,\n maxFrameRate: frameRateSpecified || 3\n }\n };\n if (widthSpecified) {\n constraints.video.mandatory.maxWidth = widthSpecified;\n }\n if (heightSpecified) {\n constraints.video.mandatory.maxHeight = heightSpecified;\n }\n return window.navigator.mediaDevices.getUserMedia(constraints);\n });\n };\n }\n\n \u002F*\n * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n * Use of this source code is governed by a BSD-style license\n * that can be found in the LICENSE file in the root of the source\n * tree.\n *\u002F\n\n function shimMediaStream(window) {\n window.MediaStream = window.MediaStream || window.webkitMediaStream;\n }\n\n function shimOnTrack(window) {\n if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in\n window.RTCPeerConnection.prototype)) {\n Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', {\n get() {\n return this._ontrack;\n },\n set(f) {\n if (this._ontrack) {\n this.removeEventListener('track', this._ontrack);\n }\n this.addEventListener('track', this._ontrack = f);\n },\n enumerable: true,\n configurable: true\n });\n const origSetRemoteDescription =\n window.RTCPeerConnection.prototype.setRemoteDescription;\n window.RTCPeerConnection.prototype.setRemoteDescription =\n function setRemoteDescription() {\n if (!this._ontrackpoly) {\n this._ontrackpoly = (e) =\u003E {\n \u002F\u002F onaddstream does not fire when a track is added to an existing\n \u002F\u002F stream. But stream.onaddtrack is implemented so we use that.\n e.stream.addEventListener('addtrack', te =\u003E {\n let receiver;\n if (window.RTCPeerConnection.prototype.getReceivers) {\n receiver = this.getReceivers()\n .find(r =\u003E r.track && r.track.id === te.track.id);\n } else {\n receiver = {track: te.track};\n }\n\n const event = new Event('track');\n event.track = te.track;\n event.receiver = receiver;\n event.transceiver = {receiver};\n event.streams = [e.stream];\n this.dispatchEvent(event);\n });\n e.stream.getTracks().forEach(track =\u003E {\n let receiver;\n if (window.RTCPeerConnection.prototype.getReceivers) {\n receiver = this.getReceivers()\n .find(r =\u003E r.track && r.track.id === track.id);\n } else {\n receiver = {track};\n }\n const event = new Event('track');\n event.track = track;\n event.receiver = receiver;\n event.transceiver = {receiver};\n event.streams = [e.stream];\n this.dispatchEvent(event);\n });\n };\n this.addEventListener('addstream', this._ontrackpoly);\n }\n return origSetRemoteDescription.apply(this, arguments);\n };\n } else {\n \u002F\u002F even if RTCRtpTransceiver is in window, it is only used and\n \u002F\u002F emitted in unified-plan. Unfortunately this means we need\n \u002F\u002F to unconditionally wrap the event.\n wrapPeerConnectionEvent(window, 'track', e =\u003E {\n if (!e.transceiver) {\n Object.defineProperty(e, 'transceiver',\n {value: {receiver: e.receiver}});\n }\n return e;\n });\n }\n }\n\n function shimGetSendersWithDtmf(window) {\n \u002F\u002F Overrides addTrack\u002FremoveTrack, depends on shimAddTrackRemoveTrack.\n if (typeof window === 'object' && window.RTCPeerConnection &&\n !('getSenders' in window.RTCPeerConnection.prototype) &&\n 'createDTMFSender' in window.RTCPeerConnection.prototype) {\n const shimSenderWithDtmf = function(pc, track) {\n return {\n track,\n get dtmf() {\n if (this._dtmf === undefined) {\n if (track.kind === 'audio') {\n this._dtmf = pc.createDTMFSender(track);\n } else {\n this._dtmf = null;\n }\n }\n return this._dtmf;\n },\n _pc: pc\n };\n };\n\n \u002F\u002F augment addTrack when getSenders is not available.\n if (!window.RTCPeerConnection.prototype.getSenders) {\n window.RTCPeerConnection.prototype.getSenders = function getSenders() {\n this._senders = this._senders || [];\n return this._senders.slice(); \u002F\u002F return a copy of the internal state.\n };\n const origAddTrack = window.RTCPeerConnection.prototype.addTrack;\n window.RTCPeerConnection.prototype.addTrack =\n function addTrack(track, stream) {\n let sender = origAddTrack.apply(this, arguments);\n if (!sender) {\n sender = shimSenderWithDtmf(this, track);\n this._senders.push(sender);\n }\n return sender;\n };\n\n const origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack;\n window.RTCPeerConnection.prototype.removeTrack =\n function removeTrack(sender) {\n origRemoveTrack.apply(this, arguments);\n const idx = this._senders.indexOf(sender);\n if (idx !== -1) {\n this._senders.splice(idx, 1);\n }\n };\n }\n const origAddStream = window.RTCPeerConnection.prototype.addStream;\n window.RTCPeerConnection.prototype.addStream = function addStream(stream) {\n this._senders = this._senders || [];\n origAddStream.apply(this, [stream]);\n stream.getTracks().forEach(track =\u003E {\n this._senders.push(shimSenderWithDtmf(this, track));\n });\n };\n\n const origRemoveStream = window.RTCPeerConnection.prototype.removeStream;\n window.RTCPeerConnection.prototype.removeStream =\n function removeStream(stream) {\n this._senders = this._senders || [];\n origRemoveStream.apply(this, [stream]);\n\n stream.getTracks().forEach(track =\u003E {\n const sender = this._senders.find(s =\u003E s.track === track);\n if (sender) { \u002F\u002F remove sender\n this._senders.splice(this._senders.indexOf(sender), 1);\n }\n });\n };\n } else if (typeof window === 'object' && window.RTCPeerConnection &&\n 'getSenders' in window.RTCPeerConnection.prototype &&\n 'createDTMFSender' in window.RTCPeerConnection.prototype &&\n window.RTCRtpSender &&\n !('dtmf' in window.RTCRtpSender.prototype)) {\n const origGetSenders = window.RTCPeerConnection.prototype.getSenders;\n window.RTCPeerConnection.prototype.getSenders = function getSenders() {\n const senders = origGetSenders.apply(this, []);\n senders.forEach(sender =\u003E sender._pc = this);\n return senders;\n };\n\n Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', {\n get() {\n if (this._dtmf === undefined) {\n if (this.track.kind === 'audio') {\n this._dtmf = this._pc.createDTMFSender(this.track);\n } else {\n this._dtmf = null;\n }\n }\n return this._dtmf;\n }\n });\n }\n }\n\n function shimGetStats(window) {\n if (!window.RTCPeerConnection) {\n return;\n }\n\n const origGetStats = window.RTCPeerConnection.prototype.getStats;\n window.RTCPeerConnection.prototype.getStats = function getStats() {\n const [selector, onSucc, onErr] = arguments;\n\n \u002F\u002F If selector is a function then we are in the old style stats so just\n \u002F\u002F pass back the original getStats format to avoid breaking old users.\n if (arguments.length \u003E 0 && typeof selector === 'function') {\n return origGetStats.apply(this, arguments);\n }\n\n \u002F\u002F When spec-style getStats is supported, return those when called with\n \u002F\u002F either no arguments or the selector argument is null.\n if (origGetStats.length === 0 && (arguments.length === 0 ||\n typeof selector !== 'function')) {\n return origGetStats.apply(this, []);\n }\n\n const fixChromeStats_ = function(response) {\n const standardReport = {};\n const reports = response.result();\n reports.forEach(report =\u003E {\n const standardStats = {\n id: report.id,\n timestamp: report.timestamp,\n type: {\n localcandidate: 'local-candidate',\n remotecandidate: 'remote-candidate'\n }[report.type] || report.type\n };\n report.names().forEach(name =\u003E {\n standardStats[name] = report.stat(name);\n });\n standardReport[standardStats.id] = standardStats;\n });\n\n return standardReport;\n };\n\n \u002F\u002F shim getStats with maplike support\n const makeMapStats = function(stats) {\n return new Map(Object.keys(stats).map(key =\u003E [key, stats[key]]));\n };\n\n if (arguments.length \u003E= 2) {\n const successCallbackWrapper_ = function(response) {\n onSucc(makeMapStats(fixChromeStats_(response)));\n };\n\n return origGetStats.apply(this, [successCallbackWrapper_,\n selector]);\n }\n\n \u002F\u002F promise-support\n return new Promise((resolve, reject) =\u003E {\n origGetStats.apply(this, [\n function(response) {\n resolve(makeMapStats(fixChromeStats_(response)));\n }, reject]);\n }).then(onSucc, onErr);\n };\n }\n\n function shimSenderReceiverGetStats(window) {\n if (!(typeof window === 'object' && window.RTCPeerConnection &&\n window.RTCRtpSender && window.RTCRtpReceiver)) {\n return;\n }\n\n \u002F\u002F shim sender stats.\n if (!('getStats' in window.RTCRtpSender.prototype)) {\n const origGetSenders = window.RTCPeerConnection.prototype.getSenders;\n if (origGetSenders) {\n window.RTCPeerConnection.prototype.getSenders = function getSenders() {\n const senders = origGetSenders.apply(this, []);\n senders.forEach(sender =\u003E sender._pc = this);\n return senders;\n };\n }\n\n const origAddTrack = window.RTCPeerConnection.prototype.addTrack;\n if (origAddTrack) {\n window.RTCPeerConnection.prototype.addTrack = function addTrack() {\n const sender = origAddTrack.apply(this, arguments);\n sender._pc = this;\n return sender;\n };\n }\n window.RTCRtpSender.prototype.getStats = function getStats() {\n const sender = this;\n return this._pc.getStats().then(result =\u003E\n \u002F* Note: this will include stats of all senders that\n * send a track with the same id as sender.track as\n * it is not possible to identify the RTCRtpSender.\n *\u002F\n filterStats(result, sender.track, true));\n };\n }\n\n \u002F\u002F shim receiver stats.\n if (!('getStats' in window.RTCRtpReceiver.prototype)) {\n const origGetReceivers = window.RTCPeerConnection.prototype.getReceivers;\n if (origGetReceivers) {\n window.RTCPeerConnection.prototype.getReceivers =\n function getReceivers() {\n const receivers = origGetReceivers.apply(this, []);\n receivers.forEach(receiver =\u003E receiver._pc = this);\n return receivers;\n };\n }\n wrapPeerConnectionEvent(window, 'track', e =\u003E {\n e.receiver._pc = e.srcElement;\n return e;\n });\n window.RTCRtpReceiver.prototype.getStats = function getStats() {\n const receiver = this;\n return this._pc.getStats().then(result =\u003E\n filterStats(result, receiver.track, false));\n };\n }\n\n if (!('getStats' in window.RTCRtpSender.prototype &&\n 'getStats' in window.RTCRtpReceiver.prototype)) {\n return;\n }\n\n \u002F\u002F shim RTCPeerConnection.getStats(track).\n const origGetStats = window.RTCPeerConnection.prototype.getStats;\n window.RTCPeerConnection.prototype.getStats = function getStats() {\n if (arguments.length \u003E 0 &&\n arguments[0] instanceof window.MediaStreamTrack) {\n const track = arguments[0];\n let sender;\n let receiver;\n let err;\n this.getSenders().forEach(s =\u003E {\n if (s.track === track) {\n if (sender) {\n err = true;\n } else {\n sender = s;\n }\n }\n });\n this.getReceivers().forEach(r =\u003E {\n if (r.track === track) {\n if (receiver) {\n err = true;\n } else {\n receiver = r;\n }\n }\n return r.track === track;\n });\n if (err || (sender && receiver)) {\n return Promise.reject(new DOMException(\n 'There are more than one sender or receiver for the track.',\n 'InvalidAccessError'));\n } else if (sender) {\n return sender.getStats();\n } else if (receiver) {\n return receiver.getStats();\n }\n return Promise.reject(new DOMException(\n 'There is no sender or receiver for the track.',\n 'InvalidAccessError'));\n }\n return origGetStats.apply(this, arguments);\n };\n }\n\n function shimAddTrackRemoveTrackWithNative(window) {\n \u002F\u002F shim addTrack\u002FremoveTrack with native variants in order to make\n \u002F\u002F the interactions with legacy getLocalStreams behave as in other browsers.\n \u002F\u002F Keeps a mapping stream.id =\u003E [stream, rtpsenders...]\n window.RTCPeerConnection.prototype.getLocalStreams =\n function getLocalStreams() {\n this._shimmedLocalStreams = this._shimmedLocalStreams || {};\n return Object.keys(this._shimmedLocalStreams)\n .map(streamId =\u003E this._shimmedLocalStreams[streamId][0]);\n };\n\n const origAddTrack = window.RTCPeerConnection.prototype.addTrack;\n window.RTCPeerConnection.prototype.addTrack =\n function addTrack(track, stream) {\n if (!stream) {\n return origAddTrack.apply(this, arguments);\n }\n this._shimmedLocalStreams = this._shimmedLocalStreams || {};\n\n const sender = origAddTrack.apply(this, arguments);\n if (!this._shimmedLocalStreams[stream.id]) {\n this._shimmedLocalStreams[stream.id] = [stream, sender];\n } else if (this._shimmedLocalStreams[stream.id].indexOf(sender) === -1) {\n this._shimmedLocalStreams[stream.id].push(sender);\n }\n return sender;\n };\n\n const origAddStream = window.RTCPeerConnection.prototype.addStream;\n window.RTCPeerConnection.prototype.addStream = function addStream(stream) {\n this._shimmedLocalStreams = this._shimmedLocalStreams || {};\n\n stream.getTracks().forEach(track =\u003E {\n const alreadyExists = this.getSenders().find(s =\u003E s.track === track);\n if (alreadyExists) {\n throw new DOMException('Track already exists.',\n 'InvalidAccessError');\n }\n });\n const existingSenders = this.getSenders();\n origAddStream.apply(this, arguments);\n const newSenders = this.getSenders()\n .filter(newSender =\u003E existingSenders.indexOf(newSender) === -1);\n this._shimmedLocalStreams[stream.id] = [stream].concat(newSenders);\n };\n\n const origRemoveStream = window.RTCPeerConnection.prototype.removeStream;\n window.RTCPeerConnection.prototype.removeStream =\n function removeStream(stream) {\n this._shimmedLocalStreams = this._shimmedLocalStreams || {};\n delete this._shimmedLocalStreams[stream.id];\n return origRemoveStream.apply(this, arguments);\n };\n\n const origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack;\n window.RTCPeerConnection.prototype.removeTrack =\n function removeTrack(sender) {\n this._shimmedLocalStreams = this._shimmedLocalStreams || {};\n if (sender) {\n Object.keys(this._shimmedLocalStreams).forEach(streamId =\u003E {\n const idx = this._shimmedLocalStreams[streamId].indexOf(sender);\n if (idx !== -1) {\n this._shimmedLocalStreams[streamId].splice(idx, 1);\n }\n if (this._shimmedLocalStreams[streamId].length === 1) {\n delete this._shimmedLocalStreams[streamId];\n }\n });\n }\n return origRemoveTrack.apply(this, arguments);\n };\n }\n\n function shimAddTrackRemoveTrack(window) {\n if (!window.RTCPeerConnection) {\n return;\n }\n const browserDetails = detectBrowser(window);\n \u002F\u002F shim addTrack and removeTrack.\n if (window.RTCPeerConnection.prototype.addTrack &&\n browserDetails.version \u003E= 65) {\n return shimAddTrackRemoveTrackWithNative(window);\n }\n\n \u002F\u002F also shim pc.getLocalStreams when addTrack is shimmed\n \u002F\u002F to return the original streams.\n const origGetLocalStreams = window.RTCPeerConnection.prototype\n .getLocalStreams;\n window.RTCPeerConnection.prototype.getLocalStreams =\n function getLocalStreams() {\n const nativeStreams = origGetLocalStreams.apply(this);\n this._reverseStreams = this._reverseStreams || {};\n return nativeStreams.map(stream =\u003E this._reverseStreams[stream.id]);\n };\n\n const origAddStream = window.RTCPeerConnection.prototype.addStream;\n window.RTCPeerConnection.prototype.addStream = function addStream(stream) {\n this._streams = this._streams || {};\n this._reverseStreams = this._reverseStreams || {};\n\n stream.getTracks().forEach(track =\u003E {\n const alreadyExists = this.getSenders().find(s =\u003E s.track === track);\n if (alreadyExists) {\n throw new DOMException('Track already exists.',\n 'InvalidAccessError');\n }\n });\n \u002F\u002F Add identity mapping for consistency with addTrack.\n \u002F\u002F Unless this is being used with a stream from addTrack.\n if (!this._reverseStreams[stream.id]) {\n const newStream = new window.MediaStream(stream.getTracks());\n this._streams[stream.id] = newStream;\n this._reverseStreams[newStream.id] = stream;\n stream = newStream;\n }\n origAddStream.apply(this, [stream]);\n };\n\n const origRemoveStream = window.RTCPeerConnection.prototype.removeStream;\n window.RTCPeerConnection.prototype.removeStream =\n function removeStream(stream) {\n this._streams = this._streams || {};\n this._reverseStreams = this._reverseStreams || {};\n\n origRemoveStream.apply(this, [(this._streams[stream.id] || stream)]);\n delete this._reverseStreams[(this._streams[stream.id] ?\n this._streams[stream.id].id : stream.id)];\n delete this._streams[stream.id];\n };\n\n window.RTCPeerConnection.prototype.addTrack =\n function addTrack(track, stream) {\n if (this.signalingState === 'closed') {\n throw new DOMException(\n 'The RTCPeerConnection\\'s signalingState is \\'closed\\'.',\n 'InvalidStateError');\n }\n const streams = [].slice.call(arguments, 1);\n if (streams.length !== 1 ||\n !streams[0].getTracks().find(t =\u003E t === track)) {\n \u002F\u002F this is not fully correct but all we can manage without\n \u002F\u002F [[associated MediaStreams]] internal slot.\n throw new DOMException(\n 'The adapter.js addTrack polyfill only supports a single ' +\n ' stream which is associated with the specified track.',\n 'NotSupportedError');\n }\n\n const alreadyExists = this.getSenders().find(s =\u003E s.track === track);\n if (alreadyExists) {\n throw new DOMException('Track already exists.',\n 'InvalidAccessError');\n }\n\n this._streams = this._streams || {};\n this._reverseStreams = this._reverseStreams || {};\n const oldStream = this._streams[stream.id];\n if (oldStream) {\n \u002F\u002F this is using odd Chrome behaviour, use with caution:\n \u002F\u002F https:\u002F\u002Fbugs.chromium.org\u002Fp\u002Fwebrtc\u002Fissues\u002Fdetail?id=7815\n \u002F\u002F Note: we rely on the high-level addTrack\u002Fdtmf shim to\n \u002F\u002F create the sender with a dtmf sender.\n oldStream.addTrack(track);\n\n \u002F\u002F Trigger ONN async.\n Promise.resolve().then(() =\u003E {\n this.dispatchEvent(new Event('negotiationneeded'));\n });\n } else {\n const newStream = new window.MediaStream([track]);\n this._streams[stream.id] = newStream;\n this._reverseStreams[newStream.id] = stream;\n this.addStream(newStream);\n }\n return this.getSenders().find(s =\u003E s.track === track);\n };\n\n \u002F\u002F replace the internal stream id with the external one and\n \u002F\u002F vice versa.\n function replaceInternalStreamId(pc, description) {\n let sdp = description.sdp;\n Object.keys(pc._reverseStreams || []).forEach(internalId =\u003E {\n const externalStream = pc._reverseStreams[internalId];\n const internalStream = pc._streams[externalStream.id];\n sdp = sdp.replace(new RegExp(internalStream.id, 'g'),\n externalStream.id);\n });\n return new RTCSessionDescription({\n type: description.type,\n sdp\n });\n }\n function replaceExternalStreamId(pc, description) {\n let sdp = description.sdp;\n Object.keys(pc._reverseStreams || []).forEach(internalId =\u003E {\n const externalStream = pc._reverseStreams[internalId];\n const internalStream = pc._streams[externalStream.id];\n sdp = sdp.replace(new RegExp(externalStream.id, 'g'),\n internalStream.id);\n });\n return new RTCSessionDescription({\n type: description.type,\n sdp\n });\n }\n ['createOffer', 'createAnswer'].forEach(function(method) {\n const nativeMethod = window.RTCPeerConnection.prototype[method];\n const methodObj = {[method]() {\n const args = arguments;\n const isLegacyCall = arguments.length &&\n typeof arguments[0] === 'function';\n if (isLegacyCall) {\n return nativeMethod.apply(this, [\n (description) =\u003E {\n const desc = replaceInternalStreamId(this, description);\n args[0].apply(null, [desc]);\n },\n (err) =\u003E {\n if (args[1]) {\n args[1].apply(null, err);\n }\n }, arguments[2]\n ]);\n }\n return nativeMethod.apply(this, arguments)\n .then(description =\u003E replaceInternalStreamId(this, description));\n }};\n window.RTCPeerConnection.prototype[method] = methodObj[method];\n });\n\n const origSetLocalDescription =\n window.RTCPeerConnection.prototype.setLocalDescription;\n window.RTCPeerConnection.prototype.setLocalDescription =\n function setLocalDescription() {\n if (!arguments.length || !arguments[0].type) {\n return origSetLocalDescription.apply(this, arguments);\n }\n arguments[0] = replaceExternalStreamId(this, arguments[0]);\n return origSetLocalDescription.apply(this, arguments);\n };\n\n \u002F\u002F TODO: mangle getStats: https:\u002F\u002Fw3c.github.io\u002Fwebrtc-stats\u002F#dom-rtcmediastreamstats-streamidentifier\n\n const origLocalDescription = Object.getOwnPropertyDescriptor(\n window.RTCPeerConnection.prototype, 'localDescription');\n Object.defineProperty(window.RTCPeerConnection.prototype,\n 'localDescription', {\n get() {\n const description = origLocalDescription.get.apply(this);\n if (description.type === '') {\n return description;\n }\n return replaceInternalStreamId(this, description);\n }\n });\n\n window.RTCPeerConnection.prototype.removeTrack =\n function removeTrack(sender) {\n if (this.signalingState === 'closed') {\n throw new DOMException(\n 'The RTCPeerConnection\\'s signalingState is \\'closed\\'.',\n 'InvalidStateError');\n }\n \u002F\u002F We can not yet check for sender instanceof RTCRtpSender\n \u002F\u002F since we shim RTPSender. So we check if sender._pc is set.\n if (!sender._pc) {\n throw new DOMException('Argument 1 of RTCPeerConnection.removeTrack ' +\n 'does not implement interface RTCRtpSender.', 'TypeError');\n }\n const isLocal = sender._pc === this;\n if (!isLocal) {\n throw new DOMException('Sender was not created by this connection.',\n 'InvalidAccessError');\n }\n\n \u002F\u002F Search for the native stream the senders track belongs to.\n this._streams = this._streams || {};\n let stream;\n Object.keys(this._streams).forEach(streamid =\u003E {\n const hasTrack = this._streams[streamid].getTracks()\n .find(track =\u003E sender.track === track);\n if (hasTrack) {\n stream = this._streams[streamid];\n }\n });\n\n if (stream) {\n if (stream.getTracks().length === 1) {\n \u002F\u002F if this is the last track of the stream, remove the stream. This\n \u002F\u002F takes care of any shimmed _senders.\n this.removeStream(this._reverseStreams[stream.id]);\n } else {\n \u002F\u002F relying on the same odd chrome behaviour as above.\n stream.removeTrack(sender.track);\n }\n this.dispatchEvent(new Event('negotiationneeded'));\n }\n };\n }\n\n function shimPeerConnection(window) {\n const browserDetails = detectBrowser(window);\n\n if (!window.RTCPeerConnection && window.webkitRTCPeerConnection) {\n \u002F\u002F very basic support for old versions.\n window.RTCPeerConnection = window.webkitRTCPeerConnection;\n }\n if (!window.RTCPeerConnection) {\n return;\n }\n\n const addIceCandidateNullSupported =\n window.RTCPeerConnection.prototype.addIceCandidate.length === 0;\n\n \u002F\u002F shim implicit creation of RTCSessionDescription\u002FRTCIceCandidate\n if (browserDetails.version \u003C 53) {\n ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']\n .forEach(function(method) {\n const nativeMethod = window.RTCPeerConnection.prototype[method];\n const methodObj = {[method]() {\n arguments[0] = new ((method === 'addIceCandidate') ?\n window.RTCIceCandidate :\n window.RTCSessionDescription)(arguments[0]);\n return nativeMethod.apply(this, arguments);\n }};\n window.RTCPeerConnection.prototype[method] = methodObj[method];\n });\n }\n\n \u002F\u002F support for addIceCandidate(null or undefined)\n const nativeAddIceCandidate =\n window.RTCPeerConnection.prototype.addIceCandidate;\n window.RTCPeerConnection.prototype.addIceCandidate =\n function addIceCandidate() {\n if (!addIceCandidateNullSupported && !arguments[0]) {\n if (arguments[1]) {\n arguments[1].apply(null);\n }\n return Promise.resolve();\n }\n \u002F\u002F Firefox 68+ emits and processes {candidate: \"\", ...}, ignore\n \u002F\u002F in older versions. Native support planned for Chrome M77.\n if (browserDetails.version \u003C 78 &&\n arguments[0] && arguments[0].candidate === '') {\n return Promise.resolve();\n }\n return nativeAddIceCandidate.apply(this, arguments);\n };\n }\n\n \u002F\u002F Attempt to fix ONN in plan-b mode.\n function fixNegotiationNeeded(window) {\n const browserDetails = detectBrowser(window);\n wrapPeerConnectionEvent(window, 'negotiationneeded', e =\u003E {\n const pc = e.target;\n if (browserDetails.version \u003C 72 || (pc.getConfiguration &&\n pc.getConfiguration().sdpSemantics === 'plan-b')) {\n if (pc.signalingState !== 'stable') {\n return;\n }\n }\n return e;\n });\n }\n\n var chromeShim = \u002F*#__PURE__*\u002FObject.freeze({\n __proto__: null,\n shimMediaStream: shimMediaStream,\n shimOnTrack: shimOnTrack,\n shimGetSendersWithDtmf: shimGetSendersWithDtmf,\n shimGetStats: shimGetStats,\n shimSenderReceiverGetStats: shimSenderReceiverGetStats,\n shimAddTrackRemoveTrackWithNative: shimAddTrackRemoveTrackWithNative,\n shimAddTrackRemoveTrack: shimAddTrackRemoveTrack,\n shimPeerConnection: shimPeerConnection,\n fixNegotiationNeeded: fixNegotiationNeeded,\n shimGetUserMedia: shimGetUserMedia,\n shimGetDisplayMedia: shimGetDisplayMedia\n });\n\n \u002F*\n * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.\n *\n * Use of this source code is governed by a BSD-style license\n * that can be found in the LICENSE file in the root of the source\n * tree.\n *\u002F\n \u002F\u002F Edge does not like\n \u002F\u002F 1) stun: filtered after 14393 unless ?transport=udp is present\n \u002F\u002F 2) turn: that does not have all of turn:host:port?transport=udp\n \u002F\u002F 3) turn: with ipv6 addresses\n \u002F\u002F 4) turn: occurring muliple times\n function filterIceServers(iceServers, edgeVersion) {\n let hasTurn = false;\n iceServers = JSON.parse(JSON.stringify(iceServers));\n return iceServers.filter(server =\u003E {\n if (server && (server.urls || server.url)) {\n var urls = server.urls || server.url;\n if (server.url && !server.urls) {\n deprecated('RTCIceServer.url', 'RTCIceServer.urls');\n }\n const isString = typeof urls === 'string';\n if (isString) {\n urls = [urls];\n }\n urls = urls.filter(url =\u003E {\n \u002F\u002F filter STUN unconditionally.\n if (url.indexOf('stun:') === 0) {\n return false;\n }\n\n const validTurn = url.startsWith('turn') &&\n !url.startsWith('turn:[') &&\n url.includes('transport=udp');\n if (validTurn && !hasTurn) {\n hasTurn = true;\n return true;\n }\n return validTurn && !hasTurn;\n });\n\n delete server.url;\n server.urls = isString ? urls[0] : urls;\n return !!urls.length;\n }\n });\n }\n\n var sdp = createCommonjsModule(function (module) {\n\n \u002F\u002F SDP helpers.\n var SDPUtils = {};\n\n \u002F\u002F Generate an alphanumeric identifier for cname or mids.\n \u002F\u002F TODO: use UUIDs instead? https:\u002F\u002Fgist.github.com\u002Fjed\u002F982883\n SDPUtils.generateIdentifier = function() {\n return Math.random().toString(36).substr(2, 10);\n };\n\n \u002F\u002F The RTCP CNAME used by all peerconnections from the same JS.\n SDPUtils.localCName = SDPUtils.generateIdentifier();\n\n \u002F\u002F Splits SDP into lines, dealing with both CRLF and LF.\n SDPUtils.splitLines = function(blob) {\n return blob.trim().split('\\n').map(function(line) {\n return line.trim();\n });\n };\n \u002F\u002F Splits SDP into sessionpart and mediasections. Ensures CRLF.\n SDPUtils.splitSections = function(blob) {\n var parts = blob.split('\\nm=');\n return parts.map(function(part, index) {\n return (index \u003E 0 ? 'm=' + part : part).trim() + '\\r\\n';\n });\n };\n\n \u002F\u002F returns the session description.\n SDPUtils.getDescription = function(blob) {\n var sections = SDPUtils.splitSections(blob);\n return sections && sections[0];\n };\n\n \u002F\u002F returns the individual media sections.\n SDPUtils.getMediaSections = function(blob) {\n var sections = SDPUtils.splitSections(blob);\n sections.shift();\n return sections;\n };\n\n \u002F\u002F Returns lines that start with a certain prefix.\n SDPUtils.matchPrefix = function(blob, prefix) {\n return SDPUtils.splitLines(blob).filter(function(line) {\n return line.indexOf(prefix) === 0;\n });\n };\n\n \u002F\u002F Parses an ICE candidate line. Sample input:\n \u002F\u002F candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8\n \u002F\u002F rport 55996\"\n SDPUtils.parseCandidate = function(line) {\n var parts;\n \u002F\u002F Parse both variants.\n if (line.indexOf('a=candidate:') === 0) {\n parts = line.substring(12).split(' ');\n } else {\n parts = line.substring(10).split(' ');\n }\n\n var candidate = {\n foundation: parts[0],\n component: parseInt(parts[1], 10),\n protocol: parts[2].toLowerCase(),\n priority: parseInt(parts[3], 10),\n ip: parts[4],\n address: parts[4], \u002F\u002F address is an alias for ip.\n port: parseInt(parts[5], 10),\n \u002F\u002F skip parts[6] == 'typ'\n type: parts[7]\n };\n\n for (var i = 8; i \u003C parts.length; i += 2) {\n switch (parts[i]) {\n case 'raddr':\n candidate.relatedAddress = parts[i + 1];\n break;\n case 'rport':\n candidate.relatedPort = parseInt(parts[i + 1], 10);\n break;\n case 'tcptype':\n candidate.tcpType = parts[i + 1];\n break;\n case 'ufrag':\n candidate.ufrag = parts[i + 1]; \u002F\u002F for backward compability.\n candidate.usernameFragment = parts[i + 1];\n break;\n default: \u002F\u002F extension handling, in particular ufrag\n candidate[parts[i]] = parts[i + 1];\n break;\n }\n }\n return candidate;\n };\n\n \u002F\u002F Translates a candidate object into SDP candidate attribute.\n SDPUtils.writeCandidate = function(candidate) {\n var sdp = [];\n sdp.push(candidate.foundation);\n sdp.push(candidate.component);\n sdp.push(candidate.protocol.toUpperCase());\n sdp.push(candidate.priority);\n sdp.push(candidate.address || candidate.ip);\n sdp.push(candidate.port);\n\n var type = candidate.type;\n sdp.push('typ');\n sdp.push(type);\n if (type !== 'host' && candidate.relatedAddress &&\n candidate.relatedPort) {\n sdp.push('raddr');\n sdp.push(candidate.relatedAddress);\n sdp.push('rport');\n sdp.push(candidate.relatedPort);\n }\n if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') {\n sdp.push('tcptype');\n sdp.push(candidate.tcpType);\n }\n if (candidate.usernameFragment || candidate.ufrag) {\n sdp.push('ufrag');\n sdp.push(candidate.usernameFragment || candidate.ufrag);\n }\n return 'candidate:' + sdp.join(' ');\n };\n\n \u002F\u002F Parses an ice-options line, returns an array of option tags.\n \u002F\u002F a=ice-options:foo bar\n SDPUtils.parseIceOptions = function(line) {\n return line.substr(14).split(' ');\n };\n\n \u002F\u002F Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input:\n \u002F\u002F a=rtpmap:111 opus\u002F48000\u002F2\n SDPUtils.parseRtpMap = function(line) {\n var parts = line.substr(9).split(' ');\n var parsed = {\n payloadType: parseInt(parts.shift(), 10) \u002F\u002F was: id\n };\n\n parts = parts[0].split('\u002F');\n\n parsed.name = parts[0];\n parsed.clockRate = parseInt(parts[1], 10); \u002F\u002F was: clockrate\n parsed.channels = parts.length === 3 ? parseInt(parts[2], 10) : 1;\n \u002F\u002F legacy alias, got renamed back to channels in ORTC.\n parsed.numChannels = parsed.channels;\n return parsed;\n };\n\n \u002F\u002F Generate an a=rtpmap line from RTCRtpCodecCapability or\n \u002F\u002F RTCRtpCodecParameters.\n SDPUtils.writeRtpMap = function(codec) {\n var pt = codec.payloadType;\n if (codec.preferredPayloadType !== undefined) {\n pt = codec.preferredPayloadType;\n }\n var channels = codec.channels || codec.numChannels || 1;\n return 'a=rtpmap:' + pt + ' ' + codec.name + '\u002F' + codec.clockRate +\n (channels !== 1 ? '\u002F' + channels : '') + '\\r\\n';\n };\n\n \u002F\u002F Parses an a=extmap line (headerextension from RFC 5285). Sample input:\n \u002F\u002F a=extmap:2 urn:ietf:params:rtp-hdrext:toffset\n \u002F\u002F a=extmap:2\u002Fsendonly urn:ietf:params:rtp-hdrext:toffset\n SDPUtils.parseExtmap = function(line) {\n var parts = line.substr(9).split(' ');\n return {\n id: parseInt(parts[0], 10),\n direction: parts[0].indexOf('\u002F') \u003E 0 ? parts[0].split('\u002F')[1] : 'sendrecv',\n uri: parts[1]\n };\n };\n\n \u002F\u002F Generates a=extmap line from RTCRtpHeaderExtensionParameters or\n \u002F\u002F RTCRtpHeaderExtension.\n SDPUtils.writeExtmap = function(headerExtension) {\n return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) +\n (headerExtension.direction && headerExtension.direction !== 'sendrecv'\n ? '\u002F' + headerExtension.direction\n : '') +\n ' ' + headerExtension.uri + '\\r\\n';\n };\n\n \u002F\u002F Parses an ftmp line, returns dictionary. Sample input:\n \u002F\u002F a=fmtp:96 vbr=on;cng=on\n \u002F\u002F Also deals with vbr=on; cng=on\n SDPUtils.parseFmtp = function(line) {\n var parsed = {};\n var kv;\n var parts = line.substr(line.indexOf(' ') + 1).split(';');\n for (var j = 0; j \u003C parts.length; j++) {\n kv = parts[j].trim().split('=');\n parsed[kv[0].trim()] = kv[1];\n }\n return parsed;\n };\n\n \u002F\u002F Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters.\n SDPUtils.writeFmtp = function(codec) {\n var line = '';\n var pt = codec.payloadType;\n if (codec.preferredPayloadType !== undefined) {\n pt = codec.preferredPayloadType;\n }\n if (codec.parameters && Object.keys(codec.parameters).length) {\n var params = [];\n Object.keys(codec.parameters).forEach(function(param) {\n if (codec.parameters[param]) {\n params.push(param + '=' + codec.parameters[param]);\n } else {\n params.push(param);\n }\n });\n line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\\r\\n';\n }\n return line;\n };\n\n \u002F\u002F Parses an rtcp-fb line, returns RTCPRtcpFeedback object. Sample input:\n \u002F\u002F a=rtcp-fb:98 nack rpsi\n SDPUtils.parseRtcpFb = function(line) {\n var parts = line.substr(line.indexOf(' ') + 1).split(' ');\n return {\n type: parts.shift(),\n parameter: parts.join(' ')\n };\n };\n \u002F\u002F Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters.\n SDPUtils.writeRtcpFb = function(codec) {\n var lines = '';\n var pt = codec.payloadType;\n if (codec.preferredPayloadType !== undefined) {\n pt = codec.preferredPayloadType;\n }\n if (codec.rtcpFeedback && codec.rtcpFeedback.length) {\n \u002F\u002F FIXME: special handling for trr-int?\n codec.rtcpFeedback.forEach(function(fb) {\n lines += 'a=rtcp-fb:' + pt + ' ' + fb.type +\n (fb.parameter && fb.parameter.length ? ' ' + fb.parameter : '') +\n '\\r\\n';\n });\n }\n return lines;\n };\n\n \u002F\u002F Parses an RFC 5576 ssrc media attribute. Sample input:\n \u002F\u002F a=ssrc:3735928559 cname:something\n SDPUtils.parseSsrcMedia = function(line) {\n var sp = line.indexOf(' ');\n var parts = {\n ssrc: parseInt(line.substr(7, sp - 7), 10)\n };\n var colon = line.indexOf(':', sp);\n if (colon \u003E -1) {\n parts.attribute = line.substr(sp + 1, colon - sp - 1);\n parts.value = line.substr(colon + 1);\n } else {\n parts.attribute = line.substr(sp + 1);\n }\n return parts;\n };\n\n SDPUtils.parseSsrcGroup = function(line) {\n var parts = line.substr(13).split(' ');\n return {\n semantics: parts.shift(),\n ssrcs: parts.map(function(ssrc) {\n return parseInt(ssrc, 10);\n })\n };\n };\n\n \u002F\u002F Extracts the MID (RFC 5888) from a media section.\n \u002F\u002F returns the MID or undefined if no mid line was found.\n SDPUtils.getMid = function(mediaSection) {\n var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0];\n if (mid) {\n return mid.substr(6);\n }\n };\n\n SDPUtils.parseFingerprint = function(line) {\n var parts = line.substr(14).split(' ');\n return {\n algorithm: parts[0].toLowerCase(), \u002F\u002F algorithm is case-sensitive in Edge.\n value: parts[1]\n };\n };\n\n \u002F\u002F Extracts DTLS parameters from SDP media section or sessionpart.\n \u002F\u002F FIXME: for consistency with other functions this should only\n \u002F\u002F get the fingerprint line as input. See also getIceParameters.\n SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) {\n var lines = SDPUtils.matchPrefix(mediaSection + sessionpart,\n 'a=fingerprint:');\n \u002F\u002F Note: a=setup line is ignored since we use the 'auto' role.\n \u002F\u002F Note2: 'algorithm' is not case sensitive except in Edge.\n return {\n role: 'auto',\n fingerprints: lines.map(SDPUtils.parseFingerprint)\n };\n };\n\n \u002F\u002F Serializes DTLS parameters to SDP.\n SDPUtils.writeDtlsParameters = function(params, setupType) {\n var sdp = 'a=setup:' + setupType + '\\r\\n';\n params.fingerprints.forEach(function(fp) {\n sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\\r\\n';\n });\n return sdp;\n };\n\n \u002F\u002F Parses a=crypto lines into\n \u002F\u002F https:\u002F\u002Frawgit.com\u002Faboba\u002Fedgertc\u002Fmaster\u002Fmsortc-rs4.html#dictionary-rtcsrtpsdesparameters-members\n SDPUtils.parseCryptoLine = function(line) {\n var parts = line.substr(9).split(' ');\n return {\n tag: parseInt(parts[0], 10),\n cryptoSuite: parts[1],\n keyParams: parts[2],\n sessionParams: parts.slice(3),\n };\n };\n\n SDPUtils.writeCryptoLine = function(parameters) {\n return 'a=crypto:' + parameters.tag + ' ' +\n parameters.cryptoSuite + ' ' +\n (typeof parameters.keyParams === 'object'\n ? SDPUtils.writeCryptoKeyParams(parameters.keyParams)\n : parameters.keyParams) +\n (parameters.sessionParams ? ' ' + parameters.sessionParams.join(' ') : '') +\n '\\r\\n';\n };\n\n \u002F\u002F Parses the crypto key parameters into\n \u002F\u002F https:\u002F\u002Frawgit.com\u002Faboba\u002Fedgertc\u002Fmaster\u002Fmsortc-rs4.html#rtcsrtpkeyparam*\n SDPUtils.parseCryptoKeyParams = function(keyParams) {\n if (keyParams.indexOf('inline:') !== 0) {\n return null;\n }\n var parts = keyParams.substr(7).split('|');\n return {\n keyMethod: 'inline',\n keySalt: parts[0],\n lifeTime: parts[1],\n mkiValue: parts[2] ? parts[2].split(':')[0] : undefined,\n mkiLength: parts[2] ? parts[2].split(':')[1] : undefined,\n };\n };\n\n SDPUtils.writeCryptoKeyParams = function(keyParams) {\n return keyParams.keyMethod + ':'\n + keyParams.keySalt +\n (keyParams.lifeTime ? '|' + keyParams.lifeTime : '') +\n (keyParams.mkiValue && keyParams.mkiLength\n ? '|' + keyParams.mkiValue + ':' + keyParams.mkiLength\n : '');\n };\n\n \u002F\u002F Extracts all SDES paramters.\n SDPUtils.getCryptoParameters = function(mediaSection, sessionpart) {\n var lines = SDPUtils.matchPrefix(mediaSection + sessionpart,\n 'a=crypto:');\n return lines.map(SDPUtils.parseCryptoLine);\n };\n\n \u002F\u002F Parses ICE information from SDP media section or sessionpart.\n \u002F\u002F FIXME: for consistency with other functions this should only\n \u002F\u002F get the ice-ufrag and ice-pwd lines as input.\n SDPUtils.getIceParameters = function(mediaSection, sessionpart) {\n var ufrag = SDPUtils.matchPrefix(mediaSection + sessionpart,\n 'a=ice-ufrag:')[0];\n var pwd = SDPUtils.matchPrefix(mediaSection + sessionpart,\n 'a=ice-pwd:')[0];\n if (!(ufrag && pwd)) {\n return null;\n }\n return {\n usernameFragment: ufrag.substr(12),\n password: pwd.substr(10),\n };\n };\n\n \u002F\u002F Serializes ICE parameters to SDP.\n SDPUtils.writeIceParameters = function(params) {\n return 'a=ice-ufrag:' + params.usernameFragment + '\\r\\n' +\n 'a=ice-pwd:' + params.password + '\\r\\n';\n };\n\n \u002F\u002F Parses the SDP media section and returns RTCRtpParameters.\n SDPUtils.parseRtpParameters = function(mediaSection) {\n var description = {\n codecs: [],\n headerExtensions: [],\n fecMechanisms: [],\n rtcp: []\n };\n var lines = SDPUtils.splitLines(mediaSection);\n var mline = lines[0].split(' ');\n for (var i = 3; i \u003C mline.length; i++) { \u002F\u002F find all codecs from mline[3..]\n var pt = mline[i];\n var rtpmapline = SDPUtils.matchPrefix(\n mediaSection, 'a=rtpmap:' + pt + ' ')[0];\n if (rtpmapline) {\n var codec = SDPUtils.parseRtpMap(rtpmapline);\n var fmtps = SDPUtils.matchPrefix(\n mediaSection, 'a=fmtp:' + pt + ' ');\n \u002F\u002F Only the first a=fmtp:\u003Cpt\u003E is considered.\n codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {};\n codec.rtcpFeedback = SDPUtils.matchPrefix(\n mediaSection, 'a=rtcp-fb:' + pt + ' ')\n .map(SDPUtils.parseRtcpFb);\n description.codecs.push(codec);\n \u002F\u002F parse FEC mechanisms from rtpmap lines.\n switch (codec.name.toUpperCase()) {\n case 'RED':\n case 'ULPFEC':\n description.fecMechanisms.push(codec.name.toUpperCase());\n break;\n }\n }\n }\n SDPUtils.matchPrefix(mediaSection, 'a=extmap:').forEach(function(line) {\n description.headerExtensions.push(SDPUtils.parseExtmap(line));\n });\n \u002F\u002F FIXME: parse rtcp.\n return description;\n };\n\n \u002F\u002F Generates parts of the SDP media section describing the capabilities \u002F\n \u002F\u002F parameters.\n SDPUtils.writeRtpDescription = function(kind, caps) {\n var sdp = '';\n\n \u002F\u002F Build the mline.\n sdp += 'm=' + kind + ' ';\n sdp += caps.codecs.length \u003E 0 ? '9' : '0'; \u002F\u002F reject if no codecs.\n sdp += ' UDP\u002FTLS\u002FRTP\u002FSAVPF ';\n sdp += caps.codecs.map(function(codec) {\n if (codec.preferredPayloadType !== undefined) {\n return codec.preferredPayloadType;\n }\n return codec.payloadType;\n }).join(' ') + '\\r\\n';\n\n sdp += 'c=IN IP4 0.0.0.0\\r\\n';\n sdp += 'a=rtcp:9 IN IP4 0.0.0.0\\r\\n';\n\n \u002F\u002F Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb.\n caps.codecs.forEach(function(codec) {\n sdp += SDPUtils.writeRtpMap(codec);\n sdp += SDPUtils.writeFmtp(codec);\n sdp += SDPUtils.writeRtcpFb(codec);\n });\n var maxptime = 0;\n caps.codecs.forEach(function(codec) {\n if (codec.maxptime \u003E maxptime) {\n maxptime = codec.maxptime;\n }\n });\n if (maxptime \u003E 0) {\n sdp += 'a=maxptime:' + maxptime + '\\r\\n';\n }\n sdp += 'a=rtcp-mux\\r\\n';\n\n if (caps.headerExtensions) {\n caps.headerExtensions.forEach(function(extension) {\n sdp += SDPUtils.writeExtmap(extension);\n });\n }\n \u002F\u002F FIXME: write fecMechanisms.\n return sdp;\n };\n\n \u002F\u002F Parses the SDP media section and returns an array of\n \u002F\u002F RTCRtpEncodingParameters.\n SDPUtils.parseRtpEncodingParameters = function(mediaSection) {\n var encodingParameters = [];\n var description = SDPUtils.parseRtpParameters(mediaSection);\n var hasRed = description.fecMechanisms.indexOf('RED') !== -1;\n var hasUlpfec = description.fecMechanisms.indexOf('ULPFEC') !== -1;\n\n \u002F\u002F filter a=ssrc:... cname:, ignore PlanB-msid\n var ssrcs = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')\n .map(function(line) {\n return SDPUtils.parseSsrcMedia(line);\n })\n .filter(function(parts) {\n return parts.attribute === 'cname';\n });\n var primarySsrc = ssrcs.length \u003E 0 && ssrcs[0].ssrc;\n var secondarySsrc;\n\n var flows = SDPUtils.matchPrefix(mediaSection, 'a=ssrc-group:FID')\n .map(function(line) {\n var parts = line.substr(17).split(' ');\n return parts.map(function(part) {\n return parseInt(part, 10);\n });\n });\n if (flows.length \u003E 0 && flows[0].length \u003E 1 && flows[0][0] === primarySsrc) {\n secondarySsrc = flows[0][1];\n }\n\n description.codecs.forEach(function(codec) {\n if (codec.name.toUpperCase() === 'RTX' && codec.parameters.apt) {\n var encParam = {\n ssrc: primarySsrc,\n codecPayloadType: parseInt(codec.parameters.apt, 10)\n };\n if (primarySsrc && secondarySsrc) {\n encParam.rtx = {ssrc: secondarySsrc};\n }\n encodingParameters.push(encParam);\n if (hasRed) {\n encParam = JSON.parse(JSON.stringify(encParam));\n encParam.fec = {\n ssrc: primarySsrc,\n mechanism: hasUlpfec ? 'red+ulpfec' : 'red'\n };\n encodingParameters.push(encParam);\n }\n }\n });\n if (encodingParameters.length === 0 && primarySsrc) {\n encodingParameters.push({\n ssrc: primarySsrc\n });\n }\n\n \u002F\u002F we support both b=AS and b=TIAS but interpret AS as TIAS.\n var bandwidth = SDPUtils.matchPrefix(mediaSection, 'b=');\n if (bandwidth.length) {\n if (bandwidth[0].indexOf('b=TIAS:') === 0) {\n bandwidth = parseInt(bandwidth[0].substr(7), 10);\n } else if (bandwidth[0].indexOf('b=AS:') === 0) {\n \u002F\u002F use formula from JSEP to convert b=AS to TIAS value.\n bandwidth = parseInt(bandwidth[0].substr(5), 10) * 1000 * 0.95\n - (50 * 40 * 8);\n } else {\n bandwidth = undefined;\n }\n encodingParameters.forEach(function(params) {\n params.maxBitrate = bandwidth;\n });\n }\n return encodingParameters;\n };\n\n \u002F\u002F parses http:\u002F\u002Fdraft.ortc.org\u002F#rtcrtcpparameters*\n SDPUtils.parseRtcpParameters = function(mediaSection) {\n var rtcpParameters = {};\n\n \u002F\u002F Gets the first SSRC. Note tha with RTX there might be multiple\n \u002F\u002F SSRCs.\n var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')\n .map(function(line) {\n return SDPUtils.parseSsrcMedia(line);\n })\n .filter(function(obj) {\n return obj.attribute === 'cname';\n })[0];\n if (remoteSsrc) {\n rtcpParameters.cname = remoteSsrc.value;\n rtcpParameters.ssrc = remoteSsrc.ssrc;\n }\n\n \u002F\u002F Edge uses the compound attribute instead of reducedSize\n \u002F\u002F compound is !reducedSize\n var rsize = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-rsize');\n rtcpParameters.reducedSize = rsize.length \u003E 0;\n rtcpParameters.compound = rsize.length === 0;\n\n \u002F\u002F parses the rtcp-mux attrіbute.\n \u002F\u002F Note that Edge does not support unmuxed RTCP.\n var mux = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-mux');\n rtcpParameters.mux = mux.length \u003E 0;\n\n return rtcpParameters;\n };\n\n \u002F\u002F parses either a=msid: or a=ssrc:... msid lines and returns\n \u002F\u002F the id of the MediaStream and MediaStreamTrack.\n SDPUtils.parseMsid = function(mediaSection) {\n var parts;\n var spec = SDPUtils.matchPrefix(mediaSection, 'a=msid:');\n if (spec.length === 1) {\n parts = spec[0].substr(7).split(' ');\n return {stream: parts[0], track: parts[1]};\n }\n var planB = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')\n .map(function(line) {\n return SDPUtils.parseSsrcMedia(line);\n })\n .filter(function(msidParts) {\n return msidParts.attribute === 'msid';\n });\n if (planB.length \u003E 0) {\n parts = planB[0].value.split(' ');\n return {stream: parts[0], track: parts[1]};\n }\n };\n\n \u002F\u002F SCTP\n \u002F\u002F parses draft-ietf-mmusic-sctp-sdp-26 first and falls back\n \u002F\u002F to draft-ietf-mmusic-sctp-sdp-05\n SDPUtils.parseSctpDescription = function(mediaSection) {\n var mline = SDPUtils.parseMLine(mediaSection);\n var maxSizeLine = SDPUtils.matchPrefix(mediaSection, 'a=max-message-size:');\n var maxMessageSize;\n if (maxSizeLine.length \u003E 0) {\n maxMessageSize = parseInt(maxSizeLine[0].substr(19), 10);\n }\n if (isNaN(maxMessageSize)) {\n maxMessageSize = 65536;\n }\n var sctpPort = SDPUtils.matchPrefix(mediaSection, 'a=sctp-port:');\n if (sctpPort.length \u003E 0) {\n return {\n port: parseInt(sctpPort[0].substr(12), 10),\n protocol: mline.fmt,\n maxMessageSize: maxMessageSize\n };\n }\n var sctpMapLines = SDPUtils.matchPrefix(mediaSection, 'a=sctpmap:');\n if (sctpMapLines.length \u003E 0) {\n var parts = SDPUtils.matchPrefix(mediaSection, 'a=sctpmap:')[0]\n .substr(10)\n .split(' ');\n return {\n port: parseInt(parts[0], 10),\n protocol: parts[1],\n maxMessageSize: maxMessageSize\n };\n }\n };\n\n \u002F\u002F SCTP\n \u002F\u002F outputs the draft-ietf-mmusic-sctp-sdp-26 version that all browsers\n \u002F\u002F support by now receiving in this format, unless we originally parsed\n \u002F\u002F as the draft-ietf-mmusic-sctp-sdp-05 format (indicated by the m-line\n \u002F\u002F protocol of DTLS\u002FSCTP -- without UDP\u002F or TCP\u002F)\n SDPUtils.writeSctpDescription = function(media, sctp) {\n var output = [];\n if (media.protocol !== 'DTLS\u002FSCTP') {\n output = [\n 'm=' + media.kind + ' 9 ' + media.protocol + ' ' + sctp.protocol + '\\r\\n',\n 'c=IN IP4 0.0.0.0\\r\\n',\n 'a=sctp-port:' + sctp.port + '\\r\\n'\n ];\n } else {\n output = [\n 'm=' + media.kind + ' 9 ' + media.protocol + ' ' + sctp.port + '\\r\\n',\n 'c=IN IP4 0.0.0.0\\r\\n',\n 'a=sctpmap:' + sctp.port + ' ' + sctp.protocol + ' 65535\\r\\n'\n ];\n }\n if (sctp.maxMessageSize !== undefined) {\n output.push('a=max-message-size:' + sctp.maxMessageSize + '\\r\\n');\n }\n return output.join('');\n };\n\n \u002F\u002F Generate a session ID for SDP.\n \u002F\u002F https:\u002F\u002Ftools.ietf.org\u002Fhtml\u002Fdraft-ietf-rtcweb-jsep-20#section-5.2.1\n \u002F\u002F recommends using a cryptographically random +ve 64-bit value\n \u002F\u002F but right now this should be acceptable and within the right range\n SDPUtils.generateSessionId = function() {\n return Math.random().toString().substr(2, 21);\n };\n\n \u002F\u002F Write boilder plate for start of SDP\n \u002F\u002F sessId argument is optional - if not supplied it will\n \u002F\u002F be generated randomly\n \u002F\u002F sessVersion is optional and defaults to 2\n \u002F\u002F sessUser is optional and defaults to 'thisisadapterortc'\n SDPUtils.writeSessionBoilerplate = function(sessId, sessVer, sessUser) {\n var sessionId;\n var version = sessVer !== undefined ? sessVer : 2;\n if (sessId) {\n sessionId = sessId;\n } else {\n sessionId = SDPUtils.generateSessionId();\n }\n var user = sessUser || 'thisisadapterortc';\n \u002F\u002F FIXME: sess-id should be an NTP timestamp.\n return 'v=0\\r\\n' +\n 'o=' + user + ' ' + sessionId + ' ' + version +\n ' IN IP4 127.0.0.1\\r\\n' +\n 's=-\\r\\n' +\n 't=0 0\\r\\n';\n };\n\n SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) {\n var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps);\n\n \u002F\u002F Map ICE parameters (ufrag, pwd) to SDP.\n sdp += SDPUtils.writeIceParameters(\n transceiver.iceGatherer.getLocalParameters());\n\n \u002F\u002F Map DTLS parameters to SDP.\n sdp += SDPUtils.writeDtlsParameters(\n transceiver.dtlsTransport.getLocalParameters(),\n type === 'offer' ? 'actpass' : 'active');\n\n sdp += 'a=mid:' + transceiver.mid + '\\r\\n';\n\n if (transceiver.direction) {\n sdp += 'a=' + transceiver.direction + '\\r\\n';\n } else if (transceiver.rtpSender && transceiver.rtpReceiver) {\n sdp += 'a=sendrecv\\r\\n';\n } else if (transceiver.rtpSender) {\n sdp += 'a=sendonly\\r\\n';\n } else if (transceiver.rtpReceiver) {\n sdp += 'a=recvonly\\r\\n';\n } else {\n sdp += 'a=inactive\\r\\n';\n }\n\n if (transceiver.rtpSender) {\n \u002F\u002F spec.\n var msid = 'msid:' + stream.id + ' ' +\n transceiver.rtpSender.track.id + '\\r\\n';\n sdp += 'a=' + msid;\n\n \u002F\u002F for Chrome.\n sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +\n ' ' + msid;\n if (transceiver.sendEncodingParameters[0].rtx) {\n sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +\n ' ' + msid;\n sdp += 'a=ssrc-group:FID ' +\n transceiver.sendEncodingParameters[0].ssrc + ' ' +\n transceiver.sendEncodingParameters[0].rtx.ssrc +\n '\\r\\n';\n }\n }\n \u002F\u002F FIXME: this should be written by writeRtpDescription.\n sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +\n ' cname:' + SDPUtils.localCName + '\\r\\n';\n if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) {\n sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +\n ' cname:' + SDPUtils.localCName + '\\r\\n';\n }\n return sdp;\n };\n\n \u002F\u002F Gets the direction from the mediaSection or the sessionpart.\n SDPUtils.getDirection = function(mediaSection, sessionpart) {\n \u002F\u002F Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv.\n var lines = SDPUtils.splitLines(mediaSection);\n for (var i = 0; i \u003C lines.length; i++) {\n switch (lines[i]) {\n case 'a=sendrecv':\n case 'a=sendonly':\n case 'a=recvonly':\n case 'a=inactive':\n return lines[i].substr(2);\n \u002F\u002F FIXME: What should happen here?\n }\n }\n if (sessionpart) {\n return SDPUtils.getDirection(sessionpart);\n }\n return 'sendrecv';\n };\n\n SDPUtils.getKind = function(mediaSection) {\n var lines = SDPUtils.splitLines(mediaSection);\n var mline = lines[0].split(' ');\n return mline[0].substr(2);\n };\n\n SDPUtils.isRejected = function(mediaSection) {\n return mediaSection.split(' ', 2)[1] === '0';\n };\n\n SDPUtils.parseMLine = function(mediaSection) {\n var lines = SDPUtils.splitLines(mediaSection);\n var parts = lines[0].substr(2).split(' ');\n return {\n kind: parts[0],\n port: parseInt(parts[1], 10),\n protocol: parts[2],\n fmt: parts.slice(3).join(' ')\n };\n };\n\n SDPUtils.parseOLine = function(mediaSection) {\n var line = SDPUtils.matchPrefix(mediaSection, 'o=')[0];\n var parts = line.substr(2).split(' ');\n return {\n username: parts[0],\n sessionId: parts[1],\n sessionVersion: parseInt(parts[2], 10),\n netType: parts[3],\n addressType: parts[4],\n address: parts[5]\n };\n };\n\n \u002F\u002F a very naive interpretation of a valid SDP.\n SDPUtils.isValidSDP = function(blob) {\n if (typeof blob !== 'string' || blob.length === 0) {\n return false;\n }\n var lines = SDPUtils.splitLines(blob);\n for (var i = 0; i \u003C lines.length; i++) {\n if (lines[i].length \u003C 2 || lines[i].charAt(1) !== '=') {\n return false;\n }\n \u002F\u002F TODO: check the modifier a bit more.\n }\n return true;\n };\n\n \u002F\u002F Expose public methods.\n {\n module.exports = SDPUtils;\n }\n });\n\n function fixStatsType(stat) {\n return {\n inboundrtp: 'inbound-rtp',\n outboundrtp: 'outbound-rtp',\n candidatepair: 'candidate-pair',\n localcandidate: 'local-candidate',\n remotecandidate: 'remote-candidate'\n }[stat.type] || stat.type;\n }\n\n function writeMediaSection(transceiver, caps, type, stream, dtlsRole) {\n var sdp$1 = sdp.writeRtpDescription(transceiver.kind, caps);\n\n \u002F\u002F Map ICE parameters (ufrag, pwd) to SDP.\n sdp$1 += sdp.writeIceParameters(\n transceiver.iceGatherer.getLocalParameters());\n\n \u002F\u002F Map DTLS parameters to SDP.\n sdp$1 += sdp.writeDtlsParameters(\n transceiver.dtlsTransport.getLocalParameters(),\n type === 'offer' ? 'actpass' : dtlsRole || 'active');\n\n sdp$1 += 'a=mid:' + transceiver.mid + '\\r\\n';\n\n if (transceiver.rtpSender && transceiver.rtpReceiver) {\n sdp$1 += 'a=sendrecv\\r\\n';\n } else if (transceiver.rtpSender) {\n sdp$1 += 'a=sendonly\\r\\n';\n } else if (transceiver.rtpReceiver) {\n sdp$1 += 'a=recvonly\\r\\n';\n } else {\n sdp$1 += 'a=inactive\\r\\n';\n }\n\n if (transceiver.rtpSender) {\n var trackId = transceiver.rtpSender._initialTrackId ||\n transceiver.rtpSender.track.id;\n transceiver.rtpSender._initialTrackId = trackId;\n \u002F\u002F spec.\n var msid = 'msid:' + (stream ? stream.id : '-') + ' ' +\n trackId + '\\r\\n';\n sdp$1 += 'a=' + msid;\n \u002F\u002F for Chrome. Legacy should no longer be required.\n sdp$1 += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +\n ' ' + msid;\n\n \u002F\u002F RTX\n if (transceiver.sendEncodingParameters[0].rtx) {\n sdp$1 += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +\n ' ' + msid;\n sdp$1 += 'a=ssrc-group:FID ' +\n transceiver.sendEncodingParameters[0].ssrc + ' ' +\n transceiver.sendEncodingParameters[0].rtx.ssrc +\n '\\r\\n';\n }\n }\n \u002F\u002F FIXME: this should be written by writeRtpDescription.\n sdp$1 += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +\n ' cname:' + sdp.localCName + '\\r\\n';\n if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) {\n sdp$1 += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +\n ' cname:' + sdp.localCName + '\\r\\n';\n }\n return sdp$1;\n }\n\n \u002F\u002F Edge does not like\n \u002F\u002F 1) stun: filtered after 14393 unless ?transport=udp is present\n \u002F\u002F 2) turn: that does not have all of turn:host:port?transport=udp\n \u002F\u002F 3) turn: with ipv6 addresses\n \u002F\u002F 4) turn: occurring muliple times\n function filterIceServers$1(iceServers, edgeVersion) {\n var hasTurn = false;\n iceServers = JSON.parse(JSON.stringify(iceServers));\n return iceServers.filter(function(server) {\n if (server && (server.urls || server.url)) {\n var urls = server.urls || server.url;\n if (server.url && !server.urls) {\n console.warn('RTCIceServer.url is deprecated! Use urls instead.');\n }\n var isString = typeof urls === 'string';\n if (isString) {\n urls = [urls];\n }\n urls = urls.filter(function(url) {\n var validTurn = url.indexOf('turn:') === 0 &&\n url.indexOf('transport=udp') !== -1 &&\n url.indexOf('turn:[') === -1 &&\n !hasTurn;\n\n if (validTurn) {\n hasTurn = true;\n return true;\n }\n return url.indexOf('stun:') === 0 && edgeVersion \u003E= 14393 &&\n url.indexOf('?transport=udp') === -1;\n });\n\n delete server.url;\n server.urls = isString ? urls[0] : urls;\n return !!urls.length;\n }\n });\n }\n\n \u002F\u002F Determines the intersection of local and remote capabilities.\n function getCommonCapabilities(localCapabilities, remoteCapabilities) {\n var commonCapabilities = {\n codecs: [],\n headerExtensions: [],\n fecMechanisms: []\n };\n\n var findCodecByPayloadType = function(pt, codecs) {\n pt = parseInt(pt, 10);\n for (var i = 0; i \u003C codecs.length; i++) {\n if (codecs[i].payloadType === pt ||\n codecs[i].preferredPayloadType === pt) {\n return codecs[i];\n }\n }\n };\n\n var rtxCapabilityMatches = function(lRtx, rRtx, lCodecs, rCodecs) {\n var lCodec = findCodecByPayloadType(lRtx.parameters.apt, lCodecs);\n var rCodec = findCodecByPayloadType(rRtx.parameters.apt, rCodecs);\n return lCodec && rCodec &&\n lCodec.name.toLowerCase() === rCodec.name.toLowerCase();\n };\n\n localCapabilities.codecs.forEach(function(lCodec) {\n for (var i = 0; i \u003C remoteCapabilities.codecs.length; i++) {\n var rCodec = remoteCapabilities.codecs[i];\n if (lCodec.name.toLowerCase() === rCodec.name.toLowerCase() &&\n lCodec.clockRate === rCodec.clockRate) {\n if (lCodec.name.toLowerCase() === 'rtx' &&\n lCodec.parameters && rCodec.parameters.apt) {\n \u002F\u002F for RTX we need to find the local rtx that has a apt\n \u002F\u002F which points to the same local codec as the remote one.\n if (!rtxCapabilityMatches(lCodec, rCodec,\n localCapabilities.codecs, remoteCapabilities.codecs)) {\n continue;\n }\n }\n rCodec = JSON.parse(JSON.stringify(rCodec)); \u002F\u002F deepcopy\n \u002F\u002F number of channels is the highest common number of channels\n rCodec.numChannels = Math.min(lCodec.numChannels,\n rCodec.numChannels);\n \u002F\u002F push rCodec so we reply with offerer payload type\n commonCapabilities.codecs.push(rCodec);\n\n \u002F\u002F determine common feedback mechanisms\n rCodec.rtcpFeedback = rCodec.rtcpFeedback.filter(function(fb) {\n for (var j = 0; j \u003C lCodec.rtcpFeedback.length; j++) {\n if (lCodec.rtcpFeedback[j].type === fb.type &&\n lCodec.rtcpFeedback[j].parameter === fb.parameter) {\n return true;\n }\n }\n return false;\n });\n \u002F\u002F FIXME: also need to determine .parameters\n \u002F\u002F see https:\u002F\u002Fgithub.com\u002Fopenpeer\u002Fortc\u002Fissues\u002F569\n break;\n }\n }\n });\n\n localCapabilities.headerExtensions.forEach(function(lHeaderExtension) {\n for (var i = 0; i \u003C remoteCapabilities.headerExtensions.length;\n i++) {\n var rHeaderExtension = remoteCapabilities.headerExtensions[i];\n if (lHeaderExtension.uri === rHeaderExtension.uri) {\n commonCapabilities.headerExtensions.push(rHeaderExtension);\n break;\n }\n }\n });\n\n \u002F\u002F FIXME: fecMechanisms\n return commonCapabilities;\n }\n\n \u002F\u002F is action=setLocalDescription with type allowed in signalingState\n function isActionAllowedInSignalingState(action, type, signalingState) {\n return {\n offer: {\n setLocalDescription: ['stable', 'have-local-offer'],\n setRemoteDescription: ['stable', 'have-remote-offer']\n },\n answer: {\n setLocalDescription: ['have-remote-offer', 'have-local-pranswer'],\n setRemoteDescription: ['have-local-offer', 'have-remote-pranswer']\n }\n }[type][action].indexOf(signalingState) !== -1;\n }\n\n function maybeAddCandidate(iceTransport, candidate) {\n \u002F\u002F Edge's internal representation adds some fields therefore\n \u002F\u002F not all fieldѕ are taken into account.\n var alreadyAdded = iceTransport.getRemoteCandidates()\n .find(function(remoteCandidate) {\n return candidate.foundation === remoteCandidate.foundation &&\n candidate.ip === remoteCandidate.ip &&\n candidate.port === remoteCandidate.port &&\n candidate.priority === remoteCandidate.priority &&\n candidate.protocol === remoteCandidate.protocol &&\n candidate.type === remoteCandidate.type;\n });\n if (!alreadyAdded) {\n iceTransport.addRemoteCandidate(candidate);\n }\n return !alreadyAdded;\n }\n\n\n function makeError(name, description) {\n var e = new Error(description);\n e.name = name;\n \u002F\u002F legacy error codes from https:\u002F\u002Fheycam.github.io\u002Fwebidl\u002F#idl-DOMException-error-names\n e.code = {\n NotSupportedError: 9,\n InvalidStateError: 11,\n InvalidAccessError: 15,\n TypeError: undefined,\n OperationError: undefined\n }[name];\n return e;\n }\n\n var rtcpeerconnection = function(window, edgeVersion) {\n \u002F\u002F https:\u002F\u002Fw3c.github.io\u002Fmediacapture-main\u002F#mediastream\n \u002F\u002F Helper function to add the track to the stream and\n \u002F\u002F dispatch the event ourselves.\n function addTrackToStreamAndFireEvent(track, stream) {\n stream.addTrack(track);\n stream.dispatchEvent(new window.MediaStreamTrackEvent('addtrack',\n {track: track}));\n }\n\n function removeTrackFromStreamAndFireEvent(track, stream) {\n stream.removeTrack(track);\n stream.dispatchEvent(new window.MediaStreamTrackEvent('removetrack',\n {track: track}));\n }\n\n function fireAddTrack(pc, track, receiver, streams) {\n var trackEvent = new Event('track');\n trackEvent.track = track;\n trackEvent.receiver = receiver;\n trackEvent.transceiver = {receiver: receiver};\n trackEvent.streams = streams;\n window.setTimeout(function() {\n pc._dispatchEvent('track', trackEvent);\n });\n }\n\n var RTCPeerConnection = function(config) {\n var pc = this;\n\n var _eventTarget = document.createDocumentFragment();\n ['addEventListener', 'removeEventListener', 'dispatchEvent']\n .forEach(function(method) {\n pc[method] = _eventTarget[method].bind(_eventTarget);\n });\n\n this.canTrickleIceCandidates = null;\n\n this.needNegotiation = false;\n\n this.localStreams = [];\n this.remoteStreams = [];\n\n this._localDescription = null;\n this._remoteDescription = null;\n\n this.signalingState = 'stable';\n this.iceConnectionState = 'new';\n this.connectionState = 'new';\n this.iceGatheringState = 'new';\n\n config = JSON.parse(JSON.stringify(config || {}));\n\n this.usingBundle = config.bundlePolicy === 'max-bundle';\n if (config.rtcpMuxPolicy === 'negotiate') {\n throw(makeError('NotSupportedError',\n 'rtcpMuxPolicy \\'negotiate\\' is not supported'));\n } else if (!config.rtcpMuxPolicy) {\n config.rtcpMuxPolicy = 'require';\n }\n\n switch (config.iceTransportPolicy) {\n case 'all':\n case 'relay':\n break;\n default:\n config.iceTransportPolicy = 'all';\n break;\n }\n\n switch (config.bundlePolicy) {\n case 'balanced':\n case 'max-compat':\n case 'max-bundle':\n break;\n default:\n config.bundlePolicy = 'balanced';\n break;\n }\n\n config.iceServers = filterIceServers$1(config.iceServers || [], edgeVersion);\n\n this._iceGatherers = [];\n if (config.iceCandidatePoolSize) {\n for (var i = config.iceCandidatePoolSize; i \u003E 0; i--) {\n this._iceGatherers.push(new window.RTCIceGatherer({\n iceServers: config.iceServers,\n gatherPolicy: config.iceTransportPolicy\n }));\n }\n } else {\n config.iceCandidatePoolSize = 0;\n }\n\n this._config = config;\n\n \u002F\u002F per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ...\n \u002F\u002F everything that is needed to describe a SDP m-line.\n this.transceivers = [];\n\n this._sdpSessionId = sdp.generateSessionId();\n this._sdpSessionVersion = 0;\n\n this._dtlsRole = undefined; \u002F\u002F role for a=setup to use in answers.\n\n this._isClosed = false;\n };\n\n Object.defineProperty(RTCPeerConnection.prototype, 'localDescription', {\n configurable: true,\n get: function() {\n return this._localDescription;\n }\n });\n Object.defineProperty(RTCPeerConnection.prototype, 'remoteDescription', {\n configurable: true,\n get: function() {\n return this._remoteDescription;\n }\n });\n\n \u002F\u002F set up event handlers on prototype\n RTCPeerConnection.prototype.onicecandidate = null;\n RTCPeerConnection.prototype.onaddstream = null;\n RTCPeerConnection.prototype.ontrack = null;\n RTCPeerConnection.prototype.onremovestream = null;\n RTCPeerConnection.prototype.onsignalingstatechange = null;\n RTCPeerConnection.prototype.oniceconnectionstatechange = null;\n RTCPeerConnection.prototype.onconnectionstatechange = null;\n RTCPeerConnection.prototype.onicegatheringstatechange = null;\n RTCPeerConnection.prototype.onnegotiationneeded = null;\n RTCPeerConnection.prototype.ondatachannel = null;\n\n RTCPeerConnection.prototype._dispatchEvent = function(name, event) {\n if (this._isClosed) {\n return;\n }\n this.dispatchEvent(event);\n if (typeof this['on' + name] === 'function') {\n this['on' + name](event);\n }\n };\n\n RTCPeerConnection.prototype._emitGatheringStateChange = function() {\n var event = new Event('icegatheringstatechange');\n this._dispatchEvent('icegatheringstatechange', event);\n };\n\n RTCPeerConnection.prototype.getConfiguration = function() {\n return this._config;\n };\n\n RTCPeerConnection.prototype.getLocalStreams = function() {\n return this.localStreams;\n };\n\n RTCPeerConnection.prototype.getRemoteStreams = function() {\n return this.remoteStreams;\n };\n\n \u002F\u002F internal helper to create a transceiver object.\n \u002F\u002F (which is not yet the same as the WebRTC 1.0 transceiver)\n RTCPeerConnection.prototype._createTransceiver = function(kind, doNotAdd) {\n var hasBundleTransport = this.transceivers.length \u003E 0;\n var transceiver = {\n track: null,\n iceGatherer: null,\n iceTransport: null,\n dtlsTransport: null,\n localCapabilities: null,\n remoteCapabilities: null,\n rtpSender: null,\n rtpReceiver: null,\n kind: kind,\n mid: null,\n sendEncodingParameters: null,\n recvEncodingParameters: null,\n stream: null,\n associatedRemoteMediaStreams: [],\n wantReceive: true\n };\n if (this.usingBundle && hasBundleTransport) {\n transceiver.iceTransport = this.transceivers[0].iceTransport;\n transceiver.dtlsTransport = this.transceivers[0].dtlsTransport;\n } else {\n var transports = this._createIceAndDtlsTransports();\n transceiver.iceTransport = transports.iceTransport;\n transceiver.dtlsTransport = transports.dtlsTransport;\n }\n if (!doNotAdd) {\n this.transceivers.push(transceiver);\n }\n return transceiver;\n };\n\n RTCPeerConnection.prototype.addTrack = function(track, stream) {\n if (this._isClosed) {\n throw makeError('InvalidStateError',\n 'Attempted to call addTrack on a closed peerconnection.');\n }\n\n var alreadyExists = this.transceivers.find(function(s) {\n return s.track === track;\n });\n\n if (alreadyExists) {\n throw makeError('InvalidAccessError', 'Track already exists.');\n }\n\n var transceiver;\n for (var i = 0; i \u003C this.transceivers.length; i++) {\n if (!this.transceivers[i].track &&\n this.transceivers[i].kind === track.kind) {\n transceiver = this.transceivers[i];\n }\n }\n if (!transceiver) {\n transceiver = this._createTransceiver(track.kind);\n }\n\n this._maybeFireNegotiationNeeded();\n\n if (this.localStreams.indexOf(stream) === -1) {\n this.localStreams.push(stream);\n }\n\n transceiver.track = track;\n transceiver.stream = stream;\n transceiver.rtpSender = new window.RTCRtpSender(track,\n transceiver.dtlsTransport);\n return transceiver.rtpSender;\n };\n\n RTCPeerConnection.prototype.addStream = function(stream) {\n var pc = this;\n if (edgeVersion \u003E= 15025) {\n stream.getTracks().forEach(function(track) {\n pc.addTrack(track, stream);\n });\n } else {\n \u002F\u002F Clone is necessary for local demos mostly, attaching directly\n \u002F\u002F to two different senders does not work (build 10547).\n \u002F\u002F Fixed in 15025 (or earlier)\n var clonedStream = stream.clone();\n stream.getTracks().forEach(function(track, idx) {\n var clonedTrack = clonedStream.getTracks()[idx];\n track.addEventListener('enabled', function(event) {\n clonedTrack.enabled = event.enabled;\n });\n });\n clonedStream.getTracks().forEach(function(track) {\n pc.addTrack(track, clonedStream);\n });\n }\n };\n\n RTCPeerConnection.prototype.removeTrack = function(sender) {\n if (this._isClosed) {\n throw makeError('InvalidStateError',\n 'Attempted to call removeTrack on a closed peerconnection.');\n }\n\n if (!(sender instanceof window.RTCRtpSender)) {\n throw new TypeError('Argument 1 of RTCPeerConnection.removeTrack ' +\n 'does not implement interface RTCRtpSender.');\n }\n\n var transceiver = this.transceivers.find(function(t) {\n return t.rtpSender === sender;\n });\n\n if (!transceiver) {\n throw makeError('InvalidAccessError',\n 'Sender was not created by this connection.');\n }\n var stream = transceiver.stream;\n\n transceiver.rtpSender.stop();\n transceiver.rtpSender = null;\n transceiver.track = null;\n transceiver.stream = null;\n\n \u002F\u002F remove the stream from the set of local streams\n var localStreams = this.transceivers.map(function(t) {\n return t.stream;\n });\n if (localStreams.indexOf(stream) === -1 &&\n this.localStreams.indexOf(stream) \u003E -1) {\n this.localStreams.splice(this.localStreams.indexOf(stream), 1);\n }\n\n this._maybeFireNegotiationNeeded();\n };\n\n RTCPeerConnection.prototype.removeStream = function(stream) {\n var pc = this;\n stream.getTracks().forEach(function(track) {\n var sender = pc.getSenders().find(function(s) {\n return s.track === track;\n });\n if (sender) {\n pc.removeTrack(sender);\n }\n });\n };\n\n RTCPeerConnection.prototype.getSenders = function() {\n return this.transceivers.filter(function(transceiver) {\n return !!transceiver.rtpSender;\n })\n .map(function(transceiver) {\n return transceiver.rtpSender;\n });\n };\n\n RTCPeerConnection.prototype.getReceivers = function() {\n return this.transceivers.filter(function(transceiver) {\n return !!transceiver.rtpReceiver;\n })\n .map(function(transceiver) {\n return transceiver.rtpReceiver;\n });\n };\n\n\n RTCPeerConnection.prototype._createIceGatherer = function(sdpMLineIndex,\n usingBundle) {\n var pc = this;\n if (usingBundle && sdpMLineIndex \u003E 0) {\n return this.transceivers[0].iceGatherer;\n } else if (this._iceGatherers.length) {\n return this._iceGatherers.shift();\n }\n var iceGatherer = new window.RTCIceGatherer({\n iceServers: this._config.iceServers,\n gatherPolicy: this._config.iceTransportPolicy\n });\n Object.defineProperty(iceGatherer, 'state',\n {value: 'new', writable: true}\n );\n\n this.transceivers[sdpMLineIndex].bufferedCandidateEvents = [];\n this.transceivers[sdpMLineIndex].bufferCandidates = function(event) {\n var end = !event.candidate || Object.keys(event.candidate).length === 0;\n \u002F\u002F polyfill since RTCIceGatherer.state is not implemented in\n \u002F\u002F Edge 10547 yet.\n iceGatherer.state = end ? 'completed' : 'gathering';\n if (pc.transceivers[sdpMLineIndex].bufferedCandidateEvents !== null) {\n pc.transceivers[sdpMLineIndex].bufferedCandidateEvents.push(event);\n }\n };\n iceGatherer.addEventListener('localcandidate',\n this.transceivers[sdpMLineIndex].bufferCandidates);\n return iceGatherer;\n };\n\n \u002F\u002F start gathering from an RTCIceGatherer.\n RTCPeerConnection.prototype._gather = function(mid, sdpMLineIndex) {\n var pc = this;\n var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer;\n if (iceGatherer.onlocalcandidate) {\n return;\n }\n var bufferedCandidateEvents =\n this.transceivers[sdpMLineIndex].bufferedCandidateEvents;\n this.transceivers[sdpMLineIndex].bufferedCandidateEvents = null;\n iceGatherer.removeEventListener('localcandidate',\n this.transceivers[sdpMLineIndex].bufferCandidates);\n iceGatherer.onlocalcandidate = function(evt) {\n if (pc.usingBundle && sdpMLineIndex \u003E 0) {\n \u002F\u002F if we know that we use bundle we can drop candidates with\n \u002F\u002F ѕdpMLineIndex \u003E 0. If we don't do this then our state gets\n \u002F\u002F confused since we dispose the extra ice gatherer.\n return;\n }\n var event = new Event('icecandidate');\n event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex};\n\n var cand = evt.candidate;\n \u002F\u002F Edge emits an empty object for RTCIceCandidateComplete‥\n var end = !cand || Object.keys(cand).length === 0;\n if (end) {\n \u002F\u002F polyfill since RTCIceGatherer.state is not implemented in\n \u002F\u002F Edge 10547 yet.\n if (iceGatherer.state === 'new' || iceGatherer.state === 'gathering') {\n iceGatherer.state = 'completed';\n }\n } else {\n if (iceGatherer.state === 'new') {\n iceGatherer.state = 'gathering';\n }\n \u002F\u002F RTCIceCandidate doesn't have a component, needs to be added\n cand.component = 1;\n \u002F\u002F also the usernameFragment. TODO: update SDP to take both variants.\n cand.ufrag = iceGatherer.getLocalParameters().usernameFragment;\n\n var serializedCandidate = sdp.writeCandidate(cand);\n event.candidate = Object.assign(event.candidate,\n sdp.parseCandidate(serializedCandidate));\n\n event.candidate.candidate = serializedCandidate;\n event.candidate.toJSON = function() {\n return {\n candidate: event.candidate.candidate,\n sdpMid: event.candidate.sdpMid,\n sdpMLineIndex: event.candidate.sdpMLineIndex,\n usernameFragment: event.candidate.usernameFragment\n };\n };\n }\n\n \u002F\u002F update local description.\n var sections = sdp.getMediaSections(pc._localDescription.sdp);\n if (!end) {\n sections[event.candidate.sdpMLineIndex] +=\n 'a=' + event.candidate.candidate + '\\r\\n';\n } else {\n sections[event.candidate.sdpMLineIndex] +=\n 'a=end-of-candidates\\r\\n';\n }\n pc._localDescription.sdp =\n sdp.getDescription(pc._localDescription.sdp) +\n sections.join('');\n var complete = pc.transceivers.every(function(transceiver) {\n return transceiver.iceGatherer &&\n transceiver.iceGatherer.state === 'completed';\n });\n\n if (pc.iceGatheringState !== 'gathering') {\n pc.iceGatheringState = 'gathering';\n pc._emitGatheringStateChange();\n }\n\n \u002F\u002F Emit candidate. Also emit null candidate when all gatherers are\n \u002F\u002F complete.\n if (!end) {\n pc._dispatchEvent('icecandidate', event);\n }\n if (complete) {\n pc._dispatchEvent('icecandidate', new Event('icecandidate'));\n pc.iceGatheringState = 'complete';\n pc._emitGatheringStateChange();\n }\n };\n\n \u002F\u002F emit already gathered candidates.\n window.setTimeout(function() {\n bufferedCandidateEvents.forEach(function(e) {\n iceGatherer.onlocalcandidate(e);\n });\n }, 0);\n };\n\n \u002F\u002F Create ICE transport and DTLS transport.\n RTCPeerConnection.prototype._createIceAndDtlsTransports = function() {\n var pc = this;\n var iceTransport = new window.RTCIceTransport(null);\n iceTransport.onicestatechange = function() {\n pc._updateIceConnectionState();\n pc._updateConnectionState();\n };\n\n var dtlsTransport = new window.RTCDtlsTransport(iceTransport);\n dtlsTransport.ondtlsstatechange = function() {\n pc._updateConnectionState();\n };\n dtlsTransport.onerror = function() {\n \u002F\u002F onerror does not set state to failed by itself.\n Object.defineProperty(dtlsTransport, 'state',\n {value: 'failed', writable: true});\n pc._updateConnectionState();\n };\n\n return {\n iceTransport: iceTransport,\n dtlsTransport: dtlsTransport\n };\n };\n\n \u002F\u002F Destroy ICE gatherer, ICE transport and DTLS transport.\n \u002F\u002F Without triggering the callbacks.\n RTCPeerConnection.prototype._disposeIceAndDtlsTransports = function(\n sdpMLineIndex) {\n var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer;\n if (iceGatherer) {\n delete iceGatherer.onlocalcandidate;\n delete this.transceivers[sdpMLineIndex].iceGatherer;\n }\n var iceTransport = this.transceivers[sdpMLineIndex].iceTransport;\n if (iceTransport) {\n delete iceTransport.onicestatechange;\n delete this.transceivers[sdpMLineIndex].iceTransport;\n }\n var dtlsTransport = this.transceivers[sdpMLineIndex].dtlsTransport;\n if (dtlsTransport) {\n delete dtlsTransport.ondtlsstatechange;\n delete dtlsTransport.onerror;\n delete this.transceivers[sdpMLineIndex].dtlsTransport;\n }\n };\n\n \u002F\u002F Start the RTP Sender and Receiver for a transceiver.\n RTCPeerConnection.prototype._transceive = function(transceiver,\n send, recv) {\n var params = getCommonCapabilities(transceiver.localCapabilities,\n transceiver.remoteCapabilities);\n if (send && transceiver.rtpSender) {\n params.encodings = transceiver.sendEncodingParameters;\n params.rtcp = {\n cname: sdp.localCName,\n compound: transceiver.rtcpParameters.compound\n };\n if (transceiver.recvEncodingParameters.length) {\n params.rtcp.ssrc = transceiver.recvEncodingParameters[0].ssrc;\n }\n transceiver.rtpSender.send(params);\n }\n if (recv && transceiver.rtpReceiver && params.codecs.length \u003E 0) {\n \u002F\u002F remove RTX field in Edge 14942\n if (transceiver.kind === 'video'\n && transceiver.recvEncodingParameters\n && edgeVersion \u003C 15019) {\n transceiver.recvEncodingParameters.forEach(function(p) {\n delete p.rtx;\n });\n }\n if (transceiver.recvEncodingParameters.length) {\n params.encodings = transceiver.recvEncodingParameters;\n } else {\n params.encodings = [{}];\n }\n params.rtcp = {\n compound: transceiver.rtcpParameters.compound\n };\n if (transceiver.rtcpParameters.cname) {\n params.rtcp.cname = transceiver.rtcpParameters.cname;\n }\n if (transceiver.sendEncodingParameters.length) {\n params.rtcp.ssrc = transceiver.sendEncodingParameters[0].ssrc;\n }\n transceiver.rtpReceiver.receive(params);\n }\n };\n\n RTCPeerConnection.prototype.setLocalDescription = function(description) {\n var pc = this;\n\n \u002F\u002F Note: pranswer is not supported.\n if (['offer', 'answer'].indexOf(description.type) === -1) {\n return Promise.reject(makeError('TypeError',\n 'Unsupported type \"' + description.type + '\"'));\n }\n\n if (!isActionAllowedInSignalingState('setLocalDescription',\n description.type, pc.signalingState) || pc._isClosed) {\n return Promise.reject(makeError('InvalidStateError',\n 'Can not set local ' + description.type +\n ' in state ' + pc.signalingState));\n }\n\n var sections;\n var sessionpart;\n if (description.type === 'offer') {\n \u002F\u002F VERY limited support for SDP munging. Limited to:\n \u002F\u002F * changing the order of codecs\n sections = sdp.splitSections(description.sdp);\n sessionpart = sections.shift();\n sections.forEach(function(mediaSection, sdpMLineIndex) {\n var caps = sdp.parseRtpParameters(mediaSection);\n pc.transceivers[sdpMLineIndex].localCapabilities = caps;\n });\n\n pc.transceivers.forEach(function(transceiver, sdpMLineIndex) {\n pc._gather(transceiver.mid, sdpMLineIndex);\n });\n } else if (description.type === 'answer') {\n sections = sdp.splitSections(pc._remoteDescription.sdp);\n sessionpart = sections.shift();\n var isIceLite = sdp.matchPrefix(sessionpart,\n 'a=ice-lite').length \u003E 0;\n sections.forEach(function(mediaSection, sdpMLineIndex) {\n var transceiver = pc.transceivers[sdpMLineIndex];\n var iceGatherer = transceiver.iceGatherer;\n var iceTransport = transceiver.iceTransport;\n var dtlsTransport = transceiver.dtlsTransport;\n var localCapabilities = transceiver.localCapabilities;\n var remoteCapabilities = transceiver.remoteCapabilities;\n\n \u002F\u002F treat bundle-only as not-rejected.\n var rejected = sdp.isRejected(mediaSection) &&\n sdp.matchPrefix(mediaSection, 'a=bundle-only').length === 0;\n\n if (!rejected && !transceiver.rejected) {\n var remoteIceParameters = sdp.getIceParameters(\n mediaSection, sessionpart);\n var remoteDtlsParameters = sdp.getDtlsParameters(\n mediaSection, sessionpart);\n if (isIceLite) {\n remoteDtlsParameters.role = 'server';\n }\n\n if (!pc.usingBundle || sdpMLineIndex === 0) {\n pc._gather(transceiver.mid, sdpMLineIndex);\n if (iceTransport.state === 'new') {\n iceTransport.start(iceGatherer, remoteIceParameters,\n isIceLite ? 'controlling' : 'controlled');\n }\n if (dtlsTransport.state === 'new') {\n dtlsTransport.start(remoteDtlsParameters);\n }\n }\n\n \u002F\u002F Calculate intersection of capabilities.\n var params = getCommonCapabilities(localCapabilities,\n remoteCapabilities);\n\n \u002F\u002F Start the RTCRtpSender. The RTCRtpReceiver for this\n \u002F\u002F transceiver has already been started in setRemoteDescription.\n pc._transceive(transceiver,\n params.codecs.length \u003E 0,\n false);\n }\n });\n }\n\n pc._localDescription = {\n type: description.type,\n sdp: description.sdp\n };\n if (description.type === 'offer') {\n pc._updateSignalingState('have-local-offer');\n } else {\n pc._updateSignalingState('stable');\n }\n\n return Promise.resolve();\n };\n\n RTCPeerConnection.prototype.setRemoteDescription = function(description) {\n var pc = this;\n\n \u002F\u002F Note: pranswer is not supported.\n if (['offer', 'answer'].indexOf(description.type) === -1) {\n return Promise.reject(makeError('TypeError',\n 'Unsupported type \"' + description.type + '\"'));\n }\n\n if (!isActionAllowedInSignalingState('setRemoteDescription',\n description.type, pc.signalingState) || pc._isClosed) {\n return Promise.reject(makeError('InvalidStateError',\n 'Can not set remote ' + description.type +\n ' in state ' + pc.signalingState));\n }\n\n var streams = {};\n pc.remoteStreams.forEach(function(stream) {\n streams[stream.id] = stream;\n });\n var receiverList = [];\n var sections = sdp.splitSections(description.sdp);\n var sessionpart = sections.shift();\n var isIceLite = sdp.matchPrefix(sessionpart,\n 'a=ice-lite').length \u003E 0;\n var usingBundle = sdp.matchPrefix(sessionpart,\n 'a=group:BUNDLE ').length \u003E 0;\n pc.usingBundle = usingBundle;\n var iceOptions = sdp.matchPrefix(sessionpart,\n 'a=ice-options:')[0];\n if (iceOptions) {\n pc.canTrickleIceCandidates = iceOptions.substr(14).split(' ')\n .indexOf('trickle') \u003E= 0;\n } else {\n pc.canTrickleIceCandidates = false;\n }\n\n sections.forEach(function(mediaSection, sdpMLineIndex) {\n var lines = sdp.splitLines(mediaSection);\n var kind = sdp.getKind(mediaSection);\n \u002F\u002F treat bundle-only as not-rejected.\n var rejected = sdp.isRejected(mediaSection) &&\n sdp.matchPrefix(mediaSection, 'a=bundle-only').length === 0;\n var protocol = lines[0].substr(2).split(' ')[2];\n\n var direction = sdp.getDirection(mediaSection, sessionpart);\n var remoteMsid = sdp.parseMsid(mediaSection);\n\n var mid = sdp.getMid(mediaSection) || sdp.generateIdentifier();\n\n \u002F\u002F Reject datachannels which are not implemented yet.\n if (rejected || (kind === 'application' && (protocol === 'DTLS\u002FSCTP' ||\n protocol === 'UDP\u002FDTLS\u002FSCTP'))) {\n \u002F\u002F TODO: this is dangerous in the case where a non-rejected m-line\n \u002F\u002F becomes rejected.\n pc.transceivers[sdpMLineIndex] = {\n mid: mid,\n kind: kind,\n protocol: protocol,\n rejected: true\n };\n return;\n }\n\n if (!rejected && pc.transceivers[sdpMLineIndex] &&\n pc.transceivers[sdpMLineIndex].rejected) {\n \u002F\u002F recycle a rejected transceiver.\n pc.transceivers[sdpMLineIndex] = pc._createTransceiver(kind, true);\n }\n\n var transceiver;\n var iceGatherer;\n var iceTransport;\n var dtlsTransport;\n var rtpReceiver;\n var sendEncodingParameters;\n var recvEncodingParameters;\n var localCapabilities;\n\n var track;\n \u002F\u002F FIXME: ensure the mediaSection has rtcp-mux set.\n var remoteCapabilities = sdp.parseRtpParameters(mediaSection);\n var remoteIceParameters;\n var remoteDtlsParameters;\n if (!rejected) {\n remoteIceParameters = sdp.getIceParameters(mediaSection,\n sessionpart);\n remoteDtlsParameters = sdp.getDtlsParameters(mediaSection,\n sessionpart);\n remoteDtlsParameters.role = 'client';\n }\n recvEncodingParameters =\n sdp.parseRtpEncodingParameters(mediaSection);\n\n var rtcpParameters = sdp.parseRtcpParameters(mediaSection);\n\n var isComplete = sdp.matchPrefix(mediaSection,\n 'a=end-of-candidates', sessionpart).length \u003E 0;\n var cands = sdp.matchPrefix(mediaSection, 'a=candidate:')\n .map(function(cand) {\n return sdp.parseCandidate(cand);\n })\n .filter(function(cand) {\n return cand.component === 1;\n });\n\n \u002F\u002F Check if we can use BUNDLE and dispose transports.\n if ((description.type === 'offer' || description.type === 'answer') &&\n !rejected && usingBundle && sdpMLineIndex \u003E 0 &&\n pc.transceivers[sdpMLineIndex]) {\n pc._disposeIceAndDtlsTransports(sdpMLineIndex);\n pc.transceivers[sdpMLineIndex].iceGatherer =\n pc.transceivers[0].iceGatherer;\n pc.transceivers[sdpMLineIndex].iceTransport =\n pc.transceivers[0].iceTransport;\n pc.transceivers[sdpMLineIndex].dtlsTransport =\n pc.transceivers[0].dtlsTransport;\n if (pc.transceivers[sdpMLineIndex].rtpSender) {\n pc.transceivers[sdpMLineIndex].rtpSender.setTransport(\n pc.transceivers[0].dtlsTransport);\n }\n if (pc.transceivers[sdpMLineIndex].rtpReceiver) {\n pc.transceivers[sdpMLineIndex].rtpReceiver.setTransport(\n pc.transceivers[0].dtlsTransport);\n }\n }\n if (description.type === 'offer' && !rejected) {\n transceiver = pc.transceivers[sdpMLineIndex] ||\n pc._createTransceiver(kind);\n transceiver.mid = mid;\n\n if (!transceiver.iceGatherer) {\n transceiver.iceGatherer = pc._createIceGatherer(sdpMLineIndex,\n usingBundle);\n }\n\n if (cands.length && transceiver.iceTransport.state === 'new') {\n if (isComplete && (!usingBundle || sdpMLineIndex === 0)) {\n transceiver.iceTransport.setRemoteCandidates(cands);\n } else {\n cands.forEach(function(candidate) {\n maybeAddCandidate(transceiver.iceTransport, candidate);\n });\n }\n }\n\n localCapabilities = window.RTCRtpReceiver.getCapabilities(kind);\n\n \u002F\u002F filter RTX until additional stuff needed for RTX is implemented\n \u002F\u002F in adapter.js\n if (edgeVersion \u003C 15019) {\n localCapabilities.codecs = localCapabilities.codecs.filter(\n function(codec) {\n return codec.name !== 'rtx';\n });\n }\n\n sendEncodingParameters = transceiver.sendEncodingParameters || [{\n ssrc: (2 * sdpMLineIndex + 2) * 1001\n }];\n\n \u002F\u002F TODO: rewrite to use http:\u002F\u002Fw3c.github.io\u002Fwebrtc-pc\u002F#set-associated-remote-streams\n var isNewTrack = false;\n if (direction === 'sendrecv' || direction === 'sendonly') {\n isNewTrack = !transceiver.rtpReceiver;\n rtpReceiver = transceiver.rtpReceiver ||\n new window.RTCRtpReceiver(transceiver.dtlsTransport, kind);\n\n if (isNewTrack) {\n var stream;\n track = rtpReceiver.track;\n \u002F\u002F FIXME: does not work with Plan B.\n if (remoteMsid && remoteMsid.stream === '-') ; else if (remoteMsid) {\n if (!streams[remoteMsid.stream]) {\n streams[remoteMsid.stream] = new window.MediaStream();\n Object.defineProperty(streams[remoteMsid.stream], 'id', {\n get: function() {\n return remoteMsid.stream;\n }\n });\n }\n Object.defineProperty(track, 'id', {\n get: function() {\n return remoteMsid.track;\n }\n });\n stream = streams[remoteMsid.stream];\n } else {\n if (!streams.default) {\n streams.default = new window.MediaStream();\n }\n stream = streams.default;\n }\n if (stream) {\n addTrackToStreamAndFireEvent(track, stream);\n transceiver.associatedRemoteMediaStreams.push(stream);\n }\n receiverList.push([track, rtpReceiver, stream]);\n }\n } else if (transceiver.rtpReceiver && transceiver.rtpReceiver.track) {\n transceiver.associatedRemoteMediaStreams.forEach(function(s) {\n var nativeTrack = s.getTracks().find(function(t) {\n return t.id === transceiver.rtpReceiver.track.id;\n });\n if (nativeTrack) {\n removeTrackFromStreamAndFireEvent(nativeTrack, s);\n }\n });\n transceiver.associatedRemoteMediaStreams = [];\n }\n\n transceiver.localCapabilities = localCapabilities;\n transceiver.remoteCapabilities = remoteCapabilities;\n transceiver.rtpReceiver = rtpReceiver;\n transceiver.rtcpParameters = rtcpParameters;\n transceiver.sendEncodingParameters = sendEncodingParameters;\n transceiver.recvEncodingParameters = recvEncodingParameters;\n\n \u002F\u002F Start the RTCRtpReceiver now. The RTPSender is started in\n \u002F\u002F setLocalDescription.\n pc._transceive(pc.transceivers[sdpMLineIndex],\n false,\n isNewTrack);\n } else if (description.type === 'answer' && !rejected) {\n transceiver = pc.transceivers[sdpMLineIndex];\n iceGatherer = transceiver.iceGatherer;\n iceTransport = transceiver.iceTransport;\n dtlsTransport = transceiver.dtlsTransport;\n rtpReceiver = transceiver.rtpReceiver;\n sendEncodingParameters = transceiver.sendEncodingParameters;\n localCapabilities = transceiver.localCapabilities;\n\n pc.transceivers[sdpMLineIndex].recvEncodingParameters =\n recvEncodingParameters;\n pc.transceivers[sdpMLineIndex].remoteCapabilities =\n remoteCapabilities;\n pc.transceivers[sdpMLineIndex].rtcpParameters = rtcpParameters;\n\n if (cands.length && iceTransport.state === 'new') {\n if ((isIceLite || isComplete) &&\n (!usingBundle || sdpMLineIndex === 0)) {\n iceTransport.setRemoteCandidates(cands);\n } else {\n cands.forEach(function(candidate) {\n maybeAddCandidate(transceiver.iceTransport, candidate);\n });\n }\n }\n\n if (!usingBundle || sdpMLineIndex === 0) {\n if (iceTransport.state === 'new') {\n iceTransport.start(iceGatherer, remoteIceParameters,\n 'controlling');\n }\n if (dtlsTransport.state === 'new') {\n dtlsTransport.start(remoteDtlsParameters);\n }\n }\n\n \u002F\u002F If the offer contained RTX but the answer did not,\n \u002F\u002F remove RTX from sendEncodingParameters.\n var commonCapabilities = getCommonCapabilities(\n transceiver.localCapabilities,\n transceiver.remoteCapabilities);\n\n var hasRtx = commonCapabilities.codecs.filter(function(c) {\n return c.name.toLowerCase() === 'rtx';\n }).length;\n if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) {\n delete transceiver.sendEncodingParameters[0].rtx;\n }\n\n pc._transceive(transceiver,\n direction === 'sendrecv' || direction === 'recvonly',\n direction === 'sendrecv' || direction === 'sendonly');\n\n \u002F\u002F TODO: rewrite to use http:\u002F\u002Fw3c.github.io\u002Fwebrtc-pc\u002F#set-associated-remote-streams\n if (rtpReceiver &&\n (direction === 'sendrecv' || direction === 'sendonly')) {\n track = rtpReceiver.track;\n if (remoteMsid) {\n if (!streams[remoteMsid.stream]) {\n streams[remoteMsid.stream] = new window.MediaStream();\n }\n addTrackToStreamAndFireEvent(track, streams[remoteMsid.stream]);\n receiverList.push([track, rtpReceiver, streams[remoteMsid.stream]]);\n } else {\n if (!streams.default) {\n streams.default = new window.MediaStream();\n }\n addTrackToStreamAndFireEvent(track, streams.default);\n receiverList.push([track, rtpReceiver, streams.default]);\n }\n } else {\n \u002F\u002F FIXME: actually the receiver should be created later.\n delete transceiver.rtpReceiver;\n }\n }\n });\n\n if (pc._dtlsRole === undefined) {\n pc._dtlsRole = description.type === 'offer' ? 'active' : 'passive';\n }\n\n pc._remoteDescription = {\n type: description.type,\n sdp: description.sdp\n };\n if (description.type === 'offer') {\n pc._updateSignalingState('have-remote-offer');\n } else {\n pc._updateSignalingState('stable');\n }\n Object.keys(streams).forEach(function(sid) {\n var stream = streams[sid];\n if (stream.getTracks().length) {\n if (pc.remoteStreams.indexOf(stream) === -1) {\n pc.remoteStreams.push(stream);\n var event = new Event('addstream');\n event.stream = stream;\n window.setTimeout(function() {\n pc._dispatchEvent('addstream', event);\n });\n }\n\n receiverList.forEach(function(item) {\n var track = item[0];\n var receiver = item[1];\n if (stream.id !== item[2].id) {\n return;\n }\n fireAddTrack(pc, track, receiver, [stream]);\n });\n }\n });\n receiverList.forEach(function(item) {\n if (item[2]) {\n return;\n }\n fireAddTrack(pc, item[0], item[1], []);\n });\n\n \u002F\u002F check whether addIceCandidate({}) was called within four seconds after\n \u002F\u002F setRemoteDescription.\n window.setTimeout(function() {\n if (!(pc && pc.transceivers)) {\n return;\n }\n pc.transceivers.forEach(function(transceiver) {\n if (transceiver.iceTransport &&\n transceiver.iceTransport.state === 'new' &&\n transceiver.iceTransport.getRemoteCandidates().length \u003E 0) {\n console.warn('Timeout for addRemoteCandidate. Consider sending ' +\n 'an end-of-candidates notification');\n transceiver.iceTransport.addRemoteCandidate({});\n }\n });\n }, 4000);\n\n return Promise.resolve();\n };\n\n RTCPeerConnection.prototype.close = function() {\n this.transceivers.forEach(function(transceiver) {\n \u002F* not yet\n if (transceiver.iceGatherer) {\n transceiver.iceGatherer.close();\n }\n *\u002F\n if (transceiver.iceTransport) {\n transceiver.iceTransport.stop();\n }\n if (transceiver.dtlsTransport) {\n transceiver.dtlsTransport.stop();\n }\n if (transceiver.rtpSender) {\n transceiver.rtpSender.stop();\n }\n if (transceiver.rtpReceiver) {\n transceiver.rtpReceiver.stop();\n }\n });\n \u002F\u002F FIXME: clean up tracks, local streams, remote streams, etc\n this._isClosed = true;\n this._updateSignalingState('closed');\n };\n\n \u002F\u002F Update the signaling state.\n RTCPeerConnection.prototype._updateSignalingState = function(newState) {\n this.signalingState = newState;\n var event = new Event('signalingstatechange');\n this._dispatchEvent('signalingstatechange', event);\n };\n\n \u002F\u002F Determine whether to fire the negotiationneeded event.\n RTCPeerConnection.prototype._maybeFireNegotiationNeeded = function() {\n var pc = this;\n if (this.signalingState !== 'stable' || this.needNegotiation === true) {\n return;\n }\n this.needNegotiation = true;\n window.setTimeout(function() {\n if (pc.needNegotiation) {\n pc.needNegotiation = false;\n var event = new Event('negotiationneeded');\n pc._dispatchEvent('negotiationneeded', event);\n }\n }, 0);\n };\n\n \u002F\u002F Update the ice connection state.\n RTCPeerConnection.prototype._updateIceConnectionState = function() {\n var newState;\n var states = {\n 'new': 0,\n closed: 0,\n checking: 0,\n connected: 0,\n completed: 0,\n disconnected: 0,\n failed: 0\n };\n this.transceivers.forEach(function(transceiver) {\n if (transceiver.iceTransport && !transceiver.rejected) {\n states[transceiver.iceTransport.state]++;\n }\n });\n\n newState = 'new';\n if (states.failed \u003E 0) {\n newState = 'failed';\n } else if (states.checking \u003E 0) {\n newState = 'checking';\n } else if (states.disconnected \u003E 0) {\n newState = 'disconnected';\n } else if (states.new \u003E 0) {\n newState = 'new';\n } else if (states.connected \u003E 0) {\n newState = 'connected';\n } else if (states.completed \u003E 0) {\n newState = 'completed';\n }\n\n if (newState !== this.iceConnectionState) {\n this.iceConnectionState = newState;\n var event = new Event('iceconnectionstatechange');\n this._dispatchEvent('iceconnectionstatechange', event);\n }\n };\n\n \u002F\u002F Update the connection state.\n RTCPeerConnection.prototype._updateConnectionState = function() {\n var newState;\n var states = {\n 'new': 0,\n closed: 0,\n connecting: 0,\n connected: 0,\n completed: 0,\n disconnected: 0,\n failed: 0\n };\n this.transceivers.forEach(function(transceiver) {\n if (transceiver.iceTransport && transceiver.dtlsTransport &&\n !transceiver.rejected) {\n states[transceiver.iceTransport.state]++;\n states[transceiver.dtlsTransport.state]++;\n }\n });\n \u002F\u002F ICETransport.completed and connected are the same for this purpose.\n states.connected += states.completed;\n\n newState = 'new';\n if (states.failed \u003E 0) {\n newState = 'failed';\n } else if (states.connecting \u003E 0) {\n newState = 'connecting';\n } else if (states.disconnected \u003E 0) {\n newState = 'disconnected';\n } else if (states.new \u003E 0) {\n newState = 'new';\n } else if (states.connected \u003E 0) {\n newState = 'connected';\n }\n\n if (newState !== this.connectionState) {\n this.connectionState = newState;\n var event = new Event('connectionstatechange');\n this._dispatchEvent('connectionstatechange', event);\n }\n };\n\n RTCPeerConnection.prototype.createOffer = function() {\n var pc = this;\n\n if (pc._isClosed) {\n return Promise.reject(makeError('InvalidStateError',\n 'Can not call createOffer after close'));\n }\n\n var numAudioTracks = pc.transceivers.filter(function(t) {\n return t.kind === 'audio';\n }).length;\n var numVideoTracks = pc.transceivers.filter(function(t) {\n return t.kind === 'video';\n }).length;\n\n \u002F\u002F Determine number of audio and video tracks we need to send\u002Frecv.\n var offerOptions = arguments[0];\n if (offerOptions) {\n \u002F\u002F Reject Chrome legacy constraints.\n if (offerOptions.mandatory || offerOptions.optional) {\n throw new TypeError(\n 'Legacy mandatory\u002Foptional constraints not supported.');\n }\n if (offerOptions.offerToReceiveAudio !== undefined) {\n if (offerOptions.offerToReceiveAudio === true) {\n numAudioTracks = 1;\n } else if (offerOptions.offerToReceiveAudio === false) {\n numAudioTracks = 0;\n } else {\n numAudioTracks = offerOptions.offerToReceiveAudio;\n }\n }\n if (offerOptions.offerToReceiveVideo !== undefined) {\n if (offerOptions.offerToReceiveVideo === true) {\n numVideoTracks = 1;\n } else if (offerOptions.offerToReceiveVideo === false) {\n numVideoTracks = 0;\n } else {\n numVideoTracks = offerOptions.offerToReceiveVideo;\n }\n }\n }\n\n pc.transceivers.forEach(function(transceiver) {\n if (transceiver.kind === 'audio') {\n numAudioTracks--;\n if (numAudioTracks \u003C 0) {\n transceiver.wantReceive = false;\n }\n } else if (transceiver.kind === 'video') {\n numVideoTracks--;\n if (numVideoTracks \u003C 0) {\n transceiver.wantReceive = false;\n }\n }\n });\n\n \u002F\u002F Create M-lines for recvonly streams.\n while (numAudioTracks \u003E 0 || numVideoTracks \u003E 0) {\n if (numAudioTracks \u003E 0) {\n pc._createTransceiver('audio');\n numAudioTracks--;\n }\n if (numVideoTracks \u003E 0) {\n pc._createTransceiver('video');\n numVideoTracks--;\n }\n }\n\n var sdp$1 = sdp.writeSessionBoilerplate(pc._sdpSessionId,\n pc._sdpSessionVersion++);\n pc.transceivers.forEach(function(transceiver, sdpMLineIndex) {\n \u002F\u002F For each track, create an ice gatherer, ice transport,\n \u002F\u002F dtls transport, potentially rtpsender and rtpreceiver.\n var track = transceiver.track;\n var kind = transceiver.kind;\n var mid = transceiver.mid || sdp.generateIdentifier();\n transceiver.mid = mid;\n\n if (!transceiver.iceGatherer) {\n transceiver.iceGatherer = pc._createIceGatherer(sdpMLineIndex,\n pc.usingBundle);\n }\n\n var localCapabilities = window.RTCRtpSender.getCapabilities(kind);\n \u002F\u002F filter RTX until additional stuff needed for RTX is implemented\n \u002F\u002F in adapter.js\n if (edgeVersion \u003C 15019) {\n localCapabilities.codecs = localCapabilities.codecs.filter(\n function(codec) {\n return codec.name !== 'rtx';\n });\n }\n localCapabilities.codecs.forEach(function(codec) {\n \u002F\u002F work around https:\u002F\u002Fbugs.chromium.org\u002Fp\u002Fwebrtc\u002Fissues\u002Fdetail?id=6552\n \u002F\u002F by adding level-asymmetry-allowed=1\n if (codec.name === 'H264' &&\n codec.parameters['level-asymmetry-allowed'] === undefined) {\n codec.parameters['level-asymmetry-allowed'] = '1';\n }\n\n \u002F\u002F for subsequent offers, we might have to re-use the payload\n \u002F\u002F type of the last offer.\n if (transceiver.remoteCapabilities &&\n transceiver.remoteCapabilities.codecs) {\n transceiver.remoteCapabilities.codecs.forEach(function(remoteCodec) {\n if (codec.name.toLowerCase() === remoteCodec.name.toLowerCase() &&\n codec.clockRate === remoteCodec.clockRate) {\n codec.preferredPayloadType = remoteCodec.payloadType;\n }\n });\n }\n });\n localCapabilities.headerExtensions.forEach(function(hdrExt) {\n var remoteExtensions = transceiver.remoteCapabilities &&\n transceiver.remoteCapabilities.headerExtensions || [];\n remoteExtensions.forEach(function(rHdrExt) {\n if (hdrExt.uri === rHdrExt.uri) {\n hdrExt.id = rHdrExt.id;\n }\n });\n });\n\n \u002F\u002F generate an ssrc now, to be used later in rtpSender.send\n var sendEncodingParameters = transceiver.sendEncodingParameters || [{\n ssrc: (2 * sdpMLineIndex + 1) * 1001\n }];\n if (track) {\n \u002F\u002F add RTX\n if (edgeVersion \u003E= 15019 && kind === 'video' &&\n !sendEncodingParameters[0].rtx) {\n sendEncodingParameters[0].rtx = {\n ssrc: sendEncodingParameters[0].ssrc + 1\n };\n }\n }\n\n if (transceiver.wantReceive) {\n transceiver.rtpReceiver = new window.RTCRtpReceiver(\n transceiver.dtlsTransport, kind);\n }\n\n transceiver.localCapabilities = localCapabilities;\n transceiver.sendEncodingParameters = sendEncodingParameters;\n });\n\n \u002F\u002F always offer BUNDLE and dispose on return if not supported.\n if (pc._config.bundlePolicy !== 'max-compat') {\n sdp$1 += 'a=group:BUNDLE ' + pc.transceivers.map(function(t) {\n return t.mid;\n }).join(' ') + '\\r\\n';\n }\n sdp$1 += 'a=ice-options:trickle\\r\\n';\n\n pc.transceivers.forEach(function(transceiver, sdpMLineIndex) {\n sdp$1 += writeMediaSection(transceiver, transceiver.localCapabilities,\n 'offer', transceiver.stream, pc._dtlsRole);\n sdp$1 += 'a=rtcp-rsize\\r\\n';\n\n if (transceiver.iceGatherer && pc.iceGatheringState !== 'new' &&\n (sdpMLineIndex === 0 || !pc.usingBundle)) {\n transceiver.iceGatherer.getLocalCandidates().forEach(function(cand) {\n cand.component = 1;\n sdp$1 += 'a=' + sdp.writeCandidate(cand) + '\\r\\n';\n });\n\n if (transceiver.iceGatherer.state === 'completed') {\n sdp$1 += 'a=end-of-candidates\\r\\n';\n }\n }\n });\n\n var desc = new window.RTCSessionDescription({\n type: 'offer',\n sdp: sdp$1\n });\n return Promise.resolve(desc);\n };\n\n RTCPeerConnection.prototype.createAnswer = function() {\n var pc = this;\n\n if (pc._isClosed) {\n return Promise.reject(makeError('InvalidStateError',\n 'Can not call createAnswer after close'));\n }\n\n if (!(pc.signalingState === 'have-remote-offer' ||\n pc.signalingState === 'have-local-pranswer')) {\n return Promise.reject(makeError('InvalidStateError',\n 'Can not call createAnswer in signalingState ' + pc.signalingState));\n }\n\n var sdp$1 = sdp.writeSessionBoilerplate(pc._sdpSessionId,\n pc._sdpSessionVersion++);\n if (pc.usingBundle) {\n sdp$1 += 'a=group:BUNDLE ' + pc.transceivers.map(function(t) {\n return t.mid;\n }).join(' ') + '\\r\\n';\n }\n sdp$1 += 'a=ice-options:trickle\\r\\n';\n\n var mediaSectionsInOffer = sdp.getMediaSections(\n pc._remoteDescription.sdp).length;\n pc.transceivers.forEach(function(transceiver, sdpMLineIndex) {\n if (sdpMLineIndex + 1 \u003E mediaSectionsInOffer) {\n return;\n }\n if (transceiver.rejected) {\n if (transceiver.kind === 'application') {\n if (transceiver.protocol === 'DTLS\u002FSCTP') { \u002F\u002F legacy fmt\n sdp$1 += 'm=application 0 DTLS\u002FSCTP 5000\\r\\n';\n } else {\n sdp$1 += 'm=application 0 ' + transceiver.protocol +\n ' webrtc-datachannel\\r\\n';\n }\n } else if (transceiver.kind === 'audio') {\n sdp$1 += 'm=audio 0 UDP\u002FTLS\u002FRTP\u002FSAVPF 0\\r\\n' +\n 'a=rtpmap:0 PCMU\u002F8000\\r\\n';\n } else if (transceiver.kind === 'video') {\n sdp$1 += 'm=video 0 UDP\u002FTLS\u002FRTP\u002FSAVPF 120\\r\\n' +\n 'a=rtpmap:120 VP8\u002F90000\\r\\n';\n }\n sdp$1 += 'c=IN IP4 0.0.0.0\\r\\n' +\n 'a=inactive\\r\\n' +\n 'a=mid:' + transceiver.mid + '\\r\\n';\n return;\n }\n\n \u002F\u002F FIXME: look at direction.\n if (transceiver.stream) {\n var localTrack;\n if (transceiver.kind === 'audio') {\n localTrack = transceiver.stream.getAudioTracks()[0];\n } else if (transceiver.kind === 'video') {\n localTrack = transceiver.stream.getVideoTracks()[0];\n }\n if (localTrack) {\n \u002F\u002F add RTX\n if (edgeVersion \u003E= 15019 && transceiver.kind === 'video' &&\n !transceiver.sendEncodingParameters[0].rtx) {\n transceiver.sendEncodingParameters[0].rtx = {\n ssrc: transceiver.sendEncodingParameters[0].ssrc + 1\n };\n }\n }\n }\n\n \u002F\u002F Calculate intersection of capabilities.\n var commonCapabilities = getCommonCapabilities(\n transceiver.localCapabilities,\n transceiver.remoteCapabilities);\n\n var hasRtx = commonCapabilities.codecs.filter(function(c) {\n return c.name.toLowerCase() === 'rtx';\n }).length;\n if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) {\n delete transceiver.sendEncodingParameters[0].rtx;\n }\n\n sdp$1 += writeMediaSection(transceiver, commonCapabilities,\n 'answer', transceiver.stream, pc._dtlsRole);\n if (transceiver.rtcpParameters &&\n transceiver.rtcpParameters.reducedSize) {\n sdp$1 += 'a=rtcp-rsize\\r\\n';\n }\n });\n\n var desc = new window.RTCSessionDescription({\n type: 'answer',\n sdp: sdp$1\n });\n return Promise.resolve(desc);\n };\n\n RTCPeerConnection.prototype.addIceCandidate = function(candidate) {\n var pc = this;\n var sections;\n if (candidate && !(candidate.sdpMLineIndex !== undefined ||\n candidate.sdpMid)) {\n return Promise.reject(new TypeError('sdpMLineIndex or sdpMid required'));\n }\n\n \u002F\u002F TODO: needs to go into ops queue.\n return new Promise(function(resolve, reject) {\n if (!pc._remoteDescription) {\n return reject(makeError('InvalidStateError',\n 'Can not add ICE candidate without a remote description'));\n } else if (!candidate || candidate.candidate === '') {\n for (var j = 0; j \u003C pc.transceivers.length; j++) {\n if (pc.transceivers[j].rejected) {\n continue;\n }\n pc.transceivers[j].iceTransport.addRemoteCandidate({});\n sections = sdp.getMediaSections(pc._remoteDescription.sdp);\n sections[j] += 'a=end-of-candidates\\r\\n';\n pc._remoteDescription.sdp =\n sdp.getDescription(pc._remoteDescription.sdp) +\n sections.join('');\n if (pc.usingBundle) {\n break;\n }\n }\n } else {\n var sdpMLineIndex = candidate.sdpMLineIndex;\n if (candidate.sdpMid) {\n for (var i = 0; i \u003C pc.transceivers.length; i++) {\n if (pc.transceivers[i].mid === candidate.sdpMid) {\n sdpMLineIndex = i;\n break;\n }\n }\n }\n var transceiver = pc.transceivers[sdpMLineIndex];\n if (transceiver) {\n if (transceiver.rejected) {\n return resolve();\n }\n var cand = Object.keys(candidate.candidate).length \u003E 0 ?\n sdp.parseCandidate(candidate.candidate) : {};\n \u002F\u002F Ignore Chrome's invalid candidates since Edge does not like them.\n if (cand.protocol === 'tcp' && (cand.port === 0 || cand.port === 9)) {\n return resolve();\n }\n \u002F\u002F Ignore RTCP candidates, we assume RTCP-MUX.\n if (cand.component && cand.component !== 1) {\n return resolve();\n }\n \u002F\u002F when using bundle, avoid adding candidates to the wrong\n \u002F\u002F ice transport. And avoid adding candidates added in the SDP.\n if (sdpMLineIndex === 0 || (sdpMLineIndex \u003E 0 &&\n transceiver.iceTransport !== pc.transceivers[0].iceTransport)) {\n if (!maybeAddCandidate(transceiver.iceTransport, cand)) {\n return reject(makeError('OperationError',\n 'Can not add ICE candidate'));\n }\n }\n\n \u002F\u002F update the remoteDescription.\n var candidateString = candidate.candidate.trim();\n if (candidateString.indexOf('a=') === 0) {\n candidateString = candidateString.substr(2);\n }\n sections = sdp.getMediaSections(pc._remoteDescription.sdp);\n sections[sdpMLineIndex] += 'a=' +\n (cand.type ? candidateString : 'end-of-candidates')\n + '\\r\\n';\n pc._remoteDescription.sdp =\n sdp.getDescription(pc._remoteDescription.sdp) +\n sections.join('');\n } else {\n return reject(makeError('OperationError',\n 'Can not add ICE candidate'));\n }\n }\n resolve();\n });\n };\n\n RTCPeerConnection.prototype.getStats = function(selector) {\n if (selector && selector instanceof window.MediaStreamTrack) {\n var senderOrReceiver = null;\n this.transceivers.forEach(function(transceiver) {\n if (transceiver.rtpSender &&\n transceiver.rtpSender.track === selector) {\n senderOrReceiver = transceiver.rtpSender;\n } else if (transceiver.rtpReceiver &&\n transceiver.rtpReceiver.track === selector) {\n senderOrReceiver = transceiver.rtpReceiver;\n }\n });\n if (!senderOrReceiver) {\n throw makeError('InvalidAccessError', 'Invalid selector.');\n }\n return senderOrReceiver.getStats();\n }\n\n var promises = [];\n this.transceivers.forEach(function(transceiver) {\n ['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport',\n 'dtlsTransport'].forEach(function(method) {\n if (transceiver[method]) {\n promises.push(transceiver[method].getStats());\n }\n });\n });\n return Promise.all(promises).then(function(allStats) {\n var results = new Map();\n allStats.forEach(function(stats) {\n stats.forEach(function(stat) {\n results.set(stat.id, stat);\n });\n });\n return results;\n });\n };\n\n \u002F\u002F fix low-level stat names and return Map instead of object.\n var ortcObjects = ['RTCRtpSender', 'RTCRtpReceiver', 'RTCIceGatherer',\n 'RTCIceTransport', 'RTCDtlsTransport'];\n ortcObjects.forEach(function(ortcObjectName) {\n var obj = window[ortcObjectName];\n if (obj && obj.prototype && obj.prototype.getStats) {\n var nativeGetstats = obj.prototype.getStats;\n obj.prototype.getStats = function() {\n return nativeGetstats.apply(this)\n .then(function(nativeStats) {\n var mapStats = new Map();\n Object.keys(nativeStats).forEach(function(id) {\n nativeStats[id].type = fixStatsType(nativeStats[id]);\n mapStats.set(id, nativeStats[id]);\n });\n return mapStats;\n });\n };\n }\n });\n\n \u002F\u002F legacy callback shims. Should be moved to adapter.js some days.\n var methods = ['createOffer', 'createAnswer'];\n methods.forEach(function(method) {\n var nativeMethod = RTCPeerConnection.prototype[method];\n RTCPeerConnection.prototype[method] = function() {\n var args = arguments;\n if (typeof args[0] === 'function' ||\n typeof args[1] === 'function') { \u002F\u002F legacy\n return nativeMethod.apply(this, [arguments[2]])\n .then(function(description) {\n if (typeof args[0] === 'function') {\n args[0].apply(null, [description]);\n }\n }, function(error) {\n if (typeof args[1] === 'function') {\n args[1].apply(null, [error]);\n }\n });\n }\n return nativeMethod.apply(this, arguments);\n };\n });\n\n methods = ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'];\n methods.forEach(function(method) {\n var nativeMethod = RTCPeerConnection.prototype[method];\n RTCPeerConnection.prototype[method] = function() {\n var args = arguments;\n if (typeof args[1] === 'function' ||\n typeof args[2] === 'function') { \u002F\u002F legacy\n return nativeMethod.apply(this, arguments)\n .then(function() {\n if (typeof args[1] === 'function') {\n args[1].apply(null);\n }\n }, function(error) {\n if (typeof args[2] === 'function') {\n args[2].apply(null, [error]);\n }\n });\n }\n return nativeMethod.apply(this, arguments);\n };\n });\n\n \u002F\u002F getStats is special. It doesn't have a spec legacy method yet we support\n \u002F\u002F getStats(something, cb) without error callbacks.\n ['getStats'].forEach(function(method) {\n var nativeMethod = RTCPeerConnection.prototype[method];\n RTCPeerConnection.prototype[method] = function() {\n var args = arguments;\n if (typeof args[1] === 'function') {\n return nativeMethod.apply(this, arguments)\n .then(function() {\n if (typeof args[1] === 'function') {\n args[1].apply(null);\n }\n });\n }\n return nativeMethod.apply(this, arguments);\n };\n });\n\n return RTCPeerConnection;\n };\n\n \u002F*\n * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n * Use of this source code is governed by a BSD-style license\n * that can be found in the LICENSE file in the root of the source\n * tree.\n *\u002F\n\n function shimGetUserMedia$1(window) {\n const navigator = window && window.navigator;\n\n const shimError_ = function(e) {\n return {\n name: {PermissionDeniedError: 'NotAllowedError'}[e.name] || e.name,\n message: e.message,\n constraint: e.constraint,\n toString() {\n return this.name;\n }\n };\n };\n\n \u002F\u002F getUserMedia error shim.\n const origGetUserMedia = navigator.mediaDevices.getUserMedia.\n bind(navigator.mediaDevices);\n navigator.mediaDevices.getUserMedia = function(c) {\n return origGetUserMedia(c).catch(e =\u003E Promise.reject(shimError_(e)));\n };\n }\n\n \u002F*\n * Copyright (c) 2018 The adapter.js project authors. All Rights Reserved.\n *\n * Use of this source code is governed by a BSD-style license\n * that can be found in the LICENSE file in the root of the source\n * tree.\n *\u002F\n\n function shimGetDisplayMedia$1(window) {\n if (!('getDisplayMedia' in window.navigator)) {\n return;\n }\n if (!(window.navigator.mediaDevices)) {\n return;\n }\n if (window.navigator.mediaDevices &&\n 'getDisplayMedia' in window.navigator.mediaDevices) {\n return;\n }\n window.navigator.mediaDevices.getDisplayMedia =\n window.navigator.getDisplayMedia.bind(window.navigator);\n }\n\n \u002F*\n * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n * Use of this source code is governed by a BSD-style license\n * that can be found in the LICENSE file in the root of the source\n * tree.\n *\u002F\n\n function shimPeerConnection$1(window) {\n const browserDetails = detectBrowser(window);\n\n if (window.RTCIceGatherer) {\n if (!window.RTCIceCandidate) {\n window.RTCIceCandidate = function RTCIceCandidate(args) {\n return args;\n };\n }\n if (!window.RTCSessionDescription) {\n window.RTCSessionDescription = function RTCSessionDescription(args) {\n return args;\n };\n }\n \u002F\u002F this adds an additional event listener to MediaStrackTrack that signals\n \u002F\u002F when a tracks enabled property was changed. Workaround for a bug in\n \u002F\u002F addStream, see below. No longer required in 15025+\n if (browserDetails.version \u003C 15025) {\n const origMSTEnabled = Object.getOwnPropertyDescriptor(\n window.MediaStreamTrack.prototype, 'enabled');\n Object.defineProperty(window.MediaStreamTrack.prototype, 'enabled', {\n set(value) {\n origMSTEnabled.set.call(this, value);\n const ev = new Event('enabled');\n ev.enabled = value;\n this.dispatchEvent(ev);\n }\n });\n }\n }\n\n \u002F\u002F ORTC defines the DTMF sender a bit different.\n \u002F\u002F https:\u002F\u002Fgithub.com\u002Fw3c\u002Fortc\u002Fissues\u002F714\n if (window.RTCRtpSender && !('dtmf' in window.RTCRtpSender.prototype)) {\n Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', {\n get() {\n if (this._dtmf === undefined) {\n if (this.track.kind === 'audio') {\n this._dtmf = new window.RTCDtmfSender(this);\n } else if (this.track.kind === 'video') {\n this._dtmf = null;\n }\n }\n return this._dtmf;\n }\n });\n }\n \u002F\u002F Edge currently only implements the RTCDtmfSender, not the\n \u002F\u002F RTCDTMFSender alias. See http:\u002F\u002Fdraft.ortc.org\u002F#rtcdtmfsender2*\n if (window.RTCDtmfSender && !window.RTCDTMFSender) {\n window.RTCDTMFSender = window.RTCDtmfSender;\n }\n\n const RTCPeerConnectionShim = rtcpeerconnection(window,\n browserDetails.version);\n window.RTCPeerConnection = function RTCPeerConnection(config) {\n if (config && config.iceServers) {\n config.iceServers = filterIceServers(config.iceServers,\n browserDetails.version);\n log('ICE servers after filtering:', config.iceServers);\n }\n return new RTCPeerConnectionShim(config);\n };\n window.RTCPeerConnection.prototype = RTCPeerConnectionShim.prototype;\n }\n\n function shimReplaceTrack(window) {\n \u002F\u002F ORTC has replaceTrack -- https:\u002F\u002Fgithub.com\u002Fw3c\u002Fortc\u002Fissues\u002F614\n if (window.RTCRtpSender &&\n !('replaceTrack' in window.RTCRtpSender.prototype)) {\n window.RTCRtpSender.prototype.replaceTrack =\n window.RTCRtpSender.prototype.setTrack;\n }\n }\n\n var edgeShim = \u002F*#__PURE__*\u002FObject.freeze({\n __proto__: null,\n shimPeerConnection: shimPeerConnection$1,\n shimReplaceTrack: shimReplaceTrack,\n shimGetUserMedia: shimGetUserMedia$1,\n shimGetDisplayMedia: shimGetDisplayMedia$1\n });\n\n \u002F*\n * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n * Use of this source code is governed by a BSD-style license\n * that can be found in the LICENSE file in the root of the source\n * tree.\n *\u002F\n\n function shimGetUserMedia$2(window) {\n const browserDetails = detectBrowser(window);\n const navigator = window && window.navigator;\n const MediaStreamTrack = window && window.MediaStreamTrack;\n\n navigator.getUserMedia = function(constraints, onSuccess, onError) {\n \u002F\u002F Replace Firefox 44+'s deprecation warning with unprefixed version.\n deprecated('navigator.getUserMedia',\n 'navigator.mediaDevices.getUserMedia');\n navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError);\n };\n\n if (!(browserDetails.version \u003E 55 &&\n 'autoGainControl' in navigator.mediaDevices.getSupportedConstraints())) {\n const remap = function(obj, a, b) {\n if (a in obj && !(b in obj)) {\n obj[b] = obj[a];\n delete obj[a];\n }\n };\n\n const nativeGetUserMedia = navigator.mediaDevices.getUserMedia.\n bind(navigator.mediaDevices);\n navigator.mediaDevices.getUserMedia = function(c) {\n if (typeof c === 'object' && typeof c.audio === 'object') {\n c = JSON.parse(JSON.stringify(c));\n remap(c.audio, 'autoGainControl', 'mozAutoGainControl');\n remap(c.audio, 'noiseSuppression', 'mozNoiseSuppression');\n }\n return nativeGetUserMedia(c);\n };\n\n if (MediaStreamTrack && MediaStreamTrack.prototype.getSettings) {\n const nativeGetSettings = MediaStreamTrack.prototype.getSettings;\n MediaStreamTrack.prototype.getSettings = function() {\n const obj = nativeGetSettings.apply(this, arguments);\n remap(obj, 'mozAutoGainControl', 'autoGainControl');\n remap(obj, 'mozNoiseSuppression', 'noiseSuppression');\n return obj;\n };\n }\n\n if (MediaStreamTrack && MediaStreamTrack.prototype.applyConstraints) {\n const nativeApplyConstraints =\n MediaStreamTrack.prototype.applyConstraints;\n MediaStreamTrack.prototype.applyConstraints = function(c) {\n if (this.kind === 'audio' && typeof c === 'object') {\n c = JSON.parse(JSON.stringify(c));\n remap(c, 'autoGainControl', 'mozAutoGainControl');\n remap(c, 'noiseSuppression', 'mozNoiseSuppression');\n }\n return nativeApplyConstraints.apply(this, [c]);\n };\n }\n }\n }\n\n \u002F*\n * Copyright (c) 2018 The adapter.js project authors. All Rights Reserved.\n *\n * Use of this source code is governed by a BSD-style license\n * that can be found in the LICENSE file in the root of the source\n * tree.\n *\u002F\n\n function shimGetDisplayMedia$2(window, preferredMediaSource) {\n if (window.navigator.mediaDevices &&\n 'getDisplayMedia' in window.navigator.mediaDevices) {\n return;\n }\n if (!(window.navigator.mediaDevices)) {\n return;\n }\n window.navigator.mediaDevices.getDisplayMedia =\n function getDisplayMedia(constraints) {\n if (!(constraints && constraints.video)) {\n const err = new DOMException('getDisplayMedia without video ' +\n 'constraints is undefined');\n err.name = 'NotFoundError';\n \u002F\u002F from https:\u002F\u002Fheycam.github.io\u002Fwebidl\u002F#idl-DOMException-error-names\n err.code = 8;\n return Promise.reject(err);\n }\n if (constraints.video === true) {\n constraints.video = {mediaSource: preferredMediaSource};\n } else {\n constraints.video.mediaSource = preferredMediaSource;\n }\n return window.navigator.mediaDevices.getUserMedia(constraints);\n };\n }\n\n \u002F*\n * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n * Use of this source code is governed by a BSD-style license\n * that can be found in the LICENSE file in the root of the source\n * tree.\n *\u002F\n\n function shimOnTrack$1(window) {\n if (typeof window === 'object' && window.RTCTrackEvent &&\n ('receiver' in window.RTCTrackEvent.prototype) &&\n !('transceiver' in window.RTCTrackEvent.prototype)) {\n Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', {\n get() {\n return {receiver: this.receiver};\n }\n });\n }\n }\n\n function shimPeerConnection$2(window) {\n const browserDetails = detectBrowser(window);\n\n if (typeof window !== 'object' ||\n !(window.RTCPeerConnection || window.mozRTCPeerConnection)) {\n return; \u002F\u002F probably media.peerconnection.enabled=false in about:config\n }\n if (!window.RTCPeerConnection && window.mozRTCPeerConnection) {\n \u002F\u002F very basic support for old versions.\n window.RTCPeerConnection = window.mozRTCPeerConnection;\n }\n\n if (browserDetails.version \u003C 53) {\n \u002F\u002F shim away need for obsolete RTCIceCandidate\u002FRTCSessionDescription.\n ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']\n .forEach(function(method) {\n const nativeMethod = window.RTCPeerConnection.prototype[method];\n const methodObj = {[method]() {\n arguments[0] = new ((method === 'addIceCandidate') ?\n window.RTCIceCandidate :\n window.RTCSessionDescription)(arguments[0]);\n return nativeMethod.apply(this, arguments);\n }};\n window.RTCPeerConnection.prototype[method] = methodObj[method];\n });\n }\n\n \u002F\u002F support for addIceCandidate(null or undefined)\n \u002F\u002F as well as ignoring {sdpMid, candidate: \"\"}\n if (browserDetails.version \u003C 68) {\n const nativeAddIceCandidate =\n window.RTCPeerConnection.prototype.addIceCandidate;\n window.RTCPeerConnection.prototype.addIceCandidate =\n function addIceCandidate() {\n if (!arguments[0]) {\n if (arguments[1]) {\n arguments[1].apply(null);\n }\n return Promise.resolve();\n }\n \u002F\u002F Firefox 68+ emits and processes {candidate: \"\", ...}, ignore\n \u002F\u002F in older versions.\n if (arguments[0] && arguments[0].candidate === '') {\n return Promise.resolve();\n }\n return nativeAddIceCandidate.apply(this, arguments);\n };\n }\n\n const modernStatsTypes = {\n inboundrtp: 'inbound-rtp',\n outboundrtp: 'outbound-rtp',\n candidatepair: 'candidate-pair',\n localcandidate: 'local-candidate',\n remotecandidate: 'remote-candidate'\n };\n\n const nativeGetStats = window.RTCPeerConnection.prototype.getStats;\n window.RTCPeerConnection.prototype.getStats = function getStats() {\n const [selector, onSucc, onErr] = arguments;\n return nativeGetStats.apply(this, [selector || null])\n .then(stats =\u003E {\n if (browserDetails.version \u003C 53 && !onSucc) {\n \u002F\u002F Shim only promise getStats with spec-hyphens in type names\n \u002F\u002F Leave callback version alone; misc old uses of forEach before Map\n try {\n stats.forEach(stat =\u003E {\n stat.type = modernStatsTypes[stat.type] || stat.type;\n });\n } catch (e) {\n if (e.name !== 'TypeError') {\n throw e;\n }\n \u002F\u002F Avoid TypeError: \"type\" is read-only, in old versions. 34-43ish\n stats.forEach((stat, i) =\u003E {\n stats.set(i, Object.assign({}, stat, {\n type: modernStatsTypes[stat.type] || stat.type\n }));\n });\n }\n }\n return stats;\n })\n .then(onSucc, onErr);\n };\n }\n\n function shimSenderGetStats(window) {\n if (!(typeof window === 'object' && window.RTCPeerConnection &&\n window.RTCRtpSender)) {\n return;\n }\n if (window.RTCRtpSender && 'getStats' in window.RTCRtpSender.prototype) {\n return;\n }\n const origGetSenders = window.RTCPeerConnection.prototype.getSenders;\n if (origGetSenders) {\n window.RTCPeerConnection.prototype.getSenders = function getSenders() {\n const senders = origGetSenders.apply(this, []);\n senders.forEach(sender =\u003E sender._pc = this);\n return senders;\n };\n }\n\n const origAddTrack = window.RTCPeerConnection.prototype.addTrack;\n if (origAddTrack) {\n window.RTCPeerConnection.prototype.addTrack = function addTrack() {\n const sender = origAddTrack.apply(this, arguments);\n sender._pc = this;\n return sender;\n };\n }\n window.RTCRtpSender.prototype.getStats = function getStats() {\n return this.track ? this._pc.getStats(this.track) :\n Promise.resolve(new Map());\n };\n }\n\n function shimReceiverGetStats(window) {\n if (!(typeof window === 'object' && window.RTCPeerConnection &&\n window.RTCRtpSender)) {\n return;\n }\n if (window.RTCRtpSender && 'getStats' in window.RTCRtpReceiver.prototype) {\n return;\n }\n const origGetReceivers = window.RTCPeerConnection.prototype.getReceivers;\n if (origGetReceivers) {\n window.RTCPeerConnection.prototype.getReceivers = function getReceivers() {\n const receivers = origGetReceivers.apply(this, []);\n receivers.forEach(receiver =\u003E receiver._pc = this);\n return receivers;\n };\n }\n wrapPeerConnectionEvent(window, 'track', e =\u003E {\n e.receiver._pc = e.srcElement;\n return e;\n });\n window.RTCRtpReceiver.prototype.getStats = function getStats() {\n return this._pc.getStats(this.track);\n };\n }\n\n function shimRemoveStream(window) {\n if (!window.RTCPeerConnection ||\n 'removeStream' in window.RTCPeerConnection.prototype) {\n return;\n }\n window.RTCPeerConnection.prototype.removeStream =\n function removeStream(stream) {\n deprecated('removeStream', 'removeTrack');\n this.getSenders().forEach(sender =\u003E {\n if (sender.track && stream.getTracks().includes(sender.track)) {\n this.removeTrack(sender);\n }\n });\n };\n }\n\n function shimRTCDataChannel(window) {\n \u002F\u002F rename DataChannel to RTCDataChannel (native fix in FF60):\n \u002F\u002F https:\u002F\u002Fbugzilla.mozilla.org\u002Fshow_bug.cgi?id=1173851\n if (window.DataChannel && !window.RTCDataChannel) {\n window.RTCDataChannel = window.DataChannel;\n }\n }\n\n function shimAddTransceiver(window) {\n \u002F\u002F https:\u002F\u002Fgithub.com\u002FwebrtcHacks\u002Fadapter\u002Fissues\u002F998#issuecomment-516921647\n \u002F\u002F Firefox ignores the init sendEncodings options passed to addTransceiver\n \u002F\u002F https:\u002F\u002Fbugzilla.mozilla.org\u002Fshow_bug.cgi?id=1396918\n if (!(typeof window === 'object' && window.RTCPeerConnection)) {\n return;\n }\n const origAddTransceiver = window.RTCPeerConnection.prototype.addTransceiver;\n if (origAddTransceiver) {\n window.RTCPeerConnection.prototype.addTransceiver =\n function addTransceiver() {\n this.setParametersPromises = [];\n const initParameters = arguments[1];\n const shouldPerformCheck = initParameters &&\n 'sendEncodings' in initParameters;\n if (shouldPerformCheck) {\n \u002F\u002F If sendEncodings params are provided, validate grammar\n initParameters.sendEncodings.forEach((encodingParam) =\u003E {\n if ('rid' in encodingParam) {\n const ridRegex = \u002F^[a-z0-9]{0,16}$\u002Fi;\n if (!ridRegex.test(encodingParam.rid)) {\n throw new TypeError('Invalid RID value provided.');\n }\n }\n if ('scaleResolutionDownBy' in encodingParam) {\n if (!(parseFloat(encodingParam.scaleResolutionDownBy) \u003E= 1.0)) {\n throw new RangeError('scale_resolution_down_by must be \u003E= 1.0');\n }\n }\n if ('maxFramerate' in encodingParam) {\n if (!(parseFloat(encodingParam.maxFramerate) \u003E= 0)) {\n throw new RangeError('max_framerate must be \u003E= 0.0');\n }\n }\n });\n }\n const transceiver = origAddTransceiver.apply(this, arguments);\n if (shouldPerformCheck) {\n \u002F\u002F Check if the init options were applied. If not we do this in an\n \u002F\u002F asynchronous way and save the promise reference in a global object.\n \u002F\u002F This is an ugly hack, but at the same time is way more robust than\n \u002F\u002F checking the sender parameters before and after the createOffer\n \u002F\u002F Also note that after the createoffer we are not 100% sure that\n \u002F\u002F the params were asynchronously applied so we might miss the\n \u002F\u002F opportunity to recreate offer.\n const {sender} = transceiver;\n const params = sender.getParameters();\n if (!('encodings' in params)) {\n params.encodings = initParameters.sendEncodings;\n this.setParametersPromises.push(\n sender.setParameters(params)\n .catch(() =\u003E {})\n );\n }\n }\n return transceiver;\n };\n }\n }\n\n function shimCreateOffer(window) {\n \u002F\u002F https:\u002F\u002Fgithub.com\u002FwebrtcHacks\u002Fadapter\u002Fissues\u002F998#issuecomment-516921647\n \u002F\u002F Firefox ignores the init sendEncodings options passed to addTransceiver\n \u002F\u002F https:\u002F\u002Fbugzilla.mozilla.org\u002Fshow_bug.cgi?id=1396918\n if (!(typeof window === 'object' && window.RTCPeerConnection)) {\n return;\n }\n const origCreateOffer = window.RTCPeerConnection.prototype.createOffer;\n window.RTCPeerConnection.prototype.createOffer = function createOffer() {\n if (this.setParametersPromises && this.setParametersPromises.length) {\n return Promise.all(this.setParametersPromises)\n .then(() =\u003E {\n return origCreateOffer.apply(this, arguments);\n })\n .finally(() =\u003E {\n this.setParametersPromises = [];\n });\n }\n return origCreateOffer.apply(this, arguments);\n };\n }\n\n function shimCreateAnswer(window) {\n \u002F\u002F https:\u002F\u002Fgithub.com\u002FwebrtcHacks\u002Fadapter\u002Fissues\u002F998#issuecomment-516921647\n \u002F\u002F Firefox ignores the init sendEncodings options passed to addTransceiver\n \u002F\u002F https:\u002F\u002Fbugzilla.mozilla.org\u002Fshow_bug.cgi?id=1396918\n if (!(typeof window === 'object' && window.RTCPeerConnection)) {\n return;\n }\n const origCreateAnswer = window.RTCPeerConnection.prototype.createAnswer;\n window.RTCPeerConnection.prototype.createAnswer = function createAnswer() {\n if (this.setParametersPromises && this.setParametersPromises.length) {\n return Promise.all(this.setParametersPromises)\n .then(() =\u003E {\n return origCreateAnswer.apply(this, arguments);\n })\n .finally(() =\u003E {\n this.setParametersPromises = [];\n });\n }\n return origCreateAnswer.apply(this, arguments);\n };\n }\n\n var firefoxShim = \u002F*#__PURE__*\u002FObject.freeze({\n __proto__: null,\n shimOnTrack: shimOnTrack$1,\n shimPeerConnection: shimPeerConnection$2,\n shimSenderGetStats: shimSenderGetStats,\n shimReceiverGetStats: shimReceiverGetStats,\n shimRemoveStream: shimRemoveStream,\n shimRTCDataChannel: shimRTCDataChannel,\n shimAddTransceiver: shimAddTransceiver,\n shimCreateOffer: shimCreateOffer,\n shimCreateAnswer: shimCreateAnswer,\n shimGetUserMedia: shimGetUserMedia$2,\n shimGetDisplayMedia: shimGetDisplayMedia$2\n });\n\n \u002F*\n * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n * Use of this source code is governed by a BSD-style license\n * that can be found in the LICENSE file in the root of the source\n * tree.\n *\u002F\n\n function shimLocalStreamsAPI(window) {\n if (typeof window !== 'object' || !window.RTCPeerConnection) {\n return;\n }\n if (!('getLocalStreams' in window.RTCPeerConnection.prototype)) {\n window.RTCPeerConnection.prototype.getLocalStreams =\n function getLocalStreams() {\n if (!this._localStreams) {\n this._localStreams = [];\n }\n return this._localStreams;\n };\n }\n if (!('addStream' in window.RTCPeerConnection.prototype)) {\n const _addTrack = window.RTCPeerConnection.prototype.addTrack;\n window.RTCPeerConnection.prototype.addStream = function addStream(stream) {\n if (!this._localStreams) {\n this._localStreams = [];\n }\n if (!this._localStreams.includes(stream)) {\n this._localStreams.push(stream);\n }\n \u002F\u002F Try to emulate Chrome's behaviour of adding in audio-video order.\n \u002F\u002F Safari orders by track id.\n stream.getAudioTracks().forEach(track =\u003E _addTrack.call(this, track,\n stream));\n stream.getVideoTracks().forEach(track =\u003E _addTrack.call(this, track,\n stream));\n };\n\n window.RTCPeerConnection.prototype.addTrack =\n function addTrack(track, ...streams) {\n if (streams) {\n streams.forEach((stream) =\u003E {\n if (!this._localStreams) {\n this._localStreams = [stream];\n } else if (!this._localStreams.includes(stream)) {\n this._localStreams.push(stream);\n }\n });\n }\n return _addTrack.apply(this, arguments);\n };\n }\n if (!('removeStream' in window.RTCPeerConnection.prototype)) {\n window.RTCPeerConnection.prototype.removeStream =\n function removeStream(stream) {\n if (!this._localStreams) {\n this._localStreams = [];\n }\n const index = this._localStreams.indexOf(stream);\n if (index === -1) {\n return;\n }\n this._localStreams.splice(index, 1);\n const tracks = stream.getTracks();\n this.getSenders().forEach(sender =\u003E {\n if (tracks.includes(sender.track)) {\n this.removeTrack(sender);\n }\n });\n };\n }\n }\n\n function shimRemoteStreamsAPI(window) {\n if (typeof window !== 'object' || !window.RTCPeerConnection) {\n return;\n }\n if (!('getRemoteStreams' in window.RTCPeerConnection.prototype)) {\n window.RTCPeerConnection.prototype.getRemoteStreams =\n function getRemoteStreams() {\n return this._remoteStreams ? this._remoteStreams : [];\n };\n }\n if (!('onaddstream' in window.RTCPeerConnection.prototype)) {\n Object.defineProperty(window.RTCPeerConnection.prototype, 'onaddstream', {\n get() {\n return this._onaddstream;\n },\n set(f) {\n if (this._onaddstream) {\n this.removeEventListener('addstream', this._onaddstream);\n this.removeEventListener('track', this._onaddstreampoly);\n }\n this.addEventListener('addstream', this._onaddstream = f);\n this.addEventListener('track', this._onaddstreampoly = (e) =\u003E {\n e.streams.forEach(stream =\u003E {\n if (!this._remoteStreams) {\n this._remoteStreams = [];\n }\n if (this._remoteStreams.includes(stream)) {\n return;\n }\n this._remoteStreams.push(stream);\n const event = new Event('addstream');\n event.stream = stream;\n this.dispatchEvent(event);\n });\n });\n }\n });\n const origSetRemoteDescription =\n window.RTCPeerConnection.prototype.setRemoteDescription;\n window.RTCPeerConnection.prototype.setRemoteDescription =\n function setRemoteDescription() {\n const pc = this;\n if (!this._onaddstreampoly) {\n this.addEventListener('track', this._onaddstreampoly = function(e) {\n e.streams.forEach(stream =\u003E {\n if (!pc._remoteStreams) {\n pc._remoteStreams = [];\n }\n if (pc._remoteStreams.indexOf(stream) \u003E= 0) {\n return;\n }\n pc._remoteStreams.push(stream);\n const event = new Event('addstream');\n event.stream = stream;\n pc.dispatchEvent(event);\n });\n });\n }\n return origSetRemoteDescription.apply(pc, arguments);\n };\n }\n }\n\n function shimCallbacksAPI(window) {\n if (typeof window !== 'object' || !window.RTCPeerConnection) {\n return;\n }\n const prototype = window.RTCPeerConnection.prototype;\n const origCreateOffer = prototype.createOffer;\n const origCreateAnswer = prototype.createAnswer;\n const setLocalDescription = prototype.setLocalDescription;\n const setRemoteDescription = prototype.setRemoteDescription;\n const addIceCandidate = prototype.addIceCandidate;\n\n prototype.createOffer =\n function createOffer(successCallback, failureCallback) {\n const options = (arguments.length \u003E= 2) ? arguments[2] : arguments[0];\n const promise = origCreateOffer.apply(this, [options]);\n if (!failureCallback) {\n return promise;\n }\n promise.then(successCallback, failureCallback);\n return Promise.resolve();\n };\n\n prototype.createAnswer =\n function createAnswer(successCallback, failureCallback) {\n const options = (arguments.length \u003E= 2) ? arguments[2] : arguments[0];\n const promise = origCreateAnswer.apply(this, [options]);\n if (!failureCallback) {\n return promise;\n }\n promise.then(successCallback, failureCallback);\n return Promise.resolve();\n };\n\n let withCallback = function(description, successCallback, failureCallback) {\n const promise = setLocalDescription.apply(this, [description]);\n if (!failureCallback) {\n return promise;\n }\n promise.then(successCallback, failureCallback);\n return Promise.resolve();\n };\n prototype.setLocalDescription = withCallback;\n\n withCallback = function(description, successCallback, failureCallback) {\n const promise = setRemoteDescription.apply(this, [description]);\n if (!failureCallback) {\n return promise;\n }\n promise.then(successCallback, failureCallback);\n return Promise.resolve();\n };\n prototype.setRemoteDescription = withCallback;\n\n withCallback = function(candidate, successCallback, failureCallback) {\n const promise = addIceCandidate.apply(this, [candidate]);\n if (!failureCallback) {\n return promise;\n }\n promise.then(successCallback, failureCallback);\n return Promise.resolve();\n };\n prototype.addIceCandidate = withCallback;\n }\n\n function shimGetUserMedia$3(window) {\n const navigator = window && window.navigator;\n\n if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {\n \u002F\u002F shim not needed in Safari 12.1\n const mediaDevices = navigator.mediaDevices;\n const _getUserMedia = mediaDevices.getUserMedia.bind(mediaDevices);\n navigator.mediaDevices.getUserMedia = (constraints) =\u003E {\n return _getUserMedia(shimConstraints(constraints));\n };\n }\n\n if (!navigator.getUserMedia && navigator.mediaDevices &&\n navigator.mediaDevices.getUserMedia) {\n navigator.getUserMedia = function getUserMedia(constraints, cb, errcb) {\n navigator.mediaDevices.getUserMedia(constraints)\n .then(cb, errcb);\n }.bind(navigator);\n }\n }\n\n function shimConstraints(constraints) {\n if (constraints && constraints.video !== undefined) {\n return Object.assign({},\n constraints,\n {video: compactObject(constraints.video)}\n );\n }\n\n return constraints;\n }\n\n function shimRTCIceServerUrls(window) {\n \u002F\u002F migrate from non-spec RTCIceServer.url to RTCIceServer.urls\n const OrigPeerConnection = window.RTCPeerConnection;\n window.RTCPeerConnection =\n function RTCPeerConnection(pcConfig, pcConstraints) {\n if (pcConfig && pcConfig.iceServers) {\n const newIceServers = [];\n for (let i = 0; i \u003C pcConfig.iceServers.length; i++) {\n let server = pcConfig.iceServers[i];\n if (!server.hasOwnProperty('urls') &&\n server.hasOwnProperty('url')) {\n deprecated('RTCIceServer.url', 'RTCIceServer.urls');\n server = JSON.parse(JSON.stringify(server));\n server.urls = server.url;\n delete server.url;\n newIceServers.push(server);\n } else {\n newIceServers.push(pcConfig.iceServers[i]);\n }\n }\n pcConfig.iceServers = newIceServers;\n }\n return new OrigPeerConnection(pcConfig, pcConstraints);\n };\n window.RTCPeerConnection.prototype = OrigPeerConnection.prototype;\n \u002F\u002F wrap static methods. Currently just generateCertificate.\n if ('generateCertificate' in window.RTCPeerConnection) {\n Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {\n get() {\n return OrigPeerConnection.generateCertificate;\n }\n });\n }\n }\n\n function shimTrackEventTransceiver(window) {\n \u002F\u002F Add event.transceiver member over deprecated event.receiver\n if (typeof window === 'object' && window.RTCTrackEvent &&\n 'receiver' in window.RTCTrackEvent.prototype &&\n !('transceiver' in window.RTCTrackEvent.prototype)) {\n Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', {\n get() {\n return {receiver: this.receiver};\n }\n });\n }\n }\n\n function shimCreateOfferLegacy(window) {\n const origCreateOffer = window.RTCPeerConnection.prototype.createOffer;\n window.RTCPeerConnection.prototype.createOffer =\n function createOffer(offerOptions) {\n if (offerOptions) {\n if (typeof offerOptions.offerToReceiveAudio !== 'undefined') {\n \u002F\u002F support bit values\n offerOptions.offerToReceiveAudio =\n !!offerOptions.offerToReceiveAudio;\n }\n const audioTransceiver = this.getTransceivers().find(transceiver =\u003E\n transceiver.receiver.track.kind === 'audio');\n if (offerOptions.offerToReceiveAudio === false && audioTransceiver) {\n if (audioTransceiver.direction === 'sendrecv') {\n if (audioTransceiver.setDirection) {\n audioTransceiver.setDirection('sendonly');\n } else {\n audioTransceiver.direction = 'sendonly';\n }\n } else if (audioTransceiver.direction === 'recvonly') {\n if (audioTransceiver.setDirection) {\n audioTransceiver.setDirection('inactive');\n } else {\n audioTransceiver.direction = 'inactive';\n }\n }\n } else if (offerOptions.offerToReceiveAudio === true &&\n !audioTransceiver) {\n this.addTransceiver('audio');\n }\n\n if (typeof offerOptions.offerToReceiveVideo !== 'undefined') {\n \u002F\u002F support bit values\n offerOptions.offerToReceiveVideo =\n !!offerOptions.offerToReceiveVideo;\n }\n const videoTransceiver = this.getTransceivers().find(transceiver =\u003E\n transceiver.receiver.track.kind === 'video');\n if (offerOptions.offerToReceiveVideo === false && videoTransceiver) {\n if (videoTransceiver.direction === 'sendrecv') {\n if (videoTransceiver.setDirection) {\n videoTransceiver.setDirection('sendonly');\n } else {\n videoTransceiver.direction = 'sendonly';\n }\n } else if (videoTransceiver.direction === 'recvonly') {\n if (videoTransceiver.setDirection) {\n videoTransceiver.setDirection('inactive');\n } else {\n videoTransceiver.direction = 'inactive';\n }\n }\n } else if (offerOptions.offerToReceiveVideo === true &&\n !videoTransceiver) {\n this.addTransceiver('video');\n }\n }\n return origCreateOffer.apply(this, arguments);\n };\n }\n\n function shimAudioContext(window) {\n if (typeof window !== 'object' || window.AudioContext) {\n return;\n }\n window.AudioContext = window.webkitAudioContext;\n }\n\n var safariShim = \u002F*#__PURE__*\u002FObject.freeze({\n __proto__: null,\n shimLocalStreamsAPI: shimLocalStreamsAPI,\n shimRemoteStreamsAPI: shimRemoteStreamsAPI,\n shimCallbacksAPI: shimCallbacksAPI,\n shimGetUserMedia: shimGetUserMedia$3,\n shimConstraints: shimConstraints,\n shimRTCIceServerUrls: shimRTCIceServerUrls,\n shimTrackEventTransceiver: shimTrackEventTransceiver,\n shimCreateOfferLegacy: shimCreateOfferLegacy,\n shimAudioContext: shimAudioContext\n });\n\n \u002F*\n * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.\n *\n * Use of this source code is governed by a BSD-style license\n * that can be found in the LICENSE file in the root of the source\n * tree.\n *\u002F\n\n function shimRTCIceCandidate(window) {\n \u002F\u002F foundation is arbitrarily chosen as an indicator for full support for\n \u002F\u002F https:\u002F\u002Fw3c.github.io\u002Fwebrtc-pc\u002F#rtcicecandidate-interface\n if (!window.RTCIceCandidate || (window.RTCIceCandidate && 'foundation' in\n window.RTCIceCandidate.prototype)) {\n return;\n }\n\n const NativeRTCIceCandidate = window.RTCIceCandidate;\n window.RTCIceCandidate = function RTCIceCandidate(args) {\n \u002F\u002F Remove the a= which shouldn't be part of the candidate string.\n if (typeof args === 'object' && args.candidate &&\n args.candidate.indexOf('a=') === 0) {\n args = JSON.parse(JSON.stringify(args));\n args.candidate = args.candidate.substr(2);\n }\n\n if (args.candidate && args.candidate.length) {\n \u002F\u002F Augment the native candidate with the parsed fields.\n const nativeCandidate = new NativeRTCIceCandidate(args);\n const parsedCandidate = sdp.parseCandidate(args.candidate);\n const augmentedCandidate = Object.assign(nativeCandidate,\n parsedCandidate);\n\n \u002F\u002F Add a serializer that does not serialize the extra attributes.\n augmentedCandidate.toJSON = function toJSON() {\n return {\n candidate: augmentedCandidate.candidate,\n sdpMid: augmentedCandidate.sdpMid,\n sdpMLineIndex: augmentedCandidate.sdpMLineIndex,\n usernameFragment: augmentedCandidate.usernameFragment,\n };\n };\n return augmentedCandidate;\n }\n return new NativeRTCIceCandidate(args);\n };\n window.RTCIceCandidate.prototype = NativeRTCIceCandidate.prototype;\n\n \u002F\u002F Hook up the augmented candidate in onicecandidate and\n \u002F\u002F addEventListener('icecandidate', ...)\n wrapPeerConnectionEvent(window, 'icecandidate', e =\u003E {\n if (e.candidate) {\n Object.defineProperty(e, 'candidate', {\n value: new window.RTCIceCandidate(e.candidate),\n writable: 'false'\n });\n }\n return e;\n });\n }\n\n function shimMaxMessageSize(window) {\n if (!window.RTCPeerConnection) {\n return;\n }\n const browserDetails = detectBrowser(window);\n\n if (!('sctp' in window.RTCPeerConnection.prototype)) {\n Object.defineProperty(window.RTCPeerConnection.prototype, 'sctp', {\n get() {\n return typeof this._sctp === 'undefined' ? null : this._sctp;\n }\n });\n }\n\n const sctpInDescription = function(description) {\n if (!description || !description.sdp) {\n return false;\n }\n const sections = sdp.splitSections(description.sdp);\n sections.shift();\n return sections.some(mediaSection =\u003E {\n const mLine = sdp.parseMLine(mediaSection);\n return mLine && mLine.kind === 'application'\n && mLine.protocol.indexOf('SCTP') !== -1;\n });\n };\n\n const getRemoteFirefoxVersion = function(description) {\n \u002F\u002F TODO: Is there a better solution for detecting Firefox?\n const match = description.sdp.match(\u002Fmozilla...THIS_IS_SDPARTA-(\\d+)\u002F);\n if (match === null || match.length \u003C 2) {\n return -1;\n }\n const version = parseInt(match[1], 10);\n \u002F\u002F Test for NaN (yes, this is ugly)\n return version !== version ? -1 : version;\n };\n\n const getCanSendMaxMessageSize = function(remoteIsFirefox) {\n \u002F\u002F Every implementation we know can send at least 64 KiB.\n \u002F\u002F Note: Although Chrome is technically able to send up to 256 KiB, the\n \u002F\u002F data does not reach the other peer reliably.\n \u002F\u002F See: https:\u002F\u002Fbugs.chromium.org\u002Fp\u002Fwebrtc\u002Fissues\u002Fdetail?id=8419\n let canSendMaxMessageSize = 65536;\n if (browserDetails.browser === 'firefox') {\n if (browserDetails.version \u003C 57) {\n if (remoteIsFirefox === -1) {\n \u002F\u002F FF \u003C 57 will send in 16 KiB chunks using the deprecated PPID\n \u002F\u002F fragmentation.\n canSendMaxMessageSize = 16384;\n } else {\n \u002F\u002F However, other FF (and RAWRTC) can reassemble PPID-fragmented\n \u002F\u002F messages. Thus, supporting ~2 GiB when sending.\n canSendMaxMessageSize = 2147483637;\n }\n } else if (browserDetails.version \u003C 60) {\n \u002F\u002F Currently, all FF \u003E= 57 will reset the remote maximum message size\n \u002F\u002F to the default value when a data channel is created at a later\n \u002F\u002F stage. :(\n \u002F\u002F See: https:\u002F\u002Fbugzilla.mozilla.org\u002Fshow_bug.cgi?id=1426831\n canSendMaxMessageSize =\n browserDetails.version === 57 ? 65535 : 65536;\n } else {\n \u002F\u002F FF \u003E= 60 supports sending ~2 GiB\n canSendMaxMessageSize = 2147483637;\n }\n }\n return canSendMaxMessageSize;\n };\n\n const getMaxMessageSize = function(description, remoteIsFirefox) {\n \u002F\u002F Note: 65536 bytes is the default value from the SDP spec. Also,\n \u002F\u002F every implementation we know supports receiving 65536 bytes.\n let maxMessageSize = 65536;\n\n \u002F\u002F FF 57 has a slightly incorrect default remote max message size, so\n \u002F\u002F we need to adjust it here to avoid a failure when sending.\n \u002F\u002F See: https:\u002F\u002Fbugzilla.mozilla.org\u002Fshow_bug.cgi?id=1425697\n if (browserDetails.browser === 'firefox'\n && browserDetails.version === 57) {\n maxMessageSize = 65535;\n }\n\n const match = sdp.matchPrefix(description.sdp,\n 'a=max-message-size:');\n if (match.length \u003E 0) {\n maxMessageSize = parseInt(match[0].substr(19), 10);\n } else if (browserDetails.browser === 'firefox' &&\n remoteIsFirefox !== -1) {\n \u002F\u002F If the maximum message size is not present in the remote SDP and\n \u002F\u002F both local and remote are Firefox, the remote peer can receive\n \u002F\u002F ~2 GiB.\n maxMessageSize = 2147483637;\n }\n return maxMessageSize;\n };\n\n const origSetRemoteDescription =\n window.RTCPeerConnection.prototype.setRemoteDescription;\n window.RTCPeerConnection.prototype.setRemoteDescription =\n function setRemoteDescription() {\n this._sctp = null;\n \u002F\u002F Chrome decided to not expose .sctp in plan-b mode.\n \u002F\u002F As usual, adapter.js has to do an 'ugly worakaround'\n \u002F\u002F to cover up the mess.\n if (browserDetails.browser === 'chrome' && browserDetails.version \u003E= 76) {\n const {sdpSemantics} = this.getConfiguration();\n if (sdpSemantics === 'plan-b') {\n Object.defineProperty(this, 'sctp', {\n get() {\n return typeof this._sctp === 'undefined' ? null : this._sctp;\n },\n enumerable: true,\n configurable: true,\n });\n }\n }\n\n if (sctpInDescription(arguments[0])) {\n \u002F\u002F Check if the remote is FF.\n const isFirefox = getRemoteFirefoxVersion(arguments[0]);\n\n \u002F\u002F Get the maximum message size the local peer is capable of sending\n const canSendMMS = getCanSendMaxMessageSize(isFirefox);\n\n \u002F\u002F Get the maximum message size of the remote peer.\n const remoteMMS = getMaxMessageSize(arguments[0], isFirefox);\n\n \u002F\u002F Determine final maximum message size\n let maxMessageSize;\n if (canSendMMS === 0 && remoteMMS === 0) {\n maxMessageSize = Number.POSITIVE_INFINITY;\n } else if (canSendMMS === 0 || remoteMMS === 0) {\n maxMessageSize = Math.max(canSendMMS, remoteMMS);\n } else {\n maxMessageSize = Math.min(canSendMMS, remoteMMS);\n }\n\n \u002F\u002F Create a dummy RTCSctpTransport object and the 'maxMessageSize'\n \u002F\u002F attribute.\n const sctp = {};\n Object.defineProperty(sctp, 'maxMessageSize', {\n get() {\n return maxMessageSize;\n }\n });\n this._sctp = sctp;\n }\n\n return origSetRemoteDescription.apply(this, arguments);\n };\n }\n\n function shimSendThrowTypeError(window) {\n if (!(window.RTCPeerConnection &&\n 'createDataChannel' in window.RTCPeerConnection.prototype)) {\n return;\n }\n\n \u002F\u002F Note: Although Firefox \u003E= 57 has a native implementation, the maximum\n \u002F\u002F message size can be reset for all data channels at a later stage.\n \u002F\u002F See: https:\u002F\u002Fbugzilla.mozilla.org\u002Fshow_bug.cgi?id=1426831\n\n function wrapDcSend(dc, pc) {\n const origDataChannelSend = dc.send;\n dc.send = function send() {\n const data = arguments[0];\n const length = data.length || data.size || data.byteLength;\n if (dc.readyState === 'open' &&\n pc.sctp && length \u003E pc.sctp.maxMessageSize) {\n throw new TypeError('Message too large (can send a maximum of ' +\n pc.sctp.maxMessageSize + ' bytes)');\n }\n return origDataChannelSend.apply(dc, arguments);\n };\n }\n const origCreateDataChannel =\n window.RTCPeerConnection.prototype.createDataChannel;\n window.RTCPeerConnection.prototype.createDataChannel =\n function createDataChannel() {\n const dataChannel = origCreateDataChannel.apply(this, arguments);\n wrapDcSend(dataChannel, this);\n return dataChannel;\n };\n wrapPeerConnectionEvent(window, 'datachannel', e =\u003E {\n wrapDcSend(e.channel, e.target);\n return e;\n });\n }\n\n\n \u002F* shims RTCConnectionState by pretending it is the same as iceConnectionState.\n * See https:\u002F\u002Fbugs.chromium.org\u002Fp\u002Fwebrtc\u002Fissues\u002Fdetail?id=6145#c12\n * for why this is a valid hack in Chrome. In Firefox it is slightly incorrect\n * since DTLS failures would be hidden. See\n * https:\u002F\u002Fbugzilla.mozilla.org\u002Fshow_bug.cgi?id=1265827\n * for the Firefox tracking bug.\n *\u002F\n function shimConnectionState(window) {\n if (!window.RTCPeerConnection ||\n 'connectionState' in window.RTCPeerConnection.prototype) {\n return;\n }\n const proto = window.RTCPeerConnection.prototype;\n Object.defineProperty(proto, 'connectionState', {\n get() {\n return {\n completed: 'connected',\n checking: 'connecting'\n }[this.iceConnectionState] || this.iceConnectionState;\n },\n enumerable: true,\n configurable: true\n });\n Object.defineProperty(proto, 'onconnectionstatechange', {\n get() {\n return this._onconnectionstatechange || null;\n },\n set(cb) {\n if (this._onconnectionstatechange) {\n this.removeEventListener('connectionstatechange',\n this._onconnectionstatechange);\n delete this._onconnectionstatechange;\n }\n if (cb) {\n this.addEventListener('connectionstatechange',\n this._onconnectionstatechange = cb);\n }\n },\n enumerable: true,\n configurable: true\n });\n\n ['setLocalDescription', 'setRemoteDescription'].forEach((method) =\u003E {\n const origMethod = proto[method];\n proto[method] = function() {\n if (!this._connectionstatechangepoly) {\n this._connectionstatechangepoly = e =\u003E {\n const pc = e.target;\n if (pc._lastConnectionState !== pc.connectionState) {\n pc._lastConnectionState = pc.connectionState;\n const newEvent = new Event('connectionstatechange', e);\n pc.dispatchEvent(newEvent);\n }\n return e;\n };\n this.addEventListener('iceconnectionstatechange',\n this._connectionstatechangepoly);\n }\n return origMethod.apply(this, arguments);\n };\n });\n }\n\n function removeAllowExtmapMixed(window) {\n \u002F* remove a=extmap-allow-mixed for Chrome \u003C M71 *\u002F\n if (!window.RTCPeerConnection) {\n return;\n }\n const browserDetails = detectBrowser(window);\n if (browserDetails.browser === 'chrome' && browserDetails.version \u003E= 71) {\n return;\n }\n const nativeSRD = window.RTCPeerConnection.prototype.setRemoteDescription;\n window.RTCPeerConnection.prototype.setRemoteDescription =\n function setRemoteDescription(desc) {\n if (desc && desc.sdp && desc.sdp.indexOf('\\na=extmap-allow-mixed') !== -1) {\n desc.sdp = desc.sdp.split('\\n').filter((line) =\u003E {\n return line.trim() !== 'a=extmap-allow-mixed';\n }).join('\\n');\n }\n return nativeSRD.apply(this, arguments);\n };\n }\n\n var commonShim = \u002F*#__PURE__*\u002FObject.freeze({\n __proto__: null,\n shimRTCIceCandidate: shimRTCIceCandidate,\n shimMaxMessageSize: shimMaxMessageSize,\n shimSendThrowTypeError: shimSendThrowTypeError,\n shimConnectionState: shimConnectionState,\n removeAllowExtmapMixed: removeAllowExtmapMixed\n });\n\n \u002F*\n * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n * Use of this source code is governed by a BSD-style license\n * that can be found in the LICENSE file in the root of the source\n * tree.\n *\u002F\n\n \u002F\u002F Shimming starts here.\n function adapterFactory({window} = {}, options = {\n shimChrome: true,\n shimFirefox: true,\n shimEdge: true,\n shimSafari: true,\n }) {\n \u002F\u002F Utils.\n const logging = log;\n const browserDetails = detectBrowser(window);\n\n const adapter = {\n browserDetails,\n commonShim,\n extractVersion: extractVersion,\n disableLog: disableLog,\n disableWarnings: disableWarnings\n };\n\n \u002F\u002F Shim browser if found.\n switch (browserDetails.browser) {\n case 'chrome':\n if (!chromeShim || !shimPeerConnection ||\n !options.shimChrome) {\n logging('Chrome shim is not included in this adapter release.');\n return adapter;\n }\n if (browserDetails.version === null) {\n logging('Chrome shim can not determine version, not shimming.');\n return adapter;\n }\n logging('adapter.js shimming chrome.');\n \u002F\u002F Export to the adapter global object visible in the browser.\n adapter.browserShim = chromeShim;\n\n shimGetUserMedia(window);\n shimMediaStream(window);\n shimPeerConnection(window);\n shimOnTrack(window);\n shimAddTrackRemoveTrack(window);\n shimGetSendersWithDtmf(window);\n shimGetStats(window);\n shimSenderReceiverGetStats(window);\n fixNegotiationNeeded(window);\n\n shimRTCIceCandidate(window);\n shimConnectionState(window);\n shimMaxMessageSize(window);\n shimSendThrowTypeError(window);\n removeAllowExtmapMixed(window);\n break;\n case 'firefox':\n if (!firefoxShim || !shimPeerConnection$2 ||\n !options.shimFirefox) {\n logging('Firefox shim is not included in this adapter release.');\n return adapter;\n }\n logging('adapter.js shimming firefox.');\n \u002F\u002F Export to the adapter global object visible in the browser.\n adapter.browserShim = firefoxShim;\n\n shimGetUserMedia$2(window);\n shimPeerConnection$2(window);\n shimOnTrack$1(window);\n shimRemoveStream(window);\n shimSenderGetStats(window);\n shimReceiverGetStats(window);\n shimRTCDataChannel(window);\n shimAddTransceiver(window);\n shimCreateOffer(window);\n shimCreateAnswer(window);\n\n shimRTCIceCandidate(window);\n shimConnectionState(window);\n shimMaxMessageSize(window);\n shimSendThrowTypeError(window);\n break;\n case 'edge':\n if (!edgeShim || !shimPeerConnection$1 || !options.shimEdge) {\n logging('MS edge shim is not included in this adapter release.');\n return adapter;\n }\n logging('adapter.js shimming edge.');\n \u002F\u002F Export to the adapter global object visible in the browser.\n adapter.browserShim = edgeShim;\n\n shimGetUserMedia$1(window);\n shimGetDisplayMedia$1(window);\n shimPeerConnection$1(window);\n shimReplaceTrack(window);\n\n \u002F\u002F the edge shim implements the full RTCIceCandidate object.\n\n shimMaxMessageSize(window);\n shimSendThrowTypeError(window);\n break;\n case 'safari':\n if (!safariShim || !options.shimSafari) {\n logging('Safari shim is not included in this adapter release.');\n return adapter;\n }\n logging('adapter.js shimming safari.');\n \u002F\u002F Export to the adapter global object visible in the browser.\n adapter.browserShim = safariShim;\n\n shimRTCIceServerUrls(window);\n shimCreateOfferLegacy(window);\n shimCallbacksAPI(window);\n shimLocalStreamsAPI(window);\n shimRemoteStreamsAPI(window);\n shimTrackEventTransceiver(window);\n shimGetUserMedia$3(window);\n shimAudioContext(window);\n\n shimRTCIceCandidate(window);\n shimMaxMessageSize(window);\n shimSendThrowTypeError(window);\n removeAllowExtmapMixed(window);\n break;\n default:\n logging('Unsupported browser!');\n break;\n }\n\n return adapter;\n }\n\n \u002F*\n * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.\n *\n * Use of this source code is governed by a BSD-style license\n * that can be found in the LICENSE file in the root of the source\n * tree.\n *\u002F\n\n const adapter = adapterFactory({window});\n\n \u002F*\n The MIT License (MIT)\n\n Copyright (c) 2016 Meetecho\n\n Permission is hereby granted, free of charge, to any person obtaining\n a copy of this software and associated documentation files (the \"Software\"),\n to deal in the Software without restriction, including without limitation\n the rights to use, copy, modify, merge, publish, distribute, sublicense,\n and\u002For sell copies of the Software, and to permit persons to whom the\n Software is furnished to do so, subject to the following conditions:\n\n The above copyright notice and this permission notice shall be included\n in all copies or substantial portions of the Software.\n\n THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR\n OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\n ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\n OTHER DEALINGS IN THE SOFTWARE.\n *\u002F\n\n \u002F\u002F List of sessions\n Janus.sessions = {};\n\n Janus.isExtensionEnabled = function() {\n if(navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) {\n \u002F\u002F No need for the extension, getDisplayMedia is supported\n return true;\n }\n if(window.navigator.userAgent.match('Chrome')) {\n var chromever = parseInt(window.navigator.userAgent.match(\u002FChrome\\\u002F(.*) \u002F)[1], 10);\n var maxver = 33;\n if(window.navigator.userAgent.match('Linux'))\n maxver = 35;\t\u002F\u002F \"known\" crash in chrome 34 and 35 on linux\n if(chromever \u003E= 26 && chromever \u003C= maxver) {\n \u002F\u002F Older versions of Chrome don't support this extension-based approach, so lie\n return true;\n }\n return Janus.extension.isInstalled();\n } else {\n \u002F\u002F Firefox of others, no need for the extension (but this doesn't mean it will work)\n return true;\n }\n };\n\n var defaultExtension = {\n \u002F\u002F Screensharing Chrome Extension ID\n extensionId: 'hapfgfdkleiggjjpfpenajgdnfckjpaj',\n isInstalled: function() { return document.querySelector('#janus-extension-installed') !== null; },\n getScreen: function (callback) {\n var pending = window.setTimeout(function () {\n var error = new Error('NavigatorUserMediaError');\n error.name = 'The required Chrome extension is not installed: click \u003Ca href=\"#\"\u003Ehere\u003C\u002Fa\u003E to install it. (NOTE: this will need you to refresh the page)';\n return callback(error);\n }, 1000);\n this.cache[pending] = callback;\n window.postMessage({ type: 'janusGetScreen', id: pending }, '*');\n },\n init: function () {\n var cache = {};\n this.cache = cache;\n \u002F\u002F Wait for events from the Chrome Extension\n window.addEventListener('message', function (event) {\n if(event.origin != window.location.origin)\n return;\n if(event.data.type == 'janusGotScreen' && cache[event.data.id]) {\n var callback = cache[event.data.id];\n delete cache[event.data.id];\n\n if (event.data.sourceId === '') {\n \u002F\u002F user canceled\n var error = new Error('NavigatorUserMediaError');\n error.name = 'You cancelled the request for permission, giving up...';\n callback(error);\n } else {\n callback(null, event.data.sourceId);\n }\n } else if (event.data.type == 'janusGetScreenPending') {\n console.log('clearing ', event.data.id);\n window.clearTimeout(event.data.id);\n }\n });\n }\n };\n\n Janus.useDefaultDependencies = function (deps) {\n var f = (deps && deps.fetch) || fetch;\n var p = (deps && deps.Promise) || Promise;\n var socketCls = (deps && deps.WebSocket) || WebSocket;\n\n return {\n newWebSocket: function(server, proto) { return new socketCls(server, proto); },\n extension: (deps && deps.extension) || defaultExtension,\n isArray: function(arr) { return Array.isArray(arr); },\n webRTCAdapter: (deps && deps.adapter) || adapter,\n httpAPICall: function(url, options) {\n var fetchOptions = {\n method: options.verb,\n headers: {\n 'Accept': 'application\u002Fjson, text\u002Fplain, *\u002F*'\n },\n cache: 'no-cache'\n };\n if(options.verb === \"POST\") {\n fetchOptions.headers['Content-Type'] = 'application\u002Fjson';\n }\n if(options.withCredentials !== undefined) {\n fetchOptions.credentials = options.withCredentials === true ? 'include' : (options.withCredentials ? options.withCredentials : 'omit');\n }\n if(options.body) {\n fetchOptions.body = JSON.stringify(options.body);\n }\n\n var fetching = f(url, fetchOptions).catch(function(error) {\n return p.reject({message: 'Probably a network error, is the server down?', error: error});\n });\n\n \u002F*\n * fetch() does not natively support timeouts.\n * Work around this by starting a timeout manually, and racing it agains the fetch() to see which thing resolves first.\n *\u002F\n\n if(options.timeout) {\n var timeout = new p(function(resolve, reject) {\n var timerId = setTimeout(function() {\n clearTimeout(timerId);\n return reject({message: 'Request timed out', timeout: options.timeout});\n }, options.timeout);\n });\n fetching = p.race([fetching,timeout]);\n }\n\n fetching.then(function(response) {\n if(response.ok) {\n if(typeof(options.success) === typeof(Janus.noop)) {\n return response.json().then(function(parsed) {\n options.success(parsed);\n }).catch(function(error) {\n return p.reject({message: 'Failed to parse response body', error: error, response: response});\n });\n }\n }\n else {\n return p.reject({message: 'API call failed', response: response});\n }\n }).catch(function(error) {\n if(typeof(options.error) === typeof(Janus.noop)) {\n options.error(error.message || '\u003C\u003C internal error \u003E\u003E', error);\n }\n });\n\n return fetching;\n }\n }\n };\n\n Janus.useOldDependencies = function (deps) {\n var jq = (deps && deps.jQuery) || jQuery;\n var socketCls = (deps && deps.WebSocket) || WebSocket;\n return {\n newWebSocket: function(server, proto) { return new socketCls(server, proto); },\n isArray: function(arr) { return jq.isArray(arr); },\n extension: (deps && deps.extension) || defaultExtension,\n webRTCAdapter: (deps && deps.adapter) || adapter,\n httpAPICall: function(url, options) {\n var payload = options.body !== undefined ? {\n contentType: 'application\u002Fjson',\n data: JSON.stringify(options.body)\n } : {};\n var credentials = options.withCredentials !== undefined ? {xhrFields: {withCredentials: options.withCredentials}} : {};\n\n return jq.ajax(jq.extend(payload, credentials, {\n url: url,\n type: options.verb,\n cache: false,\n dataType: 'json',\n async: options.async,\n timeout: options.timeout,\n success: function(result) {\n if(typeof(options.success) === typeof(Janus.noop)) {\n options.success(result);\n }\n },\n error: function(xhr, status, err) {\n if(typeof(options.error) === typeof(Janus.noop)) {\n options.error(status, err);\n }\n }\n }));\n },\n };\n };\n\n Janus.noop = function() {};\n\n Janus.dataChanDefaultLabel = \"JanusDataChannel\";\n\n \u002F\u002F Note: in the future we may want to change this, e.g., as was\n \u002F\u002F attempted in https:\u002F\u002Fgithub.com\u002Fmeetecho\u002Fjanus-gateway\u002Fissues\u002F1670\n Janus.endOfCandidates = null;\n\n \u002F\u002F Initialization\n Janus.init = function(options) {\n options = options || {};\n options.callback = (typeof options.callback == \"function\") ? options.callback : Janus.noop;\n if(Janus.initDone) {\n \u002F\u002F Already initialized\n options.callback();\n } else {\n if(typeof console == \"undefined\" || typeof console.log == \"undefined\")\n console = { log: function() {} };\n \u002F\u002F Console logging (all debugging disabled by default)\n Janus.trace = Janus.noop;\n Janus.debug = Janus.noop;\n Janus.vdebug = Janus.noop;\n Janus.log = Janus.noop;\n Janus.warn = Janus.noop;\n Janus.error = Janus.noop;\n if(options.debug === true || options.debug === \"all\") {\n \u002F\u002F Enable all debugging levels\n Janus.trace = console.trace.bind(console);\n Janus.debug = console.debug.bind(console);\n Janus.vdebug = console.debug.bind(console);\n Janus.log = console.log.bind(console);\n Janus.warn = console.warn.bind(console);\n Janus.error = console.error.bind(console);\n } else if(Array.isArray(options.debug)) {\n for(var d of options.debug) {\n switch(d) {\n case \"trace\":\n Janus.trace = console.trace.bind(console);\n break;\n case \"debug\":\n Janus.debug = console.debug.bind(console);\n break;\n case \"vdebug\":\n Janus.vdebug = console.debug.bind(console);\n break;\n case \"log\":\n Janus.log = console.log.bind(console);\n break;\n case \"warn\":\n Janus.warn = console.warn.bind(console);\n break;\n case \"error\":\n Janus.error = console.error.bind(console);\n break;\n default:\n console.error(\"Unknown debugging option '\" + d + \"' (supported: 'trace', 'debug', 'vdebug', 'log', warn', 'error')\");\n break;\n }\n }\n }\n Janus.log(\"Initializing library\");\n\n var usedDependencies = options.dependencies || Janus.useDefaultDependencies();\n Janus.isArray = usedDependencies.isArray;\n Janus.webRTCAdapter = usedDependencies.webRTCAdapter;\n Janus.httpAPICall = usedDependencies.httpAPICall;\n Janus.newWebSocket = usedDependencies.newWebSocket;\n Janus.extension = usedDependencies.extension;\n Janus.extension.init();\n\n \u002F\u002F Helper method to enumerate devices\n Janus.listDevices = function(callback, config) {\n callback = (typeof callback == \"function\") ? callback : Janus.noop;\n if (config == null) config = { audio: true, video: true };\n if(Janus.isGetUserMediaAvailable()) {\n navigator.mediaDevices.getUserMedia(config)\n .then(function(stream) {\n navigator.mediaDevices.enumerateDevices().then(function(devices) {\n Janus.debug(devices);\n callback(devices);\n \u002F\u002F Get rid of the now useless stream\n try {\n var tracks = stream.getTracks();\n for(var mst of tracks) {\n if(mst)\n mst.stop();\n }\n } catch(e) {}\n });\n })\n .catch(function(err) {\n Janus.error(err);\n callback([]);\n });\n } else {\n Janus.warn(\"navigator.mediaDevices unavailable\");\n callback([]);\n }\n };\n \u002F\u002F Helper methods to attach\u002Freattach a stream to a video element (previously part of adapter.js)\n Janus.attachMediaStream = function(element, stream) {\n try {\n\n element.srcObject = stream;\n } catch (e) {\n try {\n element.src = URL.createObjectURL(stream);\n } catch (e) {\n Janus.error(\"Error attaching stream to element\");\n }\n }\n };\n Janus.reattachMediaStream = function(to, from) {\n try {\n to.srcObject = from.srcObject;\n } catch (e) {\n try {\n to.src = from.src;\n } catch (e) {\n Janus.error(\"Error reattaching stream to element\");\n }\n }\n };\n \u002F\u002F Detect tab close: make sure we don't loose existing onbeforeunload handlers\n \u002F\u002F (note: for iOS we need to subscribe to a different event, 'pagehide', see\n \u002F\u002F https:\u002F\u002Fgist.github.com\u002Fthehunmonkgroup\u002F6bee8941a49b86be31a787fe8f4b8cfe)\n var iOS = ['iPad', 'iPhone', 'iPod'].indexOf(navigator.platform) \u003E= 0;\n var eventName = iOS ? 'pagehide' : 'beforeunload';\n var oldOBF = window[\"on\" + eventName];\n window.onbeforeunload = function(event) {\n Janus.log(\"Closing window\");\n for(var s in Janus.sessions) {\n if(Janus.sessions[s] && Janus.sessions[s].destroyOnUnload) {\n Janus.log(\"Destroying session \" + s);\n Janus.sessions[s].destroy({asyncRequest: false, notifyDestroyed: false});\n }\n }\n if(oldOBF && typeof oldOBF == \"function\")\n oldOBF();\n };\n \u002F\u002F If this is a Safari Technology Preview, check if VP8 is supported\n Janus.safariVp8 = false;\n if(Janus.webRTCAdapter.browserDetails.browser === 'safari' &&\n Janus.webRTCAdapter.browserDetails.version \u003E= 605) {\n \u002F\u002F Let's see if RTCRtpSender.getCapabilities() is there\n if(RTCRtpSender && RTCRtpSender.getCapabilities && RTCRtpSender.getCapabilities(\"video\") &&\n RTCRtpSender.getCapabilities(\"video\").codecs && RTCRtpSender.getCapabilities(\"video\").codecs.length) {\n for(var codec of RTCRtpSender.getCapabilities(\"video\").codecs) {\n if(codec && codec.mimeType && codec.mimeType.toLowerCase() === \"video\u002Fvp8\") {\n Janus.safariVp8 = true;\n break;\n }\n }\n if(Janus.safariVp8) {\n Janus.log(\"This version of Safari supports VP8\");\n } else {\n Janus.warn(\"This version of Safari does NOT support VP8: if you're using a Technology Preview, \" +\n \"try enabling the 'WebRTC VP8 codec' setting in the 'Experimental Features' Develop menu\");\n }\n } else {\n \u002F\u002F We do it in a very ugly way, as there's no alternative...\n \u002F\u002F We create a PeerConnection to see if VP8 is in an offer\n var testpc = new RTCPeerConnection({}, {});\n testpc.createOffer({offerToReceiveVideo: true}).then(function(offer) {\n Janus.safariVp8 = offer.sdp.indexOf(\"VP8\") !== -1;\n if(Janus.safariVp8) {\n Janus.log(\"This version of Safari supports VP8\");\n } else {\n Janus.warn(\"This version of Safari does NOT support VP8: if you're using a Technology Preview, \" +\n \"try enabling the 'WebRTC VP8 codec' setting in the 'Experimental Features' Develop menu\");\n }\n testpc.close();\n testpc = null;\n });\n }\n }\n \u002F\u002F Check if this browser supports Unified Plan and transceivers\n \u002F\u002F Based on https:\u002F\u002Fcodepen.io\u002Fanon\u002Fpen\u002FZqLwWV?editors=0010\n Janus.unifiedPlan = false;\n if(Janus.webRTCAdapter.browserDetails.browser === 'firefox' &&\n Janus.webRTCAdapter.browserDetails.version \u003E= 59) {\n \u002F\u002F Firefox definitely does, starting from version 59\n Janus.unifiedPlan = true;\n } else if(Janus.webRTCAdapter.browserDetails.browser === 'chrome' &&\n Janus.webRTCAdapter.browserDetails.version \u003C 72) {\n \u002F\u002F Chrome does, but it's only usable from version 72 on\n Janus.unifiedPlan = false;\n } else if(!window.RTCRtpTransceiver || !('currentDirection' in RTCRtpTransceiver.prototype)) {\n \u002F\u002F Safari supports addTransceiver() but not Unified Plan when\n \u002F\u002F currentDirection is not defined (see codepen above).\n Janus.unifiedPlan = false;\n } else {\n \u002F\u002F Check if addTransceiver() throws an exception\n const tempPc = new RTCPeerConnection();\n try {\n tempPc.addTransceiver('audio');\n Janus.unifiedPlan = true;\n } catch (e) {}\n tempPc.close();\n }\n Janus.initDone = true;\n options.callback();\n }\n };\n\n \u002F\u002F Helper method to check whether WebRTC is supported by this browser\n Janus.isWebrtcSupported = function() {\n return window.RTCPeerConnection ? true : false;\n };\n \u002F\u002F Helper method to check whether devices can be accessed by this browser (e.g., not possible via plain HTTP)\n Janus.isGetUserMediaAvailable = function() {\n return (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) ? true : false;\n };\n\n \u002F\u002F Helper method to create random identifiers (e.g., transaction)\n Janus.randomString = function(len) {\n var charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';\n var randomString = '';\n for (var i = 0; i \u003C len; i++) {\n var randomPoz = Math.floor(Math.random() * charSet.length);\n randomString += charSet.substring(randomPoz,randomPoz+1);\n }\n return randomString;\n };\n\n\n function Janus(gatewayCallbacks) {\n if(!Janus.initDone) {\n gatewayCallbacks.error(\"Library not initialized\");\n return {};\n }\n if(!Janus.isWebrtcSupported()) {\n gatewayCallbacks.error(\"WebRTC not supported by this browser\");\n return {};\n }\n Janus.log(\"Library initialized: \" + Janus.initDone);\n gatewayCallbacks = gatewayCallbacks || {};\n gatewayCallbacks.success = (typeof gatewayCallbacks.success == \"function\") ? gatewayCallbacks.success : Janus.noop;\n gatewayCallbacks.error = (typeof gatewayCallbacks.error == \"function\") ? gatewayCallbacks.error : Janus.noop;\n gatewayCallbacks.destroyed = (typeof gatewayCallbacks.destroyed == \"function\") ? gatewayCallbacks.destroyed : Janus.noop;\n if(!gatewayCallbacks.server) {\n gatewayCallbacks.error(\"Invalid server url\");\n return {};\n }\n var websockets = false;\n var ws = null;\n var wsHandlers = {};\n var wsKeepaliveTimeoutId = null;\n\n var servers = null, serversIndex = 0;\n var server = gatewayCallbacks.server;\n if(Janus.isArray(server)) {\n Janus.log(\"Multiple servers provided (\" + server.length + \"), will use the first that works\");\n server = null;\n servers = gatewayCallbacks.server;\n Janus.debug(servers);\n } else {\n if(server.indexOf(\"ws\") === 0) {\n websockets = true;\n Janus.log(\"Using WebSockets to contact Janus: \" + server);\n } else {\n websockets = false;\n Janus.log(\"Using REST API to contact Janus: \" + server);\n }\n }\n var iceServers = gatewayCallbacks.iceServers || [{urls: \"stun:stun.l.google.com:19302\"}];\n var iceTransportPolicy = gatewayCallbacks.iceTransportPolicy;\n var bundlePolicy = gatewayCallbacks.bundlePolicy;\n \u002F\u002F Whether IPv6 candidates should be gathered\n var ipv6Support = (gatewayCallbacks.ipv6 === true);\n \u002F\u002F Whether we should enable the withCredentials flag for XHR requests\n var withCredentials = false;\n if(gatewayCallbacks.withCredentials !== undefined && gatewayCallbacks.withCredentials !== null)\n withCredentials = gatewayCallbacks.withCredentials === true;\n \u002F\u002F Optional max events\n var maxev = 10;\n if(gatewayCallbacks.max_poll_events !== undefined && gatewayCallbacks.max_poll_events !== null)\n maxev = gatewayCallbacks.max_poll_events;\n if(maxev \u003C 1)\n maxev = 1;\n \u002F\u002F Token to use (only if the token based authentication mechanism is enabled)\n var token = null;\n if(gatewayCallbacks.token !== undefined && gatewayCallbacks.token !== null)\n token = gatewayCallbacks.token;\n \u002F\u002F API secret to use (only if the shared API secret is enabled)\n var apisecret = null;\n if(gatewayCallbacks.apisecret !== undefined && gatewayCallbacks.apisecret !== null)\n apisecret = gatewayCallbacks.apisecret;\n \u002F\u002F Whether we should destroy this session when onbeforeunload is called\n this.destroyOnUnload = true;\n if(gatewayCallbacks.destroyOnUnload !== undefined && gatewayCallbacks.destroyOnUnload !== null)\n this.destroyOnUnload = (gatewayCallbacks.destroyOnUnload === true);\n \u002F\u002F Some timeout-related values\n var keepAlivePeriod = 25000;\n if(gatewayCallbacks.keepAlivePeriod !== undefined && gatewayCallbacks.keepAlivePeriod !== null)\n keepAlivePeriod = gatewayCallbacks.keepAlivePeriod;\n if(isNaN(keepAlivePeriod))\n keepAlivePeriod = 25000;\n var longPollTimeout = 60000;\n if(gatewayCallbacks.longPollTimeout !== undefined && gatewayCallbacks.longPollTimeout !== null)\n longPollTimeout = gatewayCallbacks.longPollTimeout;\n if(isNaN(longPollTimeout))\n longPollTimeout = 60000;\n\n \u002F\u002F overrides for default maxBitrate values for simulcasting\n function getMaxBitrates(simulcastMaxBitrates) {\n var maxBitrates = {\n high: 900000,\n medium: 300000,\n low: 100000,\n };\n\n if (simulcastMaxBitrates !== undefined && simulcastMaxBitrates !== null) {\n if (simulcastMaxBitrates.high)\n maxBitrates.high = simulcastMaxBitrates.high;\n if (simulcastMaxBitrates.medium)\n maxBitrates.medium = simulcastMaxBitrates.medium;\n if (simulcastMaxBitrates.low)\n maxBitrates.low = simulcastMaxBitrates.low;\n }\n\n return maxBitrates;\n }\n\n var connected = false;\n var sessionId = null;\n var pluginHandles = {};\n var that = this;\n var retries = 0;\n var transactions = {};\n createSession(gatewayCallbacks);\n\n \u002F\u002F Public methods\n this.getServer = function() { return server; };\n this.isConnected = function() { return connected; };\n this.reconnect = function(callbacks) {\n callbacks = callbacks || {};\n callbacks.success = (typeof callbacks.success == \"function\") ? callbacks.success : Janus.noop;\n callbacks.error = (typeof callbacks.error == \"function\") ? callbacks.error : Janus.noop;\n callbacks[\"reconnect\"] = true;\n createSession(callbacks);\n };\n this.getSessionId = function() { return sessionId; };\n this.destroy = function(callbacks) { destroySession(callbacks); };\n this.attach = function(callbacks) { createHandle(callbacks); };\n\n function eventHandler() {\n if(sessionId == null)\n return;\n Janus.debug('Long poll...');\n if(!connected) {\n Janus.warn(\"Is the server down? (connected=false)\");\n return;\n }\n var longpoll = server + \"\u002F\" + sessionId + \"?rid=\" + new Date().getTime();\n if(maxev)\n longpoll = longpoll + \"&maxev=\" + maxev;\n if(token)\n longpoll = longpoll + \"&token=\" + encodeURIComponent(token);\n if(apisecret)\n longpoll = longpoll + \"&apisecret=\" + encodeURIComponent(apisecret);\n Janus.httpAPICall(longpoll, {\n verb: 'GET',\n withCredentials: withCredentials,\n success: handleEvent,\n timeout: longPollTimeout,\n error: function(textStatus, errorThrown) {\n Janus.error(textStatus + \":\", errorThrown);\n retries++;\n if(retries \u003E 3) {\n \u002F\u002F Did we just lose the server? :-(\n connected = false;\n gatewayCallbacks.error(\"Lost connection to the server (is it down?)\");\n return;\n }\n eventHandler();\n }\n });\n }\n\n \u002F\u002F Private event handler: this will trigger plugin callbacks, if set\n function handleEvent(json, skipTimeout) {\n retries = 0;\n if(!websockets && sessionId !== undefined && sessionId !== null && skipTimeout !== true)\n eventHandler();\n if(!websockets && Janus.isArray(json)) {\n \u002F\u002F We got an array: it means we passed a maxev \u003E 1, iterate on all objects\n for(var i=0; i\u003Cjson.length; i++) {\n handleEvent(json[i], true);\n }\n return;\n }\n if(json[\"janus\"] === \"keepalive\") {\n \u002F\u002F Nothing happened\n Janus.vdebug(\"Got a keepalive on session \" + sessionId);\n return;\n } else if(json[\"janus\"] === \"ack\") {\n \u002F\u002F Just an ack, we can probably ignore\n Janus.debug(\"Got an ack on session \" + sessionId);\n Janus.debug(json);\n var transaction = json[\"transaction\"];\n if(transaction) {\n var reportSuccess = transactions[transaction];\n if(reportSuccess)\n reportSuccess(json);\n delete transactions[transaction];\n }\n return;\n } else if(json[\"janus\"] === \"success\") {\n \u002F\u002F Success!\n Janus.debug(\"Got a success on session \" + sessionId);\n Janus.debug(json);\n var transaction = json[\"transaction\"];\n if(transaction) {\n var reportSuccess = transactions[transaction];\n if(reportSuccess)\n reportSuccess(json);\n delete transactions[transaction];\n }\n return;\n } else if(json[\"janus\"] === \"trickle\") {\n \u002F\u002F We got a trickle candidate from Janus\n var sender = json[\"sender\"];\n if(!sender) {\n Janus.warn(\"Missing sender...\");\n return;\n }\n var pluginHandle = pluginHandles[sender];\n if(!pluginHandle) {\n Janus.debug(\"This handle is not attached to this session\");\n return;\n }\n var candidate = json[\"candidate\"];\n Janus.debug(\"Got a trickled candidate on session \" + sessionId);\n Janus.debug(candidate);\n var config = pluginHandle.webrtcStuff;\n if(config.pc && config.remoteSdp) {\n \u002F\u002F Add candidate right now\n Janus.debug(\"Adding remote candidate:\", candidate);\n if(!candidate || candidate.completed === true) {\n \u002F\u002F end-of-candidates\n config.pc.addIceCandidate(Janus.endOfCandidates);\n } else {\n \u002F\u002F New candidate\n config.pc.addIceCandidate(candidate);\n }\n } else {\n \u002F\u002F We didn't do setRemoteDescription (trickle got here before the offer?)\n Janus.debug(\"We didn't do setRemoteDescription (trickle got here before the offer?), caching candidate\");\n if(!config.candidates)\n config.candidates = [];\n config.candidates.push(candidate);\n Janus.debug(config.candidates);\n }\n } else if(json[\"janus\"] === \"webrtcup\") {\n \u002F\u002F The PeerConnection with the server is up! Notify this\n Janus.debug(\"Got a webrtcup event on session \" + sessionId);\n Janus.debug(json);\n var sender = json[\"sender\"];\n if(!sender) {\n Janus.warn(\"Missing sender...\");\n return;\n }\n var pluginHandle = pluginHandles[sender];\n if(!pluginHandle) {\n Janus.debug(\"This handle is not attached to this session\");\n return;\n }\n pluginHandle.webrtcState(true);\n return;\n } else if(json[\"janus\"] === \"hangup\") {\n \u002F\u002F A plugin asked the core to hangup a PeerConnection on one of our handles\n Janus.debug(\"Got a hangup event on session \" + sessionId);\n Janus.debug(json);\n var sender = json[\"sender\"];\n if(!sender) {\n Janus.warn(\"Missing sender...\");\n return;\n }\n var pluginHandle = pluginHandles[sender];\n if(!pluginHandle) {\n Janus.debug(\"This handle is not attached to this session\");\n return;\n }\n pluginHandle.webrtcState(false, json[\"reason\"]);\n pluginHandle.hangup();\n } else if(json[\"janus\"] === \"detached\") {\n \u002F\u002F A plugin asked the core to detach one of our handles\n Janus.debug(\"Got a detached event on session \" + sessionId);\n Janus.debug(json);\n var sender = json[\"sender\"];\n if(!sender) {\n Janus.warn(\"Missing sender...\");\n return;\n }\n var pluginHandle = pluginHandles[sender];\n if(!pluginHandle) {\n \u002F\u002F Don't warn here because destroyHandle causes this situation.\n return;\n }\n pluginHandle.detached = true;\n pluginHandle.ondetached();\n pluginHandle.detach();\n } else if(json[\"janus\"] === \"media\") {\n \u002F\u002F Media started\u002Fstopped flowing\n Janus.debug(\"Got a media event on session \" + sessionId);\n Janus.debug(json);\n var sender = json[\"sender\"];\n if(!sender) {\n Janus.warn(\"Missing sender...\");\n return;\n }\n var pluginHandle = pluginHandles[sender];\n if(!pluginHandle) {\n Janus.debug(\"This handle is not attached to this session\");\n return;\n }\n pluginHandle.mediaState(json[\"type\"], json[\"receiving\"]);\n } else if(json[\"janus\"] === \"slowlink\") {\n Janus.debug(\"Got a slowlink event on session \" + sessionId);\n Janus.debug(json);\n \u002F\u002F Trouble uplink or downlink\n var sender = json[\"sender\"];\n if(!sender) {\n Janus.warn(\"Missing sender...\");\n return;\n }\n var pluginHandle = pluginHandles[sender];\n if(!pluginHandle) {\n Janus.debug(\"This handle is not attached to this session\");\n return;\n }\n pluginHandle.slowLink(json[\"uplink\"], json[\"lost\"]);\n } else if(json[\"janus\"] === \"error\") {\n \u002F\u002F Oops, something wrong happened\n Janus.error(\"Ooops: \" + json[\"error\"].code + \" \" + json[\"error\"].reason);\t\u002F\u002F FIXME\n Janus.debug(json);\n var transaction = json[\"transaction\"];\n if(transaction) {\n var reportSuccess = transactions[transaction];\n if(reportSuccess) {\n reportSuccess(json);\n }\n delete transactions[transaction];\n }\n return;\n } else if(json[\"janus\"] === \"event\") {\n Janus.debug(\"Got a plugin event on session \" + sessionId);\n Janus.debug(json);\n var sender = json[\"sender\"];\n if(!sender) {\n Janus.warn(\"Missing sender...\");\n return;\n }\n var plugindata = json[\"plugindata\"];\n if(!plugindata) {\n Janus.warn(\"Missing plugindata...\");\n return;\n }\n Janus.debug(\" -- Event is coming from \" + sender + \" (\" + plugindata[\"plugin\"] + \")\");\n var data = plugindata[\"data\"];\n Janus.debug(data);\n var pluginHandle = pluginHandles[sender];\n if(!pluginHandle) {\n Janus.warn(\"This handle is not attached to this session\");\n return;\n }\n var jsep = json[\"jsep\"];\n if(jsep) {\n Janus.debug(\"Handling SDP as well...\");\n Janus.debug(jsep);\n }\n var callback = pluginHandle.onmessage;\n if(callback) {\n Janus.debug(\"Notifying application...\");\n \u002F\u002F Send to callback specified when attaching plugin handle\n callback(data, jsep);\n } else {\n \u002F\u002F Send to generic callback (?)\n Janus.debug(\"No provided notification callback\");\n }\n } else if(json[\"janus\"] === \"timeout\") {\n Janus.error(\"Timeout on session \" + sessionId);\n Janus.debug(json);\n if (websockets) {\n ws.close(3504, \"Gateway timeout\");\n }\n return;\n } else {\n Janus.warn(\"Unknown message\u002Fevent '\" + json[\"janus\"] + \"' on session \" + sessionId);\n Janus.debug(json);\n }\n }\n\n \u002F\u002F Private helper to send keep-alive messages on WebSockets\n function keepAlive() {\n if(!server || !websockets || !connected)\n return;\n wsKeepaliveTimeoutId = setTimeout(keepAlive, keepAlivePeriod);\n var request = { \"janus\": \"keepalive\", \"session_id\": sessionId, \"transaction\": Janus.randomString(12) };\n if(token)\n request[\"token\"] = token;\n if(apisecret)\n request[\"apisecret\"] = apisecret;\n ws.send(JSON.stringify(request));\n }\n\n \u002F\u002F Private method to create a session\n function createSession(callbacks) {\n var transaction = Janus.randomString(12);\n var request = { \"janus\": \"create\", \"transaction\": transaction };\n if(callbacks[\"reconnect\"]) {\n \u002F\u002F We're reconnecting, claim the session\n connected = false;\n request[\"janus\"] = \"claim\";\n request[\"session_id\"] = sessionId;\n \u002F\u002F If we were using websockets, ignore the old connection\n if(ws) {\n ws.onopen = null;\n ws.onerror = null;\n ws.onclose = null;\n if(wsKeepaliveTimeoutId) {\n clearTimeout(wsKeepaliveTimeoutId);\n wsKeepaliveTimeoutId = null;\n }\n }\n }\n if(token)\n request[\"token\"] = token;\n if(apisecret)\n request[\"apisecret\"] = apisecret;\n if(!server && Janus.isArray(servers)) {\n \u002F\u002F We still need to find a working server from the list we were given\n server = servers[serversIndex];\n if(server.indexOf(\"ws\") === 0) {\n websockets = true;\n Janus.log(\"Server #\" + (serversIndex+1) + \": trying WebSockets to contact Janus (\" + server + \")\");\n } else {\n websockets = false;\n Janus.log(\"Server #\" + (serversIndex+1) + \": trying REST API to contact Janus (\" + server + \")\");\n }\n }\n if(websockets) {\n ws = Janus.newWebSocket(server, 'janus-protocol');\n wsHandlers = {\n 'error': function() {\n Janus.error(\"Error connecting to the Janus WebSockets server... \" + server);\n if (Janus.isArray(servers) && !callbacks[\"reconnect\"]) {\n serversIndex++;\n if (serversIndex == servers.length) {\n \u002F\u002F We tried all the servers the user gave us and they all failed\n callbacks.error(\"Error connecting to any of the provided Janus servers: Is the server down?\");\n return;\n }\n \u002F\u002F Let's try the next server\n server = null;\n setTimeout(function() {\n createSession(callbacks);\n }, 200);\n return;\n }\n callbacks.error(\"Error connecting to the Janus WebSockets server: Is the server down?\");\n },\n\n 'open': function() {\n \u002F\u002F We need to be notified about the success\n transactions[transaction] = function(json) {\n Janus.debug(json);\n if (json[\"janus\"] !== \"success\") {\n Janus.error(\"Ooops: \" + json[\"error\"].code + \" \" + json[\"error\"].reason);\t\u002F\u002F FIXME\n callbacks.error(json[\"error\"].reason);\n return;\n }\n wsKeepaliveTimeoutId = setTimeout(keepAlive, keepAlivePeriod);\n connected = true;\n sessionId = json[\"session_id\"] ? json[\"session_id\"] : json.data[\"id\"];\n if(callbacks[\"reconnect\"]) {\n Janus.log(\"Claimed session: \" + sessionId);\n } else {\n Janus.log(\"Created session: \" + sessionId);\n }\n Janus.sessions[sessionId] = that;\n callbacks.success();\n };\n ws.send(JSON.stringify(request));\n },\n\n 'message': function(event) {\n handleEvent(JSON.parse(event.data));\n },\n\n 'close': function() {\n if (!server || !connected) {\n return;\n }\n connected = false;\n \u002F\u002F FIXME What if this is called when the page is closed?\n gatewayCallbacks.error(\"Lost connection to the server (is it down?)\");\n }\n };\n\n for(var eventName in wsHandlers) {\n ws.addEventListener(eventName, wsHandlers[eventName]);\n }\n\n return;\n }\n Janus.httpAPICall(server, {\n verb: 'POST',\n withCredentials: withCredentials,\n body: request,\n success: function(json) {\n Janus.debug(json);\n if(json[\"janus\"] !== \"success\") {\n Janus.error(\"Ooops: \" + json[\"error\"].code + \" \" + json[\"error\"].reason);\t\u002F\u002F FIXME\n callbacks.error(json[\"error\"].reason);\n return;\n }\n connected = true;\n sessionId = json[\"session_id\"] ? json[\"session_id\"] : json.data[\"id\"];\n if(callbacks[\"reconnect\"]) {\n Janus.log(\"Claimed session: \" + sessionId);\n } else {\n Janus.log(\"Created session: \" + sessionId);\n }\n Janus.sessions[sessionId] = that;\n eventHandler();\n callbacks.success();\n },\n error: function(textStatus, errorThrown) {\n Janus.error(textStatus + \":\", errorThrown);\t\u002F\u002F FIXME\n if(Janus.isArray(servers) && !callbacks[\"reconnect\"]) {\n serversIndex++;\n if(serversIndex == servers.length) {\n \u002F\u002F We tried all the servers the user gave us and they all failed\n callbacks.error(\"Error connecting to any of the provided Janus servers: Is the server down?\");\n return;\n }\n \u002F\u002F Let's try the next server\n server = null;\n setTimeout(function() { createSession(callbacks); }, 200);\n return;\n }\n if(errorThrown === \"\")\n callbacks.error(textStatus + \": Is the server down?\");\n else\n callbacks.error(textStatus + \": \" + errorThrown);\n }\n });\n }\n\n \u002F\u002F Private method to destroy a session\n function destroySession(callbacks) {\n callbacks = callbacks || {};\n \u002F\u002F FIXME This method triggers a success even when we fail\n callbacks.success = (typeof callbacks.success == \"function\") ? callbacks.success : Janus.noop;\n var asyncRequest = true;\n if(callbacks.asyncRequest !== undefined && callbacks.asyncRequest !== null)\n asyncRequest = (callbacks.asyncRequest === true);\n var notifyDestroyed = true;\n if(callbacks.notifyDestroyed !== undefined && callbacks.notifyDestroyed !== null)\n notifyDestroyed = (callbacks.notifyDestroyed === true);\n var cleanupHandles = false;\n if(callbacks.cleanupHandles !== undefined && callbacks.cleanupHandles !== null)\n cleanupHandles = (callbacks.cleanupHandles === true);\n Janus.log(\"Destroying session \" + sessionId + \" (async=\" + asyncRequest + \")\");\n if(!sessionId) {\n Janus.warn(\"No session to destroy\");\n callbacks.success();\n if(notifyDestroyed)\n gatewayCallbacks.destroyed();\n return;\n }\n if(cleanupHandles) {\n for(var handleId in pluginHandles)\n destroyHandle(handleId, { noRequest: true });\n }\n if(!connected) {\n Janus.warn(\"Is the server down? (connected=false)\");\n callbacks.success();\n return;\n }\n \u002F\u002F No need to destroy all handles first, Janus will do that itself\n var request = { \"janus\": \"destroy\", \"transaction\": Janus.randomString(12) };\n if(token)\n request[\"token\"] = token;\n if(apisecret)\n request[\"apisecret\"] = apisecret;\n if(websockets) {\n request[\"session_id\"] = sessionId;\n\n var unbindWebSocket = function() {\n for(var eventName in wsHandlers) {\n ws.removeEventListener(eventName, wsHandlers[eventName]);\n }\n ws.removeEventListener('message', onUnbindMessage);\n ws.removeEventListener('error', onUnbindError);\n if(wsKeepaliveTimeoutId) {\n clearTimeout(wsKeepaliveTimeoutId);\n }\n ws.close();\n };\n\n var onUnbindMessage = function(event){\n var data = JSON.parse(event.data);\n if(data.session_id == request.session_id && data.transaction == request.transaction) {\n unbindWebSocket();\n callbacks.success();\n if(notifyDestroyed)\n gatewayCallbacks.destroyed();\n }\n };\n var onUnbindError = function(event) {\n unbindWebSocket();\n callbacks.error(\"Failed to destroy the server: Is the server down?\");\n if(notifyDestroyed)\n gatewayCallbacks.destroyed();\n };\n\n ws.addEventListener('message', onUnbindMessage);\n ws.addEventListener('error', onUnbindError);\n\n ws.send(JSON.stringify(request));\n return;\n }\n Janus.httpAPICall(server + \"\u002F\" + sessionId, {\n verb: 'POST',\n async: asyncRequest,\t\u002F\u002F Sometimes we need false here, or destroying in onbeforeunload won't work\n withCredentials: withCredentials,\n body: request,\n success: function(json) {\n Janus.log(\"Destroyed session:\");\n Janus.debug(json);\n sessionId = null;\n connected = false;\n if(json[\"janus\"] !== \"success\") {\n Janus.error(\"Ooops: \" + json[\"error\"].code + \" \" + json[\"error\"].reason);\t\u002F\u002F FIXME\n }\n callbacks.success();\n if(notifyDestroyed)\n gatewayCallbacks.destroyed();\n },\n error: function(textStatus, errorThrown) {\n Janus.error(textStatus + \":\", errorThrown);\t\u002F\u002F FIXME\n \u002F\u002F Reset everything anyway\n sessionId = null;\n connected = false;\n callbacks.success();\n if(notifyDestroyed)\n gatewayCallbacks.destroyed();\n }\n });\n }\n\n \u002F\u002F Private method to create a plugin handle\n function createHandle(callbacks) {\n callbacks = callbacks || {};\n callbacks.success = (typeof callbacks.success == \"function\") ? callbacks.success : Janus.noop;\n callbacks.error = (typeof callbacks.error == \"function\") ? callbacks.error : Janus.noop;\n callbacks.consentDialog = (typeof callbacks.consentDialog == \"function\") ? callbacks.consentDialog : Janus.noop;\n callbacks.iceState = (typeof callbacks.iceState == \"function\") ? callbacks.iceState : Janus.noop;\n callbacks.mediaState = (typeof callbacks.mediaState == \"function\") ? callbacks.mediaState : Janus.noop;\n callbacks.webrtcState = (typeof callbacks.webrtcState == \"function\") ? callbacks.webrtcState : Janus.noop;\n callbacks.slowLink = (typeof callbacks.slowLink == \"function\") ? callbacks.slowLink : Janus.noop;\n callbacks.onmessage = (typeof callbacks.onmessage == \"function\") ? callbacks.onmessage : Janus.noop;\n callbacks.onlocalstream = (typeof callbacks.onlocalstream == \"function\") ? callbacks.onlocalstream : Janus.noop;\n callbacks.onremotestream = (typeof callbacks.onremotestream == \"function\") ? callbacks.onremotestream : Janus.noop;\n callbacks.ondata = (typeof callbacks.ondata == \"function\") ? callbacks.ondata : Janus.noop;\n callbacks.ondataopen = (typeof callbacks.ondataopen == \"function\") ? callbacks.ondataopen : Janus.noop;\n callbacks.oncleanup = (typeof callbacks.oncleanup == \"function\") ? callbacks.oncleanup : Janus.noop;\n callbacks.ondetached = (typeof callbacks.ondetached == \"function\") ? callbacks.ondetached : Janus.noop;\n if(!connected) {\n Janus.warn(\"Is the server down? (connected=false)\");\n callbacks.error(\"Is the server down? (connected=false)\");\n return;\n }\n var plugin = callbacks.plugin;\n if(!plugin) {\n Janus.error(\"Invalid plugin\");\n callbacks.error(\"Invalid plugin\");\n return;\n }\n var opaqueId = callbacks.opaqueId;\n var handleToken = callbacks.token ? callbacks.token : token;\n var transaction = Janus.randomString(12);\n var request = { \"janus\": \"attach\", \"plugin\": plugin, \"opaque_id\": opaqueId, \"transaction\": transaction };\n if(handleToken)\n request[\"token\"] = handleToken;\n if(apisecret)\n request[\"apisecret\"] = apisecret;\n if(websockets) {\n transactions[transaction] = function(json) {\n Janus.debug(json);\n if(json[\"janus\"] !== \"success\") {\n Janus.error(\"Ooops: \" + json[\"error\"].code + \" \" + json[\"error\"].reason);\t\u002F\u002F FIXME\n callbacks.error(\"Ooops: \" + json[\"error\"].code + \" \" + json[\"error\"].reason);\n return;\n }\n var handleId = json.data[\"id\"];\n Janus.log(\"Created handle: \" + handleId);\n var pluginHandle =\n {\n session : that,\n plugin : plugin,\n id : handleId,\n token : handleToken,\n detached : false,\n webrtcStuff : {\n started : false,\n myStream : null,\n streamExternal : false,\n remoteStream : null,\n mySdp : null,\n mediaConstraints : null,\n pc : null,\n dataChannel : {},\n dtmfSender : null,\n trickle : true,\n iceDone : false,\n volume : {\n value : null,\n timer : null\n },\n bitrate : {\n value : null,\n bsnow : null,\n bsbefore : null,\n tsnow : null,\n tsbefore : null,\n timer : null\n }\n },\n getId : function() { return handleId; },\n getPlugin : function() { return plugin; },\n getVolume : function() { return getVolume(handleId, true); },\n getRemoteVolume : function() { return getVolume(handleId, true); },\n getLocalVolume : function() { return getVolume(handleId, false); },\n isAudioMuted : function() { return isMuted(handleId, false); },\n muteAudio : function() { return mute(handleId, false, true); },\n unmuteAudio : function() { return mute(handleId, false, false); },\n isVideoMuted : function() { return isMuted(handleId, true); },\n muteVideo : function() { return mute(handleId, true, true); },\n unmuteVideo : function() { return mute(handleId, true, false); },\n getBitrate : function() { return getBitrate(handleId); },\n send : function(callbacks) { sendMessage(handleId, callbacks); },\n data : function(callbacks) { sendData(handleId, callbacks); },\n dtmf : function(callbacks) { sendDtmf(handleId, callbacks); },\n consentDialog : callbacks.consentDialog,\n iceState : callbacks.iceState,\n mediaState : callbacks.mediaState,\n webrtcState : callbacks.webrtcState,\n slowLink : callbacks.slowLink,\n onmessage : callbacks.onmessage,\n createOffer : function(callbacks) { prepareWebrtc(handleId, true, callbacks); },\n createAnswer : function(callbacks) { prepareWebrtc(handleId, false, callbacks); },\n handleRemoteJsep : function(callbacks) { prepareWebrtcPeer(handleId, callbacks); },\n onlocalstream : callbacks.onlocalstream,\n onremotestream : callbacks.onremotestream,\n ondata : callbacks.ondata,\n ondataopen : callbacks.ondataopen,\n oncleanup : callbacks.oncleanup,\n ondetached : callbacks.ondetached,\n hangup : function(sendRequest) { cleanupWebrtc(handleId, sendRequest === true); },\n detach : function(callbacks) { destroyHandle(handleId, callbacks); }\n };\n pluginHandles[handleId] = pluginHandle;\n callbacks.success(pluginHandle);\n };\n request[\"session_id\"] = sessionId;\n ws.send(JSON.stringify(request));\n return;\n }\n Janus.httpAPICall(server + \"\u002F\" + sessionId, {\n verb: 'POST',\n withCredentials: withCredentials,\n body: request,\n success: function(json) {\n Janus.debug(json);\n if(json[\"janus\"] !== \"success\") {\n Janus.error(\"Ooops: \" + json[\"error\"].code + \" \" + json[\"error\"].reason);\t\u002F\u002F FIXME\n callbacks.error(\"Ooops: \" + json[\"error\"].code + \" \" + json[\"error\"].reason);\n return;\n }\n var handleId = json.data[\"id\"];\n Janus.log(\"Created handle: \" + handleId);\n var pluginHandle =\n {\n session : that,\n plugin : plugin,\n id : handleId,\n token : handleToken,\n detached : false,\n webrtcStuff : {\n started : false,\n myStream : null,\n streamExternal : false,\n remoteStream : null,\n mySdp : null,\n mediaConstraints : null,\n pc : null,\n dataChannel : {},\n dtmfSender : null,\n trickle : true,\n iceDone : false,\n volume : {\n value : null,\n timer : null\n },\n bitrate : {\n value : null,\n bsnow : null,\n bsbefore : null,\n tsnow : null,\n tsbefore : null,\n timer : null\n }\n },\n getId : function() { return handleId; },\n getPlugin : function() { return plugin; },\n getVolume : function() { return getVolume(handleId, true); },\n getRemoteVolume : function() { return getVolume(handleId, true); },\n getLocalVolume : function() { return getVolume(handleId, false); },\n isAudioMuted : function() { return isMuted(handleId, false); },\n muteAudio : function() { return mute(handleId, false, true); },\n unmuteAudio : function() { return mute(handleId, false, false); },\n isVideoMuted : function() { return isMuted(handleId, true); },\n muteVideo : function() { return mute(handleId, true, true); },\n unmuteVideo : function() { return mute(handleId, true, false); },\n getBitrate : function() { return getBitrate(handleId); },\n send : function(callbacks) { sendMessage(handleId, callbacks); },\n data : function(callbacks) { sendData(handleId, callbacks); },\n dtmf : function(callbacks) { sendDtmf(handleId, callbacks); },\n consentDialog : callbacks.consentDialog,\n iceState : callbacks.iceState,\n mediaState : callbacks.mediaState,\n webrtcState : callbacks.webrtcState,\n slowLink : callbacks.slowLink,\n onmessage : callbacks.onmessage,\n createOffer : function(callbacks) { prepareWebrtc(handleId, true, callbacks); },\n createAnswer : function(callbacks) { prepareWebrtc(handleId, false, callbacks); },\n handleRemoteJsep : function(callbacks) { prepareWebrtcPeer(handleId, callbacks); },\n onlocalstream : callbacks.onlocalstream,\n onremotestream : callbacks.onremotestream,\n ondata : callbacks.ondata,\n ondataopen : callbacks.ondataopen,\n oncleanup : callbacks.oncleanup,\n ondetached : callbacks.ondetached,\n hangup : function(sendRequest) { cleanupWebrtc(handleId, sendRequest === true); },\n detach : function(callbacks) { destroyHandle(handleId, callbacks); }\n };\n pluginHandles[handleId] = pluginHandle;\n callbacks.success(pluginHandle);\n },\n error: function(textStatus, errorThrown) {\n Janus.error(textStatus + \":\", errorThrown);\t\u002F\u002F FIXME\n }\n });\n }\n\n \u002F\u002F Private method to send a message\n function sendMessage(handleId, callbacks) {\n callbacks = callbacks || {};\n callbacks.success = (typeof callbacks.success == \"function\") ? callbacks.success : Janus.noop;\n callbacks.error = (typeof callbacks.error == \"function\") ? callbacks.error : Janus.noop;\n if(!connected) {\n Janus.warn(\"Is the server down? (connected=false)\");\n callbacks.error(\"Is the server down? (connected=false)\");\n return;\n }\n var pluginHandle = pluginHandles[handleId];\n if(!pluginHandle || !pluginHandle.webrtcStuff) {\n Janus.warn(\"Invalid handle\");\n callbacks.error(\"Invalid handle\");\n return;\n }\n var message = callbacks.message;\n var jsep = callbacks.jsep;\n var transaction = Janus.randomString(12);\n var request = { \"janus\": \"message\", \"body\": message, \"transaction\": transaction };\n if(pluginHandle.token)\n request[\"token\"] = pluginHandle.token;\n if(apisecret)\n request[\"apisecret\"] = apisecret;\n if(jsep)\n request.jsep = jsep;\n Janus.debug(\"Sending message to plugin (handle=\" + handleId + \"):\");\n Janus.debug(request);\n if(websockets) {\n request[\"session_id\"] = sessionId;\n request[\"handle_id\"] = handleId;\n transactions[transaction] = function(json) {\n Janus.debug(\"Message sent!\");\n Janus.debug(json);\n if(json[\"janus\"] === \"success\") {\n \u002F\u002F We got a success, must have been a synchronous transaction\n var plugindata = json[\"plugindata\"];\n if(!plugindata) {\n Janus.warn(\"Request succeeded, but missing plugindata...\");\n callbacks.success();\n return;\n }\n Janus.log(\"Synchronous transaction successful (\" + plugindata[\"plugin\"] + \")\");\n var data = plugindata[\"data\"];\n Janus.debug(data);\n callbacks.success(data);\n return;\n } else if(json[\"janus\"] !== \"ack\") {\n \u002F\u002F Not a success and not an ack, must be an error\n if(json[\"error\"]) {\n Janus.error(\"Ooops: \" + json[\"error\"].code + \" \" + json[\"error\"].reason);\t\u002F\u002F FIXME\n callbacks.error(json[\"error\"].code + \" \" + json[\"error\"].reason);\n } else {\n Janus.error(\"Unknown error\");\t\u002F\u002F FIXME\n callbacks.error(\"Unknown error\");\n }\n return;\n }\n \u002F\u002F If we got here, the plugin decided to handle the request asynchronously\n callbacks.success();\n };\n ws.send(JSON.stringify(request));\n return;\n }\n Janus.httpAPICall(server + \"\u002F\" + sessionId + \"\u002F\" + handleId, {\n verb: 'POST',\n withCredentials: withCredentials,\n body: request,\n success: function(json) {\n Janus.debug(\"Message sent!\");\n Janus.debug(json);\n if(json[\"janus\"] === \"success\") {\n \u002F\u002F We got a success, must have been a synchronous transaction\n var plugindata = json[\"plugindata\"];\n if(!plugindata) {\n Janus.warn(\"Request succeeded, but missing plugindata...\");\n callbacks.success();\n return;\n }\n Janus.log(\"Synchronous transaction successful (\" + plugindata[\"plugin\"] + \")\");\n var data = plugindata[\"data\"];\n Janus.debug(data);\n callbacks.success(data);\n return;\n } else if(json[\"janus\"] !== \"ack\") {\n \u002F\u002F Not a success and not an ack, must be an error\n if(json[\"error\"]) {\n Janus.error(\"Ooops: \" + json[\"error\"].code + \" \" + json[\"error\"].reason);\t\u002F\u002F FIXME\n callbacks.error(json[\"error\"].code + \" \" + json[\"error\"].reason);\n } else {\n Janus.error(\"Unknown error\");\t\u002F\u002F FIXME\n callbacks.error(\"Unknown error\");\n }\n return;\n }\n \u002F\u002F If we got here, the plugin decided to handle the request asynchronously\n callbacks.success();\n },\n error: function(textStatus, errorThrown) {\n Janus.error(textStatus + \":\", errorThrown);\t\u002F\u002F FIXME\n callbacks.error(textStatus + \": \" + errorThrown);\n }\n });\n }\n\n \u002F\u002F Private method to send a trickle candidate\n function sendTrickleCandidate(handleId, candidate) {\n if(!connected) {\n Janus.warn(\"Is the server down? (connected=false)\");\n return;\n }\n var pluginHandle = pluginHandles[handleId];\n if(!pluginHandle || !pluginHandle.webrtcStuff) {\n Janus.warn(\"Invalid handle\");\n return;\n }\n var request = { \"janus\": \"trickle\", \"candidate\": candidate, \"transaction\": Janus.randomString(12) };\n if(pluginHandle.token)\n request[\"token\"] = pluginHandle.token;\n if(apisecret)\n request[\"apisecret\"] = apisecret;\n Janus.vdebug(\"Sending trickle candidate (handle=\" + handleId + \"):\");\n Janus.vdebug(request);\n if(websockets) {\n request[\"session_id\"] = sessionId;\n request[\"handle_id\"] = handleId;\n ws.send(JSON.stringify(request));\n return;\n }\n Janus.httpAPICall(server + \"\u002F\" + sessionId + \"\u002F\" + handleId, {\n verb: 'POST',\n withCredentials: withCredentials,\n body: request,\n success: function(json) {\n Janus.vdebug(\"Candidate sent!\");\n Janus.vdebug(json);\n if(json[\"janus\"] !== \"ack\") {\n Janus.error(\"Ooops: \" + json[\"error\"].code + \" \" + json[\"error\"].reason);\t\u002F\u002F FIXME\n return;\n }\n },\n error: function(textStatus, errorThrown) {\n Janus.error(textStatus + \":\", errorThrown);\t\u002F\u002F FIXME\n }\n });\n }\n\n \u002F\u002F Private method to create a data channel\n function createDataChannel(handleId, dclabel, incoming, pendingText) {\n var pluginHandle = pluginHandles[handleId];\n if(!pluginHandle || !pluginHandle.webrtcStuff) {\n Janus.warn(\"Invalid handle\");\n return;\n }\n var config = pluginHandle.webrtcStuff;\n var onDataChannelMessage = function(event) {\n Janus.log('Received message on data channel:', event);\n var label = event.target.label;\n pluginHandle.ondata(event.data, label);\n };\n var onDataChannelStateChange = function(event) {\n Janus.log('Received state change on data channel:', event);\n var label = event.target.label;\n var dcState = config.dataChannel[label] ? config.dataChannel[label].readyState : \"null\";\n Janus.log('State change on \u003C' + label + '\u003E data channel: ' + dcState);\n if(dcState === 'open') {\n \u002F\u002F Any pending messages to send?\n if(config.dataChannel[label].pending && config.dataChannel[label].pending.length \u003E 0) {\n Janus.log(\"Sending pending messages on \u003C\" + label + \"\u003E:\", config.dataChannel[label].pending.length);\n for(var text of config.dataChannel[label].pending) {\n Janus.log(\"Sending string on data channel \u003C\" + label + \"\u003E: \" + text);\n config.dataChannel[label].send(text);\n }\n config.dataChannel[label].pending = [];\n }\n \u002F\u002F Notify the open data channel\n pluginHandle.ondataopen(label);\n }\n };\n var onDataChannelError = function(error) {\n Janus.error('Got error on data channel:', error);\n \u002F\u002F TODO\n };\n if(!incoming) {\n \u002F\u002F FIXME Add options (ordered, maxRetransmits, etc.)\n config.dataChannel[dclabel] = config.pc.createDataChannel(dclabel, {ordered:false});\n } else {\n \u002F\u002F The channel was created by Janus\n config.dataChannel[dclabel] = incoming;\n }\n config.dataChannel[dclabel].onmessage = onDataChannelMessage;\n config.dataChannel[dclabel].onopen = onDataChannelStateChange;\n config.dataChannel[dclabel].onclose = onDataChannelStateChange;\n config.dataChannel[dclabel].onerror = onDataChannelError;\n config.dataChannel[dclabel].pending = [];\n if(pendingText)\n config.dataChannel[dclabel].pending.push(pendingText);\n }\n\n \u002F\u002F Private method to send a data channel message\n function sendData(handleId, callbacks) {\n callbacks = callbacks || {};\n callbacks.success = (typeof callbacks.success == \"function\") ? callbacks.success : Janus.noop;\n callbacks.error = (typeof callbacks.error == \"function\") ? callbacks.error : Janus.noop;\n var pluginHandle = pluginHandles[handleId];\n if(!pluginHandle || !pluginHandle.webrtcStuff) {\n Janus.warn(\"Invalid handle\");\n callbacks.error(\"Invalid handle\");\n return;\n }\n var config = pluginHandle.webrtcStuff;\n var text = callbacks.text;\n if(!text) {\n Janus.warn(\"Invalid text\");\n callbacks.error(\"Invalid text\");\n return;\n }\n var label = callbacks.label ? callbacks.label : Janus.dataChanDefaultLabel;\n if(!config.dataChannel[label]) {\n \u002F\u002F Create new data channel and wait for it to open\n createDataChannel(handleId, label, false, text);\n callbacks.success();\n return;\n }\n if(config.dataChannel[label].readyState !== \"open\") {\n config.dataChannel[label].pending.push(text);\n callbacks.success();\n return;\n }\n Janus.log(\"Sending string on data channel \u003C\" + label + \"\u003E: \" + text);\n config.dataChannel[label].send(text);\n callbacks.success();\n }\n\n \u002F\u002F Private method to send a DTMF tone\n function sendDtmf(handleId, callbacks) {\n callbacks = callbacks || {};\n callbacks.success = (typeof callbacks.success == \"function\") ? callbacks.success : Janus.noop;\n callbacks.error = (typeof callbacks.error == \"function\") ? callbacks.error : Janus.noop;\n var pluginHandle = pluginHandles[handleId];\n if(!pluginHandle || !pluginHandle.webrtcStuff) {\n Janus.warn(\"Invalid handle\");\n callbacks.error(\"Invalid handle\");\n return;\n }\n var config = pluginHandle.webrtcStuff;\n if(!config.dtmfSender) {\n \u002F\u002F Create the DTMF sender the proper way, if possible\n if(config.pc) {\n var senders = config.pc.getSenders();\n var audioSender = senders.find(function(sender) {\n return sender.track && sender.track.kind === 'audio';\n });\n if(!audioSender) {\n Janus.warn(\"Invalid DTMF configuration (no audio track)\");\n callbacks.error(\"Invalid DTMF configuration (no audio track)\");\n return;\n }\n config.dtmfSender = audioSender.dtmf;\n if(config.dtmfSender) {\n Janus.log(\"Created DTMF Sender\");\n config.dtmfSender.ontonechange = function(tone) { Janus.debug(\"Sent DTMF tone: \" + tone.tone); };\n }\n }\n if(!config.dtmfSender) {\n Janus.warn(\"Invalid DTMF configuration\");\n callbacks.error(\"Invalid DTMF configuration\");\n return;\n }\n }\n var dtmf = callbacks.dtmf;\n if(!dtmf) {\n Janus.warn(\"Invalid DTMF parameters\");\n callbacks.error(\"Invalid DTMF parameters\");\n return;\n }\n var tones = dtmf.tones;\n if(!tones) {\n Janus.warn(\"Invalid DTMF string\");\n callbacks.error(\"Invalid DTMF string\");\n return;\n }\n var duration = (typeof dtmf.duration === 'number') ? dtmf.duration : 500; \u002F\u002F We choose 500ms as the default duration for a tone\n var gap = (typeof dtmf.gap === 'number') ? dtmf.gap : 50; \u002F\u002F We choose 50ms as the default gap between tones\n Janus.debug(\"Sending DTMF string \" + tones + \" (duration \" + duration + \"ms, gap \" + gap + \"ms)\");\n config.dtmfSender.insertDTMF(tones, duration, gap);\n callbacks.success();\n }\n\n \u002F\u002F Private method to destroy a plugin handle\n function destroyHandle(handleId, callbacks) {\n callbacks = callbacks || {};\n callbacks.success = (typeof callbacks.success == \"function\") ? callbacks.success : Janus.noop;\n callbacks.error = (typeof callbacks.error == \"function\") ? callbacks.error : Janus.noop;\n var asyncRequest = true;\n if(callbacks.asyncRequest !== undefined && callbacks.asyncRequest !== null)\n asyncRequest = (callbacks.asyncRequest === true);\n var noRequest = true;\n if(callbacks.noRequest !== undefined && callbacks.noRequest !== null)\n noRequest = (callbacks.noRequest === true);\n Janus.log(\"Destroying handle \" + handleId + \" (async=\" + asyncRequest + \")\");\n cleanupWebrtc(handleId);\n var pluginHandle = pluginHandles[handleId];\n if(!pluginHandle || pluginHandle.detached) {\n \u002F\u002F Plugin was already detached by Janus, calling detach again will return a handle not found error, so just exit here\n delete pluginHandles[handleId];\n callbacks.success();\n return;\n }\n if(noRequest) {\n \u002F\u002F We're only removing the handle locally\n delete pluginHandles[handleId];\n callbacks.success();\n return;\n }\n if(!connected) {\n Janus.warn(\"Is the server down? (connected=false)\");\n callbacks.error(\"Is the server down? (connected=false)\");\n return;\n }\n var request = { \"janus\": \"detach\", \"transaction\": Janus.randomString(12) };\n if(pluginHandle.token)\n request[\"token\"] = pluginHandle.token;\n if(apisecret)\n request[\"apisecret\"] = apisecret;\n if(websockets) {\n request[\"session_id\"] = sessionId;\n request[\"handle_id\"] = handleId;\n ws.send(JSON.stringify(request));\n delete pluginHandles[handleId];\n callbacks.success();\n return;\n }\n Janus.httpAPICall(server + \"\u002F\" + sessionId + \"\u002F\" + handleId, {\n verb: 'POST',\n async: asyncRequest,\t\u002F\u002F Sometimes we need false here, or destroying in onbeforeunload won't work\n withCredentials: withCredentials,\n body: request,\n success: function(json) {\n Janus.log(\"Destroyed handle:\");\n Janus.debug(json);\n if(json[\"janus\"] !== \"success\") {\n Janus.error(\"Ooops: \" + json[\"error\"].code + \" \" + json[\"error\"].reason);\t\u002F\u002F FIXME\n }\n delete pluginHandles[handleId];\n callbacks.success();\n },\n error: function(textStatus, errorThrown) {\n Janus.error(textStatus + \":\", errorThrown);\t\u002F\u002F FIXME\n \u002F\u002F We cleanup anyway\n delete pluginHandles[handleId];\n callbacks.success();\n }\n });\n }\n\n \u002F\u002F WebRTC stuff\n function streamsDone(handleId, jsep, media, callbacks, stream) {\n var pluginHandle = pluginHandles[handleId];\n if(!pluginHandle || !pluginHandle.webrtcStuff) {\n Janus.warn(\"Invalid handle\");\n callbacks.error(\"Invalid handle\");\n return;\n }\n var config = pluginHandle.webrtcStuff;\n Janus.debug(\"streamsDone:\", stream);\n if(stream) {\n Janus.debug(\" -- Audio tracks:\", stream.getAudioTracks());\n Janus.debug(\" -- Video tracks:\", stream.getVideoTracks());\n }\n \u002F\u002F We're now capturing the new stream: check if we're updating or if it's a new thing\n var addTracks = false;\n if(!config.myStream || !media.update || config.streamExternal) {\n config.myStream = stream;\n addTracks = true;\n } else {\n \u002F\u002F We only need to update the existing stream\n if(((!media.update && isAudioSendEnabled(media)) || (media.update && (media.addAudio || media.replaceAudio))) &&\n stream.getAudioTracks() && stream.getAudioTracks().length) {\n config.myStream.addTrack(stream.getAudioTracks()[0]);\n if(Janus.unifiedPlan) {\n \u002F\u002F Use Transceivers\n Janus.log((media.replaceAudio ? \"Replacing\" : \"Adding\") + \" audio track:\", stream.getAudioTracks()[0]);\n var audioTransceiver = null;\n var transceivers = config.pc.getTransceivers();\n if(transceivers && transceivers.length \u003E 0) {\n for(var t of transceivers) {\n if((t.sender && t.sender.track && t.sender.track.kind === \"audio\") ||\n (t.receiver && t.receiver.track && t.receiver.track.kind === \"audio\")) {\n audioTransceiver = t;\n break;\n }\n }\n }\n if(audioTransceiver && audioTransceiver.sender) {\n audioTransceiver.sender.replaceTrack(stream.getAudioTracks()[0]);\n } else {\n config.pc.addTrack(stream.getAudioTracks()[0], stream);\n }\n } else {\n Janus.log((media.replaceAudio ? \"Replacing\" : \"Adding\") + \" audio track:\", stream.getAudioTracks()[0]);\n config.pc.addTrack(stream.getAudioTracks()[0], stream);\n }\n }\n if(((!media.update && isVideoSendEnabled(media)) || (media.update && (media.addVideo || media.replaceVideo))) &&\n stream.getVideoTracks() && stream.getVideoTracks().length) {\n config.myStream.addTrack(stream.getVideoTracks()[0]);\n if(Janus.unifiedPlan) {\n \u002F\u002F Use Transceivers\n Janus.log((media.replaceVideo ? \"Replacing\" : \"Adding\") + \" video track:\", stream.getVideoTracks()[0]);\n var videoTransceiver = null;\n var transceivers = config.pc.getTransceivers();\n if(transceivers && transceivers.length \u003E 0) {\n for(var t of transceivers) {\n if((t.sender && t.sender.track && t.sender.track.kind === \"video\") ||\n (t.receiver && t.receiver.track && t.receiver.track.kind === \"video\")) {\n videoTransceiver = t;\n break;\n }\n }\n }\n if(videoTransceiver && videoTransceiver.sender) {\n videoTransceiver.sender.replaceTrack(stream.getVideoTracks()[0]);\n } else {\n config.pc.addTrack(stream.getVideoTracks()[0], stream);\n }\n } else {\n Janus.log((media.replaceVideo ? \"Replacing\" : \"Adding\") + \" video track:\", stream.getVideoTracks()[0]);\n config.pc.addTrack(stream.getVideoTracks()[0], stream);\n }\n }\n }\n \u002F\u002F If we still need to create a PeerConnection, let's do that\n if(!config.pc) {\n var pc_config = {\"iceServers\": iceServers, \"iceTransportPolicy\": iceTransportPolicy, \"bundlePolicy\": bundlePolicy};\n if(Janus.webRTCAdapter.browserDetails.browser === \"chrome\") {\n \u002F\u002F For Chrome versions before 72, we force a plan-b semantic, and unified-plan otherwise\n pc_config[\"sdpSemantics\"] = (Janus.webRTCAdapter.browserDetails.version \u003C 72) ? \"plan-b\" : \"unified-plan\";\n }\n var pc_constraints = {\n \"optional\": [{\"DtlsSrtpKeyAgreement\": true}]\n };\n if(ipv6Support) {\n pc_constraints.optional.push({\"googIPv6\":true});\n }\n \u002F\u002F Any custom constraint to add?\n if(callbacks.rtcConstraints && typeof callbacks.rtcConstraints === 'object') {\n Janus.debug(\"Adding custom PeerConnection constraints:\", callbacks.rtcConstraints);\n for(var i in callbacks.rtcConstraints) {\n pc_constraints.optional.push(callbacks.rtcConstraints[i]);\n }\n }\n if(Janus.webRTCAdapter.browserDetails.browser === \"edge\") {\n \u002F\u002F This is Edge, enable BUNDLE explicitly\n pc_config.bundlePolicy = \"max-bundle\";\n }\n Janus.log(\"Creating PeerConnection\");\n Janus.debug(pc_constraints);\n config.pc = new RTCPeerConnection(pc_config, pc_constraints);\n Janus.debug(config.pc);\n if(config.pc.getStats) {\t\u002F\u002F FIXME\n config.volume = {};\n config.bitrate.value = \"0 kbits\u002Fsec\";\n }\n Janus.log(\"Preparing local SDP and gathering candidates (trickle=\" + config.trickle + \")\");\n config.pc.oniceconnectionstatechange = function(e) {\n if(config.pc)\n pluginHandle.iceState(config.pc.iceConnectionState);\n };\n config.pc.onicecandidate = function(event) {\n if (!event.candidate ||\n (Janus.webRTCAdapter.browserDetails.browser === 'edge' && event.candidate.candidate.indexOf('endOfCandidates') \u003E 0)) {\n Janus.log(\"End of candidates.\");\n config.iceDone = true;\n if(config.trickle === true) {\n \u002F\u002F Notify end of candidates\n sendTrickleCandidate(handleId, {\"completed\": true});\n } else {\n \u002F\u002F No trickle, time to send the complete SDP (including all candidates)\n sendSDP(handleId, callbacks);\n }\n } else {\n \u002F\u002F JSON.stringify doesn't work on some WebRTC objects anymore\n \u002F\u002F See https:\u002F\u002Fcode.google.com\u002Fp\u002Fchromium\u002Fissues\u002Fdetail?id=467366\n var candidate = {\n \"candidate\": event.candidate.candidate,\n \"sdpMid\": event.candidate.sdpMid,\n \"sdpMLineIndex\": event.candidate.sdpMLineIndex\n };\n if(config.trickle === true) {\n \u002F\u002F Send candidate\n sendTrickleCandidate(handleId, candidate);\n }\n }\n };\n config.pc.ontrack = function(event) {\n Janus.log(\"Handling Remote Track\");\n Janus.debug(event);\n if(!event.streams)\n return;\n config.remoteStream = event.streams[0];\n pluginHandle.onremotestream(config.remoteStream);\n if(event.track.onended)\n return;\n Janus.log(\"Adding onended callback to track:\", event.track);\n event.track.onended = function(ev) {\n Janus.log(\"Remote track muted\u002Fremoved:\", ev);\n if(config.remoteStream) {\n config.remoteStream.removeTrack(ev.target);\n pluginHandle.onremotestream(config.remoteStream);\n }\n };\n \u002F*event.track.onmute = event.track.onended;\n event.track.onunmute = function(ev) {\n Janus.log(\"Remote track flowing again:\", ev);\n try {\n config.remoteStream.addTrack(ev.target);\n pluginHandle.onremotestream(config.remoteStream);\n } catch(e) {\n Janus.error(e);\n };\n };*\u002F\n };\n }\n if(addTracks && stream) {\n Janus.log('Adding local stream');\n var simulcast2 = (callbacks.simulcast2 === true);\n stream.getTracks().forEach(function(track) {\n Janus.log('Adding local track:', track);\n if(!simulcast2) {\n config.pc.addTrack(track, stream);\n } else {\n if(track.kind === \"audio\") {\n config.pc.addTrack(track, stream);\n } else {\n Janus.log('Enabling rid-based simulcasting:', track);\n const maxBitrates = getMaxBitrates(callbacks.simulcastMaxBitrates);\n config.pc.addTransceiver(track, {\n direction: \"sendrecv\",\n streams: [stream],\n sendEncodings: [\n { rid: \"h\", active: true, maxBitrate: maxBitrates.high },\n { rid: \"m\", active: true, maxBitrate: maxBitrates.medium, scaleResolutionDownBy: 2 },\n { rid: \"l\", active: true, maxBitrate: maxBitrates.low, scaleResolutionDownBy: 4 }\n ]\n });\n }\n }\n });\n }\n \u002F\u002F Any data channel to create?\n if(isDataEnabled(media) && !config.dataChannel[Janus.dataChanDefaultLabel]) {\n Janus.log(\"Creating data channel\");\n createDataChannel(handleId, Janus.dataChanDefaultLabel, false);\n config.pc.ondatachannel = function(event) {\n Janus.log(\"Data channel created by Janus:\", event);\n createDataChannel(handleId, event.channel.label, event.channel);\n };\n }\n \u002F\u002F If there's a new local stream, let's notify the application\n if(config.myStream)\n pluginHandle.onlocalstream(config.myStream);\n \u002F\u002F Create offer\u002Fanswer now\n if(!jsep) {\n createOffer(handleId, media, callbacks);\n } else {\n config.pc.setRemoteDescription(jsep)\n .then(function() {\n Janus.log(\"Remote description accepted!\");\n config.remoteSdp = jsep.sdp;\n \u002F\u002F Any trickle candidate we cached?\n if(config.candidates && config.candidates.length \u003E 0) {\n for(var i = 0; i\u003C config.candidates.length; i++) {\n var candidate = config.candidates[i];\n Janus.debug(\"Adding remote candidate:\", candidate);\n if(!candidate || candidate.completed === true) {\n \u002F\u002F end-of-candidates\n config.pc.addIceCandidate(Janus.endOfCandidates);\n } else {\n \u002F\u002F New candidate\n config.pc.addIceCandidate(candidate);\n }\n }\n config.candidates = [];\n }\n \u002F\u002F Create the answer now\n createAnswer(handleId, media, callbacks);\n }, callbacks.error);\n }\n }\n\n function prepareWebrtc(handleId, offer, callbacks) {\n callbacks = callbacks || {};\n callbacks.success = (typeof callbacks.success == \"function\") ? callbacks.success : Janus.noop;\n callbacks.error = (typeof callbacks.error == \"function\") ? callbacks.error : webrtcError;\n var jsep = callbacks.jsep;\n if(offer && jsep) {\n Janus.error(\"Provided a JSEP to a createOffer\");\n callbacks.error(\"Provided a JSEP to a createOffer\");\n return;\n } else if(!offer && (!jsep || !jsep.type || !jsep.sdp)) {\n Janus.error(\"A valid JSEP is required for createAnswer\");\n callbacks.error(\"A valid JSEP is required for createAnswer\");\n return;\n }\n \u002F* Check that callbacks.media is a (not null) Object *\u002F\n callbacks.media = (typeof callbacks.media === 'object' && callbacks.media) ? callbacks.media : { audio: true, video: true };\n var media = callbacks.media;\n var pluginHandle = pluginHandles[handleId];\n if(!pluginHandle || !pluginHandle.webrtcStuff) {\n Janus.warn(\"Invalid handle\");\n callbacks.error(\"Invalid handle\");\n return;\n }\n var config = pluginHandle.webrtcStuff;\n config.trickle = isTrickleEnabled(callbacks.trickle);\n \u002F\u002F Are we updating a session?\n if(!config.pc) {\n \u002F\u002F Nope, new PeerConnection\n media.update = false;\n media.keepAudio = false;\n media.keepVideo = false;\n } else {\n Janus.log(\"Updating existing media session\");\n media.update = true;\n \u002F\u002F Check if there's anything to add\u002Fremove\u002Freplace, or if we\n \u002F\u002F can go directly to preparing the new SDP offer or answer\n if(callbacks.stream) {\n \u002F\u002F External stream: is this the same as the one we were using before?\n if(callbacks.stream !== config.myStream) {\n Janus.log(\"Renegotiation involves a new external stream\");\n }\n } else {\n \u002F\u002F Check if there are changes on audio\n if(media.addAudio) {\n media.keepAudio = false;\n media.replaceAudio = false;\n media.removeAudio = false;\n media.audioSend = true;\n if(config.myStream && config.myStream.getAudioTracks() && config.myStream.getAudioTracks().length) {\n Janus.error(\"Can't add audio stream, there already is one\");\n callbacks.error(\"Can't add audio stream, there already is one\");\n return;\n }\n } else if(media.removeAudio) {\n media.keepAudio = false;\n media.replaceAudio = false;\n media.addAudio = false;\n media.audioSend = false;\n } else if(media.replaceAudio) {\n media.keepAudio = false;\n media.addAudio = false;\n media.removeAudio = false;\n media.audioSend = true;\n }\n if(!config.myStream) {\n \u002F\u002F No media stream: if we were asked to replace, it's actually an \"add\"\n if(media.replaceAudio) {\n media.keepAudio = false;\n media.replaceAudio = false;\n media.addAudio = true;\n media.audioSend = true;\n }\n if(isAudioSendEnabled(media)) {\n media.keepAudio = false;\n media.addAudio = true;\n }\n } else {\n if(!config.myStream.getAudioTracks() || config.myStream.getAudioTracks().length === 0) {\n \u002F\u002F No audio track: if we were asked to replace, it's actually an \"add\"\n if(media.replaceAudio) {\n media.keepAudio = false;\n media.replaceAudio = false;\n media.addAudio = true;\n media.audioSend = true;\n }\n if(isAudioSendEnabled(media)) {\n media.keepVideo = false;\n media.addAudio = true;\n }\n } else {\n \u002F\u002F We have an audio track: should we keep it as it is?\n if(isAudioSendEnabled(media) &&\n !media.removeAudio && !media.replaceAudio) {\n media.keepAudio = true;\n }\n }\n }\n \u002F\u002F Check if there are changes on video\n if(media.addVideo) {\n media.keepVideo = false;\n media.replaceVideo = false;\n media.removeVideo = false;\n media.videoSend = true;\n if(config.myStream && config.myStream.getVideoTracks() && config.myStream.getVideoTracks().length) {\n Janus.error(\"Can't add video stream, there already is one\");\n callbacks.error(\"Can't add video stream, there already is one\");\n return;\n }\n } else if(media.removeVideo) {\n media.keepVideo = false;\n media.replaceVideo = false;\n media.addVideo = false;\n media.videoSend = false;\n } else if(media.replaceVideo) {\n media.keepVideo = false;\n media.addVideo = false;\n media.removeVideo = false;\n media.videoSend = true;\n }\n if(!config.myStream) {\n \u002F\u002F No media stream: if we were asked to replace, it's actually an \"add\"\n if(media.replaceVideo) {\n media.keepVideo = false;\n media.replaceVideo = false;\n media.addVideo = true;\n media.videoSend = true;\n }\n if(isVideoSendEnabled(media)) {\n media.keepVideo = false;\n media.addVideo = true;\n }\n } else {\n if(!config.myStream.getVideoTracks() || config.myStream.getVideoTracks().length === 0) {\n \u002F\u002F No video track: if we were asked to replace, it's actually an \"add\"\n if(media.replaceVideo) {\n media.keepVideo = false;\n media.replaceVideo = false;\n media.addVideo = true;\n media.videoSend = true;\n }\n if(isVideoSendEnabled(media)) {\n media.keepVideo = false;\n media.addVideo = true;\n }\n } else {\n \u002F\u002F We have a video track: should we keep it as it is?\n if(isVideoSendEnabled(media) &&\n !media.removeVideo && !media.replaceVideo) {\n media.keepVideo = true;\n }\n }\n }\n \u002F\u002F Data channels can only be added\n if(media.addData)\n media.data = true;\n }\n \u002F\u002F If we're updating and keeping all tracks, let's skip the getUserMedia part\n if((isAudioSendEnabled(media) && media.keepAudio) &&\n (isVideoSendEnabled(media) && media.keepVideo)) {\n pluginHandle.consentDialog(false);\n streamsDone(handleId, jsep, media, callbacks, config.myStream);\n return;\n }\n }\n \u002F\u002F If we're updating, check if we need to remove\u002Freplace one of the tracks\n if(media.update && !config.streamExternal) {\n if(media.removeAudio || media.replaceAudio) {\n if(config.myStream && config.myStream.getAudioTracks() && config.myStream.getAudioTracks().length) {\n var s = config.myStream.getAudioTracks()[0];\n Janus.log(\"Removing audio track:\", s);\n config.myStream.removeTrack(s);\n try {\n s.stop();\n } catch(e) {}\t\t\t\t}\n if(config.pc.getSenders() && config.pc.getSenders().length) {\n var ra = true;\n if(media.replaceAudio && Janus.unifiedPlan) {\n \u002F\u002F We can use replaceTrack\n ra = false;\n }\n if(ra) {\n for(var s of config.pc.getSenders()) {\n if(s && s.track && s.track.kind === \"audio\") {\n Janus.log(\"Removing audio sender:\", s);\n config.pc.removeTrack(s);\n }\n }\n }\n }\n }\n if(media.removeVideo || media.replaceVideo) {\n if(config.myStream && config.myStream.getVideoTracks() && config.myStream.getVideoTracks().length) {\n var s = config.myStream.getVideoTracks()[0];\n Janus.log(\"Removing video track:\", s);\n config.myStream.removeTrack(s);\n try {\n s.stop();\n } catch(e) {}\t\t\t\t}\n if(config.pc.getSenders() && config.pc.getSenders().length) {\n var rv = true;\n if(media.replaceVideo && Janus.unifiedPlan) {\n \u002F\u002F We can use replaceTrack\n rv = false;\n }\n if(rv) {\n for(var s of config.pc.getSenders()) {\n if(s && s.track && s.track.kind === \"video\") {\n Janus.log(\"Removing video sender:\", s);\n config.pc.removeTrack(s);\n }\n }\n }\n }\n }\n }\n \u002F\u002F Was a MediaStream object passed, or do we need to take care of that?\n if(callbacks.stream) {\n var stream = callbacks.stream;\n Janus.log(\"MediaStream provided by the application\");\n Janus.debug(stream);\n \u002F\u002F If this is an update, let's check if we need to release the previous stream\n if(media.update) {\n if(config.myStream && config.myStream !== callbacks.stream && !config.streamExternal) {\n \u002F\u002F We're replacing a stream we captured ourselves with an external one\n try {\n \u002F\u002F Try a MediaStreamTrack.stop() for each track\n var tracks = config.myStream.getTracks();\n for(var mst of tracks) {\n Janus.log(mst);\n if(mst)\n mst.stop();\n }\n } catch(e) {\n \u002F\u002F Do nothing if this fails\n }\n config.myStream = null;\n }\n }\n \u002F\u002F Skip the getUserMedia part\n config.streamExternal = true;\n pluginHandle.consentDialog(false);\n streamsDone(handleId, jsep, media, callbacks, stream);\n return;\n }\n if(isAudioSendEnabled(media) || isVideoSendEnabled(media)) {\n if(!Janus.isGetUserMediaAvailable()) {\n callbacks.error(\"getUserMedia not available\");\n return;\n }\n var constraints = { mandatory: {}, optional: []};\n pluginHandle.consentDialog(true);\n var audioSupport = isAudioSendEnabled(media);\n if(audioSupport && media && typeof media.audio === 'object')\n audioSupport = media.audio;\n var videoSupport = isVideoSendEnabled(media);\n if(videoSupport && media) {\n var simulcast = (callbacks.simulcast === true);\n var simulcast2 = (callbacks.simulcast2 === true);\n if((simulcast || simulcast2) && !jsep && !media.video)\n media.video = \"hires\";\n if(media.video && media.video != 'screen' && media.video != 'window') {\n if(typeof media.video === 'object') {\n videoSupport = media.video;\n } else {\n var width = 0;\n var height = 0;\n if(media.video === 'lowres') {\n \u002F\u002F Small resolution, 4:3\n height = 240;\n width = 320;\n } else if(media.video === 'lowres-16:9') {\n \u002F\u002F Small resolution, 16:9\n height = 180;\n width = 320;\n } else if(media.video === 'hires' || media.video === 'hires-16:9' || media.video === 'hdres') {\n \u002F\u002F High(HD) resolution is only 16:9\n height = 720;\n width = 1280;\n } else if(media.video === 'fhdres') {\n \u002F\u002F Full HD resolution is only 16:9\n height = 1080;\n width = 1920;\n } else if(media.video === '4kres') {\n \u002F\u002F 4K resolution is only 16:9\n height = 2160;\n width = 3840;\n } else if(media.video === 'stdres') {\n \u002F\u002F Normal resolution, 4:3\n height = 480;\n width = 640;\n } else if(media.video === 'stdres-16:9') {\n \u002F\u002F Normal resolution, 16:9\n height = 360;\n width = 640;\n } else {\n Janus.log(\"Default video setting is stdres 4:3\");\n height = 480;\n width = 640;\n }\n Janus.log(\"Adding media constraint:\", media.video);\n videoSupport = {\n 'height': {'ideal': height},\n 'width': {'ideal': width}\n };\n Janus.log(\"Adding video constraint:\", videoSupport);\n }\n } else if(media.video === 'screen' || media.video === 'window') {\n if(!media.screenshareFrameRate) {\n media.screenshareFrameRate = 3;\n }\n if(navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) {\n \u002F\u002F The new experimental getDisplayMedia API is available, let's use that\n \u002F\u002F https:\u002F\u002Fgroups.google.com\u002Fforum\u002F#!topic\u002Fdiscuss-webrtc\u002FUf0SrR4uxzk\n \u002F\u002F https:\u002F\u002Fwebrtchacks.com\u002Fchrome-screensharing-getdisplaymedia\u002F\n navigator.mediaDevices.getDisplayMedia({ video: true, audio: media.captureDesktopAudio })\n .then(function(stream) {\n pluginHandle.consentDialog(false);\n if(isAudioSendEnabled(media) && !media.keepAudio) {\n navigator.mediaDevices.getUserMedia({ audio: true, video: false })\n .then(function (audioStream) {\n stream.addTrack(audioStream.getAudioTracks()[0]);\n streamsDone(handleId, jsep, media, callbacks, stream);\n });\n } else {\n streamsDone(handleId, jsep, media, callbacks, stream);\n }\n }, function (error) {\n pluginHandle.consentDialog(false);\n callbacks.error(error);\n });\n return;\n }\n \u002F\u002F We're going to try and use the extension for Chrome 34+, the old approach\n \u002F\u002F for older versions of Chrome, or the experimental support in Firefox 33+\n function callbackUserMedia (error, stream) {\n pluginHandle.consentDialog(false);\n if(error) {\n callbacks.error(error);\n } else {\n streamsDone(handleId, jsep, media, callbacks, stream);\n }\n }\t\t\t\t\tfunction getScreenMedia(constraints, gsmCallback, useAudio) {\n Janus.log(\"Adding media constraint (screen capture)\");\n Janus.debug(constraints);\n navigator.mediaDevices.getUserMedia(constraints)\n .then(function(stream) {\n if(useAudio) {\n navigator.mediaDevices.getUserMedia({ audio: true, video: false })\n .then(function (audioStream) {\n stream.addTrack(audioStream.getAudioTracks()[0]);\n gsmCallback(null, stream);\n });\n } else {\n gsmCallback(null, stream);\n }\n })\n .catch(function(error) { pluginHandle.consentDialog(false); gsmCallback(error); });\n }\t\t\t\t\tif(Janus.webRTCAdapter.browserDetails.browser === 'chrome') {\n var chromever = Janus.webRTCAdapter.browserDetails.version;\n var maxver = 33;\n if(window.navigator.userAgent.match('Linux'))\n maxver = 35;\t\u002F\u002F \"known\" crash in chrome 34 and 35 on linux\n if(chromever \u003E= 26 && chromever \u003C= maxver) {\n \u002F\u002F Chrome 26-\u003E33 requires some awkward chrome:\u002F\u002Fflags manipulation\n constraints = {\n video: {\n mandatory: {\n googLeakyBucket: true,\n maxWidth: window.screen.width,\n maxHeight: window.screen.height,\n minFrameRate: media.screenshareFrameRate,\n maxFrameRate: media.screenshareFrameRate,\n chromeMediaSource: 'screen'\n }\n },\n audio: isAudioSendEnabled(media) && !media.keepAudio\n };\n getScreenMedia(constraints, callbackUserMedia);\n } else {\n \u002F\u002F Chrome 34+ requires an extension\n Janus.extension.getScreen(function (error, sourceId) {\n if (error) {\n pluginHandle.consentDialog(false);\n return callbacks.error(error);\n }\n constraints = {\n audio: false,\n video: {\n mandatory: {\n chromeMediaSource: 'desktop',\n maxWidth: window.screen.width,\n maxHeight: window.screen.height,\n minFrameRate: media.screenshareFrameRate,\n maxFrameRate: media.screenshareFrameRate,\n },\n optional: [\n {googLeakyBucket: true},\n {googTemporalLayeredScreencast: true}\n ]\n }\n };\n constraints.video.mandatory.chromeMediaSourceId = sourceId;\n getScreenMedia(constraints, callbackUserMedia,\n isAudioSendEnabled(media) && !media.keepAudio);\n });\n }\n } else if(Janus.webRTCAdapter.browserDetails.browser === 'firefox') {\n if(Janus.webRTCAdapter.browserDetails.version \u003E= 33) {\n \u002F\u002F Firefox 33+ has experimental support for screen sharing\n constraints = {\n video: {\n mozMediaSource: media.video,\n mediaSource: media.video\n },\n audio: isAudioSendEnabled(media) && !media.keepAudio\n };\n getScreenMedia(constraints, function (err, stream) {\n callbackUserMedia(err, stream);\n \u002F\u002F Workaround for https:\u002F\u002Fbugzilla.mozilla.org\u002Fshow_bug.cgi?id=1045810\n if (!err) {\n var lastTime = stream.currentTime;\n var polly = window.setInterval(function () {\n if(!stream)\n window.clearInterval(polly);\n if(stream.currentTime == lastTime) {\n window.clearInterval(polly);\n if(stream.onended) {\n stream.onended();\n }\n }\n lastTime = stream.currentTime;\n }, 500);\n }\n });\n } else {\n var error = new Error('NavigatorUserMediaError');\n error.name = 'Your version of Firefox does not support screen sharing, please install Firefox 33 (or more recent versions)';\n pluginHandle.consentDialog(false);\n callbacks.error(error);\n return;\n }\n }\n return;\n }\n }\n \u002F\u002F If we got here, we're not screensharing\n if(!media || media.video !== 'screen') {\n \u002F\u002F Check whether all media sources are actually available or not\n navigator.mediaDevices.enumerateDevices().then(function(devices) {\n var audioExist = devices.some(function(device) {\n return device.kind === 'audioinput';\n }),\n videoExist = isScreenSendEnabled(media) || devices.some(function(device) {\n return device.kind === 'videoinput';\n });\n\n \u002F\u002F Check whether a missing device is really a problem\n var audioSend = isAudioSendEnabled(media);\n var videoSend = isVideoSendEnabled(media);\n var needAudioDevice = isAudioSendRequired(media);\n var needVideoDevice = isVideoSendRequired(media);\n if(audioSend || videoSend || needAudioDevice || needVideoDevice) {\n \u002F\u002F We need to send either audio or video\n var haveAudioDevice = audioSend ? audioExist : false;\n var haveVideoDevice = videoSend ? videoExist : false;\n if(!haveAudioDevice && !haveVideoDevice) {\n \u002F\u002F FIXME Should we really give up, or just assume recvonly for both?\n pluginHandle.consentDialog(false);\n callbacks.error('No capture device found');\n return false;\n } else if(!haveAudioDevice && needAudioDevice) {\n pluginHandle.consentDialog(false);\n callbacks.error('Audio capture is required, but no capture device found');\n return false;\n } else if(!haveVideoDevice && needVideoDevice) {\n pluginHandle.consentDialog(false);\n callbacks.error('Video capture is required, but no capture device found');\n return false;\n }\n }\n\n var gumConstraints = {\n audio: (audioExist && !media.keepAudio) ? audioSupport : false,\n video: (videoExist && !media.keepVideo) ? videoSupport : false\n };\n Janus.debug(\"getUserMedia constraints\", gumConstraints);\n if (!gumConstraints.audio && !gumConstraints.video) {\n pluginHandle.consentDialog(false);\n streamsDone(handleId, jsep, media, callbacks, stream);\n } else {\n navigator.mediaDevices.getUserMedia(gumConstraints)\n .then(function(stream) {\n pluginHandle.consentDialog(false);\n streamsDone(handleId, jsep, media, callbacks, stream);\n }).catch(function(error) {\n pluginHandle.consentDialog(false);\n callbacks.error({code: error.code, name: error.name, message: error.message});\n });\n }\n })\n .catch(function(error) {\n pluginHandle.consentDialog(false);\n callbacks.error('enumerateDevices error', error);\n });\n }\n } else {\n \u002F\u002F No need to do a getUserMedia, create offer\u002Fanswer right away\n streamsDone(handleId, jsep, media, callbacks);\n }\n }\n\n function prepareWebrtcPeer(handleId, callbacks) {\n callbacks = callbacks || {};\n callbacks.success = (typeof callbacks.success == \"function\") ? callbacks.success : Janus.noop;\n callbacks.error = (typeof callbacks.error == \"function\") ? callbacks.error : webrtcError;\n var jsep = callbacks.jsep;\n var pluginHandle = pluginHandles[handleId];\n if(!pluginHandle || !pluginHandle.webrtcStuff) {\n Janus.warn(\"Invalid handle\");\n callbacks.error(\"Invalid handle\");\n return;\n }\n var config = pluginHandle.webrtcStuff;\n if(jsep) {\n if(!config.pc) {\n Janus.warn(\"Wait, no PeerConnection?? if this is an answer, use createAnswer and not handleRemoteJsep\");\n callbacks.error(\"No PeerConnection: if this is an answer, use createAnswer and not handleRemoteJsep\");\n return;\n }\n config.pc.setRemoteDescription(jsep)\n .then(function() {\n Janus.log(\"Remote description accepted!\");\n config.remoteSdp = jsep.sdp;\n \u002F\u002F Any trickle candidate we cached?\n if(config.candidates && config.candidates.length \u003E 0) {\n for(var i = 0; i\u003C config.candidates.length; i++) {\n var candidate = config.candidates[i];\n Janus.debug(\"Adding remote candidate:\", candidate);\n if(!candidate || candidate.completed === true) {\n \u002F\u002F end-of-candidates\n config.pc.addIceCandidate(Janus.endOfCandidates);\n } else {\n \u002F\u002F New candidate\n config.pc.addIceCandidate(candidate);\n }\n }\n config.candidates = [];\n }\n \u002F\u002F Done\n callbacks.success();\n }, callbacks.error);\n } else {\n callbacks.error(\"Invalid JSEP\");\n }\n }\n\n function createOffer(handleId, media, callbacks) {\n callbacks = callbacks || {};\n callbacks.success = (typeof callbacks.success == \"function\") ? callbacks.success : Janus.noop;\n callbacks.error = (typeof callbacks.error == \"function\") ? callbacks.error : Janus.noop;\n callbacks.customizeSdp = (typeof callbacks.customizeSdp == \"function\") ? callbacks.customizeSdp : Janus.noop;\n var pluginHandle = pluginHandles[handleId];\n if(!pluginHandle || !pluginHandle.webrtcStuff) {\n Janus.warn(\"Invalid handle\");\n callbacks.error(\"Invalid handle\");\n return;\n }\n var config = pluginHandle.webrtcStuff;\n var simulcast = (callbacks.simulcast === true);\n if(!simulcast) {\n Janus.log(\"Creating offer (iceDone=\" + config.iceDone + \")\");\n } else {\n Janus.log(\"Creating offer (iceDone=\" + config.iceDone + \", simulcast=\" + simulcast + \")\");\n }\n \u002F\u002F https:\u002F\u002Fcode.google.com\u002Fp\u002Fwebrtc\u002Fissues\u002Fdetail?id=3508\n var mediaConstraints = {};\n if(Janus.unifiedPlan) {\n \u002F\u002F We can use Transceivers\n var audioTransceiver = null, videoTransceiver = null;\n var transceivers = config.pc.getTransceivers();\n if(transceivers && transceivers.length \u003E 0) {\n for(var t of transceivers) {\n if((t.sender && t.sender.track && t.sender.track.kind === \"audio\") ||\n (t.receiver && t.receiver.track && t.receiver.track.kind === \"audio\")) {\n if(!audioTransceiver)\n audioTransceiver = t;\n continue;\n }\n if((t.sender && t.sender.track && t.sender.track.kind === \"video\") ||\n (t.receiver && t.receiver.track && t.receiver.track.kind === \"video\")) {\n if(!videoTransceiver)\n videoTransceiver = t;\n continue;\n }\n }\n }\n \u002F\u002F Handle audio (and related changes, if any)\n var audioSend = isAudioSendEnabled(media);\n var audioRecv = isAudioRecvEnabled(media);\n if(!audioSend && !audioRecv) {\n \u002F\u002F Audio disabled: have we removed it?\n if(media.removeAudio && audioTransceiver) {\n if (audioTransceiver.setDirection) {\n audioTransceiver.setDirection(\"inactive\");\n } else {\n audioTransceiver.direction = \"inactive\";\n }\n Janus.log(\"Setting audio transceiver to inactive:\", audioTransceiver);\n }\n } else {\n \u002F\u002F Take care of audio m-line\n if(audioSend && audioRecv) {\n if(audioTransceiver) {\n if (audioTransceiver.setDirection) {\n audioTransceiver.setDirection(\"sendrecv\");\n } else {\n audioTransceiver.direction = \"sendrecv\";\n }\n Janus.log(\"Setting audio transceiver to sendrecv:\", audioTransceiver);\n }\n } else if(audioSend && !audioRecv) {\n if(audioTransceiver) {\n if (audioTransceiver.setDirection) {\n audioTransceiver.setDirection(\"sendonly\");\n } else {\n audioTransceiver.direction = \"sendonly\";\n }\n Janus.log(\"Setting audio transceiver to sendonly:\", audioTransceiver);\n }\n } else if(!audioSend && audioRecv) {\n if(audioTransceiver) {\n if (audioTransceiver.setDirection) {\n audioTransceiver.setDirection(\"recvonly\");\n } else {\n audioTransceiver.direction = \"recvonly\";\n }\n Janus.log(\"Setting audio transceiver to recvonly:\", audioTransceiver);\n } else {\n \u002F\u002F In theory, this is the only case where we might not have a transceiver yet\n audioTransceiver = config.pc.addTransceiver(\"audio\", { direction: \"recvonly\" });\n Janus.log(\"Adding recvonly audio transceiver:\", audioTransceiver);\n }\n }\n }\n \u002F\u002F Handle video (and related changes, if any)\n var videoSend = isVideoSendEnabled(media);\n var videoRecv = isVideoRecvEnabled(media);\n if(!videoSend && !videoRecv) {\n \u002F\u002F Video disabled: have we removed it?\n if(media.removeVideo && videoTransceiver) {\n if (videoTransceiver.setDirection) {\n videoTransceiver.setDirection(\"inactive\");\n } else {\n videoTransceiver.direction = \"inactive\";\n }\n Janus.log(\"Setting video transceiver to inactive:\", videoTransceiver);\n }\n } else {\n \u002F\u002F Take care of video m-line\n if(videoSend && videoRecv) {\n if(videoTransceiver) {\n if (videoTransceiver.setDirection) {\n videoTransceiver.setDirection(\"sendrecv\");\n } else {\n videoTransceiver.direction = \"sendrecv\";\n }\n Janus.log(\"Setting video transceiver to sendrecv:\", videoTransceiver);\n }\n } else if(videoSend && !videoRecv) {\n if(videoTransceiver) {\n if (videoTransceiver.setDirection) {\n videoTransceiver.setDirection(\"sendonly\");\n } else {\n videoTransceiver.direction = \"sendonly\";\n }\n Janus.log(\"Setting video transceiver to sendonly:\", videoTransceiver);\n }\n } else if(!videoSend && videoRecv) {\n if(videoTransceiver) {\n if (videoTransceiver.setDirection) {\n videoTransceiver.setDirection(\"recvonly\");\n } else {\n videoTransceiver.direction = \"recvonly\";\n }\n Janus.log(\"Setting video transceiver to recvonly:\", videoTransceiver);\n } else {\n \u002F\u002F In theory, this is the only case where we might not have a transceiver yet\n videoTransceiver = config.pc.addTransceiver(\"video\", { direction: \"recvonly\" });\n Janus.log(\"Adding recvonly video transceiver:\", videoTransceiver);\n }\n }\n }\n } else {\n mediaConstraints[\"offerToReceiveAudio\"] = isAudioRecvEnabled(media);\n mediaConstraints[\"offerToReceiveVideo\"] = isVideoRecvEnabled(media);\n }\n var iceRestart = (callbacks.iceRestart === true);\n if(iceRestart) {\n mediaConstraints[\"iceRestart\"] = true;\n }\n Janus.debug(mediaConstraints);\n \u002F\u002F Check if this is Firefox and we've been asked to do simulcasting\n var sendVideo = isVideoSendEnabled(media);\n if(sendVideo && simulcast && Janus.webRTCAdapter.browserDetails.browser === \"firefox\") {\n \u002F\u002F FIXME Based on https:\u002F\u002Fgist.github.com\u002Fvoluntas\u002F088bc3cc62094730647b\n Janus.log(\"Enabling Simulcasting for Firefox (RID)\");\n var sender = config.pc.getSenders().find(function(s) {return s.track.kind == \"video\"});\n if(sender) {\n var parameters = sender.getParameters();\n if(!parameters)\n parameters = {};\n\n\n const maxBitrates = getMaxBitrates(callbacks.simulcastMaxBitrates);\n parameters.encodings = [\n { rid: \"h\", active: true, maxBitrate: maxBitrates.high },\n { rid: \"m\", active: true, maxBitrate: maxBitrates.medium, scaleResolutionDownBy: 2 },\n { rid: \"l\", active: true, maxBitrate: maxBitrates.low, scaleResolutionDownBy: 4 }\n ];\n sender.setParameters(parameters);\n }\n }\n config.pc.createOffer(mediaConstraints)\n .then(function(offer) {\n Janus.debug(offer);\n \u002F\u002F JSON.stringify doesn't work on some WebRTC objects anymore\n \u002F\u002F See https:\u002F\u002Fcode.google.com\u002Fp\u002Fchromium\u002Fissues\u002Fdetail?id=467366\n var jsep = {\n \"type\": offer.type,\n \"sdp\": offer.sdp\n };\n callbacks.customizeSdp(jsep);\n offer.sdp = jsep.sdp;\n Janus.log(\"Setting local description\");\n if(sendVideo && simulcast) {\n \u002F\u002F This SDP munging only works with Chrome (Safari STP may support it too)\n if(Janus.webRTCAdapter.browserDetails.browser === \"chrome\" ||\n Janus.webRTCAdapter.browserDetails.browser === \"safari\") {\n Janus.log(\"Enabling Simulcasting for Chrome (SDP munging)\");\n offer.sdp = mungeSdpForSimulcasting(offer.sdp);\n } else if(Janus.webRTCAdapter.browserDetails.browser !== \"firefox\") {\n Janus.warn(\"simulcast=true, but this is not Chrome nor Firefox, ignoring\");\n }\n }\n config.mySdp = offer.sdp;\n config.pc.setLocalDescription(offer)\n .catch(callbacks.error);\n config.mediaConstraints = mediaConstraints;\n if(!config.iceDone && !config.trickle) {\n \u002F\u002F Don't do anything until we have all candidates\n Janus.log(\"Waiting for all candidates...\");\n return;\n }\n Janus.log(\"Offer ready\");\n Janus.debug(callbacks);\n callbacks.success(offer);\n }, callbacks.error);\n }\n\n function createAnswer(handleId, media, callbacks) {\n callbacks = callbacks || {};\n callbacks.success = (typeof callbacks.success == \"function\") ? callbacks.success : Janus.noop;\n callbacks.error = (typeof callbacks.error == \"function\") ? callbacks.error : Janus.noop;\n callbacks.customizeSdp = (typeof callbacks.customizeSdp == \"function\") ? callbacks.customizeSdp : Janus.noop;\n var pluginHandle = pluginHandles[handleId];\n if(!pluginHandle || !pluginHandle.webrtcStuff) {\n Janus.warn(\"Invalid handle\");\n callbacks.error(\"Invalid handle\");\n return;\n }\n var config = pluginHandle.webrtcStuff;\n var simulcast = (callbacks.simulcast === true);\n if(!simulcast) {\n Janus.log(\"Creating answer (iceDone=\" + config.iceDone + \")\");\n } else {\n Janus.log(\"Creating answer (iceDone=\" + config.iceDone + \", simulcast=\" + simulcast + \")\");\n }\n var mediaConstraints = null;\n if(Janus.unifiedPlan) {\n \u002F\u002F We can use Transceivers\n mediaConstraints = {};\n var audioTransceiver = null, videoTransceiver = null;\n var transceivers = config.pc.getTransceivers();\n if(transceivers && transceivers.length \u003E 0) {\n for(var t of transceivers) {\n if((t.sender && t.sender.track && t.sender.track.kind === \"audio\") ||\n (t.receiver && t.receiver.track && t.receiver.track.kind === \"audio\")) {\n if(!audioTransceiver)\n audioTransceiver = t;\n continue;\n }\n if((t.sender && t.sender.track && t.sender.track.kind === \"video\") ||\n (t.receiver && t.receiver.track && t.receiver.track.kind === \"video\")) {\n if(!videoTransceiver)\n videoTransceiver = t;\n continue;\n }\n }\n }\n \u002F\u002F Handle audio (and related changes, if any)\n var audioSend = isAudioSendEnabled(media);\n var audioRecv = isAudioRecvEnabled(media);\n if(!audioSend && !audioRecv) {\n \u002F\u002F Audio disabled: have we removed it?\n if(media.removeAudio && audioTransceiver) {\n try {\n if (audioTransceiver.setDirection) {\n audioTransceiver.setDirection(\"inactive\");\n } else {\n audioTransceiver.direction = \"inactive\";\n }\n Janus.log(\"Setting audio transceiver to inactive:\", audioTransceiver);\n } catch(e) {\n Janus.error(e);\n }\n }\n } else {\n \u002F\u002F Take care of audio m-line\n if(audioSend && audioRecv) {\n if(audioTransceiver) {\n try {\n if (audioTransceiver.setDirection) {\n audioTransceiver.setDirection(\"sendrecv\");\n } else {\n audioTransceiver.direction = \"sendrecv\";\n }\n Janus.log(\"Setting audio transceiver to sendrecv:\", audioTransceiver);\n } catch(e) {\n Janus.error(e);\n }\n }\n } else if(audioSend && !audioRecv) {\n try {\n if(audioTransceiver) {\n if (audioTransceiver.setDirection) {\n audioTransceiver.setDirection(\"sendonly\");\n } else {\n audioTransceiver.direction = \"sendonly\";\n }\n Janus.log(\"Setting audio transceiver to sendonly:\", audioTransceiver);\n }\n } catch(e) {\n Janus.error(e);\n }\n } else if(!audioSend && audioRecv) {\n if(audioTransceiver) {\n try {\n if (audioTransceiver.setDirection) {\n audioTransceiver.setDirection(\"recvonly\");\n } else {\n audioTransceiver.direction = \"recvonly\";\n }\n Janus.log(\"Setting audio transceiver to recvonly:\", audioTransceiver);\n } catch(e) {\n Janus.error(e);\n }\n } else {\n \u002F\u002F In theory, this is the only case where we might not have a transceiver yet\n audioTransceiver = config.pc.addTransceiver(\"audio\", { direction: \"recvonly\" });\n Janus.log(\"Adding recvonly audio transceiver:\", audioTransceiver);\n }\n }\n }\n \u002F\u002F Handle video (and related changes, if any)\n var videoSend = isVideoSendEnabled(media);\n var videoRecv = isVideoRecvEnabled(media);\n if(!videoSend && !videoRecv) {\n \u002F\u002F Video disabled: have we removed it?\n if(media.removeVideo && videoTransceiver) {\n try {\n if (videoTransceiver.setDirection) {\n videoTransceiver.setDirection(\"inactive\");\n } else {\n videoTransceiver.direction = \"inactive\";\n }\n Janus.log(\"Setting video transceiver to inactive:\", videoTransceiver);\n } catch(e) {\n Janus.error(e);\n }\n }\n } else {\n \u002F\u002F Take care of video m-line\n if(videoSend && videoRecv) {\n if(videoTransceiver) {\n try {\n if (videoTransceiver.setDirection) {\n videoTransceiver.setDirection(\"sendrecv\");\n } else {\n videoTransceiver.direction = \"sendrecv\";\n }\n Janus.log(\"Setting video transceiver to sendrecv:\", videoTransceiver);\n } catch(e) {\n Janus.error(e);\n }\n }\n } else if(videoSend && !videoRecv) {\n if(videoTransceiver) {\n try {\n if (videoTransceiver.setDirection) {\n videoTransceiver.setDirection(\"sendonly\");\n } else {\n videoTransceiver.direction = \"sendonly\";\n }\n Janus.log(\"Setting video transceiver to sendonly:\", videoTransceiver);\n } catch(e) {\n Janus.error(e);\n }\n }\n } else if(!videoSend && videoRecv) {\n if(videoTransceiver) {\n try {\n if (videoTransceiver.setDirection) {\n videoTransceiver.setDirection(\"recvonly\");\n } else {\n videoTransceiver.direction = \"recvonly\";\n }\n Janus.log(\"Setting video transceiver to recvonly:\", videoTransceiver);\n } catch(e) {\n Janus.error(e);\n }\n } else {\n \u002F\u002F In theory, this is the only case where we might not have a transceiver yet\n videoTransceiver = config.pc.addTransceiver(\"video\", { direction: \"recvonly\" });\n Janus.log(\"Adding recvonly video transceiver:\", videoTransceiver);\n }\n }\n }\n } else {\n if(Janus.webRTCAdapter.browserDetails.browser === \"firefox\" || Janus.webRTCAdapter.browserDetails.browser === \"edge\") {\n mediaConstraints = {\n offerToReceiveAudio: isAudioRecvEnabled(media),\n offerToReceiveVideo: isVideoRecvEnabled(media)\n };\n } else {\n mediaConstraints = {\n mandatory: {\n OfferToReceiveAudio: isAudioRecvEnabled(media),\n OfferToReceiveVideo: isVideoRecvEnabled(media)\n }\n };\n }\n }\n Janus.debug(mediaConstraints);\n \u002F\u002F Check if this is Firefox and we've been asked to do simulcasting\n var sendVideo = isVideoSendEnabled(media);\n if(sendVideo && simulcast && Janus.webRTCAdapter.browserDetails.browser === \"firefox\") {\n \u002F\u002F FIXME Based on https:\u002F\u002Fgist.github.com\u002Fvoluntas\u002F088bc3cc62094730647b\n Janus.log(\"Enabling Simulcasting for Firefox (RID)\");\n var sender = config.pc.getSenders()[1];\n Janus.log(sender);\n var parameters = sender.getParameters();\n Janus.log(parameters);\n\n const maxBitrates = getMaxBitrates(callbacks.simulcastMaxBitrates);\n sender.setParameters({encodings: [\n { rid: \"high\", active: true, priority: \"high\", maxBitrate: maxBitrates.high },\n { rid: \"medium\", active: true, priority: \"medium\", maxBitrate: maxBitrates.medium },\n { rid: \"low\", active: true, priority: \"low\", maxBitrate: maxBitrates.low }\n ]});\n }\n config.pc.createAnswer(mediaConstraints)\n .then(function(answer) {\n Janus.debug(answer);\n \u002F\u002F JSON.stringify doesn't work on some WebRTC objects anymore\n \u002F\u002F See https:\u002F\u002Fcode.google.com\u002Fp\u002Fchromium\u002Fissues\u002Fdetail?id=467366\n var jsep = {\n \"type\": answer.type,\n \"sdp\": answer.sdp\n };\n callbacks.customizeSdp(jsep);\n answer.sdp = jsep.sdp;\n Janus.log(\"Setting local description\");\n if(sendVideo && simulcast) {\n \u002F\u002F This SDP munging only works with Chrome\n if(Janus.webRTCAdapter.browserDetails.browser === \"chrome\") {\n \u002F\u002F FIXME Apparently trying to simulcast when answering breaks video in Chrome...\n \u002F\u002F~ Janus.log(\"Enabling Simulcasting for Chrome (SDP munging)\");\n \u002F\u002F~ answer.sdp = mungeSdpForSimulcasting(answer.sdp);\n Janus.warn(\"simulcast=true, but this is an answer, and video breaks in Chrome if we enable it\");\n } else if(Janus.webRTCAdapter.browserDetails.browser !== \"firefox\") {\n Janus.warn(\"simulcast=true, but this is not Chrome nor Firefox, ignoring\");\n }\n }\n config.mySdp = answer.sdp;\n config.pc.setLocalDescription(answer)\n .catch(callbacks.error);\n config.mediaConstraints = mediaConstraints;\n if(!config.iceDone && !config.trickle) {\n \u002F\u002F Don't do anything until we have all candidates\n Janus.log(\"Waiting for all candidates...\");\n return;\n }\n callbacks.success(answer);\n }, callbacks.error);\n }\n\n function sendSDP(handleId, callbacks) {\n callbacks = callbacks || {};\n callbacks.success = (typeof callbacks.success == \"function\") ? callbacks.success : Janus.noop;\n callbacks.error = (typeof callbacks.error == \"function\") ? callbacks.error : Janus.noop;\n var pluginHandle = pluginHandles[handleId];\n if(!pluginHandle || !pluginHandle.webrtcStuff) {\n Janus.warn(\"Invalid handle, not sending anything\");\n return;\n }\n var config = pluginHandle.webrtcStuff;\n Janus.log(\"Sending offer\u002Fanswer SDP...\");\n if(!config.mySdp) {\n Janus.warn(\"Local SDP instance is invalid, not sending anything...\");\n return;\n }\n config.mySdp = {\n \"type\": config.pc.localDescription.type,\n \"sdp\": config.pc.localDescription.sdp\n };\n if(config.trickle === false)\n config.mySdp[\"trickle\"] = false;\n Janus.debug(callbacks);\n config.sdpSent = true;\n callbacks.success(config.mySdp);\n }\n\n function getVolume(handleId, remote) {\n var pluginHandle = pluginHandles[handleId];\n if(!pluginHandle || !pluginHandle.webrtcStuff) {\n Janus.warn(\"Invalid handle\");\n return 0;\n }\n var stream = remote ? \"remote\" : \"local\";\n var config = pluginHandle.webrtcStuff;\n if(!config.volume[stream])\n config.volume[stream] = { value: 0 };\n \u002F\u002F Start getting the volume, if getStats is supported\n if(config.pc.getStats && Janus.webRTCAdapter.browserDetails.browser === \"chrome\") {\n if(remote && !config.remoteStream) {\n Janus.warn(\"Remote stream unavailable\");\n return 0;\n } else if(!remote && !config.myStream) {\n Janus.warn(\"Local stream unavailable\");\n return 0;\n }\n if(!config.volume[stream].timer) {\n Janus.log(\"Starting \" + stream + \" volume monitor\");\n config.volume[stream].timer = setInterval(function() {\n config.pc.getStats()\n .then(function(stats) {\n var results = stats.result();\n for(var i=0; i\u003Cresults.length; i++) {\n var res = results[i];\n if(res.type == 'ssrc') {\n if(remote && res.stat('audioOutputLevel'))\n config.volume[stream].value = parseInt(res.stat('audioOutputLevel'));\n else if(!remote && res.stat('audioInputLevel'))\n config.volume[stream].value = parseInt(res.stat('audioInputLevel'));\n }\n }\n });\n }, 200);\n return 0;\t\u002F\u002F We don't have a volume to return yet\n }\n return config.volume[stream].value;\n } else {\n \u002F\u002F audioInputLevel and audioOutputLevel seem only available in Chrome? audioLevel\n \u002F\u002F seems to be available on Chrome and Firefox, but they don't seem to work\n Janus.warn(\"Getting the \" + stream + \" volume unsupported by browser\");\n return 0;\n }\n }\n\n function isMuted(handleId, video) {\n var pluginHandle = pluginHandles[handleId];\n if(!pluginHandle || !pluginHandle.webrtcStuff) {\n Janus.warn(\"Invalid handle\");\n return true;\n }\n var config = pluginHandle.webrtcStuff;\n if(!config.pc) {\n Janus.warn(\"Invalid PeerConnection\");\n return true;\n }\n if(!config.myStream) {\n Janus.warn(\"Invalid local MediaStream\");\n return true;\n }\n if(video) {\n \u002F\u002F Check video track\n if(!config.myStream.getVideoTracks() || config.myStream.getVideoTracks().length === 0) {\n Janus.warn(\"No video track\");\n return true;\n }\n return !config.myStream.getVideoTracks()[0].enabled;\n } else {\n \u002F\u002F Check audio track\n if(!config.myStream.getAudioTracks() || config.myStream.getAudioTracks().length === 0) {\n Janus.warn(\"No audio track\");\n return true;\n }\n return !config.myStream.getAudioTracks()[0].enabled;\n }\n }\n\n function mute(handleId, video, mute) {\n var pluginHandle = pluginHandles[handleId];\n if(!pluginHandle || !pluginHandle.webrtcStuff) {\n Janus.warn(\"Invalid handle\");\n return false;\n }\n var config = pluginHandle.webrtcStuff;\n if(!config.pc) {\n Janus.warn(\"Invalid PeerConnection\");\n return false;\n }\n if(!config.myStream) {\n Janus.warn(\"Invalid local MediaStream\");\n return false;\n }\n if(video) {\n \u002F\u002F Mute\u002Funmute video track\n if(!config.myStream.getVideoTracks() || config.myStream.getVideoTracks().length === 0) {\n Janus.warn(\"No video track\");\n return false;\n }\n config.myStream.getVideoTracks()[0].enabled = mute ? false : true;\n return true;\n } else {\n \u002F\u002F Mute\u002Funmute audio track\n if(!config.myStream.getAudioTracks() || config.myStream.getAudioTracks().length === 0) {\n Janus.warn(\"No audio track\");\n return false;\n }\n config.myStream.getAudioTracks()[0].enabled = mute ? false : true;\n return true;\n }\n }\n\n function getBitrate(handleId) {\n var pluginHandle = pluginHandles[handleId];\n if(!pluginHandle || !pluginHandle.webrtcStuff) {\n Janus.warn(\"Invalid handle\");\n return \"Invalid handle\";\n }\n var config = pluginHandle.webrtcStuff;\n if(!config.pc)\n return \"Invalid PeerConnection\";\n \u002F\u002F Start getting the bitrate, if getStats is supported\n if(config.pc.getStats) {\n if(!config.bitrate.timer) {\n Janus.log(\"Starting bitrate timer (via getStats)\");\n config.bitrate.timer = setInterval(function() {\n config.pc.getStats()\n .then(function(stats) {\n stats.forEach(function (res) {\n if(!res)\n return;\n var inStats = false;\n \u002F\u002F Check if these are statistics on incoming media\n if((res.mediaType === \"video\" || res.id.toLowerCase().indexOf(\"video\") \u003E -1) &&\n res.type === \"inbound-rtp\" && res.id.indexOf(\"rtcp\") \u003C 0) {\n \u002F\u002F New stats\n inStats = true;\n } else if(res.type == 'ssrc' && res.bytesReceived &&\n (res.googCodecName === \"VP8\" || res.googCodecName === \"\")) {\n \u002F\u002F Older Chromer versions\n inStats = true;\n }\n \u002F\u002F Parse stats now\n if(inStats) {\n config.bitrate.bsnow = res.bytesReceived;\n config.bitrate.tsnow = res.timestamp;\n if(config.bitrate.bsbefore === null || config.bitrate.tsbefore === null) {\n \u002F\u002F Skip this round\n config.bitrate.bsbefore = config.bitrate.bsnow;\n config.bitrate.tsbefore = config.bitrate.tsnow;\n } else {\n \u002F\u002F Calculate bitrate\n var timePassed = config.bitrate.tsnow - config.bitrate.tsbefore;\n if(Janus.webRTCAdapter.browserDetails.browser === \"safari\")\n timePassed = timePassed\u002F1000;\t\u002F\u002F Apparently the timestamp is in microseconds, in Safari\n var bitRate = Math.round((config.bitrate.bsnow - config.bitrate.bsbefore) * 8 \u002F timePassed);\n if(Janus.webRTCAdapter.browserDetails.browser === \"safari\")\n bitRate = parseInt(bitRate\u002F1000);\n config.bitrate.value = bitRate + ' kbits\u002Fsec';\n \u002F\u002F~ Janus.log(\"Estimated bitrate is \" + config.bitrate.value);\n config.bitrate.bsbefore = config.bitrate.bsnow;\n config.bitrate.tsbefore = config.bitrate.tsnow;\n }\n }\n });\n });\n }, 1000);\n return \"0 kbits\u002Fsec\";\t\u002F\u002F We don't have a bitrate value yet\n }\n return config.bitrate.value;\n } else {\n Janus.warn(\"Getting the video bitrate unsupported by browser\");\n return \"Feature unsupported by browser\";\n }\n }\n\n function webrtcError(error) {\n Janus.error(\"WebRTC error:\", error);\n }\n\n function cleanupWebrtc(handleId, hangupRequest) {\n Janus.log(\"Cleaning WebRTC stuff\");\n var pluginHandle = pluginHandles[handleId];\n if(!pluginHandle) {\n \u002F\u002F Nothing to clean\n return;\n }\n var config = pluginHandle.webrtcStuff;\n if(config) {\n if(hangupRequest === true) {\n \u002F\u002F Send a hangup request (we don't really care about the response)\n var request = { \"janus\": \"hangup\", \"transaction\": Janus.randomString(12) };\n if(pluginHandle.token)\n request[\"token\"] = pluginHandle.token;\n if(apisecret)\n request[\"apisecret\"] = apisecret;\n Janus.debug(\"Sending hangup request (handle=\" + handleId + \"):\");\n Janus.debug(request);\n if(websockets) {\n request[\"session_id\"] = sessionId;\n request[\"handle_id\"] = handleId;\n ws.send(JSON.stringify(request));\n } else {\n Janus.httpAPICall(server + \"\u002F\" + sessionId + \"\u002F\" + handleId, {\n verb: 'POST',\n withCredentials: withCredentials,\n body: request\n });\n }\n }\n \u002F\u002F Cleanup stack\n config.remoteStream = null;\n if(config.volume) {\n if(config.volume[\"local\"] && config.volume[\"local\"].timer)\n clearInterval(config.volume[\"local\"].timer);\n if(config.volume[\"remote\"] && config.volume[\"remote\"].timer)\n clearInterval(config.volume[\"remote\"].timer);\n }\n config.volume = {};\n if(config.bitrate.timer)\n clearInterval(config.bitrate.timer);\n config.bitrate.timer = null;\n config.bitrate.bsnow = null;\n config.bitrate.bsbefore = null;\n config.bitrate.tsnow = null;\n config.bitrate.tsbefore = null;\n config.bitrate.value = null;\n try {\n \u002F\u002F Try a MediaStreamTrack.stop() for each track\n if(!config.streamExternal && config.myStream) {\n Janus.log(\"Stopping local stream tracks\");\n var tracks = config.myStream.getTracks();\n for(var mst of tracks) {\n Janus.log(mst);\n if(mst)\n mst.stop();\n }\n }\n } catch(e) {\n \u002F\u002F Do nothing if this fails\n }\n config.streamExternal = false;\n config.myStream = null;\n \u002F\u002F Close PeerConnection\n try {\n config.pc.close();\n } catch(e) {\n \u002F\u002F Do nothing\n }\n config.pc = null;\n config.candidates = null;\n config.mySdp = null;\n config.remoteSdp = null;\n config.iceDone = false;\n config.dataChannel = {};\n config.dtmfSender = null;\n }\n pluginHandle.oncleanup();\n }\n\n \u002F\u002F Helper method to munge an SDP to enable simulcasting (Chrome only)\n function mungeSdpForSimulcasting(sdp) {\n \u002F\u002F Let's munge the SDP to add the attributes for enabling simulcasting\n \u002F\u002F (based on https:\u002F\u002Fgist.github.com\u002Fggarber\u002Fa19b4c33510028b9c657)\n var lines = sdp.split(\"\\r\\n\");\n var video = false;\n var ssrc = [ -1 ], ssrc_fid = [ -1 ];\n var cname = null, msid = null, mslabel = null, label = null;\n var insertAt = -1;\n for(var i=0; i\u003Clines.length; i++) {\n var mline = lines[i].match(\u002Fm=(\\w+) *\u002F);\n if(mline) {\n var medium = mline[1];\n if(medium === \"video\") {\n \u002F\u002F New video m-line: make sure it's the first one\n if(ssrc[0] \u003C 0) {\n video = true;\n } else {\n \u002F\u002F We're done, let's add the new attributes here\n insertAt = i;\n break;\n }\n } else {\n \u002F\u002F New non-video m-line: do we have what we were looking for?\n if(ssrc[0] \u003E -1) {\n \u002F\u002F We're done, let's add the new attributes here\n insertAt = i;\n break;\n }\n }\n continue;\n }\n if(!video)\n continue;\n var fid = lines[i].match(\u002Fa=ssrc-group:FID (\\d+) (\\d+)\u002F);\n if(fid) {\n ssrc[0] = fid[1];\n ssrc_fid[0] = fid[2];\n lines.splice(i, 1); i--;\n continue;\n }\n if(ssrc[0]) {\n var match = lines[i].match('a=ssrc:' + ssrc[0] + ' cname:(.+)');\n if(match) {\n cname = match[1];\n }\n match = lines[i].match('a=ssrc:' + ssrc[0] + ' msid:(.+)');\n if(match) {\n msid = match[1];\n }\n match = lines[i].match('a=ssrc:' + ssrc[0] + ' mslabel:(.+)');\n if(match) {\n mslabel = match[1];\n }\n match = lines[i].match('a=ssrc:' + ssrc[0] + ' label:(.+)');\n if(match) {\n label = match[1];\n }\n if(lines[i].indexOf('a=ssrc:' + ssrc_fid[0]) === 0) {\n lines.splice(i, 1); i--;\n continue;\n }\n if(lines[i].indexOf('a=ssrc:' + ssrc[0]) === 0) {\n lines.splice(i, 1); i--;\n continue;\n }\n }\n if(lines[i].length == 0) {\n lines.splice(i, 1); i--;\n continue;\n }\n }\n if(ssrc[0] \u003C 0) {\n \u002F\u002F Couldn't find a FID attribute, let's just take the first video SSRC we find\n insertAt = -1;\n video = false;\n for(var i=0; i\u003Clines.length; i++) {\n var mline = lines[i].match(\u002Fm=(\\w+) *\u002F);\n if(mline) {\n var medium = mline[1];\n if(medium === \"video\") {\n \u002F\u002F New video m-line: make sure it's the first one\n if(ssrc[0] \u003C 0) {\n video = true;\n } else {\n \u002F\u002F We're done, let's add the new attributes here\n insertAt = i;\n break;\n }\n } else {\n \u002F\u002F New non-video m-line: do we have what we were looking for?\n if(ssrc[0] \u003E -1) {\n \u002F\u002F We're done, let's add the new attributes here\n insertAt = i;\n break;\n }\n }\n continue;\n }\n if(!video)\n continue;\n if(ssrc[0] \u003C 0) {\n var value = lines[i].match(\u002Fa=ssrc:(\\d+)\u002F);\n if(value) {\n ssrc[0] = value[1];\n lines.splice(i, 1); i--;\n continue;\n }\n } else {\n var match = lines[i].match('a=ssrc:' + ssrc[0] + ' cname:(.+)');\n if(match) {\n cname = match[1];\n }\n match = lines[i].match('a=ssrc:' + ssrc[0] + ' msid:(.+)');\n if(match) {\n msid = match[1];\n }\n match = lines[i].match('a=ssrc:' + ssrc[0] + ' mslabel:(.+)');\n if(match) {\n mslabel = match[1];\n }\n match = lines[i].match('a=ssrc:' + ssrc[0] + ' label:(.+)');\n if(match) {\n label = match[1];\n }\n if(lines[i].indexOf('a=ssrc:' + ssrc_fid[0]) === 0) {\n lines.splice(i, 1); i--;\n continue;\n }\n if(lines[i].indexOf('a=ssrc:' + ssrc[0]) === 0) {\n lines.splice(i, 1); i--;\n continue;\n }\n }\n if(lines[i].length == 0) {\n lines.splice(i, 1); i--;\n continue;\n }\n }\n }\n if(ssrc[0] \u003C 0) {\n \u002F\u002F Still nothing, let's just return the SDP we were asked to munge\n Janus.warn(\"Couldn't find the video SSRC, simulcasting NOT enabled\");\n return sdp;\n }\n if(insertAt \u003C 0) {\n \u002F\u002F Append at the end\n insertAt = lines.length;\n }\n \u002F\u002F Generate a couple of SSRCs (for retransmissions too)\n \u002F\u002F Note: should we check if there are conflicts, here?\n ssrc[1] = Math.floor(Math.random()*0xFFFFFFFF);\n ssrc[2] = Math.floor(Math.random()*0xFFFFFFFF);\n ssrc_fid[1] = Math.floor(Math.random()*0xFFFFFFFF);\n ssrc_fid[2] = Math.floor(Math.random()*0xFFFFFFFF);\n \u002F\u002F Add attributes to the SDP\n for(var i=0; i\u003Cssrc.length; i++) {\n if(cname) {\n lines.splice(insertAt, 0, 'a=ssrc:' + ssrc[i] + ' cname:' + cname);\n insertAt++;\n }\n if(msid) {\n lines.splice(insertAt, 0, 'a=ssrc:' + ssrc[i] + ' msid:' + msid);\n insertAt++;\n }\n if(mslabel) {\n lines.splice(insertAt, 0, 'a=ssrc:' + ssrc[i] + ' mslabel:' + mslabel);\n insertAt++;\n }\n if(label) {\n lines.splice(insertAt, 0, 'a=ssrc:' + ssrc[i] + ' label:' + label);\n insertAt++;\n }\n \u002F\u002F Add the same info for the retransmission SSRC\n if(cname) {\n lines.splice(insertAt, 0, 'a=ssrc:' + ssrc_fid[i] + ' cname:' + cname);\n insertAt++;\n }\n if(msid) {\n lines.splice(insertAt, 0, 'a=ssrc:' + ssrc_fid[i] + ' msid:' + msid);\n insertAt++;\n }\n if(mslabel) {\n lines.splice(insertAt, 0, 'a=ssrc:' + ssrc_fid[i] + ' mslabel:' + mslabel);\n insertAt++;\n }\n if(label) {\n lines.splice(insertAt, 0, 'a=ssrc:' + ssrc_fid[i] + ' label:' + label);\n insertAt++;\n }\n }\n lines.splice(insertAt, 0, 'a=ssrc-group:FID ' + ssrc[2] + ' ' + ssrc_fid[2]);\n lines.splice(insertAt, 0, 'a=ssrc-group:FID ' + ssrc[1] + ' ' + ssrc_fid[1]);\n lines.splice(insertAt, 0, 'a=ssrc-group:FID ' + ssrc[0] + ' ' + ssrc_fid[0]);\n lines.splice(insertAt, 0, 'a=ssrc-group:SIM ' + ssrc[0] + ' ' + ssrc[1] + ' ' + ssrc[2]);\n sdp = lines.join(\"\\r\\n\");\n if(!sdp.endsWith(\"\\r\\n\"))\n sdp += \"\\r\\n\";\n return sdp;\n }\n\n \u002F\u002F Helper methods to parse a media object\n function isAudioSendEnabled(media) {\n Janus.debug(\"isAudioSendEnabled:\", media);\n if(!media)\n return true;\t\u002F\u002F Default\n if(media.audio === false)\n return false;\t\u002F\u002F Generic audio has precedence\n if(media.audioSend === undefined || media.audioSend === null)\n return true;\t\u002F\u002F Default\n return (media.audioSend === true);\n }\n\n function isAudioSendRequired(media) {\n Janus.debug(\"isAudioSendRequired:\", media);\n if(!media)\n return false;\t\u002F\u002F Default\n if(media.audio === false || media.audioSend === false)\n return false;\t\u002F\u002F If we're not asking to capture audio, it's not required\n if(media.failIfNoAudio === undefined || media.failIfNoAudio === null)\n return false;\t\u002F\u002F Default\n return (media.failIfNoAudio === true);\n }\n\n function isAudioRecvEnabled(media) {\n Janus.debug(\"isAudioRecvEnabled:\", media);\n if(!media)\n return true;\t\u002F\u002F Default\n if(media.audio === false)\n return false;\t\u002F\u002F Generic audio has precedence\n if(media.audioRecv === undefined || media.audioRecv === null)\n return true;\t\u002F\u002F Default\n return (media.audioRecv === true);\n }\n\n function isVideoSendEnabled(media) {\n Janus.debug(\"isVideoSendEnabled:\", media);\n if(!media)\n return true;\t\u002F\u002F Default\n if(media.video === false)\n return false;\t\u002F\u002F Generic video has precedence\n if(media.videoSend === undefined || media.videoSend === null)\n return true;\t\u002F\u002F Default\n return (media.videoSend === true);\n }\n\n function isVideoSendRequired(media) {\n Janus.debug(\"isVideoSendRequired:\", media);\n if(!media)\n return false;\t\u002F\u002F Default\n if(media.video === false || media.videoSend === false)\n return false;\t\u002F\u002F If we're not asking to capture video, it's not required\n if(media.failIfNoVideo === undefined || media.failIfNoVideo === null)\n return false;\t\u002F\u002F Default\n return (media.failIfNoVideo === true);\n }\n\n function isVideoRecvEnabled(media) {\n Janus.debug(\"isVideoRecvEnabled:\", media);\n if(!media)\n return true;\t\u002F\u002F Default\n if(media.video === false)\n return false;\t\u002F\u002F Generic video has precedence\n if(media.videoRecv === undefined || media.videoRecv === null)\n return true;\t\u002F\u002F Default\n return (media.videoRecv === true);\n }\n\n function isScreenSendEnabled(media) {\n Janus.debug(\"isScreenSendEnabled:\", media);\n if (!media)\n return false;\n if (typeof media.video !== 'object' || typeof media.video.mandatory !== 'object')\n return false;\n var constraints = media.video.mandatory;\n if (constraints.chromeMediaSource)\n return constraints.chromeMediaSource === 'desktop' || constraints.chromeMediaSource === 'screen';\n else if (constraints.mozMediaSource)\n return constraints.mozMediaSource === 'window' || constraints.mozMediaSource === 'screen';\n else if (constraints.mediaSource)\n return constraints.mediaSource === 'window' || constraints.mediaSource === 'screen';\n return false;\n }\n\n function isDataEnabled(media) {\n Janus.debug(\"isDataEnabled:\", media);\n if(Janus.webRTCAdapter.browserDetails.browser === \"edge\") {\n Janus.warn(\"Edge doesn't support data channels yet\");\n return false;\n }\n if(media === undefined || media === null)\n return false;\t\u002F\u002F Default\n return (media.data === true);\n }\n\n function isTrickleEnabled(trickle) {\n Janus.debug(\"isTrickleEnabled:\", trickle);\n return (trickle === false) ? false : true;\n }\n }\n\n var JanusClient = function (dom,janusServer){\n var server = null;\n server = \"https:\u002F\u002F\" + (janusServer || window.location.hostname) + \":8089\u002Fjanus\";\n\n var janus = null;\n var sfutest = null;\n var opaqueId = \"orkestra-\"+Janus.randomString(12);\n\n var myroom = 1234;\t\u002F\u002F Demo room\n var myid = null;\n var mystream = null;\n \u002F\u002F We use this other ID just to map our subscriptions to us\n var mypvtid = null;\n var feeds = [];\n var bitrateTimer = [];\n\n var doSimulcast = (getQueryStringValue(\"simulcast\") === \"yes\" || getQueryStringValue(\"simulcast\") === \"true\");\n var doSimulcast2 = (getQueryStringValue(\"simulcast2\") === \"yes\" || getQueryStringValue(\"simulcast2\") === \"true\");\n \u002F\u002F Initialize the library (all console debuggers enabled)\n\n function init (){\n if (Janus.running) return;\n Janus.init({debug: \"all\", callback: function() {\n Janus.running = true;\n \u002F\u002F Make sure the browser supports WebRTC\n if(!Janus.isWebrtcSupported()) {\n bootbox.alert(\"No WebRTC support... \");\n return;\n }\n \u002F\u002F Create session\n\n janus = new Janus(\n {\n server: server,\n success: function() {\n \u002F\u002F Attach to video room test plugin\n janus.attach(\n {\n plugin: \"janus.plugin.videoroom\",\n opaqueId: opaqueId,\n success: function(pluginHandle) {\n sfutest = pluginHandle;\n Janus.log(\"Plugin attached! (\" + sfutest.getPlugin() + \", id=\" + sfutest.getId() + \")\");\n Janus.log(\" -- This is a publisher\u002Fmanager\");\n \u002F\u002F Prepare the username registration\n },\n error: function(error) {\n Janus.error(\" -- Error attaching plugin...\", error);\n bootbox.alert(\"Error attaching plugin... \" + error);\n },\n consentDialog: function(on) {\n Janus.debug(\"Consent dialog should be \" + (on ? \"on\" : \"off\") + \" now\");\n if(on) {\n \u002F\u002F Darken screen and show hint\n $.blockUI({\n message: '\u003Cdiv\u003E\u003Cimg src=\"up_arrow.png\"\u002F\u003E\u003C\u002Fdiv\u003E',\n css: {\n border: 'none',\n padding: '15px',\n backgroundColor: 'transparent',\n color: '#aaa',\n top: '10px',\n left: (navigator.mozGetUserMedia ? '-100px' : '300px')\n } });\n } else {\n \u002F\u002F Restore screen\n $.unblockUI();\n }\n },\n mediaState: function(medium, on) {\n Janus.log(\"Janus \" + (on ? \"started\" : \"stopped\") + \" receiving our \" + medium);\n },\n webrtcState: function(on) {\n Janus.log(\"Janus says our WebRTC PeerConnection is \" + (on ? \"up\" : \"down\") + \" now\");\n \u002F*\t$(\"#videolocal\").parent().parent().unblock();\n if(!on)\n return;\n $('#publish').remove();\n \u002F\u002F This controls allows us to override the global room bitrate cap\n $('#bitrate').parent().parent().removeClass('hide').show();\n $('#bitrate a').click(function() {\n var id = $(this).attr(\"id\");\n var bitrate = parseInt(id)*1000;\n if(bitrate === 0) {\n Janus.log(\"Not limiting bandwidth via REMB\");\n } else {\n Janus.log(\"Capping bandwidth to \" + bitrate + \" via REMB\");\n }\n $('#bitrateset').html($(this).html() + '\u003Cspan class=\"caret\"\u003E\u003C\u002Fspan\u003E').parent().removeClass('open');\n sfutest.send({\"message\": { \"request\": \"configure\", \"bitrate\": bitrate }});\n return false;\n });*\u002F\n },\n onmessage: function(msg, jsep) {\n Janus.debug(\" ::: Got a message (publisher) :::\");\n\n Janus.debug(msg);\n var event = msg[\"videoroom\"];\n Janus.debug(\"Event: \" + event);\n\n if(event != undefined && event != null) {\n if(event === \"joined\") {\n \u002F\u002F Publisher\u002Fmanager created, negotiate WebRTC and attach to existing feeds, if any\n \u002F\u002F\n \u002F\u002F\n var janusReady =new CustomEvent(\"JanusReady\", {\n detail: {\n ready: true\n }\n });\n document.dispatchEvent(janusReady);\n\n myid = msg[\"id\"];\n mypvtid = msg[\"private_id\"];\n Janus.log(\"Successfully joined room \" + msg[\"room\"] + \" with ID \" + myid);\n \u002F\u002F Any new feed to attach to?\n if(msg[\"publishers\"] !== undefined && msg[\"publishers\"] !== null) {\n janus.list = msg[\"publishers\"];\n Janus.debug(\"Got a list of available publishers\u002Ffeeds:\");\n Janus.debug(janus.list);\n for(var f = 0; f\u003C janus.list.length;f++) {\n var id = janus.list[f][\"id\"];\n var display = janus.list[f][\"display\"];\n var audio = janus.list[f][\"audio_codec\"];\n var video = janus.list[f][\"video_codec\"];\n Janus.debug(\" \u003E\u003E [\" + id + \"] \" + display + \" (audio: \" + audio + \", video: \" + video + \")\");\n newRemoteFeed(id, display, audio, video);\n }\n }\n } else if(event === \"destroyed\") {\n \u002F\u002F The room has been destroyed\n Janus.warn(\"The room has been destroyed!\");\n bootbox.alert(\"The room has been destroyed\", function() {\n window.location.reload();\n });\n } else if(event === \"event\") {\n \u002F\u002F Any new feed to attach to?\n if(msg[\"publishers\"] !== undefined && msg[\"publishers\"] !== null) {\n var list = msg[\"publishers\"];\n console.log(\"HEMEN\u003E\u003E\u003E\u003E\u003E\u003E\u003E\u003E\u003E\u003E\u003E\u003E\u003E\u003E\");\n Janus.debug(\"Got a list of available publishers\u002Ffeeds:\");\n Janus.debug(janus.list);\n for(var f=0; f\u003C list.length;f++) {\n janus.list.push(list[f]);\n var newStream =new CustomEvent(\"newStream\", {detail: {\n id: list[f][\"display\"]\n }\n });\n document.dispatchEvent(newStream);\n var r = msg[\"publishers\"];\n newRemoteFeed(r[0].id, r[0].display, r[0].audio,r[0].video);\n }\n } else if(msg[\"leaving\"] !== undefined && msg[\"leaving\"] !== null) {\n \u002F\u002F One of the publishers has gone away?\n var leaving = msg[\"leaving\"];\n Janus.log(\"Publisher left: \" + leaving);\n var remoteFeed = null;\n for(var i=0; i\u003C26; i++) {\n if(feeds[i] != null && feeds[i] != undefined && feeds[i].rfid == leaving) {\n remoteFeed = feeds[i];\n break;\n }\n\n }\n FlexJanus.feeds = FlexJanus.feeds.filter((el)=\u003E {return el.rfid !==leaving});\n var deleteStream =new CustomEvent(\"updateStream\", {detail: {id: unpublished}});\n document.dispatchEvent(deleteStream);\n\n if(remoteFeed != null) {\n Janus.debug(\"Feed \" + remoteFeed.rfid + \" (\" + remoteFeed.rfdisplay + \") has left the room, detaching\");\n \u002F\u002F\tdom.querySelector('#remote'+remoteFeed.rfindex).empty().hide();\n \u002F\u002F\tdom.querySelector('#videoremote'+remoteFeed.rfindex).empty();\n feeds[remoteFeed.rfindex] = null;\n remoteFeed.detach();\n }\n } else if(msg[\"unpublished\"] !== undefined && msg[\"unpublished\"] !== null) {\n \u002F\u002F One of the publishers has unpublished?\n var unpublished = msg[\"unpublished\"];\n Janus.log(\"Publisher left: \" + unpublished);\n if(unpublished === 'ok') {\n \u002F\u002F That's us\n sfutest.hangup();\n return;\n }\n var remoteFeed = null;\n for(var i=0; i\u003C26; i++) {\n if(feeds[i] != null && feeds[i] != undefined && feeds[i].rfid == unpublished) {\n remoteFeed = feeds[i];\n break;\n }\n }\n\n FlexJanus.feeds = FlexJanus.feeds.filter((el)=\u003E {return el.rfid !==unpublished});\n\n var deleteStream =new CustomEvent(\"updateStream\", {detail: {id: unpublished}});\n document.dispatchEvent(deleteStream);\n\n if(remoteFeed != null) {\n Janus.debug(\"Feed \" + remoteFeed.rfid + \" (\" + remoteFeed.rfdisplay + \") has left the room, detaching\");\n \u002F\u002F\tdom.querySelector('#remote'+remoteFeed.rfindex).empty().hide();\n \u002F\u002F\tdom.querySelector('#videoremote'+remoteFeed.rfindex).empty();\n feeds[remoteFeed.rfindex] = null;\n remoteFeed.detach();\n }\n } else if(msg[\"error\"] !== undefined && msg[\"error\"] !== null) {\n if(msg[\"error_code\"] === 426) {\n \u002F\u002F This is a \"no such room\" error: give a more meaningful description\n bootbox.alert(\n \"\u003Cp\u003EApparently room \u003Ccode\u003E\" + myroom + \"\u003C\u002Fcode\u003E (the one this demo uses as a test room) \" +\n \"does not exist...\u003C\u002Fp\u003E\u003Cp\u003EDo you have an updated \u003Ccode\u003Ejanus.plugin.videoroom.cfg\u003C\u002Fcode\u003E \" +\n \"configuration file? If not, make sure you copy the details of room \u003Ccode\u003E\" + myroom + \"\u003C\u002Fcode\u003E \" +\n \"from that sample in your current configuration file, then restart Janus and try again.\"\n );\n } else {\n bootbox.alert(msg[\"error\"]);\n }\n }\n }\n }\n if(jsep !== undefined && jsep !== null) {\n Janus.debug(\"Handling SDP as well...\");\n Janus.debug(jsep);\n sfutest.handleRemoteJsep({jsep: jsep});\n \u002F\u002F Check if any of the media we wanted to publish has\n \u002F\u002F been rejected (e.g., wrong or unsupported codec)\n var audio = msg[\"audio_codec\"];\n if(mystream && mystream.getAudioTracks() && mystream.getAudioTracks().length \u003E 0 && !audio) {\n \u002F\u002F Audio has been rejected\n toastr.warning(\"Our audio stream has been rejected, viewers won't hear us\");\n }\n var video = msg[\"video_codec\"];\n if(mystream && mystream.getVideoTracks() && mystream.getVideoTracks().length \u003E 0 && !video) {\n \u002F\u002F Video has been rejected\n toastr.warning(\"Our video stream has been rejected, viewers won't see us\");\n \u002F\u002F Hide the webcam video\n $('#myvideo').hide();\n $('#videolocal').append(\n '\u003Cdiv class=\"no-video-container\"\u003E' +\n '\u003Ci class=\"fa fa-video-camera fa-5 no-video-icon\" style=\"height: 100%;\"\u003E\u003C\u002Fi\u003E' +\n '\u003Cspan class=\"no-video-text\" style=\"font-size: 16px;\"\u003EVideo rejected, no webcam\u003C\u002Fspan\u003E' +\n '\u003C\u002Fdiv\u003E');\n }\n }\n },\n onlocalstream: function(stream) {\n Janus.debug(\" ::: Got a local stream :::\");\n\n },\n onremotestream: function(stream) {\n \u002F\u002F The publisher stream is sendonly, we don't expect anything here\n },\n oncleanup: function() {\n Janus.log(\" ::: Got a cleanup notification: we are unpublished now :::\");\n mystream = null;\n $('#videolocal').html('\u003Cbutton id=\"publish\" class=\"btn btn-primary\"\u003EPublish\u003C\u002Fbutton\u003E');\n $(\"#videolocal\").parent().parent().unblock();\n $('#bitrate').parent().parent().addClass('hide');\n $('#bitrate a').unbind('click');\n }\n });\n },\n error: function(error) {\n Janus.error(error);\n bootbox.alert(error, function() {\n window.location.reload();\n });\n },\n destroyed: function() {\n window.location.reload();\n }\n });\n janus.list = [];\n janus.activeHandles = [];\n setTimeout(()=\u003EregisterUsername(),5000);\n\n }});\n }\n function start(id,dom){\n var handler = FlexJanus.feeds.find((f)=\u003E{if(f.rfdisplay===id) return true;});\n if(!handler)\n {\n console.warn( \"There is not that feed\",id);\n }\n\n else changeCamera(id,dom);\n }\n function stopCamera(id){\n var elem = dom.querySelector('video');\n elem.src = \"\";\n elem.srcObject=null;\n\n }\n function registerUsername() {\n var register = { \"request\": \"join\", \"room\": myroom, \"ptype\": \"publisher\", \"display\":\"flex\" };\n let inter = setInterval(()=\u003E{\n if (sfutest){\n sfutest.send({\"message\": register});\n clearInterval(inter);\n }\n },500);\n }\n function changeCamera (id,dom){\n var handler = FlexJanus.feeds.find((f)=\u003E{if(f.rfdisplay===id) return true;});\n var elem = dom.querySelector('video');\n if(handler)Janus.attachMediaStream(elem,handler.stream);\n\n }\n\n\n function newRemoteFeed(id, display, audio, video) {\n \u002F\u002F A new feed has been published, create a new plugin handle and attach to it as a subscriber\n var remoteFeed = null;\n janus.attach(\n {\n plugin: \"janus.plugin.videoroom\",\n opaqueId: opaqueId,\n success: function(pluginHandle) {\n janus.activeHandles.push({id:id,handle:pluginHandle});\n remoteFeed = pluginHandle;\n remoteFeed.simulcastStarted = false;\n Janus.log(\"Plugin attached! (\" + remoteFeed.getPlugin() + \", id=\" + remoteFeed.getId() + \")\");\n Janus.log(\" -- This is a subscriber\");\n \u002F\u002F We wait for the plugin to send us an offer\n var subscribe = { \"request\": \"join\", \"room\": myroom, \"ptype\": \"subscriber\", \"feed\": id, \"private_id\": mypvtid,offer_data:false };\n \u002F\u002F In case you don't want to receive audio, video or data, even if the\n \u002F\u002F publisher is sending them, set the 'offer_audio', 'offer_video' or\n \u002F\u002F 'offer_data' properties to false (they're true by default), e.g.:\n \u002F\u002F \t\tsubscribe[\"offer_video\"] = false;\n \u002F\u002F For example, if the publisher is VP8 and this is Safari, let's avoid video\n if(Janus.webRTCAdapter.browserDetails.browser === \"safari\" &&\n (video === \"vp9\" || (video === \"vp8\" && !Janus.safariVp8))) {\n if(video)\n video = video.toUpperCase();\n toastr.warning(\"Publisher is using \" + video + \", but Safari doesn't support it: disabling video\");\n subscribe[\"offer_video\"] = false;\n }\n remoteFeed.videoCodec = video;\n remoteFeed.send({\"message\": subscribe});\n },\n error: function(error) {\n Janus.error(\" -- Error attaching plugin...\", error);\n bootbox.alert(\"Error attaching plugin... \" + error);\n },\n onmessage: function(msg, jsep) {\n Janus.debug(\" ::: Got a message (subscriber) :::\");\n Janus.debug(msg);\n var event = msg[\"videoroom\"];\n Janus.debug(\"Event: \" + event);\n if(msg[\"error\"] !== undefined && msg[\"error\"] !== null) {\n bootbox.alert(msg[\"error\"]);\n } else if(event != undefined && event != null) {\n if(event === \"attached\") {\n \u002F\u002F Subscriber created and attached\n for(var i=0;i\u003C26;i++) {\n if(feeds[i] === undefined || feeds[i] === null) {\n feeds[i] = remoteFeed;\n remoteFeed.rfindex = i;\n break;\n }\n }\n remoteFeed.rfid = msg[\"id\"];\n remoteFeed.rfdisplay = msg[\"display\"];\n\n Janus.log(\"Successfully attached to feed \" + remoteFeed.rfid + \" (\" + remoteFeed.rfdisplay + \") in room \" + msg[\"room\"]);\n \u002F\u002F\t$('#remote'+remoteFeed.rfindex).removeClass('hide').html(remoteFeed.rfdisplay).show();\n } else if(event === \"event\") {\n \u002F\u002F Check if we got an event on a simulcast-related event from this publisher\n var substream = msg[\"substream\"];\n var temporal = msg[\"temporal\"];\n if((substream !== null && substream !== undefined) || (temporal !== null && temporal !== undefined)) {\n if(!remoteFeed.simulcastStarted) {\n remoteFeed.simulcastStarted = true;\n \u002F\u002F Add some new buttons\n addSimulcastButtons(0, remoteFeed.videoCodec === \"vp8\" || remoteFeed.videoCodec === \"h264\");\n }\n \u002F\u002F We just received notice that there's been a switch, update the buttons\n updateSimulcastButtons(0, substream, temporal);\n }\n }\n }\n if(jsep !== undefined && jsep !== null) {\n Janus.debug(\"Handling SDP as well...\");\n Janus.debug(jsep);\n \u002F\u002F Answer and attach\n remoteFeed.createAnswer(\n {\n jsep: jsep,\n \u002F\u002F Add data:true here if you want to subscribe to datachannels as well\n \u002F\u002F (obviously only works if the publisher offered them in the first place)\n media: { audioSend: false, videoSend: false,audioRecv:false,recvonly:true },\t\u002F\u002F We want recvonly audio\u002Fvideo\n success: function(jsep) {\n Janus.debug(\"Got SDP!\");\n Janus.debug(jsep);\n var body = { \"request\": \"start\", \"room\": myroom };\n remoteFeed.send({\"message\": body, \"jsep\": jsep});\n },\n error: function(error) {\n Janus.error(\"WebRTC error:\", error);\n bootbox.alert(\"WebRTC error... \" + JSON.stringify(error));\n }\n });\n }\n },\n webrtcState: function(on) {\n Janus.log(\"Janus says this WebRTC PeerConnection (feed #\" + remoteFeed.rfindex + \") is \" + (on ? \"up\" : \"down\") + \" now\");\n },\n onlocalstream: function(stream) {\n \u002F\u002F The subscriber stream is recvonly, we don't expect anything here\n },\n onremotestream: function(stream) {\n if (!remoteFeed) return;\n Janus.debug(\"Remote feed #\" + remoteFeed.rfindex);\n FlexJanus.feeds = FlexJanus.feeds.filter((x)=\u003E{ return x!=null});\n var exists = FlexJanus.feeds.find((x)=\u003E{\n if (remoteFeed && x && x.rfid == remoteFeed.rfid) return true;\n });\n if (exists)\n FlexJanus.feeds.forEach((r)=\u003E{\n if (r.rfid == remoteFeed.rfid) r.stream = stream;\n });\n else {\n\n remoteFeed.stream = stream;\n FlexJanus.feeds.push(remoteFeed);\n }\t\t\t\t\t\t\t\t\t\t\t},\n oncleanup: function() {\n Janus.log(\" ::: Got a cleanup notification (remote feed \" + id + \") :::\");\n\n remoteFeed.rfindex = 0;\n\n if(bitrateTimer[remoteFeed.rfindex] !== null && bitrateTimer[remoteFeed.rfindex] !== null)\n clearInterval(bitrateTimer[remoteFeed.rfindex]);\n bitrateTimer[remoteFeed.rfindex] = null;\n remoteFeed.simulcastStarted = false;\n\n }\n });\n }\n\n \u002F\u002F Helper to parse query string\n function getQueryStringValue(name) {\n name = name.replace(\u002F[\\[]\u002F, \"\\\\[\").replace(\u002F[\\]]\u002F, \"\\\\]\");\n var regex = new RegExp(\"[\\\\?&]\" + name + \"=([^&#]*)\"),\n results = regex.exec(location.search);\n return results === null ? \"\" : decodeURIComponent(results[1].replace(\u002F\\+\u002Fg, \" \"));\n }\n\n return {\n changeCamera:changeCamera,\n init:init,\n stop:stopCamera,\n start:start,\n client:janus,\n feeds:feeds\n }\n };\n\n var JanusPublish = function (dom,janusServer){\n var server = null;\n\n server = \"https:\u002F\u002F\"+janusServer+\":8089\u002Fjanus\";\n var audioDeviceId = null;\n var videoDeviceId = null;\n\n var janus = null;\n var sfutest = null;\n var opaqueId = \"orkestra-\"+Janus.randomString(12);\n\n var myroom = 1234;\t\u002F\u002F Demo room\n var myid = null;\n var mystream = null;\n \u002F\u002F We use this other ID just to map our subscriptions to us\n var mypvtid = null;\n var firstTime = true;\n var feeds = [];\n var doSimulcast = (getQueryStringValue(\"simulcast\") === \"yes\" || getQueryStringValue(\"simulcast\") === \"true\");\n var doSimulcast2 = (getQueryStringValue(\"simulcast2\") === \"yes\" || getQueryStringValue(\"simulcast2\") === \"true\");\n \u002F\u002F Initialize the library (all console debuggers enabled)\n function init (aid){\n Janus.init({debug: \"all\", callback: function() {\n \u002F\u002F Make sure the browser supports WebRTC\n if(!Janus.isWebrtcSupported()) {\n bootbox.alert(\"No WebRTC support... \");\n return;\n }\n \u002F\u002F Create session\n\n janus = new Janus(\n {\n server: server,\n success: function() {\n \u002F\u002F Attach to video room test plugin\n janus.attach(\n {\n plugin: \"janus.plugin.videoroom\",\n opaqueId: opaqueId,\n success: function(pluginHandle) {\n sfutest = pluginHandle;\n Janus.log(\"Plugin attached! (\" + sfutest.getPlugin() + \", id=\" + sfutest.getId() + \")\");\n Janus.log(\" -- This is a publisher\u002Fmanager\");\n \u002F\u002F Prepare the username registration\n var janusReady =new CustomEvent(\"JanusPublishReady\", {\n detail: {\n ready: true\n }\n });\n document.dispatchEvent(janusReady);\n },\n error: function(error) {\n Janus.error(\" -- Error attaching plugin...\", error);\n bootbox.alert(\"Error attaching plugin... \" + error);\n },\n consentDialog: function(on) {\n \u002F*\tJanus.debug(\"Consent dialog should be \" + (on ? \"on\" : \"off\") + \" now\");\n if(on) {\n \u002F\u002F Darken screen and show hint\n $.blockUI({\n message: '\u003Cdiv\u003E\u003Cimg src=\"up_arrow.png\"\u002F\u003E\u003C\u002Fdiv\u003E',\n css: {\n border: 'none',\n padding: '15px',\n backgroundColor: 'transparent',\n color: '#aaa',\n top: '10px',\n left: (navigator.mozGetUserMedia ? '-100px' : '300px')\n } });\n } else {\n \u002F\u002F Restore screen\n $.unblockUI();\n }*\u002F\n },\n mediaState: function(medium, on) {\n Janus.log(\"Janus \" + (on ? \"started\" : \"stopped\") + \" receiving our \" + medium);\n },\n webrtcState: function(on) {\n Janus.log(\"Janus says our WebRTC PeerConnection is \" + (on ? \"up\" : \"down\") + \" now\");\n \n },\n onmessage: function(msg, jsep) {\n Janus.debug(\" ::: Got a message (publisher) :::\");\n\n Janus.debug(msg);\n var event = msg[\"videoroom\"];\n Janus.debug(\"Event: \" + event);\n if(event != undefined && event != null) {\n if(event === \"joined\") {\n \u002F\u002F Publisher\u002Fmanager created, negotiate WebRTC and attach to existing feeds, if any\n myid = msg[\"id\"];\n mypvtid = msg[\"private_id\"];\n Janus.log(\"Successfully joined room \" + msg[\"room\"] + \" with ID \" + myid);\n \u002F\u002FpublishOwnFeed(true);\n \u002F\u002F Any new feed to attach to?\n if(msg[\"publishers\"] !== undefined && msg[\"publishers\"] !== null) {\n janus.list = msg[\"publishers\"];\n Janus.debug(\"Got a list of available publishers\u002Ffeeds:\");\n Janus.debug(janus.list);\n for(var f in janus.list) {\n var id = janus.list[f][\"id\"];\n var display = janus.list[f][\"display\"];\n var audio = janus.list[f][\"audio_codec\"];\n var video = janus.list[f][\"video_codec\"];\n Janus.debug(\" \u003E\u003E [\" + id + \"] \" + display + \" (audio: \" + audio + \", video: \" + video + \")\");\n \u002F\u002F\t\tnewRemoteFeed(id, display, audio, video);\n }\n }\n } else if(event === \"destroyed\") {\n \u002F\u002F The room has been destroyed\n Janus.warn(\"The room has been destroyed!\");\n bootbox.alert(\"The room has been destroyed\", function() {\n window.location.reload();\n });\n } else if(event === \"event\") {\n \u002F\u002F Any new feed to attach to?\n if(msg[\"publishers\"] !== undefined && msg[\"publishers\"] !== null) {\n janus.list = msg[\"publishers\"];\n Janus.debug(\"Got a list of available publishers\u002Ffeeds:\");\n Janus.debug(janus.list);\n for(var f in janus.list) {\n var id = janus.list[f][\"id\"];\n var display = janus.list[f][\"display\"];\n var audio = janus.list[f][\"audio_codec\"];\n var video = janus.list[f][\"video_codec\"];\n Janus.debug(\" \u003E\u003E [\" + id + \"] \" + display + \" (audio: \" + audio + \", video: \" + video + \")\");\n \u002F\u002F\t\tnewRemoteFeed(id, display, audio, video);\n }\n } else if(msg[\"leaving\"] !== undefined && msg[\"leaving\"] !== null) {\n \u002F\u002F One of the publishers has gone away?\n var leaving = msg[\"leaving\"];\n Janus.log(\"Publisher left: \" + leaving);\n var remoteFeed = null;\n for(var i=1; i\u003C6; i++) {\n if(feeds[i] != null && feeds[i] != undefined && feeds[i].rfid == leaving) {\n remoteFeed = feeds[i];\n break;\n }\n }\n if(remoteFeed != null) {\n Janus.debug(\"Feed \" + remoteFeed.rfid + \" (\" + remoteFeed.rfdisplay + \") has left the room, detaching\");\n dom.querySelector('#remote'+remoteFeed.rfindex).empty().hide();\n dom.querySelector('#videoremote'+remoteFeed.rfindex).empty();\n feeds[remoteFeed.rfindex] = null;\n remoteFeed.detach();\n }\n } else if(msg[\"unpublished\"] !== undefined && msg[\"unpublished\"] !== null) {\n \u002F\u002F One of the publishers has unpublished?\n var unpublished = msg[\"unpublished\"];\n Janus.log(\"Publisher left: \" + unpublished);\n \u002F*if(unpublished === 'ok') {\n \u002F\u002F That's us\n sfutest.hangup();\n return;\n }\n var remoteFeed = null;\n for(var i=1; i\u003C6; i++) {\n if(feeds[i] != null && feeds[i] != undefined && feeds[i].rfid == unpublished) {\n remoteFeed = feeds[i];\n break;\n }\n }\n if(remoteFeed != null) {\n Janus.debug(\"Feed \" + remoteFeed.rfid + \" (\" + remoteFeed.rfdisplay + \") has left the room, detaching\");\n dom.querySelector('#remote'+remoteFeed.rfindex).empty().hide();\n dom.querySelector('#videoremote'+remoteFeed.rfindex).empty();\n feeds[remoteFeed.rfindex] = null;\n remoteFeed.detach();\n }*\u002F\n } else if(msg[\"error\"] !== undefined && msg[\"error\"] !== null) {\n if(msg[\"error_code\"] === 426) {\n \u002F\u002F This is a \"no such room\" error: give a more meaningful description\n bootbox.alert(\n \"\u003Cp\u003EApparently room \u003Ccode\u003E\" + myroom + \"\u003C\u002Fcode\u003E (the one this demo uses as a test room) \" +\n \"does not exist...\u003C\u002Fp\u003E\u003Cp\u003EDo you have an updated \u003Ccode\u003Ejanus.plugin.videoroom.cfg\u003C\u002Fcode\u003E \" +\n \"configuration file? If not, make sure you copy the details of room \u003Ccode\u003E\" + myroom + \"\u003C\u002Fcode\u003E \" +\n \"from that sample in your current configuration file, then restart Janus and try again.\"\n );\n } else {\n bootbox.alert(msg[\"error\"]);\n }\n }\n }\n }\n if(jsep !== undefined && jsep !== null) {\n Janus.debug(\"Handling SDP as well...\");\n Janus.debug(jsep);\n sfutest.handleRemoteJsep({jsep: jsep});\n \u002F\u002F Check if any of the media we wanted to publish has\n \u002F\u002F been rejected (e.g., wrong or unsupported codec)\n var audio = msg[\"audio_codec\"];\n if(mystream && mystream.getAudioTracks() && mystream.getAudioTracks().length \u003E 0 && !audio) {\n \u002F\u002F Audio has been rejected\n toastr.warning(\"Our audio stream has been rejected, viewers won't hear us\");\n }\n var video = msg[\"video_codec\"];\n if(mystream && mystream.getVideoTracks() && mystream.getVideoTracks().length \u003E 0 && !video) {\n \u002F\u002F Video has been rejected\n toastr.warning(\"Our video stream has been rejected, viewers won't see us\");\n \u002F\u002F Hide the webcam video\n $('#myvideo').hide();\n $('#videolocal').append(\n '\u003Cdiv class=\"no-video-container\"\u003E' +\n '\u003Ci class=\"fa fa-video-camera fa-5 no-video-icon\" style=\"height: 100%;\"\u003E\u003C\u002Fi\u003E' +\n '\u003Cspan class=\"no-video-text\" style=\"font-size: 16px;\"\u003EVideo rejected, no webcam\u003C\u002Fspan\u003E' +\n '\u003C\u002Fdiv\u003E');\n }\n }\n },\n onlocalstream: function(stream) {\n Janus.debug(\" ::: Got a local stream :::\");\n mystream = stream;\n Janus.debug(stream);\n janus.list.push(stream);\n Janus.attachMediaStream(dom.querySelector('#myvideo'), stream);\n\n },\n onremotestream: function(stream) {\n \u002F\u002F The publisher stream is sendonly, we don't expect anything here\n },\n oncleanup: function() {\n Janus.log(\" ::: Got a cleanup notification: we are unpublished now :::\");\n mystream = null;\n \u002F\u002F$('#videolocal').html('\u003Cbutton id=\"publish\" class=\"btn btn-primary\"\u003EPublish\u003C\u002Fbutton\u003E');\n Janus.attachMediaStream(dom.querySelector('#myvideo'), mystream);\n }\n });\n },\n error: function(error) {\n Janus.error(error);\n bootbox.alert(error, function() {\n window.location.reload();\n });\n },\n destroyed: function() {\n window.location.reload();\n }\n });\n janus.list = [];\n setTimeout(()=\u003E{initDevices();registerUsername(aid);},1000);\n\n }});\n }\n function start(id){\n var feed_ = janus.list.find((f)=\u003E{if(f.display===id) return true;});\n newRemoteFeed(feed_.id,feed_.display,feed_.audio,feed_.video);\n }\n function stop(id){\n var unpublish = { \"request\": \"unpublish\" };\n sfutest.send({\"message\": unpublish});\n\n }\n function registerUsername(aid) {\n var register = { \"request\": \"join\", \"room\": myroom, \"ptype\": \"publisher\", \"display\":aid };\n let idx = setInterval(()=\u003E{\n if (sfutest){\n clearInterval(idx);\n sfutest.send({\"message\": register});\n publishOwnFeed(true);\n sfutest.unmuteAudio();\n }\n },50);\n }\n function changeCamera (id){\n var feed_ = janus.list.find((f)=\u003E{if(f.display===id) return true;});\n newRemoteFeed(feed_.id,feed_.display,feed_.audio,feed_.video);\n\n }\n function listDevices(callback){\n navigator.mediaDevices.getUserMedia({ audio: true, video: true })\n .then(function(stream) {\n navigator.mediaDevices.enumerateDevices().then(function(devices) {\n\n callback(devices);\n \u002F\u002F Get rid of the now useless stream\n try {\n var tracks = stream.getTracks();\n for(var i in tracks) {\n var mst = tracks[i];\n if(mst !== null && mst !== undefined)\n mst.stop();\n }\n } catch(e) {}\n });\n });\n }\n function initDevices() {\n\n listDevices((devices)=\u003E{\n\n if (dom.querySelector('#choose-device')) dom.querySelector('#choose-device').addEventListener('click',restartCapture);\n var audio = dom.querySelector('#audio-device').value;\n var video = dom.querySelector('#video-device').value;\n \u002F\u002F$('#audio-device, #video-device').find('option').remove();\n\n devices.forEach(function(device) {\n var label = device.label;\n if(label === null || label === undefined || label === \"\")\n label = device.deviceId;\n var option = '\u003Coption value=\"' + device.deviceId + '\"\u003E' + label + '\u003C\u002Foption\u003E';\n if(device.kind === 'audioinput') {\n dom.querySelector('#audio-device').innerHTML+=option;\n } else if(device.kind === 'videoinput') {\n dom.querySelector('#video-device').innerHTML+=option;\n } else if(device.kind === 'audiooutput') ;\n });\n });\n\n\n\n if(dom.querySelector('#change-devices')) dom.querySelector('#change-devices').addEventListener('click', (e)=\u003E {\n \u002F\u002F A different device has been selected: hangup the session, and set it up again\n \u002F\u002F$('#audio-device, #video-device').attr('disabled', true);\n \u002F\u002F$('#change-devices').attr('disabled', true);\n if(firstTime) {\n firstTime = false;\n restartCapture();\n return;\n }\n restartCapture();\n });\n }\n\n function changeDev(){\n if(firstTime) {\n firstTime = false;\n restartCapture();\n return;\n }\n restartCapture();\n }\n function restartCapture() {\n \u002F\u002F Negotiate WebRTC\n var body = { \"audio\": true, \"video\": true };\n Janus.debug(\"Sending message (\" + JSON.stringify(body) + \")\");\n sfutest.send({\"message\": body});\n Janus.debug(\"Trying a createOffer too (audio\u002Fvideo sendrecv)\");\n var replaceAudio = $('#audio-device').val() !== audioDeviceId;\n audioDeviceId = $('#audio-device').val();\n var replaceVideo = $('#video-device').val() !== videoDeviceId;\n videoDeviceId = $('#video-device').val();\n sfutest.createOffer(\n {\n \u002F\u002F We provide a specific device ID for both audio and video\n media: {\n audio: {\n deviceId: {\n exact: audioDeviceId\n }\n },\n replaceAudio: replaceAudio,\t\u002F\u002F This is only needed in case of a renegotiation\n video: {\n deviceId: {\n exact: videoDeviceId\n }\n },\n replaceVideo: replaceVideo,\t\u002F\u002F This is only needed in case of a renegotiation\n data: true\t\u002F\u002F Let's negotiate data channels as well\n },\n \u002F\u002F If you want to test simulcasting (Chrome and Firefox only), then\n \u002F\u002F pass a ?simulcast=true when opening this demo page: it will turn\n \u002F\u002F the following 'simulcast' property to pass to janus.js to true\n simulcast: doSimulcast,\n success: function(jsep) {\n Janus.debug(\"Got SDP!\");\n Janus.debug(jsep);\n sfutest.send({\"message\": body, \"jsep\": jsep});\n },\n error: function(error) {\n Janus.error(\"WebRTC error:\", error);\n bootbox.alert(\"WebRTC error... \" + JSON.stringify(error));\n }\n });\n \n }\n\n\n\n\n function publishOwnFeed(useAudio) {\n \u002F\u002F Publish our stream\n \u002F\u002F$('#publish').attr('disabled', true).unbind('click');\n sfutest.createOffer(\n {\n \u002F\u002F Add data:true here if you want to publish datachannels as well\n media: { audioRecv: false, videoRecv: false, audioSend: useAudio, videoSend: true },\t\u002F\u002F Publishers are sendonly\n \u002F\u002F If you want to test simulcasting (Chrome and Firefox only), then\n \u002F\u002F pass a ?simulcast=true when opening this demo page: it will turn\n \u002F\u002F the following 'simulcast' property to pass to janus.js to true\n simulcast: doSimulcast,\n simulcast2: doSimulcast2,\n success: function(jsep) {\n Janus.debug(\"Got publisher SDP!\");\n Janus.debug(jsep);\n var publish = { \"request\": \"configure\", \"audio\": useAudio, \"video\": true };\n \u002F\u002F You can force a specific codec to use when publishing by using the\n \u002F\u002F audiocodec and videocodec properties, for instance:\n \u002F\u002F \t\tpublish[\"audiocodec\"] = \"opus\"\n \u002F\u002F to force Opus as the audio codec to use, or:\n \u002F\u002F \t\tpublish[\"videocodec\"] = \"vp9\"\n \u002F\u002F to force VP9 as the videocodec to use. In both case, though, forcing\n \u002F\u002F a codec will only work if: (1) the codec is actually in the SDP (and\n \u002F\u002F so the browser supports it), and (2) the codec is in the list of\n \u002F\u002F allowed codecs in a room. With respect to the point (2) above,\n \u002F\u002F refer to the text in janus.plugin.videoroom.cfg for more details\n sfutest.send({\"message\": publish, \"jsep\": jsep});\n },\n error: function(error) {\n Janus.error(\"WebRTC error:\", error);\n if (useAudio) {\n publishOwnFeed(false);\n } else {\n bootbox.alert(\"WebRTC error... \" + JSON.stringify(error));\n $('#publish').removeAttr('disabled').click(function() { publishOwnFeed(true); });\n }\n }\n });\n }\n \u002F\u002F Helper to parse query string\n function getQueryStringValue(name) {\n name = name.replace(\u002F[\\[]\u002F, \"\\\\[\").replace(\u002F[\\]]\u002F, \"\\\\]\");\n var regex = new RegExp(\"[\\\\?&]\" + name + \"=([^&#]*)\"),\n results = regex.exec(location.search);\n return results === null ? \"\" : decodeURIComponent(results[1].replace(\u002F\\+\u002Fg, \" \"));\n }\n\n \n return {\n changeCamera:changeCamera,\n init:init,\n stop:stop,\n start:start,\n restartCapture:restartCapture,\n initDevices:initDevices,\n changeDev:changeDev\n }\n \n };\n\n class XMedia extends HTMLElement {\n static get observedAttributes () {\n return ['input', 'type',\"style\",\"config\"];\n }\n \n attributeChangedCallback (name, oldValue, newValue) {\n if (oldValue !== newValue) {\n if (name === 'type') {\n this.type = newValue;\n } else {\n this[name] = newValue;\n }\n }\n }\n\n get config () { return this.getAttribute('config'); }\n set config (value) {\n let old = this.getAttribute('config');\n if (old != value ){\n this.setAttribute('config', value);\n }\n let obj = JSON.parse(value);\n if (!window.FlexJanus && obj.janusServer){\n window.FlexJanus = JanusClient(this,obj.janusServer);\n FlexJanus.init(this);\n\n\n }\n if (obj.muted===\"true\"){\n this.muted = true;\n \n \n }\n else if (obj.muted===\"false\" ){\n this.muted = false;\n\n }\n if (obj.me){\n if (this.input === obj.me){\n if (this.querySelector('video')){\n this.muted = true;\n }\n }\n }\n }\n get input () { return this.getAttribute('input'); }\n set input (value) {\n let old = this.getAttribute('input');\n if (old != value ){\n this.setAttribute('input', value);\n let mediaType = this.identifiyMedia();\n this.render();\n if(mediaType==\"webrtc\" && this.janusReady) FlexJanus.start(this.input,this);\n else if (mediaType==\"webrtc\" && this.janusReady==undefined) {\n setTimeout(()=\u003E{FlexJanus.start(this.input,this);},5000);\n }\n\n }\n else if (this.querySelector('video') && this.querySelector('video').paused){\n let mediaType = this.identifiyMedia();\n if(mediaType==\"webrtc\" && this.janusReady) FlexJanus.start(this.input,this);\n }\n let obj = JSON.parse(this.getAttribute('config'));\n if (obj && obj.me){\n if (this.input === obj.me){\n if (this.querySelector('video')){\n this.querySelector('video').muted = true;\n }\n }\n }\n\n }\n\n get type () { return this.getAttribute('type'); }\n set type (value) {\n let old = this.getAttribute('type');\n if (old != value){\n this.setAttribute('type', value);\n this.render();\n }\n }\n\n constructor () {\n super();\n\n this.muted = true;\n\n document.addEventListener('JanusReady',this.janusReadyListener.bind(this));\n document.addEventListener('newStream',this.newStreamListener.bind(this));\n console.log(this.input,\"could not connect\");\n\n\n }\n janusReadyListener (e){\n this.janusReady = true;\n }\n newStreamListener (e){\n if (this.input === e.detail.id)\n setTimeout(()=\u003E{\n FlexJanus.start(this.input,this);\n },2000);\n\n }\n disconnectedCallback() {\n document.removeEventListener('JanusReady',this.janusReadyListener.bind(this));\n document.removeEventListener('newStream',this.newStreamListener.bind(this));\n console.log('disconnected from the DOM');\n }\n async connectedCallback () {\n if (this.hasAttribute('input')) {\n this.render();\n }\n if (this.hasAttribute('type')) {\n this.render();\n }\n\n }\n identifiyMedia(){\n if (this.input){\n let isURI = this.input.indexOf('\u002F') != -1;\n if (!isURI){\n return \"webrtc\";\n }\n else {\n\n let fileName = this.input.substring( this.input.lastIndexOf('\u002F')+1, this.input.length );\n let ext = fileName.substring(fileName.lastIndexOf('.'),this.input.length);\n if (ext === \"\") console.warn(\"Not extension found could not know media type\");\n switch (ext){\n case '.png':return \"image\";\n case '.jpg':return \"image\";\n case '.jpeg':return \"image\";\n case '.gif':return \"image\";\n case '.svg':return \"image\";\n case '.mp4':return \"video\";\n case '.ogv':return \"video\";\n case '.vp9':return \"video\";\n case '.vp8':return \"video\";\n case '.mpeg':return \"video\";\n case '.mpg':return \"video\";\n default:return \"unknown\";\n }\n }\n }\n else return undefined;\n }\n cleanInput(type){\n let v = this.querySelector('video');\n if (v) this.removeChild(v);\n let img = this.querySelector('img');\n if (img) this.removeChild(img);\n\n }\n\n render () {\n let mediaType = this.identifiyMedia();\n if (mediaType && mediaType !=\"unknown\"){\n switch (mediaType){\n case \"image\":{\n if (!this.querySelector('img')){\n this.cleanInput('video');\n let v = document.createElement(\"img\");\n v.id =\"video\";\n v.src=this.input;\n v.style = \"width:100%;height:100%\";\n this.appendChild(v);\n }\n break;\n }\n case \"video\":{\n let v = this.querySelector('video');\n if (!v){\n this.cleanInput('img');\n let v = document.createElement(\"video\");\n v.id =\"video\";\n v.muted = this.muted;\n v.controls = true;\n v.autoplay = true;\n v.style = \"width:100%;height:100%\";\n v.src = this.input;\n this.appendChild(v);\n\n }\n else v.src = this.input;\n break;\n }\n case \"webrtc\":{\n if (!this.querySelector('video')){\n this.cleanInput('img');\n let v = document.createElement(\"video\");\n v.id =\"video\";\n v.muted = this.muted;\n v.controls = true;\n v.autoplay = true;\n v.style = \"width:100%;height:100%\";\n this.appendChild(v);\n }\n }\n }\n\n }\n\n }\n\n\n }\n\n\n customElements.define('x-media', XMedia);\n\n exports.Byname = Byname;\n exports.Bytimeline = Bytimeline;\n exports.Divided = Divided;\n exports.JanusClient = JanusClient;\n exports.JanusPublish = JanusPublish;\n exports.KeyPress = KeyPress;\n exports.ObjectSync = ObjectSync;\n exports.Orkestra = Orkestra;\n exports.TextData = TextData;\n exports.UI = UI;\n exports.URI = URI;\n exports.UserData = UserData;\n exports.WCAppTable = WCAppTable;\n exports.WCUserTable = WCUserTable;\n exports.XMedia = XMedia;\n\n Object.defineProperty(exports, '__esModule', { value: true });\n\n})));","id":"mod_9adTbSfBCW1iRJ7hLAsyck","is_binary":false,"title":"orkestra.umd.js","sha":null,"inserted_at":"2020-11-12T08:45:40","updated_at":"2020-11-12T08:45:44","upload_id":null,"shortid":"jYp4l","source_id":"src_XPxRh8dLpfjawjbf3ha9Lw","directory_shortid":null},{"code":"\u003C!DOCTYPE html\u003E\n\u003Chtml lang=\"en\"\u003E\n \u003Chead\u003E\n \u003Cmeta charset=\"UTF-8\" \u002F\u003E\n \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \u002F\u003E\n \u003Cmeta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\" \u002F\u003E\n \u003Ctitle\u003EStatic Template\u003C\u002Ftitle\u003E\n \u003Cscript src=\"orkestra.umd.js\"\u003E\u003C\u002Fscript\u003E\n \u003C\u002Fhead\u003E\n \u003Cbody\u003E\n \u003Cdiv id=\"me\"\u003E\n Me:\n \u003C\u002Fdiv\u003E\n \u003Cdiv id=\"other\"\u003E\n Other user:\n \u003C\u002Fdiv\u003E\n \u003Cscript\u003E\n const ork = new OrkestraAPI.Orkestra({\n url: \"https:\u002F\u002Fcloud.flexcontrol.net\u002F\",\n channel: \"test43\",\n privateChannel: false,\n profile: \"viewer\",\n master: true\n });\n ork.readyObservable.subscribe((e) =\u003E {\n ork.data(\"keyPress\", KeyPress, []);\n });\n ork.userObservable.subscribe((e) =\u003E {\n if (e.data && e.data.key == \"keyPress\")\n if (e.data.agentid === ork.getMyAgentId())\n document.querySelector(\"#me\").innerHTML +=\n e.data.value == \"undefined\" ? \"\" : e.data.value;\n else\n document.querySelector(\"#other\").innerHTML +=\n e.data.value == \"undefined\" ? \"\" : e.data.value;\n });\n\n function KeyPress() {\n let ms = {\n init: function () {\n this.setCapability(\"keyPress\", \"supported\");\n },\n on: function () {\n document.addEventListener(\"keypress\", (txt) =\u003E {\n this.setItem(\"keyPress\", txt.key);\n });\n },\n off: function () {\n document.removeEventListener(\"keypress\", (txt) =\u003E {\n this.setItem(\"keyPress\", txt.key);\n });\n }\n };\n return { keyPress: ms };\n }\n \u003C\u002Fscript\u003E\n \u003C\u002Fbody\u003E\n\u003C\u002Fhtml\u003E\n","id":"mod_MqaG245uQAXky1tSFoTStg","is_binary":false,"title":"index.html","sha":null,"inserted_at":"2020-11-12T08:38:30","updated_at":"2020-11-12T10:33:25","upload_id":null,"shortid":"SynEipfsL","source_id":"src_XPxRh8dLpfjawjbf3ha9Lw","directory_shortid":null}],"team":null,"pr_number":null,"screenshot_url":"https:\u002F\u002Fscreenshots.codesandbox.io\u002Fyhiht\u002F61.png","npm_dependencies":{},"like_count":0,"title":null,"is_sse":false,"updated_at":"2020-11-12T10:33:25","description":null,"forked_template":{"id":"sbtempl_6QCSKG78owS1SfMb3dmFAa","title":"HTML + CSS","v2":false,"color":"#3AA855","url":null,"published":false,"sdk":false,"icon_url":"github","official":false},"preview_secret":null,"forked_template_sandbox":{"alias":"magical-scott-rjk9n4zj7m","id":"rjk9n4zj7m","title":"HTML + CSS","template":"static","inserted_at":"2018-10-31T15:16:47","updated_at":"2024-02-01T09:06:43","git":{"path":"","branch":"master","repo":"static-template","username":"codesandbox-app","commit_sha":"1d81ddb8e9033a7e70fcad13cfd721e43b86d6a3"},"privacy":0,"sdk":false,"custom_template":{"id":"sbtempl_6QCSKG78owS1SfMb3dmFAa","title":"HTML + CSS","v2":false,"color":"#3AA855","url":null,"published":false,"sdk":false,"icon_url":"github","official":false}},"picks":[],"alias":"recursing-newton-yhiht","npm_registries":[],"restricted":false,"owned":false,"template":"static","base_git":null,"version":61,"sdk":false,"always_on":false,"id":"yhiht","room_id":null,"ai_consent":false,"draft":true,"fork_count":2,"permissions":{"prevent_sandbox_export":false,"prevent_sandbox_leaving":false},"settings":{"ai_consent":null,"use_pint":false},"is_frozen":false,"original_git":null,"custom_template":null,"restrictions":{"free_plan_editing_restricted":false,"live_sessions_restricted":true},"forked_from_sandbox":null,"inserted_at":"2020-11-12T08:38:30","privacy":0,"tags":[],"v2":false,"collection":false,"authorization":"read"};