This commit is contained in:
19
react/features/mobile/polyfills/RTCPeerConnection.js
Normal file
19
react/features/mobile/polyfills/RTCPeerConnection.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import { RTCPeerConnection as PC } from 'react-native-webrtc';
|
||||
|
||||
import { synthesizeIPv6Addresses } from './ipv6utils';
|
||||
|
||||
/**
|
||||
* Override PeerConnection to synthesize IPv6 addresses.
|
||||
*/
|
||||
export default class RTCPeerConnection extends PC {
|
||||
|
||||
/**
|
||||
* Synthesize IPv6 addresses before calling the underlying setRemoteDescription.
|
||||
*
|
||||
* @param {Object} description - SDP.
|
||||
* @returns {Promise<undefined>} A promise which is resolved once the operation is complete.
|
||||
*/
|
||||
async setRemoteDescription(description) {
|
||||
return super.setRemoteDescription(await synthesizeIPv6Addresses(description));
|
||||
}
|
||||
}
|
||||
192
react/features/mobile/polyfills/Storage.js
Normal file
192
react/features/mobile/polyfills/Storage.js
Normal file
@@ -0,0 +1,192 @@
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
|
||||
/**
|
||||
* A Web Sorage API implementation used for polyfilling
|
||||
* {@code window.localStorage} and/or {@code window.sessionStorage}.
|
||||
* <p>
|
||||
* The Web Storage API is synchronous whereas React Native's builtin generic
|
||||
* storage API {@code AsyncStorage} is asynchronous so the implementation with
|
||||
* persistence is optimistic: it will first store the value locally in memory so
|
||||
* that results can be served synchronously and then persist the value
|
||||
* asynchronously. If an asynchronous operation produces an error, it's ignored.
|
||||
*/
|
||||
export default class Storage {
|
||||
/**
|
||||
* Initializes a new {@code Storage} instance. Loads all previously
|
||||
* persisted data items from React Native's {@code AsyncStorage} if
|
||||
* necessary.
|
||||
*
|
||||
* @param {string|undefined} keyPrefix - The prefix of the
|
||||
* {@code AsyncStorage} keys to be persisted by this storage.
|
||||
*/
|
||||
constructor(keyPrefix) {
|
||||
/**
|
||||
* The prefix of the {@code AsyncStorage} keys persisted by this
|
||||
* storage. If {@code undefined}, then the data items stored in this
|
||||
* storage will not be persisted.
|
||||
*
|
||||
* @private
|
||||
* @type {string}
|
||||
*/
|
||||
this._keyPrefix = keyPrefix;
|
||||
|
||||
// Perform optional asynchronous initialization.
|
||||
const initializing = this._initializeAsync();
|
||||
|
||||
if (initializing) {
|
||||
// Indicate that asynchronous initialization is under way.
|
||||
this._initializing = initializing;
|
||||
|
||||
// When the asynchronous initialization completes, indicate its
|
||||
// completion.
|
||||
initializing.finally(() => {
|
||||
if (this._initializing === initializing) {
|
||||
this._initializing = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all keys from this storage.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
clear() {
|
||||
for (const key of Object.keys(this)) {
|
||||
this.removeItem(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value associated with a specific key in this storage.
|
||||
*
|
||||
* @param {string} key - The name of the key to retrieve the value of.
|
||||
* @returns {string|null} The value associated with {@code key} or
|
||||
* {@code null}.
|
||||
*/
|
||||
getItem(key) {
|
||||
return this.hasOwnProperty(key) ? this[key] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value associated with a specific key in this {@code Storage}
|
||||
* in an async manner. The method is required for the cases where we need
|
||||
* the stored data but we're not sure yet whether this {@code Storage} is
|
||||
* already initialized (e.g. On app start).
|
||||
*
|
||||
* @param {string} key - The name of the key to retrieve the value of.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_getItemAsync(key) {
|
||||
return (
|
||||
(this._initializing || Promise.resolve())
|
||||
.catch(() => { /* _getItemAsync should always resolve! */ })
|
||||
.then(() => this.getItem(key)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs asynchronous initialization of this {@code Storage} instance
|
||||
* such as loading all keys from {@link AsyncStorage}.
|
||||
*
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_initializeAsync() {
|
||||
if (typeof this._keyPrefix !== 'undefined') {
|
||||
// Load all previously persisted data items from React Native's
|
||||
// AsyncStorage.
|
||||
|
||||
return new Promise(resolve => {
|
||||
AsyncStorage.getAllKeys().then((...getAllKeysCallbackArgs) => {
|
||||
// XXX The keys argument of getAllKeys' callback may or may
|
||||
// not be preceded by an error argument.
|
||||
const keys
|
||||
= getAllKeysCallbackArgs[
|
||||
getAllKeysCallbackArgs.length - 1
|
||||
].filter(key => key.startsWith(this._keyPrefix));
|
||||
|
||||
AsyncStorage.multiGet(keys)
|
||||
.then((...multiGetCallbackArgs) => {
|
||||
// XXX The result argument of multiGet may or may not be
|
||||
// preceded by an errors argument.
|
||||
const result
|
||||
= multiGetCallbackArgs[
|
||||
multiGetCallbackArgs.length - 1
|
||||
];
|
||||
const keyPrefixLength
|
||||
= this._keyPrefix && this._keyPrefix.length;
|
||||
|
||||
// eslint-disable-next-line prefer-const
|
||||
for (let [ key, value ] of result) {
|
||||
key = key.substring(keyPrefixLength);
|
||||
|
||||
// XXX The loading of the previously persisted data
|
||||
// items from AsyncStorage is asynchronous which
|
||||
// means that it is technically possible to invoke
|
||||
// setItem with a key before the key is loaded from
|
||||
// AsyncStorage.
|
||||
if (!this.hasOwnProperty(key)) {
|
||||
this[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the nth key in this storage.
|
||||
*
|
||||
* @param {number} n - The zero-based integer index of the key to get the
|
||||
* name of.
|
||||
* @returns {string} The name of the nth key in this storage.
|
||||
*/
|
||||
key(n) {
|
||||
const keys = Object.keys(this);
|
||||
|
||||
return n < keys.length ? keys[n] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an integer representing the number of data items stored in this
|
||||
* storage.
|
||||
*
|
||||
* @returns {number}
|
||||
*/
|
||||
get length() {
|
||||
return Object.keys(this).length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a specific key from this storage.
|
||||
*
|
||||
* @param {string} key - The name of the key to remove.
|
||||
* @returns {void}
|
||||
*/
|
||||
removeItem(key) {
|
||||
delete this[key];
|
||||
typeof this._keyPrefix === 'undefined'
|
||||
|| AsyncStorage.removeItem(`${String(this._keyPrefix)}${key}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a specific key to this storage and associates it with a specific
|
||||
* value. If the key exists already, updates its value.
|
||||
*
|
||||
* @param {string} key - The name of the key to add/update.
|
||||
* @param {string} value - The value to associate with {@code key}.
|
||||
* @returns {void}
|
||||
*/
|
||||
setItem(key, value) {
|
||||
value = String(value); // eslint-disable-line no-param-reassign
|
||||
this[key] = value;
|
||||
typeof this._keyPrefix === 'undefined'
|
||||
|| AsyncStorage.setItem(`${String(this._keyPrefix)}${key}`, value);
|
||||
}
|
||||
}
|
||||
341
react/features/mobile/polyfills/browser.js
Normal file
341
react/features/mobile/polyfills/browser.js
Normal file
@@ -0,0 +1,341 @@
|
||||
import { DOMParser } from '@xmldom/xmldom';
|
||||
import { atob, btoa } from 'abab';
|
||||
import { NativeModules, Platform } from 'react-native';
|
||||
import BackgroundTimer from 'react-native-background-timer';
|
||||
import { TextDecoder, TextEncoder } from 'text-encoding';
|
||||
|
||||
import 'promise.withresolvers/auto'; // Promise.withResolvers.
|
||||
import 'react-native-url-polyfill/auto'; // Complete URL polyfill.
|
||||
|
||||
import Storage from './Storage';
|
||||
|
||||
const { AppInfo } = NativeModules;
|
||||
|
||||
/**
|
||||
* Implements an absolute minimum of the common logic of
|
||||
* {@code Document.querySelector} and {@code 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 => {
|
||||
// DOMParser
|
||||
//
|
||||
// Required by:
|
||||
// - lib-jitsi-meet requires this if using WebSockets
|
||||
global.DOMParser = DOMParser;
|
||||
|
||||
// addEventListener
|
||||
//
|
||||
// Required by:
|
||||
// - jQuery
|
||||
if (typeof global.addEventListener === 'undefined') {
|
||||
// eslint-disable-next-line no-empty-function
|
||||
global.addEventListener = () => {};
|
||||
}
|
||||
|
||||
// removeEventListener
|
||||
//
|
||||
// Required by:
|
||||
// - features/base/conference/middleware
|
||||
if (typeof global.removeEventListener === 'undefined') {
|
||||
// eslint-disable-next-line no-empty-function
|
||||
global.removeEventListener = () => {};
|
||||
}
|
||||
|
||||
// document
|
||||
//
|
||||
// Required by:
|
||||
// - jQuery
|
||||
// - Strophe
|
||||
if (typeof global.document === 'undefined') {
|
||||
const document
|
||||
= new DOMParser().parseFromString(
|
||||
'<html><head></head><body></body></html>',
|
||||
'text/xml');
|
||||
|
||||
// document.addEventListener
|
||||
//
|
||||
// Required by:
|
||||
// - jQuery
|
||||
if (typeof document.addEventListener === 'undefined') {
|
||||
// eslint-disable-next-line no-empty-function
|
||||
document.addEventListener = () => {};
|
||||
}
|
||||
|
||||
// document.cookie
|
||||
//
|
||||
// Required by:
|
||||
// - herment
|
||||
if (typeof document.cookie === 'undefined') {
|
||||
document.cookie = '';
|
||||
}
|
||||
|
||||
// document.implementation.createHTMLDocument
|
||||
//
|
||||
// Required by:
|
||||
// - jQuery
|
||||
if (typeof document.implementation.createHTMLDocument === 'undefined') {
|
||||
document.implementation.createHTMLDocument = function(title = '') {
|
||||
const htmlDocument
|
||||
= new DOMParser().parseFromString(
|
||||
`<html>
|
||||
<head><title>${title}</title></head>
|
||||
<body></body>
|
||||
</html>`,
|
||||
'text/xml');
|
||||
|
||||
Object.defineProperty(htmlDocument, 'body', {
|
||||
get() {
|
||||
return htmlDocument.getElementsByTagName('body')[0];
|
||||
}
|
||||
});
|
||||
|
||||
return htmlDocument;
|
||||
};
|
||||
}
|
||||
|
||||
// Element.querySelector
|
||||
//
|
||||
// Required by:
|
||||
// - lib-jitsi-meet/modules/xmpp
|
||||
const elementPrototype
|
||||
= Object.getPrototypeOf(document.documentElement);
|
||||
|
||||
if (elementPrototype) {
|
||||
if (typeof elementPrototype.querySelector === 'undefined') {
|
||||
elementPrototype.querySelector = function(selectors) {
|
||||
return _querySelector(this, selectors);
|
||||
};
|
||||
}
|
||||
|
||||
// Element.remove
|
||||
//
|
||||
// Required by:
|
||||
// - lib-jitsi-meet ChatRoom#onPresence parsing
|
||||
if (typeof elementPrototype.remove === 'undefined') {
|
||||
elementPrototype.remove = function() {
|
||||
if (this.parentNode !== null) {
|
||||
this.parentNode.removeChild(this);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 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(
|
||||
`<div>${innerHTML}</div>`,
|
||||
'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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Element.children
|
||||
//
|
||||
// Required by:
|
||||
// - lib-jitsi-meet ChatRoom#onPresence parsing
|
||||
if (!elementPrototype.hasOwnProperty('children')) {
|
||||
Object.defineProperty(elementPrototype, 'children', {
|
||||
get() {
|
||||
const nodes = this.childNodes;
|
||||
const children = [];
|
||||
let i = 0;
|
||||
let node = nodes[i];
|
||||
|
||||
while (node) {
|
||||
if (node.nodeType === 1) {
|
||||
children.push(node);
|
||||
}
|
||||
i += 1;
|
||||
node = nodes[i];
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
global.document = document;
|
||||
}
|
||||
|
||||
// location
|
||||
if (typeof global.location === 'undefined') {
|
||||
global.location = {
|
||||
href: '',
|
||||
|
||||
// Required by:
|
||||
// - lib-jitsi-meet/modules/xmpp/xmpp.js
|
||||
search: ''
|
||||
};
|
||||
}
|
||||
|
||||
const { navigator } = global;
|
||||
|
||||
if (navigator) {
|
||||
// userAgent
|
||||
//
|
||||
// Required by:
|
||||
// - lib-jitsi-meet/modules/browser/BrowserDetection.js
|
||||
|
||||
// React Native version
|
||||
const { reactNativeVersion } = Platform.constants;
|
||||
const rnVersion
|
||||
= `react-native/${reactNativeVersion.major}.${reactNativeVersion.minor}.${reactNativeVersion.patch}`;
|
||||
|
||||
// (OS version)
|
||||
const os = `${Platform.OS.toLowerCase()}/${Platform.Version}`;
|
||||
|
||||
// SDK
|
||||
const liteTxt = AppInfo.isLiteSDK ? '-lite' : '';
|
||||
const sdkVersion = `JitsiMeetSDK/${AppInfo.sdkVersion}${liteTxt}`;
|
||||
|
||||
const parts = [
|
||||
navigator.userAgent ?? '',
|
||||
sdkVersion,
|
||||
os,
|
||||
rnVersion
|
||||
];
|
||||
|
||||
navigator.userAgent = parts.filter(Boolean).join(' ');
|
||||
}
|
||||
|
||||
// WebRTC
|
||||
require('./webrtc');
|
||||
|
||||
// Performance API
|
||||
|
||||
// RN only provides the now() method, since the polyfill refers the global
|
||||
// performance object itself we extract it here to avoid infinite recursion.
|
||||
const performanceNow = global.performance.now;
|
||||
|
||||
const perf = require('react-native-performance');
|
||||
|
||||
global.performance = perf.default;
|
||||
global.performance.now = performanceNow;
|
||||
global.PerformanceObserver = perf.PerformanceObserver;
|
||||
|
||||
// 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.
|
||||
if (Platform.OS === 'android') {
|
||||
global.clearTimeout = BackgroundTimer.clearTimeout.bind(BackgroundTimer);
|
||||
global.clearInterval = BackgroundTimer.clearInterval.bind(BackgroundTimer);
|
||||
global.setInterval = BackgroundTimer.setInterval.bind(BackgroundTimer);
|
||||
global.setTimeout = (fn, ms = 0) => BackgroundTimer.setTimeout(fn, ms);
|
||||
}
|
||||
|
||||
// localStorage
|
||||
if (typeof global.localStorage === 'undefined') {
|
||||
global.localStorage = new Storage('@jitsi-meet/');
|
||||
}
|
||||
|
||||
// sessionStorage
|
||||
//
|
||||
// Required by:
|
||||
// - herment
|
||||
// - Strophe
|
||||
if (typeof global.sessionStorage === 'undefined') {
|
||||
global.sessionStorage = new Storage();
|
||||
}
|
||||
|
||||
global.TextDecoder = TextDecoder;
|
||||
global.TextEncoder = TextEncoder;
|
||||
|
||||
// atob
|
||||
//
|
||||
// Required by:
|
||||
// - Strophe
|
||||
if (typeof global.atob === 'undefined') {
|
||||
global.atob = atob;
|
||||
}
|
||||
|
||||
// btoa
|
||||
//
|
||||
// Required by:
|
||||
// - Strophe
|
||||
if (typeof global.btoa === 'undefined') {
|
||||
global.btoa = btoa;
|
||||
}
|
||||
|
||||
})(global || window || this); // eslint-disable-line no-invalid-this
|
||||
4
react/features/mobile/polyfills/custom.js
Normal file
4
react/features/mobile/polyfills/custom.js
Normal file
@@ -0,0 +1,4 @@
|
||||
import { NativeModules } from 'react-native';
|
||||
|
||||
|
||||
global.JITSI_MEET_LITE_SDK = Boolean(NativeModules.AppInfo.isLiteSDK);
|
||||
2
react/features/mobile/polyfills/index.js
Normal file
2
react/features/mobile/polyfills/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import './browser';
|
||||
import './custom';
|
||||
197
react/features/mobile/polyfills/ipv6utils.js
Normal file
197
react/features/mobile/polyfills/ipv6utils.js
Normal file
@@ -0,0 +1,197 @@
|
||||
import { NativeModules } from 'react-native';
|
||||
import { RTCSessionDescription } from 'react-native-webrtc';
|
||||
|
||||
/**
|
||||
* Synthesizes IPv6 addresses on iOS in order to support IPv6 NAT64 networks.
|
||||
*
|
||||
* @param {RTCSessionDescription} sdp - The RTCSessionDescription which
|
||||
* specifies the configuration of the remote end of the connection.
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function synthesizeIPv6Addresses(sdp) {
|
||||
return (
|
||||
new Promise(resolve => resolve(_synthesizeIPv6Addresses0(sdp)))
|
||||
.then(({ ips, lines }) =>
|
||||
Promise.all(Array.from(ips.values()))
|
||||
.then(() => _synthesizeIPv6Addresses1(sdp, ips, lines))
|
||||
));
|
||||
}
|
||||
|
||||
/* eslint-disable max-depth */
|
||||
|
||||
/**
|
||||
* Synthesizes an IPv6 address from a specific IPv4 address.
|
||||
*
|
||||
* @param {string} ipv4 - The IPv4 address from which an IPv6 address is to be
|
||||
* synthesized.
|
||||
* @returns {Promise<?string>} A {@code Promise} which gets resolved with the
|
||||
* IPv6 address synthesized from the specified {@code ipv4} or a falsy value to
|
||||
* be treated as inability to synthesize an IPv6 address from the specified
|
||||
* {@code ipv4}.
|
||||
*/
|
||||
const _synthesizeIPv6FromIPv4Address = (function() {
|
||||
// POSIX.getaddrinfo
|
||||
const { POSIX } = NativeModules;
|
||||
|
||||
if (POSIX) {
|
||||
const { getaddrinfo } = POSIX;
|
||||
|
||||
if (typeof getaddrinfo === 'function') {
|
||||
return ipv4 =>
|
||||
getaddrinfo(/* hostname */ ipv4, /* servname */ undefined)
|
||||
.then(([ { ai_addr: ipv6 } ]) => ipv6);
|
||||
}
|
||||
}
|
||||
|
||||
// NAT64AddrInfo.getIPv6Address
|
||||
const { NAT64AddrInfo } = NativeModules;
|
||||
|
||||
if (NAT64AddrInfo) {
|
||||
const { getIPv6Address } = NAT64AddrInfo;
|
||||
|
||||
if (typeof getIPv6Address === 'function') {
|
||||
return getIPv6Address;
|
||||
}
|
||||
}
|
||||
|
||||
// There's no POSIX.getaddrinfo or NAT64AddrInfo.getIPv6Address.
|
||||
return ip => Promise.resolve(ip);
|
||||
})();
|
||||
|
||||
/**
|
||||
* Begins the asynchronous synthesis of IPv6 addresses.
|
||||
*
|
||||
* @param {RTCSessionDescription} sessionDescription - The RTCSessionDescription
|
||||
* for which IPv6 addresses will be synthesized.
|
||||
* @private
|
||||
* @returns {{
|
||||
* ips: Map,
|
||||
* lines: Array
|
||||
* }}
|
||||
*/
|
||||
function _synthesizeIPv6Addresses0(sessionDescription) {
|
||||
const sdp = sessionDescription.sdp;
|
||||
let start = 0;
|
||||
const lines = [];
|
||||
const ips = new Map();
|
||||
|
||||
do {
|
||||
const end = sdp.indexOf('\r\n', start);
|
||||
let line;
|
||||
|
||||
if (end === -1) {
|
||||
line = sdp.substring(start);
|
||||
|
||||
// Break out of the loop at the end of the iteration.
|
||||
start = undefined;
|
||||
} else {
|
||||
line = sdp.substring(start, end);
|
||||
start = end + 2;
|
||||
}
|
||||
|
||||
if (line.startsWith('a=candidate:')) {
|
||||
const candidate = line.split(' ');
|
||||
|
||||
if (candidate.length >= 10 && candidate[6] === 'typ') {
|
||||
const ip4s = [ candidate[4] ];
|
||||
let abort = false;
|
||||
|
||||
for (let i = 8; i < candidate.length; ++i) {
|
||||
if (candidate[i] === 'raddr') {
|
||||
ip4s.push(candidate[++i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (const ip of ip4s) {
|
||||
if (ip.indexOf(':') === -1) {
|
||||
ips.has(ip)
|
||||
|| ips.set(ip, new Promise((resolve, reject) => {
|
||||
const v = ips.get(ip);
|
||||
|
||||
if (v && typeof v === 'string') {
|
||||
resolve(v);
|
||||
} else {
|
||||
_synthesizeIPv6FromIPv4Address(ip).then(
|
||||
value => {
|
||||
if (!value
|
||||
|| value.indexOf(':') === -1
|
||||
|| value === ips.get(ip)) {
|
||||
ips.delete(ip);
|
||||
} else {
|
||||
ips.set(ip, value);
|
||||
}
|
||||
resolve(value);
|
||||
},
|
||||
reject);
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
abort = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (abort) {
|
||||
ips.clear();
|
||||
break;
|
||||
}
|
||||
|
||||
line = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
lines.push(line);
|
||||
} while (start);
|
||||
|
||||
return {
|
||||
ips,
|
||||
lines
|
||||
};
|
||||
}
|
||||
|
||||
/* eslint-enable max-depth */
|
||||
|
||||
/**
|
||||
* Completes the asynchronous synthesis of IPv6 addresses.
|
||||
*
|
||||
* @param {RTCSessionDescription} sessionDescription - The RTCSessionDescription
|
||||
* for which IPv6 addresses are being synthesized.
|
||||
* @param {Map} ips - A Map of IPv4 addresses found in the specified
|
||||
* sessionDescription to synthesized IPv6 addresses.
|
||||
* @param {Array} lines - The lines of the specified sessionDescription.
|
||||
* @private
|
||||
* @returns {RTCSessionDescription} A RTCSessionDescription that represents the
|
||||
* result of the synthesis of IPv6 addresses.
|
||||
*/
|
||||
function _synthesizeIPv6Addresses1(sessionDescription, ips, lines) {
|
||||
if (ips.size === 0) {
|
||||
return sessionDescription;
|
||||
}
|
||||
|
||||
for (let l = 0; l < lines.length; ++l) {
|
||||
const candidate = lines[l];
|
||||
|
||||
if (typeof candidate !== 'string') {
|
||||
let ip4 = candidate[4];
|
||||
let ip6 = ips.get(ip4);
|
||||
|
||||
ip6 && (candidate[4] = ip6);
|
||||
|
||||
for (let i = 8; i < candidate.length; ++i) {
|
||||
if (candidate[i] === 'raddr') {
|
||||
ip4 = candidate[++i];
|
||||
(ip6 = ips.get(ip4)) && (candidate[i] = ip6);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
lines[l] = candidate.join(' ');
|
||||
}
|
||||
}
|
||||
|
||||
return new RTCSessionDescription({
|
||||
sdp: lines.join('\r\n'),
|
||||
type: sessionDescription.type
|
||||
});
|
||||
}
|
||||
11
react/features/mobile/polyfills/webrtc.js
Normal file
11
react/features/mobile/polyfills/webrtc.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { registerGlobals } from 'react-native-webrtc';
|
||||
|
||||
import RTCPeerConnection from './RTCPeerConnection';
|
||||
|
||||
registerGlobals();
|
||||
|
||||
(global => {
|
||||
// Override with ours.
|
||||
// TODO: consider dropping our override.
|
||||
global.RTCPeerConnection = RTCPeerConnection;
|
||||
})(global || window || this); // eslint-disable-line no-invalid-this
|
||||
Reference in New Issue
Block a user