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 @@
*.tgz

2
react-native-sdk/.npmrc Normal file
View File

@@ -0,0 +1,2 @@
package-lock=true
legacy-peer-deps=true

132
react-native-sdk/README.md Normal file
View File

@@ -0,0 +1,132 @@
# <p align="center">Jitsi Meet React Native SDK</p>
## Installation
Inside your project, run;
```console
npm i @jitsi/react-native-sdk
```
If there are conflicts, you can use ```--force```
<br/>Additionally, if not already installed, some dependencies will need to be added.
This can be done by running the following script:
```console
node node_modules/@jitsi/react-native-sdk/update_dependencies.js
```
This will check and update all your dependencies.<br/><br/>
After that you need to ```npm i```, if some dependency versions were updated.
[comment]: # (These deps definitely need to be added manually, more could be neccesary)
Because of SVG use in react native, you need to update metro.config your project's file:
```javascript
const { getDefaultConfig } = require('metro-config');
module.exports = (async () => {
const {
resolver: {
sourceExts,
assetExts
}
} = await getDefaultConfig();
return {
transformer: {
babelTransformerPath: require.resolve('react-native-svg-transformer'),
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: true,
},
}),
},
resolver: {
assetExts: assetExts.filter(ext => ext !== 'svg'),
sourceExts: [...sourceExts, 'svg']
}
}
})();
```
## iOS
#### Project Info.plist
- Add a *Privacy - Camera Usage Description*
- Add a *Privacy - Microphone Usage Description*
#### General
- Signing & capabilites:
- Add Background modes
- Audio
- Voice over IP
- Background fetch
Run;
```console
cd ios && pod install && cd ..
```
## Android
- In your build.gradle have at least `minSdkVersion = 26`
- In your build.gradle have `gradlePluginVersion = "8.4.2"` or higher
- In `android/app/src/debug/AndroidManifest.xml` and `android/app/src/main/AndroidManifest.xml`, under the `</application>` tag, include
```xml
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
```
### Services
#### Screen share
- Go to your `MainApplication.java` file and add:
1. `import com.oney.WebRTCModule.WebRTCModuleOptions;` that comes from `react-native-webrtc` dependency.
2. `WebRTCModuleOptions options = WebRTCModuleOptions.getInstance();` instance it.
3. `options.enableMediaProjectionService = true;` enable foreground service that takes care of screen-sharing feature.
- Go to your `android/app/src/main/AndroidManifest.xml`, under the `</application>` tag and include
```xml
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
```
If you want to test all the steps before applying them to your app, you can check our React Native SDK sample app here:
https://github.com/jitsi/jitsi-meet-sdk-samples/tree/master/react-native
## Using JWT tokens
- If you are planning to use tokens or another domain, you can do that by updating the following props, as shown below.
- For example:
```javascript
<JitsiMeeting
room = { 'ThisIsNotATestRoomName' }
serverURL = { 'https://meet.jit.si/' }
token={ 'dkhalhfajhflahlfaahalhfahfsl' } />
```
## Using custom overflow menu buttons
- If you are planning to add custom overflow menu buttons, you can do that by updating the ```config``` prop, as shown below.
- For example:
```javascript
<JitsiMeeting
config = {{
customToolbarButtons: [
{
icon: "https://w7.pngwing.com/pngs/987/537/png-transparent-download-downloading-save-basic-user-interface-icon-thumbnail.png",
id: "btn1",
text: "Button one"
}, {
icon: "https://w7.pngwing.com/pngs/987/537/png-transparent-download-downloading-save-basic-user-interface-icon-thumbnail.png",
id: "btn2",
text: "Button two"
}
]
}}
room = { 'ThisIsNotATestRoomName' }
serverURL = { 'https://meet.jit.si/' }
token = { 'dkhalhfajhflahlfaahalhfahfsl' } />
```
For more details on how you can use React Native SDK with React Native app, you can follow this link:
https://jitsi.github.io/handbook/docs/dev-guide/dev-guide-react-native-sdk

View File

