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 2s

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,7 @@
// Re-export JitsiMeetJS from the library lib-jitsi-meet to (the other features
// of) the project jitsi-meet.
//
// @ts-ignore
import JitsiMeetJS from 'lib-jitsi-meet';
export { JitsiMeetJS as default };

View File

@@ -0,0 +1,3 @@
declare let JitsiMeetJS: any;
export default JitsiMeetJS;

View File

@@ -0,0 +1,48 @@
/**
* The type of Redux action which signals that {@link JitsiMeetJS} was disposed.
*
* {
* type: LIB_DID_DISPOSE
* }
*/
export const LIB_DID_DISPOSE = 'LIB_DID_DISPOSE';
/**
* The type of Redux action which signals that {@link JitsiMeetJS.init()} was
* invoked and completed successfully.
*
* {
* type: LIB_DID_INIT
* }
*/
export const LIB_DID_INIT = 'LIB_DID_INIT';
/**
* Action to signal that lib-jitsi-meet initialized failed with error.
*
* {
* type: LIB_INIT_ERROR,
* error: Error
* }
*/
export const LIB_INIT_ERROR = 'LIB_INIT_ERROR';
/**
* The type of Redux action which signals that {@link JitsiMeetJS} will be
* disposed.
*
* {
* type: LIB_WILL_DISPOSE
* }
*/
export const LIB_WILL_DISPOSE = 'LIB_WILL_DISPOSE';
/**
* The type of Redux action which signals that {@link JitsiMeetJS.init()} will
* be invoked.
*
* {
* type: LIB_WILL_INIT
* }
*/
export const LIB_WILL_INIT = 'LIB_WILL_INIT';

View File

@@ -0,0 +1,83 @@
import { jitsiLocalStorage } from '@jitsi/js-utils';
import { IStore } from '../../app/types';
import { isOnline } from '../net-info/selectors';
import JitsiMeetJS from './_';
import {
LIB_DID_DISPOSE,
LIB_DID_INIT,
LIB_INIT_ERROR,
LIB_WILL_DISPOSE,
LIB_WILL_INIT
} from './actionTypes';
import { isAnalyticsEnabled } from './functions.any';
import logger from './logger';
/**
* Disposes (of) lib-jitsi-meet.
*
* @returns {Function}
*/
export function disposeLib() {
return (dispatch: IStore['dispatch']) => {
dispatch({ type: LIB_WILL_DISPOSE });
// TODO Currently, lib-jitsi-meet doesn't have the functionality to
// dispose itself.
dispatch({ type: LIB_DID_DISPOSE });
};
}
/**
* Initializes lib-jitsi-meet (i.e. {@link invokes JitsiMeetJS.init()}) with the
* current config(uration).
*
* @returns {Function}
*/
export function initLib() {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const state = getState();
const config = state['features/base/config'];
if (!config) {
throw new Error('Cannot init lib-jitsi-meet without config');
}
dispatch({ type: LIB_WILL_INIT });
try {
JitsiMeetJS.init({
enableAnalyticsLogging: isAnalyticsEnabled(getState),
...config,
externalStorage: jitsiLocalStorage.isLocalStorageDisabled() ? jitsiLocalStorage : undefined
});
JitsiMeetJS.setNetworkInfo({
isOnline: isOnline(state)
});
logger.info(`lib-jitsi-meet version: ${JitsiMeetJS.version}`);
logger.info(`User Agent: ${navigator.userAgent}`);
dispatch({ type: LIB_DID_INIT });
} catch (error: any) {
dispatch(libInitError(error));
}
};
}
/**
* Notifies about a specific error raised by {@link JitsiMeetJS.init()}.
*
* @param {Error} error - The Error raised by JitsiMeetJS.init().
* @returns {{
* type: LIB_INIT_ERROR,
* error: Error
* }}
*/
export function libInitError(error: Error) {
return {
type: LIB_INIT_ERROR,
error
};
}

View File

