Compare commits

...

21 Commits

Author SHA1 Message Date
Saúl Ibarra Corretgé
d490b29b7a deps: replace node-sass with sass
The former is no longer actively maintained.

Fixes: https://github.com/jitsi/jitsi-meet/issues/6427
2020-09-17 16:24:57 +02:00
Saúl Ibarra Corretgé
ca306f47b6 deps: react-native-background-timer@2.4.0
We hadn't updated in a while.
2020-09-17 13:15:04 +02:00
Saúl Ibarra Corretgé
56da400f19 ios: stop using react-native-background-timer
Ever since https://github.com/facebook/react-native/pull/23674 landed it has
been possible to run timers in the background, assuming your app is allowed to
run in the background already, as is our case. So, stop using the library on
iOS, which will avoid creatring needless backgound tasks.
2020-09-17 13:15:04 +02:00
Manuel Garcia
ab21e3cd5e fix(embed): remove legacy attribute from embed meeting code 2020-09-17 09:11:41 +02:00
damencho
2c026754ef fix: Fix ws reconnect piling up previd param. 2020-09-16 16:43:48 -05:00
Jaya Allamsetty
8dbe3e37b9 feat(iFrame): add a method for getting the participants info 2020-09-16 15:03:47 -04:00
Tudor-Ovidiu Avram
7f67f78db6 fix(embed) fix embed meeting code 2020-09-16 06:59:41 -05:00
Saúl Ibarra Corretgé
312949eef6 ios: update Crashlytics dependency 2020-09-15 21:22:50 +02:00
Saúl Ibarra Corretgé
41ea94c0c2 android: update AndroidX core library dependencies 2020-09-15 21:22:50 +02:00
Saúl Ibarra Corretgé
e70adef2ef android: update Crashlytics dependency 2020-09-15 21:22:50 +02:00
Saúl Ibarra Corretgé
57bbe3f75a android: fix crash when requesting permissions
The RN Permissions module calls this in a non-UI thread. What we observe is a
crash in ViewGroup.dispatchCancelPendingInputEvents, which is called on the
calling (ie, non-UI) thread. This doesn't look very safe, so try to avoid a
crash by pretending the permission was denied.
2020-09-15 16:17:46 +02:00
Tudor-Ovidiu Avram
e2731ce73e feat(loggin) forward logs to external api 2020-09-15 09:44:50 +02:00
yjhgull
d5dae945a8 lang: update Korean translation 2020-09-15 09:33:35 +02:00
Jaya Allamsetty
4d1dba937f feat(external_api): Add method for displaying participant on large video 2020-09-14 19:39:19 -04:00
Jaya Allamsetty
b6792db65f feat(external_api): Add cmd for selecting a user to be displayed in large video 2020-09-14 19:39:19 -04:00
Saúl Ibarra Corretgé
9815b633fc deps: lib-jitsi-meet@latest
Avoids CORS issues with the E2EE worker.
2020-09-11 16:13:12 +02:00
Joris Bodin
b4bf82429c lang: update French translation 2020-09-11 10:07:41 +02:00
Aaron van Meerten
53d485b397 Merge pull request #7679 from jitsi/mod-token-update
fix: Updates docs and verification to halt joining process.
2020-09-10 12:46:28 -05:00
damencho
0354dbe889 fix: Updates docs and verification to halt joining process.
When returning the error and showing to user not allowed screen we were not completely halting the prejoin operation when token verification fails on room join and the token is valid in general.
2020-09-10 10:07:30 -05:00
Saúl Ibarra Corretgé
7cafa205ee e2ee: stage 2
Adapt to E2EE changes in lib-jitsi-meet. Notably:

---
    e2ee: introduce per-participant randomly generated keys

    This the second stage in our E2EE journey.

    Instead of using a single pre-shared passphrase for deriving the key used for
    E2EE, we now establish a secure E2EE communication channel amongst peers.

    This channel is implemented using libolm, using XMPP groupchat or JVB channels
    as the transport.

    Once the secure E2EE channel has been established each participant will generate
    a random 32 byte key and exchange it over this channel.

    Keys are rotated (well, just re-created at the moment) when a participant joins
    or leaves.
---
2020-09-10 16:06:25 +02:00
Saúl Ibarra Corretgé
2b4f33bef8 e2ee: use a separate bundle for the worker 2020-09-10 16:06:25 +02:00
37 changed files with 611 additions and 1604 deletions

View File

@@ -3,8 +3,9 @@ CLEANCSS = ./node_modules/.bin/cleancss
DEPLOY_DIR = libs
LIBJITSIMEET_DIR = node_modules/lib-jitsi-meet/
LIBFLAC_DIR = node_modules/libflacjs/dist/min/
OLM_DIR = node_modules/olm
RNNOISE_WASM_DIR = node_modules/rnnoise-wasm/dist/
NODE_SASS = ./node_modules/.bin/node-sass
NODE_SASS = ./node_modules/.bin/sass
NPM = npm
OUTPUT_DIR = .
STYLES_BUNDLE = css/all.bundle.css
@@ -22,7 +23,7 @@ clean:
rm -fr $(BUILD_DIR)
.NOTPARALLEL:
deploy: deploy-init deploy-appbundle deploy-rnnoise-binary deploy-lib-jitsi-meet deploy-libflac deploy-css deploy-local
deploy: deploy-init deploy-appbundle deploy-rnnoise-binary deploy-lib-jitsi-meet deploy-libflac deploy-olm deploy-css deploy-local
deploy-init:
rm -fr $(DEPLOY_DIR)
@@ -59,6 +60,7 @@ deploy-lib-jitsi-meet:
cp \
$(LIBJITSIMEET_DIR)/lib-jitsi-meet.min.js \
$(LIBJITSIMEET_DIR)/lib-jitsi-meet.min.map \
$(LIBJITSIMEET_DIR)/lib-jitsi-meet.e2ee-worker.js \
$(LIBJITSIMEET_DIR)/connection_optimization/external_connect.js \
$(LIBJITSIMEET_DIR)/modules/browser/capabilities.json \
$(DEPLOY_DIR)
@@ -69,6 +71,11 @@ deploy-libflac:
$(LIBFLAC_DIR)/libflac4-1.3.2.min.js.mem \
$(DEPLOY_DIR)
deploy-olm:
cp \
$(OLM_DIR)/olm.wasm \
$(DEPLOY_DIR)
deploy-rnnoise-binary:
cp \
$(RNNOISE_WASM_DIR)/rnnoise.wasm \
@@ -83,7 +90,7 @@ deploy-local:
([ ! -x deploy-local.sh ] || ./deploy-local.sh)
.NOTPARALLEL:
dev: deploy-init deploy-css deploy-rnnoise-binary deploy-lib-jitsi-meet deploy-libflac
dev: deploy-init deploy-css deploy-rnnoise-binary deploy-lib-jitsi-meet deploy-libflac deploy-olm
$(WEBPACK_DEV_SERVER) --detect-circular-deps
source-package:

View File