@@ -0,0 +1,156 @@
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:$rootProject.ext.gradlePluginVersion"
}
}
def isNewArchitectureEnabled() {
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
}
apply plugin: 'com.android.library'
if (isNewArchitectureEnabled()) {
apply plugin: 'com.facebook.react'
}
def getExtOrDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['JitsiMeetReactNative_' + name]
}
def getExtOrIntegerDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties['JitsiMeetReactNative_' + name]).toInteger()
}
android {
namespace 'org.jitsi.meet.sdk'
compileSdkVersion getExtOrIntegerDefault('compileSdkVersion')
defaultConfig {
minSdkVersion getExtOrIntegerDefault('minSdkVersion')
targetSdkVersion getExtOrIntegerDefault('targetSdkVersion')
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
}
buildTypes {
release {
minifyEnabled false
}
}
lintOptions {
disable 'GradleCompatible'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
}
repositories {
mavenCentral()
google()
def found = false
def defaultDir = null
def androidSourcesName = 'React Native sources'
if (rootProject.ext.has('reactNativeAndroidRoot')) {
defaultDir = rootProject.ext.get('reactNativeAndroidRoot')
} else {
defaultDir = new File(
projectDir,
'/../../../node_modules/react-native/android'
)
}
if (defaultDir.exists()) {
maven {
url defaultDir.toString()
name androidSourcesName
}
logger.info(":${project.name}:reactNativeAndroidRoot ${defaultDir.canonicalPath}")
found = true
} else {
def parentDir = rootProject.projectDir
1.upto(5, {
if (found) return true
parentDir = parentDir.parentFile
def androidSourcesDir = new File(
parentDir,
'node_modules/react-native'
)
def androidPrebuiltBinaryDir = new File(
parentDir,
'node_modules/react-native/android'
)
if (androidPrebuiltBinaryDir.exists()) {
maven {
url androidPrebuiltBinaryDir.toString()
name androidSourcesName
}
logger.info(":${project.name}:reactNativeAndroidRoot ${androidPrebuiltBinaryDir.canonicalPath}")
found = true
} else if (androidSourcesDir.exists()) {
maven {
url androidSourcesDir.toString()
name androidSourcesName
}
logger.info(":${project.name}:reactNativeAndroidRoot ${androidSourcesDir.canonicalPath}")
found = true
}
})
}
if (!found) {
throw new GradleException(
"${project.name}: unable to locate React Native android sources. " +
"Ensure you have you installed React Native as a dependency in your project and try again."
)
}
}
dependencies {
// noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+"
implementation 'com.dropbox.core:dropbox-core-sdk:4.0.1'
implementation 'com.jakewharton.timber:timber:4.7.1'
// From node_modules
}
if (isNewArchitectureEnabled()) {
react {
jsRootDir = file("../")
libraryName = "JitsiMeetReactNative"
codegenJavaPackageName = "org.jitsi.meet.sdk"
}
}
// Copy sounds to assets directory
android.libraryVariants.all { def variant ->
def mergeAssetsTask = variant.mergeAssetsProvider.get()
def mergeResourcesTask = variant.mergeResourcesProvider.get()
mergeAssetsTask.doLast {
def assetsDir = mergeAssetsTask.outputDir.get()
def soundsDir = "${projectDir}/../sounds"
copy {
from("${soundsDir}")
include("*.wav")
include("*.mp3")
into("${assetsDir}/sounds")
}
}
}

View File

@@ -0,0 +1,5 @@
JitsiMeetReactNative_kotlinVersion=2.0.21
JitsiMeetReactNative_minSdkVersion=26
JitsiMeetReactNative_targetSdkVersion=34
JitsiMeetReactNative_compileSdkVersion=34
JitsiMeetReactNative_ndkversion=27.1.12297006

View File

@@ -0,0 +1,117 @@
package org.jitsi.meet.sdk;
import static android.Manifest.permission.POST_NOTIFICATIONS;
import static android.Manifest.permission.RECORD_AUDIO;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import com.facebook.react.ReactActivity;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.modules.core.PermissionListener;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* This class implements a ReactModule and it's
* responsible for launching/aborting a service when a conference is in progress.
*/
@ReactModule(name = JMOngoingConferenceModule.NAME)
class JMOngoingConferenceModule extends ReactContextBaseJavaModule {
public static final String NAME = "JMOngoingConference";
private static final int PERMISSIONS_REQUEST_CODE = (int) (Math.random() * Short.MAX_VALUE);
public JMOngoingConferenceModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@ReactMethod
public void launch() {
Context context = getReactApplicationContext();
ReactActivity reactActivity = (ReactActivity) getCurrentActivity();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
JMOngoingConferenceService.launch(context);
JitsiMeetLogger.i(NAME + " launch");
return;
}
PermissionListener listener = new PermissionListener() {
@Override
public boolean onRequestPermissionsResult(int i, String[] strings, int[] results) {
JitsiMeetLogger.i(NAME + " Permission callback received");
if (results == null || results.length == 0) {
JitsiMeetLogger.w(NAME + " Permission results are null or empty");
return true;
}
int counter = 0;
for (int result : results) {
if (result == PackageManager.PERMISSION_GRANTED) {
counter++;
}
}
JitsiMeetLogger.i(NAME + " Permissions granted: " + counter + "/" + results.length);
if (counter == results.length) {
JitsiMeetLogger.i(NAME + " All permissions granted, launching service");
JMOngoingConferenceService.launch(context);
} else {
JitsiMeetLogger.w(NAME + " Not all permissions were granted");
}
return true;
}
};
JitsiMeetLogger.i(NAME + " Checking Tiramisu permissions");
List<String> permissionsList = new ArrayList<>();
permissionsList.add(POST_NOTIFICATIONS);
permissionsList.add(RECORD_AUDIO);
String[] permissionsArray = new String[ permissionsList.size() ];
permissionsArray = permissionsList.toArray( permissionsArray );
try {
JitsiMeetLogger.i(NAME + " Requesting permissions: " + Arrays.toString(permissionsArray));
reactActivity.requestPermissions(permissionsArray, PERMISSIONS_REQUEST_CODE, listener);
} catch (Exception e) {
JitsiMeetLogger.e(e, NAME + " Error requesting permissions");
}
}
@ReactMethod
public void abort() {
Context context = getReactApplicationContext();
JMOngoingConferenceService.abort(context);
JitsiMeetLogger.i(NAME + " abort");
}
@NonNull
@Override
public String getName() {
return NAME;
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright @ 2019-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import android.app.Notification;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ServiceInfo;
import android.os.Build;
import android.os.IBinder;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.Random;
/**
* This class implements an Android {@link Service}, a foreground one specifically, and it's
* responsible for presenting an ongoing notification when a conference is in progress.
* The service will help keep the app running while in the background.
*
* See: https://developer.android.com/guide/components/services
*/
public class JMOngoingConferenceService extends Service {
private static final String TAG = JMOngoingConferenceService.class.getSimpleName();
static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000;
public static void launch(Context context) {
try {
RNOngoingNotification.createOngoingConferenceNotificationChannel(context);
JitsiMeetLogger.i(TAG + " Notification channel creation completed");
} catch (Exception e) {
JitsiMeetLogger.e(e, TAG + " Error creating notification channel");
}
Intent intent = new Intent(context, JMOngoingConferenceService.class);
try {
context.startForegroundService(intent);
JitsiMeetLogger.i(TAG + " Starting foreground service");
} catch (RuntimeException e) {
// Avoid crashing due to ForegroundServiceStartNotAllowedException (API level 31).
// See: https://developer.android.com/guide/components/foreground-services#background-start-restrictions
JitsiMeetLogger.w(TAG + " Ongoing conference service not started", e);
}
}
public static void abort(Context context) {
Intent intent = new Intent(context, JMOngoingConferenceService.class);
context.stopService(intent);
}
@Override
public void onCreate() {
super.onCreate();
JitsiMeetLogger.i(TAG + " onCreate called");
try {
Notification notification = RNOngoingNotification.buildOngoingConferenceNotification(this);
JitsiMeetLogger.i(TAG + " Notification build result: " + (notification != null));
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
} else {
JitsiMeetLogger.i(TAG + " Starting service in foreground with notification");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK | ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE);
} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK);
} else {
startForeground(NOTIFICATION_ID, notification);
}
}
} catch (Exception e) {
JitsiMeetLogger.e(e, TAG + " Error in onCreate");
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_NOT_STICKY;
}
}