@@ -0,0 +1,99 @@
import { IStateful } from '../app/types';
import { ConnectionFailedError } from '../connection/types';
import { toState } from '../redux/functions';
import JitsiMeetJS from './_';
const JitsiConferenceErrors = JitsiMeetJS.errors.conference;
const JitsiConnectionErrors = JitsiMeetJS.errors.connection;
/**
* Creates a {@link JitsiLocalTrack} model from the given device id.
*
* @param {string} type - The media type of track being created. Expected values
* are "video" or "audio".
* @param {string} deviceId - The id of the target media source.
* @param {number} [timeout] - A timeout for the JitsiMeetJS.createLocalTracks function call.
* @param {Object} additionalOptions - Extra options to be passed to lib-jitsi-meet's {@code createLocalTracks}.
*
* @returns {Promise<JitsiLocalTrack>}
*/
export function createLocalTrack(type: string, deviceId: string | null, timeout?: number | null,
additionalOptions?: Object) {
return (
JitsiMeetJS.createLocalTracks({
cameraDeviceId: deviceId,
devices: [ type ],
micDeviceId: deviceId,
timeout,
...additionalOptions
})
.then(([ jitsiLocalTrack ]: any[]) => jitsiLocalTrack));
}
/**
* Determines whether analytics is enabled in a specific redux {@code store}.
*
* @param {IStateful} stateful - The redux store, state, or
* {@code getState} function.
* @returns {boolean} If analytics is enabled, {@code true}; {@code false},
* otherwise.
*/
export function isAnalyticsEnabled(stateful: IStateful) {
const { disableThirdPartyRequests, analytics = {} } = toState(stateful)['features/base/config'];
return !(disableThirdPartyRequests || analytics.disabled);
}
/**
* Determines whether a specific {@link JitsiConferenceErrors} instance
* indicates a fatal {@link JitsiConference} error.
*
* FIXME Figure out the category of errors defined by the function and describe
* that category. I've currently named the category fatal because it appears to
* be used in the cases of unrecoverable errors that necessitate a reload.
*
* @param {Error|string} error - The {@code JitsiConferenceErrors} instance to
* categorize/classify or an {@link Error}-like object.
* @returns {boolean} If the specified {@code JitsiConferenceErrors} instance
* indicates a fatal {@code JitsiConference} error, {@code true}; otherwise,
* {@code false}.
*/
export function isFatalJitsiConferenceError(error: Error | string) {
if (typeof error !== 'string') {
error = error.name; // eslint-disable-line no-param-reassign
}
return (
error === JitsiConferenceErrors.FOCUS_DISCONNECTED
|| error === JitsiConferenceErrors.FOCUS_LEFT
|| error === JitsiConferenceErrors.ICE_FAILED
|| error === JitsiConferenceErrors.OFFER_ANSWER_FAILED
|| error === JitsiConferenceErrors.VIDEOBRIDGE_NOT_AVAILABLE);
}
/**
* Determines whether a specific {@link JitsiConnectionErrors} instance
* indicates a fatal {@link JitsiConnection} error.
*
* FIXME Figure out the category of errors defined by the function and describe
* that category. I've currently named the category fatal because it appears to
* be used in the cases of unrecoverable errors that necessitate a reload.
*
* @param {Error|string} error - The {@code JitsiConnectionErrors} instance to
* categorize/classify or an {@link Error}-like object.
* @returns {boolean} If the specified {@code JitsiConnectionErrors} instance
* indicates a fatal {@code JitsiConnection} error, {@code true}; otherwise,
* {@code false}.
*/
export function isFatalJitsiConnectionError(error: Error | string | ConnectionFailedError) {
if (typeof error !== 'string') {
error = error.name; // eslint-disable-line no-param-reassign
}
return (
error === JitsiConnectionErrors.CONNECTION_DROPPED_ERROR
|| error === JitsiConnectionErrors.OTHER_ERROR
|| error === JitsiConnectionErrors.SERVER_ERROR);
}

View File

