import Iterator from 'es6-iterator'; import BackgroundTimer from 'react-native-background-timer'; import 'url-polyfill'; // Polyfill for URL constructor /** * Gets the first common prototype of two specified Objects (treating the * objects themselves as prototypes as well). * * @param {Object} a - The first prototype chain to climb in search of a common * prototype. * @param {Object} b - The second prototype chain to climb in search of a common * prototype. * @returns {Object|undefined} - The first common prototype of a and b. */ function _getCommonPrototype(a, b) { // Allow the arguments to be prototypes themselves. if (a === b) { return a; } let p; if ((p = Object.getPrototypeOf(a)) && (p = _getCommonPrototype(b, p))) { return p; } if ((p = Object.getPrototypeOf(b)) && (p = _getCommonPrototype(a, p))) { return p; } return undefined; } /** * Implements an absolute minimum of the common logic of Document.querySelector * and Element.querySelector. Implements the most simple of selectors necessary * to satisfy the call sites at the time of this writing i.e. select by tagName. * * @param {Node} node - The Node which is the root of the tree to query. * @param {string} selectors - The group of CSS selectors to match on. * @returns {Element} - The first Element which is a descendant of the specified * node and matches the specified group of selectors. */ function _querySelector(node, selectors) { let element = null; node && _visitNode(node, n => { if (n.nodeType === 1 /* ELEMENT_NODE */ && n.nodeName === selectors) { element = n; return true; } return false; }); return element; } /** * Visits each Node in the tree of a specific root Node (using depth-first * traversal) and invokes a specific callback until the callback returns true. * * @param {Node} node - The root Node which represents the tree of Nodes to * visit. * @param {Function} callback - The callback to invoke with each visited Node. * @returns {boolean} - True if the specified callback returned true for a Node * (at which point the visiting stopped); otherwise, false. */ function _visitNode(node, callback) { if (callback(node)) { return true; } /* eslint-disable no-param-reassign, no-extra-parens */ if ((node = node.firstChild)) { do { if (_visitNode(node, callback)) { return true; } } while ((node = node.nextSibling)); } /* eslint-enable no-param-reassign, no-extra-parens */ return false; } (global => { const DOMParser = require('xmldom').DOMParser; // addEventListener // // Required by: // - jQuery if (typeof global.addEventListener === 'undefined') { // eslint-disable-next-line no-empty-function global.addEventListener = () => {}; } // Array.prototype[@@iterator] // // Required by: // - for...of statement use(s) in lib-jitsi-meet const arrayPrototype = Array.prototype; if (typeof arrayPrototype['@@iterator'] === 'undefined') { arrayPrototype['@@iterator'] = function() { return new Iterator(this); }; } // document // // Required by: // - jQuery // - lib-jitsi-meet/modules/RTC/adapter.screenshare.js // - Strophe if (typeof global.document === 'undefined') { const document = new DOMParser().parseFromString( '', 'text/xml'); // document.addEventListener // // Required by: // - jQuery if (typeof document.addEventListener === 'undefined') { // eslint-disable-next-line no-empty-function document.addEventListener = () => {}; } // Document.querySelector // // Required by: // - strophejs-plugins/caps/strophe.caps.jsonly.js const documentPrototype = Object.getPrototypeOf(document); if (documentPrototype) { if (typeof documentPrototype.querySelector === 'undefined') { documentPrototype.querySelector = function(selectors) { return _querySelector(this.elementNode, selectors); }; } } // Element.querySelector // // Required by: // - strophejs-plugins/caps/strophe.caps.jsonly.js const elementPrototype = Object.getPrototypeOf(document.documentElement); if (elementPrototype) { if (typeof elementPrototype.querySelector === 'undefined') { elementPrototype.querySelector = function(selectors) { return _querySelector(this, selectors); }; } // Element.innerHTML // // Required by: // - jQuery's .append method if (!elementPrototype.hasOwnProperty('innerHTML')) { Object.defineProperty(elementPrototype, 'innerHTML', { get() { return this.childNodes.toString(); }, set(innerHTML) { // MDN says: removes all of element's children, parses // the content string and assigns the resulting nodes as // children of the element. // Remove all of element's children. this.textContent = ''; // Parse the content string. const d = new DOMParser().parseFromString( `
${innerHTML}
`, 'text/xml'); // Assign the resulting nodes as children of the // element. const documentElement = d.documentElement; let child; // eslint-disable-next-line no-cond-assign while (child = documentElement.firstChild) { this.appendChild(child); } } }); } } // FIXME There is a weird infinite loop related to console.log and // Document and/or Element at the time of this writing. Work around it // by patching Node and/or overriding console.log. const nodePrototype = _getCommonPrototype(documentPrototype, elementPrototype); if (nodePrototype // XXX The intention was to find Node from which Document and // Element extend. If for whatever reason we've reached Object, // then it doesn't sound like what expected. && nodePrototype !== Object.getPrototypeOf({})) { // Override console.log. const console = global.console; if (console) { const loggerLevels = require('jitsi-meet-logger').levels; Object.keys(loggerLevels).forEach(key => { const level = loggerLevels[key]; const consoleLog = console[level]; /* eslint-disable prefer-rest-params */ if (typeof consoleLog === 'function') { console[level] = function(...args) { const length = args.length; for (let i = 0; i < length; ++i) { let arg = args[i]; if (arg && typeof arg !== 'string' // Limit the console.log override to // Node (instances). && nodePrototype.isPrototypeOf(arg)) { const toString = arg.toString; if (toString) { arg = toString.call(arg); } } args[i] = arg; } consoleLog.apply(this, args); }; } /* eslint-enable prefer-rest-params */ }); } } global.document = document; } // location if (typeof global.location === 'undefined') { global.location = { href: '' }; } const { navigator } = global; if (navigator) { // platform // // Required by: // - lib-jitsi-meet/modules/RTC/adapter.screenshare.js if (typeof navigator.platform === 'undefined') { navigator.platform = ''; } // plugins // // Required by: // - lib-jitsi-meet/modules/RTC/adapter.screenshare.js if (typeof navigator.plugins === 'undefined') { navigator.plugins = []; } // userAgent // // Required by: // - lib-jitsi-meet/modules/RTC/adapter.screenshare.js // - lib-jitsi-meet/modules/RTC/RTCBrowserType.js (() => { const reactNativePackageJSON = require('react-native/package.json'); let userAgent = reactNativePackageJSON.name || 'react-native'; const version = reactNativePackageJSON.version; if (version) { userAgent += `/${version}`; } if (typeof navigator.userAgent !== 'undefined') { const s = navigator.userAgent.toString(); if (s.length > 0 && s.indexOf(userAgent) === -1) { userAgent = `${s} ${userAgent}`; } } navigator.userAgent = userAgent; })(); } // performance if (typeof global.performance === 'undefined') { global.performance = { now() { return 0; } }; } // sessionStorage // // Required by: // - Strophe if (typeof global.sessionStorage === 'undefined') { global.sessionStorage = { /* eslint-disable no-empty-function */ getItem() {}, removeItem() {}, setItem() {} /* eslint-enable no-empty-function */ }; } // WebRTC require('./polyfills-webrtc'); // XMLHttpRequest if (global.XMLHttpRequest) { const prototype = global.XMLHttpRequest.prototype; // XMLHttpRequest.responseXML // // Required by: // - Strophe if (prototype && !prototype.hasOwnProperty('responseXML')) { Object.defineProperty(prototype, 'responseXML', { get() { const responseText = this.responseText; let responseXML; if (responseText) { responseXML = new DOMParser().parseFromString( responseText, 'text/xml'); } return responseXML; } }); } } // Timers // // React Native's timers won't run while the app is in the background, this // is a known limitation. Replace them with a background-friendly // alternative. // // Required by: // - lib-jitsi-meet // - Strophe global.clearTimeout = window.clearTimeout = BackgroundTimer.clearTimeout.bind(BackgroundTimer); global.clearInterval = window.clearInterval = BackgroundTimer.clearInterval.bind(BackgroundTimer); global.setInterval = window.setInterval = BackgroundTimer.setInterval.bind(BackgroundTimer); global.setTimeout = window.setTimeout = BackgroundTimer.setTimeout.bind(BackgroundTimer); })(global || window || this); // eslint-disable-line no-invalid-this