View File

@@ -0,0 +1,38 @@
package org.jitsi.meet.sdk;
import androidx.annotation.NonNull;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class JitsiMeetReactNativePackage implements ReactPackage {
@NonNull
@Override
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
List<NativeModule> modules
= new ArrayList<>(Arrays.<NativeModule>asList(
new AndroidSettingsModule(reactContext),
new AppInfoModule(reactContext),
new AudioModeModule(reactContext),
new JMOngoingConferenceModule(reactContext),
new LocaleDetector(reactContext),
new LogBridgeModule(reactContext),
new PictureInPictureModule(reactContext),
new ProximityModule(reactContext)
));
return modules;
}
@NonNull
@Override
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,108 @@
/*
* Copyright @ 2019-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import androidx.core.app.NotificationCompat;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
/**
* Helper class for creating the ongoing notification which is used with
* {@link JMOngoingConferenceService}. It allows the user to easily get back to the app
* and to hangup from within the notification itself.
*/
class RNOngoingNotification {
private static final String TAG = RNOngoingNotification.class.getSimpleName();
static final String RN_ONGOING_CONFERENCE_CHANNEL_ID = "OngoingConferenceChannel";
static void createOngoingConferenceNotificationChannel(Context context) {
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Service.NOTIFICATION_SERVICE);
NotificationChannel channel = notificationManager.getNotificationChannel(RN_ONGOING_CONFERENCE_CHANNEL_ID);
if (channel != null) {
JitsiMeetLogger.i(TAG + " Notification channel already exists");
return;
}
channel = new NotificationChannel(
RN_ONGOING_CONFERENCE_CHANNEL_ID,
context.getString(R.string.ongoing_notification_channel_name),
NotificationManager.IMPORTANCE_DEFAULT
);
channel.enableVibration(true);
channel.setShowBadge(true);
channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
notificationManager.createNotificationChannel(channel);
JitsiMeetLogger.i(TAG + " Notification channel created with importance: " + channel.getImportance());
}
static Notification buildOngoingConferenceNotification(Context context) {
if (context == null) {
JitsiMeetLogger.w(TAG + " Cannot create notification: no current context");
return null;
}
JitsiMeetLogger.i(TAG + " Creating notification with context: " + context);
// Creating an intent to launch app's main activity
Intent intent = context.getPackageManager()
.getLaunchIntentForPackage(context.getPackageName());
assert intent != null;
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
// Creating PendingIntent
PendingIntent pendingIntent = PendingIntent.getActivity(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, RN_ONGOING_CONFERENCE_CHANNEL_ID);
builder
.setCategory(NotificationCompat.CATEGORY_CALL)
.setContentTitle(context.getString(R.string.ongoing_notification_title))
.setContentText(context.getString(R.string.ongoing_notification_text))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setOngoing(true)
.setWhen(System.currentTimeMillis())
.setUsesChronometer(true)
.setAutoCancel(false)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setOnlyAlertOnce(true)
.setSmallIcon(context.getResources().getIdentifier("ic_notification", "drawable", context.getPackageName()))
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
.setContentIntent(pendingIntent);
return builder.build();
}
}