@@ -0,0 +1,74 @@
import { safeJsonParse } from '@jitsi/js-utils/json';
// @ts-ignore
import { Worklets } from 'react-native-worklets-core';
import { loadScript } from '../util/loadScript.native';
import logger from './logger';
export * from './functions.any';
/**
* Worklet context usefull for running small tasks off the JS thread.
*/
export const workletContext = Worklets.createContext('ConfigParser');
/**
* Loads config.js from a specific remote server.
*
* @param {string} url - The URL to load.
* @returns {Promise<Object>}
*/
export async function loadConfig(url: string): Promise<Object> {
try {
const configTxt = await loadScript(url, 10 * 1000, true);
const parseConfigAsync = workletContext.createRunAsync(function parseConfig(configText: string): string {
'worklet';
try {
// Used IIFE wrapper to capture config object from config.js
const configObj = eval(
'(function(){\n'
+ configText
+ '\n; return (typeof config !== "undefined" ? config : globalThis.config); })()'
);
if (configObj == void 0) {
return 'Worklet_Error: config is undefined after eval()';
}
if (typeof configObj !== 'object') {
return 'Worklet_Error: config is not an object';
}
return JSON.stringify(configObj);
} catch (err) {
return 'Worklet_Error:' + ((err as Error)?.message ?? String(err));
}
});
const workletConfig = await parseConfigAsync(configTxt);
if (typeof workletConfig !== 'string') {
throw new Error('Worklet error: workletConfig is not a string');
}
if (workletConfig.startsWith('Worklet_Error:')) {
const msg = workletConfig.slice('Worklet_Error:'.length);
throw new Error(`Worklet error: ${msg}`);
}
const config = safeJsonParse(workletConfig);
logger.info(`Config loaded from ${url}`);
return config;
} catch (err) {
logger.error(`Failed to load config from ${url}`, err);
throw err;
}
}

View File

@@ -0,0 +1,99 @@
import { safeJsonParse } from '@jitsi/js-utils/json';
import { IConfig } from '../config/configType';
import logger from './logger';
export * from './functions.any';
const WORKER_SCRIPT = `
self.onmessage = function(e) {
const configText = e.data;
try {
const configObj = eval(
'(function(){\\n'
+ configText
+ '\\n; return (typeof config !== "undefined" ? config : globalThis.config); })()'
);
if (configObj == void 0) {
self.postMessage({ error: 'config is undefined after eval()' });
return;
}
if (typeof configObj !== 'object') {
self.postMessage({ error: 'config is not an object' });
return;
}
self.postMessage({ result: JSON.stringify(configObj) });
} catch (err) {
self.postMessage({ error: err?.message ?? String(err) });
}
};
`;
/**
* Parses config.js text content inside a Web Worker for isolation.
*
* @param {string} configText - The raw config.js source text.
* @returns {Promise<Object>}
*/
function parseConfigInWorker(configText: string): Promise<Object> {
return new Promise((resolve, reject) => {
const blob = new Blob([ WORKER_SCRIPT ]);
// @ts-ignore -- URL.createObjectURL/revokeObjectURL not typed in this ts-loader context
const workerUrl = URL.createObjectURL(blob);
const worker = new Worker(workerUrl);
worker.onmessage = (e: MessageEvent) => {
// @ts-ignore
URL.revokeObjectURL(workerUrl);
worker.terminate();
if (e.data.error) {
reject(new Error(e.data.error));
} else {
resolve(safeJsonParse(e.data.result));
}
};
worker.onerror = (err: ErrorEvent) => {
// @ts-ignore
URL.revokeObjectURL(workerUrl);
worker.terminate();
reject(new Error(err.message));
};
worker.postMessage(configText);
});
}
/**
* Loads config.js from a specific URL.
*
* @param {string} [url] - The URL to load.
* @returns {Promise<IConfig>}
*/
export async function loadConfig(url: string): Promise<IConfig> {
try {
const response = await fetch(url);
if (!response.ok) {
return Promise.reject(
new Error(`Failed to fetch config: ${response.status} ${response.statusText}`));
}
const configText = await response.text();
const config = await parseConfigInWorker(configText);
logger.info(`Config loaded from ${url}`);
return Promise.resolve(config);
} catch (err) {
logger.error(`Failed to load config from ${url}`, err);
return Promise.reject(err);
}
}

View File

