init
Some checks failed
Close stale issues and PRs / stale (push) Has been cancelled

This commit is contained in:
2025-09-02 14:49:16 +08:00
commit 38ba663466
2885 changed files with 391107 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,84 @@
// @ts-expect-error
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,37 @@
// @ts-ignore
import { safeJsonParse } from '@jitsi/js-utils/json';
import { NativeModules } from 'react-native';
import { loadScript } from '../util/loadScript.native';
import logger from './logger';
export * from './functions.any';
const { JavaScriptSandbox } = NativeModules;
/**
* 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 /* Timeout in ms */, true /* skipeval */);
const configJson = await JavaScriptSandbox.evaluate(`${configTxt}\nJSON.stringify(config);`);
const config = safeJsonParse(configJson);
if (typeof config !== 'object') {
throw new Error('config is not an object');
}
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,14 @@
export * from './functions.any';
/**
* Loads config.js from a specific remote server.
*
* @param {string} _url - The URL to load.
* @returns {Promise<IConfig>}
*/
export async function loadConfig(_url?: string) {
// Return "the config.js file" from the global scope - that is how the
// Web app on both the client and the server was implemented before the
// React Native app was even conceived.
return window.config;
}

View File

@@ -0,0 +1,24 @@
// 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 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 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('features/base/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;
}
});