@@ -3,7 +3,7 @@ apply plugin: 'com.android.application'
// Crashlytics integration is done as part of Firebase now, so it gets
// automagically activated with google-services.json
if (googleServicesEnabled) {
apply plugin: 'io.fabric'
apply plugin: 'com.google.firebase.crashlytics'
}
// Use the number of seconds/10 since Jan 1 2019 as the versionCode.
@@ -70,14 +70,9 @@ android {
}
}
repositories {
maven { url 'https://maven.fabric.io/public' }
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.appcompat:appcompat:1.2.0'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-beta-5'
@@ -87,9 +82,8 @@ dependencies {
// Firebase
// - Crashlytics
// - Dynamic Links
implementation 'com.google.firebase:firebase-core:16.0.6'
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.8'
implementation 'com.google.firebase:firebase-dynamic-links:16.1.5'
implementation 'com.google.firebase:firebase-crashlytics:17.2.1'
implementation 'com.google.firebase:firebase-dynamic-links:19.1.0'
}
implementation project(':sdk')

View File

@@ -3,9 +3,8 @@ package org.jitsi.meet;
import android.net.Uri;
import android.util.Log;
import com.crashlytics.android.Crashlytics;
import com.google.firebase.crashlytics.FirebaseCrashlytics;
import com.google.firebase.dynamiclinks.FirebaseDynamicLinks;
import io.fabric.sdk.android.Fabric;
import org.jitsi.meet.sdk.JitsiMeet;
import org.jitsi.meet.sdk.JitsiMeetActivity;
@@ -22,10 +21,7 @@ final class GoogleServicesHelper {
if (BuildConfig.GOOGLE_SERVICES_ENABLED) {
Log.d(activity.getClass().getSimpleName(), "Initializing Google Services");
if (!JitsiMeet.isCrashReportingDisabled(activity)) {
Fabric.with(activity, new Crashlytics());
}
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(!JitsiMeet.isCrashReportingDisabled(activity));
FirebaseDynamicLinks.getInstance().getDynamicLink(activity.getIntent())
.addOnSuccessListener(activity, pendingDynamicLinkData -> {
Uri dynamicLink = null;

View File

@@ -7,17 +7,11 @@ buildscript {
repositories {
google()
jcenter()
repositories {
maven { url 'https://maven.fabric.io/public' }
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.2'
classpath 'com.google.gms:google-services:4.3.3'
classpath 'io.fabric.tools:gradle:1.28.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files.
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.3.0'
}
}

View File

@@ -44,9 +44,9 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.fragment:fragment:1.2.0'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.fragment:fragment:1.2.5'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
//noinspection GradleDynamicVersion
api 'com.facebook.react:react-native:+'

View File

@@ -24,6 +24,8 @@ import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.core.PermissionListener;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
/**
* Helper class to encapsulate the work which needs to be done on
* {@link Activity} lifecycle methods in order for the React side to be aware of
@@ -177,6 +179,16 @@ public class JitsiMeetActivityDelegate {
public static void requestPermissions(Activity activity, String[] permissions, int requestCode, PermissionListener listener) {
permissionListener = listener;
activity.requestPermissions(permissions, requestCode);
// The RN Permissions module calls this in a non-UI thread. What we observe is a crash in ViewGroup.dispatchCancelPendingInputEvents,
// which is called on the calling (ie, non-UI) thread. This doesn't look very safe, so try to avoid a crash by pretending the permission
// was denied.
try {
activity.requestPermissions(permissions, requestCode);
} catch (Exception e) {
JitsiMeetLogger.e(e, "Error requesting permissions");
onRequestPermissionsResult(requestCode, permissions, new int[0]);
}
}
}

7
app.js
View File

@@ -4,6 +4,8 @@ import 'jquery';
import 'jquery-contextmenu';
import 'jQuery-Impromptu';
import 'olm';
import conference from './conference';
import API from './modules/API';
import UI from './modules/UI/UI';
@@ -11,6 +13,11 @@ import keyboardshortcut from './modules/keyboardshortcut/keyboardshortcut';
import remoteControl from './modules/remotecontrol/RemoteControl';
import translation from './modules/translation/translation';
// Initialize Olm as early as possible.
if (window.Olm) {
window.Olm.init();
}
window.APP = {
API,
conference,

View File

@@ -110,7 +110,6 @@ import {
} from './react/features/base/util';
import { showDesktopPicker } from './react/features/desktop-picker';
import { appendSuffix } from './react/features/display-name';
import { setE2EEKey } from './react/features/e2ee';
import {
maybeOpenFeedbackDialog,
submitFeedback
@@ -746,8 +745,6 @@ export default {
this.roomName = roomName;
window.addEventListener('hashchange', this.onHashChange.bind(this), false);
try {
// Initialize the device list first. This way, when creating tracks
// based on preferred devices, loose label matching can be done in
@@ -1239,34 +1236,6 @@ export default {
}));
},
/**
* Handled location hash change events.
*/
onHashChange() {
const items = {};
const parts = window.location.hash.substr(1).split('&');
for (const part of parts) {
const param = part.split('=');
const key = param[0];
if (!key) {
continue; // eslint-disable-line no-continue
}
items[key] = param[1];
}
if (typeof items.e2eekey !== 'undefined') {
APP.store.dispatch(setE2EEKey(items.e2eekey));
// Clean URL in browser history.
const cleanUrl = window.location.href.split('#')[0];
history.replaceState(history.state, document.title, cleanUrl);
}
},
/**
* Exposes a Command(s) API on this instance. It is necessitated by (1) the
* desire to keep room private to this instance and (2) the need of other

View File

@@ -510,6 +510,9 @@ var config = {
// ],
},
// Logs that should go be passed through the 'log' event if a handler is defined for it
// apiLogLevels: ['warn', 'log', 'error', 'info', 'debug'],
// Information about the jitsi-meet instance we are connecting to, including
// the user region as seen by the server.
deploymentInfo: {

View File

@@ -1,7 +1,6 @@
#e2ee-section {
.title {
font-weight: 700;
}
display: flex;
flex-direction: column;
.description {
font-size: 13px;
@@ -13,29 +12,15 @@
}
}
.key-field {
align-items: center;
.control-row {
display: flex;
flex-direction: row;
justify-content: space-between;
margin-top: 15px;
label {
font-size: 14px;
font-weight: 700;
}
input {
background-color: inherit;
border: none;
color: inherit;
flex: 1;
padding: 0 5px;
}
a {
color: #6FB1EA;
cursor: pointer;
font-size: 14px;
text-decoration: none;
font-weight: bold;
}
}
}

View File

@@ -5,10 +5,8 @@ require_relative '../node_modules/@react-native-community/cli-platform-ios/nativ
target 'jitsi-meet' do
project 'app/app.xcodeproj'
pod 'Crashlytics', '~> 3.14.0'
pod 'Fabric', '~> 1.10.2'
pod 'Firebase/Core', '~> 6.16.0'
pod 'Firebase/DynamicLinks', '~> 6.16.0'
pod 'Firebase/Crashlytics', '~> 6.24.0'
pod 'Firebase/DynamicLinks', '~> 6.24.0'
end
target 'JitsiMeet' do

View File

@@ -11,10 +11,7 @@ PODS:
- CocoaLumberjack (3.5.3):
- CocoaLumberjack/Core (= 3.5.3)
- CocoaLumberjack/Core (3.5.3)
- Crashlytics (3.14.0):
- Fabric (~> 1.10.2)
- DoubleConversion (1.1.6)
- Fabric (1.10.2)
- FBLazyVector (0.61.5-jitsi.1)
- FBReactNativeSpec (0.61.5-jitsi.1):
- Folly (= 2018.10.22.00)
@@ -23,48 +20,43 @@ PODS:
- React-Core (= 0.61.5-jitsi.1)
- React-jsi (= 0.61.5-jitsi.1)
- ReactCommon/turbomodule/core (= 0.61.5-jitsi.1)
- Firebase/Core (6.16.0):
- Firebase/CoreOnly (6.24.0):
- FirebaseCore (= 6.7.0)
- Firebase/Crashlytics (6.24.0):
- Firebase/CoreOnly
- FirebaseAnalytics (= 6.2.2)
- Firebase/CoreOnly (6.16.0):
- FirebaseCore (= 6.6.1)
- Firebase/DynamicLinks (6.16.0):
- FirebaseCrashlytics (~> 4.1.0)
- Firebase/DynamicLinks (6.24.0):
- Firebase/CoreOnly
- FirebaseDynamicLinks (~> 4.0.6)
- FirebaseAnalytics (6.2.2):
- FirebaseCore (~> 6.6)
- FirebaseInstanceID (~> 4.3)
- GoogleAppMeasurement (= 6.2.2)
- GoogleUtilities/AppDelegateSwizzler (~> 6.0)
- GoogleUtilities/MethodSwizzler (~> 6.0)
- GoogleUtilities/Network (~> 6.0)
- "GoogleUtilities/NSData+zlib (~> 6.0)"
- nanopb (= 0.3.9011)
- FirebaseDynamicLinks (~> 4.0.8)
- FirebaseAnalyticsInterop (1.5.0)
- FirebaseCore (6.6.1):
- FirebaseCoreDiagnostics (~> 1.2)
- FirebaseCore (6.7.0):
- FirebaseCoreDiagnostics (~> 1.3)
- FirebaseCoreDiagnosticsInterop (~> 1.2)
- GoogleUtilities/Environment (~> 6.5)
- GoogleUtilities/Logger (~> 6.5)
- FirebaseCoreDiagnostics (1.2.2):
- FirebaseCoreDiagnostics (1.3.0):
- FirebaseCoreDiagnosticsInterop (~> 1.2)
- GoogleDataTransportCCTSupport (~> 2.0)
- GoogleDataTransportCCTSupport (~> 3.1)
- GoogleUtilities/Environment (~> 6.5)
- GoogleUtilities/Logger (~> 6.5)
- nanopb (~> 0.3.901)
- nanopb (~> 1.30905.0)
- FirebaseCoreDiagnosticsInterop (1.2.0)
- FirebaseCrashlytics (4.1.1):
- FirebaseAnalyticsInterop (~> 1.2)
- FirebaseCore (~> 6.6)
- FirebaseInstallations (~> 1.1)
- GoogleDataTransport (~> 6.1)
- GoogleDataTransportCCTSupport (~> 3.1)
- nanopb (~> 1.30905.0)
- PromisesObjC (~> 1.2)
- FirebaseDynamicLinks (4.0.8):
- FirebaseAnalyticsInterop (~> 1.3)
- FirebaseCore (~> 6.2)
- FirebaseInstallations (1.1.1):
- FirebaseInstallations (1.2.0):
- FirebaseCore (~> 6.6)
- GoogleUtilities/UserDefaults (~> 6.5)
- GoogleUtilities/Environment (~> 6.6)
- GoogleUtilities/UserDefaults (~> 6.6)
- PromisesObjC (~> 1.2)
- FirebaseInstanceID (4.3.2):
- FirebaseCore (~> 6.6)
- FirebaseInstallations (~> 1.0)
- GoogleUtilities/Environment (~> 6.5)
- GoogleUtilities/UserDefaults (~> 6.5)
- Folly (2018.10.22.00):
- boost-for-react-native
- DoubleConversion
@@ -75,37 +67,19 @@ PODS:
- DoubleConversion
- glog
- glog (0.3.5)
- GoogleAppMeasurement (6.2.2):
- GoogleUtilities/AppDelegateSwizzler (~> 6.0)
- GoogleUtilities/MethodSwizzler (~> 6.0)
- GoogleUtilities/Network (~> 6.0)
- "GoogleUtilities/NSData+zlib (~> 6.0)"
- nanopb (= 0.3.9011)
- GoogleDataTransport (5.1.0)
- GoogleDataTransportCCTSupport (2.0.1):
- GoogleDataTransport (~> 5.1)
- nanopb (~> 0.3.901)
- GoogleDataTransport (6.1.0)
- GoogleDataTransportCCTSupport (3.1.0):
- GoogleDataTransport (~> 6.1)
- nanopb (~> 1.30905.0)
- GoogleSignIn (5.0.1):
- AppAuth (~> 1.2)
- GTMAppAuth (~> 1.0)
- GTMSessionFetcher/Core (~> 1.1)
- GoogleUtilities/AppDelegateSwizzler (6.5.2):
- GoogleUtilities/Environment (6.6.0):
- PromisesObjC (~> 1.2)
- GoogleUtilities/Logger (6.6.0):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
- GoogleUtilities/Network
- GoogleUtilities/Environment (6.5.2)
- GoogleUtilities/Logger (6.5.2):
- GoogleUtilities/Environment
- GoogleUtilities/MethodSwizzler (6.5.2):
- GoogleUtilities/Logger
- GoogleUtilities/Network (6.5.2):
- GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib"
- GoogleUtilities/Reachability
- "GoogleUtilities/NSData+zlib (6.5.2)"
- GoogleUtilities/Reachability (6.5.2):
- GoogleUtilities/Logger
- GoogleUtilities/UserDefaults (6.5.2):
- GoogleUtilities/UserDefaults (6.6.0):
- GoogleUtilities/Logger
- GTMAppAuth (1.0.0):
- AppAuth/Core (~> 1.0)
@@ -115,11 +89,11 @@ PODS:
- GTMSessionFetcher/Core (1.2.2)
- GTMSessionFetcher/Full (1.2.2):
- GTMSessionFetcher/Core (= 1.2.2)
- nanopb (0.3.9011):
- nanopb/decode (= 0.3.9011)
- nanopb/encode (= 0.3.9011)
- nanopb/decode (0.3.9011)
- nanopb/encode (0.3.9011)
- nanopb (1.30905.0):
- nanopb/decode (= 1.30905.0)
- nanopb/encode (= 1.30905.0)
- nanopb/decode (1.30905.0)
- nanopb/encode (1.30905.0)
- ObjectiveDropboxOfficial (3.9.4)
- PromisesObjC (1.2.8)
- RCTRequired (0.61.5-jitsi.1)
@@ -285,7 +259,7 @@ PODS:
- React-cxxreact (= 0.61.5-jitsi.1)
- React-jsi (= 0.61.5-jitsi.1)
- React-jsinspector (0.61.5-jitsi.1)
- react-native-background-timer (2.1.1):
- react-native-background-timer (2.4.0):
- React
- react-native-calendar-events (2.0.0):
- React
@@ -373,13 +347,11 @@ DEPENDENCIES:
- Amplitude-iOS (~> 4.0.4)
- BVLinearGradient (from `../node_modules/react-native-linear-gradient`)
- CocoaLumberjack (~> 3.5.3)
- Crashlytics (~> 3.14.0)
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
- Fabric (~> 1.10.2)
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector/`)
- FBReactNativeSpec (from `../node_modules/react-native/Libraries/FBReactNativeSpec/`)
- Firebase/Core (~> 6.16.0)
- Firebase/DynamicLinks (~> 6.16.0)
- Firebase/Crashlytics (~> 6.24.0)
- Firebase/DynamicLinks (~> 6.24.0)
- Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`)
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
- ObjectiveDropboxOfficial (~> 3.9.4)
@@ -424,18 +396,14 @@ SPEC REPOS:
- AppAuth
- boost-for-react-native
- CocoaLumberjack
- Crashlytics
- Fabric
- Firebase
- FirebaseAnalytics
- FirebaseAnalyticsInterop
- FirebaseCore
- FirebaseCoreDiagnostics
- FirebaseCoreDiagnosticsInterop
- FirebaseCrashlytics
- FirebaseDynamicLinks
- FirebaseInstallations
- FirebaseInstanceID
- GoogleAppMeasurement
- GoogleDataTransport
- GoogleDataTransportCCTSupport
- GoogleSignIn
@@ -530,30 +498,26 @@ SPEC CHECKSUMS:
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
BVLinearGradient: e3aad03778a456d77928f594a649e96995f1c872
CocoaLumberjack: 2f44e60eb91c176d471fdba43b9e3eae6a721947
Crashlytics: 540b7e5f5da5a042647227a5e3ac51d85eed06df
DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2
Fabric: 706c8b8098fff96c33c0db69cbf81f9c551d0d74
FBLazyVector: 4a5251159a3ed05dc11cc8b74cf937869935814b
FBReactNativeSpec: 6fa602a20993212cc9877a81838578ffb0008bc9
Firebase: 497158b816d0a86fc31babbd05546fcd7e6083ff
FirebaseAnalytics: cf95d3aab897612783020fbd98401d5366f135ee
Firebase: b28e55c60efd98963cd9011fe2fac5a10c2ba124
FirebaseAnalyticsInterop: 3f86269c38ae41f47afeb43ebf32a001f58fcdae
FirebaseCore: 85064903ed6c28e47fec9c7bd149d94ba1b6b6e7
FirebaseCoreDiagnostics: e9b4cd8ba60dee0f2d13347332e4b7898cca5b61
FirebaseCore: e610482f64097b0e9f056cd97bc6b33dfabcbb6a
FirebaseCoreDiagnostics: 4a773a47bd83bbd5a9b1ccf1ce7caa8b2d535e67
FirebaseCoreDiagnosticsInterop: 296e2c5f5314500a850ad0b83e9e7c10b011a850
FirebaseCrashlytics: a87cce5746d3335995bd18b1b60d073cd05a6920
FirebaseDynamicLinks: 417dc6dbb6013233c77558290d73296f429656a6
FirebaseInstallations: acb3216eb9784d3b1d2d2d635ff74fa892cc0c44
FirebaseInstanceID: 7ee0d6777013bb952f377b41965bf132b6a075be
FirebaseInstallations: 2119fb3e46b0a88bfdbf12562f855ee3252462fa
Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51
glog: 1f3da668190260b06b429bb211bfbee5cd790c28
GoogleAppMeasurement: d0560d915abf15e692e8538ba1d58442217b6aff
GoogleDataTransport: b29a21d813e906014ca16c00897827e40e4a24ab
GoogleDataTransportCCTSupport: 6f15a89b0ca35d6fa523e1f752ef818588885988
GoogleDataTransport: f6f8eba931df03ebd2232ff4645aa85f8f47b5ab
GoogleDataTransportCCTSupport: d70a561f7d236af529fee598835caad5e25f6d3d
GoogleSignIn: 3a51b9bb8e48b635fd7f4272cee06ca260345b86
GoogleUtilities: ad0f3b691c67909d03a3327cc205222ab8f42e0e
GoogleUtilities: 39530bc0ad980530298e9c4af8549e991fd033b1
GTMAppAuth: 4deac854479704f348309e7b66189e604cf5e01e
GTMSessionFetcher: 61bb0f61a4cb560030f1222021178008a5727a23
nanopb: 18003b5e52dab79db540fe93fe9579f399bd1ccd
nanopb: c43f40fadfe79e8b8db116583945847910cbabc9
ObjectiveDropboxOfficial: a5afefc83f6467c42c45f2253f583f2ad1ffc701
PromisesObjC: c119f3cd559f50b7ae681fa59dc1acd19173b7e6
RCTRequired: f63dd90a89a60602acdd44c42e5d2645ca60ab79
@@ -565,7 +529,7 @@ SPEC CHECKSUMS:
React-jsi: 4f35c1a2273d193a80c1c3831c808413840c260c
React-jsiexecutor: de1c37cf59ae9adcbf2be82eea0e090dc3f3205e
React-jsinspector: b76c4e84a7833bb4c90549d59ed53ec299ff912b
react-native-background-timer: 0d34748e53a972507c66963490c775321a88f6f2
react-native-background-timer: e0384ea2fa5a98f67f84f9c4dc274260ddd674ed
react-native-calendar-events: 1442fad71a00388f933cfa25512588fec300fcf8
react-native-keep-awake: eba3137546b10003361b37c761f6c429b59814ae
react-native-netinfo: 8d8db463bcc5db66a8ac5c48a7d86beb3b92f61a
@@ -589,6 +553,6 @@ SPEC CHECKSUMS:
RNWatch: a5320c959c75e72845c07985f3e935e58998f1d3
Yoga: 7b4209fda2441f99d54dd6cf4c82b094409bb68f
PODFILE CHECKSUM: 082858daebbe170e7a490de433e7f2a99e0c3701
PODFILE CHECKSUM: 7255ec38ea51a8bc10a7a582248b4eb4bbbff80c
COCOAPODS: 1.9.1
COCOAPODS: 1.9.3

View File

@@ -20,8 +20,6 @@
#import "Types.h"
#import "ViewController.h"
@import Crashlytics;
@import Fabric;
@import Firebase;
@import JitsiMeet;
@@ -48,10 +46,11 @@
}];
// Initialize Crashlytics and Firebase if a valid GoogleService-Info.plist file was provided.
if ([FIRUtilities appContainsRealServiceInfoPlist] && ![jitsiMeet isCrashReportingDisabled]) {
NSLog(@"Enabling Crashlytics and Firebase");
if ([FIRUtilities appContainsRealServiceInfoPlist]) {
NSLog(@"Enabling Firebase");
[FIRApp configure];
[Fabric with:@[[Crashlytics class]]];
// Crashlytics defaults to disabled wirth the FirebaseCrashlyticsCollectionEnabled Info.plist key.
[[FIRCrashlytics crashlytics] setCrashlyticsCollectionEnabled:![jitsiMeet isCrashReportingDisabled]];
}
[jitsiMeet application:application didFinishLaunchingWithOptions:launchOptions];

View File

@@ -99,7 +99,7 @@
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>firebase_crashlytics_collection_enabled</key>
<key>FirebaseCrashlyticsCollectionEnabled</key>
<string>false</string>
</dict>
</plist>

View File

@@ -4,7 +4,7 @@
"addContacts": "Inviter vos contacts",
"copyInvite": "Copier l'invitation à la réunion",
"copyLink": "Copier le lien de la réunion",
"copyStream": "Copier le lien de diffision en direct",
"copyStream": "Copier le lien de diffusion en direct",
"countryNotSupported": "Cette destination n'est pas actuellement supportée.",
"countryReminder": "Appel hors des États-Unis ? Veuillez débuter par le code du pays !",
"defaultEmail": "Votre email par défaut",
@@ -194,7 +194,7 @@
"done": "Terminé",
"enterDisplayName": "Merci de saisir votre nom ici",
"error": "Erreur",
"grantModeratorDialog": "Êtes vous sûr de vouloir rendre ce participant modérateur?",
"grantModeratorDialog": "Êtes-vous sûr de vouloir rendre ce participant modérateur?",
"grantModeratorTitle": "Nommer modérateur",
"externalInstallationMsg": "Vous devez installer notre extension de partage de bureau.",
"externalInstallationTitle": "Extension requise",
@@ -230,7 +230,7 @@
"micUnknownError": "Vous ne pouvez pas utiliser le microphone pour une raison inconnue.",
"muteEveryoneElseDialog": "Une fois leur micro coupé, vous ne pourrez plus le réactiver, mais ils pourront l'activer par eux-mêmes à tout moment.",
"muteEveryoneElseTitle": "Couper le micro de tout le monde sauf de {{whom}} ?",
"muteEveryoneDialog": "Etes-vous sûr de vouloir couper les micros de tout le monde ? Vous ne pourrez plus réactiver leur micro, mais ils pourront l'activer par eux-mêmes à tout moment.",
"muteEveryoneDialog": "Êtes-vous sûr de vouloir couper les micros de tout le monde ? Vous ne pourrez plus réactiver leur micro, mais ils pourront l'activer par eux-mêmes à tout moment.",
"muteEveryoneTitle": "Couper le micro de tout le monde ?",
"muteEveryoneSelf": "vous",
"muteEveryoneStartMuted": "Tout le monde démarre avec le micro coupé",
@@ -697,6 +697,8 @@
"hangup": "Quitter",
"help": "Aide",
"invite": "Inviter des participants",
"lobbyButtonDisable": "Désactiver le contrôle des participant·e·s",
"lobbyButtonEnable": "Activer le contrôle des participant·e·s",
"login": "Connexion",
"logout": "Déconnexion",
"lowerYourHand": "Baisser la main",

View File

@@ -1,60 +1,86 @@
{
"addPeople": {
"add": "초대",
"countryNotSupported": "아직 해당 지역을 지원하지 않습니다",
"addContacts": "연락처로 초대하세요",
"copyInvite": "호의 초대 복사",
"copyLink": "회의 링크 복사",
"copyStream": "라이브 스트리밍 링크 복사",
"countryNotSupported": "아직 해당 지역을 지원하지 않습니다.",
"countryReminder": "미국 이외의 지역으로 전화하시겠습니까? 국가 번호로 시작해야합니다!",
"disabled": "사람들을 초대 할 수 없습니다",
"failedToAdd": "",
"footerText": "",
"defaultEmail": "기본 이메일",
"disabled": "사람들을 초대 할 수 없습니다.",
"failedToAdd": "참가자를 추가하지 못했습니다.",
"footerText": "전화 걸기가 비활성화되었습니다.",
"googleEmail": "Google 이메일",
"inviteMoreHeader": "회의에 혼자 참여하고 있습니다.",
"inviteMoreMailSubject": "{{appName}} 회의에 참여하세요",
"inviteMorePrompt": "더 많은 사람을 초대하세요",
"linkCopied": "링크가 클립보드에 복사되었습니다.",
"loading": "사람 및 전화번호 검색",
"loadingNumber": "전화번호 확인 중",
"loadingPeople": "초대할 사람 찾기",
"noResults": "일치하는 검색 결과 없음",
"noValidNumbers": "전화 번호를 입력하십시오.",
"outlookEmail": "Outlook 이메일",
"searchNumbers": "전화번호 추가",
"searchPeople": "인명 검색",
"searchPeopleAndNumbers": "인명 검색 또는 전화번호 추가",
"shareInvite": "회의 초대 공유",
"shareLink": "다른 사람을 초대하려면 회의 링크를 공유하세요.",
"shareStream": "라이브 스트리밍 링크 공유",
"telephone": "전화: {{number}}",
"title": "이 회의에 사람들을 초대하십시오"
"title": "이 회의에 사람들을 초대하십시오",
"yahooEmail": "Yahoo 이메일"
},
"audioDevices": {
"bluetooth": "블루투스",
"headphones": "헤드폰",
"phone": "폰",
"speaker": "스피커"
"speaker": "스피커",
"none": "사용 가능한 오디오 장치가 없습니다."
},
"audioOnly": {
"audioOnly": "음성 전용"
},
"calendarSync": {
"addMeetingURL": "",
"confirmAddLink": "",
"addMeetingURL": "회의 링크 추가",
"confirmAddLink": "이 이벤트에 Jitsi 링크를 추가 하시겠습니까?",
"error": {
"appConfiguration": "",
"generic": "",
"notSignedIn": ""
"appConfiguration": "캘린더가 제대로 구성되지 않았습니다.",
"generic": "오류가 발생했습니다. 캘린더 설정을 확인하거나 캘린더를 새로 고침 해보세요.",
"notSignedIn": "캘린더 이벤트를 보기 위해 인증하는 동안 오류가 발생했습니다. 캘린더 설정을 확인하고 다시 로그인하십시오."
},
"join": "",
"joinTooltip": "",
"join": "참여",
"joinTooltip": "회의에 참여하세요",
"nextMeeting": "다음 회의",
"noEvents": "",
"ongoingMeeting": "",
"noEvents": "예정된 예정된 이벤트가 없습니다.",
"ongoingMeeting": "진행중인 회의",
"permissionButton": "설정 열기",
"permissionMessage": "앱에 회의를 나열하려면 캘린더 권한이 필요합니다",
"refresh": "",
"today": ""
"refresh": "달력 새로고침",
"today": "오늘"
},
"chat": {
"error": "",
"messagebox": "",
"error": "오류 : 메시지가 전송되지 않았습니다. 이유 : {{error}}",
"fieldPlaceHolder": "메세지를 여기에 입력하세요",
"messagebox": "메시지 입력",
"messageTo": "{{recipient}}에게 보내는 비공개 메시지",
"noMessagesMessage": "아직 회의에 메시지가 없습니다. 여기서 대화를 시작하세요!",
"nickname": {
"popover": "닉네임을 선택하세요",
"title": ""
"title": "채팅에서 사용할 닉네임을 입력하세요"
},
"title": ""
"privateNotice": "{{recipient}}에게 보내는 비공개 메시지",
"title": "채팅",
"you": "당신"
},
"chromeExtensionBanner": {
"installExtensionText": "Google 캘린더 및 Office 365 확장 프로그램을 설치합니다.",
"buttonText": "Chrome 확장 프로그램을 설치합니다.",
"dontShowAgain": "다시 보지 않기"
},
"connectingOverlay": {
"joiningRoom": ""
"joiningRoom": "회의에 연결 중 ..."
},
"connection": {
"ATTACHED": "첨부",
@@ -66,7 +92,10 @@
"DISCONNECTED": "연결 끊김",
"DISCONNECTING": "연결 종료 중",
"ERROR": "에러",
"RECONNECTING": "네트워크 문제가 발생했습니다. 다시 연결 중..."
"RECONNECTING": "네트워크 문제가 발생했습니다. 다시 연결 중...",
"GET_SESSION_ID_ERROR": "세션 ID 가져 오기 오류 : {{code}}",
"GOT_SESSION_ID": "세션 ID를 가져 오는 중 ... 완료",
"LOW_BANDWIDTH": "대역폭을 절약하기 위해 {{displayName}}의 동영상이 중지되었습니다."
},
"connectionindicator": {
"address": "주소:",
@@ -95,15 +124,18 @@
"turn": " (turn)"
},
"dateUtils": {
"earlier": "",
"today": "",
"yesterday": ""
"earlier": "일찍이",
"today": "오늘",
"yesterday": "어제"
},
"deepLinking": {
"appNotInstalled": "중계 서비스에 참여하려면 모바일 앱 설치가 필요합니다",
"appNotInstalled": "회의에 참여하려면 모바일 앱 설치가 필요합니다",
"description": "{{app}} 데스크톱 앱에서 회의를 시작했습니다. {{app}} 웹 응용 프로그램에서 다시 시도하거나 실행하십시오.",
"descriptionWithoutWeb": "",
"downloadApp": "앱 다운로드",
"ifDoNotHaveApp": "앱이 설치되지 않은 경우:",
"ifHaveApp": "앱이 설치되어 있는 경우:",
"joinInApp": "앱을 사용하여 회의에 참여하세요.",
"launchWebButton": "웹에서 실행",
"openApp": "방으로 이동하기",
"title": "{{app}}에서 회의 시작…",
@@ -126,8 +158,9 @@
"accessibilityLabel": {
"liveStreaming": "실시간 스트리밍:"
},
"add": "추가",
"allow": "허락",
"alreadySharedVideoMsg": "",
"alreadySharedVideoMsg": "다른 참가자가 이미 비디오를 공유하고 있습니다. 이 회의는 한 번에 하나의 공유 비디오 만 허용합니다.",
"alreadySharedVideoTitle": "한 번에 하나의 공유 비디오 만 허용됩니다",
"applicationWindow": "응용 프로그램 창",
"Back": "뒤로가기",
@@ -145,64 +178,64 @@
"conferenceReloadMsg": "문제를 해결하려고 노력하고 있습니다. {{seconds}} 초 안에 다시 연결중입니다.",
"conferenceReloadTitle": "불행하게도 문제가 발생했습니다",
"confirm": "확인",
"confirmNo": "",
"confirmYes": "",
"confirmNo": "아니요",
"confirmYes": "",
"connectError": "죄송합니다. 문제가 발생하여 회의에 연결할 수 없습니다",
"connectErrorWithMsg": "죄송합니다. 뭔가 잘못되어 회의에 연결할 수 없습니다: {{msg}}",
"connecting": "연결 중",
"contactSupport": "지원 연락처",
"copy": "복사",
"dismiss": "",
"displayNameRequired": "",
"displayNameRequired": "당신의 이름은 무엇입니까?",
"done": "완료",
"enterDisplayName": "",
"enterDisplayName": "당신의 이름을 입력해주세요.",
"error": "에러",
"externalInstallationMsg": "데스크톱 공유 확장 프로그램을 설치해야합니다",
"externalInstallationTitle": "확장 프로그램이 필요합니다",
"goToStore": "웹 스토어로 이동",
"gracefulShutdown": "서비스는 현재 유지 관리를 위해 중단되었습니다. 나중에 다시 시도 해주십시오.",
"IamHost": "내가 호스트",
"incorrectRoomLockPassword": "",
"incorrectRoomLockPassword": "잘못된 비밀번호",
"incorrectPassword": "잘못된 사용자 이름 또는 비밀번호",
"inlineInstallationMsg": "데스크톱 공유 확장 프로그램을 설치해야합니다",
"inlineInstallExtension": "지금 설치",
"internalError": "죄송합니다. 뭔가 잘못 됐습니다. 다음 오류가 발생했습니다: {{error}}",
"internalErrorTitle": "내부 에러",
"kickMessage": "",
"kickParticipantButton": "",
"kickParticipantDialog": "",
"kickParticipantTitle": "",
"kickTitle": "",
"kickMessage": "자세한 내용은 {{participantDisplayName}}에 문의하세요.",
"kickParticipantButton": "추방",
"kickParticipantDialog": "이 참가자를 정말 추방 하시겠습니까?",
"kickParticipantTitle": "이 참가자를 추방 하시겠습니까?",
"kickTitle": "{{participantDisplayName}} 님이 회의에서 퇴장했습니다.",
"liveStreaming": "실시간 스트리밍",
"liveStreamingDisabledForGuestTooltip": "",
"liveStreamingDisabledTooltip": "",
"liveStreamingDisabledForGuestTooltip": "게스트는 라이브 스트리밍을 시작할 수 없습니다.",
"liveStreamingDisabledTooltip": "라이브 스트림 시작이 비활성화되었습니다.",
"lockMessage": "회의를 비공개하지 못했습니다",
"lockRoom": "",
"lockRoom": "회의 추가 $t(lockRoomPasswordUppercase)",
"lockTitle": "비공개 실패",
"logoutQuestion": "로그 아웃하고 컨퍼런스를 중지하시겠습니까?",
"logoutTitle": "로그아웃",
"maxUsersLimitReached": "",
"maxUsersLimitReachedTitle": "",
"maxUsersLimitReached": "회의의 최대 참가자 수에 도달했습니다. 회의 소유자에게 연락하거나 나중에 다시 시도하십시오!",
"maxUsersLimitReachedTitle": "최대 참가자 수에 도달했습니다.",
"micConstraintFailedError": "마이크가 필요한 제약 조건 중 일부를 충족하지 못합니다",
"micNotFoundError": "마이크를 찾을 수 없습니다",
"micNotSendingData": "",
"micNotSendingDataTitle": "",
"micNotSendingData": "컴퓨터의 설정으로 이동하여 마이크 음소거를 해제하고 레벨을 조정하세요.",
"micNotSendingDataTitle": "시스템 설정에 의해 마이크가 음소거되었습니다.",
"micPermissionDeniedError": "마이크를 사용할 수있는 권한을 부여하지 않았습니다. 회의에 계속 참여할 수는 있지만 다른 사람들은 듣지 않습니다. 검색 주소창의 카메라 버튼을 사용하여 문제를 해결하십시오.",
"micUnknownError": "알 수 없는 이유로 마이크를 사용할 수 없습니다",
"muteParticipantBody": "당신이 다른 사람들의 음소거를 해제 할 수는 없지만 언제든지 다른 사람들은 스스로 음소거를 해제할 수 있습니다.",
"muteParticipantButton": "음소거",
"muteParticipantDialog": "",
"muteParticipantTitle": "",
"muteParticipantTitle": "이 참가자를 음소거 하시겠습니까?",
"Ok": "확인",
"passwordLabel": "",
"passwordNotSupported": "미팅 비밀번호 설정은 지원되지 않습니다",
"passwordNotSupportedTitle": "",
"passwordRequired": "",
"passwordLabel": "잠긴 회의 입니다. 회의에 참여하려면 비밀번호를 입력하세요.",
"passwordNotSupported": "회의 비밀번호 설정은 지원되지 않습니다",
"passwordNotSupportedTitle": "비밀번호 미지원",
"passwordRequired": "비밀번호 필수",
"popupError": "브라우저가이 사이트의 팝업 창을 차단하고 있습니다. 브라우저의 보안 설정에서 팝업을 활성화하고 다시 시도하십시오.",
"popupErrorTitle": "팝업 차단됨",
"recording": "레코딩",
"recordingDisabledForGuestTooltip": "",
"recordingDisabledTooltip": "",
"recordingDisabledForGuestTooltip": "게스트는 녹음을 시작할 수 없습니다.",
"recordingDisabledTooltip": "녹화이 비활성화 되었습니다.",
"rejoinNow": "지금 재가입",
"remoteControlAllowedMessage": "{{user}}이(가) 원격 제어 요청을 수락했습니다",
"remoteControlDeniedMessage": "{{user}}이(가) 원격 제어 요청을 거부했습니다",
@@ -212,25 +245,30 @@
"remoteControlStopMessage": "원격 제어 세션이 종료되었습니다",
"remoteControlTitle": "원격 데스크탑 컨트롤",
"Remove": "제거",
"removePassword": "",
"removePassword": "비밀번호 제거",
"removeSharedVideoMsg": "공유한 동영상을 삭제하시겠습니까?",
"removeSharedVideoTitle": "공유된 동영상 삭제",
"reservationError": "예약 시스템 오류",
"reservationErrorMsg": "오류 코드: {{code}}, 메시지: {{msg}}",
"retry": "재시도",
"screenSharingAudio": "오디오 공유",
"screenSharingFailedToInstall": "죄송합니다. 화면 공유 확장 프로그램을 설치하지 못했습니다.",
"screenSharingFailedToInstallTitle": "화면 공유 확장 프로그램을 설치하지 못했습니다",
"screenSharingFirefoxPermissionDeniedError": "화면을 공유하는 동안 문제가 발생했습니다. 그렇게 할 수 있는 권한을 부여했는지 확인하십시오.",
"screenSharingFirefoxPermissionDeniedTitle": "죄송합니다. 화면 공유를 시작할 수 없었습니다!",
"screenSharingPermissionDeniedError": "죄송합니다. 화면 공유 확장 권한으로 문제가 발생했습니다. 다시 로드하고 재시도하십시오.",
"sendPrivateMessage": "최근에 비공개 메시지를 받았습니다. 비공개로 답장을 보내시겠습니까, 아니면 그룹에 메시지를 보내시겠습니까?",
"sendPrivateMessageCancel": "그룹에 보내기",
"sendPrivateMessageOk": "비공개로 보내기",
"sendPrivateMessageTitle": "비공개로 보낼까요?",
"serviceUnavailable": "서비스를 사용할 수 없음",
"sessTerminated": "통화 종료",
"Share": "공유",
"shareVideoLinkError": "올바른 YouTube 링크를 제공하십시오",
"shareVideoTitle": "비디오 공유",
"shareYourScreen": "화면공유",
"shareYourScreenDisabled": "",
"shareYourScreenDisabledForGuest": "",
"shareYourScreenDisabled": "화면 공유가 비활성화 되었습니다.",
"shareYourScreenDisabledForGuest": "게스트는 화면을 공유 할 수 없습니다.",
"startLiveStreaming": "라이브 스트리밍 시작",
"startRecording": "레코딩 시작",
"startRemoteControlErrorMessage": "원격 제어 세션을 시작하는 동안 오류가 발생했습니다",
@@ -245,17 +283,20 @@
"tokenAuthFailed": "죄송합니다. 통화에 참여하실 수 없습니다.",
"tokenAuthFailedTitle": "인증 실패",
"transcribing": "",
"unlockRoom": "",
"unlockRoom": "회의 비밀번호 제거",
"userPassword": "사용자 비밀번호",
"WaitForHostMsg": "",
"WaitForHostMsgWOk": "",
"WaitForHostMsg": "<b>{{room}}</b> 회의가 시작되지 않았습니다. 호스트 인 경우 인증하십시오. 그렇지 않으면 호스트가 도착할 때까지 기다리십시오.",
"WaitForHostMsgWOk": "<b>{{room}}</b> 회의가 아직 시작되지 않았습니다. 호스트 인 경우 확인을 눌러 인증하십시오. 그렇지 않으면 호스트가 도착할 때까지 기다리십시오.",
"WaitingForHost": "호스트를 기다리는 중입니다…",
"Yes": "",
"Yes": "",
"yourEntireScreen": "전체 화면"
},
"dialOut": {
"statusMessage": "지금은 {{status}}입니다"
},
"documentSharing": {
"title": "문서 공유"
},
"feedback": {
"average": "보통",
"bad": "나쁨",
@@ -266,49 +307,49 @@
"veryGood": "매우 좋음"
},
"incomingCall": {
"answer": "",
"audioCallTitle": "",
"decline": "",
"productLabel": "",
"videoCallTitle": ""
"answer": "응답",
"audioCallTitle": "수신 전화",
"decline": "거절",
"productLabel": "Jitsi Meet에서",
"videoCallTitle": "수신 화상 전화"
},
"info": {
"accessibilityLabel": "",
"addPassword": "",
"cancelPassword": "",
"accessibilityLabel": "정보 보기",
"addPassword": "$t(lockRoomPassword) 추가",
"cancelPassword": "$t(lockRoomPassword) 취소",
"conferenceURL": "링크:",
"country": "지역",
"dialANumber": "",
"dialANumber": "회의에 참여하려면이 번호 중 하나를 누른 다음 PIN을 입력하십시오.",
"dialInConferenceID": "PIN:",
"dialInNotSupported": "죄송합니다. 현재 전화를 걸 수 없습니다.",
"dialInNumber": "Dial-in:",
"dialInSummaryError": "",
"dialInSummaryError": "지금 전화 접속 정보를 가져 오는 중에 오류가 발생했습니다. 나중에 다시 시도하십시오.",
"dialInTollFree": "",
"genericError": "일반적인 오류가 발생했습니다",
"inviteLiveStream": "이 회의의 실시간 스트림을 보려면이 링크를 클릭하십시오: {{url}}",
"invitePhone": "",
"invitePhoneAlternatives": "",
"inviteURLFirstPartGeneral": "",
"inviteURLFirstPartPersonal": "",
"inviteURLSecondPart": "",
"inviteURLFirstPartGeneral": "회의에 초대되었습니다.",
"inviteURLFirstPartPersonal": "{{name}}이 회의에 초대하였습니다.\n",
"inviteURLSecondPart": "\n회의에 참여하기:\n{{url}}\n",
"liveStreamURL": "실시간 스트리밍:",
"moreNumbers": "더 많은 번호",
"noNumbers": "전화 접속 번호 없음",
"noPassword": "없음",
"noRoom": "전화 접속이 가능한 방을 지정하지 않았습니다",
"numbers": "전화 접속 번호",
"password": "",
"password": "비밀번호",
"title": "공유",
"tooltip": "링크 공유 및 회의에 대한 정보",
"label": ""
"label": "회의 정보"
},
"inviteDialog": {
"alertText": "",
"alertText": "일부 참가자를 초대하지 못했습니다.",
"header": "초대",
"searchCallOnlyPlaceholder": "",
"searchPeopleOnlyPlaceholder": "",
"searchPlaceholder": "",
"send": ""
"searchCallOnlyPlaceholder": "전화 번호 입력",
"searchPeopleOnlyPlaceholder": "참가자 검색",
"searchPlaceholder": "참가자 또는 전화 번호",
"send": "전송"
},
"inlineDialogFailure": {
"msg": "약간의 문제가 있습니다",
@@ -321,7 +362,7 @@
"focusRemote": "다른 발신자의 동영상에 포커스",
"fullScreen": "전체화면 표시 또는 종료",
"keyboardShortcuts": "키보드 단축키",
"localRecording": "",
"localRecording": "로컬 녹음 컨트롤 표시 또는 숨기기",
"mute": "마이크 음소거 또는 음소거 해제",
"pushToTalk": "대화 요청",
"raiseHand": "말하기 요청/해제",
@@ -341,24 +382,26 @@
"enterStreamKey": "YouTube 실시간 스트리밍 키를 입력하십시오",
"error": "실시간 스트리밍에 실패했습니다. 다시 시도하십시오.",
"errorAPI": "YouTube 방송에 액세스하는 중에 오류가 발생했습니다. 다시 로그인하십시오.",
"errorLiveStreamNotEnabled": "",
"expandedOff": "",
"expandedOn": "",
"expandedPending": "",
"errorLiveStreamNotEnabled": "{{email}}에 의해 라이브 스트리밍이 활성화되지 않았습니다. 라이브 스트리밍을 활성화하거나 라이브 스트리밍이 활성화 된 계정으로 로그인하십시오.",
"expandedOff": "라이브 스트리밍이 중지되었습니다",
"expandedOn": "현재 회의가 YouTube로 스트리밍되고 있습니다.",
"expandedPending": "라이브 스트리밍이 시작됩니다 ...",
"failedToStart": "실시간 스트리밍 시작 실패",
"getStreamKeyManually": "",
"invalidStreamKey": "",
"getStreamKeyManually": "실시간 스트림을 가져올 수 없습니다. YouTube에서 실시간 스트림 키를 받아보세요.",
"invalidStreamKey": "라이브 스트림 키가 잘못되었을 수 있습니다.",
"off": "실시간 스트리밍이 중지됨",
"on": "실시간 스트리밍",
"pending": "실시간 스트리밍 시작…",
"serviceName": "실시간 스트리밍 서비스",
"signedInAs": "",
"signedInAs": "현재 다음 계정으로 로그인되어 있습니다.",
"signIn": "Google로 로그인",
"signInCTA": "YouTube에서 로그인하거나 실시간 스트리밍 키를 입력하십시오",
"signOut": "",
"signOut": "로그아웃",
"start": "실시간 스트리밍 시작",
"streamIdHelp": "도움말?",
"unavailableTitle": "실시간 스트리밍을 사용할 수 없음"
"unavailableTitle": "실시간 스트리밍을 사용할 수 없음",
"youtubeTerms": "YouTube 서비스 약관",
"googlePrivacyPolicy": "Google 개인 정보 보호 정책"
},
"localRecording": {
"clientState": {
@@ -381,50 +424,50 @@
"notModerator": ""
},
"moderator": "",
"no": "",
"no": "아니요",
"participant": "",
"participantStats": "",
"sessionToken": "",
"start": "레코딩 시작",
"stop": "레코딩 종료",
"yes": ""
"yes": ""
},
"lockRoomPassword": "패스워드",
"lockRoomPasswordUppercase": "패스워드",
"me": "Me",
"lockRoomPassword": "비밀번호",
"lockRoomPasswordUppercase": "비밀번호",
"me": "",
"notify": {
"connectedOneMember": "",
"connectedThreePlusMembers": "",
"connectedTwoMembers": "",
"connectedOneMember": "{{name}}님이 회의에 참여했습니다.",
"connectedThreePlusMembers": "{{name}}님 외 {{count}}명이 회의에 참여했습니다.",
"connectedTwoMembers": "{{first}}님과 {{second}}님이 회의에 참여했습니다.",
"disconnected": "연결이 끊김",
"focus": "컨퍼런스 포커스",
"focusFail": "{{component}}을 사용할 수 없음 - {{ms}} 초 후에 다시 시도하십시오",
"grantedTo": "{{to}}에게 방장 권한이 부여되었습니다!",
"invitedOneMember": "",
"invitedThreePlusMembers": "",
"invitedTwoMembers": "",
"kickParticipant": "",
"me": "",
"invitedOneMember": "{{name}}님이 초대되었습니다.",
"invitedThreePlusMembers": "{{name}}님 외 {{count}}명이 초대되었습니다.",
"invitedTwoMembers": "{{first}}님과 {{second}}님이 초대되었습니다.",
"kickParticipant": "{{kicker}}님이 {{kicked}}님을 추방했습니다.",
"me": "",
"moderator": "방장 권한이 부여되었습니다!",
"muted": "음소거로 대화가 시작되었습니다",
"mutedTitle": "음소거 상태입니다!",
"mutedRemotelyTitle": "",
"mutedRemotelyDescription": "",
"passwordRemovedRemotely": "",
"passwordSetRemotely": "",
"raisedHand": "",
"mutedRemotelyTitle": "{{participantDisplayName}}에 의해 음소거되었습니다!",
"mutedRemotelyDescription": "말할 준비가되면 언제든지 음소거를 해제 할 수 있습니다.",
"passwordRemovedRemotely": "다른 참가자가 $t(lockRoomPasswordUppercase)를 제거했습니다.",
"passwordSetRemotely": "다른 참가자가 $t(lockRoomPasswordUppercase)를 설정했습니다.",
"raisedHand": "{{name}}님이 말하고 싶어합니다.",
"somebody": "누군가",
"startSilentTitle": "",
"startSilentDescription": "",
"startSilentTitle": "오디오 출력없이 참여했습니다!",
"startSilentDescription": "오디오를 사용하려면 회의에 다시 참여하세요.",
"suboptimalExperienceDescription": "{{appName}}에 대한 귀하의 경험이 없으시다면 <a href='{{recommendedBrowserPageLink}}' target='_blank'>완벽하게 지원되는 브라우저</a> 중 하나를 사용해보십시오.",
"suboptimalExperienceTitle": "브라우저 경고",
"unmute": "",
"newDeviceCameraTitle": "",
"newDeviceAudioTitle": "",
"newDeviceAction": ""
"unmute": "음소거 해제",
"newDeviceCameraTitle": "새 카메라 감지",
"newDeviceAudioTitle": "새 오디오 장치 감지",
"newDeviceAction": "사용"
},
"passwordSetRemotely": "",
"passwordDigitsOnly": "",
"passwordSetRemotely": "다른 참가자가 설정",
"passwordDigitsOnly": "최대 {{number}} 자리",
"poweredby": "powered by",
"presenceStatus": {
"busy": "바쁨",
@@ -447,27 +490,27 @@
"title": "프로필"
},
"recording": {
"authDropboxText": "",
"availableSpace": "",
"authDropboxText": "Dropbox에 업로드",
"availableSpace": "사용 가능한 공간 : {{spaceLeft}}MB (약 {{duration}}분 녹화)",
"beta": "베타",
"busy": "레코딩 자원을 확보하고 있습니다. 몇 분 후에 다시 시도하십시오.",
"busyTitle": "모든 레코더가 현재 사용 중입니다",
"error": "레코딩이 실패했습니다. 다시 시도하십시오.",
"expandedOff": "레코딩이 중지됨",
"expandedOn": "",
"expandedPending": "",
"expandedOn": "회의가 현재 녹화 중입니다.",
"expandedPending": "녹화가 시작됩니다 ...",
"failedToStart": "레코딩을 시작하지 못했습니다",
"fileSharingdescription": "",
"fileSharingdescription": "회의 참가자와 녹음 공유",
"live": "라이브",
"loggedIn": "",
"loggedIn": "{{userName}}으로 로그인했습니다.",
"off": "레코딩이 중지됨",
"on": "레코딩",
"pending": "참석할 멤버를 기다리는 중입니다…",
"rec": "REC",
"serviceDescription": "",
"rec": "녹음",
"serviceDescription": "녹음은 녹음 서비스에 의해 저장됩니다.",
"serviceName": "레코딩 서비스",
"signIn": "",
"signOut": "",
"signIn": "로그인",
"signOut": "로그아웃",
"unavailable": "죄송합니다. {{serviceName}}은 현재 사용할 수 없습니다. 저희는 문제를 해결하기 위해 노력하고 있습니다. 나중에 다시 시도 해주십시오.",
"unavailableTitle": "레코딩을 사용할 수 없습니다"
},
@@ -476,18 +519,18 @@
},
"settings": {
"calendar": {
"about": "",
"about": "{{appName}} 캘린더 통합은 예정된 일정을 읽을 수 있도록 캘린더에 안전하게 액세스하는 데 사용됩니다.",
"disconnect": "연결 끊김",
"microsoftSignIn": "",
"signedIn": "",
"title": ""
"microsoftSignIn": "Microsoft로 로그인",
"signedIn": "현재 {{email}}의 캘린더 일정에 액세스하고 있습니다. 캘린더 이벤트 액세스를 중지하려면 아래 연결 해제 버튼을 클릭하세요.",
"title": "캘린더"
},
"devices": "",
"devices": "장치",
"followMe": "모두 나와 같은 설정 상태로",
"language": "",
"loggedIn": "",
"moderator": "",
"more": "",
"language": "언어",
"loggedIn": "{{name}}으로 로그인",
"moderator": "마이크",
"more": "더보기",
"name": "이름",
"noDevice": "없음",
"selectAudioOutput": "오디오 출력",
@@ -495,26 +538,28 @@
"selectMic": "오디오",
"startAudioMuted": "모두가 음소거를 시작합니다",
"startVideoMuted": "모두가 비디오 비활성화로 시작합니다",
"title": "세티"
"title": "설정"
},
"settingsView": {
"advanced": "고급",
"alertOk": "확인",
"alertCancel": "취소",
"alertTitle": "경고",
"alertURLText": "입력된 서버 URL이 잘못되었습니다",
"buildInfoSection": "",
"buildInfoSection": "빌드 정보",
"conferenceSection": "회의",
"displayName": "유저이름",
"email": "이메일",
"header": "세티",
"header": "설정",
"profileSection": "프로필",
"serverURL": "서버 URL",
"startWithAudioMuted": "오디오 음소거 상태로 시작",
"startWithVideoMuted": "비디오 비활성화 상태로 시작",
"version": ""
"version": "버전"
},
"share": {
"dialInfoText": "",
"mainText": ""
"mainText": "회의에 참여하려면 다음 링크를 클릭하십시오.\n{{roomUrl}}"
},
"speaker": "스피커",
"speakerStats": {
@@ -561,11 +606,11 @@
"sharedvideo": "",
"shareRoom": "",
"shareYourScreen": "",
"shortcuts": "단축키 토그",
"shortcuts": "단축키 전환",
"show": "",
"speakerStats": "",
"tileView": "",
"toggleCamera": "카메라 토ㄱ",
"toggleCamera": "카메라 전환",
"videomute": "",
"videoblur": ""
},
@@ -575,54 +620,58 @@
"audioRoute": "음성 장비 선택하기",
"authenticate": "인증 중",
"callQuality": "품질 설정하기",
"chat": "",
"closeChat": "",
"documentClose": "",
"documentOpen": "",
"chat": "대화 열기/닫기",
"closeChat": "대화 닫기",
"documentClose": "문서 공유 닫기",
"documentOpen": "문서 공유 열기",
"download": "앱 다운로드",
"enterFullScreen": "전체화면 보기",
"enterTileView": "",
"enterTileView": "타일보기 시작",
"exitFullScreen": "전체화면 취소",
"exitTileView": "",
"exitTileView": "타일보기 종료",
"feedback": "피드백 남기기",
"hangup": "",
"invite": "",
"login": "",
"hangup": "떠나기",
"invite": "초대",
"login": "로그인",
"logout": "로그아웃",
"lowerYourHand": "",
"lowerYourHand": "손을 내려주세요",
"moreActions": "추가 액션",
"mute": "마이크",
"openChat": "",
"moreOptions": "옵션 더보기",
"mute": "음소거 설정/해제",
"muteEveryone": "모두 음소거",
"openChat": "대화 열기",
"pip": "",
"profile": "",
"privateMessage": "비공개 메시지 보내기",
"profile": "프로필 수정",
"raiseHand": "말하기 요청/해제",
"raiseYourHand": "",
"Settings": "세티",
"sharedvideo": "",
"shareRoom": "",
"shortcuts": "",
"raiseYourHand": "손 들어주세요",
"Settings": "설정",
"sharedvideo": "YouTube 비디오 공유",
"shareRoom": "초대하기",
"shortcuts": "단축키보기",
"speakerStats": "접속자 통계",
"startScreenSharing": "",
"startSubtitles": "",
"stopScreenSharing": "",
"stopSubtitles": "",
"stopSharedVideo": "",
"talkWhileMutedPopup": "",
"tileViewToggle": "",
"toggleCamera": "카메라 토ㄱ",
"videomute": "",
"startvideoblur": "",
"stopvideoblur": ""
"startScreenSharing": "화면 공유 시작",
"startSubtitles": "자막 시작",
"stopScreenSharing": "화면 공유 중지",
"stopSubtitles": "자막 중지",
"stopSharedVideo": "UouTube 비디오 공유 중지",
"talkWhileMutedPopup": "음소거 상태입니다.",
"tileViewToggle": "타일뷰 전환",
"toggleCamera": "카메라 전환",
"videomute": "카메라 시작/중지",
"startvideoblur": "내 배경을 흐리게",
"stopvideoblur": "배경 흐림 비활성화"
},
"transcribing": {
"ccButtonTooltip": "",
"ccButtonTooltip": "자막 시작/종료",
"error": "레코딩이 실패했습니다. 다시 시도하십시오.",
"expandedLabel": "",
"failedToStart": "",
"labelToolTip": "",
"off": "",
"expandedLabel": "현재 스크립트 작성 중",
"failedToStart": "스크립트 작성을 시작하지 못했습니다.",
"labelToolTip": "회의가 기록되고 있습니다.",
"off": "스크립트 작성이 중지되었습니다.",
"pending": "참석할 멤버를 기다리는 중입니다…",
"start": "",
"stop": "",
"start": "자막 표시 시작",
"stop": "자막 표시 중지",
"tr": ""
},
"userMedia": {
@@ -649,8 +698,8 @@
},
"videoStatus": {
"audioOnly": "오디오 전용",
"audioOnlyExpanded": "",
"callQuality": "",
"audioOnlyExpanded": "낮은 대역폭 모드에 있습니다. 이 모드에서는 오디오 및 화면 공유 만 수신합니다.",
"callQuality": "비디오 품질",
"hd": "HD",
"highDefinition": "고해상도",
"labelTooiltipNoVideo": "비디오 없음",
@@ -666,12 +715,12 @@
"domute": "음소거",
"flip": "플립",
"kick": "내보내기",
"moderator": "",
"mute": "",
"moderator": "중재자",
"mute": "참가자 음소거",
"muted": "음소거됨",
"remoteControl": "원격 제어",
"show": "",
"videomute": ""
"show": "화면에 표시",
"videomute": "참가자가 카메라를 중지했습니다."
},
"welcomepage": {
"accessibilityLabel": {
@@ -683,22 +732,33 @@
"audio": "음성",
"video": "비디오"
},
"calendar": "",
"connectCalendarButton": "",
"connectCalendarText": "",
"enterRoomTitle": "",
"calendar": "캘린더",
"connectCalendarButton": "캘린더를 연결하세요",
"connectCalendarText": "{{app}}에서 모든 회의를 보려면 캘린더를 연결하세요. 또한 캘린더에 {{provider}} 회의를 추가하고 클릭 한 번으로 시작하세요.",
"enterRoomTitle": "새 회의 시작",
"getHelp": "도움 받기",
"roomNameAllowedChars": "회의 이름은 이러한 문자를 포함 할 수 없습니다.: ?, &, :, ', \", %, #.",
"go": "계속",
"goSmall": "계속",
"join": "가입",
"info": "",
"info": "정보",
"privacy": "개인정보",
"recentList": "",
"recentListDelete": "",
"recentListEmpty": "",
"reducedUIText": "",
"recentList": "최근",
"recentListDelete": "삭제",
"recentListEmpty": "최근 목록이 현재 비어 있습니다. 팀과 채팅하면 여기에서 최근 회의를 모두 찾을 수 있습니다.",
"reducedUIText": "{{app}}에 오신 것을 환영합니다!",
"roomname": "방 이름 입력",
"roomnameHint": "",
"sendFeedback": "",
"roomnameHint": "참여하려는 방의 이름 또는 URL을 입력하십시오. 이름을 정하고 만나는 사람들에게 같은 이름을 입력하도록 알리면됩니다.",
"sendFeedback": "피드백 보내기",
"terms": "이용약관",
"title": ""
"title": "안전하고 모든 기능을 갖춘 완전 무료 화상 회의"
},
"lonelyMeetingExperience": {
"button": "초대하기",
"youAreAlone": "회의에 참여자가 없습니다."
},
"helpView": {
"header": "지원 센터"
}
}

View File

@@ -197,10 +197,7 @@
"displayNameRequired": "Hi! Whats your name?",
"done": "Done",
"e2eeDescription": "End-to-End Encryption is currently EXPERIMENTAL. Please keep in mind that turning on end-to-end encryption will effectively disable server-side provided services such as: recording, live streaming and phone participation. Also keep in mind that the meeting will only work for people joining from browsers with support for insertable streams.",
"e2eeLabel": "E2EE key",
"e2eeNoKey": "None",
"e2eeToggleSet": "Set key",
"e2eeSet": "Set",
"e2eeLabel": "Enable End-to-End Encryption",
"e2eeWarning": "WARNING: Not all participants in this meeting seem to have support for End-to-End encryption. If you enable it they won't be able to see nor hear you.",
"enterDisplayName": "Please enter your name here",
"error": "Error",
@@ -697,7 +694,6 @@
"document": "Toggle shared document",
"download": "Download our apps",
"embedMeeting": "Embed meeting",
"e2ee": "End-to-End Encryption",
"feedback": "Leave feedback",
"fullScreen": "Toggle full screen",
"grantModerator": "Grant Moderator",

View File

@@ -19,8 +19,9 @@ import {
processExternalDeviceRequest
} from '../../react/features/device-selection/functions';
import { isEnabled as isDropboxEnabled } from '../../react/features/dropbox';
import { setE2EEKey } from '../../react/features/e2ee';
import { toggleE2EE } from '../../react/features/e2ee/actions';
import { invite } from '../../react/features/invite';
import { selectParticipantInLargeVideo } from '../../react/features/large-video/actions';
import { toggleLobbyMode } from '../../react/features/lobby/actions.web';
import { RECORDING_TYPES } from '../../react/features/recording/constants';
import { getActiveSession } from '../../react/features/recording/functions';
@@ -123,6 +124,11 @@ function initCommands() {
APP.store.dispatch(sendTones(tones, duration, pause));
},
'set-large-video-participant': participantId => {
logger.debug('Set large video participant command received');
sendAnalytics(createApiEvent('largevideo.participant.set'));
APP.store.dispatch(selectParticipantInLargeVideo(participantId));
},
'subject': subject => {
sendAnalytics(createApiEvent('subject.changed'));
APP.store.dispatch(setSubject(subject));
@@ -191,9 +197,9 @@ function initCommands() {
logger.error('Failed sending endpoint text message', err);
}
},
'e2ee-key': key => {
logger.debug('Set E2EE key command received');
APP.store.dispatch(setE2EEKey(key));
'toggle-e2ee': enabled => {
logger.debug('Toggle E2EE key command received');
APP.store.dispatch(toggleE2EE(enabled));
},
'set-video-quality': frameHeight => {
logger.debug('Set video quality command received');
@@ -690,6 +696,21 @@ class API {
});
}
/**
* Notify external application (if API is enabled) that the an error has been logged.
*
* @param {string} logLevel - The message log level.
* @param {Array} args - Array of strings composing the log message.
* @returns {void}
*/
notifyLog(logLevel: string, args: Array<string>) {
this._sendEvent({
name: 'log',
logLevel,
args
});
}
/**
* Notify external application (if API is enabled) that the conference has
* been joined.
@@ -710,8 +731,7 @@ class API {
}
/**
* Notify external application (if API is enabled) that user changed their
* nickname.
* Notify external application (if API is enabled) that local user has left the conference.
*
* @param {string} roomName - User id.
* @returns {void}

View File

@@ -37,6 +37,7 @@ const commands = {
password: 'password',
sendEndpointTextMessage: 'send-endpoint-text-message',
sendTones: 'send-tones',
setLargeVideoParticipant: 'set-large-video-participant',
setVideoQuality: 'set-video-quality',
startRecording: 'start-recording',
stopRecording: 'stop-recording',
@@ -67,6 +68,7 @@ const events = {
'feedback-prompt-displayed': 'feedbackPromptDisplayed',
'filmstrip-display-changed': 'filmstripDisplayChanged',
'incoming-message': 'incomingMessage',
'log': 'log',
'mic-error': 'micError',
'outgoing-message': 'outgoingMessage',
'participant-joined': 'participantJoined',
@@ -356,6 +358,19 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
);
}
/**
* Returns the formatted display name of a participant.
*
* @param {string} participantId - The id of the participant.
* @returns {string} The formatted display name.
*/
_getFormattedDisplayName(participantId) {
const { formattedDisplayName }
= this._participants[participantId] || {};
return formattedDisplayName;
}
/**
* Returns the id of the on stage participant.
*
@@ -542,6 +557,13 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
* the event and value - the listener.
* Currently we support the following
* events:
* {@code log} - receives event notifications whenever information has
* been logged and has a log level specified within {@code config.apiLogLevels}.
* The listener will receive object with the following structure:
* {{
* logLevel: the message log level
* arguments: an array of strings that compose the actual log message
* }}
* {@code incomingMessage} - receives event notifications about incoming
* messages. The listener will receive object with the following structure:
* {{
@@ -693,6 +715,23 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
return getCurrentDevices(this._transport);
}
/**
* Returns the conference participants information.
*
* @returns {Array<Object>} - Returns an array containing participants
* information like participant id, display name, avatar URL and email.
*/
getParticipantsInfo() {
const participantIds = Object.keys(this._participants);
const participantsInfo = Object.values(this._participants);
participantsInfo.forEach((participant, idx) => {
participant.participantId = participantIds[idx];
});
return participantsInfo;
}
/**
* Returns the current video quality setting.
*
@@ -822,19 +861,6 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
return email;
}
/**
* Returns the formatted display name of a participant.
*
* @param {string} participantId - The id of the participant.
* @returns {string} The formatted display name.
*/
_getFormattedDisplayName(participantId) {
const { formattedDisplayName }
= this._participants[participantId] || {};
return formattedDisplayName;
}
/**
* Returns the iframe that loads Jitsi Meet.
*
@@ -947,6 +973,18 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
return setAudioOutputDevice(this._transport, label, deviceId);
}
/**
* Displays the given participant on the large video. If no participant id is specified,
* dominant and pinned speakers will be taken into consideration while selecting the
* the large video participant.
*
* @param {string} participantId - Jid of the participant to be displayed on the large video.
* @returns {void}
*/
setLargeVideoParticipant(participantId) {
this.executeCommand('setLargeVideoParticipant', participantId);
}
/**
* Sets the video input device to the one with the label or id that is
* passed.

1097
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -56,11 +56,12 @@
"jquery-i18next": "1.2.1",
"js-md5": "0.6.1",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#94318fce12c855aefefdf8586bc8772065b505c9",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#43e7c853b834dc7ced0f81ee5f4b130444d85e95",
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
"lodash": "4.17.19",
"moment": "2.19.4",
"moment-duration-format": "2.2.2",
"olm": "https://packages.matrix.org/npm/olm/olm-3.1.4.tgz",
"pixelmatch": "5.1.0",
"react": "16.9",
"react-dom": "16.9",
@@ -68,7 +69,7 @@
"react-i18next": "10.11.4",
"react-linkify": "1.0.0-alpha",
"react-native": "github:jitsi/react-native#efd2aff5661d75a230e36406b698cfe0ee545be2",
"react-native-background-timer": "2.1.1",
"react-native-background-timer": "2.4.0",
"react-native-calendar-events": "github:jitsi/react-native-calendar-events#928a80e2ffef0d7e84936d7e7e0acc4f53ee8470",
"react-native-callstats": "3.61.0",
"react-native-collapsible": "1.5.1",
@@ -127,7 +128,7 @@
"imports-loader": "0.7.1",
"jetifier": "1.6.4",
"metro-react-native-babel-preset": "0.56.0",
"node-sass": "4.14.1",
"sass": "1.26.8",
"string-replace-loader": "2.1.1",
"style-loader": "0.19.0",
"unorm": "1.6.0",

View File

@@ -17,6 +17,7 @@ export default [
'audioLevelsInterval',
'autoRecord',
'autoRecordToken',
'apiLogLevels',
'avgRtpStatsN',
/**

View File

@@ -0,0 +1,22 @@
// @flow
declare var APP: Object;
/**
* Constructs a log transport object for use with external API.
*
* @param {Array} levels - The log levels forwarded to the external API.
* @returns {Object} - The transport object.
*/
function buildTransport(levels: Array<string>) {
return levels.reduce((logger, level) => {
logger[level] = (...args) => {
APP.API.notifyLog(level, args);
};
return logger;
}, {});
}
export default buildTransport;

View File

@@ -11,6 +11,7 @@ import JitsiMeetJS, {
import { MiddlewareRegistry } from '../redux';
import { isTestModeEnabled } from '../testing';
import buildExternalApiLogTransport from './ExternalApiLogTransport';
import JitsiMeetInMemoryLogStorage from './JitsiMeetInMemoryLogStorage';
import JitsiMeetLogStorage from './JitsiMeetLogStorage';
import { SET_LOGGING_CONFIG } from './actionTypes';
@@ -141,6 +142,15 @@ function _initLogging({ dispatch, getState }, loggingConfig, isTestingEnabled) {
const _logCollector
= new Logger.LogCollector(new JitsiMeetLogStorage(getState));
const { apiLogLevels } = getState()['features/base/config'];
if (apiLogLevels && Array.isArray(apiLogLevels) && typeof APP === 'object') {
const transport = buildExternalApiLogTransport(apiLogLevels);
Logger.addGlobalTransport(transport);
JitsiMeetJS.addGlobalLogTransport(transport);
}
Logger.addGlobalTransport(_logCollector);
JitsiMeetJS.addGlobalLogTransport(_logCollector);
dispatch(setLogCollector(_logCollector));

View File

@@ -1,8 +1,8 @@
/**
* The type of the action which signals the E2EE key has changed.
* The type of the action which signals that E2EE needs to be enabled / disabled.
*
* {
* type: SET_E2EE_KEY
* type: TOGGLE_E2EE
* }
*/
export const SET_E2EE_KEY = 'SET_E2EE_KEY';
export const TOGGLE_E2EE = 'TOGGLE_E2EE';

View File

@@ -1,16 +1,16 @@
// @flow
import { SET_E2EE_KEY } from './actionTypes';
import { TOGGLE_E2EE } from './actionTypes';
/**
* Dispatches an action to set the E2EE key.
* Dispatches an action to enable / disable E2EE.
*
* @param {string|undefined} key - The new key to be used for E2EE.
* @param {boolean} enabled - Whether E2EE is to be enabled or not.
* @returns {Object}
*/
export function setE2EEKey(key: ?string) {
export function toggleE2EE(enabled: boolean) {
return {
type: SET_E2EE_KEY,
key
type: TOGGLE_E2EE,
enabled
};
}

View File

@@ -6,22 +6,23 @@ import type { Dispatch } from 'redux';
import { createE2EEEvent, sendAnalytics } from '../../analytics';
import { translate } from '../../base/i18n';
import { getParticipants } from '../../base/participants';
import { Switch } from '../../base/react';
import { connect } from '../../base/redux';
import { setE2EEKey } from '../actions';
import { toggleE2EE } from '../actions';
type Props = {
/**
* Whether E2EE is currently enabled or not.
*/
_enabled: boolean,
/**
* Indicates whether all participants in the conference currently support E2EE.
*/
_everyoneSupportsE2EE: boolean,
/**
* The current E2EE key.
*/
_key: string,
/**
* The redux {@code dispatch} function.
*/
@@ -36,19 +37,14 @@ type Props = {
type State = {
/**
* True if the key is being edited.
* True if the switch is toggled on.
*/
editing: boolean,
enabled: boolean,
/**
* True if the section description should be expanded, false otherwise.
*/
expand: boolean,
/**
* The current E2EE key.
*/
key: string
expand: boolean
};
/**
@@ -58,30 +54,38 @@ type State = {
* @extends Component
*/
class E2EESection extends Component<Props, State> {
fieldRef: Object;
/**
* Implements React's {@link Component#getDerivedStateFromProps()}.
*
* @inheritdoc
*/
static getDerivedStateFromProps(props: Props, state: Object) {
if (props._enabled !== state.enabled) {
return {
enabled: props._enabled
};
}
return null;
}
/**
* Initializes a new {@code E2EEDialog } instance.
* Instantiates a new component.
*
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this.fieldRef = React.createRef();
this.state = {
editing: false,
expand: false,
key: this.props._key
enabled: false,
expand: false
};
// Bind event handlers so they are only bound once for every instance.
this._onExpand = this._onExpand.bind(this);
this._onKeyChange = this._onKeyChange.bind(this);
this._onSet = this._onSet.bind(this);
this._onToggleSetKey = this._onToggleSetKey.bind(this);
this._onToggle = this._onToggle.bind(this);
}
/**
@@ -92,7 +96,7 @@ class E2EESection extends Component<Props, State> {
*/
render() {
const { _everyoneSupportsE2EE, t } = this.props;
const { editing, expand } = this.state;
const { enabled, expand } = this.state;
const description = t('dialog.e2eeDescription');
return (
@@ -112,25 +116,13 @@ class E2EESection extends Component<Props, State> {
{ t('dialog.e2eeWarning') }
</span>
}
<div className = 'key-field'>
<div className = 'control-row'>
<label>
{ t('dialog.e2eeLabel') }:
{ t('dialog.e2eeLabel') }
</label>
<input
disabled = { !editing }
name = 'e2eeKey'
onChange = { this._onKeyChange }
onKeyDown = { this._onKeyDown }
placeholder = { t('dialog.e2eeNoKey') }
ref = { this.fieldRef }
type = 'password'
value = { this.state.key } />
{ editing && <a onClick = { this._onSet }>
{ t('dialog.e2eeSet') }
</a> }
{ !editing && <a onClick = { this._onToggleSetKey }>
{ t('dialog.e2eeToggleSet') }
</a> }
<Switch
onValueChange = { this._onToggle }
value = { enabled } />
</div>
</div>
);
@@ -149,65 +141,23 @@ class E2EESection extends Component<Props, State> {
});
}
_onKeyChange: (Object) => void;
_onToggle: () => void;
/**
* Updates the entered key.
*
* @param {Object} event - The DOM event triggered from the entered value having changed.
* @private
* @returns {void}
*/
_onKeyChange(event) {
this.setState({ key: event.target.value.trim() });
}
_onKeyDown: (Object) => void;
/**
* Handler for the keydown event on the form, preventing the closing of the dialog.
*
* @param {Object} event - The DOM event triggered by keydown events.
* @returns {void}
*/
_onKeyDown(event) {
if (event.key === 'Enter') {
event.preventDefault();
}
}
_onSet: () => void;
/**
* Dispatches an action to set/unset the E2EE key.
* Callback to be invoked when the user toggles E2EE on or off.
*
* @private
* @returns {void}
*/
_onSet() {
const { key } = this.state;
sendAnalytics(createE2EEEvent(`key.${key ? 'set' : 'unset'}`));
this.props.dispatch(setE2EEKey(key));
_onToggle() {
const newValue = !this.state.enabled;
this.setState({
editing: false
enabled: newValue
});
}
_onToggleSetKey: () => void;
/**
* Sets the section into edit mode so then the user can set the key.
*
* @returns {void}
*/
_onToggleSetKey() {
this.setState({
editing: true
}, () => {
this.fieldRef.current.focus();
});
sendAnalytics(createE2EEEvent(`enabled.${String(newValue)}`));
this.props.dispatch(toggleE2EE(newValue));
}
}
@@ -219,12 +169,12 @@ class E2EESection extends Component<Props, State> {
* @returns {Props}
*/
function mapStateToProps(state) {
const { e2eeKey } = state['features/e2ee'];
const { enabled } = state['features/e2ee'];
const participants = getParticipants(state).filter(p => !p.local);
return {
_everyoneSupportsE2EE: participants.every(p => Boolean(p.e2eeSupported)),
_key: e2eeKey || ''
_enabled: enabled,
_everyoneSupportsE2EE: participants.every(p => Boolean(p.e2eeSupported))
};
}

View File

@@ -4,8 +4,8 @@ import { getCurrentConference } from '../base/conference';
import { getLocalParticipant, participantUpdated } from '../base/participants';
import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
import { SET_E2EE_KEY } from './actionTypes';
import { setE2EEKey } from './actions';
import { TOGGLE_E2EE } from './actionTypes';
import { toggleE2EE } from './actions';
import logger from './logger';
/**
@@ -16,18 +16,18 @@ import logger from './logger';
*/
MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
switch (action.type) {
case SET_E2EE_KEY: {
case TOGGLE_E2EE: {
const conference = getCurrentConference(getState);
if (conference) {
logger.debug(`New E2EE key: ${action.key}`);
conference.setE2EEKey(action.key);
logger.debug(`E2EE will be ${action.enabled ? 'enabled' : 'disabled'}`);
conference.toggleE2EE(action.enabled);
// Broadccast that we enabled / disabled E2EE.
const participant = getLocalParticipant(getState);
dispatch(participantUpdated({
e2eeEnabled: Boolean(action.key),
e2eeEnabled: action.enabled,
id: participant.id,
local: true
}));
@@ -48,6 +48,6 @@ StateListenerRegistry.register(
state => getCurrentConference(state),
(conference, { dispatch }, previousConference) => {
if (previousConference) {
dispatch(setE2EEKey(undefined));
dispatch(toggleE2EE(false));
}
});

View File

@@ -2,14 +2,10 @@
import { ReducerRegistry } from '../base/redux';
import { SET_E2EE_KEY } from './actionTypes';
import { TOGGLE_E2EE } from './actionTypes';
const DEFAULT_STATE = {
/**
* E2EE key.
*/
e2eeKey: undefined
enabled: false
};
/**
@@ -17,10 +13,10 @@ const DEFAULT_STATE = {
*/
ReducerRegistry.register('features/e2ee', (state = DEFAULT_STATE, action) => {
switch (action.type) {
case SET_E2EE_KEY:
case TOGGLE_E2EE:
return {
...state,
e2eeKey: action.key
enabled: action.enabled
};
default:

View File

@@ -35,8 +35,8 @@ function EmbedMeeting({ t, url }: Props) {
* @returns {string} The iframe embed code.
*/
const getEmbedCode = () =>
`<iframe allow="camera; microphone; display-capture" src="${url}`
+ 'allowfullscreen="true" style="height: 100%; width: 100%; border: 0px;"></iframe>';
`<iframe allow="camera; microphone; fullscreen; display-capture" src="${url}"`
+ ' style="height: 100%; width: 100%; border: 0px;"></iframe>';
return (
<Dialog

View File

@@ -49,16 +49,19 @@ export function selectParticipant() {
}
/**
* Action to select the participant to be displayed in LargeVideo based on a
* variety of factors: If there is a dominant or pinned speaker, or if there are
* remote tracks, etc.
* Action to select the participant to be displayed in LargeVideo based on the
* participant id provided. If a partcipant id is not provided, the LargeVideo
* participant will be selected based on a variety of factors: If there is a
* dominant or pinned speaker, or if there are remote tracks, etc.
*
* @param {string} participant - The participant id of the user that needs to be
* displayed on the large video.
* @returns {Function}
*/
export function selectParticipantInLargeVideo() {
export function selectParticipantInLargeVideo(participant: ?string) {
return (dispatch: Dispatch<any>, getState: Function) => {
const state = getState();
const participantId = _electParticipantInLargeVideo(state);
const participantId = participant ?? _electParticipantInLargeVideo(state);
const largeVideo = state['features/large-video'];
if (participantId !== largeVideo.participantId) {

View File

@@ -415,10 +415,12 @@ function _visitNode(node, callback) {
// Required by:
// - lib-jitsi-meet
// - Strophe
global.clearTimeout = BackgroundTimer.clearTimeout.bind(BackgroundTimer);
global.clearInterval = BackgroundTimer.clearInterval.bind(BackgroundTimer);
global.setInterval = BackgroundTimer.setInterval.bind(BackgroundTimer);
global.setTimeout = (fn, ms = 0) => BackgroundTimer.setTimeout(fn, ms);
if (Platform.OS === 'android') {
global.clearTimeout = BackgroundTimer.clearTimeout.bind(BackgroundTimer);
global.clearInterval = BackgroundTimer.clearInterval.bind(BackgroundTimer);
global.setInterval = BackgroundTimer.setInterval.bind(BackgroundTimer);
global.setTimeout = (fn, ms = 0) => BackgroundTimer.setTimeout(fn, ms);
}
// localStorage
if (typeof global.localStorage === 'undefined') {

View File

@@ -40,6 +40,7 @@ local function load_config()
end
load_config();
-- verify user and whether he is allowed to join a room based on the token information
local function verify_user(session, stanza)
log("debug", "Session token: %s, session room: %s",
tostring(session.auth_token),
@@ -49,7 +50,7 @@ local function verify_user(session, stanza)
local user_jid = stanza.attr.from;
if is_admin(user_jid) then
log("debug", "Token not required from admin user: %s", user_jid);
return nil;
return true;
end
log("debug",
@@ -64,18 +65,23 @@ local function verify_user(session, stanza)
end
log("debug",
"allowed: %s to enter/create room: %s", user_jid, stanza.attr.to);
return true;
end
module:hook("muc-room-pre-create", function(event)
local origin, stanza = event.origin, event.stanza;
log("debug", "pre create: %s %s", tostring(origin), tostring(stanza));
return verify_user(origin, stanza);
if not verify_user(origin, stanza) then
return true; -- Returning any value other than nil will halt processing of the event
end
end);
module:hook("muc-occupant-pre-join", function(event)
local origin, room, stanza = event.origin, event.room, event.stanza;
log("debug", "pre join: %s %s", tostring(room), tostring(stanza));
return verify_user(origin, stanza);
if not verify_user(origin, stanza) then
return true; -- Returning any value other than nil will halt processing of the event
end
end);
for event_name, method in pairs {

View File

@@ -140,7 +140,10 @@ const config = {
// Allow the use of the real filename of the module being executed. By
// default Webpack does not leak path-related information and provides a
// value that is a mock (/index.js).
__filename: true
__filename: true,
// Provide an empty 'fs' module.
fs: 'empty'
},
optimization: {
concatenateModules: minimize,
@@ -187,7 +190,7 @@ module.exports = [
entry: {
'app.bundle': './app.js'
},
performance: getPerformanceHints(4 * 1024 * 1024)
performance: getPerformanceHints(4.5 * 1024 * 1024)
}),
Object.assign({}, config, {
entry: {