diff --git a/android/sdk/build.gradle b/android/sdk/build.gradle index 58224331fbff..3263b1004869 100644 --- a/android/sdk/build.gradle +++ b/android/sdk/build.gradle @@ -45,6 +45,7 @@ dependencies { implementation 'com.dropbox.core:dropbox-core-sdk:3.0.8' implementation 'com.jakewharton.timber:timber:4.7.1' + implementation 'com.squareup.duktape:duktape-android:1.3.0' if (!rootProject.ext.libreBuild) { implementation 'com.amplitude:android-sdk:2.14.1' diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/JavaScriptSandboxModule.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/JavaScriptSandboxModule.java new file mode 100644 index 000000000000..38d8d94d9101 --- /dev/null +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/JavaScriptSandboxModule.java @@ -0,0 +1,57 @@ +/* + * Copyright @ 2019-present 8x8, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jitsi.meet.sdk; + +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.module.annotations.ReactModule; +import com.squareup.duktape.Duktape; + +@ReactModule(name = JavaScriptSandboxModule.NAME) +class JavaScriptSandboxModule extends ReactContextBaseJavaModule { + public static final String NAME = "JavaScriptSandbox"; + + public JavaScriptSandboxModule(ReactApplicationContext reactContext) { + super(reactContext); + } + + /** + * Evaluates the given code in a Duktape VM. + * @param code - The code that needs to evaluated. + * @param promise - Resolved with the output in case of success or rejected with an exception + * in case of failure. + */ + @ReactMethod + public void evaluate(String code, Promise promise) { + Duktape vm = Duktape.create(); + try { + Object res = vm.evaluate(code); + promise.resolve(res.toString()); + } catch (Throwable tr) { + promise.reject(tr); + } finally { + vm.close(); + } + } + + @Override + public String getName() { + return NAME; + } +} diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/ReactInstanceManagerHolder.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/ReactInstanceManagerHolder.java index 4c04571136e9..d7c2982a1bb4 100644 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/ReactInstanceManagerHolder.java +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/ReactInstanceManagerHolder.java @@ -67,6 +67,7 @@ private static List createNativeModules(ReactApplicationContext re new AudioModeModule(reactContext), new DropboxModule(reactContext), new ExternalAPIModule(reactContext), + new JavaScriptSandboxModule(reactContext), new LocaleDetector(reactContext), new LogBridgeModule(reactContext), new PictureInPictureModule(reactContext), diff --git a/ios/sdk/sdk.xcodeproj/project.pbxproj b/ios/sdk/sdk.xcodeproj/project.pbxproj index 30efb656c835..dbd7950fc9f4 100644 --- a/ios/sdk/sdk.xcodeproj/project.pbxproj +++ b/ios/sdk/sdk.xcodeproj/project.pbxproj @@ -42,6 +42,7 @@ C69EFA0E209A0F660027712B /* JMCallKitListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69EFA0B209A0F660027712B /* JMCallKitListener.swift */; }; C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A3425E204EF76800E062DD /* DragGestureController.swift */; }; C6CC49AF207412CF000DFA42 /* PiPViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */; }; + DE438CDA2350934700DD541D /* JavaScriptSandbox.m in Sources */ = {isa = PBXBuildFile; fileRef = DE438CD82350934700DD541D /* JavaScriptSandbox.m */; }; DE65AACA2317FFCD00290BEC /* LogUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = DE65AAC92317FFCD00290BEC /* LogUtils.h */; }; DE65AACC2318028300290BEC /* JitsiMeetBaseLogHandler+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DE65AACB2318028300290BEC /* JitsiMeetBaseLogHandler+Private.h */; }; DE762DB422AFDE76000DEBD6 /* JitsiMeetUserInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DE762DB322AFDE76000DEBD6 /* JitsiMeetUserInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -104,6 +105,7 @@ C6A3425E204EF76800E062DD /* DragGestureController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DragGestureController.swift; sourceTree = ""; }; C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiPViewCoordinator.swift; sourceTree = ""; }; C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JitsiMeetView+Private.h"; sourceTree = ""; }; + DE438CD82350934700DD541D /* JavaScriptSandbox.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JavaScriptSandbox.m; sourceTree = ""; }; DE65AAC92317FFCD00290BEC /* LogUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LogUtils.h; sourceTree = ""; }; DE65AACB2318028300290BEC /* JitsiMeetBaseLogHandler+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "JitsiMeetBaseLogHandler+Private.h"; sourceTree = ""; }; DE762DB322AFDE76000DEBD6 /* JitsiMeetUserInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeetUserInfo.h; sourceTree = ""; }; @@ -190,6 +192,7 @@ A4A934E7212F3AB8001E9388 /* dropbox */, 0BA13D301EE83FF8007BEF7F /* ExternalAPI.m */, 0BD906E91EC0C00300C8C18E /* Info.plist */, + DE438CD82350934700DD541D /* JavaScriptSandbox.m */, 0BD906E81EC0C00300C8C18E /* JitsiMeet.h */, DEFE535821FB311F00011A3A /* JitsiMeet+Private.h */, DEFE535321FB1BF800011A3A /* JitsiMeet.m */, @@ -493,6 +496,7 @@ C69EFA0E209A0F660027712B /* JMCallKitListener.swift in Sources */, 0B412F191EDEC65D00B1A0A6 /* JitsiMeetView.m in Sources */, DEFE535421FB1BF800011A3A /* JitsiMeet.m in Sources */, + DE438CDA2350934700DD541D /* JavaScriptSandbox.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -635,11 +639,7 @@ INFOPLIST_FILE = src/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.JitsiMeetSDK.ios; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -670,11 +670,7 @@ INFOPLIST_FILE = src/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.JitsiMeetSDK.ios; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/ios/sdk/src/JavaScriptSandbox.m b/ios/sdk/src/JavaScriptSandbox.m new file mode 100644 index 000000000000..66a9d74a1e05 --- /dev/null +++ b/ios/sdk/src/JavaScriptSandbox.m @@ -0,0 +1,55 @@ +/* + * Copyright @ 2019-present 8x8, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@import JavaScriptCore; + +#import + + +@interface JavaScriptSandbox : NSObject +@end + +@implementation JavaScriptSandbox + +RCT_EXPORT_MODULE(); + ++ (BOOL)requiresMainQueueSetup { + return NO; +} + +#pragma mark - Exported methods + +RCT_EXPORT_METHOD(evaluate:(NSString *)code + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) { + __block BOOL hasError = NO; + JSContext *ctx = [[JSContext alloc] init]; + ctx.exceptionHandler = ^(JSContext *context, JSValue *exception) { + hasError = YES; + reject(@"evaluate", [exception toString], nil); + }; + JSValue *ret = [ctx evaluateScript:code]; + if (!hasError) { + NSString *result = [ret toString]; + if (result == nil) { + reject(@"evaluate", @"Error in string coercion", nil); + } else { + resolve(result); + } + } +} + +@end diff --git a/react/features/base/lib-jitsi-meet/functions.js b/react/features/base/lib-jitsi-meet/functions.any.js similarity index 74% rename from react/features/base/lib-jitsi-meet/functions.js rename to react/features/base/lib-jitsi-meet/functions.any.js index 64d3f78151a0..ab60d215b378 100644 --- a/react/features/base/lib-jitsi-meet/functions.js +++ b/react/features/base/lib-jitsi-meet/functions.any.js @@ -1,12 +1,7 @@ // @flow import { toState } from '../redux'; -import { loadScript } from '../util'; - import JitsiMeetJS from './_'; -import logger from './logger'; - -declare var APP: Object; const JitsiConferenceErrors = JitsiMeetJS.errors.conference; const JitsiConnectionErrors = JitsiMeetJS.errors.connection; @@ -97,42 +92,3 @@ export function isFatalJitsiConnectionError(error: Object | string) { || error === JitsiConnectionErrors.OTHER_ERROR || error === JitsiConnectionErrors.SERVER_ERROR); } - -/** - * Loads config.js from a specific remote server. - * - * @param {string} url - The URL to load. - * @returns {Promise} - */ -export function loadConfig(url: string): Promise { - let promise; - - if (typeof APP === 'undefined') { - promise - = loadScript(url, 2.5 * 1000 /* Timeout in ms */) - .then(() => { - const { config } = window; - - // We don't want to pollute the global scope. - window.config = undefined; - - if (typeof config !== 'object') { - throw new Error('window.config is not an object'); - } - - return config; - }) - .catch(err => { - logger.error(`Failed to load config from ${url}`, err); - - throw err; - }); - } else { - // Return "the config.js file" from the global scope - that is how the - // Web app on both the client and the server was implemented before the - // React Native app was even conceived. - promise = Promise.resolve(window.config); - } - - return promise; -} diff --git a/react/features/base/lib-jitsi-meet/functions.native.js b/react/features/base/lib-jitsi-meet/functions.native.js new file mode 100644 index 000000000000..ded4ffe8fe44 --- /dev/null +++ b/react/features/base/lib-jitsi-meet/functions.native.js @@ -0,0 +1,36 @@ +// @flow + +import { NativeModules } from 'react-native'; + +import { loadScript } from '../util'; +import logger from './logger'; + +export * from './functions.any'; + +const { JavaScriptSandbox } = NativeModules; + +/** + * Loads config.js from a specific remote server. + * + * @param {string} url - The URL to load. + * @returns {Promise} + */ +export async function loadConfig(url: string): Promise { + try { + const configTxt = await loadScript(url, 2.5 * 1000 /* Timeout in ms */, true /* skipeval */); + const configJson = await JavaScriptSandbox.evaluate(`${configTxt}\nJSON.stringify(config);`); + const config = JSON.parse(configJson); + + if (typeof config !== 'object') { + throw new Error('config is not an object'); + } + + logger.info(`Config loaded from ${url}`); + + return config; + } catch (err) { + logger.error(`Failed to load config from ${url}`, err); + + throw err; + } +} diff --git a/react/features/base/lib-jitsi-meet/functions.web.js b/react/features/base/lib-jitsi-meet/functions.web.js new file mode 100644 index 000000000000..e96b86a0d094 --- /dev/null +++ b/react/features/base/lib-jitsi-meet/functions.web.js @@ -0,0 +1,16 @@ +// @flow + +export * from './functions.any'; + +/** + * Loads config.js from a specific remote server. + * + * @param {string} url - The URL to load. + * @returns {Promise} + */ +export async function loadConfig(url: string): Promise { // eslint-disable-line no-unused-vars + // Return "the config.js file" from the global scope - that is how the + // Web app on both the client and the server was implemented before the + // React Native app was even conceived. + return window.config; +}