176
react-native-sdk/index.tsx Normal file
View File

@@ -0,0 +1,176 @@
/* eslint-disable lines-around-comment, no-undef, no-unused-vars */
// NB: This import must always come first.
import './react/bootstrap.native';
import React, {
forwardRef,
useEffect,
useImperativeHandle,
useLayoutEffect,
useRef,
useState
} from 'react';
import { View, ViewStyle } from 'react-native';
import type { IRoomsInfo } from '../react/features/breakout-rooms/types';
import { appNavigate } from './react/features/app/actions.native';
import { App } from './react/features/app/components/App.native';
import { setLowBandwidthMode } from './react/features/base/low-bandwidth-mode/actions';
import { setAudioMuted, setVideoMuted } from './react/features/base/media/actions';
import { getRoomsInfo } from './react/features/breakout-rooms/functions';
interface IEventListeners {
onAudioMutedChanged?: Function;
onVideoMutedChanged?: Function;
onConferenceBlurred?: Function;
onConferenceFocused?: Function;
onConferenceJoined?: Function;
onConferenceLeft?: Function;
onConferenceWillJoin?: Function;
onEnterPictureInPicture?: Function;
onEndpointMessageReceived?: Function;
onParticipantJoined?: Function;
onParticipantLeft?: ({ id }: { id: string }) => void;
onReadyToClose?: Function;
}
interface IUserInfo {
avatarURL: string;
displayName: string;
email: string;
}
interface IAppProps {
config: object;
eventListeners?: IEventListeners;
flags?: object;
room: string;
serverURL?: string;
style?: Object;
token?: string;
userInfo?: IUserInfo;
}
export interface JitsiRefProps {
close: Function;
setLowBandwidthMode?: (value: boolean) => void;
setAudioMuted?: (muted: boolean) => void;
setVideoMuted?: (muted: boolean) => void;
getRoomsInfo?: () => IRoomsInfo;
}
/**
* Main React Native SDK component that displays a Jitsi Meet conference and gets all required params as props
*/
export const JitsiMeeting = forwardRef<JitsiRefProps, IAppProps>((props, ref) => {
const [ appProps, setAppProps ] = useState({});
const app = useRef(null);
const {
config,
eventListeners,
flags,
room,
serverURL,
style,
token,
userInfo
} = props;
// eslint-disable-next-line arrow-body-style
useImperativeHandle(ref, () => ({
close: () => {
const dispatch = app.current.state.store.dispatch;
dispatch(appNavigate(undefined));
},
setLowBandwidthMode: value => {
const dispatch = app.current.state.store.dispatch;
dispatch(setLowBandwidthMode(value));
},
setAudioMuted: muted => {
const dispatch = app.current.state.store.dispatch;
dispatch(setAudioMuted(muted));
},
setVideoMuted: muted => {
const dispatch = app.current.state.store.dispatch;
dispatch(setVideoMuted(muted));
},
getRoomsInfo: () => {
const state = app.current.state.store.getState();
return getRoomsInfo(state);
}
}));
useEffect(
() => {
const urlObj = {
config,
jwt: token
};
let urlProps;
if (room.includes('://')) {
urlProps = {
...urlObj,
url: room
};
} else {
urlProps = {
...urlObj,
room,
serverURL
};
}
setAppProps({
'flags': flags,
'rnSdkHandlers': {
onAudioMutedChanged: eventListeners?.onAudioMutedChanged,
onVideoMutedChanged: eventListeners?.onVideoMutedChanged,
onConferenceBlurred: eventListeners?.onConferenceBlurred,
onConferenceFocused: eventListeners?.onConferenceFocused,
onConferenceJoined: eventListeners?.onConferenceJoined,
onConferenceWillJoin: eventListeners?.onConferenceWillJoin,
onConferenceLeft: eventListeners?.onConferenceLeft,
onEnterPictureInPicture: eventListeners?.onEnterPictureInPicture,
onEndpointMessageReceived: eventListeners?.onEndpointMessageReceived,
onParticipantJoined: eventListeners?.onParticipantJoined,
onParticipantLeft: eventListeners?.onParticipantLeft,
onReadyToClose: eventListeners?.onReadyToClose
},
'url': urlProps,
'userInfo': userInfo
});
}, []
);
// eslint-disable-next-line arrow-body-style
useLayoutEffect(() => {
/**
* When you close the component you need to reset it.
* In some cases it needs to be added as the parent component may have been destroyed.
* Without this change the call remains active without having the jitsi screen.
*/
return () => {
const dispatch = app.current?.state?.store?.dispatch;
dispatch && dispatch(appNavigate(undefined));
};
}, []);
return (
<View style = { style as ViewStyle }>
<App
{ ...appProps }
ref = { app } />
</View>
);
});

