mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2025-12-30 03:12:29 +00:00
feat(rnsdk) add initial React Native SDK
Co-authored-by: Calin-Teodor <calin.chitu@8x8.com> Co-authored-by: Saúl Ibarra Corretgé <saghul@jitsi.org>
This commit is contained in:
committed by
Saúl Ibarra Corretgé
parent
d0f9231603
commit
935a391525
3
react-native-sdk/.npmrc
Normal file
3
react-native-sdk/.npmrc
Normal file
@@ -0,0 +1,3 @@
|
||||
package-lock=true
|
||||
; FIXME Set legacy-peer-deps=false when we upgrade RN.
|
||||
legacy-peer-deps=true
|
||||
48
react-native-sdk/README.md
Normal file
48
react-native-sdk/README.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# <p align="center">Jitsi Meet React Native SDK</p>
|
||||
|
||||
|
||||
## Installation
|
||||
Inside your project, run `npm i @jitsi/react-native-sdk`.<br/><br/>Additionally if not already installed, the following dependencies need to be added:
|
||||
<br/>`npm i @react-native-async-storage/async-storage react-native-webrtc`
|
||||
|
||||
[comment]: # (These deps definitely need to be added manually, more could be neccesary)
|
||||
|
||||
### 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
|
||||
- Add Copy Sounds step:
|
||||
|
||||
```
|
||||
SOUNDS_DIR="${PROJECT_DIR}/../node_modules/rnsdk/sounds"
|
||||
cp $SOUNDS_DIR/* ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/
|
||||
```
|
||||
#### Podfile
|
||||
- At the beginning of your target step add `pod 'ObjectiveDropboxOfficial', :modular_headers => true`
|
||||
|
||||
Run `cd ios && pod install && cd ..`
|
||||
|
||||
### Android
|
||||
|
||||
- In your build.gradle have at least `minSdkVersion = 24`
|
||||
- TODO: HOW TO ADD COPY SOUNDS STEP
|
||||
- Under the `</application>` tag of your AndroidManifest.xml make sure that it includes
|
||||
```
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
```
|
||||
|
||||
### TODOS
|
||||
- Ref ConnectionService to not rely on ReactInstanceHolder anymore
|
||||
- Add Copy Sounds step to build.gradle
|
||||
- Include copy sounds step in podspec (if possible)
|
||||
- Add ranges for dependencies
|
||||
- Add Build_Config for react native to AppInfoModule
|
||||
140
react-native-sdk/android/build.gradle
Normal file
140
react-native-sdk/android/build.gradle
Normal file
@@ -0,0 +1,140 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.5.3'
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
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.squareup.duktape:duktape-android:1.3.0'
|
||||
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("../src/")
|
||||
libraryName = "JitsiMeetReactNative"
|
||||
codegenJavaPackageName = "org.jitsi.meet.sdk"
|
||||
}
|
||||
}
|
||||
5
react-native-sdk/android/gradle.properties
Normal file
5
react-native-sdk/android/gradle.properties
Normal file
@@ -0,0 +1,5 @@
|
||||
JitsiMeetReactNative_kotlinVersion=1.7.0
|
||||
JitsiMeetReactNative_minSdkVersion=21
|
||||
JitsiMeetReactNative_targetSdkVersion=31
|
||||
JitsiMeetReactNative_compileSdkVersion=31
|
||||
JitsiMeetReactNative_ndkversion=21.4.7075529
|
||||
4
react-native-sdk/android/src/main/AndroidManifest.xml
Normal file
4
react-native-sdk/android/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.jitsi.meet.sdk">
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,39 @@
|
||||
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 JavaScriptSandboxModule(reactContext),
|
||||
new LocaleDetector(reactContext),
|
||||
new LogBridgeModule(reactContext),
|
||||
new PictureInPictureModule(reactContext),
|
||||
new ProximityModule(reactContext),
|
||||
new org.jitsi.meet.sdk.net.NAT64AddrInfoModule(reactContext)
|
||||
));
|
||||
return modules;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
99
react-native-sdk/components/JitsiMeet.tsx
Normal file
99
react-native-sdk/components/JitsiMeet.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
/* eslint-disable lines-around-comment, no-undef, no-unused-vars */
|
||||
|
||||
import 'react-native-gesture-handler';
|
||||
// Apply all necessary polyfills as early as possible
|
||||
// to make sure anything imported henceforth sees them.
|
||||
import 'react-native-get-random-values';
|
||||
import '../react/features/mobile/polyfills';
|
||||
|
||||
// @ts-ignore
|
||||
import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
||||
import { View } from 'react-native';
|
||||
|
||||
import { convertPropsToURL } from '../functions';
|
||||
import { appNavigate } from '../react/features/app/actions.native';
|
||||
import { App } from '../react/features/app/components/App.native';
|
||||
import { setAudioMuted, setVideoMuted } from '../react/features/base/media/actions';
|
||||
// @ts-ignore
|
||||
import JitsiThemePaperProvider from '../react/features/base/ui/components/JitsiThemeProvider';
|
||||
|
||||
|
||||
interface IAppProps {
|
||||
flags: [];
|
||||
meetingOptions: {
|
||||
domain: string;
|
||||
roomName: string;
|
||||
onReadyToClose?: Function;
|
||||
onConferenceJoined?: Function;
|
||||
onConferenceWillJoin?: Function;
|
||||
onConferenceLeft?: Function;
|
||||
onParticipantJoined?: Function;
|
||||
settings?: {
|
||||
startWithAudioMuted?: boolean;
|
||||
startAudioOnly?: boolean;
|
||||
startWithVideoMuted?: boolean;
|
||||
}
|
||||
};
|
||||
style?: Object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main React Native SDK component that displays a Jitsi Meet conference and gets all required params as props
|
||||
*/
|
||||
const JitsiMeet = forwardRef(({ flags, meetingOptions, style }: IAppProps, ref) => {
|
||||
const [ appProps, setAppProps ] = useState({});
|
||||
const app = useRef(null);
|
||||
|
||||
// eslint-disable-next-line arrow-body-style
|
||||
useImperativeHandle(ref, () => ({
|
||||
close: () => {
|
||||
const dispatch = app.current.state.store.dispatch;
|
||||
|
||||
dispatch(appNavigate(undefined));
|
||||
},
|
||||
setAudioMuted: muted => {
|
||||
const dispatch = app.current.state.store.dispatch;
|
||||
|
||||
dispatch(setAudioMuted(muted));
|
||||
},
|
||||
setVideoMuted: muted => {
|
||||
const dispatch = app.current.state.store.dispatch;
|
||||
|
||||
dispatch(setVideoMuted(muted));
|
||||
}
|
||||
}));
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
const url = convertPropsToURL(meetingOptions.domain, meetingOptions.roomName);
|
||||
|
||||
setAppProps({
|
||||
'url': {
|
||||
url,
|
||||
config: meetingOptions.settings
|
||||
},
|
||||
'rnSdkHandlers': {
|
||||
onReadyToClose: meetingOptions.onReadyToClose,
|
||||
onConferenceJoined: meetingOptions.onConferenceJoined,
|
||||
onConferenceWillJoin: meetingOptions.onConferenceWillJoin,
|
||||
onConferenceLeft: meetingOptions.onConferenceLeft,
|
||||
onParticipantJoined: meetingOptions.onParticipantJoined
|
||||
},
|
||||
'flags': { ...flags }
|
||||
});
|
||||
}, []
|
||||
);
|
||||
|
||||
return (
|
||||
<View style = { style }>
|
||||
<JitsiThemePaperProvider>
|
||||
{/* @ts-ignore */}
|
||||
<App
|
||||
{ ...appProps }
|
||||
ref = { app } />
|
||||
</JitsiThemePaperProvider>
|
||||
</View>
|
||||
);
|
||||
});
|
||||
|
||||
export default JitsiMeet;
|
||||
7
react-native-sdk/constants.ts
Normal file
7
react-native-sdk/constants.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
module.exports = {
|
||||
androidSourcePath: '../android/sdk/src/main/java/org/jitsi/meet/sdk',
|
||||
androidTargetPath: './android/src/main/java/org/jitsi/meet/sdk',
|
||||
iosSrcPath: '../ios/sdk/src',
|
||||
iosDestPath: './ios/src'
|
||||
};
|
||||
8
react-native-sdk/functions.ts
Normal file
8
react-native-sdk/functions.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Converts the meetingOptions domain and roomName to a URL that can be passed to the App component.
|
||||
* @param {*} domain domain address from props.
|
||||
* @param {*} roomName room name from props.
|
||||
*/
|
||||
export function convertPropsToURL(domain, roomName) {
|
||||
return `${domain}/${roomName}`;
|
||||
}
|
||||
26
react-native-sdk/jitsi-meet-react-native.podspec
Normal file
26
react-native-sdk/jitsi-meet-react-native.podspec
Normal file
@@ -0,0 +1,26 @@
|
||||
require 'json'
|
||||
|
||||
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
|
||||
|
||||
Pod::Spec.new do |s|
|
||||
s.name = package['name']
|
||||
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, '12.4'
|
||||
|
||||
s.preserve_paths = 'ios/**/*'
|
||||
s.source_files = 'ios/**/*.{h,m,swift}'
|
||||
|
||||
|
||||
s.dependency 'React-Core'
|
||||
s.dependency 'ObjectiveDropboxOfficial', '6.2.3'
|
||||
s.dependency 'JitsiWebRTC', '~> 111.0.0'
|
||||
|
||||
end
|
||||
6075
react-native-sdk/package-lock.json
generated
Normal file
6075
react-native-sdk/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
107
react-native-sdk/package.json
Normal file
107
react-native-sdk/package.json
Normal file
@@ -0,0 +1,107 @@
|
||||
{
|
||||
"name": "@jitsi/react-native-sdk",
|
||||
"version": "0.1.0",
|
||||
"description": "React Native SDK for Jitsi Meet.",
|
||||
"main": "index.js",
|
||||
"license": "Apache-2.0",
|
||||
"author": "",
|
||||
"homepage": "https://jitsi.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/jitsi/jitsi-meet.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@amplitude/react-native": "2.7.0",
|
||||
"@giphy/react-components": "6.8.1",
|
||||
"@giphy/react-native-sdk": "2.3.0",
|
||||
"@hapi/bourne": "2.0.0",
|
||||
"@jitsi/js-utils": "2.0.5",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
"@jitsi/rtcstats": "9.5.1",
|
||||
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
|
||||
"@react-navigation/bottom-tabs": "6.5.3",
|
||||
"@react-navigation/elements": "1.3.13",
|
||||
"@react-navigation/material-top-tabs": "6.5.2",
|
||||
"@react-navigation/native": "6.1.2",
|
||||
"@react-navigation/stack": "6.3.11",
|
||||
"@xmldom/xmldom": "0.8.7",
|
||||
"base64-js": "1.3.1",
|
||||
"grapheme-splitter": "1.0.4",
|
||||
"i18n-iso-countries": "6.8.0",
|
||||
"i18next": "17.0.6",
|
||||
"i18next-browser-languagedetector": "3.0.1",
|
||||
"i18next-xhr-backend": "3.0.0",
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1643.0.0+0748d89a/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
"optional-require": "1.0.3",
|
||||
"promise.allsettled": "1.0.4",
|
||||
"punycode": "2.1.1",
|
||||
"react-emoji-render": "1.2.4",
|
||||
"react-i18next": "10.11.4",
|
||||
"react-linkify": "1.0.0-alpha",
|
||||
"react-redux": "7.1.0",
|
||||
"react-window": "1.8.6",
|
||||
"react-youtube": "10.1.0",
|
||||
"redux": "4.0.4",
|
||||
"redux-thunk": "2.4.1",
|
||||
"unorm": "1.6.0",
|
||||
"util": "0.12.1",
|
||||
"uuid": "8.3.2",
|
||||
"zxcvbn": "4.4.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-native-async-storage/async-storage": "1.17.3",
|
||||
"@react-native-community/clipboard": "1.5.1",
|
||||
"@react-native-community/netinfo": "7.1.7",
|
||||
"@react-native-community/slider": "4.1.12",
|
||||
"@react-native-google-signin/google-signin": "7.0.4",
|
||||
"@react-native-masked-view/masked-view": "0.2.6",
|
||||
"react-native": "*",
|
||||
"react": "*",
|
||||
"react-dom": "*",
|
||||
"react-native-background-timer": "2.4.1",
|
||||
"react-native-calendar-events": "2.2.0",
|
||||
"react-native-callstats": "3.73.7",
|
||||
"react-native-collapsible": "1.6.0",
|
||||
"react-native-default-preference": "1.4.4",
|
||||
"react-native-device-info": "8.4.8",
|
||||
"react-native-dialog": "https://github.com/jitsi/react-native-dialog/releases/download/v9.2.2-jitsi.1/react-native-dialog-9.2.2.tgz",
|
||||
"react-native-get-random-values": "1.7.2",
|
||||
"react-native-immersive": "2.0.0",
|
||||
"react-native-keep-awake": "4.0.0",
|
||||
"react-native-pager-view": "5.4.9",
|
||||
"react-native-paper": "4.11.1",
|
||||
"react-native-performance": "2.1.0",
|
||||
"react-native-orientation-locker": "1.5.0",
|
||||
"react-native-sound": "0.11.1",
|
||||
"react-native-splash-screen": "3.3.0",
|
||||
"react-native-svg": "12.4.3",
|
||||
"react-native-svg-transformer": "1.0.0",
|
||||
"react-native-tab-view": "3.1.1",
|
||||
"react-native-url-polyfill": "1.3.0",
|
||||
"react-native-video": "https://git@github.com/react-native-video/react-native-video#7c48ae7c8544b2b537fb60194e9620b9fcceae52",
|
||||
"react-native-watch-connectivity": "1.0.11",
|
||||
"react-native-webrtc": "111.0.0",
|
||||
"react-native-webview": "11.15.1",
|
||||
"react-native-youtube-iframe": "2.2.1"
|
||||
},
|
||||
"overrides": {
|
||||
"strophe.js@1.5.0": {
|
||||
"@xmldom/xmldom": "0.8.7"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "node prepare_sdk.js"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/jitsi/jitsi-meet/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"react-native"
|
||||
]
|
||||
}
|
||||
214
react-native-sdk/prepare_sdk.js
vendored
Normal file
214
react-native-sdk/prepare_sdk.js
vendored
Normal file
@@ -0,0 +1,214 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const packageJSON = require('../package.json');
|
||||
|
||||
const {
|
||||
androidSourcePath,
|
||||
androidTargetPath,
|
||||
iosDestPath,
|
||||
iosSrcPath
|
||||
} = require('./constants.ts');
|
||||
const SDKPackageJSON = require('./package.json');
|
||||
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the dependency versions from the root package.json with the dependencies of the SDK package.json.
|
||||
*/
|
||||
function mergeDependencyVersions() {
|
||||
for (const key in SDKPackageJSON.dependencies) {
|
||||
if (SDKPackageJSON.dependencies.hasOwnProperty(key)) {
|
||||
SDKPackageJSON.dependencies[key] = packageJSON.dependencies[key] || packageJSON.devDependencies[key];
|
||||
}
|
||||
}
|
||||
const data = JSON.stringify(SDKPackageJSON, null, 4);
|
||||
|
||||
fs.writeFileSync('package.json', data);
|
||||
}
|
||||
|
||||
// TODO: put this in a seperate step
|
||||
mergeDependencyVersions();
|
||||
|
||||
copyFolderRecursiveSync(
|
||||
'../images',
|
||||
'.'
|
||||
);
|
||||
copyFolderRecursiveSync(
|
||||
'../sounds',
|
||||
'.'
|
||||
);
|
||||
copyFolderRecursiveSync(
|
||||
'../lang',
|
||||
'.'
|
||||
);
|
||||
copyFolderRecursiveSync(
|
||||
'../modules',
|
||||
'.'
|
||||
);
|
||||
copyFolderRecursiveSync(
|
||||
'../react',
|
||||
'.'
|
||||
);
|
||||
copyFolderRecursiveSync(
|
||||
'../service',
|
||||
'.'
|
||||
);
|
||||
copyFolderRecursiveSync(
|
||||
'../ios/sdk/sdk.xcodeproj',
|
||||
'./ios'
|
||||
);
|
||||
copyFolderRecursiveSync(
|
||||
`${iosSrcPath}/callkit`,
|
||||
iosDestPath
|
||||
);
|
||||
copyFolderRecursiveSync(
|
||||
`${iosSrcPath}/dropbox`,
|
||||
iosDestPath
|
||||
);
|
||||
copyFolderRecursiveSync(
|
||||
'../ios/sdk/src/picture-in-picture',
|
||||
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}/JavaScriptSandbox.m`,
|
||||
`${iosDestPath}/JavaScriptSandbox.m`
|
||||
);
|
||||
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}/POSIX.m`,
|
||||
`${iosDestPath}/POSIX.m`
|
||||
);
|
||||
fs.copyFileSync(
|
||||
`${iosSrcPath}/Proximity.m`,
|
||||
`${iosDestPath}/Proximity.m`
|
||||
);
|
||||
copyFolderRecursiveSync(
|
||||
`${androidSourcePath}/log`,
|
||||
`${androidTargetPath}/log`
|
||||
);
|
||||
copyFolderRecursiveSync(
|
||||
`${androidSourcePath}/net`,
|
||||
`${androidTargetPath}/log`
|
||||
);
|
||||
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}/JavaScriptSandboxModule.java`,
|
||||
`${androidTargetPath}/JavaScriptSandboxModule.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`
|
||||
);
|
||||
@@ -10,6 +10,7 @@ import '../mobile/navigation/middleware';
|
||||
import '../mobile/permissions/middleware';
|
||||
import '../mobile/proximity/middleware';
|
||||
import '../mobile/wake-lock/middleware';
|
||||
import '../mobile/react-native-sdk/middleware';
|
||||
import '../mobile/watchos/middleware';
|
||||
import '../share-room/middleware';
|
||||
import '../shared-video/middleware';
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import debounce from 'lodash/debounce';
|
||||
import { NativeModules } from 'react-native';
|
||||
|
||||
import { IParticipant } from '../../base/participants/types';
|
||||
|
||||
import { readyToClose } from './actions';
|
||||
|
||||
|
||||
@@ -25,3 +27,21 @@ export function sendEvent(store: Object, name: string, data: Object) {
|
||||
export const _sendReadyToClose = debounce(dispatch => {
|
||||
dispatch(readyToClose());
|
||||
}, 2500, { leading: true });
|
||||
|
||||
/**
|
||||
* Returns a participant info object based on the passed participant object from redux.
|
||||
*
|
||||
* @param {Participant} participant - The participant object from the redux store.
|
||||
* @returns {Object} - The participant info object.
|
||||
*/
|
||||
export function participantToParticipantInfo(participant: IParticipant) {
|
||||
return {
|
||||
isLocal: participant.local,
|
||||
email: participant.email,
|
||||
name: participant.name,
|
||||
participantId: participant.id,
|
||||
displayName: participant.displayName,
|
||||
avatarUrl: participant.avatarURL,
|
||||
role: participant.role
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
|
||||
import debounce from 'lodash/debounce';
|
||||
import { NativeEventEmitter, NativeModules } from 'react-native';
|
||||
import { AnyAction } from 'redux';
|
||||
|
||||
// @ts-expect-error
|
||||
import { ENDPOINT_TEXT_MESSAGE_NAME } from '../../../../modules/API/constants';
|
||||
import { appNavigate } from '../../app/actions';
|
||||
import { appNavigate } from '../../app/actions.native';
|
||||
import { IStore } from '../../app/types';
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../base/app/actionTypes';
|
||||
import {
|
||||
@@ -35,25 +37,24 @@ import { PARTICIPANT_JOINED, PARTICIPANT_LEFT } from '../../base/participants/ac
|
||||
import {
|
||||
getLocalParticipant,
|
||||
getParticipantById,
|
||||
getRemoteParticipants,
|
||||
isScreenShareParticipant
|
||||
getRemoteParticipants
|
||||
} from '../../base/participants/functions';
|
||||
import { IParticipant } from '../../base/participants/types';
|
||||
import MiddlewareRegistry from '../../base/redux/MiddlewareRegistry';
|
||||
import StateListenerRegistry from '../../base/redux/StateListenerRegistry';
|
||||
import { toggleScreensharing } from '../../base/tracks/actions.native';
|
||||
import { getLocalTracks, isLocalTrackMuted } from '../../base/tracks/functions.native';
|
||||
import { ITrack } from '../../base/tracks/types';
|
||||
import { CLOSE_CHAT, OPEN_CHAT } from '../../chat/actionTypes';
|
||||
import { openChat } from '../../chat/actions';
|
||||
import { closeChat, sendMessage, setPrivateMessageRecipient } from '../../chat/actions.any';
|
||||
import { closeChat, openChat, sendMessage, setPrivateMessageRecipient } from '../../chat/actions.native';
|
||||
import { setRequestingSubtitles } from '../../subtitles/actions.any';
|
||||
import { muteLocal } from '../../video-menu/actions';
|
||||
import { muteLocal } from '../../video-menu/actions.native';
|
||||
import { ENTER_PICTURE_IN_PICTURE } from '../picture-in-picture/actionTypes';
|
||||
// @ts-ignore
|
||||
import { isExternalAPIAvailable } from '../react-native-sdk/functions';
|
||||
|
||||
import { READY_TO_CLOSE } from './actionTypes';
|
||||
import { setParticipantsWithScreenShare } from './actions';
|
||||
import { sendEvent } from './functions';
|
||||
import { participantToParticipantInfo, sendEvent } from './functions';
|
||||
import logger from './logger';
|
||||
|
||||
/**
|
||||
@@ -90,8 +91,15 @@ const SCREEN_SHARE_TOGGLED = 'SCREEN_SHARE_TOGGLED';
|
||||
*/
|
||||
const PARTICIPANTS_INFO_RETRIEVED = 'PARTICIPANTS_INFO_RETRIEVED';
|
||||
|
||||
const externalAPIEnabled = isExternalAPIAvailable();
|
||||
|
||||
let eventEmitter: any;
|
||||
|
||||
const { ExternalAPI } = NativeModules;
|
||||
const eventEmitter = new NativeEventEmitter(ExternalAPI);
|
||||
|
||||
if (externalAPIEnabled) {
|
||||
eventEmitter = new NativeEventEmitter(ExternalAPI);
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware that captures Redux actions and uses the ExternalAPI module to
|
||||
@@ -100,7 +108,7 @@ const eventEmitter = new NativeEventEmitter(ExternalAPI);
|
||||
* @param {Store} store - Redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
externalAPIEnabled && MiddlewareRegistry.register(store => next => action => {
|
||||
const oldAudioMuted = store.getState()['features/base/media'].audio.muted;
|
||||
const result = next(action);
|
||||
const { type } = action;
|
||||
@@ -144,7 +152,8 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
|
||||
case CONNECTION_DISCONNECTED: {
|
||||
// FIXME: This is a hack. See the description in the JITSI_CONNECTION_CONFERENCE_KEY constant definition.
|
||||
// Check if this connection was attached to any conference. If it wasn't, fake a CONFERENCE_TERMINATED event.
|
||||
// Check if this connection was attached to any conference.
|
||||
// If it wasn't, fake a CONFERENCE_TERMINATED event.
|
||||
const { connection } = action;
|
||||
const conference = connection[JITSI_CONNECTION_CONFERENCE_KEY];
|
||||
|
||||
@@ -189,14 +198,14 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
|
||||
const { participant } = action;
|
||||
|
||||
if (isScreenShareParticipant(participant)) {
|
||||
if (participant?.isVirtualScreenshareParticipant) {
|
||||
break;
|
||||
}
|
||||
|
||||
sendEvent(
|
||||
store,
|
||||
action.type,
|
||||
_participantToParticipantInfo(participant) /* data */
|
||||
participantToParticipantInfo(participant) /* data */
|
||||
);
|
||||
break;
|
||||
}
|
||||
@@ -239,7 +248,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
* The listener is debounced to avoid state thrashing that might occur,
|
||||
* especially when switching in or out of p2p.
|
||||
*/
|
||||
StateListenerRegistry.register(
|
||||
externalAPIEnabled && StateListenerRegistry.register(
|
||||
/* selector */ state => state['features/base/tracks'],
|
||||
/* listener */ debounce((tracks: ITrack[], store: IStore) => {
|
||||
const oldScreenShares = store.getState()['features/mobile/external-api'].screenShares || [];
|
||||
@@ -275,24 +284,6 @@ StateListenerRegistry.register(
|
||||
|
||||
}, 100));
|
||||
|
||||
/**
|
||||
* Returns a participant info object based on the passed participant object from redux.
|
||||
*
|
||||
* @param {Participant} participant - The participant object from the redux store.
|
||||
* @returns {Object} - The participant info object.
|
||||
*/
|
||||
function _participantToParticipantInfo(participant: IParticipant) {
|
||||
return {
|
||||
isLocal: participant.local,
|
||||
email: participant.email,
|
||||
name: participant.name,
|
||||
participantId: participant.id,
|
||||
displayName: participant.displayName,
|
||||
avatarUrl: participant.avatarURL,
|
||||
role: participant.role
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers for events sent from the native side via NativeEventEmitter.
|
||||
*
|
||||
@@ -307,15 +298,15 @@ function _registerForNativeEvents(store: IStore) {
|
||||
dispatch(appNavigate(undefined));
|
||||
});
|
||||
|
||||
eventEmitter.addListener(ExternalAPI.SET_AUDIO_MUTED, ({ muted }) => {
|
||||
eventEmitter.addListener(ExternalAPI.SET_AUDIO_MUTED, ({ muted }: any) => {
|
||||
dispatch(muteLocal(muted, MEDIA_TYPE.AUDIO));
|
||||
});
|
||||
|
||||
eventEmitter.addListener(ExternalAPI.SET_VIDEO_MUTED, ({ muted }) => {
|
||||
eventEmitter.addListener(ExternalAPI.SET_VIDEO_MUTED, ({ muted }: any) => {
|
||||
dispatch(muteLocal(muted, MEDIA_TYPE.VIDEO));
|
||||
});
|
||||
|
||||
eventEmitter.addListener(ExternalAPI.SEND_ENDPOINT_TEXT_MESSAGE, ({ to, message }) => {
|
||||
eventEmitter.addListener(ExternalAPI.SEND_ENDPOINT_TEXT_MESSAGE, ({ to, message }: any) => {
|
||||
const conference = getCurrentConference(getState());
|
||||
|
||||
try {
|
||||
@@ -328,20 +319,20 @@ function _registerForNativeEvents(store: IStore) {
|
||||
}
|
||||
});
|
||||
|
||||
eventEmitter.addListener(ExternalAPI.TOGGLE_SCREEN_SHARE, ({ enabled }) => {
|
||||
eventEmitter.addListener(ExternalAPI.TOGGLE_SCREEN_SHARE, ({ enabled }: any) => {
|
||||
dispatch(toggleScreensharing(enabled));
|
||||
});
|
||||
|
||||
eventEmitter.addListener(ExternalAPI.RETRIEVE_PARTICIPANTS_INFO, ({ requestId }) => {
|
||||
eventEmitter.addListener(ExternalAPI.RETRIEVE_PARTICIPANTS_INFO, ({ requestId }: any) => {
|
||||
|
||||
const participantsInfo = [];
|
||||
const remoteParticipants = getRemoteParticipants(store);
|
||||
const localParticipant = getLocalParticipant(store);
|
||||
|
||||
localParticipant && participantsInfo.push(_participantToParticipantInfo(localParticipant));
|
||||
localParticipant && participantsInfo.push(participantToParticipantInfo(localParticipant));
|
||||
remoteParticipants.forEach(participant => {
|
||||
if (!participant.fakeParticipant) {
|
||||
participantsInfo.push(_participantToParticipantInfo(participant));
|
||||
participantsInfo.push(participantToParticipantInfo(participant));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -354,7 +345,7 @@ function _registerForNativeEvents(store: IStore) {
|
||||
});
|
||||
});
|
||||
|
||||
eventEmitter.addListener(ExternalAPI.OPEN_CHAT, ({ to }) => {
|
||||
eventEmitter.addListener(ExternalAPI.OPEN_CHAT, ({ to }: any) => {
|
||||
const participant = getParticipantById(store, to);
|
||||
|
||||
dispatch(openChat(participant));
|
||||
@@ -364,7 +355,7 @@ function _registerForNativeEvents(store: IStore) {
|
||||
dispatch(closeChat());
|
||||
});
|
||||
|
||||
eventEmitter.addListener(ExternalAPI.SEND_CHAT_MESSAGE, ({ message, to }) => {
|
||||
eventEmitter.addListener(ExternalAPI.SEND_CHAT_MESSAGE, ({ message, to }: any) => {
|
||||
const participant = getParticipantById(store, to);
|
||||
|
||||
if (participant) {
|
||||
@@ -374,7 +365,7 @@ function _registerForNativeEvents(store: IStore) {
|
||||
dispatch(sendMessage(message));
|
||||
});
|
||||
|
||||
eventEmitter.addListener(ExternalAPI.SET_CLOSED_CAPTIONS_ENABLED, ({ enabled }) => {
|
||||
eventEmitter.addListener(ExternalAPI.SET_CLOSED_CAPTIONS_ENABLED, ({ enabled }: any) => {
|
||||
dispatch(setRequestingSubtitles(enabled));
|
||||
});
|
||||
}
|
||||
|
||||
13
react/features/mobile/react-native-sdk/functions.js
vendored
Normal file
13
react/features/mobile/react-native-sdk/functions.js
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
import { NativeModules } from 'react-native';
|
||||
|
||||
|
||||
/**
|
||||
* Determimes if the ExternalAPI native module is available.
|
||||
*
|
||||
* @returns {boolean} If yes {@code true} otherwise {@code false}.
|
||||
*/
|
||||
export function isExternalAPIAvailable() {
|
||||
const { ExternalAPI } = NativeModules;
|
||||
|
||||
return ExternalAPI !== null;
|
||||
}
|
||||
50
react/features/mobile/react-native-sdk/middleware.js
vendored
Normal file
50
react/features/mobile/react-native-sdk/middleware.js
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
import { getAppProp } from '../../base/app/functions';
|
||||
import {
|
||||
CONFERENCE_JOINED,
|
||||
CONFERENCE_LEFT,
|
||||
CONFERENCE_WILL_JOIN
|
||||
} from '../../base/conference/actionTypes';
|
||||
import { PARTICIPANT_JOINED } from '../../base/participants/actionTypes';
|
||||
import MiddlewareRegistry from '../../base/redux/MiddlewareRegistry';
|
||||
import { READY_TO_CLOSE } from '../external-api/actionTypes';
|
||||
import { participantToParticipantInfo } from '../external-api/functions';
|
||||
|
||||
import { isExternalAPIAvailable } from './functions';
|
||||
|
||||
const externalAPIEnabled = isExternalAPIAvailable();
|
||||
|
||||
/**
|
||||
* Check if native modules are being used or not. If not then the init of middleware doesn't happen.
|
||||
*/
|
||||
!externalAPIEnabled && MiddlewareRegistry.register(store => next => action => {
|
||||
const result = next(action);
|
||||
const { type } = action;
|
||||
const rnSdkHandlers = getAppProp(store, 'rnSdkHandlers');
|
||||
|
||||
switch (type) {
|
||||
case READY_TO_CLOSE:
|
||||
rnSdkHandlers.onReadyToClose && rnSdkHandlers.onReadyToClose();
|
||||
break;
|
||||
case CONFERENCE_JOINED:
|
||||
rnSdkHandlers.onConferenceJoined && rnSdkHandlers.onConferenceJoined();
|
||||
break;
|
||||
case CONFERENCE_WILL_JOIN:
|
||||
rnSdkHandlers.onConferenceWillJoin && rnSdkHandlers.onConferenceWillJoin();
|
||||
break;
|
||||
case CONFERENCE_LEFT:
|
||||
// Props are torn down at this point, perhaps need to leave this one out
|
||||
break;
|
||||
case PARTICIPANT_JOINED: {
|
||||
const { participant } = action;
|
||||
const participantInfo = participantToParticipantInfo(participant);
|
||||
|
||||
rnSdkHandlers.onParticipantJoined && rnSdkHandlers.onParticipantJoined(participantInfo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user