@@ -0,0 +1,26 @@
// Re-export JitsiMeetJS from the library lib-jitsi-meet to (the other features
// of) the project jitsi-meet.
import JitsiMeetJS from './_';
export { JitsiMeetJS as default };
// XXX Re-export the properties exported by JitsiMeetJS in order to prevent
// undefined imported JitsiMeetJS. It may be caused by import cycles but I have
// not confirmed the theory.
export const analytics = JitsiMeetJS.analytics;
export const browser = JitsiMeetJS.util.browser;
export const JitsiAudioTranslationErrors = JitsiMeetJS.errors.audioTranslation;
export const JitsiConferenceErrors = JitsiMeetJS.errors.conference;
export const JitsiConferenceEvents = JitsiMeetJS.events.conference;
export const JitsiConnectionErrors = JitsiMeetJS.errors.connection;
export const JitsiConnectionEvents = JitsiMeetJS.events.connection;
export const JitsiConnectionQualityEvents = JitsiMeetJS.events.connectionQuality;
export const JitsiDetectionEvents = JitsiMeetJS.events.detection;
export const JitsiE2ePingEvents = JitsiMeetJS.events.e2eping;
export const JitsiIceRestartReason = JitsiMeetJS.constants.iceRestartReason;
export const JitsiMediaDevicesEvents = JitsiMeetJS.events.mediaDevices;
export const JitsiTrackStreamingStatus = JitsiMeetJS.constants.trackStreamingStatus;
export const JitsiRecordingConstants = JitsiMeetJS.constants.recording;
export const JitsiSIPVideoGWStatus = JitsiMeetJS.constants.sipVideoGW;
export const JitsiTrackErrors = JitsiMeetJS.errors.track;
export const JitsiTrackEvents = JitsiMeetJS.events.track;
export const RTCStatsEvents = JitsiMeetJS.events.rtcstats;

View File

@@ -0,0 +1,3 @@
import { getLogger } from '../logging/functions';
export default getLogger('app:lib-jitsi-meet');

View File

@@ -0,0 +1,74 @@
import { AnyAction } from 'redux';
import { IStore } from '../../app/types';
import { SET_CONFIG } from '../config/actionTypes';
import { SET_NETWORK_INFO } from '../net-info/actionTypes';
import { PARTICIPANT_LEFT } from '../participants/actionTypes';
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
import JitsiMeetJS from './_';
import { disposeLib, initLib } from './actions';
/**
* Middleware that captures PARTICIPANT_LEFT action for a local participant
* (which signalizes that we finally left the app) and disposes lib-jitsi-meet.
* Also captures SET_CONFIG action and disposes previous instance (if any) of
* lib-jitsi-meet, and initializes a new one with new config.
*
* @param {Store} store - Redux store.
* @private
* @returns {Function}
*/
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case SET_NETWORK_INFO:
JitsiMeetJS.setNetworkInfo({
isOnline: action.isOnline
});
break;
case PARTICIPANT_LEFT:
action.participant.local && store.dispatch(disposeLib());
break;
case SET_CONFIG:
return _setConfig(store, next, action);
}
return next(action);
});
/**
* Notifies the feature base/lib-jitsi-meet that the action SET_CONFIG is being
* dispatched within a specific Redux store.
*
* @param {Store} store - The Redux store in which the specified action is being
* dispatched.
* @param {Dispatch} next - The Redux dispatch function to dispatch the
* specified action to the specified store.
* @param {Action} action - The Redux action SET_CONFIG which is being
* dispatched in the specified store.
* @private
* @returns {Object} The new state that is the result of the reduction of the
* specified action.
*/
function _setConfig({ dispatch, getState }: IStore, next: Function, action: AnyAction) {
const { initialized } = getState()['features/base/lib-jitsi-meet'];
// XXX Since the config is changing, the library lib-jitsi-meet must be
// initialized again with the new config. Consequently, it may need to be
// disposed of first.
// TODO Currently, disposeLib actually does not dispose of lib-jitsi-meet
// because lib-jitsi-meet does not implement such functionality.
if (initialized) {
dispatch(disposeLib());
}
// Let the new config into the Redux store (because initLib will read it
// from there).
const result = next(action);
dispatch(initLib());
return result;
}

View File

@@ -0,0 +1,45 @@
import ReducerRegistry from '../redux/ReducerRegistry';
import {
LIB_DID_DISPOSE,
LIB_DID_INIT,
LIB_INIT_ERROR
} from './actionTypes';
/**
* The default/initial redux state of the feature base/lib-jitsi-meet.
*
* @type {Object}
*/
const DEFAULT_STATE = {};
export interface ILibJitsiMeetState {
initError?: Error;
initialized?: boolean;
}
ReducerRegistry.register<ILibJitsiMeetState>(
'features/base/lib-jitsi-meet',
(state = DEFAULT_STATE, action): ILibJitsiMeetState => {
switch (action.type) {
case LIB_DID_DISPOSE:
return DEFAULT_STATE;
case LIB_DID_INIT:
return {
...state,
initError: undefined,
initialized: true
};
case LIB_INIT_ERROR:
return {
...state,
initError: action.error,
initialized: false
};
default:
return state;
}
});