View File

@@ -0,0 +1,35 @@
require 'json'
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
Pod::Spec.new do |s|
s.name = 'jitsi-meet-rnsdk'
s.version = package['version']
s.summary = package['description']
s.description = package['description']
s.license = package['license']
s.author = package['author']
s.homepage = package['homepage']
s.source = { :git => package['repository']['url'], :tag => s.version }
s.requires_arc = true
s.platform = :ios, '15.1'
s.preserve_paths = 'ios/**/*'
s.source_files = 'ios/**/*.{h,m}'
s.dependency 'React-Core'
s.dependency 'react-native-webrtc'
s.dependency 'ObjectiveDropboxOfficial', '6.2.3'
s.script_phase = {
:name => 'Copy Sound Files',
:script => '
SOURCE_PATH="${PODS_TARGET_SRCROOT}/sounds/"
TARGET_PATH=$(dirname "${CONFIGURATION_BUILD_DIR}")
PROJECT_NAME=$(basename $(dirname $(dirname "${PROJECT_DIR}"))).app
ditto "${SOURCE_PATH}" "${TARGET_PATH}/${PROJECT_NAME}/sounds"
',
}
end

View File

@@ -0,0 +1,106 @@
{
"name": "@jitsi/react-native-sdk",
"version": "0.0.0",
"description": "React Native SDK for Jitsi Meet.",
"main": "index.tsx",
"license": "Apache-2.0",
"author": "",
"homepage": "https://jitsi.org",
"repository": {
"type": "git",
"url": "git+https://github.com/jitsi/jitsi-meet.git"
},
"dependencies": {
"@braintree/sanitize-url": "0.0.0",
"@jitsi/js-utils": "0.0.0",
"@jitsi/logger": "0.0.0",
"@jitsi/rtcstats": "0.0.0",
"@react-navigation/bottom-tabs": "0.0.0",
"@react-navigation/elements": "0.0.0",
"@react-navigation/material-top-tabs": "0.0.0",
"@react-navigation/native": "0.0.0",
"@react-navigation/stack": "0.0.0",
"@stomp/stompjs": "0.0.0",
"@xmldom/xmldom": "0.0.0",
"abab": "0.0.0",
"base64-js": "0.0.0",
"dayjs": "0.0.0",
"grapheme-splitter": "0.0.0",
"i18n-iso-countries": "0.0.0",
"i18next": "0.0.0",
"js-md5": "0.0.0",
"i18next-http-backend": "0.0.0",
"js-sha512": "0.0.0",
"jwt-decode": "0.0.0",
"lib-jitsi-meet": "0.0.0",
"lodash-es": "0.0.0",
"optional-require": "0.0.0",
"promise.allsettled": "0.0.0",
"promise.withresolvers": "0.0.0",
"punycode": "0.0.0",
"react-emoji-render": "0.0.0",
"react-i18next": "0.0.0",
"react-linkify": "0.0.0",
"react-native-dialog": "0.0.0",
"react-native-paper": "0.0.0",
"react-native-svg-transformer": "0.0.0",
"react-native-tab-view": "0.0.0",
"react-native-url-polyfill": "0.0.0",
"react-native-youtube-iframe": "0.0.0",
"react-redux": "0.0.0",
"redux": "0.0.0",
"redux-thunk": "0.0.0",
"text-encoding": "0.0.0",
"unorm": "0.0.0",
"util": "0.0.0",
"uuid": "0.0.0",
"zxcvbn": "0.0.0"
},
"peerDependencies": {
"@amplitude/analytics-react-native": "0.0.0",
"@giphy/react-native-sdk": "0.0.0",
"@react-native-async-storage/async-storage": "0.0.0",
"@react-native-clipboard/clipboard": "0.0.0",
"@react-native-community/netinfo": "0.0.0",
"@react-native-community/slider": "0.0.0",
"@react-native-google-signin/google-signin": "0.0.0",
"@sayem314/react-native-keep-awake": "0.0.0",
"react-native": "0.0.0",
"react": "0.0.0",
"react-native-background-timer": "0.0.0",
"react-native-calendar-events": "0.0.0",
"react-native-default-preference": "0.0.0",
"react-native-device-info": "0.0.0",
"react-native-get-random-values": "0.0.0",
"react-native-gesture-handler": "0.0.0",
"react-native-pager-view": "0.0.0",
"react-native-performance": "0.0.0",
"react-native-orientation-locker": "0.0.0",
"react-native-safe-area-context": "0.0.0",
"react-native-screens": "0.0.0",
"react-native-sound": "0.0.0",
"react-native-splash-view": "0.0.0",
"react-native-svg": "0.0.0",
"react-native-video": "0.0.0",
"react-native-webrtc": "0.0.0",
"react-native-webview": "0.0.0",
"react-native-worklets-core": "0.0.0"
},
"devDependencies": {
"@babel/plugin-proposal-nullish-coalescing-operator": "0.0.0",
"@babel/plugin-proposal-optional-chaining": "0.0.0"
},
"scripts": {
"postinstall": "node sdk_instructions.js",
"prepare": "node prepare_sdk.js"
},
"bugs": {
"url": "https://github.com/jitsi/jitsi-meet/issues"
},
"keywords": [
"react-native"
],
"overrides": {
"hermes-compiler": "0.0.0"
}
}

