snapshot of https://github.com/jitsi/jitsi-meet.git @ fd1749dd28f4138aa80234fc7f311b19aaffa0e7 (2026-09-15)
Some checks failed
Prosody Plugin Tests / test (push) Failing after 10m11s
Close stale issues and PRs / stale (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-15 15:56:32 +05:00
commit 1d2bfcac0c
3159 changed files with 464524 additions and 0 deletions

View 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);
}
}

View File

@@ -0,0 +1,338 @@
import { querySelector, querySelectorAll } from '@jitsi/js-utils/polyfills/querySelectorPolyfill';
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;
(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:
// - 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.querySelectorAll
//
// Required by:
// - lib-jitsi-meet XMLUtils
if (typeof elementPrototype.querySelectorAll === 'undefined') {
elementPrototype.querySelectorAll = function(selectors) {
return querySelectorAll(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;
}
});
}
}
// Document.querySelector
//
// Required by:
// - lib-jitsi-meet -> XMLUtils.ts -> parseXML
if (typeof document.querySelector === 'undefined') {
document.querySelector = function(selectors) {
return querySelector(this, selectors);
};
}
// Document.querySelectorAll
//
// Required by:
// - lib-jitsi-meet -> XMLUtils.ts -> parseXML
if (typeof document.querySelectorAll === 'undefined') {
document.querySelectorAll = function(selectors) {
return querySelectorAll(this, selectors);
};
}
// Also add querySelector methods to Document.prototype for DOMParser-created documents
const documentPrototype = Object.getPrototypeOf(document);
if (documentPrototype) {
// Document.querySelector
//
// Required by:
// - lib-jitsi-meet -> XMLUtils.ts -> parseXML
if (typeof documentPrototype.querySelector === 'undefined') {
documentPrototype.querySelector = function(selectors) {
return querySelector(this, selectors);
};
}
// Document.querySelectorAll
//
// Required by:
// - lib-jitsi-meet -> XMLUtils.ts -> parseXML
if (typeof documentPrototype.querySelectorAll === 'undefined') {
documentPrototype.querySelectorAll = function(selectors) {
return querySelectorAll(this, selectors);
};
}
}
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

View File

@@ -0,0 +1,4 @@
import { NativeModules } from 'react-native';
global.JITSI_MEET_LITE_SDK = Boolean(NativeModules.AppInfo.isLiteSDK);

View File

@@ -0,0 +1,2 @@
import './browser';
import './custom';

View File

@@ -0,0 +1,3 @@
import { registerGlobals } from 'react-native-webrtc';
registerGlobals();