194
react-native-sdk/prepare_sdk.js vendored Normal file
View File

@@ -0,0 +1,194 @@
const fs = require('fs');
const path = require('path');
const androidSourcePath = '../android/sdk/src/main/java/org/jitsi/meet/sdk';
const androidMainSourcePath = '../android/sdk/src/main/res';
const androidTargetPath = './android/src/main/java/org/jitsi/meet/sdk';
const androidMainTargetPath = './android/src/main/res';
const iosSrcPath = '../ios/sdk/src';
const iosDestPath = './ios/src';
/**
* Copies a specified file in a way that recursive copy is possible.
*/
function copyFileSync(source, target) {
let targetFile = target;
// If target is a directory, a new file with the same name will be created
if (fs.existsSync(target)) {
if (fs.lstatSync(target).isDirectory()) {
targetFile = path.join(target, path.basename(source));
}
}
fs.copyFileSync(source, targetFile);
}
/**
* Copies a specified directory recursively.
*/
function copyFolderRecursiveSync(source, target) {
let files = [];
const targetFolder = path.join(target, path.basename(source));
if (!fs.existsSync(targetFolder)) {
fs.mkdirSync(targetFolder, { recursive: true });
}
if (fs.lstatSync(source).isDirectory()) {
files = fs.readdirSync(source);
files.forEach(file => {
const curSource = path.join(source, file);
if (fs.lstatSync(curSource).isDirectory()) {
copyFolderRecursiveSync(curSource, targetFolder);
} else {
copyFileSync(curSource, targetFolder);
}
});
}
}
copyFolderRecursiveSync(
'../images',
'.'
);
copyFolderRecursiveSync(
'../sounds',
'.'
);
copyFolderRecursiveSync(
'../lang',
'.'
);
copyFolderRecursiveSync(
'../modules',
'.'
);
copyFolderRecursiveSync(
'../react',
'.'
);
copyFolderRecursiveSync(
'../ios/sdk/sdk.xcodeproj',
'./ios'
);
copyFolderRecursiveSync(
`${iosSrcPath}/callkit`,
iosDestPath
);
copyFolderRecursiveSync(
`${iosSrcPath}/dropbox`,
iosDestPath
);
fs.copyFileSync(
`${iosSrcPath}/AppInfo.m`,
`${iosDestPath}/AppInfo.m`
);
fs.copyFileSync(
`${iosSrcPath}/AudioMode.m`,
`${iosDestPath}/AudioMode.m`
);
fs.copyFileSync(
`${iosSrcPath}/InfoPlistUtil.m`,
`${iosDestPath}/InfoPlistUtil.m`
);
fs.copyFileSync(
`${iosSrcPath}/InfoPlistUtil.h`,
`${iosDestPath}/InfoPlistUtil.h`
);
fs.copyFileSync(
`${iosSrcPath}/JitsiAudioSession.m`,
`${iosDestPath}/JitsiAudioSession.m`
);
fs.copyFileSync(
`${iosSrcPath}/JitsiAudioSession.h`,
`${iosDestPath}/JitsiAudioSession.h`
);
fs.copyFileSync(
`${iosSrcPath}/JitsiAudioSession+Private.h`,
`${iosDestPath}/JitsiAudioSession+Private.h`
);
fs.copyFileSync(
`${iosSrcPath}/LocaleDetector.m`,
`${iosDestPath}/LocaleDetector.m`
);
fs.copyFileSync(
`${iosSrcPath}/Proximity.m`,
`${iosDestPath}/Proximity.m`
);
copyFolderRecursiveSync(
`${androidSourcePath}/log`,
`${androidTargetPath}/log`
);
copyFolderRecursiveSync(
`${androidMainSourcePath}/values`,
`${androidMainTargetPath}`
);
copyFolderRecursiveSync(
`${androidMainSourcePath}/drawable-hdpi`,
`${androidMainTargetPath}`
);
copyFolderRecursiveSync(
`${androidMainSourcePath}/drawable-mdpi`,
`${androidMainTargetPath}`
);
copyFolderRecursiveSync(
`${androidMainSourcePath}/drawable-xhdpi`,
`${androidMainTargetPath}`
);
copyFolderRecursiveSync(
`${androidMainSourcePath}/drawable-xxhdpi`,
`${androidMainTargetPath}`
);
copyFolderRecursiveSync(
`${androidMainSourcePath}/drawable-xxxhdpi`,
`${androidMainTargetPath}`
);
fs.copyFileSync(
`${androidSourcePath}/AndroidSettingsModule.java`,
`${androidTargetPath}/AndroidSettingsModule.java`
);
fs.copyFileSync(
`${androidSourcePath}/AppInfoModule.java`,
`${androidTargetPath}/AppInfoModule.java`
);
fs.copyFileSync(
`${androidSourcePath}/AudioDeviceHandlerConnectionService.java`,
`${androidTargetPath}/AudioDeviceHandlerConnectionService.java`
);
fs.copyFileSync(
`${androidSourcePath}/AudioDeviceHandlerGeneric.java`,
`${androidTargetPath}/AudioDeviceHandlerGeneric.java`
);
fs.copyFileSync(
`${androidSourcePath}/AudioModeModule.java`,
`${androidTargetPath}/AudioModeModule.java`
);
fs.copyFileSync(
`${androidSourcePath}/ConnectionService.java`,
`${androidTargetPath}/ConnectionService.java`
);
fs.copyFileSync(
`${androidSourcePath}/LocaleDetector.java`,
`${androidTargetPath}/LocaleDetector.java`
);
fs.copyFileSync(
`${androidSourcePath}/LogBridgeModule.java`,
`${androidTargetPath}/LogBridgeModule.java`
);
fs.copyFileSync(
`${androidSourcePath}/PictureInPictureModule.java`,
`${androidTargetPath}/PictureInPictureModule.java`
);
fs.copyFileSync(
`${androidSourcePath}/ProximityModule.java`,
`${androidTargetPath}/ProximityModule.java`
);
fs.copyFileSync(
`${androidSourcePath}/RNConnectionService.java`,
`${androidTargetPath}/RNConnectionService.java`
);

2
react-native-sdk/sdk_instructions.js vendored Normal file
View File

@@ -0,0 +1,2 @@
console.log('Run node node_modules/@jitsi/react-native-sdk/update_dependencies.js '
+ 'script to update the necessary dependencies');

110
react-native-sdk/update_dependencies.js vendored Normal file
View File

@@ -0,0 +1,110 @@
/* eslint-disable guard-for-in */
/* global __dirname */
const fs = require('fs');
const path = require('path');
const semver = require('semver');
const pathToPackageJSON = path.resolve(__dirname, '../../../package.json');
const packageJSON = require(pathToPackageJSON);
const RNSDKpackageJSON = require(path.resolve(__dirname, './package.json'));
/**
* Updates dependencies from the app package.json with the peer dependencies of the RNSDK package.json.
*/
function updateDependencies() {
let updated = false;
for (const key in RNSDKpackageJSON.peerDependencies) {
if (!packageJSON.dependencies.hasOwnProperty(key)) {
packageJSON.dependencies[key] = RNSDKpackageJSON.peerDependencies[key];
updated = true;
}
if (!semver.valid(packageJSON.dependencies[key])
&& packageJSON.dependencies[key] !== RNSDKpackageJSON.peerDependencies[key]) {
packageJSON.dependencies[key] = RNSDKpackageJSON.peerDependencies[key];
updated = true;
console.log(`
⚠️We changed ${key} version number from ${packageJSON.dependencies[key]} to ${RNSDKpackageJSON.peerDependencies[key]}`
);
continue;
}
if (semver.satisfies(RNSDKpackageJSON.peerDependencies[key], `=${packageJSON.dependencies[key]}`)) {
continue;
}
if (semver.satisfies(RNSDKpackageJSON.peerDependencies[key], `>${packageJSON.dependencies[key]}`)) {
packageJSON.dependencies[key] = RNSDKpackageJSON.peerDependencies[key];
updated = true;
console.log(`${key} is now set to ${RNSDKpackageJSON.peerDependencies[key]}`);
}
if (!semver.valid(RNSDKpackageJSON.peerDependencies[key])
&& RNSDKpackageJSON.peerDependencies[key].includes('github')
&& packageJSON.dependencies[key] !== RNSDKpackageJSON.peerDependencies[key]) {
packageJSON.dependencies[key] = RNSDKpackageJSON.peerDependencies[key];
updated = true;
console.log(
`A fix for ${key} is available on ${RNSDKpackageJSON.peerDependencies[key]}.
This is now set on your end.`
);
}
}
packageJSON.devDependencies = packageJSON.devDependencies || {};
for (const key in RNSDKpackageJSON.devDependencies) {
if (!packageJSON.devDependencies.hasOwnProperty(key)) {
packageJSON.devDependencies[key] = RNSDKpackageJSON.devDependencies[key];
updated = true;
}
}
if (!updated) {
console.log('All your dependencies are up to date!');
return;
}
console.log(`
=========================
🚀 Your project was updated!
🛠 Make sure you run npm install
📱 If you are building for iOS run cd ios && pod install to link them.
=========================
`);
packageJSON.dependencies = Object.keys(packageJSON.dependencies)
.sort()
.reduce((item, itemKey) => {
item[itemKey] = packageJSON.dependencies[itemKey];
return item;
}, {});
packageJSON.devDependencies = Object.keys(packageJSON.devDependencies)
.sort()
.reduce((item, itemKey) => {
item[itemKey] = packageJSON.devDependencies[itemKey];
return item;
}, {});
fs.writeFileSync(pathToPackageJSON, JSON.stringify(packageJSON, null, 2));
console.log(
'All needed dependencies have been updated. \nPlease run npm install.'
);
}
updateDependencies();

View File

@@ -0,0 +1,59 @@
const fs = require('fs');
const semver = require('semver');
const packageJSON = require('../package.json');
const SDKPackageJSON = require('./package.json');
// Skip checking these.
const skipDeps = [ 'react-native' ];
/**
* Merges the dependency versions from the root package.json with the dependencies of the SDK package.json.
*/
function mergeDependencyVersions() {
// Updates SDK dependencies to match project dependencies.
for (const key in SDKPackageJSON.dependencies) {
if (SDKPackageJSON.dependencies.hasOwnProperty(key)) {
SDKPackageJSON.dependencies[key] = packageJSON.dependencies[key] || packageJSON.devDependencies[key];
}
}
// Updates SDK peer dependencies.
for (const key in packageJSON.dependencies) {
if (SDKPackageJSON.peerDependencies.hasOwnProperty(key) && !skipDeps.includes(key)) {
SDKPackageJSON.peerDependencies[key] = packageJSON.dependencies[key];
}
}
// Updates SDK dev dependencies(used by react-native-worklets-core lib. babel plugin)
for (const key in packageJSON.devDependencies) {
if (SDKPackageJSON.devDependencies.hasOwnProperty(key)) {
SDKPackageJSON.devDependencies[key] = packageJSON.devDependencies[key];
}
}
// Set RN peer dependency.
const rnVersion = semver.parse(packageJSON.dependencies['react-native']);
if (!rnVersion) {
throw new Error('failed to parse React Native version');
}
// In RN the "major" version is the Semver minor.
SDKPackageJSON.peerDependencies['react-native'] = `~0.${rnVersion.minor}.0`;
// Updates SDK overrides dependencies.
for (const key in packageJSON.overrides) {
if (SDKPackageJSON.overrides.hasOwnProperty(key)) {
SDKPackageJSON.overrides[key] = packageJSON.overrides[key];
}
}
const data = JSON.stringify(SDKPackageJSON, null, 4);
fs.writeFileSync('package.json', data);
}
mergeDependencyVersions();