From 535f754b2688ad161a0ba99df1da8905a82682eb Mon Sep 17 00:00:00 2001 From: Guy Eldar Date: Wed, 15 Jun 2016 18:55:06 +0300 Subject: [PATCH 01/77] Made changes in the code to fit our new HBRichTextEditor lib --- ios/RCTWebViewBridge.h | 1 + ios/RCTWebViewBridge.m | 6 +++++- ios/RCTWebViewBridgeManager.m | 14 ++++++++++++++ webview-bridge/index.ios.js | 5 +++++ 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/ios/RCTWebViewBridge.h b/ios/RCTWebViewBridge.h index 09006303..7f76f008 100644 --- a/ios/RCTWebViewBridge.h +++ b/ios/RCTWebViewBridge.h @@ -44,5 +44,6 @@ shouldStartLoadForRequest:(NSMutableDictionary *)request - (void)goBack; - (void)reload; - (void)sendToBridge:(NSString *)message; +- (NSString*) getEditorHTML; @end diff --git a/ios/RCTWebViewBridge.m b/ios/RCTWebViewBridge.m index f97efeba..b447e7a0 100644 --- a/ios/RCTWebViewBridge.m +++ b/ios/RCTWebViewBridge.m @@ -99,7 +99,11 @@ - (void)sendToBridge:(NSString *)message ); NSString *command = [NSString stringWithFormat: format, message]; - [_webView stringByEvaluatingJavaScriptFromString:command]; + [_webView stringByEvaluatingJavaScriptFromString:message]; +} + +- (NSString*) getEditorHTML { + return [_webView stringByEvaluatingJavaScriptFromString:@"document.getElementById(\"zss_editor_content\").innerHTML"]; } - (NSURL *)URL diff --git a/ios/RCTWebViewBridgeManager.m b/ios/RCTWebViewBridgeManager.m index c3b1e6b9..cb2c6e2e 100644 --- a/ios/RCTWebViewBridgeManager.m +++ b/ios/RCTWebViewBridgeManager.m @@ -115,6 +115,20 @@ - (UIView *)view }]; } +RCT_EXPORT_METHOD(getEditorHTML:(nonnull NSNumber *)reactTag :(RCTResponseSenderBlock)callback) +{ + [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { + RCTWebViewBridge *view = viewRegistry[reactTag]; + if (![view isKindOfClass:[RCTWebViewBridge class]]) { + RCTLogError(@"Invalid view returned from registry, expecting RCTWebViewBridge, got: %@", view); + } else { + NSString* html = [view getEditorHTML]; + NSLog(@"Editor HTML is %@", html); + callback(@[[NSNull null], html]); + } + }]; +} + #pragma mark - Exported synchronous methods - (BOOL)webView:(__unused RCTWebViewBridge *)webView diff --git a/webview-bridge/index.ios.js b/webview-bridge/index.ios.js index b21dbee9..440d647d 100644 --- a/webview-bridge/index.ios.js +++ b/webview-bridge/index.ios.js @@ -230,6 +230,11 @@ var WebViewBridge = React.createClass({ WebViewBridgeManager.sendToBridge(this.getWebViewBridgeHandle(), message); }, + getEditorHTML: function (callback) { + var html = WebViewBridgeManager.getEditorHTML(this.getWebViewBridgeHandle(), callback); + console.log("html from editor is " + html); + }, + /** * We return an event with a bunch of fields including: * url, title, loading, canGoBack, canGoForward From a78221a09ce905a8120619aff160f4aa3fcc34f3 Mon Sep 17 00:00:00 2001 From: Guy Eldar Date: Sun, 19 Jun 2016 12:15:03 +0300 Subject: [PATCH 02/77] Changed getEditorHTML to getElementHTML - so it is generalised and now fits any element in the DOM --- ios/RCTWebViewBridge.h | 2 +- ios/RCTWebViewBridge.m | 5 +++-- ios/RCTWebViewBridgeManager.m | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/ios/RCTWebViewBridge.h b/ios/RCTWebViewBridge.h index 7f76f008..7c20fbfd 100644 --- a/ios/RCTWebViewBridge.h +++ b/ios/RCTWebViewBridge.h @@ -44,6 +44,6 @@ shouldStartLoadForRequest:(NSMutableDictionary *)request - (void)goBack; - (void)reload; - (void)sendToBridge:(NSString *)message; -- (NSString*) getEditorHTML; +- (NSString*) getElementHTML:(NSString*)elementId; @end diff --git a/ios/RCTWebViewBridge.m b/ios/RCTWebViewBridge.m index b447e7a0..484b66f3 100644 --- a/ios/RCTWebViewBridge.m +++ b/ios/RCTWebViewBridge.m @@ -102,8 +102,9 @@ - (void)sendToBridge:(NSString *)message [_webView stringByEvaluatingJavaScriptFromString:message]; } -- (NSString*) getEditorHTML { - return [_webView stringByEvaluatingJavaScriptFromString:@"document.getElementById(\"zss_editor_content\").innerHTML"]; +- (NSString*) getElementHTML:(NSString*)elementId { + NSString* JSString = [NSString stringWithFormat:@"document.getElementById(\"%@\").innerHTML",elementId]; + return [_webView stringByEvaluatingJavaScriptFromString:JSString]; } - (NSURL *)URL diff --git a/ios/RCTWebViewBridgeManager.m b/ios/RCTWebViewBridgeManager.m index cb2c6e2e..98ee9e42 100644 --- a/ios/RCTWebViewBridgeManager.m +++ b/ios/RCTWebViewBridgeManager.m @@ -115,14 +115,14 @@ - (UIView *)view }]; } -RCT_EXPORT_METHOD(getEditorHTML:(nonnull NSNumber *)reactTag :(RCTResponseSenderBlock)callback) +RCT_EXPORT_METHOD(getElementHTML:(nonnull NSNumber *)reactTag :(NSString*)elementId :(RCTResponseSenderBlock)callback) { [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { RCTWebViewBridge *view = viewRegistry[reactTag]; if (![view isKindOfClass:[RCTWebViewBridge class]]) { RCTLogError(@"Invalid view returned from registry, expecting RCTWebViewBridge, got: %@", view); } else { - NSString* html = [view getEditorHTML]; + NSString* html = [view getElementHTML:elementId]; NSLog(@"Editor HTML is %@", html); callback(@[[NSNull null], html]); } From f8e7a6375f87d7ce2b7e096da8a273b809a17560 Mon Sep 17 00:00:00 2001 From: Guy Eldar Date: Sun, 19 Jun 2016 12:29:37 +0300 Subject: [PATCH 03/77] Committing another leftover for converting getEditorHTML to getElementHTML --- webview-bridge/index.ios.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webview-bridge/index.ios.js b/webview-bridge/index.ios.js index 440d647d..34ae3469 100644 --- a/webview-bridge/index.ios.js +++ b/webview-bridge/index.ios.js @@ -230,8 +230,8 @@ var WebViewBridge = React.createClass({ WebViewBridgeManager.sendToBridge(this.getWebViewBridgeHandle(), message); }, - getEditorHTML: function (callback) { - var html = WebViewBridgeManager.getEditorHTML(this.getWebViewBridgeHandle(), callback); + getElementHTML: function (elementId: string, callback) { + var html = WebViewBridgeManager.getEditorHTML(this.getWebViewBridgeHandle(), elementId, callback); console.log("html from editor is " + html); }, From 0006d1d056c79547a8aa331766051fda42a4208e Mon Sep 17 00:00:00 2001 From: Guy Eldar Date: Sun, 19 Jun 2016 13:06:44 +0300 Subject: [PATCH 04/77] Sealing getElementHTML function --- webview-bridge/index.ios.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webview-bridge/index.ios.js b/webview-bridge/index.ios.js index 34ae3469..d2209646 100644 --- a/webview-bridge/index.ios.js +++ b/webview-bridge/index.ios.js @@ -231,7 +231,7 @@ var WebViewBridge = React.createClass({ }, getElementHTML: function (elementId: string, callback) { - var html = WebViewBridgeManager.getEditorHTML(this.getWebViewBridgeHandle(), elementId, callback); + var html = WebViewBridgeManager.getElementHTML(this.getWebViewBridgeHandle(), elementId, callback); console.log("html from editor is " + html); }, From 3c884701498ad849ec7f2d4d2b9f6a91fee45123 Mon Sep 17 00:00:00 2001 From: Ali Najafizadeh Date: Sat, 24 Sep 2016 15:28:46 -0400 Subject: [PATCH 05/77] added rpc empty example --- examples/rpc/.buckconfig | 6 + examples/rpc/.flowconfig | 58 ++ examples/rpc/.gitignore | 41 + examples/rpc/.watchmanconfig | 1 + examples/rpc/android/app/BUCK | 66 ++ examples/rpc/android/app/build.gradle | 139 ++++ examples/rpc/android/app/proguard-rules.pro | 66 ++ .../android/app/src/main/AndroidManifest.xml | 31 + .../src/main/java/com/rpc/MainActivity.java | 15 + .../main/java/com/rpc/MainApplication.java | 35 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3418 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2206 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4842 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 7718 bytes .../app/src/main/res/values/strings.xml | 3 + .../app/src/main/res/values/styles.xml | 8 + examples/rpc/android/build.gradle | 24 + examples/rpc/android/gradle.properties | 20 + .../android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 52266 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + examples/rpc/android/gradlew | 164 ++++ examples/rpc/android/gradlew.bat | 90 ++ examples/rpc/android/keystores/BUCK | 8 + .../keystores/debug.keystore.properties | 4 + examples/rpc/android/settings.gradle | 3 + examples/rpc/index.android.js | 53 ++ examples/rpc/index.ios.js | 53 ++ .../rpc/ios/rpc.xcodeproj/project.pbxproj | 769 ++++++++++++++++++ .../xcshareddata/xcschemes/rpc.xcscheme | 112 +++ examples/rpc/ios/rpc/AppDelegate.h | 16 + examples/rpc/ios/rpc/AppDelegate.m | 37 + .../rpc/ios/rpc/Base.lproj/LaunchScreen.xib | 42 + .../AppIcon.appiconset/Contents.json | 38 + examples/rpc/ios/rpc/Info.plist | 54 ++ examples/rpc/ios/rpc/main.m | 18 + examples/rpc/ios/rpcTests/Info.plist | 24 + examples/rpc/ios/rpcTests/rpcTests.m | 70 ++ examples/rpc/package.json | 12 + 38 files changed, 2085 insertions(+) create mode 100644 examples/rpc/.buckconfig create mode 100644 examples/rpc/.flowconfig create mode 100644 examples/rpc/.gitignore create mode 100644 examples/rpc/.watchmanconfig create mode 100644 examples/rpc/android/app/BUCK create mode 100644 examples/rpc/android/app/build.gradle create mode 100644 examples/rpc/android/app/proguard-rules.pro create mode 100644 examples/rpc/android/app/src/main/AndroidManifest.xml create mode 100644 examples/rpc/android/app/src/main/java/com/rpc/MainActivity.java create mode 100644 examples/rpc/android/app/src/main/java/com/rpc/MainApplication.java create mode 100644 examples/rpc/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 examples/rpc/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 examples/rpc/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 examples/rpc/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 examples/rpc/android/app/src/main/res/values/strings.xml create mode 100644 examples/rpc/android/app/src/main/res/values/styles.xml create mode 100644 examples/rpc/android/build.gradle create mode 100644 examples/rpc/android/gradle.properties create mode 100644 examples/rpc/android/gradle/wrapper/gradle-wrapper.jar create mode 100644 examples/rpc/android/gradle/wrapper/gradle-wrapper.properties create mode 100755 examples/rpc/android/gradlew create mode 100644 examples/rpc/android/gradlew.bat create mode 100644 examples/rpc/android/keystores/BUCK create mode 100644 examples/rpc/android/keystores/debug.keystore.properties create mode 100644 examples/rpc/android/settings.gradle create mode 100644 examples/rpc/index.android.js create mode 100644 examples/rpc/index.ios.js create mode 100644 examples/rpc/ios/rpc.xcodeproj/project.pbxproj create mode 100644 examples/rpc/ios/rpc.xcodeproj/xcshareddata/xcschemes/rpc.xcscheme create mode 100644 examples/rpc/ios/rpc/AppDelegate.h create mode 100644 examples/rpc/ios/rpc/AppDelegate.m create mode 100644 examples/rpc/ios/rpc/Base.lproj/LaunchScreen.xib create mode 100644 examples/rpc/ios/rpc/Images.xcassets/AppIcon.appiconset/Contents.json create mode 100644 examples/rpc/ios/rpc/Info.plist create mode 100644 examples/rpc/ios/rpc/main.m create mode 100644 examples/rpc/ios/rpcTests/Info.plist create mode 100644 examples/rpc/ios/rpcTests/rpcTests.m create mode 100644 examples/rpc/package.json diff --git a/examples/rpc/.buckconfig b/examples/rpc/.buckconfig new file mode 100644 index 00000000..934256cb --- /dev/null +++ b/examples/rpc/.buckconfig @@ -0,0 +1,6 @@ + +[android] + target = Google Inc.:Google APIs:23 + +[maven_repositories] + central = https://repo1.maven.org/maven2 diff --git a/examples/rpc/.flowconfig b/examples/rpc/.flowconfig new file mode 100644 index 00000000..f565799e --- /dev/null +++ b/examples/rpc/.flowconfig @@ -0,0 +1,58 @@ +[ignore] + +# We fork some components by platform. +.*/*[.]android.js + +# Ignore templates with `@flow` in header +.*/local-cli/generator.* + +# Ignore malformed json +.*/node_modules/y18n/test/.*\.json + +# Ignore the website subdir +/website/.* + +# Ignore BUCK generated dirs +/\.buckd/ + +# Ignore unexpected extra @providesModule +.*/node_modules/commoner/test/source/widget/share.js + +# Ignore duplicate module providers +# For RN Apps installed via npm, "Libraries" folder is inside node_modules/react-native but in the source repo it is in the root +.*/Libraries/react-native/React.js +.*/Libraries/react-native/ReactNative.js +.*/node_modules/jest-runtime/build/__tests__/.* + +[include] + +[libs] +node_modules/react-native/Libraries/react-native/react-native-interface.js +node_modules/react-native/flow +flow/ + +[options] +module.system=haste + +esproposal.class_static_fields=enable +esproposal.class_instance_fields=enable + +experimental.strict_type_args=true + +munge_underscores=true + +module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub' +module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' + +suppress_type=$FlowIssue +suppress_type=$FlowFixMe +suppress_type=$FixMe + +suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(3[0-2]\\|[1-2][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) +suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(3[0-2]\\|1[0-9]\\|[1-2][0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ +suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy + +unsafe.enable_getters_and_setters=true + +[version] +^0.32.0 diff --git a/examples/rpc/.gitignore b/examples/rpc/.gitignore new file mode 100644 index 00000000..eb1535e4 --- /dev/null +++ b/examples/rpc/.gitignore @@ -0,0 +1,41 @@ +# OSX +# +.DS_Store + +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate +project.xcworkspace + +# Android/IJ +# +*.iml +.idea +.gradle +local.properties + +# node.js +# +node_modules/ +npm-debug.log + +# BUCK +buck-out/ +\.buckd/ +android/app/libs +android/keystores/debug.keystore diff --git a/examples/rpc/.watchmanconfig b/examples/rpc/.watchmanconfig new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/examples/rpc/.watchmanconfig @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/examples/rpc/android/app/BUCK b/examples/rpc/android/app/BUCK new file mode 100644 index 00000000..db1800ab --- /dev/null +++ b/examples/rpc/android/app/BUCK @@ -0,0 +1,66 @@ +import re + +# To learn about Buck see [Docs](https://buckbuild.com/). +# To run your application with Buck: +# - install Buck +# - `npm start` - to start the packager +# - `cd android` +# - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` +# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck +# - `buck install -r android/app` - compile, install and run application +# + +lib_deps = [] +for jarfile in glob(['libs/*.jar']): + name = 'jars__' + re.sub(r'^.*/([^/]+)\.jar$', r'\1', jarfile) + lib_deps.append(':' + name) + prebuilt_jar( + name = name, + binary_jar = jarfile, + ) + +for aarfile in glob(['libs/*.aar']): + name = 'aars__' + re.sub(r'^.*/([^/]+)\.aar$', r'\1', aarfile) + lib_deps.append(':' + name) + android_prebuilt_aar( + name = name, + aar = aarfile, + ) + +android_library( + name = 'all-libs', + exported_deps = lib_deps +) + +android_library( + name = 'app-code', + srcs = glob([ + 'src/main/java/**/*.java', + ]), + deps = [ + ':all-libs', + ':build_config', + ':res', + ], +) + +android_build_config( + name = 'build_config', + package = 'com.rpc', +) + +android_resource( + name = 'res', + res = 'src/main/res', + package = 'com.rpc', +) + +android_binary( + name = 'app', + package_type = 'debug', + manifest = 'src/main/AndroidManifest.xml', + keystore = '//android/keystores:debug', + deps = [ + ':app-code', + ], +) diff --git a/examples/rpc/android/app/build.gradle b/examples/rpc/android/app/build.gradle new file mode 100644 index 00000000..8ba8b8ac --- /dev/null +++ b/examples/rpc/android/app/build.gradle @@ -0,0 +1,139 @@ +apply plugin: "com.android.application" + +import com.android.build.OutputFile + +/** + * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets + * and bundleReleaseJsAndAssets). + * These basically call `react-native bundle` with the correct arguments during the Android build + * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the + * bundle directly from the development server. Below you can see all the possible configurations + * and their defaults. If you decide to add a configuration block, make sure to add it before the + * `apply from: "../../node_modules/react-native/react.gradle"` line. + * + * project.ext.react = [ + * // the name of the generated asset file containing your JS bundle + * bundleAssetName: "index.android.bundle", + * + * // the entry file for bundle generation + * entryFile: "index.android.js", + * + * // whether to bundle JS and assets in debug mode + * bundleInDebug: false, + * + * // whether to bundle JS and assets in release mode + * bundleInRelease: true, + * + * // whether to bundle JS and assets in another build variant (if configured). + * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants + * // The configuration property can be in the following formats + * // 'bundleIn${productFlavor}${buildType}' + * // 'bundleIn${buildType}' + * // bundleInFreeDebug: true, + * // bundleInPaidRelease: true, + * // bundleInBeta: true, + * + * // the root of your project, i.e. where "package.json" lives + * root: "../../", + * + * // where to put the JS bundle asset in debug mode + * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", + * + * // where to put the JS bundle asset in release mode + * jsBundleDirRelease: "$buildDir/intermediates/assets/release", + * + * // where to put drawable resources / React Native assets, e.g. the ones you use via + * // require('./image.png')), in debug mode + * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", + * + * // where to put drawable resources / React Native assets, e.g. the ones you use via + * // require('./image.png')), in release mode + * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", + * + * // by default the gradle tasks are skipped if none of the JS files or assets change; this means + * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to + * // date; if you have any other folders that you want to ignore for performance reasons (gradle + * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ + * // for example, you might want to remove it from here. + * inputExcludes: ["android/**", "ios/**"], + * + * // override which node gets called and with what additional arguments + * nodeExecutableAndArgs: ["node"] + * + * // supply additional arguments to the packager + * extraPackagerArgs: [] + * ] + */ + +apply from: "../../node_modules/react-native/react.gradle" + +/** + * Set this to true to create two separate APKs instead of one: + * - An APK that only works on ARM devices + * - An APK that only works on x86 devices + * The advantage is the size of the APK is reduced by about 4MB. + * Upload all the APKs to the Play Store and people will download + * the correct one based on the CPU architecture of their device. + */ +def enableSeparateBuildPerCPUArchitecture = false + +/** + * Run Proguard to shrink the Java bytecode in release builds. + */ +def enableProguardInReleaseBuilds = false + +android { + compileSdkVersion 23 + buildToolsVersion "23.0.1" + + defaultConfig { + applicationId "com.rpc" + minSdkVersion 16 + targetSdkVersion 22 + versionCode 1 + versionName "1.0" + ndk { + abiFilters "armeabi-v7a", "x86" + } + } + splits { + abi { + reset() + enable enableSeparateBuildPerCPUArchitecture + universalApk false // If true, also generate a universal APK + include "armeabi-v7a", "x86" + } + } + buildTypes { + release { + minifyEnabled enableProguardInReleaseBuilds + proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" + } + } + // applicationVariants are e.g. debug, release + applicationVariants.all { variant -> + variant.outputs.each { output -> + // For each separate APK per architecture, set a unique version code as described here: + // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits + def versionCodes = ["armeabi-v7a":1, "x86":2] + def abi = output.getFilter(OutputFile.ABI) + if (abi != null) { // null for the universal-debug, universal-release variants + output.versionCodeOverride = + versionCodes.get(abi) * 1048576 + defaultConfig.versionCode + } + } + } +} + +dependencies { + compile fileTree(dir: "libs", include: ["*.jar"]) + compile "com.android.support:appcompat-v7:23.0.1" + compile "com.facebook.react:react-native:+" // From node_modules +} + +// Run this once to be able to run the application with BUCK +// puts all compile dependencies into folder libs for BUCK to use +task copyDownloadableDepsToLibs(type: Copy) { + from configurations.compile + into 'libs' +} diff --git a/examples/rpc/android/app/proguard-rules.pro b/examples/rpc/android/app/proguard-rules.pro new file mode 100644 index 00000000..48361a90 --- /dev/null +++ b/examples/rpc/android/app/proguard-rules.pro @@ -0,0 +1,66 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Disabling obfuscation is useful if you collect stack traces from production crashes +# (unless you are using a system that supports de-obfuscate the stack traces). +-dontobfuscate + +# React Native + +# Keep our interfaces so they can be used by other ProGuard rules. +# See http://sourceforge.net/p/proguard/bugs/466/ +-keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip +-keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters +-keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip + +# Do not strip any method/class that is annotated with @DoNotStrip +-keep @com.facebook.proguard.annotations.DoNotStrip class * +-keep @com.facebook.common.internal.DoNotStrip class * +-keepclassmembers class * { + @com.facebook.proguard.annotations.DoNotStrip *; + @com.facebook.common.internal.DoNotStrip *; +} + +-keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * { + void set*(***); + *** get*(); +} + +-keep class * extends com.facebook.react.bridge.JavaScriptModule { *; } +-keep class * extends com.facebook.react.bridge.NativeModule { *; } +-keepclassmembers,includedescriptorclasses class * { native ; } +-keepclassmembers class * { @com.facebook.react.uimanager.UIProp ; } +-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp ; } +-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup ; } + +-dontwarn com.facebook.react.** + +# okhttp + +-keepattributes Signature +-keepattributes *Annotation* +-keep class okhttp3.** { *; } +-keep interface okhttp3.** { *; } +-dontwarn okhttp3.** + +# okio + +-keep class sun.misc.Unsafe { *; } +-dontwarn java.nio.file.* +-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement +-dontwarn okio.** diff --git a/examples/rpc/android/app/src/main/AndroidManifest.xml b/examples/rpc/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..8b5dc259 --- /dev/null +++ b/examples/rpc/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + diff --git a/examples/rpc/android/app/src/main/java/com/rpc/MainActivity.java b/examples/rpc/android/app/src/main/java/com/rpc/MainActivity.java new file mode 100644 index 00000000..95c98ab3 --- /dev/null +++ b/examples/rpc/android/app/src/main/java/com/rpc/MainActivity.java @@ -0,0 +1,15 @@ +package com.rpc; + +import com.facebook.react.ReactActivity; + +public class MainActivity extends ReactActivity { + + /** + * Returns the name of the main component registered from JavaScript. + * This is used to schedule rendering of the component. + */ + @Override + protected String getMainComponentName() { + return "rpc"; + } +} diff --git a/examples/rpc/android/app/src/main/java/com/rpc/MainApplication.java b/examples/rpc/android/app/src/main/java/com/rpc/MainApplication.java new file mode 100644 index 00000000..774c206f --- /dev/null +++ b/examples/rpc/android/app/src/main/java/com/rpc/MainApplication.java @@ -0,0 +1,35 @@ +package com.rpc; + +import android.app.Application; +import android.util.Log; + +import com.facebook.react.ReactApplication; +import com.facebook.react.ReactInstanceManager; +import com.facebook.react.ReactNativeHost; +import com.facebook.react.ReactPackage; +import com.facebook.react.shell.MainReactPackage; + +import java.util.Arrays; +import java.util.List; + +public class MainApplication extends Application implements ReactApplication { + + private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { + @Override + protected boolean getUseDeveloperSupport() { + return BuildConfig.DEBUG; + } + + @Override + protected List getPackages() { + return Arrays.asList( + new MainReactPackage() + ); + } + }; + + @Override + public ReactNativeHost getReactNativeHost() { + return mReactNativeHost; + } +} diff --git a/examples/rpc/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/examples/rpc/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..cde69bcccec65160d92116f20ffce4fce0b5245c GIT binary patch literal 3418 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 literal 0 HcmV?d00001 diff --git a/examples/rpc/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/examples/rpc/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..bfa42f0e7b91d006d22352c9ff2f134e504e3c1d GIT binary patch literal 4842 zcmZ{oXE5C1x5t0WvTCfdv7&7fy$d2l*k#q|U5FAbL??P!61}%ovaIM)mL!5G(V|6J zAtDH(OY|Du^}l!K&fFLG%sJ2JIp@rG=9y>Ci)Wq~U2RobsvA@Q0MM$dq4lq5{hy#9 zzgp+B{O(-=?1<7r0l>Q?>N6X%s~lmgrmqD6fjj_!c?AF`S0&6U06Z51fWOuNAe#jM z%pSN#J-Mp}`ICpL=qp~?u~Jj$6(~K_%)9}Bn(;pY0&;M00H9x2N23h=CpR7kr8A9X zU%oh4-E@i!Ac}P+&%vOPQ3warO9l!SCN)ixGW54Jsh!`>*aU)#&Mg7;#O_6xd5%I6 zneGSZL3Kn-4B^>#T7pVaIHs3^PY-N^v1!W=%gzfioIWosZ!BN?_M)OOux&6HCyyMf z3ToZ@_h75A33KyC!T)-zYC-bp`@^1n;w3~N+vQ0#4V7!f|JPMlWWJ@+Tg~8>1$GzLlHGuxS)w&NAF*&Y;ef`T^w4HP7GK%6UA8( z{&ALM(%!w2U7WFWwq8v4H3|0cOjdt7$JLh(;U8VcTG;R-vmR7?21nA?@@b+XPgJbD z*Y@v&dTqo5Bcp-dIQQ4@?-m{=7>`LZ{g4jvo$CE&(+7(rp#WShT9&9y>V#ikmXFau03*^{&d(AId0Jg9G;tc7K_{ivzBjqHuJx08cx<8U`z2JjtOK3( zvtuduBHha>D&iu#))5RKXm>(|$m=_;e?7ZveYy=J$3wjL>xPCte-MDcVW<;ng`nf= z9);CVVZjI-&UcSAlhDB{%0v$wPd=w6MBwsVEaV!hw~8G(rs`lw@|#AAHbyA&(I-7Y zFE&1iIGORsaskMqSYfX33U%&17oTszdHPjr&Sx(`IQzoccST*}!cU!ZnJ+~duBM6f z{Lf8PITt%uWZ zTY09Jm5t<2+Un~yC-%DYEP>c-7?=+|reXO4Cd^neCQ{&aP@yODLN8}TQAJ8ogsnkb zM~O>~3&n6d+ee`V_m@$6V`^ltL&?uwt|-afgd7BQ9Kz|g{B@K#qQ#$o4ut`9lQsYfHofccNoqE+`V zQ&UXP{X4=&Z16O_wCk9SFBQPKyu?<&B2zDVhI6%B$12c^SfcRYIIv!s1&r|8;xw5t zF~*-cE@V$vaB;*+91`CiN~1l8w${?~3Uy#c|D{S$I? zb!9y)DbLJ3pZ>!*+j=n@kOLTMr-T2>Hj^I~lml-a26UP1_?#!5S_a&v zeZ86(21wU0)4(h&W0iE*HaDlw+-LngX=}es#X$u*1v9>qR&qUGfADc7yz6$WN`cx9 zzB#!5&F%AK=ed|-eV6kb;R>Atp2Rk=g3lU6(IVEP3!;0YNAmqz=x|-mE&8u5W+zo7 z-QfwS6uzp9K4wC-Te-1~u?zPb{RjjIVoL1bQ=-HK_a_muB>&3I z*{e{sE_sI$CzyK-x>7abBc+uIZf?#e8;K_JtJexgpFEBMq92+Fm0j*DziUMras`o= zTzby8_XjyCYHeE@q&Q_7x?i|V9XY?MnSK;cLV?k>vf?!N87)gFPc9#XB?p)bEWGs$ zH>f$8?U7In{9@vsd%#sY5u!I$)g^%ZyutkNBBJ0eHQeiR5!DlQbYZJ-@09;c?IP7A zx>P=t*xm1rOqr@ec>|ziw@3e$ymK7YSXtafMk30i?>>1lC>LLK1~JV1n6EJUGJT{6 zWP4A(129xkvDP09j<3#1$T6j6$mZaZ@vqUBBM4Pi!H>U8xvy`bkdSNTGVcfkk&y8% z=2nfA@3kEaubZ{1nwTV1gUReza>QX%_d}x&2`jE*6JZN{HZtXSr{{6v6`r47MoA~R zejyMpeYbJ$F4*+?*=Fm7E`S_rUC0v+dHTlj{JnkW-_eRa#9V`9o!8yv_+|lB4*+p1 zUI-t)X$J{RRfSrvh80$OW_Wwp>`4*iBr|oodPt*&A9!SO(x|)UgtVvETLuLZ<-vRp z&zAubgm&J8Pt647V?Qxh;`f6E#Zgx5^2XV($YMV7;Jn2kx6aJn8T>bo?5&;GM4O~| zj>ksV0U}b}wDHW`pgO$L@Hjy2`a)T}s@(0#?y3n zj;yjD76HU&*s!+k5!G4<3{hKah#gBz8HZ6v`bmURyDi(wJ!C7+F%bKnRD4=q{(Fl0 zOp*r}F`6~6HHBtq$afFuXsGAk58!e?O(W$*+3?R|cDO88<$~pg^|GRHN}yml3WkbL zzSH*jmpY=`g#ZX?_XT`>-`INZ#d__BJ)Ho^&ww+h+3>y8Z&T*EI!mtgEqiofJ@5&E z6M6a}b255hCw6SFJ4q(==QN6CUE3GYnfjFNE+x8T(+J!C!?v~Sbh`Sl_0CJ;vvXsP z5oZRiPM-Vz{tK(sJM~GI&VRbBOd0JZmGzqDrr9|?iPT(qD#M*RYb$>gZi*i)xGMD`NbmZt;ky&FR_2+YqpmFb`8b`ry;}D+y&WpUNd%3cfuUsb8 z7)1$Zw?bm@O6J1CY9UMrle_BUM<$pL=YI^DCz~!@p25hE&g62n{j$?UsyYjf#LH~b z_n!l6Z(J9daalVYSlA?%=mfp(!e+Hk%%oh`t%0`F`KR*b-Zb=7SdtDS4`&&S@A)f>bKC7vmRWwT2 zH}k+2Hd7@>jiHwz^GrOeU8Y#h?YK8>a*vJ#s|8-uX_IYp*$9Y=W_Edf%$V4>w;C3h z&>ZDGavV7UA@0QIQV$&?Z_*)vj{Q%z&(IW!b-!MVDGytRb4DJJV)(@WG|MbhwCx!2 z6QJMkl^4ju9ou8Xjb*pv=Hm8DwYsw23wZqQFUI)4wCMjPB6o8yG7@Sn^5%fmaFnfD zSxp8R-L({J{p&cR7)lY+PA9#8Bx87;mB$zXCW8VDh0&g#@Z@lktyArvzgOn&-zerA zVEa9h{EYvWOukwVUGWUB5xr4{nh}a*$v^~OEasKj)~HyP`YqeLUdN~f!r;0dV7uho zX)iSYE&VG67^NbcP5F*SIE@T#=NVjJ1=!Mn!^oeCg1L z?lv_%(ZEe%z*pGM<(UG{eF1T(#PMw}$n0aihzGoJAP^UceQMiBuE8Y`lZ|sF2_h_6 zQw*b*=;2Ey_Flpfgsr4PimZ~8G~R(vU}^Zxmri5)l?N>M_dWyCsjZw<+a zqjmL0l*}PXNGUOh)YxP>;ENiJTd|S^%BARx9D~%7x?F6u4K(Bx0`KK2mianotlX^9 z3z?MW7Coqy^ol0pH)Z3+GwU|Lyuj#7HCrqs#01ZF&KqEg!olHc$O#Wn>Ok_k2`zoD z+LYbxxVMf<(d2OkPIm8Xn>bwFsF6m8@i7PA$sdK~ZA4|ic?k*q2j1YQ>&A zjPO%H@H(h`t+irQqx+e)ll9LGmdvr1zXV;WTi}KCa>K82n90s|K zi`X}C*Vb12p?C-sp5maVDP5{&5$E^k6~BuJ^UxZaM=o+@(LXBWChJUJ|KEckEJTZL zI2K&Nd$U65YoF3_J6+&YU4uKGMq2W6ZQ%BG>4HnIM?V;;Ohes{`Ucs56ue^7@D7;4 z+EsFB)a_(%K6jhxND}n!UBTuF3wfrvll|mp7)3wi&2?LW$+PJ>2)2C-6c@O&lKAn zOm=$x*dn&dI8!QCb(ul|t3oDY^MjHqxl~lp{p@#C%Od-U4y@NQ4=`U!YjK$7b=V}D z%?E40*f8DVrvV2nV>`Z3f5yuz^??$#3qR#q6F($w>kmKK`x21VmX=9kb^+cPdBY2l zGkIZSf%C+`2nj^)j zo}g}v;5{nk<>%xj-2OqDbJ3S`7|tQWqdvJdgiL{1=w0!qS9$A`w9Qm7>N0Y*Ma%P_ zr@fR4>5u{mKwgZ33Xs$RD6(tcVH~Mas-87Fd^6M6iuV^_o$~ql+!eBIw$U)lzl`q9 z=L6zVsZzi0IIW=DT&ES9HajKhb5lz4yQxT-NRBLv_=2sn7WFX&Wp6Y!&}P+%`!A;s zrCwXO3}jrdA7mB`h~N~HT64TM{R$lNj*~ekqSP^n9P~z;P zWPlRPz0h6za8-P>!ARb+A1-r>8VF*xhrGa8W6J$p*wy`ULrD$CmYV7Gt^scLydQWbo7XN-o9X1i7;l+J_8Ncu zc=EX&dg`GRo4==cz2d_Rz28oLS`Suf6OCp~f{0-aQ`t5YZ=!CAMc6-RZw#}A%;s44 znf2`6gcgm=0SezTH9h+JzeR3Lcm;8?*@+?FDfguK^9)z(Z`I!RKrSAI?H~4et6GTkz07Qgq4B6%Q*8Y0yPc4x z8(^YwtZjYIeOvVLey#>@$UzIciJ#x0pJLFg=8UaZv%-&?Yzp7gWNIo_x^(d75=x2c zv|LQ`HrKP(8TqFxTiP5gdT2>aTN0S7XW*pilASS$UkJ2*n+==D)0mgTGxv43t61fr z47GkfMnD-zSH@|mZ26r*d3WEtr+l-xH@L}BM)~ThoMvKqGw=Ifc}BdkL$^wC}=(XSf4YpG;sA9#OSJf)V=rs#Wq$?Wj+nTlu$YXn yn3SQon5>kvtkl(BT2@T#Mvca!|08g9w{vm``2PjZHg=b<1c17-HkzPl9sXa)&-Ts$ literal 0 HcmV?d00001 diff --git a/examples/rpc/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/examples/rpc/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..324e72cdd7480cb983fa1bcc7ce686e51ef87fe7 GIT binary patch literal 7718 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 literal 0 HcmV?d00001 diff --git a/examples/rpc/android/app/src/main/res/values/strings.xml b/examples/rpc/android/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..896c66e3 --- /dev/null +++ b/examples/rpc/android/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + rpc + diff --git a/examples/rpc/android/app/src/main/res/values/styles.xml b/examples/rpc/android/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..319eb0ca --- /dev/null +++ b/examples/rpc/android/app/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/examples/rpc/android/build.gradle b/examples/rpc/android/build.gradle new file mode 100644 index 00000000..fcba4c58 --- /dev/null +++ b/examples/rpc/android/build.gradle @@ -0,0 +1,24 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.3.1' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + mavenLocal() + jcenter() + maven { + // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm + url "$rootDir/../node_modules/react-native/android" + } + } +} diff --git a/examples/rpc/android/gradle.properties b/examples/rpc/android/gradle.properties new file mode 100644 index 00000000..1fd964e9 --- /dev/null +++ b/examples/rpc/android/gradle.properties @@ -0,0 +1,20 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx10248m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true + +android.useDeprecatedNdk=true diff --git a/examples/rpc/android/gradle/wrapper/gradle-wrapper.jar b/examples/rpc/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..b5166dad4d90021f6a0b45268c0755719f1d5cd4 GIT binary patch literal 52266 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 z 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 literal 0 HcmV?d00001 diff --git a/examples/rpc/android/gradle/wrapper/gradle-wrapper.properties b/examples/rpc/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..b9fbfaba --- /dev/null +++ b/examples/rpc/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip diff --git a/examples/rpc/android/gradlew b/examples/rpc/android/gradlew new file mode 100755 index 00000000..91a7e269 --- /dev/null +++ b/examples/rpc/android/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/examples/rpc/android/gradlew.bat b/examples/rpc/android/gradlew.bat new file mode 100644 index 00000000..aec99730 --- /dev/null +++ b/examples/rpc/android/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/examples/rpc/android/keystores/BUCK b/examples/rpc/android/keystores/BUCK new file mode 100644 index 00000000..15da20e6 --- /dev/null +++ b/examples/rpc/android/keystores/BUCK @@ -0,0 +1,8 @@ +keystore( + name = 'debug', + store = 'debug.keystore', + properties = 'debug.keystore.properties', + visibility = [ + 'PUBLIC', + ], +) diff --git a/examples/rpc/android/keystores/debug.keystore.properties b/examples/rpc/android/keystores/debug.keystore.properties new file mode 100644 index 00000000..121bfb49 --- /dev/null +++ b/examples/rpc/android/keystores/debug.keystore.properties @@ -0,0 +1,4 @@ +key.store=debug.keystore +key.alias=androiddebugkey +key.store.password=android +key.alias.password=android diff --git a/examples/rpc/android/settings.gradle b/examples/rpc/android/settings.gradle new file mode 100644 index 00000000..756c08a8 --- /dev/null +++ b/examples/rpc/android/settings.gradle @@ -0,0 +1,3 @@ +rootProject.name = 'rpc' + +include ':app' diff --git a/examples/rpc/index.android.js b/examples/rpc/index.android.js new file mode 100644 index 00000000..7b23b330 --- /dev/null +++ b/examples/rpc/index.android.js @@ -0,0 +1,53 @@ +/** + * Sample React Native App + * https://github.com/facebook/react-native + * @flow + */ + +import React, { Component } from 'react'; +import { + AppRegistry, + StyleSheet, + Text, + View +} from 'react-native'; + +class rpc extends Component { + render() { + return ( + + + Welcome to React Native! + + + To get started, edit index.android.js + + + Double tap R on your keyboard to reload,{'\n'} + Shake or press menu button for dev menu + + + ); + } +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: '#F5FCFF', + }, + welcome: { + fontSize: 20, + textAlign: 'center', + margin: 10, + }, + instructions: { + textAlign: 'center', + color: '#333333', + marginBottom: 5, + }, +}); + +AppRegistry.registerComponent('rpc', () => rpc); diff --git a/examples/rpc/index.ios.js b/examples/rpc/index.ios.js new file mode 100644 index 00000000..6561e400 --- /dev/null +++ b/examples/rpc/index.ios.js @@ -0,0 +1,53 @@ +/** + * Sample React Native App + * https://github.com/facebook/react-native + * @flow + */ + +import React, { Component } from 'react'; +import { + AppRegistry, + StyleSheet, + Text, + View +} from 'react-native'; + +class rpc extends Component { + render() { + return ( + + + Welcome to React Native! + + + To get started, edit index.ios.js + + + Press Cmd+R to reload,{'\n'} + Cmd+D or shake for dev menu + + + ); + } +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: '#F5FCFF', + }, + welcome: { + fontSize: 20, + textAlign: 'center', + margin: 10, + }, + instructions: { + textAlign: 'center', + color: '#333333', + marginBottom: 5, + }, +}); + +AppRegistry.registerComponent('rpc', () => rpc); diff --git a/examples/rpc/ios/rpc.xcodeproj/project.pbxproj b/examples/rpc/ios/rpc.xcodeproj/project.pbxproj new file mode 100644 index 00000000..b445f715 --- /dev/null +++ b/examples/rpc/ios/rpc.xcodeproj/project.pbxproj @@ -0,0 +1,769 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; }; + 00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */; }; + 00C302E81ABCBA2D00DB3ED1 /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */; }; + 00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */; }; + 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */; }; + 00E356F31AD99517003FC87E /* rpcTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* rpcTests.m */; }; + 133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 78C398B91ACF4ADC00677621 /* libRCTLinking.a */; }; + 139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */; }; + 139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */; }; + 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; + 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; }; + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; + 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; + 140ED2AC1D01E1AD002B40FF /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; }; + 146834051AC3E58100842450 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; }; + 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 00C302AB1ABCB8CE00DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RCTActionSheet; + }; + 00C302B91ABCB90400DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RCTGeolocation; + }; + 00C302BF1ABCB91800DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 58B5115D1A9E6B3D00147676; + remoteInfo = RCTImage; + }; + 00C302DB1ABCB9D200DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 58B511DB1A9E6C8500147676; + remoteInfo = RCTNetwork; + }; + 00C302E31ABCB9EE00DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 832C81801AAF6DEF007FA2F7; + remoteInfo = RCTVibration; + }; + 00E356F41AD99517003FC87E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 13B07F861A680F5B00A75B9A; + remoteInfo = rpc; + }; + 139105C01AF99BAD00B5F7CC /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RCTSettings; + }; + 139FDEF31B06529B00C62182 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3C86DF461ADF2C930047B81A; + remoteInfo = RCTWebSocket; + }; + 146834031AC3E56700842450 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 83CBBA2E1A601D0E00E9B192; + remoteInfo = React; + }; + 78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RCTLinking; + }; + 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 58B5119B1A9E6C1200147676; + remoteInfo = RCTText; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = main.jsbundle; path = main.jsbundle; sourceTree = ""; }; + 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = ../node_modules/react-native/Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj; sourceTree = ""; }; + 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = ../node_modules/react-native/Libraries/Geolocation/RCTGeolocation.xcodeproj; sourceTree = ""; }; + 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = ../node_modules/react-native/Libraries/Image/RCTImage.xcodeproj; sourceTree = ""; }; + 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = ../node_modules/react-native/Libraries/Network/RCTNetwork.xcodeproj; sourceTree = ""; }; + 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = ../node_modules/react-native/Libraries/Vibration/RCTVibration.xcodeproj; sourceTree = ""; }; + 00E356EE1AD99517003FC87E /* rpcTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = rpcTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 00E356F21AD99517003FC87E /* rpcTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = rpcTests.m; sourceTree = ""; }; + 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = ../node_modules/react-native/Libraries/Settings/RCTSettings.xcodeproj; sourceTree = ""; }; + 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = ../node_modules/react-native/Libraries/WebSocket/RCTWebSocket.xcodeproj; sourceTree = ""; }; + 13B07F961A680F5B00A75B9A /* rpc.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = rpc.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = rpc/AppDelegate.h; sourceTree = ""; }; + 13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = rpc/AppDelegate.m; sourceTree = ""; }; + 13B07FB21A68108700A75B9A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; + 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = rpc/Images.xcassets; sourceTree = ""; }; + 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = rpc/Info.plist; sourceTree = ""; }; + 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = rpc/main.m; sourceTree = ""; }; + 146833FF1AC3E56700842450 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = ../node_modules/react-native/React/React.xcodeproj; sourceTree = ""; }; + 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = ../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj; sourceTree = ""; }; + 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../node_modules/react-native/Libraries/Text/RCTText.xcodeproj; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 00E356EB1AD99517003FC87E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 140ED2AC1D01E1AD002B40FF /* libReact.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 146834051AC3E58100842450 /* libReact.a in Frameworks */, + 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */, + 00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */, + 00C302E81ABCBA2D00DB3ED1 /* libRCTImage.a in Frameworks */, + 133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */, + 00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */, + 139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */, + 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */, + 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */, + 139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 00C302A81ABCB8CE00DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */, + ); + name = Products; + sourceTree = ""; + }; + 00C302B61ABCB90400DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */, + ); + name = Products; + sourceTree = ""; + }; + 00C302BC1ABCB91800DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */, + ); + name = Products; + sourceTree = ""; + }; + 00C302D41ABCB9D200DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */, + ); + name = Products; + sourceTree = ""; + }; + 00C302E01ABCB9EE00DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */, + ); + name = Products; + sourceTree = ""; + }; + 00E356EF1AD99517003FC87E /* rpcTests */ = { + isa = PBXGroup; + children = ( + 00E356F21AD99517003FC87E /* rpcTests.m */, + 00E356F01AD99517003FC87E /* Supporting Files */, + ); + path = rpcTests; + sourceTree = ""; + }; + 00E356F01AD99517003FC87E /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 00E356F11AD99517003FC87E /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 139105B71AF99BAD00B5F7CC /* Products */ = { + isa = PBXGroup; + children = ( + 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */, + ); + name = Products; + sourceTree = ""; + }; + 139FDEE71B06529A00C62182 /* Products */ = { + isa = PBXGroup; + children = ( + 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */, + ); + name = Products; + sourceTree = ""; + }; + 13B07FAE1A68108700A75B9A /* rpc */ = { + isa = PBXGroup; + children = ( + 008F07F21AC5B25A0029DE68 /* main.jsbundle */, + 13B07FAF1A68108700A75B9A /* AppDelegate.h */, + 13B07FB01A68108700A75B9A /* AppDelegate.m */, + 13B07FB51A68108700A75B9A /* Images.xcassets */, + 13B07FB61A68108700A75B9A /* Info.plist */, + 13B07FB11A68108700A75B9A /* LaunchScreen.xib */, + 13B07FB71A68108700A75B9A /* main.m */, + ); + name = rpc; + sourceTree = ""; + }; + 146834001AC3E56700842450 /* Products */ = { + isa = PBXGroup; + children = ( + 146834041AC3E56700842450 /* libReact.a */, + ); + name = Products; + sourceTree = ""; + }; + 78C398B11ACF4ADC00677621 /* Products */ = { + isa = PBXGroup; + children = ( + 78C398B91ACF4ADC00677621 /* libRCTLinking.a */, + ); + name = Products; + sourceTree = ""; + }; + 832341AE1AAA6A7D00B99B32 /* Libraries */ = { + isa = PBXGroup; + children = ( + 146833FF1AC3E56700842450 /* React.xcodeproj */, + 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */, + 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */, + 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */, + 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */, + 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */, + 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */, + 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */, + 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */, + 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */, + ); + name = Libraries; + sourceTree = ""; + }; + 832341B11AAA6A8300B99B32 /* Products */ = { + isa = PBXGroup; + children = ( + 832341B51AAA6A8300B99B32 /* libRCTText.a */, + ); + name = Products; + sourceTree = ""; + }; + 83CBB9F61A601CBA00E9B192 = { + isa = PBXGroup; + children = ( + 13B07FAE1A68108700A75B9A /* rpc */, + 832341AE1AAA6A7D00B99B32 /* Libraries */, + 00E356EF1AD99517003FC87E /* rpcTests */, + 83CBBA001A601CBA00E9B192 /* Products */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + }; + 83CBBA001A601CBA00E9B192 /* Products */ = { + isa = PBXGroup; + children = ( + 13B07F961A680F5B00A75B9A /* rpc.app */, + 00E356EE1AD99517003FC87E /* rpcTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 00E356ED1AD99517003FC87E /* rpcTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "rpcTests" */; + buildPhases = ( + 00E356EA1AD99517003FC87E /* Sources */, + 00E356EB1AD99517003FC87E /* Frameworks */, + 00E356EC1AD99517003FC87E /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 00E356F51AD99517003FC87E /* PBXTargetDependency */, + ); + name = rpcTests; + productName = rpcTests; + productReference = 00E356EE1AD99517003FC87E /* rpcTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 13B07F861A680F5B00A75B9A /* rpc */ = { + isa = PBXNativeTarget; + buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "rpc" */; + buildPhases = ( + 13B07F871A680F5B00A75B9A /* Sources */, + 13B07F8C1A680F5B00A75B9A /* Frameworks */, + 13B07F8E1A680F5B00A75B9A /* Resources */, + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = rpc; + productName = "Hello World"; + productReference = 13B07F961A680F5B00A75B9A /* rpc.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 83CBB9F71A601CBA00E9B192 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0610; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 00E356ED1AD99517003FC87E = { + CreatedOnToolsVersion = 6.2; + TestTargetID = 13B07F861A680F5B00A75B9A; + }; + }; + }; + buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "rpc" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 83CBB9F61A601CBA00E9B192; + productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 00C302A81ABCB8CE00DB3ED1 /* Products */; + ProjectRef = 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */; + }, + { + ProductGroup = 00C302B61ABCB90400DB3ED1 /* Products */; + ProjectRef = 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */; + }, + { + ProductGroup = 00C302BC1ABCB91800DB3ED1 /* Products */; + ProjectRef = 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */; + }, + { + ProductGroup = 78C398B11ACF4ADC00677621 /* Products */; + ProjectRef = 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */; + }, + { + ProductGroup = 00C302D41ABCB9D200DB3ED1 /* Products */; + ProjectRef = 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */; + }, + { + ProductGroup = 139105B71AF99BAD00B5F7CC /* Products */; + ProjectRef = 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */; + }, + { + ProductGroup = 832341B11AAA6A8300B99B32 /* Products */; + ProjectRef = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */; + }, + { + ProductGroup = 00C302E01ABCB9EE00DB3ED1 /* Products */; + ProjectRef = 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */; + }, + { + ProductGroup = 139FDEE71B06529A00C62182 /* Products */; + ProjectRef = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */; + }, + { + ProductGroup = 146834001AC3E56700842450 /* Products */; + ProjectRef = 146833FF1AC3E56700842450 /* React.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + 13B07F861A680F5B00A75B9A /* rpc */, + 00E356ED1AD99517003FC87E /* rpcTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTActionSheet.a; + remoteRef = 00C302AB1ABCB8CE00DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTGeolocation.a; + remoteRef = 00C302B91ABCB90400DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTImage.a; + remoteRef = 00C302BF1ABCB91800DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTNetwork.a; + remoteRef = 00C302DB1ABCB9D200DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTVibration.a; + remoteRef = 00C302E31ABCB9EE00DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTSettings.a; + remoteRef = 139105C01AF99BAD00B5F7CC /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTWebSocket.a; + remoteRef = 139FDEF31B06529B00C62182 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 146834041AC3E56700842450 /* libReact.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libReact.a; + remoteRef = 146834031AC3E56700842450 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 78C398B91ACF4ADC00677621 /* libRCTLinking.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTLinking.a; + remoteRef = 78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 832341B51AAA6A8300B99B32 /* libRCTText.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTText.a; + remoteRef = 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + 00E356EC1AD99517003FC87E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13B07F8E1A680F5B00A75B9A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, + 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Bundle React Native code and images"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "export NODE_BINARY=node\n../node_modules/react-native/packager/react-native-xcode.sh"; + showEnvVarsInLog = 1; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 00E356EA1AD99517003FC87E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 00E356F31AD99517003FC87E /* rpcTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13B07F871A680F5B00A75B9A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */, + 13B07FC11A68108700A75B9A /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 00E356F51AD99517003FC87E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 13B07F861A680F5B00A75B9A /* rpc */; + targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 13B07FB11A68108700A75B9A /* LaunchScreen.xib */ = { + isa = PBXVariantGroup; + children = ( + 13B07FB21A68108700A75B9A /* Base */, + ); + name = LaunchScreen.xib; + path = rpc; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 00E356F61AD99517003FC87E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = rpcTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/rpc.app/rpc"; + }; + name = Debug; + }; + 00E356F71AD99517003FC87E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + COPY_PHASE_STRIP = NO; + INFOPLIST_FILE = rpcTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/rpc.app/rpc"; + }; + name = Release; + }; + 13B07F941A680F5B00A75B9A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = NO; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../node_modules/react-native/React/**", + ); + INFOPLIST_FILE = "rpc/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-lc++", + ); + PRODUCT_NAME = rpc; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 13B07F951A680F5B00A75B9A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CURRENT_PROJECT_VERSION = 1; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../node_modules/react-native/React/**", + ); + INFOPLIST_FILE = "rpc/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-lc++", + ); + PRODUCT_NAME = rpc; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; + 83CBBA201A601CBA00E9B192 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../node_modules/react-native/React/**", + ); + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 83CBBA211A601CBA00E9B192 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../node_modules/react-native/React/**", + ); + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "rpcTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 00E356F61AD99517003FC87E /* Debug */, + 00E356F71AD99517003FC87E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "rpc" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13B07F941A680F5B00A75B9A /* Debug */, + 13B07F951A680F5B00A75B9A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "rpc" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 83CBBA201A601CBA00E9B192 /* Debug */, + 83CBBA211A601CBA00E9B192 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; +} diff --git a/examples/rpc/ios/rpc.xcodeproj/xcshareddata/xcschemes/rpc.xcscheme b/examples/rpc/ios/rpc.xcodeproj/xcshareddata/xcschemes/rpc.xcscheme new file mode 100644 index 00000000..79d0d8a9 --- /dev/null +++ b/examples/rpc/ios/rpc.xcodeproj/xcshareddata/xcschemes/rpc.xcscheme @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/rpc/ios/rpc/AppDelegate.h b/examples/rpc/ios/rpc/AppDelegate.h new file mode 100644 index 00000000..a9654d5e --- /dev/null +++ b/examples/rpc/ios/rpc/AppDelegate.h @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +@interface AppDelegate : UIResponder + +@property (nonatomic, strong) UIWindow *window; + +@end diff --git a/examples/rpc/ios/rpc/AppDelegate.m b/examples/rpc/ios/rpc/AppDelegate.m new file mode 100644 index 00000000..a7128926 --- /dev/null +++ b/examples/rpc/ios/rpc/AppDelegate.m @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "AppDelegate.h" + +#import "RCTBundleURLProvider.h" +#import "RCTRootView.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ + NSURL *jsCodeLocation; + + jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil]; + + RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation + moduleName:@"rpc" + initialProperties:nil + launchOptions:launchOptions]; + rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1]; + + self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + UIViewController *rootViewController = [UIViewController new]; + rootViewController.view = rootView; + self.window.rootViewController = rootViewController; + [self.window makeKeyAndVisible]; + return YES; +} + +@end diff --git a/examples/rpc/ios/rpc/Base.lproj/LaunchScreen.xib b/examples/rpc/ios/rpc/Base.lproj/LaunchScreen.xib new file mode 100644 index 00000000..d4cfc96e --- /dev/null +++ b/examples/rpc/ios/rpc/Base.lproj/LaunchScreen.xib @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/rpc/ios/rpc/Images.xcassets/AppIcon.appiconset/Contents.json b/examples/rpc/ios/rpc/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..118c98f7 --- /dev/null +++ b/examples/rpc/ios/rpc/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,38 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/rpc/ios/rpc/Info.plist b/examples/rpc/ios/rpc/Info.plist new file mode 100644 index 00000000..e98ebb00 --- /dev/null +++ b/examples/rpc/ios/rpc/Info.plist @@ -0,0 +1,54 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + NSLocationWhenInUseUsageDescription + + NSAppTransportSecurity + + + NSExceptionDomains + + localhost + + NSTemporaryExceptionAllowsInsecureHTTPLoads + + + + + + diff --git a/examples/rpc/ios/rpc/main.m b/examples/rpc/ios/rpc/main.m new file mode 100644 index 00000000..3d767fcb --- /dev/null +++ b/examples/rpc/ios/rpc/main.m @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/examples/rpc/ios/rpcTests/Info.plist b/examples/rpc/ios/rpcTests/Info.plist new file mode 100644 index 00000000..886825cc --- /dev/null +++ b/examples/rpc/ios/rpcTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/examples/rpc/ios/rpcTests/rpcTests.m b/examples/rpc/ios/rpcTests/rpcTests.m new file mode 100644 index 00000000..d7795172 --- /dev/null +++ b/examples/rpc/ios/rpcTests/rpcTests.m @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import +#import + +#import "RCTLog.h" +#import "RCTRootView.h" + +#define TIMEOUT_SECONDS 600 +#define TEXT_TO_LOOK_FOR @"Welcome to React Native!" + +@interface rpcTests : XCTestCase + +@end + +@implementation rpcTests + +- (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test +{ + if (test(view)) { + return YES; + } + for (UIView *subview in [view subviews]) { + if ([self findSubviewInView:subview matching:test]) { + return YES; + } + } + return NO; +} + +- (void)testRendersWelcomeScreen +{ + UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; + NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; + BOOL foundElement = NO; + + __block NSString *redboxError = nil; + RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { + if (level >= RCTLogLevelError) { + redboxError = message; + } + }); + + while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { + [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; + + foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { + if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { + return YES; + } + return NO; + }]; + } + + RCTSetLogFunction(RCTDefaultLogFunction); + + XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); + XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); +} + + +@end diff --git a/examples/rpc/package.json b/examples/rpc/package.json new file mode 100644 index 00000000..07d08587 --- /dev/null +++ b/examples/rpc/package.json @@ -0,0 +1,12 @@ +{ + "name": "rpc", + "version": "0.0.1", + "private": true, + "scripts": { + "start": "node node_modules/react-native/local-cli/cli.js start" + }, + "dependencies": { + "react": "15.3.2", + "react-native": "0.34.0" + } +} From 0a5c808a07b022fe709c9d2309adf94374bbee16 Mon Sep 17 00:00:00 2001 From: Ali Najafizadeh Date: Sun, 25 Sep 2016 16:29:07 -0400 Subject: [PATCH 06/77] rewrote with latest webview --- ios/RCTWebViewBridge.h | 6 +- ios/RCTWebViewBridge.m | 240 +++++------------ ios/RCTWebViewBridgeManager.h | 3 - ios/RCTWebViewBridgeManager.m | 40 ++- scripts/bootstrap.js | 88 ++++++ scripts/webviewbridge.js | 72 ----- webview-bridge/index.ios.js | 491 +++++++++++++++++++++++++--------- 7 files changed, 531 insertions(+), 409 deletions(-) create mode 100644 scripts/bootstrap.js delete mode 100644 scripts/webviewbridge.js diff --git a/ios/RCTWebViewBridge.h b/ios/RCTWebViewBridge.h index 09006303..caba7be9 100644 --- a/ios/RCTWebViewBridge.h +++ b/ios/RCTWebViewBridge.h @@ -2,9 +2,6 @@ * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * - * Copyright (c) 2015-present, Ali Najafizadeh (github.com/alinz) - * All rights reserved - * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. @@ -37,12 +34,13 @@ shouldStartLoadForRequest:(NSMutableDictionary *)request @property (nonatomic, copy) NSDictionary *source; @property (nonatomic, assign) UIEdgeInsets contentInset; @property (nonatomic, assign) BOOL automaticallyAdjustContentInsets; -@property (nonatomic, assign) BOOL hideKeyboardAccessoryView; @property (nonatomic, copy) NSString *injectedJavaScript; +@property (nonatomic, assign) BOOL scalesPageToFit; - (void)goForward; - (void)goBack; - (void)reload; +- (void)stopLoading; - (void)sendToBridge:(NSString *)message; @end diff --git a/ios/RCTWebViewBridge.m b/ios/RCTWebViewBridge.m index ce7868c4..4b26b073 100644 --- a/ios/RCTWebViewBridge.m +++ b/ios/RCTWebViewBridge.m @@ -2,9 +2,6 @@ * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * - * Copyright (c) 2015-present, Ali Najafizadeh (github.com/alinz) - * All rights reserved - * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. @@ -21,25 +18,8 @@ #import "RCTUtils.h" #import "RCTView.h" #import "UIView+React.h" -#import - -//This is a very elegent way of defining multiline string in objective-c. -//source: http://stackoverflow.com/a/23387659/828487 -#define NSStringMultiline(...) [[NSString alloc] initWithCString:#__VA_ARGS__ encoding:NSUTF8StringEncoding] - -//we don'e need this one since it has been defined in RCTWebView.m -//NSString *const RCTJSNavigationScheme = @"react-js-navigation"; -NSString *const RCTWebViewBridgeSchema = @"wvb"; -// runtime trick to remove UIWebview keyboard default toolbar -// see: http://stackoverflow.com/questions/19033292/ios-7-uiwebview-keyboard-issue/19042279#19042279 -@interface _SwizzleHelper : NSObject @end -@implementation _SwizzleHelper --(id)inputAccessoryView -{ - return nil; -} -@end +NSString *const RCTJSNavigationScheme = @"react-js-navigation"; @interface RCTWebViewBridge () @@ -47,7 +27,6 @@ @interface RCTWebViewBridge () @property (nonatomic, copy) RCTDirectEventBlock onLoadingFinish; @property (nonatomic, copy) RCTDirectEventBlock onLoadingError; @property (nonatomic, copy) RCTDirectEventBlock onShouldStartLoadWithRequest; -@property (nonatomic, copy) RCTDirectEventBlock onBridgeMessage; @end @@ -57,6 +36,11 @@ @implementation RCTWebViewBridge NSString *_injectedJavaScript; } +- (void)dealloc +{ + _webView.delegate = nil; +} + - (instancetype)initWithFrame:(CGRect)frame { if ((self = [super initWithFrame:frame])) { @@ -84,28 +68,18 @@ - (void)goBack - (void)reload { - [_webView reload]; -} - -- (void)sendToBridge:(NSString *)message -{ - //we are warpping the send message in a function to make sure that if - //WebView is not injected, we don't crash the app. - NSString *format = NSStringMultiline( - (function(){ - if (WebViewBridge && WebViewBridge.__push__) { - WebViewBridge.__push__('%@'); - } - }()); - ); - - NSString *command = [NSString stringWithFormat: format, message]; - [_webView stringByEvaluatingJavaScriptFromString:command]; + NSURLRequest *request = [RCTConvert NSURLRequest:self.source]; + if (request.URL && !_webView.request.URL.absoluteString.length) { + [_webView loadRequest:request]; + } + else { + [_webView reload]; + } } -- (NSURL *)URL +- (void)stopLoading { - return _webView.request.URL; + [_webView stopLoading]; } - (void)setSource:(NSDictionary *)source @@ -117,6 +91,9 @@ - (void)setSource:(NSDictionary *)source NSString *html = [RCTConvert NSString:source[@"html"]]; if (html) { NSURL *baseURL = [RCTConvert NSURL:source[@"baseUrl"]]; + if (!baseURL) { + baseURL = [NSURL URLWithString:@"about:blank"]; + } [_webView loadHTMLString:html baseURL:baseURL]; return; } @@ -152,6 +129,19 @@ - (void)setContentInset:(UIEdgeInsets)contentInset updateOffset:NO]; } +- (void)setScalesPageToFit:(BOOL)scalesPageToFit +{ + if (_webView.scalesPageToFit != scalesPageToFit) { + _webView.scalesPageToFit = scalesPageToFit; + [_webView reload]; + } +} + +- (BOOL)scalesPageToFit +{ + return _webView.scalesPageToFit; +} + - (void)setBackgroundColor:(UIColor *)backgroundColor { CGFloat alpha = CGColorGetAlpha(backgroundColor.CGColor); @@ -184,37 +174,6 @@ - (void)refreshContentInset updateOffset:YES]; } --(void)setHideKeyboardAccessoryView:(BOOL)hideKeyboardAccessoryView -{ - if (!hideKeyboardAccessoryView) { - return; - } - - UIView* subview; - for (UIView* view in _webView.scrollView.subviews) { - if([[view.class description] hasPrefix:@"UIWeb"]) - subview = view; - } - - if(subview == nil) return; - - NSString* name = [NSString stringWithFormat:@"%@_SwizzleHelper", subview.class.superclass]; - Class newClass = NSClassFromString(name); - - if(newClass == nil) - { - newClass = objc_allocateClassPair(subview.class, [name cStringUsingEncoding:NSASCIIStringEncoding], 0); - if(!newClass) return; - - Method method = class_getInstanceMethod([_SwizzleHelper class], @selector(inputAccessoryView)); - class_addMethod(newClass, @selector(inputAccessoryView), method_getImplementation(method), method_getTypeEncoding(method)); - - objc_registerClassPair(newClass); - } - - object_setClass(subview, newClass); -} - #pragma mark - UIWebViewDelegate methods - (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request @@ -222,24 +181,25 @@ - (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLR { BOOL isJSNavigation = [request.URL.scheme isEqualToString:RCTJSNavigationScheme]; - if (!isJSNavigation && [request.URL.scheme isEqualToString:RCTWebViewBridgeSchema]) { - NSString* message = [webView stringByEvaluatingJavaScriptFromString:@"WebViewBridge.__fetch__()"]; - - NSMutableDictionary *onBridgeMessageEvent = [[NSMutableDictionary alloc] initWithDictionary:@{ - @"messages": [self stringArrayJsonToArray: message] - }]; - - _onBridgeMessage(onBridgeMessageEvent); - - isJSNavigation = YES; - } + static NSDictionary *navigationTypes; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + navigationTypes = @{ + @(UIWebViewNavigationTypeLinkClicked): @"click", + @(UIWebViewNavigationTypeFormSubmitted): @"formsubmit", + @(UIWebViewNavigationTypeBackForward): @"backforward", + @(UIWebViewNavigationTypeReload): @"reload", + @(UIWebViewNavigationTypeFormResubmitted): @"formresubmit", + @(UIWebViewNavigationTypeOther): @"other", + }; + }); // skip this for the JS Navigation handler if (!isJSNavigation && _onShouldStartLoadWithRequest) { NSMutableDictionary *event = [self baseEvent]; [event addEntriesFromDictionary: @{ @"url": (request.URL).absoluteString, - @"navigationType": @(navigationType) + @"navigationType": navigationTypes[@(navigationType)] }]; if (![self.delegate webView:self shouldStartLoadForRequest:event @@ -255,7 +215,7 @@ - (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLR NSMutableDictionary *event = [self baseEvent]; [event addEntriesFromDictionary: @{ @"url": (request.URL).absoluteString, - @"navigationType": @(navigationType) + @"navigationType": navigationTypes[@(navigationType)] }]; _onLoadingStart(event); } @@ -288,10 +248,8 @@ - (void)webView:(__unused UIWebView *)webView didFailLoadWithError:(NSError *)er - (void)webViewDidFinishLoad:(UIWebView *)webView { - //injecting WebViewBridge Script - NSString *webViewBridgeScriptContent = [self webViewBridgeScript]; - [webView stringByEvaluatingJavaScriptFromString:webViewBridgeScriptContent]; - ////////////////////////////////////////////////////////////////////////////// + //injecting WebViewBridge bootstrap + [webView stringByEvaluatingJavaScriptFromString:[self webViewBridgeScript]]; if (_injectedJavaScript != nil) { NSString *jsEvaluationValue = [webView stringByEvaluatingJavaScriptFromString:_injectedJavaScript]; @@ -307,97 +265,27 @@ - (void)webViewDidFinishLoad:(UIWebView *)webView } } -- (NSArray*)stringArrayJsonToArray:(NSString *)message +- (void)sendToBridge:(NSString *)message { - return [NSJSONSerialization JSONObjectWithData:[message dataUsingEncoding:NSUTF8StringEncoding] - options:NSJSONReadingAllowFragments - error:nil]; -} - -//since there is no easy way to load the static lib resource in ios, -//we are loading the script from this method. -- (NSString *)webViewBridgeScript { - // NSBundle *bundle = [NSBundle mainBundle]; - // NSString *webViewBridgeScriptFile = [bundle pathForResource:@"webviewbridge" - // ofType:@"js"]; - // NSString *webViewBridgeScriptContent = [NSString stringWithContentsOfFile:webViewBridgeScriptFile - // encoding:NSUTF8StringEncoding - // error:nil]; - - return NSStringMultiline( - (function (window) { - 'use strict'; - - //Make sure that if WebViewBridge already in scope we don't override it. - if (window.WebViewBridge) { - return; - } - - var RNWBSchema = 'wvb'; - var sendQueue = []; - var receiveQueue = []; - var doc = window.document; - var customEvent = doc.createEvent('Event'); - - function callFunc(func, message) { - if ('function' === typeof func) { - func(message); - } + //we are warpping the send message in a function to make sure that if + //WebView is not injected, we don't crash the app. + NSString *format = NSStringMultiline( + (function(){ + if (WebViewBridge && WebViewBridge.__push__) { + WebViewBridge.__push__('%@'); } + }()); + ); - function signalNative() { - window.location = RNWBSchema + '://message' + new Date().getTime(); - } + NSString *command = [NSString stringWithFormat: format, message]; + [_webView stringByEvaluatingJavaScriptFromString:command]; +} - //I made the private function ugly signiture so user doesn't called them accidently. - //if you do, then I have nothing to say. :( - var WebViewBridge = { - //this function will be called by native side to push a new message - //to webview. - __push__: function (message) { - receiveQueue.push(message); - //reason I need this setTmeout is to return this function as fast as - //possible to release the native side thread. - setTimeout(function () { - var message = receiveQueue.pop(); - callFunc(WebViewBridge.onMessage, message); - }, 15); //this magic number is just a random small value. I don't like 0. - }, - __fetch__: function () { - //since our sendQueue array only contains string, and our connection to native - //can only accept string, we need to convert array of strings into single string. - var messages = JSON.stringify(sendQueue); - - //we make sure that sendQueue is resets - sendQueue = []; - - //return the messages back to native side. - return messages; - }, - //make sure message is string. because only string can be sent to native, - //if you don't pass it as string, onError function will be called. - send: function (message) { - if ('string' !== typeof message) { - callFunc(WebViewBridge.onError, "message is type '" + typeof message + "', and it needs to be string"); - return; - } - - //we queue the messages to make sure that native can collects all of them in one shot. - sendQueue.push(message); - //signal the objective-c that there is a message in the queue - signalNative(); - }, - onMessage: null, - onError: null - }; - - window.WebViewBridge = WebViewBridge; - - //dispatch event - customEvent.initEvent('WebViewBridge', true, true); - doc.dispatchEvent(customEvent); - }(window)); - ); +- (NSString *)webViewBridgeBootrstrap +{ + NSString* path = [[NSBundle mainBundle] pathForResource:@"bootstrap" ofType:@"js"]; + NSString* content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL]; + return content; } @end diff --git a/ios/RCTWebViewBridgeManager.h b/ios/RCTWebViewBridgeManager.h index e0659a8d..8797862b 100644 --- a/ios/RCTWebViewBridgeManager.h +++ b/ios/RCTWebViewBridgeManager.h @@ -2,9 +2,6 @@ * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * - * Copyright (c) 2015-present, Ali Najafizadeh (github.com/alinz) - * All rights reserved - * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. diff --git a/ios/RCTWebViewBridgeManager.m b/ios/RCTWebViewBridgeManager.m index ad15e14b..e12ed682 100644 --- a/ios/RCTWebViewBridgeManager.m +++ b/ios/RCTWebViewBridgeManager.m @@ -2,9 +2,6 @@ * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * - * Copyright (c) 2015-present, Ali Najafizadeh (github.com/alinz) - * All rights reserved - * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. @@ -39,33 +36,18 @@ - (UIView *)view RCT_EXPORT_VIEW_PROPERTY(source, NSDictionary) RCT_REMAP_VIEW_PROPERTY(bounces, _webView.scrollView.bounces, BOOL) RCT_REMAP_VIEW_PROPERTY(scrollEnabled, _webView.scrollView.scrollEnabled, BOOL) -RCT_REMAP_VIEW_PROPERTY(scalesPageToFit, _webView.scalesPageToFit, BOOL) +RCT_REMAP_VIEW_PROPERTY(decelerationRate, _webView.scrollView.decelerationRate, CGFloat) +RCT_EXPORT_VIEW_PROPERTY(scalesPageToFit, BOOL) RCT_EXPORT_VIEW_PROPERTY(injectedJavaScript, NSString) RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets) RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL) -RCT_EXPORT_VIEW_PROPERTY(hideKeyboardAccessoryView, BOOL) RCT_EXPORT_VIEW_PROPERTY(onLoadingStart, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onLoadingFinish, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onLoadingError, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onShouldStartLoadWithRequest, RCTDirectEventBlock) RCT_REMAP_VIEW_PROPERTY(allowsInlineMediaPlayback, _webView.allowsInlineMediaPlayback, BOOL) -RCT_EXPORT_VIEW_PROPERTY(onBridgeMessage, RCTDirectEventBlock) -RCT_REMAP_VIEW_PROPERTY(keyboardDisplayRequiresUserAction, _webView.keyboardDisplayRequiresUserAction, BOOL) - -- (NSDictionary *)constantsToExport -{ - return @{ - @"JSNavigationScheme": RCTJSNavigationScheme, - @"NavigationType": @{ - @"LinkClicked": @(UIWebViewNavigationTypeLinkClicked), - @"FormSubmitted": @(UIWebViewNavigationTypeFormSubmitted), - @"BackForward": @(UIWebViewNavigationTypeBackForward), - @"Reload": @(UIWebViewNavigationTypeReload), - @"FormResubmitted": @(UIWebViewNavigationTypeFormResubmitted), - @"Other": @(UIWebViewNavigationTypeOther) - }, - }; -} +RCT_REMAP_VIEW_PROPERTY(mediaPlaybackRequiresUserAction, _webView.mediaPlaybackRequiresUserAction, BOOL) +RCT_REMAP_VIEW_PROPERTY(dataDetectorTypes, _webView.dataDetectorTypes, UIDataDetectorTypes) RCT_EXPORT_METHOD(goBack:(nonnull NSNumber *)reactTag) { @@ -103,8 +85,20 @@ - (UIView *)view }]; } +RCT_EXPORT_METHOD(stopLoading:(nonnull NSNumber *)reactTag) +{ + [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { + RCTWebViewBridge *view = viewRegistry[reactTag]; + if (![view isKindOfClass:[RCTWebViewBridge class]]) { + RCTLogError(@"Invalid view returned from registry, expecting RCTWebViewBridge, got: %@", view); + } else { + [view stopLoading]; + } + }]; +} + RCT_EXPORT_METHOD(sendToBridge:(nonnull NSNumber *)reactTag - value:(NSString*)message) + value:(NSString*)message) { [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { RCTWebViewBridge *view = viewRegistry[reactTag]; diff --git a/scripts/bootstrap.js b/scripts/bootstrap.js new file mode 100644 index 00000000..3429057d --- /dev/null +++ b/scripts/bootstrap.js @@ -0,0 +1,88 @@ +(function () { + 'use strict' + + //base64 encode and decode polyfil + //modified version of https://github.com/davidchambers/Base64.js + //you can replace the b64Encode and b64Decode for atob and btoa. + var b64ch = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' + function b64Encode(input) { + var output = '' + for ( + var block, charCode, idx = 0, map = b64ch; + input.charAt(idx | 0) || (map = '=', idx % 1); + output += map.charAt(63 & block >> 8 - idx % 1 * 8) + ) { + charCode = input.charCodeAt(idx += 3/4) + block = block << 8 | charCode + } + return output + } + + function b64Decode(input) { + var str = input.replace(/=+$/, '') + var output = '' + for ( + var bc = 0, bs, buffer, idx = 0; + buffer = str.charAt(idx++); + ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, + bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0 + ) { + buffer = b64ch.indexOf(buffer) + } + return output + } + + function encode(input) { + if (typeof input !== 'string') { + try { + input = JSON.stringify(input) + } catch(e) {} + } + input = unescape(encodeURIComponent(input)) + return b64Encode(input) + } + + function decode(input) { + return decodeURIComponent(escape(b64Decode(input))) + } + + // event section + var doc = window.document + + function signalNative() { + window.location = 'rnwb://message' + new Date().getTime() + } + + function dispatch(name, value) { + var event = new CustomEvent(name, { + detail: value, + bubbles: true + }); + + setTimeout(function () { + doc.dispatchEvent(event) + }, 15) + } + + var queue = [] + + window.WebViewBridge = { + __push__: function (encoded) { + decoded = decode(encoded) + //dispatch('webviewbridge:message', decoded); + window.WebViewBridge.message(decoded) + }, + __fetch__: function () { + var val = JSON.stringify(queue) + queue = [] + return val + }, + send: function (input) { + queue.push(encode(input)) + setTimeout(signalNative, 15) + }, + onMessage: function(msg) {} + } + + dispatch('webviewbridge:init', window.WebViewBridge) +}()) diff --git a/scripts/webviewbridge.js b/scripts/webviewbridge.js deleted file mode 100644 index 32b43e0f..00000000 --- a/scripts/webviewbridge.js +++ /dev/null @@ -1,72 +0,0 @@ -(function (window) { - 'use strict'; - - //Make sure that if WebViewBridge already in scope we don't override it. - if (window.WebViewBridge) { - return; - } - - var RNWBSchema = 'wvb'; - var sendQueue = []; - var receiveQueue = []; - var doc = window.document; - var customEvent = doc.createEvent('Event'); - - function callFunc(func, message) { - if ('function' === typeof func) { - func(message); - } - } - - function signalNative() { - window.location = RNWBSchema + '://message' + new Date().getTime(); - } - - //I made the private function ugly signiture so user doesn't called them accidently. - //if you do, then I have nothing to say. :( - var WebViewBridge = { - //this function will be called by native side to push a new message - //to webview. - __push__: function (message) { - receiveQueue.push(message); - //reason I need this setTmeout is to return this function as fast as - //possible to release the native side thread. - setTimeout(function () { - var message = receiveQueue.pop(); - callFunc(WebViewBridge.onMessage, message); - }, 15); //this magic number is just a random small value. I don't like 0. - }, - __fetch__: function () { - //since our sendQueue array only contains string, and our connection to native - //can only accept string, we need to convert array of strings into single string. - var messages = JSON.stringify(sendQueue); - - //we make sure that sendQueue is resets - sendQueue = []; - - //return the messages back to native side. - return messages; - }, - //make sure message is string. because only string can be sent to native, - //if you don't pass it as string, onError function will be called. - send: function (message) { - if ('string' !== typeof message) { - callFunc(WebViewBridge.onError, "message is type '" + typeof message + "', and it needs to be string"); - return; - } - - //we queue the messages to make sure that native can collects all of them in one shot. - sendQueue.push(message); - //signal the objective-c that there is a message in the queue - signalNative(); - }, - onMessage: null, - onError: null - }; - - window.WebViewBridge = WebViewBridge; - - //dispatch event - customEvent.initEvent('WebViewBridge', true, true); - doc.dispatchEvent(customEvent); -}(window)); diff --git a/webview-bridge/index.ios.js b/webview-bridge/index.ios.js index b47dc338..fa3159b1 100644 --- a/webview-bridge/index.ios.js +++ b/webview-bridge/index.ios.js @@ -2,72 +2,75 @@ * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * - * Copyright (c) 2016-present, Ali Najafizadeh - * All rights reserved. - * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * - * @providesModule WebViewBridge - * @flow + * @providesModule WebView + * @noflow */ 'use strict'; -var React = require('react'); -var ReactNative = require('react-native'); -var invariant = require('invariant'); -var keyMirror = require('keymirror'); -var resolveAssetSource = require('react-native/Libraries/Image/resolveAssetSource'); - -var { - ActivityIndicator, - EdgeInsetsPropType, - StyleSheet, - Text, - View, - WebView, - requireNativeComponent, - UIManager, - NativeModules: { - WebViewBridgeManager - } -} = ReactNative; -var { PropTypes } = React; +var ActivityIndicator = require('ActivityIndicator'); +var EdgeInsetsPropType = require('EdgeInsetsPropType'); +var React = require('React'); +var ReactNative = require('react/lib/ReactNative'); +var StyleSheet = require('StyleSheet'); +var Text = require('Text'); +var UIManager = require('UIManager'); +var View = require('View'); +var ScrollView = require('ScrollView'); + +var deprecatedPropType = require('deprecatedPropType'); +var invariant = require('fbjs/lib/invariant'); +var keyMirror = require('fbjs/lib/keyMirror'); +var processDecelerationRate = require('processDecelerationRate'); +var requireNativeComponent = require('requireNativeComponent'); +var resolveAssetSource = require('resolveAssetSource'); + +var PropTypes = React.PropTypes; +var RCTWebViewBridgeManager = require('NativeModules').WebViewManager; var BGWASH = 'rgba(255,255,255,0.8)'; -var RCT_WEBVIEWBRIDGE_REF = 'webviewbridge'; - -var RCTWebViewBridgeManager = WebViewBridgeManager; +var RCT_WEBVIEW_REF = 'webview'; -var WebViewBridgeState = keyMirror({ +var WebViewState = keyMirror({ IDLE: null, LOADING: null, ERROR: null, }); -var NavigationType = { - click: RCTWebViewBridgeManager.NavigationType.LinkClicked, - formsubmit: RCTWebViewBridgeManager.NavigationType.FormSubmitted, - backforward: RCTWebViewBridgeManager.NavigationType.BackForward, - reload: RCTWebViewBridgeManager.NavigationType.Reload, - formresubmit: RCTWebViewBridgeManager.NavigationType.FormResubmitted, - other: RCTWebViewBridgeManager.NavigationType.Other, -}; +const NavigationType = keyMirror({ + click: true, + formsubmit: true, + backforward: true, + reload: true, + formresubmit: true, + other: true, +}); -var JSNavigationScheme = RCTWebViewBridgeManager.JSNavigationScheme; +const JSNavigationScheme = 'react-js-navigation'; type ErrorEvent = { - domain: any; - code: any; - description: any; + domain: any, + code: any, + description: any, } type Event = Object; +const DataDetectorTypes = [ + 'phoneNumber', + 'link', + 'address', + 'calendarEvent', + 'none', + 'all', +]; + var defaultRenderLoading = () => ( - + ); var defaultRenderError = (errorDomain, errorCode, errorDesc) => ( @@ -88,47 +91,259 @@ var defaultRenderError = (errorDomain, errorCode, errorDesc) => ( ); /** - * Renders a native WebView. + * `WebView` renders web content in a native view. + * + *``` + * import React, { Component } from 'react'; + * import { WebView } from 'react-native'; + * + * class MyWeb extends Component { + * render() { + * return ( + * + * ); + * } + * } + *``` + * + * You can use this component to navigate back and forth in the web view's + * history and configure various properties for the web content. */ -var WebViewBridge = React.createClass({ - statics: { - JSNavigationScheme: JSNavigationScheme, - NavigationType: NavigationType, - }, +class WebView extends React.Component { + static JSNavigationScheme = JSNavigationScheme; + static NavigationType = NavigationType; - propTypes: { - ...WebView.propTypes, + static propTypes = { + ...View.propTypes, + + html: deprecatedPropType( + PropTypes.string, + 'Use the `source` prop instead.' + ), + + url: deprecatedPropType( + PropTypes.string, + 'Use the `source` prop instead.' + ), /** - * Will be called once the message is being sent from webview + * Loads static html or a uri (with optional headers) in the WebView. */ - onBridgeMessage: PropTypes.func, + source: PropTypes.oneOfType([ + PropTypes.shape({ + /* + * The URI to load in the `WebView`. Can be a local or remote file. + */ + uri: PropTypes.string, + /* + * The HTTP Method to use. Defaults to GET if not specified. + * NOTE: On Android, only GET and POST are supported. + */ + method: PropTypes.string, + /* + * Additional HTTP headers to send with the request. + * NOTE: On Android, this can only be used with GET requests. + */ + headers: PropTypes.object, + /* + * The HTTP body to send with the request. This must be a valid + * UTF-8 string, and will be sent exactly as specified, with no + * additional encoding (e.g. URL-escaping or base64) applied. + * NOTE: On Android, this can only be used with POST requests. + */ + body: PropTypes.string, + }), + PropTypes.shape({ + /* + * A static HTML page to display in the WebView. + */ + html: PropTypes.string, + /* + * The base URL to be used for any relative links in the HTML. + */ + baseUrl: PropTypes.string, + }), + /* + * Used internally by packager. + */ + PropTypes.number, + ]), - hideKeyboardAccessoryView: PropTypes.bool, + /** + * Function that returns a view to show if there's an error. + */ + renderError: PropTypes.func, // view to show if there's an error + /** + * Function that returns a loading indicator. + */ + renderLoading: PropTypes.func, + /** + * Function that is invoked when the `WebView` has finished loading. + */ + onLoad: PropTypes.func, + /** + * Function that is invoked when the `WebView` load succeeds or fails. + */ + onLoadEnd: PropTypes.func, + /** + * Function that is invoked when the `WebView` starts loading. + */ + onLoadStart: PropTypes.func, + /** + * Function that is invoked when the `WebView` load fails. + */ + onError: PropTypes.func, + /** + * Boolean value that determines whether the web view bounces + * when it reaches the edge of the content. The default value is `true`. + * @platform ios + */ + bounces: PropTypes.bool, + /** + * A floating-point number that determines how quickly the scroll view + * decelerates after the user lifts their finger. You may also use the + * string shortcuts `"normal"` and `"fast"` which match the underlying iOS + * settings for `UIScrollViewDecelerationRateNormal` and + * `UIScrollViewDecelerationRateFast` respectively: + * + * - normal: 0.998 + * - fast: 0.99 (the default for iOS web view) + * @platform ios + */ + decelerationRate: ScrollView.propTypes.decelerationRate, + /** + * Boolean value that determines whether scrolling is enabled in the + * `WebView`. The default value is `true`. + * @platform ios + */ + scrollEnabled: PropTypes.bool, + /** + * Controls whether to adjust the content inset for web views that are + * placed behind a navigation bar, tab bar, or toolbar. The default value + * is `true`. + */ + automaticallyAdjustContentInsets: PropTypes.bool, + /** + * The amount by which the web view content is inset from the edges of + * the scroll view. Defaults to {top: 0, left: 0, bottom: 0, right: 0}. + */ + contentInset: EdgeInsetsPropType, + /** + * Function that is invoked when the `WebView` loading starts or ends. + */ + onNavigationStateChange: PropTypes.func, + /** + * Boolean value that forces the `WebView` to show the loading view + * on the first load. + */ + startInLoadingState: PropTypes.bool, + /** + * The style to apply to the `WebView`. + */ + style: View.propTypes.style, - keyboardDisplayRequiresUserAction: PropTypes.bool, - }, + /** + * Determines the types of data converted to clickable URLs in the web view’s content. + * By default only phone numbers are detected. + * + * You can provide one type or an array of many types. + * + * Possible values for `dataDetectorTypes` are: + * + * - `'phoneNumber'` + * - `'link'` + * - `'address'` + * - `'calendarEvent'` + * - `'none'` + * - `'all'` + * + * @platform ios + */ + dataDetectorTypes: PropTypes.oneOfType([ + PropTypes.oneOf(DataDetectorTypes), + PropTypes.arrayOf(PropTypes.oneOf(DataDetectorTypes)), + ]), - getInitialState: function() { - return { - viewState: WebViewBridgeState.IDLE, - lastErrorEvent: (null: ?ErrorEvent), - startInLoadingState: true, - }; - }, + /** + * Boolean value to enable JavaScript in the `WebView`. Used on Android only + * as JavaScript is enabled by default on iOS. The default value is `true`. + * @platform android + */ + javaScriptEnabled: PropTypes.bool, + + /** + * Boolean value to control whether DOM Storage is enabled. Used only in + * Android. + * @platform android + */ + domStorageEnabled: PropTypes.bool, + + /** + * Set this to provide JavaScript that will be injected into the web page + * when the view loads. + */ + injectedJavaScript: PropTypes.string, + + /** + * Sets the user-agent for the `WebView`. + * @platform android + */ + userAgent: PropTypes.string, + + /** + * Boolean that controls whether the web content is scaled to fit + * the view and enables the user to change the scale. The default value + * is `true`. + */ + scalesPageToFit: PropTypes.bool, + + /** + * Function that allows custom handling of any web view requests. Return + * `true` from the function to continue loading the request and `false` + * to stop loading. + * @platform ios + */ + onShouldStartLoadWithRequest: PropTypes.func, + + /** + * Boolean that determines whether HTML5 videos play inline or use the + * native full-screen controller. The default value is `false`. + * + * **NOTE** : In order for video to play inline, not only does this + * property need to be set to `true`, but the video element in the HTML + * document must also include the `webkit-playsinline` attribute. + * @platform ios + */ + allowsInlineMediaPlayback: PropTypes.bool, - componentWillMount: function() { + /** + * Boolean that determines whether HTML5 audio and video requires the user + * to tap them before they start playing. The default value is `true`. + */ + mediaPlaybackRequiresUserAction: PropTypes.bool, + }; + + state = { + viewState: WebViewState.IDLE, + lastErrorEvent: (null: ?ErrorEvent), + startInLoadingState: true, + }; + + componentWillMount() { if (this.props.startInLoadingState) { - this.setState({viewState: WebViewBridgeState.LOADING}); + this.setState({viewState: WebViewState.LOADING}); } - }, + } - render: function() { + render() { var otherView = null; - if (this.state.viewState === WebViewBridgeState.LOADING) { + if (this.state.viewState === WebViewState.LOADING) { otherView = (this.props.renderLoading || defaultRenderLoading)(); - } else if (this.state.viewState === WebViewBridgeState.ERROR) { + } else if (this.state.viewState === WebViewState.ERROR) { var errorEvent = this.state.lastErrorEvent; invariant( errorEvent != null, @@ -139,15 +354,15 @@ var WebViewBridge = React.createClass({ errorEvent.code, errorEvent.description ); - } else if (this.state.viewState !== WebViewBridgeState.IDLE) { + } else if (this.state.viewState !== WebViewState.IDLE) { console.error( 'RCTWebViewBridge invalid state encountered: ' + this.state.loading ); } var webViewStyles = [styles.container, styles.webView, this.props.style]; - if (this.state.viewState === WebViewBridgeState.LOADING || - this.state.viewState === WebViewBridgeState.ERROR) { + if (this.state.viewState === WebViewState.LOADING || + this.state.viewState === WebViewState.ERROR) { // if we're in either LOADING or ERROR states, don't show the webView webViewStyles.push(styles.hidden); } @@ -158,42 +373,35 @@ var WebViewBridge = React.createClass({ RCTWebViewBridgeManager.startLoadWithResult(!!shouldStart, event.nativeEvent.lockIdentifier); }); - var {javaScriptEnabled, domStorageEnabled} = this.props; - if (this.props.javaScriptEnabledAndroid) { - console.warn('javaScriptEnabledAndroid is deprecated. Use javaScriptEnabled instead'); - javaScriptEnabled = this.props.javaScriptEnabledAndroid; - } - if (this.props.domStorageEnabledAndroid) { - console.warn('domStorageEnabledAndroid is deprecated. Use domStorageEnabled instead'); - domStorageEnabled = this.props.domStorageEnabledAndroid; - } - - var onBridgeMessage = (event: Event) => { - const onBridgeMessageCallback = this.props.onBridgeMessage; - if (onBridgeMessageCallback) { - const messages = event.nativeEvent.messages; - messages.forEach((message) => { - onBridgeMessageCallback(message); - }); - } - }; + var decelerationRate = processDecelerationRate(this.props.decelerationRate); - let {source, ...props} = {...this.props}; - delete props.onBridgeMessage; - delete props.onShouldStartLoadWithRequest; + var source = this.props.source || {}; + if (this.props.html) { + source.html = this.props.html; + } else if (this.props.url) { + source.uri = this.props.url; + } var webView = ; return ( @@ -202,57 +410,77 @@ var WebViewBridge = React.createClass({ {otherView} ); - }, + } - goForward: function() { + /** + * Go forward one page in the web view's history. + */ + goForward = () => { UIManager.dispatchViewManagerCommand( - this.getWebViewBridgeHandle(), + this.getWebViewHandle(), UIManager.RCTWebViewBridge.Commands.goForward, null ); - }, + }; - goBack: function() { + /** + * Go back one page in the web view's history. + */ + goBack = () => { UIManager.dispatchViewManagerCommand( - this.getWebViewBridgeHandle(), + this.getWebViewHandle(), UIManager.RCTWebViewBridge.Commands.goBack, null ); - }, + }; - reload: function() { + /** + * Reloads the current page. + */ + reload = () => { + this.setState({viewState: WebViewState.LOADING}); UIManager.dispatchViewManagerCommand( - this.getWebViewBridgeHandle(), + this.getWebViewHandle(), UIManager.RCTWebViewBridge.Commands.reload, null ); - }, + }; - sendToBridge: function (message: string) { - WebViewBridgeManager.sendToBridge(this.getWebViewBridgeHandle(), message); - }, + /** + * Stop loading the current page. + */ + stopLoading = () => { + UIManager.dispatchViewManagerCommand( + this.getWebViewHandle(), + UIManager.RCTWebViewBridge.Commands.stopLoading, + null + ); + }; /** * We return an event with a bunch of fields including: * url, title, loading, canGoBack, canGoForward */ - updateNavigationState: function(event: Event) { + _updateNavigationState = (event: Event) => { if (this.props.onNavigationStateChange) { this.props.onNavigationStateChange(event.nativeEvent); } - }, + }; - getWebViewBridgeHandle: function(): any { - return ReactNative.findNodeHandle(this.refs[RCT_WEBVIEWBRIDGE_REF]); - }, + /** + * Returns the native `WebView` node. + */ + getWebViewHandle = (): any => { + return ReactNative.findNodeHandle(this.refs[RCT_WEBVIEW_REF]); + }; - onLoadingStart: function(event: Event) { + _onLoadingStart = (event: Event) => { var onLoadStart = this.props.onLoadStart; onLoadStart && onLoadStart(event); - this.updateNavigationState(event); - }, + this._updateNavigationState(event); + }; - onLoadingError: function(event: Event) { + _onLoadingError = (event: Event) => { event.persist(); // persist this event because we need to store it var {onError, onLoadEnd} = this.props; onError && onError(event); @@ -261,22 +489,22 @@ var WebViewBridge = React.createClass({ this.setState({ lastErrorEvent: event.nativeEvent, - viewState: WebViewBridgeState.ERROR + viewState: WebViewState.ERROR }); - }, + }; - onLoadingFinish: function(event: Event) { + _onLoadingFinish = (event: Event) => { var {onLoad, onLoadEnd} = this.props; onLoad && onLoad(event); onLoadEnd && onLoadEnd(event); this.setState({ - viewState: WebViewBridgeState.IDLE, + viewState: WebViewState.IDLE, }); - this.updateNavigationState(event); - }, -}); + this._updateNavigationState(event); + }; +} -var RCTWebViewBridge = requireNativeComponent('RCTWebViewBridge', WebViewBridge, { +var RCTWebViewBridge = requireNativeComponent('RCTWebViewBridge', WebView, { nativeOnly: { onLoadingStart: true, onLoadingError: true, @@ -313,10 +541,11 @@ var styles = StyleSheet.create({ flex: 1, justifyContent: 'center', alignItems: 'center', + height: 100, }, webView: { backgroundColor: '#ffffff', } }); -module.exports = WebViewBridge; +module.exports = WebView; From 06e8b2493c02ee830bea6da0aebdfc6910a483af Mon Sep 17 00:00:00 2001 From: Ali Najafizadeh Date: Mon, 26 Sep 2016 02:10:13 -0400 Subject: [PATCH 07/77] wip --- examples/rpc/index.ios.js | 30 +++------ .../rpc/ios/rpc.xcodeproj/project.pbxproj | 63 ++++++++++++++----- .../xcshareddata/xcschemes/rpc.xcscheme | 11 ++-- examples/rpc/package.json | 3 +- ios/RCTWebViewBridge.m | 6 +- webview-bridge/index.ios.js | 47 +++++++------- 6 files changed, 95 insertions(+), 65 deletions(-) diff --git a/examples/rpc/index.ios.js b/examples/rpc/index.ios.js index 6561e400..ea89fe78 100644 --- a/examples/rpc/index.ios.js +++ b/examples/rpc/index.ios.js @@ -12,21 +12,15 @@ import { View } from 'react-native'; +import WebView from 'react-native-webview-bridge' + class rpc extends Component { render() { return ( - - - Welcome to React Native! - - - To get started, edit index.ios.js - - - Press Cmd+R to reload,{'\n'} - Cmd+D or shake for dev menu - - + ); } } @@ -37,17 +31,7 @@ const styles = StyleSheet.create({ justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', - }, - welcome: { - fontSize: 20, - textAlign: 'center', - margin: 10, - }, - instructions: { - textAlign: 'center', - color: '#333333', - marginBottom: 5, - }, + } }); AppRegistry.registerComponent('rpc', () => rpc); diff --git a/examples/rpc/ios/rpc.xcodeproj/project.pbxproj b/examples/rpc/ios/rpc.xcodeproj/project.pbxproj index b445f715..2020b962 100644 --- a/examples/rpc/ios/rpc.xcodeproj/project.pbxproj +++ b/examples/rpc/ios/rpc.xcodeproj/project.pbxproj @@ -22,6 +22,8 @@ 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; 140ED2AC1D01E1AD002B40FF /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; }; 146834051AC3E58100842450 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; }; + 41B4735E1D98EAD4002C71D7 /* libReact-Native-Webview-Bridge.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 418B30531D98E6EC00716CA2 /* libReact-Native-Webview-Bridge.a */; }; + 41B4736D1D98F18D002C71D7 /* bootstrap.js in Resources */ = {isa = PBXBuildFile; fileRef = 41B4736C1D98F18D002C71D7 /* bootstrap.js */; }; 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; }; /* End PBXBuildFile section */ @@ -89,6 +91,13 @@ remoteGlobalIDString = 83CBBA2E1A601D0E00E9B192; remoteInfo = React; }; + 418B30521D98E6EC00716CA2 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 41BC38FF1D986EDA00018E84 /* React-Native-Webview-Bridge.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 4114DC4C1C187C3A003CD988; + remoteInfo = "React-Native-Webview-Bridge"; + }; 78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */; @@ -106,17 +115,17 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = main.jsbundle; path = main.jsbundle; sourceTree = ""; }; - 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = ../node_modules/react-native/Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj; sourceTree = ""; }; - 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = ../node_modules/react-native/Libraries/Geolocation/RCTGeolocation.xcodeproj; sourceTree = ""; }; - 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = ../node_modules/react-native/Libraries/Image/RCTImage.xcodeproj; sourceTree = ""; }; - 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = ../node_modules/react-native/Libraries/Network/RCTNetwork.xcodeproj; sourceTree = ""; }; - 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = ../node_modules/react-native/Libraries/Vibration/RCTVibration.xcodeproj; sourceTree = ""; }; + 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = ""; }; + 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = "../node_modules/react-native/Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj"; sourceTree = ""; }; + 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = "../node_modules/react-native/Libraries/Geolocation/RCTGeolocation.xcodeproj"; sourceTree = ""; }; + 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = "../node_modules/react-native/Libraries/Image/RCTImage.xcodeproj"; sourceTree = ""; }; + 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = "../node_modules/react-native/Libraries/Network/RCTNetwork.xcodeproj"; sourceTree = ""; }; + 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = "../node_modules/react-native/Libraries/Vibration/RCTVibration.xcodeproj"; sourceTree = ""; }; 00E356EE1AD99517003FC87E /* rpcTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = rpcTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 00E356F21AD99517003FC87E /* rpcTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = rpcTests.m; sourceTree = ""; }; - 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = ../node_modules/react-native/Libraries/Settings/RCTSettings.xcodeproj; sourceTree = ""; }; - 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = ../node_modules/react-native/Libraries/WebSocket/RCTWebSocket.xcodeproj; sourceTree = ""; }; + 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = "../node_modules/react-native/Libraries/Settings/RCTSettings.xcodeproj"; sourceTree = ""; }; + 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = "../node_modules/react-native/Libraries/WebSocket/RCTWebSocket.xcodeproj"; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* rpc.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = rpc.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = rpc/AppDelegate.h; sourceTree = ""; }; 13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = rpc/AppDelegate.m; sourceTree = ""; }; @@ -124,9 +133,11 @@ 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = rpc/Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = rpc/Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = rpc/main.m; sourceTree = ""; }; - 146833FF1AC3E56700842450 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = ../node_modules/react-native/React/React.xcodeproj; sourceTree = ""; }; - 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = ../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj; sourceTree = ""; }; - 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../node_modules/react-native/Libraries/Text/RCTText.xcodeproj; sourceTree = ""; }; + 146833FF1AC3E56700842450 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = "../node_modules/react-native/React/React.xcodeproj"; sourceTree = ""; }; + 41B4736C1D98F18D002C71D7 /* bootstrap.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = bootstrap.js; path = "../node_modules/react-native-webview-bridge/scripts/bootstrap.js"; sourceTree = ""; }; + 41BC38FF1D986EDA00018E84 /* React-Native-Webview-Bridge.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = "React-Native-Webview-Bridge.xcodeproj"; path = "../node_modules/react-native-webview-bridge/ios/React-Native-Webview-Bridge.xcodeproj"; sourceTree = ""; }; + 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = ""; }; + 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -142,6 +153,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 41B4735E1D98EAD4002C71D7 /* libReact-Native-Webview-Bridge.a in Frameworks */, 146834051AC3E58100842450 /* libReact.a in Frameworks */, 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */, 00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */, @@ -234,6 +246,7 @@ 13B07FAE1A68108700A75B9A /* rpc */ = { isa = PBXGroup; children = ( + 41B4736C1D98F18D002C71D7 /* bootstrap.js */, 008F07F21AC5B25A0029DE68 /* main.jsbundle */, 13B07FAF1A68108700A75B9A /* AppDelegate.h */, 13B07FB01A68108700A75B9A /* AppDelegate.m */, @@ -253,6 +266,14 @@ name = Products; sourceTree = ""; }; + 418B304F1D98E6EC00716CA2 /* Products */ = { + isa = PBXGroup; + children = ( + 418B30531D98E6EC00716CA2 /* libReact-Native-Webview-Bridge.a */, + ); + name = Products; + sourceTree = ""; + }; 78C398B11ACF4ADC00677621 /* Products */ = { isa = PBXGroup; children = ( @@ -264,6 +285,7 @@ 832341AE1AAA6A7D00B99B32 /* Libraries */ = { isa = PBXGroup; children = ( + 41BC38FF1D986EDA00018E84 /* React-Native-Webview-Bridge.xcodeproj */, 146833FF1AC3E56700842450 /* React.xcodeproj */, 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */, 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */, @@ -409,6 +431,10 @@ ProductGroup = 139FDEE71B06529A00C62182 /* Products */; ProjectRef = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */; }, + { + ProductGroup = 418B304F1D98E6EC00716CA2 /* Products */; + ProjectRef = 41BC38FF1D986EDA00018E84 /* React-Native-Webview-Bridge.xcodeproj */; + }, { ProductGroup = 146834001AC3E56700842450 /* Products */; ProjectRef = 146833FF1AC3E56700842450 /* React.xcodeproj */; @@ -479,6 +505,13 @@ remoteRef = 146834031AC3E56700842450 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; + 418B30531D98E6EC00716CA2 /* libReact-Native-Webview-Bridge.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = "libReact-Native-Webview-Bridge.a"; + remoteRef = 418B30521D98E6EC00716CA2 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; 78C398B91ACF4ADC00677621 /* libRCTLinking.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; @@ -507,6 +540,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 41B4736D1D98F18D002C71D7 /* bootstrap.js in Resources */, 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */, ); @@ -528,7 +562,6 @@ runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "export NODE_BINARY=node\n../node_modules/react-native/packager/react-native-xcode.sh"; - showEnvVarsInLog = 1; }; /* End PBXShellScriptBuildPhase section */ @@ -613,7 +646,8 @@ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, "$(SRCROOT)/../node_modules/react-native/React/**", ); - INFOPLIST_FILE = "rpc/Info.plist"; + INFOPLIST_FILE = rpc/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; OTHER_LDFLAGS = ( "$(inherited)", @@ -635,7 +669,8 @@ /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, "$(SRCROOT)/../node_modules/react-native/React/**", ); - INFOPLIST_FILE = "rpc/Info.plist"; + INFOPLIST_FILE = rpc/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; OTHER_LDFLAGS = ( "$(inherited)", diff --git a/examples/rpc/ios/rpc.xcodeproj/xcshareddata/xcschemes/rpc.xcscheme b/examples/rpc/ios/rpc.xcodeproj/xcshareddata/xcschemes/rpc.xcscheme index 79d0d8a9..ab967633 100644 --- a/examples/rpc/ios/rpc.xcodeproj/xcshareddata/xcschemes/rpc.xcscheme +++ b/examples/rpc/ios/rpc.xcodeproj/xcshareddata/xcschemes/rpc.xcscheme @@ -37,10 +37,10 @@ + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -62,15 +62,18 @@ ReferencedContainer = "container:rpc.xcodeproj"> + + @@ -86,10 +89,10 @@ diff --git a/examples/rpc/package.json b/examples/rpc/package.json index 07d08587..a516884d 100644 --- a/examples/rpc/package.json +++ b/examples/rpc/package.json @@ -7,6 +7,7 @@ }, "dependencies": { "react": "15.3.2", - "react-native": "0.34.0" + "react-native": "0.34.0", + "react-native-webview-bridge": "../.." } } diff --git a/ios/RCTWebViewBridge.m b/ios/RCTWebViewBridge.m index 4b26b073..52775cc1 100644 --- a/ios/RCTWebViewBridge.m +++ b/ios/RCTWebViewBridge.m @@ -19,7 +19,9 @@ #import "RCTView.h" #import "UIView+React.h" -NSString *const RCTJSNavigationScheme = @"react-js-navigation"; +//This is a very elegent way of defining multiline string in objective-c. +//source: http://stackoverflow.com/a/23387659/828487 +#define NSStringMultiline(...) [[NSString alloc] initWithCString:#__VA_ARGS__ encoding:NSUTF8StringEncoding] @interface RCTWebViewBridge () @@ -249,7 +251,7 @@ - (void)webView:(__unused UIWebView *)webView didFailLoadWithError:(NSError *)er - (void)webViewDidFinishLoad:(UIWebView *)webView { //injecting WebViewBridge bootstrap - [webView stringByEvaluatingJavaScriptFromString:[self webViewBridgeScript]]; + [webView stringByEvaluatingJavaScriptFromString:[self webViewBridgeBootrstrap]]; if (_injectedJavaScript != nil) { NSString *jsEvaluationValue = [webView stringByEvaluatingJavaScriptFromString:_injectedJavaScript]; diff --git a/webview-bridge/index.ios.js b/webview-bridge/index.ios.js index fa3159b1..1a5012fe 100644 --- a/webview-bridge/index.ios.js +++ b/webview-bridge/index.ios.js @@ -2,34 +2,39 @@ * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * + * Copyright (c) 2016-present, Ali Najafizadeh + * All rights reserved. + * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * - * @providesModule WebView + * @providesModule WebViewBridge * @noflow */ 'use strict'; -var ActivityIndicator = require('ActivityIndicator'); -var EdgeInsetsPropType = require('EdgeInsetsPropType'); -var React = require('React'); -var ReactNative = require('react/lib/ReactNative'); -var StyleSheet = require('StyleSheet'); -var Text = require('Text'); -var UIManager = require('UIManager'); -var View = require('View'); -var ScrollView = require('ScrollView'); - -var deprecatedPropType = require('deprecatedPropType'); -var invariant = require('fbjs/lib/invariant'); -var keyMirror = require('fbjs/lib/keyMirror'); -var processDecelerationRate = require('processDecelerationRate'); -var requireNativeComponent = require('requireNativeComponent'); -var resolveAssetSource = require('resolveAssetSource'); - -var PropTypes = React.PropTypes; -var RCTWebViewBridgeManager = require('NativeModules').WebViewManager; +import React, { PropTypes } from 'react' +import { + ActivityIndicator, + EdgeInsetsPropType, + StyleSheet, + Text, + UIManager, + View, + ScrollView, + NativeModules, + requireNativeComponent +} from 'react-native' + +import invariant from 'fbjs/lib/invariant' +import keyMirror from 'fbjs/lib/keyMirror' + +import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource'; +import processDecelerationRate from 'react-native/Libraries/Components/ScrollView/processDecelerationRate' +import deprecatedPropType from 'react-native/Libraries/Utilities/deprecatedPropType' + +const { WebViewBridgeManager } = NativeModules var BGWASH = 'rgba(255,255,255,0.8)'; var RCT_WEBVIEW_REF = 'webview'; @@ -370,7 +375,7 @@ class WebView extends React.Component { var onShouldStartLoadWithRequest = this.props.onShouldStartLoadWithRequest && ((event: Event) => { var shouldStart = this.props.onShouldStartLoadWithRequest && this.props.onShouldStartLoadWithRequest(event.nativeEvent); - RCTWebViewBridgeManager.startLoadWithResult(!!shouldStart, event.nativeEvent.lockIdentifier); + WebViewBridgeManager.startLoadWithResult(!!shouldStart, event.nativeEvent.lockIdentifier); }); var decelerationRate = processDecelerationRate(this.props.decelerationRate); From d94aa03af6335e39c687db18b3eba0d298a62ceb Mon Sep 17 00:00:00 2001 From: Ali Najafizadeh Date: Mon, 26 Sep 2016 02:10:59 -0400 Subject: [PATCH 08/77] updatetd code base --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index fd4f2b06..bce24eb4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ node_modules .DS_Store + +.tags +.tags1 From 86c8fe9554edee584c5c77767eb5056ec1a1ddda Mon Sep 17 00:00:00 2001 From: Ali Najafizadeh Date: Mon, 26 Sep 2016 02:15:24 -0400 Subject: [PATCH 09/77] cleaned up --- ios/RCTWebViewBridge.m | 2 +- .../project.pbxproj | 13 +------------ .../xcschemes/xcschememanagement.plist | 5 +++++ 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/ios/RCTWebViewBridge.m b/ios/RCTWebViewBridge.m index 52775cc1..d6d6dc53 100644 --- a/ios/RCTWebViewBridge.m +++ b/ios/RCTWebViewBridge.m @@ -285,7 +285,7 @@ - (void)sendToBridge:(NSString *)message - (NSString *)webViewBridgeBootrstrap { - NSString* path = [[NSBundle mainBundle] pathForResource:@"bootstrap" ofType:@"js"]; + NSString *path = [[NSBundle mainBundle] pathForResource:@"bootstrap" ofType:@"js"]; NSString* content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL]; return content; } diff --git a/ios/React-Native-Webview-Bridge.xcodeproj/project.pbxproj b/ios/React-Native-Webview-Bridge.xcodeproj/project.pbxproj index c4c381d7..18bce707 100644 --- a/ios/React-Native-Webview-Bridge.xcodeproj/project.pbxproj +++ b/ios/React-Native-Webview-Bridge.xcodeproj/project.pbxproj @@ -15,7 +15,7 @@ 4114DC4A1C187C3A003CD988 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; - dstPath = "include/$(PRODUCT_NAME)"; + dstPath = "include/$(TARGET_NAME)"; dstSubfolderSpec = 16; files = ( ); @@ -29,7 +29,6 @@ 4114DC591C187CCB003CD988 /* RCTWebViewBridge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWebViewBridge.m; sourceTree = SOURCE_ROOT; }; 4114DC5A1C187CCB003CD988 /* RCTWebViewBridgeManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebViewBridgeManager.h; sourceTree = SOURCE_ROOT; }; 4114DC5B1C187CCB003CD988 /* RCTWebViewBridgeManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWebViewBridgeManager.m; sourceTree = SOURCE_ROOT; }; - 4114DC5F1C187CE4003CD988 /* webviewbridge.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = webviewbridge.js; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -47,7 +46,6 @@ isa = PBXGroup; children = ( 4114DC4E1C187C3A003CD988 /* React-Native-Webview-Bridge */, - 4114DC5E1C187CE4003CD988 /* scripts */, 4114DC4D1C187C3A003CD988 /* Products */, ); sourceTree = ""; @@ -71,15 +69,6 @@ path = "React-Native-Webview-Bridge"; sourceTree = ""; }; - 4114DC5E1C187CE4003CD988 /* scripts */ = { - isa = PBXGroup; - children = ( - 4114DC5F1C187CE4003CD988 /* webviewbridge.js */, - ); - name = scripts; - path = ../scripts; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ diff --git a/ios/React-Native-Webview-Bridge.xcodeproj/xcuserdata/ali.xcuserdatad/xcschemes/xcschememanagement.plist b/ios/React-Native-Webview-Bridge.xcodeproj/xcuserdata/ali.xcuserdatad/xcschemes/xcschememanagement.plist index 2f988c46..d69df63b 100644 --- a/ios/React-Native-Webview-Bridge.xcodeproj/xcuserdata/ali.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/ios/React-Native-Webview-Bridge.xcodeproj/xcuserdata/ali.xcuserdatad/xcschemes/xcschememanagement.plist @@ -9,6 +9,11 @@ orderHint 0 + react-native-webview-bridge.xcscheme + + orderHint + 1 + SuppressBuildableAutocreation From a6230e19a63264b1af9f5289432fc30bf1fb605c Mon Sep 17 00:00:00 2001 From: Ali Najafizadeh Date: Mon, 26 Sep 2016 08:58:17 -0400 Subject: [PATCH 10/77] cleaned up and added some basic functionality --- ios/RCTWebViewBridge.m | 24 +++++++++++++++++++++++- ios/RCTWebViewBridgeManager.m | 1 + 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/ios/RCTWebViewBridge.m b/ios/RCTWebViewBridge.m index d6d6dc53..09948fc1 100644 --- a/ios/RCTWebViewBridge.m +++ b/ios/RCTWebViewBridge.m @@ -23,12 +23,15 @@ //source: http://stackoverflow.com/a/23387659/828487 #define NSStringMultiline(...) [[NSString alloc] initWithCString:#__VA_ARGS__ encoding:NSUTF8StringEncoding] +static const NSString* RCTWebViewBridgeSchema = @"rnwb"; + @interface RCTWebViewBridge () @property (nonatomic, copy) RCTDirectEventBlock onLoadingStart; @property (nonatomic, copy) RCTDirectEventBlock onLoadingFinish; @property (nonatomic, copy) RCTDirectEventBlock onLoadingError; @property (nonatomic, copy) RCTDirectEventBlock onShouldStartLoadWithRequest; +@property (nonatomic, copy) RCTDirectEventBlock onBridgeMessage; @end @@ -183,6 +186,18 @@ - (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLR { BOOL isJSNavigation = [request.URL.scheme isEqualToString:RCTJSNavigationScheme]; + if (!isJSNavigation && [request.URL.scheme isEqualToString:RCTWebViewBridgeSchema]) { + NSString* message = [webView stringByEvaluatingJavaScriptFromString:@"WebViewBridge.__fetch__()"]; + + NSMutableDictionary *onBridgeMessageEvent = [[NSMutableDictionary alloc] initWithDictionary:@{ + @"messages": [self stringArrayJsonToArray: message] + }]; + + _onBridgeMessage(onBridgeMessageEvent); + + isJSNavigation = YES; + } + static NSDictionary *navigationTypes; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ @@ -274,7 +289,7 @@ - (void)sendToBridge:(NSString *)message NSString *format = NSStringMultiline( (function(){ if (WebViewBridge && WebViewBridge.__push__) { - WebViewBridge.__push__('%@'); + WebViewBridge.__push__("%@"); } }()); ); @@ -283,6 +298,13 @@ - (void)sendToBridge:(NSString *)message [_webView stringByEvaluatingJavaScriptFromString:command]; } +- (NSArray*)stringArrayJsonToArray:(NSString *)message +{ + return [NSJSONSerialization JSONObjectWithData:[message dataUsingEncoding:NSUTF8StringEncoding] + options:NSJSONReadingAllowFragments + error:nil]; +} + - (NSString *)webViewBridgeBootrstrap { NSString *path = [[NSBundle mainBundle] pathForResource:@"bootstrap" ofType:@"js"]; diff --git a/ios/RCTWebViewBridgeManager.m b/ios/RCTWebViewBridgeManager.m index e12ed682..c09a008b 100644 --- a/ios/RCTWebViewBridgeManager.m +++ b/ios/RCTWebViewBridgeManager.m @@ -45,6 +45,7 @@ - (UIView *)view RCT_EXPORT_VIEW_PROPERTY(onLoadingFinish, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onLoadingError, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onShouldStartLoadWithRequest, RCTDirectEventBlock) +RCT_EXPORT_VIEW_PROPERTY(onBridgeMessage, RCTDirectEventBlock) RCT_REMAP_VIEW_PROPERTY(allowsInlineMediaPlayback, _webView.allowsInlineMediaPlayback, BOOL) RCT_REMAP_VIEW_PROPERTY(mediaPlaybackRequiresUserAction, _webView.mediaPlaybackRequiresUserAction, BOOL) RCT_REMAP_VIEW_PROPERTY(dataDetectorTypes, _webView.dataDetectorTypes, UIDataDetectorTypes) From 4ad07f4e919859f0f648aa2171184488f2194e44 Mon Sep 17 00:00:00 2001 From: Ali Najafizadeh Date: Tue, 27 Sep 2016 00:26:14 -0400 Subject: [PATCH 11/77] added sendToBridge and onBridgeMessage --- scripts/bootstrap.js | 6 ++++- webview-bridge/base64.js | 53 +++++++++++++++++++++++++++++++++++++ webview-bridge/index.ios.js | 19 +++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 webview-bridge/base64.js diff --git a/scripts/bootstrap.js b/scripts/bootstrap.js index 3429057d..5cc497a0 100644 --- a/scripts/bootstrap.js +++ b/scripts/bootstrap.js @@ -43,7 +43,11 @@ } function decode(input) { - return decodeURIComponent(escape(b64Decode(input))) + let result = decodeURIComponent(escape(b64Decode(input))) + try { + result = JSON.parse(result) + } catch(e){} + return result } // event section diff --git a/webview-bridge/base64.js b/webview-bridge/base64.js new file mode 100644 index 00000000..ac0cf649 --- /dev/null +++ b/webview-bridge/base64.js @@ -0,0 +1,53 @@ +//base64 encode and decode polyfil +//modified version of https://github.com/davidchambers/Base64.js +//you can replace the b64Encode and b64Decode for atob and btoa. +var b64ch = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; +function b64Encode(input) { + var output = ''; + for ( + var block, charCode, idx = 0, map = b64ch; + input.charAt(idx | 0) || (map = '=', idx % 1); + output += map.charAt(63 & block >> 8 - idx % 1 * 8) + ) { + charCode = input.charCodeAt(idx += 3/4); + block = block << 8 | charCode; + } + return output; +} + +function b64Decode(input) { + var str = input.replace(/=+$/, ''); + var output = ''; + for ( + var bc = 0, bs, buffer, idx = 0; + buffer = str.charAt(idx++); + ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, + bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0 + ) { + buffer = b64ch.indexOf(buffer); + } + return output; +} + +function encode(input) { + if (typeof input !== 'string') { + try { + input = JSON.stringify(input); + } catch(e) {} + } + input = unescape(encodeURIComponent(input)); + return b64Encode(input); +} + +function decode(input) { + let result = decodeURIComponent(escape(b64Decode(input))); + try { + result = JSON.parse(result); + } catch(e){} + return result; +} + +export default { + encode, + decode +}; diff --git a/webview-bridge/index.ios.js b/webview-bridge/index.ios.js index 1a5012fe..4084bd6b 100644 --- a/webview-bridge/index.ios.js +++ b/webview-bridge/index.ios.js @@ -34,6 +34,8 @@ import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource' import processDecelerationRate from 'react-native/Libraries/Components/ScrollView/processDecelerationRate' import deprecatedPropType from 'react-native/Libraries/Utilities/deprecatedPropType' +import { encode, decode } from './base64' + const { WebViewBridgeManager } = NativeModules var BGWASH = 'rgba(255,255,255,0.8)'; @@ -329,6 +331,8 @@ class WebView extends React.Component { * to tap them before they start playing. The default value is `true`. */ mediaPlaybackRequiresUserAction: PropTypes.bool, + + onBridgeMessage: PropTypes.func }; state = { @@ -407,6 +411,7 @@ class WebView extends React.Component { allowsInlineMediaPlayback={this.props.allowsInlineMediaPlayback} mediaPlaybackRequiresUserAction={this.props.mediaPlaybackRequiresUserAction} dataDetectorTypes={this.props.dataDetectorTypes} + onBridgeMessage={this.onBridgeMessage} />; return ( @@ -472,6 +477,20 @@ class WebView extends React.Component { } }; + onBridgeMessage = (event: Event) => { + const { onBridgeMessage } = this.props; + const messages = event.nativeEvent.messages; + if (onBridgeMessage) { + messages.forEach((message) => { + onBridgeMessage(decode(message)); + }); + } + }; + + sendToBridge = (message: any) => { + WebViewBridgeManager.sendToBridge(this.getWebViewHandle(), encode(message)); + }; + /** * Returns the native `WebView` node. */ From cddc619004b1ac4b8c0186198bc47dc7294f9d34 Mon Sep 17 00:00:00 2001 From: Ali Najafizadeh Date: Tue, 27 Sep 2016 01:10:31 -0400 Subject: [PATCH 12/77] got the message passing fully functional --- examples/rpc/index.ios.js | 22 ++++++++++++++++++++++ scripts/bootstrap.js | 4 +--- webview-bridge/base64.js | 10 +++++----- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/examples/rpc/index.ios.js b/examples/rpc/index.ios.js index ea89fe78..bc511ff1 100644 --- a/examples/rpc/index.ios.js +++ b/examples/rpc/index.ios.js @@ -14,12 +14,34 @@ import { import WebView from 'react-native-webview-bridge' +const injectScript = ` + (function () { + if (window.WebViewBridge) { + + WebViewBridge.onMessage = function (message) { + if (message === "hello from react-native") { + WebViewBridge.send("got the message inside webview"); + } + }; + + WebViewBridge.send({ message: 'nice to see you' }); + } + }()); +`; + class rpc extends Component { + onMessage = (payload) => { + console.log(payload.message) + } render() { + console.log(injectScript) return ( ); } diff --git a/scripts/bootstrap.js b/scripts/bootstrap.js index 5cc497a0..6689fc62 100644 --- a/scripts/bootstrap.js +++ b/scripts/bootstrap.js @@ -1,6 +1,4 @@ (function () { - 'use strict' - //base64 encode and decode polyfil //modified version of https://github.com/davidchambers/Base64.js //you can replace the b64Encode and b64Decode for atob and btoa. @@ -43,7 +41,7 @@ } function decode(input) { - let result = decodeURIComponent(escape(b64Decode(input))) + var result = decodeURIComponent(escape(b64Decode(input))) try { result = JSON.parse(result) } catch(e){} diff --git a/webview-bridge/base64.js b/webview-bridge/base64.js index ac0cf649..8f9a442b 100644 --- a/webview-bridge/base64.js +++ b/webview-bridge/base64.js @@ -40,14 +40,14 @@ function encode(input) { } function decode(input) { - let result = decodeURIComponent(escape(b64Decode(input))); + var result = decodeURIComponent(escape(b64Decode(input))); try { result = JSON.parse(result); } catch(e){} return result; } -export default { - encode, - decode -}; +module.exports = { + encode: encode, + decode: decode +} From 88df0d1be5072f0d67d7acfe32036261a053b5ef Mon Sep 17 00:00:00 2001 From: Ali Najafizadeh Date: Tue, 27 Sep 2016 09:33:52 -0400 Subject: [PATCH 13/77] added basic rpc and multiple callbacks for onMessage functions --- scripts/bootstrap.js | 22 ++++++++++++++++++---- scripts/rpc.js | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 scripts/rpc.js diff --git a/scripts/bootstrap.js b/scripts/bootstrap.js index 6689fc62..5c923b68 100644 --- a/scripts/bootstrap.js +++ b/scripts/bootstrap.js @@ -67,12 +67,21 @@ } var queue = [] + var onMessageListeners = {} window.WebViewBridge = { + __dispatch__: dispatch, __push__: function (encoded) { - decoded = decode(encoded) - //dispatch('webviewbridge:message', decoded); - window.WebViewBridge.message(decoded) + //we need to release native caller as soon as possible + //that's why we are wrap this on setTimeout + setTimeout(function () { + var fn = null + var decoded = decode(encoded) + Object.keys(onMessageListeners).forEach(function (onMessage) { + fn = onMessageListeners[onMessage] + fn(decoded) + }) + }, 15) }, __fetch__: function () { var val = JSON.stringify(queue) @@ -83,7 +92,12 @@ queue.push(encode(input)) setTimeout(signalNative, 15) }, - onMessage: function(msg) {} + addMessageListener: function(fn) { + onMessageListeners[fn] = fn + }, + removeMessageListener: function (fn) { + delete onMessageListeners[fn] + } } dispatch('webviewbridge:init', window.WebViewBridge) diff --git a/scripts/rpc.js b/scripts/rpc.js new file mode 100644 index 00000000..49178279 --- /dev/null +++ b/scripts/rpc.js @@ -0,0 +1,40 @@ +(function () { + 'use strict' + + var ids = 0 + var invokeFns = {} + var responseFns = {} + + function registerFn(name, fn) { + invokeFns[name] = fn + } + + function invokeFn(name, args, responseFn) { + var requestId = ++id + responseFns[requestId] = responseFn + + window.WebViewBridge.send({ + requestId: requestId, + name: name, + args: args + }) + } + + //registeration + function init(WebViewBridge) { + window.removeEventListener('webviewbridge:init', init) + + WebViewBridge.rpc = { + registerFn: registerFn, + invokeFn: invokeFn + } + + WebViewBridge.__dispatch__('webviewbridge:rpc', WebViewBridge) + } + + if (window.WebViewBridge) { + init(window.WebViewBridge) + } else { + window.addEventListener('webviewbridge:init', init) + } +}()) From 352863c0fb7a7e0d5c05b84331c1242f52f44ccc Mon Sep 17 00:00:00 2001 From: Ali Najafizadeh Date: Tue, 27 Sep 2016 09:34:07 -0400 Subject: [PATCH 14/77] updated rpc example --- examples/rpc/index.ios.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/examples/rpc/index.ios.js b/examples/rpc/index.ios.js index bc511ff1..c4e6557d 100644 --- a/examples/rpc/index.ios.js +++ b/examples/rpc/index.ios.js @@ -18,13 +18,11 @@ const injectScript = ` (function () { if (window.WebViewBridge) { - WebViewBridge.onMessage = function (message) { - if (message === "hello from react-native") { - WebViewBridge.send("got the message inside webview"); - } - }; + WebViewBridge.addMessageListener(function (message) { + alert(message) + }); - WebViewBridge.send({ message: 'nice to see you' }); + WebViewBridge.send({ message: 'This is working' }); } }()); `; From 05188ac40dffced803bea558a8dfa29c67f2cf7f Mon Sep 17 00:00:00 2001 From: Ali Najafizadeh Date: Tue, 27 Sep 2016 13:22:19 -0400 Subject: [PATCH 15/77] got 2 ways to work --- examples/rpc/index.ios.js | 12 ++++++++---- webview-bridge/index.ios.js | 5 +++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/examples/rpc/index.ios.js b/examples/rpc/index.ios.js index c4e6557d..8576a281 100644 --- a/examples/rpc/index.ios.js +++ b/examples/rpc/index.ios.js @@ -18,8 +18,8 @@ const injectScript = ` (function () { if (window.WebViewBridge) { - WebViewBridge.addMessageListener(function (message) { - alert(message) + WebViewBridge.addMessageListener(function (payload) { + alert(payload.message) }); WebViewBridge.send({ message: 'This is working' }); @@ -29,12 +29,16 @@ const injectScript = ` class rpc extends Component { onMessage = (payload) => { - console.log(payload.message) + if (this.bridgeRef) { + console.log(payload.message) + this.bridgeRef.sendToBridge({ message: 'This is working too' }) + } } + render() { - console.log(injectScript) return ( this.bridgeRef = ref} source={{uri: 'https://github.com/facebook/react-native'}} style={{marginTop: 20}} javaScriptEnabled={true} diff --git a/webview-bridge/index.ios.js b/webview-bridge/index.ios.js index 4084bd6b..fd915e84 100644 --- a/webview-bridge/index.ios.js +++ b/webview-bridge/index.ios.js @@ -24,7 +24,8 @@ import { View, ScrollView, NativeModules, - requireNativeComponent + requireNativeComponent, + findNodeHandle } from 'react-native' import invariant from 'fbjs/lib/invariant' @@ -495,7 +496,7 @@ class WebView extends React.Component { * Returns the native `WebView` node. */ getWebViewHandle = (): any => { - return ReactNative.findNodeHandle(this.refs[RCT_WEBVIEW_REF]); + return findNodeHandle(this.refs[RCT_WEBVIEW_REF]); }; _onLoadingStart = (event: Event) => { From 27e3eaa05d3c4079fac8e52e7e4fa4c16cf77c20 Mon Sep 17 00:00:00 2001 From: Ali Najafizadeh Date: Tue, 27 Sep 2016 14:01:32 -0400 Subject: [PATCH 16/77] added rpc calls --- scripts/rpc.js | 84 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 71 insertions(+), 13 deletions(-) diff --git a/scripts/rpc.js b/scripts/rpc.js index 49178279..f8a970f0 100644 --- a/scripts/rpc.js +++ b/scripts/rpc.js @@ -2,34 +2,92 @@ 'use strict' var ids = 0 - var invokeFns = {} - var responseFns = {} + var invokers = {} + var responseCallbacks = {} - function registerFn(name, fn) { - invokeFns[name] = fn + //invoker has accept 2 arguments + //fn(args, result). result is a function which accept an argument. + //that argument is being used to send to caller as a result value. + //by using this function, we can support both sync and async operation + function register(name, fn) { + invokers[name] = function (id, args) { + fn(args, function (result) { + window.WebViewBridge.send({ + id: id, + type: 'response', + result: result + }) + }) + } } - function invokeFn(name, args, responseFn) { - var requestId = ++id - responseFns[requestId] = responseFn + function invoke(name, args, callback) { + var id = ++ids + responseCallbacks[id] = callback window.WebViewBridge.send({ - requestId: requestId, + id: id, + type: 'invoke', name: name, args: args }) } - //registeration + function onInvoke(payload) { + var invoker = invokers[payload.name] + if (invoker) { + setTimeout(function () { + invoker(payload.id, payload.args) + }, 15) + } + } + + function onResponse(payload) { + var callback = responseCallbacks[payload.id] + if (callback) { + delete responseCallbacks[payload.id] + setTimeout(function () { + callback(payload.result) + }, 15) + } + } + + function onMessage(payload) { + if (typeof payload === 'string') { + return + } + + //there are two types of payload + // invoke: { type: 'payload', id, name, args } + // response: { type: 'response', id, result } + + switch(payload.type) { + case 'invoke': + onInvoke(payload) + break + case 'response': + onResponse(payload) + break + default: + //ignore + } + } + + //init this method register and attaches rpc to WebViewBridge. + //you can either check whether WebViewBridge.rpc is availebe or + //simply register to `webviewbridge:rpc` event. function init(WebViewBridge) { window.removeEventListener('webviewbridge:init', init) - WebViewBridge.rpc = { - registerFn: registerFn, - invokeFn: invokeFn + var rpc = { + register: register, + invoke: invoke } - WebViewBridge.__dispatch__('webviewbridge:rpc', WebViewBridge) + WebViewBridge.addMessageListener(onMessage) + + WebViewBridge.rpc = rpc + WebViewBridge.__dispatch__('webviewbridge:rpc', rpc) } if (window.WebViewBridge) { From 23f16619a8a65ef71775c1606a4db9433608a42e Mon Sep 17 00:00:00 2001 From: Ali Najafizadeh Date: Tue, 27 Sep 2016 20:53:03 -0400 Subject: [PATCH 17/77] cleaned up code base --- .../WebViewBridge}/index.android.js | 0 .../WebViewBridge}/index.ios.js | 2 +- lib/WebViewBridgeRPC/index.js | 52 ++++++++++++ {webview-bridge => lib/core}/base64.js | 13 ++- lib/core/rpc.js | 83 +++++++++++++++++++ lib/index.js | 14 ++++ package.json | 16 ++-- scripts/{bootstrap.js => WebViewBridge.js} | 0 scripts/{rpc.js => WebViewBridgeRPC.js} | 21 +++-- 9 files changed, 178 insertions(+), 23 deletions(-) rename {webview-bridge => lib/WebViewBridge}/index.android.js (100%) rename {webview-bridge => lib/WebViewBridge}/index.ios.js (99%) create mode 100644 lib/WebViewBridgeRPC/index.js rename {webview-bridge => lib/core}/base64.js (84%) create mode 100644 lib/core/rpc.js create mode 100644 lib/index.js rename scripts/{bootstrap.js => WebViewBridge.js} (100%) rename scripts/{rpc.js => WebViewBridgeRPC.js} (85%) diff --git a/webview-bridge/index.android.js b/lib/WebViewBridge/index.android.js similarity index 100% rename from webview-bridge/index.android.js rename to lib/WebViewBridge/index.android.js diff --git a/webview-bridge/index.ios.js b/lib/WebViewBridge/index.ios.js similarity index 99% rename from webview-bridge/index.ios.js rename to lib/WebViewBridge/index.ios.js index fd915e84..e388e9cb 100644 --- a/webview-bridge/index.ios.js +++ b/lib/WebViewBridge/index.ios.js @@ -35,7 +35,7 @@ import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource' import processDecelerationRate from 'react-native/Libraries/Components/ScrollView/processDecelerationRate' import deprecatedPropType from 'react-native/Libraries/Utilities/deprecatedPropType' -import { encode, decode } from './base64' +import { encode, decode } from '../core/base64' const { WebViewBridgeManager } = NativeModules diff --git a/lib/WebViewBridgeRPC/index.js b/lib/WebViewBridgeRPC/index.js new file mode 100644 index 00000000..11d277d5 --- /dev/null +++ b/lib/WebViewBridgeRPC/index.js @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2016-present, Ali Najafizadeh + * All rights reserved. + * + * @providesModule WebViewBridgeRPC + * @noflow + */ +import React, { Component } from 'react' + +import WebViewBridge from '../WebViewBridge' +import rpc from '../core/rpc' + +export default class WebViewBridgeRPC extends Component { + constructor(props, context) { + this.bridgeRef = null + this.rpc = null + } + + getWebViewRef = () => { + return this.bridgeRef + } + + register = (name, fn) => { + this.rpc.register(name, fn) + } + + invoke = (name, args) => { + //for now we don't care about reject. + //TODO: reject can be implemeneted by introducing timeout and failer in result. + //for now we don't support it. + return new Promise((resolve, reject) => { + this.rpc.invoke(name, args, resolve) + }) + } + + componentDidMount() { + this.rpc = rpc() + } + + componentWillMount() { + this.rpc = null + } + + render() { + return ( + this.bridgeRef = ref} + injectRPC={true}/> + ) + } +} diff --git a/webview-bridge/base64.js b/lib/core/base64.js similarity index 84% rename from webview-bridge/base64.js rename to lib/core/base64.js index 8f9a442b..e5356d3c 100644 --- a/webview-bridge/base64.js +++ b/lib/core/base64.js @@ -1,7 +1,14 @@ -//base64 encode and decode polyfil -//modified version of https://github.com/davidchambers/Base64.js -//you can replace the b64Encode and b64Decode for atob and btoa. +/** + * Copyright (c) 2016-present, Ali Najafizadeh + * All rights reserved. + * + * base64 encode and decode polyfil + * both b64Encode and b64Decode are the modified version of + * https://github.com/davidchambers/Base64.js + */ + var b64ch = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + function b64Encode(input) { var output = ''; for ( diff --git a/lib/core/rpc.js b/lib/core/rpc.js new file mode 100644 index 00000000..a922f874 --- /dev/null +++ b/lib/core/rpc.js @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2016-present, Ali Najafizadeh + * All rights reserved. + */ +function rpc() { + var ids = 0 + var invokers = {} + var responseCallbacks = {} + + //invoker has accept 2 arguments + //fn(args, result). result is a function which accept an argument. + //that argument is being used to send to caller as a result value. + //by using this function, we can support both sync and async operation + function register(sender, name, fn) { + invokers[name] = function (id, args) { + fn(args, function (result) { + sender({ + id: id, + type: 'response', + result: result + }) + }) + } + } + + function invoke(sender, name, args, callback) { + var id = ++ids + responseCallbacks[id] = callback + + sender({ + id: id, + type: 'invoke', + name: name, + args: args + }) + } + + function onInvoke(payload) { + var invoker = invokers[payload.name] + if (invoker) { + setTimeout(function () { + invoker(payload.id, payload.args) + }, 15) + } + } + + function onResponse(payload) { + var callback = responseCallbacks[payload.id] + if (callback) { + delete responseCallbacks[payload.id] + setTimeout(function () { + callback(payload.result) + }, 15) + } + } + + function onMessage(payload) { + if (typeof payload === 'string') { + return + } + + // there are two types of payload + // invoke: { type: 'payload', id, name, args } + // response: { type: 'response', id, result } + switch(payload.type) { + case 'invoke': + onInvoke(payload) + break + case 'response': + onResponse(payload) + break + default: + //ignore + } + } + + return { + register: register, + invoke: invoke + } +} + +module.exports = rpc diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 00000000..92f411df --- /dev/null +++ b/lib/index.js @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2016-present, Ali Najafizadeh + * All rights reserved. + * + * @providesModule WebViewBridgeRPC + * @noflow + */ +import WebViewBridge from './WebViewBridge' +import WebViewBridgeRPC from './WebViewBridgeRPC' + +export default { + WebViewBridge, + WebViewBridgeRPC +} diff --git a/package.json b/package.json index b15b8d58..dcaa33ae 100644 --- a/package.json +++ b/package.json @@ -2,11 +2,7 @@ "name": "react-native-webview-bridge", "version": "0.33.0", "description": "React Native WebView Javascript Bridge", - "main": "webview-bridge", - "directories": { - "example": "example", - "lib": "lib" - }, + "main": "lib", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, @@ -18,7 +14,9 @@ "react-native", "react-component", "ios", - "webview-bridge" + "android", + "webview-bridge", + "rpc" ], "author": "Ali Najafizadeh", "contributors": [ @@ -28,9 +26,5 @@ "bugs": { "url": "https://github.com/alinz/react-native-webview-bridge/issues" }, - "homepage": "https://github.com/alinz/react-native-webview-bridge", - "dependencies": { - "invariant": "2.2.0", - "keymirror": "0.1.1" - } + "homepage": "https://github.com/alinz/react-native-webview-bridge" } diff --git a/scripts/bootstrap.js b/scripts/WebViewBridge.js similarity index 100% rename from scripts/bootstrap.js rename to scripts/WebViewBridge.js diff --git a/scripts/rpc.js b/scripts/WebViewBridgeRPC.js similarity index 85% rename from scripts/rpc.js rename to scripts/WebViewBridgeRPC.js index f8a970f0..c46565d1 100644 --- a/scripts/rpc.js +++ b/scripts/WebViewBridgeRPC.js @@ -9,10 +9,10 @@ //fn(args, result). result is a function which accept an argument. //that argument is being used to send to caller as a result value. //by using this function, we can support both sync and async operation - function register(name, fn) { + function register(sender, name, fn) { invokers[name] = function (id, args) { fn(args, function (result) { - window.WebViewBridge.send({ + sender({ id: id, type: 'response', result: result @@ -21,11 +21,11 @@ } } - function invoke(name, args, callback) { + function invoke(sender, name, args, callback) { var id = ++ids responseCallbacks[id] = callback - window.WebViewBridge.send({ + sender({ id: id, type: 'invoke', name: name, @@ -77,14 +77,19 @@ //you can either check whether WebViewBridge.rpc is availebe or //simply register to `webviewbridge:rpc` event. function init(WebViewBridge) { + var rpc = {} + var sender = window.WebViewBridge.send + window.removeEventListener('webviewbridge:init', init) + WebViewBridge.addMessageListener(onMessage) - var rpc = { - register: register, - invoke: invoke + rpc.register = function (name, fn) { + register(sender, name, fn) } - WebViewBridge.addMessageListener(onMessage) + rpc.invoke = function (name, args, callback) { + invoke(sender, name, args, callback) + } WebViewBridge.rpc = rpc WebViewBridge.__dispatch__('webviewbridge:rpc', rpc) From e8f648e17c6ece3dc30e00a90d25a5f24cb763c2 Mon Sep 17 00:00:00 2001 From: Ali Najafizadeh Date: Tue, 27 Sep 2016 23:48:39 -0400 Subject: [PATCH 18/77] got basic to work --- examples/rpc/index.ios.js | 45 ++++++++++++++----- .../rpc/ios/rpc.xcodeproj/project.pbxproj | 12 +++-- ios/RCTWebViewBridge.h | 1 + ios/RCTWebViewBridge.m | 14 +++++- ios/RCTWebViewBridgeManager.m | 1 + lib/WebViewBridge/index.ios.js | 4 +- lib/WebViewBridgeRPC/index.js | 23 ++++++---- lib/core/rpc.js | 3 +- lib/index.js | 9 +--- 9 files changed, 77 insertions(+), 35 deletions(-) diff --git a/examples/rpc/index.ios.js b/examples/rpc/index.ios.js index 8576a281..ba75ff7b 100644 --- a/examples/rpc/index.ios.js +++ b/examples/rpc/index.ios.js @@ -12,38 +12,59 @@ import { View } from 'react-native'; -import WebView from 'react-native-webview-bridge' +import { WebViewBridge, WebViewBridgeRPC } from 'react-native-webview-bridge' const injectScript = ` (function () { if (window.WebViewBridge) { - WebViewBridge.addMessageListener(function (payload) { - alert(payload.message) - }); + WebViewBridge.rpc.invoke('onNativeCall', null, function (resp) { + alert(resp) + }) - WebViewBridge.send({ message: 'This is working' }); + WebViewBridge.rpc.register('onWebViewCall', function (args, resolve) { + resolve('this is web call') + }) + + // WebViewBridge.addMessageListener(function (payload) { + // alert(payload.message) + // }); + // + // WebViewBridge.send({ message: 'This is working' }); } }()); `; class rpc extends Component { - onMessage = (payload) => { - if (this.bridgeRef) { - console.log(payload.message) - this.bridgeRef.sendToBridge({ message: 'This is working too' }) - } + constructor(props, context) { + super(props, context) + + this.bridgeRef = null + } + + componentDidMount() { + this.bridgeRef.register('onNativeCall', this.onNativeCall) + } + + onNativeCall = (args, resolve) => { + resolve('native call') + + setTimeout(() => { + console.log('invoking....') + this.bridgeRef.invoke('onWebViewCall', null, function (resp) { + console.log(resp) + }) + }, 3000) } render() { return ( - this.bridgeRef = ref} source={{uri: 'https://github.com/facebook/react-native'}} style={{marginTop: 20}} javaScriptEnabled={true} injectedJavaScript={injectScript} - onBridgeMessage={this.onMessage} /> ); } diff --git a/examples/rpc/ios/rpc.xcodeproj/project.pbxproj b/examples/rpc/ios/rpc.xcodeproj/project.pbxproj index 2020b962..7a3a3389 100644 --- a/examples/rpc/ios/rpc.xcodeproj/project.pbxproj +++ b/examples/rpc/ios/rpc.xcodeproj/project.pbxproj @@ -22,8 +22,9 @@ 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; 140ED2AC1D01E1AD002B40FF /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; }; 146834051AC3E58100842450 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; }; + 41538B161D9B4E41002A1C00 /* WebViewBridge.js in Resources */ = {isa = PBXBuildFile; fileRef = 41538B141D9B4E41002A1C00 /* WebViewBridge.js */; }; + 41538B171D9B4E41002A1C00 /* WebViewBridgeRPC.js in Resources */ = {isa = PBXBuildFile; fileRef = 41538B151D9B4E41002A1C00 /* WebViewBridgeRPC.js */; }; 41B4735E1D98EAD4002C71D7 /* libReact-Native-Webview-Bridge.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 418B30531D98E6EC00716CA2 /* libReact-Native-Webview-Bridge.a */; }; - 41B4736D1D98F18D002C71D7 /* bootstrap.js in Resources */ = {isa = PBXBuildFile; fileRef = 41B4736C1D98F18D002C71D7 /* bootstrap.js */; }; 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; }; /* End PBXBuildFile section */ @@ -134,7 +135,8 @@ 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = rpc/Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = rpc/main.m; sourceTree = ""; }; 146833FF1AC3E56700842450 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = "../node_modules/react-native/React/React.xcodeproj"; sourceTree = ""; }; - 41B4736C1D98F18D002C71D7 /* bootstrap.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = bootstrap.js; path = "../node_modules/react-native-webview-bridge/scripts/bootstrap.js"; sourceTree = ""; }; + 41538B141D9B4E41002A1C00 /* WebViewBridge.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = WebViewBridge.js; path = "../node_modules/react-native-webview-bridge/scripts/WebViewBridge.js"; sourceTree = ""; }; + 41538B151D9B4E41002A1C00 /* WebViewBridgeRPC.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = WebViewBridgeRPC.js; path = "../node_modules/react-native-webview-bridge/scripts/WebViewBridgeRPC.js"; sourceTree = ""; }; 41BC38FF1D986EDA00018E84 /* React-Native-Webview-Bridge.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = "React-Native-Webview-Bridge.xcodeproj"; path = "../node_modules/react-native-webview-bridge/ios/React-Native-Webview-Bridge.xcodeproj"; sourceTree = ""; }; 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = ""; }; 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = ""; }; @@ -246,7 +248,8 @@ 13B07FAE1A68108700A75B9A /* rpc */ = { isa = PBXGroup; children = ( - 41B4736C1D98F18D002C71D7 /* bootstrap.js */, + 41538B141D9B4E41002A1C00 /* WebViewBridge.js */, + 41538B151D9B4E41002A1C00 /* WebViewBridgeRPC.js */, 008F07F21AC5B25A0029DE68 /* main.jsbundle */, 13B07FAF1A68108700A75B9A /* AppDelegate.h */, 13B07FB01A68108700A75B9A /* AppDelegate.m */, @@ -540,7 +543,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 41B4736D1D98F18D002C71D7 /* bootstrap.js in Resources */, + 41538B161D9B4E41002A1C00 /* WebViewBridge.js in Resources */, + 41538B171D9B4E41002A1C00 /* WebViewBridgeRPC.js in Resources */, 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */, ); diff --git a/ios/RCTWebViewBridge.h b/ios/RCTWebViewBridge.h index caba7be9..76af1c43 100644 --- a/ios/RCTWebViewBridge.h +++ b/ios/RCTWebViewBridge.h @@ -36,6 +36,7 @@ shouldStartLoadForRequest:(NSMutableDictionary *)request @property (nonatomic, assign) BOOL automaticallyAdjustContentInsets; @property (nonatomic, copy) NSString *injectedJavaScript; @property (nonatomic, assign) BOOL scalesPageToFit; +@property (nonatomic, assign) BOOL rpc; - (void)goForward; - (void)goBack; diff --git a/ios/RCTWebViewBridge.m b/ios/RCTWebViewBridge.m index 09948fc1..0555cb2e 100644 --- a/ios/RCTWebViewBridge.m +++ b/ios/RCTWebViewBridge.m @@ -267,6 +267,11 @@ - (void)webViewDidFinishLoad:(UIWebView *)webView { //injecting WebViewBridge bootstrap [webView stringByEvaluatingJavaScriptFromString:[self webViewBridgeBootrstrap]]; + + //injecting WebViewBridgeRPC bootstrap + if (self.rpc) { + [webView stringByEvaluatingJavaScriptFromString:[self webViewBridgeRPCBootstrap]]; + } if (_injectedJavaScript != nil) { NSString *jsEvaluationValue = [webView stringByEvaluatingJavaScriptFromString:_injectedJavaScript]; @@ -307,7 +312,14 @@ - (NSArray*)stringArrayJsonToArray:(NSString *)message - (NSString *)webViewBridgeBootrstrap { - NSString *path = [[NSBundle mainBundle] pathForResource:@"bootstrap" ofType:@"js"]; + NSString *path = [[NSBundle mainBundle] pathForResource:@"WebViewBridge" ofType:@"js"]; + NSString* content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL]; + return content; +} + +- (NSString *)webViewBridgeRPCBootstrap +{ + NSString *path = [[NSBundle mainBundle] pathForResource:@"WebViewBridgeRPC" ofType:@"js"]; NSString* content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL]; return content; } diff --git a/ios/RCTWebViewBridgeManager.m b/ios/RCTWebViewBridgeManager.m index c09a008b..57bffc14 100644 --- a/ios/RCTWebViewBridgeManager.m +++ b/ios/RCTWebViewBridgeManager.m @@ -49,6 +49,7 @@ - (UIView *)view RCT_REMAP_VIEW_PROPERTY(allowsInlineMediaPlayback, _webView.allowsInlineMediaPlayback, BOOL) RCT_REMAP_VIEW_PROPERTY(mediaPlaybackRequiresUserAction, _webView.mediaPlaybackRequiresUserAction, BOOL) RCT_REMAP_VIEW_PROPERTY(dataDetectorTypes, _webView.dataDetectorTypes, UIDataDetectorTypes) +RCT_EXPORT_VIEW_PROPERTY(rpc, BOOL) RCT_EXPORT_METHOD(goBack:(nonnull NSNumber *)reactTag) { diff --git a/lib/WebViewBridge/index.ios.js b/lib/WebViewBridge/index.ios.js index e388e9cb..5a0fe2cd 100644 --- a/lib/WebViewBridge/index.ios.js +++ b/lib/WebViewBridge/index.ios.js @@ -333,7 +333,8 @@ class WebView extends React.Component { */ mediaPlaybackRequiresUserAction: PropTypes.bool, - onBridgeMessage: PropTypes.func + onBridgeMessage: PropTypes.func, + rpc: PropTypes.bool }; state = { @@ -413,6 +414,7 @@ class WebView extends React.Component { mediaPlaybackRequiresUserAction={this.props.mediaPlaybackRequiresUserAction} dataDetectorTypes={this.props.dataDetectorTypes} onBridgeMessage={this.onBridgeMessage} + rpc={!!this.props.rpc} />; return ( diff --git a/lib/WebViewBridgeRPC/index.js b/lib/WebViewBridgeRPC/index.js index 11d277d5..f4a0dcd8 100644 --- a/lib/WebViewBridgeRPC/index.js +++ b/lib/WebViewBridgeRPC/index.js @@ -12,25 +12,29 @@ import rpc from '../core/rpc' export default class WebViewBridgeRPC extends Component { constructor(props, context) { + super(props, context) this.bridgeRef = null this.rpc = null } + sender = (payload) => { + this.bridgeRef.sendToBridge(payload) + } + getWebViewRef = () => { return this.bridgeRef } register = (name, fn) => { - this.rpc.register(name, fn) + this.rpc.register(this.sender, name, fn) + } + + invoke = (name, args, resolve) => { + this.rpc.invoke(this.sender, name, args, resolve) } - invoke = (name, args) => { - //for now we don't care about reject. - //TODO: reject can be implemeneted by introducing timeout and failer in result. - //for now we don't support it. - return new Promise((resolve, reject) => { - this.rpc.invoke(name, args, resolve) - }) + onMessage = (payload) => { + this.rpc.onMessage(payload) } componentDidMount() { @@ -46,7 +50,8 @@ export default class WebViewBridgeRPC extends Component { this.bridgeRef = ref} - injectRPC={true}/> + rpc={true} + onBridgeMessage={this.onMessage}/> ) } } diff --git a/lib/core/rpc.js b/lib/core/rpc.js index a922f874..b25e6bc0 100644 --- a/lib/core/rpc.js +++ b/lib/core/rpc.js @@ -76,7 +76,8 @@ function rpc() { return { register: register, - invoke: invoke + invoke: invoke, + onMessage: onMessage } } diff --git a/lib/index.js b/lib/index.js index 92f411df..e67da4d7 100644 --- a/lib/index.js +++ b/lib/index.js @@ -5,10 +5,5 @@ * @providesModule WebViewBridgeRPC * @noflow */ -import WebViewBridge from './WebViewBridge' -import WebViewBridgeRPC from './WebViewBridgeRPC' - -export default { - WebViewBridge, - WebViewBridgeRPC -} +export { default as WebViewBridge } from './WebViewBridge' +export { default as WebViewBridgeRPC } from './WebViewBridgeRPC' From cfd6ceaf8c0e7e13dc3d73f69c436c03091d1bf1 Mon Sep 17 00:00:00 2001 From: Ali Najafizadeh Date: Wed, 28 Sep 2016 01:35:14 -0400 Subject: [PATCH 19/77] cleaned up code --- examples/rpc/index.ios.js | 14 +++---- lib/WebViewBridgeRPC/index.js | 12 ++---- lib/core/rpc.js | 61 ++++++++++++++++++++++++------ scripts/WebViewBridgeRPC.js | 70 +++++++++++++++++++++++++++-------- 4 files changed, 115 insertions(+), 42 deletions(-) diff --git a/examples/rpc/index.ios.js b/examples/rpc/index.ios.js index ba75ff7b..5b76b355 100644 --- a/examples/rpc/index.ios.js +++ b/examples/rpc/index.ios.js @@ -16,13 +16,14 @@ import { WebViewBridge, WebViewBridgeRPC } from 'react-native-webview-bridge' const injectScript = ` (function () { - if (window.WebViewBridge) { + if (window.WebViewBridge && window.WebViewBridge.rpc) { + var rpc = WebViewBridge.rpc - WebViewBridge.rpc.invoke('onNativeCall', null, function (resp) { + rpc.invoke('onNativeCall', null, { timeout: 0 }, function (err, resp) { alert(resp) }) - WebViewBridge.rpc.register('onWebViewCall', function (args, resolve) { + rpc.register('onWebViewCall', function (args, resolve, reject) { resolve('this is web call') }) @@ -46,13 +47,12 @@ class rpc extends Component { this.bridgeRef.register('onNativeCall', this.onNativeCall) } - onNativeCall = (args, resolve) => { + onNativeCall = (args, resolve, reject) => { resolve('native call') setTimeout(() => { - console.log('invoking....') - this.bridgeRef.invoke('onWebViewCall', null, function (resp) { - console.log(resp) + this.bridgeRef.invoke('onWebViewCall', null, { timeout: 0 }, function (err, resp) { + console.log(err, resp) }) }, 3000) } diff --git a/lib/WebViewBridgeRPC/index.js b/lib/WebViewBridgeRPC/index.js index f4a0dcd8..7a75f03f 100644 --- a/lib/WebViewBridgeRPC/index.js +++ b/lib/WebViewBridgeRPC/index.js @@ -17,7 +17,7 @@ export default class WebViewBridgeRPC extends Component { this.rpc = null } - sender = (payload) => { + __sender = (payload) => { this.bridgeRef.sendToBridge(payload) } @@ -26,15 +26,11 @@ export default class WebViewBridgeRPC extends Component { } register = (name, fn) => { - this.rpc.register(this.sender, name, fn) + this.rpc.register(this.__sender, name, fn) } invoke = (name, args, resolve) => { - this.rpc.invoke(this.sender, name, args, resolve) - } - - onMessage = (payload) => { - this.rpc.onMessage(payload) + this.rpc.invoke(this.__sender, name, args, resolve) } componentDidMount() { @@ -51,7 +47,7 @@ export default class WebViewBridgeRPC extends Component { {...this.props} ref={(ref) => this.bridgeRef = ref} rpc={true} - onBridgeMessage={this.onMessage}/> + onBridgeMessage={(payload) => this.rpc.onMessage(payload)}/> ) } } diff --git a/lib/core/rpc.js b/lib/core/rpc.js index b25e6bc0..dfe16627 100644 --- a/lib/core/rpc.js +++ b/lib/core/rpc.js @@ -16,16 +16,40 @@ function rpc() { fn(args, function (result) { sender({ id: id, - type: 'response', + type: 'resolve', result: result }) + }, function (err) { + sender({ + id: id, + type: 'reject', + result: err + }) }) } } - function invoke(sender, name, args, callback) { + function invoke(sender, name, args, opt, callback) { var id = ++ids - responseCallbacks[id] = callback + var target = { + callback: callback + } + + if (opt) { + opt = {} + } + + if (!opt.timeout) { + opt.timeout = 0 + } + + if (opt.timeout) { + target.timeoutHandler = setTimeout(function () { + onReject(id, 'timeout') + }, opt.timeout) + } + + responseCallbacks[id] = target sender({ id: id, @@ -44,12 +68,24 @@ function rpc() { } } - function onResponse(payload) { - var callback = responseCallbacks[payload.id] - if (callback) { - delete responseCallbacks[payload.id] + function onResolve(id, result) { + var target = responseCallbacks[id] + if (target) { + clearTimeout(target.timeoutHandler) + delete responseCallbacks[id] + setTimeout(function () { + target.callback(null, result) + }, 15) + } + } + + function onReject(id, result) { + var target = responseCallbacks[id] + if (target) { + clearTimeout(target.timeoutHandler) + delete responseCallbacks[id] setTimeout(function () { - callback(payload.result) + target(result, null) }, 15) } } @@ -61,14 +97,17 @@ function rpc() { // there are two types of payload // invoke: { type: 'payload', id, name, args } - // response: { type: 'response', id, result } + // resolve: { type: 'resolve', id, result } + // reject: { type: 'reject', id, result } switch(payload.type) { case 'invoke': onInvoke(payload) break - case 'response': - onResponse(payload) + case 'resolve': + onResolve(payload.id, payload.result) break + case 'reject': + onReject(payload.id, payload.result) default: //ignore } diff --git a/scripts/WebViewBridgeRPC.js b/scripts/WebViewBridgeRPC.js index c46565d1..fb0e8205 100644 --- a/scripts/WebViewBridgeRPC.js +++ b/scripts/WebViewBridgeRPC.js @@ -14,16 +14,40 @@ fn(args, function (result) { sender({ id: id, - type: 'response', + type: 'resolve', result: result }) + }, function (err) { + sender({ + id: id, + type: 'reject', + result: err + }) }) } } - function invoke(sender, name, args, callback) { + function invoke(sender, name, args, opt, callback) { var id = ++ids - responseCallbacks[id] = callback + var target = { + callback: callback + } + + if (opt) { + opt = {} + } + + if (!opt.timeout) { + opt.timeout = 0 + } + + if (opt.timeout) { + target.timeoutHandler = setTimeout(function () { + onReject(id, 'timeout') + }, opt.timeout) + } + + responseCallbacks[id] = target sender({ id: id, @@ -42,12 +66,24 @@ } } - function onResponse(payload) { - var callback = responseCallbacks[payload.id] - if (callback) { - delete responseCallbacks[payload.id] + function onResolve(id, result) { + var target = responseCallbacks[id] + if (target) { + clearTimeout(target.timeoutHandler) + delete responseCallbacks[id] + setTimeout(function () { + target.callback(null, result) + }, 15) + } + } + + function onReject(id, result) { + var target = responseCallbacks[id] + if (target) { + clearTimeout(target.timeoutHandler) + delete responseCallbacks[id] setTimeout(function () { - callback(payload.result) + target(result, null) }, 15) } } @@ -57,17 +93,19 @@ return } - //there are two types of payload + // there are two types of payload // invoke: { type: 'payload', id, name, args } - // response: { type: 'response', id, result } - + // resolve: { type: 'resolve', id, result } + // reject: { type: 'reject', id, result } switch(payload.type) { case 'invoke': onInvoke(payload) break - case 'response': - onResponse(payload) + case 'resolve': + onResolve(payload.id, payload.result) break + case 'reject': + onReject(payload.id, payload.result) default: //ignore } @@ -78,7 +116,7 @@ //simply register to `webviewbridge:rpc` event. function init(WebViewBridge) { var rpc = {} - var sender = window.WebViewBridge.send + var sender = WebViewBridge.send window.removeEventListener('webviewbridge:init', init) WebViewBridge.addMessageListener(onMessage) @@ -87,8 +125,8 @@ register(sender, name, fn) } - rpc.invoke = function (name, args, callback) { - invoke(sender, name, args, callback) + rpc.invoke = function (name, args, opt, callback) { + invoke(sender, name, args, opt, callback) } WebViewBridge.rpc = rpc From eef8c707f1c2f391eebb6cce55b91e9d94b595d9 Mon Sep 17 00:00:00 2001 From: Ali Najafizadeh Date: Wed, 28 Sep 2016 01:41:17 -0400 Subject: [PATCH 20/77] fixed an issue related to native invoker --- lib/WebViewBridgeRPC/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/WebViewBridgeRPC/index.js b/lib/WebViewBridgeRPC/index.js index 7a75f03f..0a1df672 100644 --- a/lib/WebViewBridgeRPC/index.js +++ b/lib/WebViewBridgeRPC/index.js @@ -29,8 +29,8 @@ export default class WebViewBridgeRPC extends Component { this.rpc.register(this.__sender, name, fn) } - invoke = (name, args, resolve) => { - this.rpc.invoke(this.__sender, name, args, resolve) + invoke = (name, args, opt, resolve) => { + this.rpc.invoke(this.__sender, name, args, opt, resolve) } componentDidMount() { From 602b4a0f821a66ed6de7d98b6f94f668b18e7146 Mon Sep 17 00:00:00 2001 From: Ali Najafizadeh Date: Wed, 28 Sep 2016 17:48:57 -0400 Subject: [PATCH 21/77] fixed path is not set --- ios/RCTWebViewBridge.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ios/RCTWebViewBridge.m b/ios/RCTWebViewBridge.m index 0555cb2e..35fa3491 100644 --- a/ios/RCTWebViewBridge.m +++ b/ios/RCTWebViewBridge.m @@ -267,7 +267,7 @@ - (void)webViewDidFinishLoad:(UIWebView *)webView { //injecting WebViewBridge bootstrap [webView stringByEvaluatingJavaScriptFromString:[self webViewBridgeBootrstrap]]; - + //injecting WebViewBridgeRPC bootstrap if (self.rpc) { [webView stringByEvaluatingJavaScriptFromString:[self webViewBridgeRPCBootstrap]]; @@ -313,6 +313,7 @@ - (NSArray*)stringArrayJsonToArray:(NSString *)message - (NSString *)webViewBridgeBootrstrap { NSString *path = [[NSBundle mainBundle] pathForResource:@"WebViewBridge" ofType:@"js"]; + assert(path != nil); NSString* content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL]; return content; } @@ -320,6 +321,7 @@ - (NSString *)webViewBridgeBootrstrap - (NSString *)webViewBridgeRPCBootstrap { NSString *path = [[NSBundle mainBundle] pathForResource:@"WebViewBridgeRPC" ofType:@"js"]; + assert(path != nil); NSString* content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL]; return content; } From b7de2c6e3e4a18012d454390008dc5d85ca04dbf Mon Sep 17 00:00:00 2001 From: Ali Najafizadeh Date: Wed, 28 Sep 2016 20:44:42 -0400 Subject: [PATCH 22/77] fixed README --- README.md | 180 +++--------------- .../rpc/ios/rpc.xcodeproj/project.pbxproj | 8 +- 2 files changed, 35 insertions(+), 153 deletions(-) diff --git a/README.md b/README.md index 1b72bfa1..2dfddc65 100644 --- a/README.md +++ b/README.md @@ -1,173 +1,55 @@ -## Please take a look at this [issue](https://github.com/alinz/react-native-webview-bridge/issues/109) first - # React Native WebView Javascript Bridge -I have been testing and reading a lot of way to safely create a bridge between react-native and webview. I'm happy to announced that the wait is over and from **React-Native 0.20 and above**, the bridge is fully functional. - +This is an experimental version 2 of this module. *at the moment, it does not work with RNPM* ## Installation -In order to use this extension, you have to do the following steps: - -in your react-native project, run `npm install react-native-webview-bridge` - -### iOS - -1. go to xcode's `Project Navigator` tab -

- -

-2. right click on `Libraries` -3. select `Add Files to ...` option -

- -

-4. navigate to `node_modules/react-native-webview-bridge/ios` and add `React-Native-Webview-Bridge.xcodeproj` folder -

- -

-5. on project `Project Navigator` tab, click on your project's name and select Target's name and from there click on `Build Phases` -

- -

-6. expand `Link Binary With Libraries` and click `+` sign to add a new one. -7. select `libReact-Native-Webviwe-Bridge.a` and click `Add` button. -

- -

-8. clean compile to make sure your project can compile and build. - -### Android - -1. add the following import to `MainApplication.java` (`MainActivity.java` if RN < 0.29) of your application - -```java -import com.github.alinz.reactnativewebviewbridge.WebViewBridgePackage; -``` +- run `npm install react-native-webview-bridge` +- add both `scripts/WebViewBridge.js` and `scripts/WebViewBridgeRPC.js` into your xcode project -2. add the following code to add the package to `MainApplication.java`` (`MainActivity.java` if RN < 0.29) +## V2 -```java -protected List getPackages() { - return Arrays.asList( - new MainReactPackage(), - new WebViewBridgePackage() //<- this - ); - } -``` +I initially created this module so I can communicate from react-native to webview's script and by default react-native's webview +does have a way for achiving this feature. As times went by I found myself writing the proper communication code over and over again on top of it. So I have decided to make it one of the core feature of bridge by brining RPC into the table. -3. add the following codes to your `android/setting.gradle` +This will be the first initial of RPC, as you can easily call either react-native and webview function from either side. -> you might have multiple 3rd party libraries, make sure that you don't create multiple include. +## RPC -``` -include ':app', ':react-native-webview-bridge' -project(':react-native-webview-bridge').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webview-bridge/android') -``` +You have to 2 things, in order to make the RPC work. First, adding the file `scripts/WebViewBridgeRPC.js` to your project along side of `scripts/WebViewBridge.js`. These two file will be injected into every WebView instance you create. +Second instead of using `import { WebViewBridge } from 'react-native-webview-bridge'`, you have to use `import { WebViewBridgeRPC } from 'react-native-webview-bridge'`. `WebViewBridgeRPC` is exactly the same as `WebViewBridge` but it will manage all the communication via RPC calls. -4. edit `android/app/build.gradle` and add the following line inside `dependencies` +## APIs -``` -compile project(':react-native-webview-bridge') -``` +#### WebViewBridge -5. run `react-native run-android` to see if everything is compilable. +#### Props +- onBridgeMessage(fn: Function) -## Usage +accept a function as an argument and it will call it once a new message coming from WebView's script. -just import the module with one of your choices way: - -** CommonJS style ** - -```js -var WebViewBridge = require('react-native-webview-bridge'); +```javascript +onBridgeMessage(function (payload) { + console.log('got this payload', payload) +}) ``` -** ES6/ES2015 style ** +##### Methods +- sendToBridge(payload: string|object) -```js -import WebViewBridge from 'react-native-webview-bridge'; +```javascript +this.webViewRef.sendToBridge('hello world') +//or +this.webViewRef.sendToBridge({ message: 'hello world' }) ``` -`WebViewBridge` is an extension of `WebView`. It injects special script into any pages once it loads. Also it extends the functionality of `WebView` by adding 1 new method and 1 new props. - -#### sendToBridge(message) -the message must be in string. because this is the only way to send data back and forth between native and webview. - - -#### onBridgeMessage -this is a prop that needs to be a function. it will be called once a message is received from webview. The type of received message is also in string. - -#### allowFileAccessFromFileURLs (Android only) -this is a prop that allows locally loaded pages via file:// to access other file:// resources. Pass-thru to corresponding [setting](https://developer.android.com/reference/android/webkit/WebSettings.html#setAllowFileAccessFromFileURLs(boolean)) in WebView. Default is `false` for Android 4.1 and above. - -#### allowUniversalAccessFromFileURLs (Android only) -this is a prop that allows locally loaded pages via file:// to access resources in any origin. Pass-thru to corresponding [setting](https://developer.android.com/reference/android/webkit/WebSettings.html#setAllowUniversalAccessFromFileURLs(boolean)) in WebView. Default is `false` for Android 4.1 and above. +accept either `string` or `object` and it will send it to `WebView`'s script -## Bridge Script +> Note: you do not need to escape any character as the underneath transportation is using base64. -bridge script is a special script which injects into all the webview. It automatically register a global variable called `WebViewBridge`. It has 2 optional methods to implement and one method to send message to native side. +#### WebViewBridgeRPC -#### send(message) +##### Methods -this method sends a message to native side. the message must be in string type or `onError` method will be called. - -#### onMessage - -this method needs to be implemented. it will be called once a message arrives from native side. The type of message is in string. - -#### onError (iOS only) - -this is an error reporting method. It will be called if there is an error happens during sending a message. It receives a error message in string type. - -## Notes - -> a special bridge script will be injected once the page is going to different URL. So you don't have to manage when it needs to be injected. - -> You can still pass your own javascript to be injected into webview. However, Bridge script will be injected first and then your custom script. - - -## Simple Example -This example can be found in `examples` folder. - -```js -const injectScript = ` - (function () { - if (WebViewBridge) { - - WebViewBridge.onMessage = function (message) { - if (message === "hello from react-native") { - WebViewBridge.send("got the message inside webview"); - } - }; - - WebViewBridge.send("hello from webview"); - } - }()); -`; - -var Sample2 = React.createClass({ - onBridgeMessage(message){ - const { webviewbridge } = this.refs; - - switch (message) { - case "hello from webview": - webviewbridge.sendToBridge("hello from react-native"); - break; - case "got the message inside webview": - console.log("we have got a message from webview! yeah"); - break; - } - }, - - render() { - return ( - - ); - } -}); -``` +- register(name: string, fn: Function(resolve: Function, reject: Function)) +- invoke(name: string, args: any, opt: { timeout: 0}, fn: Function(err: any, result: any)) diff --git a/examples/rpc/ios/rpc.xcodeproj/project.pbxproj b/examples/rpc/ios/rpc.xcodeproj/project.pbxproj index 7a3a3389..c5f60efa 100644 --- a/examples/rpc/ios/rpc.xcodeproj/project.pbxproj +++ b/examples/rpc/ios/rpc.xcodeproj/project.pbxproj @@ -22,9 +22,9 @@ 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; 140ED2AC1D01E1AD002B40FF /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; }; 146834051AC3E58100842450 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; }; - 41538B161D9B4E41002A1C00 /* WebViewBridge.js in Resources */ = {isa = PBXBuildFile; fileRef = 41538B141D9B4E41002A1C00 /* WebViewBridge.js */; }; 41538B171D9B4E41002A1C00 /* WebViewBridgeRPC.js in Resources */ = {isa = PBXBuildFile; fileRef = 41538B151D9B4E41002A1C00 /* WebViewBridgeRPC.js */; }; 41B4735E1D98EAD4002C71D7 /* libReact-Native-Webview-Bridge.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 418B30531D98E6EC00716CA2 /* libReact-Native-Webview-Bridge.a */; }; + 41C983301D9C71F400EEF6BE /* WebViewBridge.js in Resources */ = {isa = PBXBuildFile; fileRef = 41C9832F1D9C71F400EEF6BE /* WebViewBridge.js */; }; 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; }; /* End PBXBuildFile section */ @@ -135,9 +135,9 @@ 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = rpc/Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = rpc/main.m; sourceTree = ""; }; 146833FF1AC3E56700842450 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = "../node_modules/react-native/React/React.xcodeproj"; sourceTree = ""; }; - 41538B141D9B4E41002A1C00 /* WebViewBridge.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = WebViewBridge.js; path = "../node_modules/react-native-webview-bridge/scripts/WebViewBridge.js"; sourceTree = ""; }; 41538B151D9B4E41002A1C00 /* WebViewBridgeRPC.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = WebViewBridgeRPC.js; path = "../node_modules/react-native-webview-bridge/scripts/WebViewBridgeRPC.js"; sourceTree = ""; }; 41BC38FF1D986EDA00018E84 /* React-Native-Webview-Bridge.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = "React-Native-Webview-Bridge.xcodeproj"; path = "../node_modules/react-native-webview-bridge/ios/React-Native-Webview-Bridge.xcodeproj"; sourceTree = ""; }; + 41C9832F1D9C71F400EEF6BE /* WebViewBridge.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = WebViewBridge.js; path = "../node_modules/react-native-webview-bridge/scripts/WebViewBridge.js"; sourceTree = ""; }; 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = ""; }; 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -248,7 +248,7 @@ 13B07FAE1A68108700A75B9A /* rpc */ = { isa = PBXGroup; children = ( - 41538B141D9B4E41002A1C00 /* WebViewBridge.js */, + 41C9832F1D9C71F400EEF6BE /* WebViewBridge.js */, 41538B151D9B4E41002A1C00 /* WebViewBridgeRPC.js */, 008F07F21AC5B25A0029DE68 /* main.jsbundle */, 13B07FAF1A68108700A75B9A /* AppDelegate.h */, @@ -543,8 +543,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 41538B161D9B4E41002A1C00 /* WebViewBridge.js in Resources */, 41538B171D9B4E41002A1C00 /* WebViewBridgeRPC.js in Resources */, + 41C983301D9C71F400EEF6BE /* WebViewBridge.js in Resources */, 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */, ); From 5cba955beec0523ea2a2fc246f48b0f6e7ad3a6c Mon Sep 17 00:00:00 2001 From: Ali Najafizadeh Date: Wed, 28 Sep 2016 23:49:20 -0400 Subject: [PATCH 23/77] make the code ready for android --- .../JavascriptBridge.java | 4 +- .../WebViewBridgeManager.java | 20 ++++- scripts/WebViewBridge.js | 77 ++++++++++--------- 3 files changed, 59 insertions(+), 42 deletions(-) diff --git a/android/src/main/java/com/github/alinz/reactnativewebviewbridge/JavascriptBridge.java b/android/src/main/java/com/github/alinz/reactnativewebviewbridge/JavascriptBridge.java index 2ad37116..5a95e901 100644 --- a/android/src/main/java/com/github/alinz/reactnativewebviewbridge/JavascriptBridge.java +++ b/android/src/main/java/com/github/alinz/reactnativewebviewbridge/JavascriptBridge.java @@ -17,7 +17,7 @@ public JavascriptBridge(WebView webView) { } @JavascriptInterface - public void send(String message) { + public void nativeAndroidSend(String message) { WritableMap event = Arguments.createMap(); event.putString("message", message); ReactContext reactContext = (ReactContext) this.webView.getContext(); @@ -26,4 +26,4 @@ public void send(String message) { "topChange", event); } -} \ No newline at end of file +} diff --git a/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgeManager.java b/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgeManager.java index c1250163..8988bb7b 100644 --- a/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgeManager.java +++ b/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgeManager.java @@ -16,6 +16,8 @@ public class WebViewBridgeManager extends ReactWebViewManager { private static final String REACT_CLASS = "RCTWebViewBridge"; public static final int COMMAND_SEND_TO_BRIDGE = 101; + public static final int COMMAND_INJECT_WEBVIEW_BRIDGE = 102; + public static final int COMMAND_INJECT_RPC = 103; @Override public String getName() { @@ -48,13 +50,27 @@ public void receiveCommand(WebView root, int commandId, @Nullable ReadableArray case COMMAND_SEND_TO_BRIDGE: sendToBridge(root, args.getString(0)); break; + case COMMAND_INJECT_WEBVIEW_BRIDGE: + injectWebViewBridgeScript(root); + break; + case COMMAND_INJECT_RPC: + injectWebViewBridgeRPCScript(root); + break; default: //do nothing!!!! } } + private void injectWebViewBridgeScript(WebView root) { + + } + + private void injectWebViewBridgeRPCScript(WebView root) { + + } + private void sendToBridge(WebView root, String message) { - String script = "WebViewBridge.onMessage('" + message + "');"; + String script = "(function(){ if (WebViewBridge && WebViewBridge.__push__) { WebViewBridge.__push__(\"" + message + "\"); } }());"; WebViewBridgeManager.evaluateJavascript(root, script); } @@ -75,4 +91,4 @@ public void setAllowFileAccessFromFileURLs(WebView root, boolean allows) { public void setAllowUniversalAccessFromFileURLs(WebView root, boolean allows) { root.getSettings().setAllowUniversalAccessFromFileURLs(allows); } -} \ No newline at end of file +} diff --git a/scripts/WebViewBridge.js b/scripts/WebViewBridge.js index 5c923b68..92ed4b84 100644 --- a/scripts/WebViewBridge.js +++ b/scripts/WebViewBridge.js @@ -1,4 +1,8 @@ (function () { + var WebViewBridge = window.WebViewBridge ? window.WebViewBridge : {} + var queue = [] + var onMessageListeners = {} + //base64 encode and decode polyfil //modified version of https://github.com/davidchambers/Base64.js //you can replace the b64Encode and b64Decode for atob and btoa. @@ -48,11 +52,12 @@ return result } - // event section - var doc = window.document - function signalNative() { - window.location = 'rnwb://message' + new Date().getTime() + if (WebViewBridge.nativeAndroidSend) { + WebViewBridge.nativeAndroidSend(WebViewBridge.__fetch__()) + } else { + window.location = 'rnwb://message' + new Date().getTime() + } } function dispatch(name, value) { @@ -62,43 +67,39 @@ }); setTimeout(function () { - doc.dispatchEvent(event) + window.document.dispatchEvent(event) }, 15) } - var queue = [] - var onMessageListeners = {} - - window.WebViewBridge = { - __dispatch__: dispatch, - __push__: function (encoded) { - //we need to release native caller as soon as possible - //that's why we are wrap this on setTimeout - setTimeout(function () { - var fn = null - var decoded = decode(encoded) - Object.keys(onMessageListeners).forEach(function (onMessage) { - fn = onMessageListeners[onMessage] - fn(decoded) - }) - }, 15) - }, - __fetch__: function () { - var val = JSON.stringify(queue) - queue = [] - return val - }, - send: function (input) { - queue.push(encode(input)) - setTimeout(signalNative, 15) - }, - addMessageListener: function(fn) { - onMessageListeners[fn] = fn - }, - removeMessageListener: function (fn) { - delete onMessageListeners[fn] - } + WebViewBridge.__dispatch__ = dispatch + WebViewBridge.__push__ = function (encoded) { + //we need to release native caller as soon as possible + //that's why we are wrap this on setTimeout + setTimeout(function () { + var fn = null + var decoded = decode(encoded) + Object.keys(onMessageListeners).forEach(function (onMessage) { + fn = onMessageListeners[onMessage] + fn(decoded) + }) + }, 15) + } + WebViewBridge.__fetch__ = function () { + var val = JSON.stringify(queue) + queue = [] + return val + } + WebViewBridge.send = function (input) { + queue.push(encode(input)) + setTimeout(signalNative, 15) + } + WebViewBridge.addMessageListener = function(fn) { + onMessageListeners[fn] = fn + } + WebViewBridge.removeMessageListener = function (fn) { + delete onMessageListeners[fn] } - dispatch('webviewbridge:init', window.WebViewBridge) + window.WebViewBridge = WebViewBridge + dispatch('webviewbridge:init', WebViewBridge) }()) From 268e82a96e9c90d27bf6eedc4e071b791105297e Mon Sep 17 00:00:00 2001 From: Ali Najafizadeh Date: Fri, 30 Sep 2016 01:44:36 -0400 Subject: [PATCH 24/77] wip for android --- .../WebViewBridgeManager.java | 53 ++- .../WebViewBridgePackage.java | 2 +- examples/rpc/android/app/build.gradle | 1 + .../main/java/com/rpc/MainApplication.java | 4 +- .../android/app/src/main/res/WebViewBridge.js | 105 ++++++ .../app/src/main/res/WebViewBridgeRPC.js | 141 +++++++ examples/rpc/android/settings.gradle | 3 +- examples/rpc/app.js | 79 ++++ examples/rpc/index.android.js | 49 +-- examples/rpc/index.ios.js | 78 +--- lib/WebViewBridge/index.android.js | 354 +++++++++++++----- 11 files changed, 636 insertions(+), 233 deletions(-) create mode 100644 examples/rpc/android/app/src/main/res/WebViewBridge.js create mode 100644 examples/rpc/android/app/src/main/res/WebViewBridgeRPC.js create mode 100644 examples/rpc/app.js diff --git a/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgeManager.java b/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgeManager.java index 8988bb7b..f3c9d757 100644 --- a/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgeManager.java +++ b/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgeManager.java @@ -1,12 +1,16 @@ package com.github.alinz.reactnativewebviewbridge; +import android.content.Context; import android.webkit.WebView; import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.views.webview.ReactWebViewManager; import com.facebook.react.uimanager.annotations.ReactProp; +import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.Map; @@ -15,9 +19,17 @@ public class WebViewBridgeManager extends ReactWebViewManager { private static final String REACT_CLASS = "RCTWebViewBridge"; - public static final int COMMAND_SEND_TO_BRIDGE = 101; - public static final int COMMAND_INJECT_WEBVIEW_BRIDGE = 102; - public static final int COMMAND_INJECT_RPC = 103; + public static final int COMMAND_INJECT_WEBVIEW_BRIDGE = 101; + public static final int COMMAND_INJECT_RPC = 102; + public static final int COMMAND_SEND_TO_BRIDGE = 103; + + private ReactApplicationContext reactApplicationContext; + + public WebViewBridgeManager(ReactApplicationContext reactApplicationContext) { + super(); + //we need to know the context because we need to load files from asset + this.reactApplicationContext = reactApplicationContext; + } @Override public String getName() { @@ -31,6 +43,8 @@ Map getCommandsMap() { Map commandsMap = super.getCommandsMap(); commandsMap.put("sendToBridge", COMMAND_SEND_TO_BRIDGE); + commandsMap.put("injectWebViewBridge", COMMAND_INJECT_WEBVIEW_BRIDGE); + commandsMap.put("injectRPC", COMMAND_INJECT_RPC); return commandsMap; } @@ -61,12 +75,41 @@ public void receiveCommand(WebView root, int commandId, @Nullable ReadableArray } } - private void injectWebViewBridgeScript(WebView root) { + private static String inputStreamToString(InputStream input) throws IOException { + StringBuilder builder = new StringBuilder(); + int ch; + while((ch = input.read()) != -1){ + builder.append((char)ch); + } + input.close(); + return builder.toString(); + } + private static String loadAsset(String filename, final Context context) { + String output = null; + + try { + InputStream inputStream = context.getAssets().open(filename); + output = inputStreamToString(inputStream); + } catch (IOException e) { + e.printStackTrace(); + } + + return output; } - private void injectWebViewBridgeRPCScript(WebView root) { + private void injectWebViewBridgeScript(WebView root) { + String injectContent = loadAsset("WebViewBridge.js", this.reactApplicationContext); + if (injectContent != null) { + evaluateJavascript(root, injectContent); + } + } + private void injectWebViewBridgeRPCScript(WebView root) { + String injectContent = loadAsset("WebViewBridgeRPC.js", this.reactApplicationContext); + if (injectContent != null) { + evaluateJavascript(root, injectContent); + } } private void sendToBridge(WebView root, String message) { diff --git a/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgePackage.java b/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgePackage.java index 1e189c4d..de48b193 100644 --- a/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgePackage.java +++ b/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgePackage.java @@ -19,7 +19,7 @@ public List createNativeModules(ReactApplicationContext reactAppli @Override public List createViewManagers(ReactApplicationContext reactApplicationContext) { return Arrays.asList( - new WebViewBridgeManager() + new WebViewBridgeManager(reactApplicationContext) ); } diff --git a/examples/rpc/android/app/build.gradle b/examples/rpc/android/app/build.gradle index 8ba8b8ac..c34cb8df 100644 --- a/examples/rpc/android/app/build.gradle +++ b/examples/rpc/android/app/build.gradle @@ -128,6 +128,7 @@ android { dependencies { compile fileTree(dir: "libs", include: ["*.jar"]) compile "com.android.support:appcompat-v7:23.0.1" + compile project(':react-native-webview-bridge') compile "com.facebook.react:react-native:+" // From node_modules } diff --git a/examples/rpc/android/app/src/main/java/com/rpc/MainApplication.java b/examples/rpc/android/app/src/main/java/com/rpc/MainApplication.java index 774c206f..ad076dfc 100644 --- a/examples/rpc/android/app/src/main/java/com/rpc/MainApplication.java +++ b/examples/rpc/android/app/src/main/java/com/rpc/MainApplication.java @@ -8,6 +8,7 @@ import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactPackage; import com.facebook.react.shell.MainReactPackage; +import com.github.alinz.reactnativewebviewbridge.WebViewBridgePackage; import java.util.Arrays; import java.util.List; @@ -23,7 +24,8 @@ protected boolean getUseDeveloperSupport() { @Override protected List getPackages() { return Arrays.asList( - new MainReactPackage() + new MainReactPackage(), + new WebViewBridgePackage() ); } }; diff --git a/examples/rpc/android/app/src/main/res/WebViewBridge.js b/examples/rpc/android/app/src/main/res/WebViewBridge.js new file mode 100644 index 00000000..92ed4b84 --- /dev/null +++ b/examples/rpc/android/app/src/main/res/WebViewBridge.js @@ -0,0 +1,105 @@ +(function () { + var WebViewBridge = window.WebViewBridge ? window.WebViewBridge : {} + var queue = [] + var onMessageListeners = {} + + //base64 encode and decode polyfil + //modified version of https://github.com/davidchambers/Base64.js + //you can replace the b64Encode and b64Decode for atob and btoa. + var b64ch = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' + function b64Encode(input) { + var output = '' + for ( + var block, charCode, idx = 0, map = b64ch; + input.charAt(idx | 0) || (map = '=', idx % 1); + output += map.charAt(63 & block >> 8 - idx % 1 * 8) + ) { + charCode = input.charCodeAt(idx += 3/4) + block = block << 8 | charCode + } + return output + } + + function b64Decode(input) { + var str = input.replace(/=+$/, '') + var output = '' + for ( + var bc = 0, bs, buffer, idx = 0; + buffer = str.charAt(idx++); + ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, + bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0 + ) { + buffer = b64ch.indexOf(buffer) + } + return output + } + + function encode(input) { + if (typeof input !== 'string') { + try { + input = JSON.stringify(input) + } catch(e) {} + } + input = unescape(encodeURIComponent(input)) + return b64Encode(input) + } + + function decode(input) { + var result = decodeURIComponent(escape(b64Decode(input))) + try { + result = JSON.parse(result) + } catch(e){} + return result + } + + function signalNative() { + if (WebViewBridge.nativeAndroidSend) { + WebViewBridge.nativeAndroidSend(WebViewBridge.__fetch__()) + } else { + window.location = 'rnwb://message' + new Date().getTime() + } + } + + function dispatch(name, value) { + var event = new CustomEvent(name, { + detail: value, + bubbles: true + }); + + setTimeout(function () { + window.document.dispatchEvent(event) + }, 15) + } + + WebViewBridge.__dispatch__ = dispatch + WebViewBridge.__push__ = function (encoded) { + //we need to release native caller as soon as possible + //that's why we are wrap this on setTimeout + setTimeout(function () { + var fn = null + var decoded = decode(encoded) + Object.keys(onMessageListeners).forEach(function (onMessage) { + fn = onMessageListeners[onMessage] + fn(decoded) + }) + }, 15) + } + WebViewBridge.__fetch__ = function () { + var val = JSON.stringify(queue) + queue = [] + return val + } + WebViewBridge.send = function (input) { + queue.push(encode(input)) + setTimeout(signalNative, 15) + } + WebViewBridge.addMessageListener = function(fn) { + onMessageListeners[fn] = fn + } + WebViewBridge.removeMessageListener = function (fn) { + delete onMessageListeners[fn] + } + + window.WebViewBridge = WebViewBridge + dispatch('webviewbridge:init', WebViewBridge) +}()) diff --git a/examples/rpc/android/app/src/main/res/WebViewBridgeRPC.js b/examples/rpc/android/app/src/main/res/WebViewBridgeRPC.js new file mode 100644 index 00000000..fb0e8205 --- /dev/null +++ b/examples/rpc/android/app/src/main/res/WebViewBridgeRPC.js @@ -0,0 +1,141 @@ +(function () { + 'use strict' + + var ids = 0 + var invokers = {} + var responseCallbacks = {} + + //invoker has accept 2 arguments + //fn(args, result). result is a function which accept an argument. + //that argument is being used to send to caller as a result value. + //by using this function, we can support both sync and async operation + function register(sender, name, fn) { + invokers[name] = function (id, args) { + fn(args, function (result) { + sender({ + id: id, + type: 'resolve', + result: result + }) + }, function (err) { + sender({ + id: id, + type: 'reject', + result: err + }) + }) + } + } + + function invoke(sender, name, args, opt, callback) { + var id = ++ids + var target = { + callback: callback + } + + if (opt) { + opt = {} + } + + if (!opt.timeout) { + opt.timeout = 0 + } + + if (opt.timeout) { + target.timeoutHandler = setTimeout(function () { + onReject(id, 'timeout') + }, opt.timeout) + } + + responseCallbacks[id] = target + + sender({ + id: id, + type: 'invoke', + name: name, + args: args + }) + } + + function onInvoke(payload) { + var invoker = invokers[payload.name] + if (invoker) { + setTimeout(function () { + invoker(payload.id, payload.args) + }, 15) + } + } + + function onResolve(id, result) { + var target = responseCallbacks[id] + if (target) { + clearTimeout(target.timeoutHandler) + delete responseCallbacks[id] + setTimeout(function () { + target.callback(null, result) + }, 15) + } + } + + function onReject(id, result) { + var target = responseCallbacks[id] + if (target) { + clearTimeout(target.timeoutHandler) + delete responseCallbacks[id] + setTimeout(function () { + target(result, null) + }, 15) + } + } + + function onMessage(payload) { + if (typeof payload === 'string') { + return + } + + // there are two types of payload + // invoke: { type: 'payload', id, name, args } + // resolve: { type: 'resolve', id, result } + // reject: { type: 'reject', id, result } + switch(payload.type) { + case 'invoke': + onInvoke(payload) + break + case 'resolve': + onResolve(payload.id, payload.result) + break + case 'reject': + onReject(payload.id, payload.result) + default: + //ignore + } + } + + //init this method register and attaches rpc to WebViewBridge. + //you can either check whether WebViewBridge.rpc is availebe or + //simply register to `webviewbridge:rpc` event. + function init(WebViewBridge) { + var rpc = {} + var sender = WebViewBridge.send + + window.removeEventListener('webviewbridge:init', init) + WebViewBridge.addMessageListener(onMessage) + + rpc.register = function (name, fn) { + register(sender, name, fn) + } + + rpc.invoke = function (name, args, opt, callback) { + invoke(sender, name, args, opt, callback) + } + + WebViewBridge.rpc = rpc + WebViewBridge.__dispatch__('webviewbridge:rpc', rpc) + } + + if (window.WebViewBridge) { + init(window.WebViewBridge) + } else { + window.addEventListener('webviewbridge:init', init) + } +}()) diff --git a/examples/rpc/android/settings.gradle b/examples/rpc/android/settings.gradle index 756c08a8..62831da7 100644 --- a/examples/rpc/android/settings.gradle +++ b/examples/rpc/android/settings.gradle @@ -1,3 +1,4 @@ rootProject.name = 'rpc' -include ':app' +include ':app', ':react-native-webview-bridge' +project(':react-native-webview-bridge').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webview-bridge/android') diff --git a/examples/rpc/app.js b/examples/rpc/app.js new file mode 100644 index 00000000..ab25949b --- /dev/null +++ b/examples/rpc/app.js @@ -0,0 +1,79 @@ +/** + * Sample React Native App + * https://github.com/facebook/react-native + * @flow + */ + +import React, { Component } from 'react'; +import { + StyleSheet, + Text, + View +} from 'react-native'; + +import { WebViewBridge, WebViewBridgeRPC } from 'react-native-webview-bridge' + +const injectScript = ` + (function () { + if (window.WebViewBridge && window.WebViewBridge.rpc) { + var rpc = WebViewBridge.rpc + + rpc.invoke('onNativeCall', null, { timeout: 0 }, function (err, resp) { + alert(resp) + }) + + rpc.register('onWebViewCall', function (args, resolve, reject) { + resolve('this is web call') + }) + + // WebViewBridge.addMessageListener(function (payload) { + // alert(payload.message) + // }); + // + // WebViewBridge.send({ message: 'This is working' }); + } + }()); +`; + +export default class app extends Component { + constructor(props, context) { + super(props, context) + + this.bridgeRef = null + } + + componentDidMount() { + this.bridgeRef.register('onNativeCall', this.onNativeCall) + } + + onNativeCall = (args, resolve, reject) => { + resolve('native call') + + setTimeout(() => { + this.bridgeRef.invoke('onWebViewCall', null, { timeout: 0 }, function (err, resp) { + console.log(err, resp) + }) + }, 3000) + } + + render() { + return ( + this.bridgeRef = ref} + source={{uri: 'https://github.com/facebook/react-native'}} + style={{marginTop: 20}} + javaScriptEnabled={true} + injectedJavaScript={injectScript} + /> + ); + } +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: '#F5FCFF', + } +}); diff --git a/examples/rpc/index.android.js b/examples/rpc/index.android.js index 7b23b330..f0e056d8 100644 --- a/examples/rpc/index.android.js +++ b/examples/rpc/index.android.js @@ -4,50 +4,7 @@ * @flow */ -import React, { Component } from 'react'; -import { - AppRegistry, - StyleSheet, - Text, - View -} from 'react-native'; +import { AppRegistry } from 'react-native'; +import app from './app' -class rpc extends Component { - render() { - return ( - - - Welcome to React Native! - - - To get started, edit index.android.js - - - Double tap R on your keyboard to reload,{'\n'} - Shake or press menu button for dev menu - - - ); - } -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - backgroundColor: '#F5FCFF', - }, - welcome: { - fontSize: 20, - textAlign: 'center', - margin: 10, - }, - instructions: { - textAlign: 'center', - color: '#333333', - marginBottom: 5, - }, -}); - -AppRegistry.registerComponent('rpc', () => rpc); +AppRegistry.registerComponent('rpc', () => app); diff --git a/examples/rpc/index.ios.js b/examples/rpc/index.ios.js index 5b76b355..f0e056d8 100644 --- a/examples/rpc/index.ios.js +++ b/examples/rpc/index.ios.js @@ -4,79 +4,7 @@ * @flow */ -import React, { Component } from 'react'; -import { - AppRegistry, - StyleSheet, - Text, - View -} from 'react-native'; +import { AppRegistry } from 'react-native'; +import app from './app' -import { WebViewBridge, WebViewBridgeRPC } from 'react-native-webview-bridge' - -const injectScript = ` - (function () { - if (window.WebViewBridge && window.WebViewBridge.rpc) { - var rpc = WebViewBridge.rpc - - rpc.invoke('onNativeCall', null, { timeout: 0 }, function (err, resp) { - alert(resp) - }) - - rpc.register('onWebViewCall', function (args, resolve, reject) { - resolve('this is web call') - }) - - // WebViewBridge.addMessageListener(function (payload) { - // alert(payload.message) - // }); - // - // WebViewBridge.send({ message: 'This is working' }); - } - }()); -`; - -class rpc extends Component { - constructor(props, context) { - super(props, context) - - this.bridgeRef = null - } - - componentDidMount() { - this.bridgeRef.register('onNativeCall', this.onNativeCall) - } - - onNativeCall = (args, resolve, reject) => { - resolve('native call') - - setTimeout(() => { - this.bridgeRef.invoke('onWebViewCall', null, { timeout: 0 }, function (err, resp) { - console.log(err, resp) - }) - }, 3000) - } - - render() { - return ( - this.bridgeRef = ref} - source={{uri: 'https://github.com/facebook/react-native'}} - style={{marginTop: 20}} - javaScriptEnabled={true} - injectedJavaScript={injectScript} - /> - ); - } -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - backgroundColor: '#F5FCFF', - } -}); - -AppRegistry.registerComponent('rpc', () => rpc); +AppRegistry.registerComponent('rpc', () => app); diff --git a/lib/WebViewBridge/index.android.js b/lib/WebViewBridge/index.android.js index bdfe8180..e0044823 100644 --- a/lib/WebViewBridge/index.android.js +++ b/lib/WebViewBridge/index.android.js @@ -2,132 +2,243 @@ * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * - * Copyright (c) 2016-present, Ali Najafizadeh - * All rights reserved. - * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * - * @providesModule WebViewBridge + * @providesModule WebView */ 'use strict'; -var React = require('react'); -var ReactNative = require('react-native'); -var invariant = require('invariant'); -var keyMirror = require('keymirror'); -var resolveAssetSource = require('react-native/Libraries/Image/resolveAssetSource'); - -var { - ReactNativeViewAttributes, - UIManager, +import React, { PropTypes } from 'react' +import { + ActivityIndicator, EdgeInsetsPropType, StyleSheet, Text, + UIManager, View, - WebView, + ScrollView, + NativeModules, requireNativeComponent, - DeviceEventEmitter, - NativeModules: { - WebViewBridgeManager - } -} = ReactNative; -var { PropTypes } = React; + findNodeHandle +} from 'react-native' + +import invariant from 'fbjs/lib/invariant' +import keyMirror from 'fbjs/lib/keyMirror' + +import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource'; +import processDecelerationRate from 'react-native/Libraries/Components/ScrollView/processDecelerationRate' +import deprecatedPropType from 'react-native/Libraries/Utilities/deprecatedPropType' + +import { encode, decode } from '../core/base64' -var RCT_WEBVIEWBRIDGE_REF = 'webviewbridge'; +const { WebViewBridgeManager } = NativeModules -var WebViewBridgeState = keyMirror({ +var RCT_WEBVIEW_REF = 'webview'; + +var WebViewState = keyMirror({ IDLE: null, LOADING: null, ERROR: null, }); -var RCTWebViewBridge = requireNativeComponent('RCTWebViewBridge', WebViewBridge); +var defaultRenderLoading = () => ( + + + +); /** * Renders a native WebView. */ -var WebViewBridge = React.createClass({ +class WebView extends React.Component { + static propTypes = { + ...View.propTypes, + renderError: PropTypes.func, + renderLoading: PropTypes.func, + onLoad: PropTypes.func, + onLoadEnd: PropTypes.func, + onLoadStart: PropTypes.func, + onError: PropTypes.func, + automaticallyAdjustContentInsets: PropTypes.bool, + contentInset: EdgeInsetsPropType, + onNavigationStateChange: PropTypes.func, + onContentSizeChange: PropTypes.func, + startInLoadingState: PropTypes.bool, // force WebView to show loadingView on first load + style: View.propTypes.style, + + html: deprecatedPropType( + PropTypes.string, + 'Use the `source` prop instead.' + ), + + url: deprecatedPropType( + PropTypes.string, + 'Use the `source` prop instead.' + ), + + /** + * Loads static html or a uri (with optional headers) in the WebView. + */ + source: PropTypes.oneOfType([ + PropTypes.shape({ + /* + * The URI to load in the WebView. Can be a local or remote file. + */ + uri: PropTypes.string, + /* + * The HTTP Method to use. Defaults to GET if not specified. + * NOTE: On Android, only GET and POST are supported. + */ + method: PropTypes.oneOf(['GET', 'POST']), + /* + * Additional HTTP headers to send with the request. + * NOTE: On Android, this can only be used with GET requests. + */ + headers: PropTypes.object, + /* + * The HTTP body to send with the request. This must be a valid + * UTF-8 string, and will be sent exactly as specified, with no + * additional encoding (e.g. URL-escaping or base64) applied. + * NOTE: On Android, this can only be used with POST requests. + */ + body: PropTypes.string, + }), + PropTypes.shape({ + /* + * A static HTML page to display in the WebView. + */ + html: PropTypes.string, + /* + * The base URL to be used for any relative links in the HTML. + */ + baseUrl: PropTypes.string, + }), + /* + * Used internally by packager. + */ + PropTypes.number, + ]), + + /** + * Used on Android only, JS is enabled by default for WebView on iOS + * @platform android + */ + javaScriptEnabled: PropTypes.bool, + + /** + * Used on Android only, controls whether DOM Storage is enabled or not + * @platform android + */ + domStorageEnabled: PropTypes.bool, - propTypes: { - ...RCTWebViewBridge.propTypes, + /** + * Sets the JS to be injected when the webpage loads. + */ + injectedJavaScript: PropTypes.string, /** - * Will be called once the message is being sent from webview + * Sets whether the webpage scales to fit the view and the user can change the scale. */ + scalesPageToFit: PropTypes.bool, + + /** + * Sets the user-agent for this WebView. The user-agent can also be set in native using + * WebViewConfig. This prop will overwrite that config. + */ + userAgent: PropTypes.string, + + /** + * Used to locate this view in end-to-end tests. + */ + testID: PropTypes.string, + + /** + * Determines whether HTML5 audio & videos require the user to tap before they can + * start playing. The default value is `false`. + */ + mediaPlaybackRequiresUserAction: PropTypes.bool, + onBridgeMessage: PropTypes.func, - }, + rpc: PropTypes.bool + }; - getInitialState: function() { - return { - viewState: WebViewBridgeState.IDLE, - lastErrorEvent: null, - startInLoadingState: true, - }; - }, + static defaultProps = { + javaScriptEnabled : true, + scalesPageToFit: true, + }; - - componentWillMount: function() { - DeviceEventEmitter.addListener("webViewBridgeMessage", (body) => { - const { onBridgeMessage } = this.props; - const message = body.message; - if (onBridgeMessage) { - onBridgeMessage(message); - } - }); + state = { + viewState: WebViewState.IDLE, + lastErrorEvent: null, + startInLoadingState: true, + }; + componentWillMount() { if (this.props.startInLoadingState) { - this.setState({viewState: WebViewBridgeState.LOADING}); + this.setState({viewState: WebViewState.LOADING}); } - }, + } - render: function() { + render() { var otherView = null; - if (this.state.viewState === WebViewBridgeState.LOADING) { - otherView = this.props.renderLoading && this.props.renderLoading(); - } else if (this.state.viewState === WebViewBridgeState.ERROR) { + if (this.state.viewState === WebViewState.LOADING) { + otherView = (this.props.renderLoading || defaultRenderLoading)(); + } else if (this.state.viewState === WebViewState.ERROR) { var errorEvent = this.state.lastErrorEvent; otherView = this.props.renderError && this.props.renderError( errorEvent.domain, errorEvent.code, errorEvent.description); - } else if (this.state.viewState !== WebViewBridgeState.IDLE) { + } else if (this.state.viewState !== WebViewState.IDLE) { console.error('RCTWebViewBridge invalid state encountered: ' + this.state.loading); } var webViewStyles = [styles.container, this.props.style]; - if (this.state.viewState === WebViewBridgeState.LOADING || - this.state.viewState === WebViewBridgeState.ERROR) { + if (this.state.viewState === WebViewState.LOADING || + this.state.viewState === WebViewState.ERROR) { // if we're in either LOADING or ERROR states, don't show the webView webViewStyles.push(styles.hidden); } - var {javaScriptEnabled, domStorageEnabled} = this.props; - if (this.props.javaScriptEnabledAndroid) { - console.warn('javaScriptEnabledAndroid is deprecated. Use javaScriptEnabled instead'); - javaScriptEnabled = this.props.javaScriptEnabledAndroid; - } - if (this.props.domStorageEnabledAndroid) { - console.warn('domStorageEnabledAndroid is deprecated. Use domStorageEnabled instead'); - domStorageEnabled = this.props.domStorageEnabledAndroid; + var source = this.props.source || {}; + if (this.props.html) { + source.html = this.props.html; + } else if (this.props.url) { + source.uri = this.props.url; } - let {source, ...props} = {...this.props}; + if (source.method === 'POST' && source.headers) { + console.warn('WebView: `source.headers` is not supported when using POST.'); + } else if (source.method === 'GET' && source.body) { + console.warn('WebView: `source.body` is not supported when using GET.'); + } var webView = ; return ( @@ -136,89 +247,116 @@ var WebViewBridge = React.createClass({ {otherView} ); - }, - - onMessage(event) { - if (this.props.onBridgeMessage != null && event.nativeEvent != null) { - this.props.onBridgeMessage(event.nativeEvent.message) - } - }, + } - goForward: function() { + goForward = () => { UIManager.dispatchViewManagerCommand( - this.getWebViewBridgeHandle(), + this.getWebViewHandle(), UIManager.RCTWebViewBridge.Commands.goForward, null ); - }, + }; - goBack: function() { + goBack = () => { UIManager.dispatchViewManagerCommand( - this.getWebViewBridgeHandle(), + this.getWebViewHandle(), UIManager.RCTWebViewBridge.Commands.goBack, null ); - }, + }; - reload: function() { + reload = () => { UIManager.dispatchViewManagerCommand( - this.getWebViewBridgeHandle(), + this.getWebViewHandle(), UIManager.RCTWebViewBridge.Commands.reload, null ); - }, + }; - sendToBridge: function (message: string) { + stopLoading = () => { UIManager.dispatchViewManagerCommand( - this.getWebViewBridgeHandle(), - UIManager.RCTWebViewBridge.Commands.sendToBridge, - [message] + this.getWebViewHandle(), + UIManager.RCTWebViewBridge.Commands.stopLoading, + null ); - }, + }; /** * We return an event with a bunch of fields including: * url, title, loading, canGoBack, canGoForward */ - updateNavigationState: function(event) { + updateNavigationState = (event) => { if (this.props.onNavigationStateChange) { this.props.onNavigationStateChange(event.nativeEvent); } - }, + }; + + onBridgeMessage = (event: Event) => { + const { onBridgeMessage } = this.props; + const messages = event.nativeEvent.messages; + if (onBridgeMessage) { + messages.forEach((message) => { + onBridgeMessage(decode(message)); + }); + } + }; - getWebViewBridgeHandle: function() { - return ReactNative.findNodeHandle(this.refs[RCT_WEBVIEWBRIDGE_REF]); - }, + sendToBridge = (message: any) => { + WebViewBridgeManager.sendToBridge(this.getWebViewHandle(), encode(message)); + }; + + getWebViewHandle = () => { + return findNodeHandle(this.refs[RCT_WEBVIEW_REF]); + }; - onLoadingStart: function(event) { + onLoadingStart = (event) => { var onLoadStart = this.props.onLoadStart; onLoadStart && onLoadStart(event); this.updateNavigationState(event); - }, + }; - onLoadingError: function(event) { + onLoadingError = (event) => { event.persist(); // persist this event because we need to store it var {onError, onLoadEnd} = this.props; onError && onError(event); onLoadEnd && onLoadEnd(event); + console.warn('Encountered an error loading page', event.nativeEvent); this.setState({ lastErrorEvent: event.nativeEvent, - viewState: WebViewBridgeState.ERROR + viewState: WebViewState.ERROR }); - }, + }; + + onLoadingFinish = (event) => { + var { onLoad, onLoadEnd, rpc } = this.props; + + //we need to inject WebViewBridge and RPC once the page is loaded + UIManager.dispatchViewManagerCommand( + this.getWebViewHandle(), + UIManager.RCTWebViewBridge.Commands.injectWebViewBridge, + null + ); + + //check if RPC falg is also on + if (rps) { + UIManager.dispatchViewManagerCommand( + this.getWebViewHandle(), + UIManager.RCTWebViewBridge.Commands.injectRPC, + null + ); + } - onLoadingFinish: function(event) { - var {onLoad, onLoadEnd} = this.props; onLoad && onLoad(event); onLoadEnd && onLoadEnd(event); this.setState({ - viewState: WebViewBridgeState.IDLE, + viewState: WebViewState.IDLE, }); this.updateNavigationState(event); - }, -}); + }; +} +var RCTWebViewBridge = requireNativeComponent('RCTWebViewBridge', WebView); var styles = StyleSheet.create({ container: { @@ -228,6 +366,14 @@ var styles = StyleSheet.create({ height: 0, flex: 0, // disable 'flex:1' when hiding a View }, + loadingView: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + loadingProgressBar: { + height: 20, + }, }); -module.exports = WebViewBridge; \ No newline at end of file +module.exports = WebView; From 9aa1b75cf3d2eae849a70bb831fb772973b39efa Mon Sep 17 00:00:00 2001 From: Ali Najafizadeh Date: Fri, 30 Sep 2016 11:21:08 -0400 Subject: [PATCH 25/77] android code is ready --- .../JavascriptBridge.java | 8 +++--- .../WebViewBridgeManager.java | 5 ++++ lib/WebViewBridge/index.android.js | 25 ++++++++++++++----- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/android/src/main/java/com/github/alinz/reactnativewebviewbridge/JavascriptBridge.java b/android/src/main/java/com/github/alinz/reactnativewebviewbridge/JavascriptBridge.java index 5a95e901..61ddf727 100644 --- a/android/src/main/java/com/github/alinz/reactnativewebviewbridge/JavascriptBridge.java +++ b/android/src/main/java/com/github/alinz/reactnativewebviewbridge/JavascriptBridge.java @@ -21,9 +21,9 @@ public void nativeAndroidSend(String message) { WritableMap event = Arguments.createMap(); event.putString("message", message); ReactContext reactContext = (ReactContext) this.webView.getContext(); - reactContext.getJSModule(RCTEventEmitter.class).receiveEvent( - this.webView.getId(), - "topChange", - event); + reactContext + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit("WebViewBridgeMessageEvent", event); + } } diff --git a/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgeManager.java b/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgeManager.java index f3c9d757..cba0481d 100644 --- a/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgeManager.java +++ b/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgeManager.java @@ -125,6 +125,11 @@ static private void evaluateJavascript(WebView root, String javascript) { } } + @ReactProp(name = "injectedJavaScript") + public void setInjectedJavaScript(WebView root, @Nullable String injectedJavaScript) { + evaluateJavascript(root, injectedJavaScript); + } + @ReactProp(name = "allowFileAccessFromFileURLs") public void setAllowFileAccessFromFileURLs(WebView root, boolean allows) { root.getSettings().setAllowFileAccessFromFileURLs(allows); diff --git a/lib/WebViewBridge/index.android.js b/lib/WebViewBridge/index.android.js index e0044823..b11fd0d1 100644 --- a/lib/WebViewBridge/index.android.js +++ b/lib/WebViewBridge/index.android.js @@ -21,7 +21,8 @@ import { ScrollView, NativeModules, requireNativeComponent, - findNodeHandle + findNodeHandle, + DeviceEventEmitter } from 'react-native' import invariant from 'fbjs/lib/invariant' @@ -162,6 +163,8 @@ class WebView extends React.Component { */ mediaPlaybackRequiresUserAction: PropTypes.bool, + allowUniversalAccessFromFileURLs: PropTypes.bool, + allowFileAccessFromFileURLs: PropTypes.bool, onBridgeMessage: PropTypes.func, rpc: PropTypes.bool }; @@ -178,11 +181,17 @@ class WebView extends React.Component { }; componentWillMount() { + DeviceEventEmitter.addListener('WebViewBridgeMessageEvent', this.onBridgeMessage); + if (this.props.startInLoadingState) { this.setState({viewState: WebViewState.LOADING}); } } + componentWillUnmount() { + DeviceEventEmitter.removeListener('WebViewBridgeMessageEvent', this.onBridgeMessage); + } + render() { var otherView = null; @@ -237,8 +246,8 @@ class WebView extends React.Component { onLoadingError={this.onLoadingError} testID={this.props.testID} mediaPlaybackRequiresUserAction={this.props.mediaPlaybackRequiresUserAction} - onBridgeMessage={this.onBridgeMessage} - rpc={!!this.props.rpc} + allowFileAccessFromFileURLs={this.props.allowFileAccessFromFileURLs} + allowUniversalAccessFromFileURLs={this.props.allowUniversalAccessFromFileURLs} />; return ( @@ -293,7 +302,7 @@ class WebView extends React.Component { onBridgeMessage = (event: Event) => { const { onBridgeMessage } = this.props; - const messages = event.nativeEvent.messages; + const messages = JSON.parse(event.message) if (onBridgeMessage) { messages.forEach((message) => { onBridgeMessage(decode(message)); @@ -302,7 +311,11 @@ class WebView extends React.Component { }; sendToBridge = (message: any) => { - WebViewBridgeManager.sendToBridge(this.getWebViewHandle(), encode(message)); + UIManager.dispatchViewManagerCommand( + this.getWebViewHandle(), + UIManager.RCTWebViewBridge.Commands.sendToBridge, + [encode(message)] + ); }; getWebViewHandle = () => { @@ -339,7 +352,7 @@ class WebView extends React.Component { ); //check if RPC falg is also on - if (rps) { + if (rpc) { UIManager.dispatchViewManagerCommand( this.getWebViewHandle(), UIManager.RCTWebViewBridge.Commands.injectRPC, From 283b59a6a8a029774d2627e6e2542d8c4d54fe46 Mon Sep 17 00:00:00 2001 From: Ali Najafizadeh Date: Fri, 30 Sep 2016 11:21:47 -0400 Subject: [PATCH 26/77] made the code robust by using listeners --- examples/rpc/app.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/examples/rpc/app.js b/examples/rpc/app.js index ab25949b..224d456d 100644 --- a/examples/rpc/app.js +++ b/examples/rpc/app.js @@ -15,7 +15,8 @@ import { WebViewBridge, WebViewBridgeRPC } from 'react-native-webview-bridge' const injectScript = ` (function () { - if (window.WebViewBridge && window.WebViewBridge.rpc) { + + function test() { var rpc = WebViewBridge.rpc rpc.invoke('onNativeCall', null, { timeout: 0 }, function (err, resp) { @@ -25,12 +26,14 @@ const injectScript = ` rpc.register('onWebViewCall', function (args, resolve, reject) { resolve('this is web call') }) + } - // WebViewBridge.addMessageListener(function (payload) { - // alert(payload.message) - // }); - // - // WebViewBridge.send({ message: 'This is working' }); + if (window.WebViewBridge && window.WebViewBridge.rpc) { + test() + } else { + window.addEventListener('webviewbridge:rpc', function () { + test() + }) } }()); `; From 0feaf014b37896bb2b61acd1b6f7b7207fbbf885 Mon Sep 17 00:00:00 2001 From: Ali Najafizadeh Date: Fri, 30 Sep 2016 11:22:31 -0400 Subject: [PATCH 27/77] moved base WebViewBridge and WebViewBridgeRPC to assets folder --- .../rpc/android/app/src/main/{res => assets}/WebViewBridge.js | 0 .../rpc/android/app/src/main/{res => assets}/WebViewBridgeRPC.js | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename examples/rpc/android/app/src/main/{res => assets}/WebViewBridge.js (100%) rename examples/rpc/android/app/src/main/{res => assets}/WebViewBridgeRPC.js (100%) diff --git a/examples/rpc/android/app/src/main/res/WebViewBridge.js b/examples/rpc/android/app/src/main/assets/WebViewBridge.js similarity index 100% rename from examples/rpc/android/app/src/main/res/WebViewBridge.js rename to examples/rpc/android/app/src/main/assets/WebViewBridge.js diff --git a/examples/rpc/android/app/src/main/res/WebViewBridgeRPC.js b/examples/rpc/android/app/src/main/assets/WebViewBridgeRPC.js similarity index 100% rename from examples/rpc/android/app/src/main/res/WebViewBridgeRPC.js rename to examples/rpc/android/app/src/main/assets/WebViewBridgeRPC.js From 64e1ef93cc1d77d3fff4347e91dbd374b916496b Mon Sep 17 00:00:00 2001 From: Ali Najafizadeh Date: Fri, 30 Sep 2016 11:23:11 -0400 Subject: [PATCH 28/77] upgraded example project to latest gradle --- examples/rpc/android/build.gradle | 2 +- examples/rpc/android/gradle/wrapper/gradle-wrapper.properties | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/rpc/android/build.gradle b/examples/rpc/android/build.gradle index fcba4c58..7ab08063 100644 --- a/examples/rpc/android/build.gradle +++ b/examples/rpc/android/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:1.3.1' + classpath 'com.android.tools.build:gradle:2.2.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/examples/rpc/android/gradle/wrapper/gradle-wrapper.properties b/examples/rpc/android/gradle/wrapper/gradle-wrapper.properties index b9fbfaba..6ef9a9a7 100644 --- a/examples/rpc/android/gradle/wrapper/gradle-wrapper.properties +++ b/examples/rpc/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Fri Sep 30 01:45:24 EDT 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip From 2d7bae88d38f6d68a742f8eafcf1aaf8bfbb027f Mon Sep 17 00:00:00 2001 From: eladgel Date: Sun, 9 Oct 2016 11:52:05 +0300 Subject: [PATCH 29/77] trying --- .idea/.name | 1 + .idea/encodings.xml | 6 + .idea/misc.xml | 48 +++++ .idea/modules.xml | 8 + .idea/react-native-webview-bridge.iml | 8 + .idea/vcs.xml | 6 + .idea/workspace.xml | 169 ++++++++++++++++++ README.md | 15 +- .../JavascriptBridge.java | 29 +-- .../WebViewBridgeManager.java | 145 +++++++-------- ios/RCTWebViewBridge.m | 9 +- ios/RCTWebViewBridgeManager.m | 1 + .../project.pbxproj | 2 + package.json | 5 +- webview-bridge/index.android.js | 26 +-- webview-bridge/index.ios.js | 6 +- 16 files changed, 366 insertions(+), 118 deletions(-) create mode 100644 .idea/.name create mode 100644 .idea/encodings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/react-native-webview-bridge.iml create mode 100644 .idea/vcs.xml create mode 100644 .idea/workspace.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 00000000..b4d80f55 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +react-native-webview-bridge \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 00000000..97626ba4 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..cdc7de13 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + CSS + + + + + CoffeeScript + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..d31bbad0 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/react-native-webview-bridge.iml b/.idea/react-native-webview-bridge.iml new file mode 100644 index 00000000..579af63c --- /dev/null +++ b/.idea/react-native-webview-bridge.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..94a25f7f --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 00000000..adf8711d --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1476002628408 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 4b65d602..a7edcdff 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +## Please take a look at this [issue](https://github.com/alinz/react-native-webview-bridge/issues/109) first + # React Native WebView Javascript Bridge I have been testing and reading a lot of way to safely create a bridge between react-native and webview. I'm happy to announced that the wait is over and from **React-Native 0.20 and above**, the bridge is fully functional. @@ -7,7 +9,7 @@ I have been testing and reading a lot of way to safely create a bridge between r In order to use this extension, you have to do the following steps: -in your react-native project, run `npm install react-native-webview-bridge` +in your react-native project, run `npm install react-native-webview-bridge --save` ### iOS @@ -35,15 +37,15 @@ in your react-native project, run `npm install react-native-webview-bridge`

8. clean compile to make sure your project can compile and build. -### Android (Beta) +### Android -1. add the following import to `MainActivity.java` of your application +1. add the following import to `MainApplication.java` (`MainActivity.java` if RN < 0.29) of your application ```java import com.github.alinz.reactnativewebviewbridge.WebViewBridgePackage; ``` -2. add the following code to add the package to `MainActivity.java` +2. add the following code to add the package to `MainApplication.java`` (`MainActivity.java` if RN < 0.29) ```java protected List getPackages() { @@ -96,6 +98,11 @@ the message must be in string. because this is the only way to send data back an #### onBridgeMessage this is a prop that needs to be a function. it will be called once a message is received from webview. The type of received message is also in string. +#### allowFileAccessFromFileURLs (Android only) +this is a prop that allows locally loaded pages via file:// to access other file:// resources. Pass-thru to corresponding [setting](https://developer.android.com/reference/android/webkit/WebSettings.html#setAllowFileAccessFromFileURLs(boolean)) in WebView. Default is `false` for Android 4.1 and above. + +#### allowUniversalAccessFromFileURLs (Android only) +this is a prop that allows locally loaded pages via file:// to access resources in any origin. Pass-thru to corresponding [setting](https://developer.android.com/reference/android/webkit/WebSettings.html#setAllowUniversalAccessFromFileURLs(boolean)) in WebView. Default is `false` for Android 4.1 and above. ## Bridge Script diff --git a/android/src/main/java/com/github/alinz/reactnativewebviewbridge/JavascriptBridge.java b/android/src/main/java/com/github/alinz/reactnativewebviewbridge/JavascriptBridge.java index ebbc02b9..2ad37116 100644 --- a/android/src/main/java/com/github/alinz/reactnativewebviewbridge/JavascriptBridge.java +++ b/android/src/main/java/com/github/alinz/reactnativewebviewbridge/JavascriptBridge.java @@ -1,24 +1,29 @@ package com.github.alinz.reactnativewebviewbridge; import android.webkit.JavascriptInterface; +import android.webkit.WebView; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.WritableMap; import com.facebook.react.modules.core.DeviceEventManagerModule; +import com.facebook.react.uimanager.events.RCTEventEmitter; class JavascriptBridge { - private ReactContext context; + private WebView webView; - public JavascriptBridge(ReactContext context) { - this.context = context; - } + public JavascriptBridge(WebView webView) { + this.webView = webView; + } - @JavascriptInterface - public void send(String message) { - WritableMap params = Arguments.createMap(); - params.putString("message", message); - context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit("webViewBridgeMessage", params); - } -} + @JavascriptInterface + public void send(String message) { + WritableMap event = Arguments.createMap(); + event.putString("message", message); + ReactContext reactContext = (ReactContext) this.webView.getContext(); + reactContext.getJSModule(RCTEventEmitter.class).receiveEvent( + this.webView.getId(), + "topChange", + event); + } +} \ No newline at end of file diff --git a/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgeManager.java b/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgeManager.java index 2a4112ee..c1250163 100644 --- a/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgeManager.java +++ b/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgeManager.java @@ -2,98 +2,77 @@ import android.webkit.WebView; -import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.views.webview.ReactWebViewManager; -import com.facebook.react.views.webview.WebViewConfig; +import com.facebook.react.uimanager.annotations.ReactProp; +import java.util.ArrayList; import java.util.Map; import javax.annotation.Nullable; public class WebViewBridgeManager extends ReactWebViewManager { - private static final String REACT_CLASS = "RCTWebViewBridge"; - - public static final int COMMAND_INJECT_BRIDGE_SCRIPT = 100; - public static final int COMMAND_SEND_TO_BRIDGE = 101; - - private boolean initializedBridge; - - public WebViewBridgeManager() { - super(); - initializedBridge = false; - } - - public WebViewBridgeManager(WebViewConfig webViewConfig) { - super(webViewConfig); - initializedBridge = false; - } - - @Override - public String getName() { - return REACT_CLASS; - } - - @Override - public @Nullable Map getCommandsMap() { - Map commandsMap = super.getCommandsMap(); - - commandsMap.put("injectBridgeScript", COMMAND_INJECT_BRIDGE_SCRIPT); - commandsMap.put("sendToBridge", COMMAND_SEND_TO_BRIDGE); - - return commandsMap; - } - - @Override - public void receiveCommand(WebView root, int commandId, @Nullable ReadableArray args) { - super.receiveCommand(root, commandId, args); - - switch (commandId) { - case COMMAND_INJECT_BRIDGE_SCRIPT: - injectBridgeScript(root); - break; - case COMMAND_SEND_TO_BRIDGE: - sendToBridge(root, args.getString(0)); - break; - default: - //do nothing!!!! + private static final String REACT_CLASS = "RCTWebViewBridge"; + + public static final int COMMAND_SEND_TO_BRIDGE = 101; + + @Override + public String getName() { + return REACT_CLASS; + } + + @Override + public + @Nullable + Map getCommandsMap() { + Map commandsMap = super.getCommandsMap(); + + commandsMap.put("sendToBridge", COMMAND_SEND_TO_BRIDGE); + + return commandsMap; + } + + @Override + protected WebView createViewInstance(ThemedReactContext reactContext) { + WebView root = super.createViewInstance(reactContext); + root.addJavascriptInterface(new JavascriptBridge(root), "WebViewBridge"); + return root; } - } - - private void sendToBridge(WebView root, String message) { - //root.loadUrl("javascript:(function() {\n" + script + ";\n})();"); - String script = "WebViewBridge.onMessage('" + message + "');"; - WebViewBridgeManager.evaluateJavascript(root, script); - } - - private void injectBridgeScript(WebView root) { - //this code needs to be called once per context - if (!initializedBridge) { - root.addJavascriptInterface(new JavascriptBridge((ReactContext) root.getContext()), "WebViewBridgeAndroid"); - initializedBridge = true; - root.reload(); + + @Override + public void receiveCommand(WebView root, int commandId, @Nullable ReadableArray args) { + super.receiveCommand(root, commandId, args); + + switch (commandId) { + case COMMAND_SEND_TO_BRIDGE: + sendToBridge(root, args.getString(0)); + break; + default: + //do nothing!!!! + } + } + + private void sendToBridge(WebView root, String message) { + String script = "WebViewBridge.onMessage('" + message + "');"; + WebViewBridgeManager.evaluateJavascript(root, script); + } + + static private void evaluateJavascript(WebView root, String javascript) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { + root.evaluateJavascript(javascript, null); + } else { + root.loadUrl("javascript:" + javascript); + } + } + + @ReactProp(name = "allowFileAccessFromFileURLs") + public void setAllowFileAccessFromFileURLs(WebView root, boolean allows) { + root.getSettings().setAllowFileAccessFromFileURLs(allows); } - // this code needs to be executed everytime a url changes. - WebViewBridgeManager.evaluateJavascript(root, "" - + "(function() {" - + "if (window.WebViewBridge) return;" - + "var customEvent = document.createEvent('Event');" - + "var WebViewBridge = {" - + "send: function(message) { WebViewBridgeAndroid.send(message); }," - + "onMessage: function() {}" - + "};" - + "window.WebViewBridge = WebViewBridge;" - + "customEvent.initEvent('WebViewBridge', true, true);" - + "document.dispatchEvent(customEvent);" - + "}());"); - } - - static private void evaluateJavascript(WebView root, String javascript) { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { - root.evaluateJavascript(javascript, null); - } else { - root.loadUrl("javascript:" + javascript); + @ReactProp(name = "allowUniversalAccessFromFileURLs") + public void setAllowUniversalAccessFromFileURLs(WebView root, boolean allows) { + root.getSettings().setAllowUniversalAccessFromFileURLs(allows); } - } -} +} \ No newline at end of file diff --git a/ios/RCTWebViewBridge.m b/ios/RCTWebViewBridge.m index 484b66f3..edebf135 100644 --- a/ios/RCTWebViewBridge.m +++ b/ios/RCTWebViewBridge.m @@ -21,6 +21,7 @@ #import "RCTUtils.h" #import "RCTView.h" #import "UIView+React.h" +#import //This is a very elegent way of defining multiline string in objective-c. //source: http://stackoverflow.com/a/23387659/828487 @@ -99,13 +100,15 @@ - (void)sendToBridge:(NSString *)message ); NSString *command = [NSString stringWithFormat: format, message]; - [_webView stringByEvaluatingJavaScriptFromString:message]; + [_webView stringByEvaluatingJavaScriptFromString:command]; } -- (NSString*) getElementHTML:(NSString*)elementId { +- (NSString*) getElementHTML:(NSString*)elementId +{ NSString* JSString = [NSString stringWithFormat:@"document.getElementById(\"%@\").innerHTML",elementId]; return [_webView stringByEvaluatingJavaScriptFromString:JSString]; -} + } + - (NSURL *)URL { diff --git a/ios/RCTWebViewBridgeManager.m b/ios/RCTWebViewBridgeManager.m index 98ee9e42..cd36e53d 100644 --- a/ios/RCTWebViewBridgeManager.m +++ b/ios/RCTWebViewBridgeManager.m @@ -50,6 +50,7 @@ - (UIView *)view RCT_EXPORT_VIEW_PROPERTY(onShouldStartLoadWithRequest, RCTDirectEventBlock) RCT_REMAP_VIEW_PROPERTY(allowsInlineMediaPlayback, _webView.allowsInlineMediaPlayback, BOOL) RCT_EXPORT_VIEW_PROPERTY(onBridgeMessage, RCTDirectEventBlock) +RCT_REMAP_VIEW_PROPERTY(keyboardDisplayRequiresUserAction, _webView.keyboardDisplayRequiresUserAction, BOOL) - (NSDictionary *)constantsToExport { diff --git a/ios/React-Native-Webview-Bridge.xcodeproj/project.pbxproj b/ios/React-Native-Webview-Bridge.xcodeproj/project.pbxproj index c4c381d7..8f5454de 100644 --- a/ios/React-Native-Webview-Bridge.xcodeproj/project.pbxproj +++ b/ios/React-Native-Webview-Bridge.xcodeproj/project.pbxproj @@ -232,6 +232,7 @@ "$(SRCROOT)/../../../node_module/React/**", "$(SRCROOT)/../../../node_modules/react-native/React/**", ); + IPHONEOS_DEPLOYMENT_TARGET = 8.0; OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -246,6 +247,7 @@ "$(SRCROOT)/../../../node_module/React/**", "$(SRCROOT)/../../../node_modules/react-native/React/**", ); + IPHONEOS_DEPLOYMENT_TARGET = 8.0; OTHER_LDFLAGS = "-ObjC"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; diff --git a/package.json b/package.json index 8c2532f9..b15b8d58 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-webview-bridge", - "version": "0.20.3", + "version": "0.33.0", "description": "React Native WebView Javascript Bridge", "main": "webview-bridge", "directories": { @@ -21,6 +21,9 @@ "webview-bridge" ], "author": "Ali Najafizadeh", + "contributors": [ + "Hunter Medney" + ], "license": "MIT", "bugs": { "url": "https://github.com/alinz/react-native-webview-bridge/issues" diff --git a/webview-bridge/index.android.js b/webview-bridge/index.android.js index c4454585..bdfe8180 100644 --- a/webview-bridge/index.android.js +++ b/webview-bridge/index.android.js @@ -43,13 +43,15 @@ var WebViewBridgeState = keyMirror({ ERROR: null, }); +var RCTWebViewBridge = requireNativeComponent('RCTWebViewBridge', WebViewBridge); + /** * Renders a native WebView. */ var WebViewBridge = React.createClass({ propTypes: { - ...WebView.propTypes, + ...RCTWebViewBridge.propTypes, /** * Will be called once the message is being sent from webview @@ -65,6 +67,7 @@ var WebViewBridge = React.createClass({ }; }, + componentWillMount: function() { DeviceEventEmitter.addListener("webViewBridgeMessage", (body) => { const { onBridgeMessage } = this.props; @@ -117,12 +120,14 @@ var WebViewBridge = React.createClass({ ; return ( @@ -133,6 +138,12 @@ var WebViewBridge = React.createClass({ ); }, + onMessage(event) { + if (this.props.onBridgeMessage != null && event.nativeEvent != null) { + this.props.onBridgeMessage(event.nativeEvent.message) + } + }, + goForward: function() { UIManager.dispatchViewManagerCommand( this.getWebViewBridgeHandle(), @@ -165,14 +176,6 @@ var WebViewBridge = React.createClass({ ); }, - injectBridgeScript: function () { - UIManager.dispatchViewManagerCommand( - this.getWebViewBridgeHandle(), - UIManager.RCTWebViewBridge.Commands.injectBridgeScript, - null - ); - }, - /** * We return an event with a bunch of fields including: * url, title, loading, canGoBack, canGoForward @@ -188,7 +191,6 @@ var WebViewBridge = React.createClass({ }, onLoadingStart: function(event) { - this.injectBridgeScript(); var onLoadStart = this.props.onLoadStart; onLoadStart && onLoadStart(event); this.updateNavigationState(event); @@ -199,7 +201,6 @@ var WebViewBridge = React.createClass({ var {onError, onLoadEnd} = this.props; onError && onError(event); onLoadEnd && onLoadEnd(event); - console.error('Encountered an error loading page', event.nativeEvent); this.setState({ lastErrorEvent: event.nativeEvent, @@ -218,7 +219,6 @@ var WebViewBridge = React.createClass({ }, }); -var RCTWebViewBridge = requireNativeComponent('RCTWebViewBridge', WebViewBridge); var styles = StyleSheet.create({ container: { @@ -230,4 +230,4 @@ var styles = StyleSheet.create({ }, }); -module.exports = WebViewBridge; +module.exports = WebViewBridge; \ No newline at end of file diff --git a/webview-bridge/index.ios.js b/webview-bridge/index.ios.js index d2209646..9784e4fb 100644 --- a/webview-bridge/index.ios.js +++ b/webview-bridge/index.ios.js @@ -21,7 +21,7 @@ var keyMirror = require('keymirror'); var resolveAssetSource = require('react-native/Libraries/Image/resolveAssetSource'); var { - ActivityIndicatorIOS, + ActivityIndicator, EdgeInsetsPropType, StyleSheet, Text, @@ -67,7 +67,7 @@ type Event = Object; var defaultRenderLoading = () => ( - + ); var defaultRenderError = (errorDomain, errorCode, errorDesc) => ( @@ -105,6 +105,8 @@ var WebViewBridge = React.createClass({ onBridgeMessage: PropTypes.func, hideKeyboardAccessoryView: PropTypes.bool, + + keyboardDisplayRequiresUserAction: PropTypes.bool, }, getInitialState: function() { From 4404bdb608b82ac91f027a9775afb831d6936a7c Mon Sep 17 00:00:00 2001 From: eladgel Date: Sun, 9 Oct 2016 13:51:19 +0300 Subject: [PATCH 30/77] make it so --- ios/RCTWebViewBridge.m | 381 ++++++++++++++++++++--------------------- 1 file changed, 187 insertions(+), 194 deletions(-) diff --git a/ios/RCTWebViewBridge.m b/ios/RCTWebViewBridge.m index c6356db0..6ac4bf2d 100644 --- a/ios/RCTWebViewBridge.m +++ b/ios/RCTWebViewBridge.m @@ -18,7 +18,6 @@ #import "RCTUtils.h" #import "RCTView.h" #import "UIView+React.h" -#import //This is a very elegent way of defining multiline string in objective-c. //source: http://stackoverflow.com/a/23387659/828487 @@ -38,53 +37,49 @@ @interface RCTWebViewBridge () @implementation RCTWebViewBridge { - UIWebView *_webView; - NSString *_injectedJavaScript; + UIWebView *_webView; + NSString *_injectedJavaScript; } - (void)dealloc { - _webView.delegate = nil; + _webView.delegate = nil; } - (instancetype)initWithFrame:(CGRect)frame { - if ((self = [super initWithFrame:frame])) { - super.backgroundColor = [UIColor clearColor]; - _automaticallyAdjustContentInsets = YES; - _contentInset = UIEdgeInsetsZero; - _webView = [[UIWebView alloc] initWithFrame:self.bounds]; - _webView.delegate = self; - [self addSubview:_webView]; - } - return self; + if ((self = [super initWithFrame:frame])) { + super.backgroundColor = [UIColor clearColor]; + _automaticallyAdjustContentInsets = YES; + _contentInset = UIEdgeInsetsZero; + _webView = [[UIWebView alloc] initWithFrame:self.bounds]; + _webView.delegate = self; + [self addSubview:_webView]; + } + return self; } RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder) - (void)goForward { - [_webView goForward]; + [_webView goForward]; } - (void)goBack { - [_webView goBack]; + [_webView goBack]; } - (void)reload { - NSURLRequest *request = [RCTConvert NSURLRequest:self.source]; - if (request.URL && !_webView.request.URL.absoluteString.length) { - [_webView loadRequest:request]; - } - else { - [_webView reload]; - } -} - - NSString *command = [NSString stringWithFormat: format, message]; - [_webView stringByEvaluatingJavaScriptFromString:command]; + NSURLRequest *request = [RCTConvert NSURLRequest:self.source]; + if (request.URL && !_webView.request.URL.absoluteString.length) { + [_webView loadRequest:request]; + } + else { + [_webView reload]; + } } - (NSString*) getElementHTML:(NSString*)elementId @@ -92,103 +87,101 @@ - (NSString*) getElementHTML:(NSString*)elementId NSString* JSString = [NSString stringWithFormat:@"document.getElementById(\"%@\").innerHTML",elementId]; return [_webView stringByEvaluatingJavaScriptFromString:JSString]; } - - - (void)stopLoading { - [_webView stopLoading]; + [_webView stopLoading]; } - (void)setSource:(NSDictionary *)source { - if (![_source isEqualToDictionary:source]) { - _source = [source copy]; - - // Check for a static html source first - NSString *html = [RCTConvert NSString:source[@"html"]]; - if (html) { - NSURL *baseURL = [RCTConvert NSURL:source[@"baseUrl"]]; - if (!baseURL) { - baseURL = [NSURL URLWithString:@"about:blank"]; - } - [_webView loadHTMLString:html baseURL:baseURL]; - return; + if (![_source isEqualToDictionary:source]) { + _source = [source copy]; + + // Check for a static html source first + NSString *html = [RCTConvert NSString:source[@"html"]]; + if (html) { + NSURL *baseURL = [RCTConvert NSURL:source[@"baseUrl"]]; + if (!baseURL) { + baseURL = [NSURL URLWithString:@"about:blank"]; + } + [_webView loadHTMLString:html baseURL:baseURL]; + return; + } + + NSURLRequest *request = [RCTConvert NSURLRequest:source]; + // Because of the way React works, as pages redirect, we actually end up + // passing the redirect urls back here, so we ignore them if trying to load + // the same url. We'll expose a call to 'reload' to allow a user to load + // the existing page. + if ([request.URL isEqual:_webView.request.URL]) { + return; + } + if (!request.URL) { + // Clear the webview + [_webView loadHTMLString:@"" baseURL:nil]; + return; + } + [_webView loadRequest:request]; } - - NSURLRequest *request = [RCTConvert NSURLRequest:source]; - // Because of the way React works, as pages redirect, we actually end up - // passing the redirect urls back here, so we ignore them if trying to load - // the same url. We'll expose a call to 'reload' to allow a user to load - // the existing page. - if ([request.URL isEqual:_webView.request.URL]) { - return; - } - if (!request.URL) { - // Clear the webview - [_webView loadHTMLString:@"" baseURL:nil]; - return; - } - [_webView loadRequest:request]; - } } - (void)layoutSubviews { - [super layoutSubviews]; - _webView.frame = self.bounds; + [super layoutSubviews]; + _webView.frame = self.bounds; } - (void)setContentInset:(UIEdgeInsets)contentInset { - _contentInset = contentInset; - [RCTView autoAdjustInsetsForView:self - withScrollView:_webView.scrollView - updateOffset:NO]; + _contentInset = contentInset; + [RCTView autoAdjustInsetsForView:self + withScrollView:_webView.scrollView + updateOffset:NO]; } - (void)setScalesPageToFit:(BOOL)scalesPageToFit { - if (_webView.scalesPageToFit != scalesPageToFit) { - _webView.scalesPageToFit = scalesPageToFit; - [_webView reload]; - } + if (_webView.scalesPageToFit != scalesPageToFit) { + _webView.scalesPageToFit = scalesPageToFit; + [_webView reload]; + } } - (BOOL)scalesPageToFit { - return _webView.scalesPageToFit; + return _webView.scalesPageToFit; } - (void)setBackgroundColor:(UIColor *)backgroundColor { - CGFloat alpha = CGColorGetAlpha(backgroundColor.CGColor); - self.opaque = _webView.opaque = (alpha == 1.0); - _webView.backgroundColor = backgroundColor; + CGFloat alpha = CGColorGetAlpha(backgroundColor.CGColor); + self.opaque = _webView.opaque = (alpha == 1.0); + _webView.backgroundColor = backgroundColor; } - (UIColor *)backgroundColor { - return _webView.backgroundColor; + return _webView.backgroundColor; } - (NSMutableDictionary *)baseEvent { - NSMutableDictionary *event = [[NSMutableDictionary alloc] initWithDictionary:@{ - @"url": _webView.request.URL.absoluteString ?: @"", - @"loading" : @(_webView.loading), - @"title": [_webView stringByEvaluatingJavaScriptFromString:@"document.title"], - @"canGoBack": @(_webView.canGoBack), - @"canGoForward" : @(_webView.canGoForward), - }]; - - return event; + NSMutableDictionary *event = [[NSMutableDictionary alloc] initWithDictionary:@{ + @"url": _webView.request.URL.absoluteString ?: @"", + @"loading" : @(_webView.loading), + @"title": [_webView stringByEvaluatingJavaScriptFromString:@"document.title"], + @"canGoBack": @(_webView.canGoBack), + @"canGoForward" : @(_webView.canGoForward), + }]; + + return event; } - (void)refreshContentInset { - [RCTView autoAdjustInsetsForView:self - withScrollView:_webView.scrollView - updateOffset:YES]; + [RCTView autoAdjustInsetsForView:self + withScrollView:_webView.scrollView + updateOffset:YES]; } #pragma mark - UIWebViewDelegate methods @@ -196,146 +189,146 @@ - (void)refreshContentInset - (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { - BOOL isJSNavigation = [request.URL.scheme isEqualToString:RCTJSNavigationScheme]; - - if (!isJSNavigation && [request.URL.scheme isEqualToString:RCTWebViewBridgeSchema]) { - NSString* message = [webView stringByEvaluatingJavaScriptFromString:@"WebViewBridge.__fetch__()"]; - - NSMutableDictionary *onBridgeMessageEvent = [[NSMutableDictionary alloc] initWithDictionary:@{ - @"messages": [self stringArrayJsonToArray: message] - }]; - - _onBridgeMessage(onBridgeMessageEvent); - - isJSNavigation = YES; - } - - static NSDictionary *navigationTypes; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - navigationTypes = @{ - @(UIWebViewNavigationTypeLinkClicked): @"click", - @(UIWebViewNavigationTypeFormSubmitted): @"formsubmit", - @(UIWebViewNavigationTypeBackForward): @"backforward", - @(UIWebViewNavigationTypeReload): @"reload", - @(UIWebViewNavigationTypeFormResubmitted): @"formresubmit", - @(UIWebViewNavigationTypeOther): @"other", - }; - }); - - // skip this for the JS Navigation handler - if (!isJSNavigation && _onShouldStartLoadWithRequest) { - NSMutableDictionary *event = [self baseEvent]; - [event addEntriesFromDictionary: @{ - @"url": (request.URL).absoluteString, - @"navigationType": navigationTypes[@(navigationType)] - }]; - if (![self.delegate webView:self - shouldStartLoadForRequest:event - withCallback:_onShouldStartLoadWithRequest]) { - return NO; + BOOL isJSNavigation = [request.URL.scheme isEqualToString:RCTJSNavigationScheme]; + + if (!isJSNavigation && [request.URL.scheme isEqualToString:RCTWebViewBridgeSchema]) { + NSString* message = [webView stringByEvaluatingJavaScriptFromString:@"WebViewBridge.__fetch__()"]; + + NSMutableDictionary *onBridgeMessageEvent = [[NSMutableDictionary alloc] initWithDictionary:@{ + @"messages": [self stringArrayJsonToArray: message] + }]; + + _onBridgeMessage(onBridgeMessageEvent); + + isJSNavigation = YES; } - } - - if (_onLoadingStart) { - // We have this check to filter out iframe requests and whatnot - BOOL isTopFrame = [request.URL isEqual:request.mainDocumentURL]; - if (isTopFrame) { - NSMutableDictionary *event = [self baseEvent]; - [event addEntriesFromDictionary: @{ - @"url": (request.URL).absoluteString, - @"navigationType": navigationTypes[@(navigationType)] - }]; - _onLoadingStart(event); + + static NSDictionary *navigationTypes; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + navigationTypes = @{ + @(UIWebViewNavigationTypeLinkClicked): @"click", + @(UIWebViewNavigationTypeFormSubmitted): @"formsubmit", + @(UIWebViewNavigationTypeBackForward): @"backforward", + @(UIWebViewNavigationTypeReload): @"reload", + @(UIWebViewNavigationTypeFormResubmitted): @"formresubmit", + @(UIWebViewNavigationTypeOther): @"other", + }; + }); + + // skip this for the JS Navigation handler + if (!isJSNavigation && _onShouldStartLoadWithRequest) { + NSMutableDictionary *event = [self baseEvent]; + [event addEntriesFromDictionary: @{ + @"url": (request.URL).absoluteString, + @"navigationType": navigationTypes[@(navigationType)] + }]; + if (![self.delegate webView:self + shouldStartLoadForRequest:event + withCallback:_onShouldStartLoadWithRequest]) { + return NO; + } } - } - // JS Navigation handler - return !isJSNavigation; + if (_onLoadingStart) { + // We have this check to filter out iframe requests and whatnot + BOOL isTopFrame = [request.URL isEqual:request.mainDocumentURL]; + if (isTopFrame) { + NSMutableDictionary *event = [self baseEvent]; + [event addEntriesFromDictionary: @{ + @"url": (request.URL).absoluteString, + @"navigationType": navigationTypes[@(navigationType)] + }]; + _onLoadingStart(event); + } + } + + // JS Navigation handler + return !isJSNavigation; } - (void)webView:(__unused UIWebView *)webView didFailLoadWithError:(NSError *)error { - if (_onLoadingError) { - if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) { - // NSURLErrorCancelled is reported when a page has a redirect OR if you load - // a new URL in the WebView before the previous one came back. We can just - // ignore these since they aren't real errors. - // http://stackoverflow.com/questions/1024748/how-do-i-fix-nsurlerrordomain-error-999-in-iphone-3-0-os - return; + if (_onLoadingError) { + if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) { + // NSURLErrorCancelled is reported when a page has a redirect OR if you load + // a new URL in the WebView before the previous one came back. We can just + // ignore these since they aren't real errors. + // http://stackoverflow.com/questions/1024748/how-do-i-fix-nsurlerrordomain-error-999-in-iphone-3-0-os + return; + } + + NSMutableDictionary *event = [self baseEvent]; + [event addEntriesFromDictionary:@{ + @"domain": error.domain, + @"code": @(error.code), + @"description": error.localizedDescription, + }]; + _onLoadingError(event); } - - NSMutableDictionary *event = [self baseEvent]; - [event addEntriesFromDictionary:@{ - @"domain": error.domain, - @"code": @(error.code), - @"description": error.localizedDescription, - }]; - _onLoadingError(event); - } } - (void)webViewDidFinishLoad:(UIWebView *)webView { - //injecting WebViewBridge bootstrap - [webView stringByEvaluatingJavaScriptFromString:[self webViewBridgeBootrstrap]]; - - //injecting WebViewBridgeRPC bootstrap - if (self.rpc) { - [webView stringByEvaluatingJavaScriptFromString:[self webViewBridgeRPCBootstrap]]; - } - - if (_injectedJavaScript != nil) { - NSString *jsEvaluationValue = [webView stringByEvaluatingJavaScriptFromString:_injectedJavaScript]; - - NSMutableDictionary *event = [self baseEvent]; - event[@"jsEvaluationValue"] = jsEvaluationValue; - - _onLoadingFinish(event); - } - // we only need the final 'finishLoad' call so only fire the event when we're actually done loading. - else if (_onLoadingFinish && !webView.loading && ![webView.request.URL.absoluteString isEqualToString:@"about:blank"]) { - _onLoadingFinish([self baseEvent]); - } + //injecting WebViewBridge bootstrap + [webView stringByEvaluatingJavaScriptFromString:[self webViewBridgeBootrstrap]]; + + //injecting WebViewBridgeRPC bootstrap + if (self.rpc) { + [webView stringByEvaluatingJavaScriptFromString:[self webViewBridgeRPCBootstrap]]; + } + + if (_injectedJavaScript != nil) { + NSString *jsEvaluationValue = [webView stringByEvaluatingJavaScriptFromString:_injectedJavaScript]; + + NSMutableDictionary *event = [self baseEvent]; + event[@"jsEvaluationValue"] = jsEvaluationValue; + + _onLoadingFinish(event); + } + // we only need the final 'finishLoad' call so only fire the event when we're actually done loading. + else if (_onLoadingFinish && !webView.loading && ![webView.request.URL.absoluteString isEqualToString:@"about:blank"]) { + _onLoadingFinish([self baseEvent]); + } } - (void)sendToBridge:(NSString *)message { - //we are warpping the send message in a function to make sure that if - //WebView is not injected, we don't crash the app. - NSString *format = NSStringMultiline( - (function(){ - if (WebViewBridge && WebViewBridge.__push__) { - WebViewBridge.__push__("%@"); - } + //we are warpping the send message in a function to make sure that if + //WebView is not injected, we don't crash the app. + NSString *format = NSStringMultiline( + (function(){ + if (WebViewBridge && WebViewBridge.__push__) { + WebViewBridge.__push__("%@"); + } }()); - ); + ); - NSString *command = [NSString stringWithFormat: format, message]; - [_webView stringByEvaluatingJavaScriptFromString:command]; + NSString *command = [NSString stringWithFormat: format, message]; + [_webView stringByEvaluatingJavaScriptFromString:command]; } - (NSArray*)stringArrayJsonToArray:(NSString *)message { - return [NSJSONSerialization JSONObjectWithData:[message dataUsingEncoding:NSUTF8StringEncoding] - options:NSJSONReadingAllowFragments - error:nil]; + return [NSJSONSerialization JSONObjectWithData:[message dataUsingEncoding:NSUTF8StringEncoding] + options:NSJSONReadingAllowFragments + error:nil]; } - (NSString *)webViewBridgeBootrstrap { - NSString *path = [[NSBundle mainBundle] pathForResource:@"WebViewBridge" ofType:@"js"]; - assert(path != nil); - NSString* content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL]; - return content; + NSString *path = [[NSBundle mainBundle] pathForResource:@"WebViewBridge" ofType:@"js"]; + assert(path != nil); + NSString* content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL]; + return content; } - (NSString *)webViewBridgeRPCBootstrap { - NSString *path = [[NSBundle mainBundle] pathForResource:@"WebViewBridgeRPC" ofType:@"js"]; - assert(path != nil); - NSString* content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL]; - return content; + NSString *path = [[NSBundle mainBundle] pathForResource:@"WebViewBridgeRPC" ofType:@"js"]; + assert(path != nil); + NSString* content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL]; + return content; } @end From 7f76e93da2d664a50bb55741f201572797726a15 Mon Sep 17 00:00:00 2001 From: eladgel Date: Sun, 9 Oct 2016 13:51:19 +0300 Subject: [PATCH 31/77] make it so --- ios/RCTWebViewBridge.m | 381 +++++++++++++++++++-------------------- scripts/WebViewBridge.js | 151 ++++++---------- 2 files changed, 246 insertions(+), 286 deletions(-) diff --git a/ios/RCTWebViewBridge.m b/ios/RCTWebViewBridge.m index c6356db0..6ac4bf2d 100644 --- a/ios/RCTWebViewBridge.m +++ b/ios/RCTWebViewBridge.m @@ -18,7 +18,6 @@ #import "RCTUtils.h" #import "RCTView.h" #import "UIView+React.h" -#import //This is a very elegent way of defining multiline string in objective-c. //source: http://stackoverflow.com/a/23387659/828487 @@ -38,53 +37,49 @@ @interface RCTWebViewBridge () @implementation RCTWebViewBridge { - UIWebView *_webView; - NSString *_injectedJavaScript; + UIWebView *_webView; + NSString *_injectedJavaScript; } - (void)dealloc { - _webView.delegate = nil; + _webView.delegate = nil; } - (instancetype)initWithFrame:(CGRect)frame { - if ((self = [super initWithFrame:frame])) { - super.backgroundColor = [UIColor clearColor]; - _automaticallyAdjustContentInsets = YES; - _contentInset = UIEdgeInsetsZero; - _webView = [[UIWebView alloc] initWithFrame:self.bounds]; - _webView.delegate = self; - [self addSubview:_webView]; - } - return self; + if ((self = [super initWithFrame:frame])) { + super.backgroundColor = [UIColor clearColor]; + _automaticallyAdjustContentInsets = YES; + _contentInset = UIEdgeInsetsZero; + _webView = [[UIWebView alloc] initWithFrame:self.bounds]; + _webView.delegate = self; + [self addSubview:_webView]; + } + return self; } RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder) - (void)goForward { - [_webView goForward]; + [_webView goForward]; } - (void)goBack { - [_webView goBack]; + [_webView goBack]; } - (void)reload { - NSURLRequest *request = [RCTConvert NSURLRequest:self.source]; - if (request.URL && !_webView.request.URL.absoluteString.length) { - [_webView loadRequest:request]; - } - else { - [_webView reload]; - } -} - - NSString *command = [NSString stringWithFormat: format, message]; - [_webView stringByEvaluatingJavaScriptFromString:command]; + NSURLRequest *request = [RCTConvert NSURLRequest:self.source]; + if (request.URL && !_webView.request.URL.absoluteString.length) { + [_webView loadRequest:request]; + } + else { + [_webView reload]; + } } - (NSString*) getElementHTML:(NSString*)elementId @@ -92,103 +87,101 @@ - (NSString*) getElementHTML:(NSString*)elementId NSString* JSString = [NSString stringWithFormat:@"document.getElementById(\"%@\").innerHTML",elementId]; return [_webView stringByEvaluatingJavaScriptFromString:JSString]; } - - - (void)stopLoading { - [_webView stopLoading]; + [_webView stopLoading]; } - (void)setSource:(NSDictionary *)source { - if (![_source isEqualToDictionary:source]) { - _source = [source copy]; - - // Check for a static html source first - NSString *html = [RCTConvert NSString:source[@"html"]]; - if (html) { - NSURL *baseURL = [RCTConvert NSURL:source[@"baseUrl"]]; - if (!baseURL) { - baseURL = [NSURL URLWithString:@"about:blank"]; - } - [_webView loadHTMLString:html baseURL:baseURL]; - return; + if (![_source isEqualToDictionary:source]) { + _source = [source copy]; + + // Check for a static html source first + NSString *html = [RCTConvert NSString:source[@"html"]]; + if (html) { + NSURL *baseURL = [RCTConvert NSURL:source[@"baseUrl"]]; + if (!baseURL) { + baseURL = [NSURL URLWithString:@"about:blank"]; + } + [_webView loadHTMLString:html baseURL:baseURL]; + return; + } + + NSURLRequest *request = [RCTConvert NSURLRequest:source]; + // Because of the way React works, as pages redirect, we actually end up + // passing the redirect urls back here, so we ignore them if trying to load + // the same url. We'll expose a call to 'reload' to allow a user to load + // the existing page. + if ([request.URL isEqual:_webView.request.URL]) { + return; + } + if (!request.URL) { + // Clear the webview + [_webView loadHTMLString:@"" baseURL:nil]; + return; + } + [_webView loadRequest:request]; } - - NSURLRequest *request = [RCTConvert NSURLRequest:source]; - // Because of the way React works, as pages redirect, we actually end up - // passing the redirect urls back here, so we ignore them if trying to load - // the same url. We'll expose a call to 'reload' to allow a user to load - // the existing page. - if ([request.URL isEqual:_webView.request.URL]) { - return; - } - if (!request.URL) { - // Clear the webview - [_webView loadHTMLString:@"" baseURL:nil]; - return; - } - [_webView loadRequest:request]; - } } - (void)layoutSubviews { - [super layoutSubviews]; - _webView.frame = self.bounds; + [super layoutSubviews]; + _webView.frame = self.bounds; } - (void)setContentInset:(UIEdgeInsets)contentInset { - _contentInset = contentInset; - [RCTView autoAdjustInsetsForView:self - withScrollView:_webView.scrollView - updateOffset:NO]; + _contentInset = contentInset; + [RCTView autoAdjustInsetsForView:self + withScrollView:_webView.scrollView + updateOffset:NO]; } - (void)setScalesPageToFit:(BOOL)scalesPageToFit { - if (_webView.scalesPageToFit != scalesPageToFit) { - _webView.scalesPageToFit = scalesPageToFit; - [_webView reload]; - } + if (_webView.scalesPageToFit != scalesPageToFit) { + _webView.scalesPageToFit = scalesPageToFit; + [_webView reload]; + } } - (BOOL)scalesPageToFit { - return _webView.scalesPageToFit; + return _webView.scalesPageToFit; } - (void)setBackgroundColor:(UIColor *)backgroundColor { - CGFloat alpha = CGColorGetAlpha(backgroundColor.CGColor); - self.opaque = _webView.opaque = (alpha == 1.0); - _webView.backgroundColor = backgroundColor; + CGFloat alpha = CGColorGetAlpha(backgroundColor.CGColor); + self.opaque = _webView.opaque = (alpha == 1.0); + _webView.backgroundColor = backgroundColor; } - (UIColor *)backgroundColor { - return _webView.backgroundColor; + return _webView.backgroundColor; } - (NSMutableDictionary *)baseEvent { - NSMutableDictionary *event = [[NSMutableDictionary alloc] initWithDictionary:@{ - @"url": _webView.request.URL.absoluteString ?: @"", - @"loading" : @(_webView.loading), - @"title": [_webView stringByEvaluatingJavaScriptFromString:@"document.title"], - @"canGoBack": @(_webView.canGoBack), - @"canGoForward" : @(_webView.canGoForward), - }]; - - return event; + NSMutableDictionary *event = [[NSMutableDictionary alloc] initWithDictionary:@{ + @"url": _webView.request.URL.absoluteString ?: @"", + @"loading" : @(_webView.loading), + @"title": [_webView stringByEvaluatingJavaScriptFromString:@"document.title"], + @"canGoBack": @(_webView.canGoBack), + @"canGoForward" : @(_webView.canGoForward), + }]; + + return event; } - (void)refreshContentInset { - [RCTView autoAdjustInsetsForView:self - withScrollView:_webView.scrollView - updateOffset:YES]; + [RCTView autoAdjustInsetsForView:self + withScrollView:_webView.scrollView + updateOffset:YES]; } #pragma mark - UIWebViewDelegate methods @@ -196,146 +189,146 @@ - (void)refreshContentInset - (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { - BOOL isJSNavigation = [request.URL.scheme isEqualToString:RCTJSNavigationScheme]; - - if (!isJSNavigation && [request.URL.scheme isEqualToString:RCTWebViewBridgeSchema]) { - NSString* message = [webView stringByEvaluatingJavaScriptFromString:@"WebViewBridge.__fetch__()"]; - - NSMutableDictionary *onBridgeMessageEvent = [[NSMutableDictionary alloc] initWithDictionary:@{ - @"messages": [self stringArrayJsonToArray: message] - }]; - - _onBridgeMessage(onBridgeMessageEvent); - - isJSNavigation = YES; - } - - static NSDictionary *navigationTypes; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - navigationTypes = @{ - @(UIWebViewNavigationTypeLinkClicked): @"click", - @(UIWebViewNavigationTypeFormSubmitted): @"formsubmit", - @(UIWebViewNavigationTypeBackForward): @"backforward", - @(UIWebViewNavigationTypeReload): @"reload", - @(UIWebViewNavigationTypeFormResubmitted): @"formresubmit", - @(UIWebViewNavigationTypeOther): @"other", - }; - }); - - // skip this for the JS Navigation handler - if (!isJSNavigation && _onShouldStartLoadWithRequest) { - NSMutableDictionary *event = [self baseEvent]; - [event addEntriesFromDictionary: @{ - @"url": (request.URL).absoluteString, - @"navigationType": navigationTypes[@(navigationType)] - }]; - if (![self.delegate webView:self - shouldStartLoadForRequest:event - withCallback:_onShouldStartLoadWithRequest]) { - return NO; + BOOL isJSNavigation = [request.URL.scheme isEqualToString:RCTJSNavigationScheme]; + + if (!isJSNavigation && [request.URL.scheme isEqualToString:RCTWebViewBridgeSchema]) { + NSString* message = [webView stringByEvaluatingJavaScriptFromString:@"WebViewBridge.__fetch__()"]; + + NSMutableDictionary *onBridgeMessageEvent = [[NSMutableDictionary alloc] initWithDictionary:@{ + @"messages": [self stringArrayJsonToArray: message] + }]; + + _onBridgeMessage(onBridgeMessageEvent); + + isJSNavigation = YES; } - } - - if (_onLoadingStart) { - // We have this check to filter out iframe requests and whatnot - BOOL isTopFrame = [request.URL isEqual:request.mainDocumentURL]; - if (isTopFrame) { - NSMutableDictionary *event = [self baseEvent]; - [event addEntriesFromDictionary: @{ - @"url": (request.URL).absoluteString, - @"navigationType": navigationTypes[@(navigationType)] - }]; - _onLoadingStart(event); + + static NSDictionary *navigationTypes; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + navigationTypes = @{ + @(UIWebViewNavigationTypeLinkClicked): @"click", + @(UIWebViewNavigationTypeFormSubmitted): @"formsubmit", + @(UIWebViewNavigationTypeBackForward): @"backforward", + @(UIWebViewNavigationTypeReload): @"reload", + @(UIWebViewNavigationTypeFormResubmitted): @"formresubmit", + @(UIWebViewNavigationTypeOther): @"other", + }; + }); + + // skip this for the JS Navigation handler + if (!isJSNavigation && _onShouldStartLoadWithRequest) { + NSMutableDictionary *event = [self baseEvent]; + [event addEntriesFromDictionary: @{ + @"url": (request.URL).absoluteString, + @"navigationType": navigationTypes[@(navigationType)] + }]; + if (![self.delegate webView:self + shouldStartLoadForRequest:event + withCallback:_onShouldStartLoadWithRequest]) { + return NO; + } } - } - // JS Navigation handler - return !isJSNavigation; + if (_onLoadingStart) { + // We have this check to filter out iframe requests and whatnot + BOOL isTopFrame = [request.URL isEqual:request.mainDocumentURL]; + if (isTopFrame) { + NSMutableDictionary *event = [self baseEvent]; + [event addEntriesFromDictionary: @{ + @"url": (request.URL).absoluteString, + @"navigationType": navigationTypes[@(navigationType)] + }]; + _onLoadingStart(event); + } + } + + // JS Navigation handler + return !isJSNavigation; } - (void)webView:(__unused UIWebView *)webView didFailLoadWithError:(NSError *)error { - if (_onLoadingError) { - if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) { - // NSURLErrorCancelled is reported when a page has a redirect OR if you load - // a new URL in the WebView before the previous one came back. We can just - // ignore these since they aren't real errors. - // http://stackoverflow.com/questions/1024748/how-do-i-fix-nsurlerrordomain-error-999-in-iphone-3-0-os - return; + if (_onLoadingError) { + if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) { + // NSURLErrorCancelled is reported when a page has a redirect OR if you load + // a new URL in the WebView before the previous one came back. We can just + // ignore these since they aren't real errors. + // http://stackoverflow.com/questions/1024748/how-do-i-fix-nsurlerrordomain-error-999-in-iphone-3-0-os + return; + } + + NSMutableDictionary *event = [self baseEvent]; + [event addEntriesFromDictionary:@{ + @"domain": error.domain, + @"code": @(error.code), + @"description": error.localizedDescription, + }]; + _onLoadingError(event); } - - NSMutableDictionary *event = [self baseEvent]; - [event addEntriesFromDictionary:@{ - @"domain": error.domain, - @"code": @(error.code), - @"description": error.localizedDescription, - }]; - _onLoadingError(event); - } } - (void)webViewDidFinishLoad:(UIWebView *)webView { - //injecting WebViewBridge bootstrap - [webView stringByEvaluatingJavaScriptFromString:[self webViewBridgeBootrstrap]]; - - //injecting WebViewBridgeRPC bootstrap - if (self.rpc) { - [webView stringByEvaluatingJavaScriptFromString:[self webViewBridgeRPCBootstrap]]; - } - - if (_injectedJavaScript != nil) { - NSString *jsEvaluationValue = [webView stringByEvaluatingJavaScriptFromString:_injectedJavaScript]; - - NSMutableDictionary *event = [self baseEvent]; - event[@"jsEvaluationValue"] = jsEvaluationValue; - - _onLoadingFinish(event); - } - // we only need the final 'finishLoad' call so only fire the event when we're actually done loading. - else if (_onLoadingFinish && !webView.loading && ![webView.request.URL.absoluteString isEqualToString:@"about:blank"]) { - _onLoadingFinish([self baseEvent]); - } + //injecting WebViewBridge bootstrap + [webView stringByEvaluatingJavaScriptFromString:[self webViewBridgeBootrstrap]]; + + //injecting WebViewBridgeRPC bootstrap + if (self.rpc) { + [webView stringByEvaluatingJavaScriptFromString:[self webViewBridgeRPCBootstrap]]; + } + + if (_injectedJavaScript != nil) { + NSString *jsEvaluationValue = [webView stringByEvaluatingJavaScriptFromString:_injectedJavaScript]; + + NSMutableDictionary *event = [self baseEvent]; + event[@"jsEvaluationValue"] = jsEvaluationValue; + + _onLoadingFinish(event); + } + // we only need the final 'finishLoad' call so only fire the event when we're actually done loading. + else if (_onLoadingFinish && !webView.loading && ![webView.request.URL.absoluteString isEqualToString:@"about:blank"]) { + _onLoadingFinish([self baseEvent]); + } } - (void)sendToBridge:(NSString *)message { - //we are warpping the send message in a function to make sure that if - //WebView is not injected, we don't crash the app. - NSString *format = NSStringMultiline( - (function(){ - if (WebViewBridge && WebViewBridge.__push__) { - WebViewBridge.__push__("%@"); - } + //we are warpping the send message in a function to make sure that if + //WebView is not injected, we don't crash the app. + NSString *format = NSStringMultiline( + (function(){ + if (WebViewBridge && WebViewBridge.__push__) { + WebViewBridge.__push__("%@"); + } }()); - ); + ); - NSString *command = [NSString stringWithFormat: format, message]; - [_webView stringByEvaluatingJavaScriptFromString:command]; + NSString *command = [NSString stringWithFormat: format, message]; + [_webView stringByEvaluatingJavaScriptFromString:command]; } - (NSArray*)stringArrayJsonToArray:(NSString *)message { - return [NSJSONSerialization JSONObjectWithData:[message dataUsingEncoding:NSUTF8StringEncoding] - options:NSJSONReadingAllowFragments - error:nil]; + return [NSJSONSerialization JSONObjectWithData:[message dataUsingEncoding:NSUTF8StringEncoding] + options:NSJSONReadingAllowFragments + error:nil]; } - (NSString *)webViewBridgeBootrstrap { - NSString *path = [[NSBundle mainBundle] pathForResource:@"WebViewBridge" ofType:@"js"]; - assert(path != nil); - NSString* content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL]; - return content; + NSString *path = [[NSBundle mainBundle] pathForResource:@"WebViewBridge" ofType:@"js"]; + assert(path != nil); + NSString* content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL]; + return content; } - (NSString *)webViewBridgeRPCBootstrap { - NSString *path = [[NSBundle mainBundle] pathForResource:@"WebViewBridgeRPC" ofType:@"js"]; - assert(path != nil); - NSString* content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL]; - return content; + NSString *path = [[NSBundle mainBundle] pathForResource:@"WebViewBridgeRPC" ofType:@"js"]; + assert(path != nil); + NSString* content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL]; + return content; } @end diff --git a/scripts/WebViewBridge.js b/scripts/WebViewBridge.js index 92ed4b84..32b43e0f 100644 --- a/scripts/WebViewBridge.js +++ b/scripts/WebViewBridge.js @@ -1,105 +1,72 @@ -(function () { - var WebViewBridge = window.WebViewBridge ? window.WebViewBridge : {} - var queue = [] - var onMessageListeners = {} +(function (window) { + 'use strict'; - //base64 encode and decode polyfil - //modified version of https://github.com/davidchambers/Base64.js - //you can replace the b64Encode and b64Decode for atob and btoa. - var b64ch = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' - function b64Encode(input) { - var output = '' - for ( - var block, charCode, idx = 0, map = b64ch; - input.charAt(idx | 0) || (map = '=', idx % 1); - output += map.charAt(63 & block >> 8 - idx % 1 * 8) - ) { - charCode = input.charCodeAt(idx += 3/4) - block = block << 8 | charCode - } - return output + //Make sure that if WebViewBridge already in scope we don't override it. + if (window.WebViewBridge) { + return; } - function b64Decode(input) { - var str = input.replace(/=+$/, '') - var output = '' - for ( - var bc = 0, bs, buffer, idx = 0; - buffer = str.charAt(idx++); - ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, - bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0 - ) { - buffer = b64ch.indexOf(buffer) - } - return output - } + var RNWBSchema = 'wvb'; + var sendQueue = []; + var receiveQueue = []; + var doc = window.document; + var customEvent = doc.createEvent('Event'); - function encode(input) { - if (typeof input !== 'string') { - try { - input = JSON.stringify(input) - } catch(e) {} + function callFunc(func, message) { + if ('function' === typeof func) { + func(message); } - input = unescape(encodeURIComponent(input)) - return b64Encode(input) - } - - function decode(input) { - var result = decodeURIComponent(escape(b64Decode(input))) - try { - result = JSON.parse(result) - } catch(e){} - return result } function signalNative() { - if (WebViewBridge.nativeAndroidSend) { - WebViewBridge.nativeAndroidSend(WebViewBridge.__fetch__()) - } else { - window.location = 'rnwb://message' + new Date().getTime() - } + window.location = RNWBSchema + '://message' + new Date().getTime(); } - function dispatch(name, value) { - var event = new CustomEvent(name, { - detail: value, - bubbles: true - }); + //I made the private function ugly signiture so user doesn't called them accidently. + //if you do, then I have nothing to say. :( + var WebViewBridge = { + //this function will be called by native side to push a new message + //to webview. + __push__: function (message) { + receiveQueue.push(message); + //reason I need this setTmeout is to return this function as fast as + //possible to release the native side thread. + setTimeout(function () { + var message = receiveQueue.pop(); + callFunc(WebViewBridge.onMessage, message); + }, 15); //this magic number is just a random small value. I don't like 0. + }, + __fetch__: function () { + //since our sendQueue array only contains string, and our connection to native + //can only accept string, we need to convert array of strings into single string. + var messages = JSON.stringify(sendQueue); - setTimeout(function () { - window.document.dispatchEvent(event) - }, 15) - } + //we make sure that sendQueue is resets + sendQueue = []; - WebViewBridge.__dispatch__ = dispatch - WebViewBridge.__push__ = function (encoded) { - //we need to release native caller as soon as possible - //that's why we are wrap this on setTimeout - setTimeout(function () { - var fn = null - var decoded = decode(encoded) - Object.keys(onMessageListeners).forEach(function (onMessage) { - fn = onMessageListeners[onMessage] - fn(decoded) - }) - }, 15) - } - WebViewBridge.__fetch__ = function () { - var val = JSON.stringify(queue) - queue = [] - return val - } - WebViewBridge.send = function (input) { - queue.push(encode(input)) - setTimeout(signalNative, 15) - } - WebViewBridge.addMessageListener = function(fn) { - onMessageListeners[fn] = fn - } - WebViewBridge.removeMessageListener = function (fn) { - delete onMessageListeners[fn] - } + //return the messages back to native side. + return messages; + }, + //make sure message is string. because only string can be sent to native, + //if you don't pass it as string, onError function will be called. + send: function (message) { + if ('string' !== typeof message) { + callFunc(WebViewBridge.onError, "message is type '" + typeof message + "', and it needs to be string"); + return; + } + + //we queue the messages to make sure that native can collects all of them in one shot. + sendQueue.push(message); + //signal the objective-c that there is a message in the queue + signalNative(); + }, + onMessage: null, + onError: null + }; + + window.WebViewBridge = WebViewBridge; - window.WebViewBridge = WebViewBridge - dispatch('webviewbridge:init', WebViewBridge) -}()) + //dispatch event + customEvent.initEvent('WebViewBridge', true, true); + doc.dispatchEvent(customEvent); +}(window)); From c443c948171efd91ecf161baa0a28bf450f617c3 Mon Sep 17 00:00:00 2001 From: eladgel Date: Sun, 9 Oct 2016 14:02:38 +0300 Subject: [PATCH 32/77] removed unused file --- scripts/WebViewBridgeRPC.js | 141 ------------------------------------ 1 file changed, 141 deletions(-) delete mode 100644 scripts/WebViewBridgeRPC.js diff --git a/scripts/WebViewBridgeRPC.js b/scripts/WebViewBridgeRPC.js deleted file mode 100644 index fb0e8205..00000000 --- a/scripts/WebViewBridgeRPC.js +++ /dev/null @@ -1,141 +0,0 @@ -(function () { - 'use strict' - - var ids = 0 - var invokers = {} - var responseCallbacks = {} - - //invoker has accept 2 arguments - //fn(args, result). result is a function which accept an argument. - //that argument is being used to send to caller as a result value. - //by using this function, we can support both sync and async operation - function register(sender, name, fn) { - invokers[name] = function (id, args) { - fn(args, function (result) { - sender({ - id: id, - type: 'resolve', - result: result - }) - }, function (err) { - sender({ - id: id, - type: 'reject', - result: err - }) - }) - } - } - - function invoke(sender, name, args, opt, callback) { - var id = ++ids - var target = { - callback: callback - } - - if (opt) { - opt = {} - } - - if (!opt.timeout) { - opt.timeout = 0 - } - - if (opt.timeout) { - target.timeoutHandler = setTimeout(function () { - onReject(id, 'timeout') - }, opt.timeout) - } - - responseCallbacks[id] = target - - sender({ - id: id, - type: 'invoke', - name: name, - args: args - }) - } - - function onInvoke(payload) { - var invoker = invokers[payload.name] - if (invoker) { - setTimeout(function () { - invoker(payload.id, payload.args) - }, 15) - } - } - - function onResolve(id, result) { - var target = responseCallbacks[id] - if (target) { - clearTimeout(target.timeoutHandler) - delete responseCallbacks[id] - setTimeout(function () { - target.callback(null, result) - }, 15) - } - } - - function onReject(id, result) { - var target = responseCallbacks[id] - if (target) { - clearTimeout(target.timeoutHandler) - delete responseCallbacks[id] - setTimeout(function () { - target(result, null) - }, 15) - } - } - - function onMessage(payload) { - if (typeof payload === 'string') { - return - } - - // there are two types of payload - // invoke: { type: 'payload', id, name, args } - // resolve: { type: 'resolve', id, result } - // reject: { type: 'reject', id, result } - switch(payload.type) { - case 'invoke': - onInvoke(payload) - break - case 'resolve': - onResolve(payload.id, payload.result) - break - case 'reject': - onReject(payload.id, payload.result) - default: - //ignore - } - } - - //init this method register and attaches rpc to WebViewBridge. - //you can either check whether WebViewBridge.rpc is availebe or - //simply register to `webviewbridge:rpc` event. - function init(WebViewBridge) { - var rpc = {} - var sender = WebViewBridge.send - - window.removeEventListener('webviewbridge:init', init) - WebViewBridge.addMessageListener(onMessage) - - rpc.register = function (name, fn) { - register(sender, name, fn) - } - - rpc.invoke = function (name, args, opt, callback) { - invoke(sender, name, args, opt, callback) - } - - WebViewBridge.rpc = rpc - WebViewBridge.__dispatch__('webviewbridge:rpc', rpc) - } - - if (window.WebViewBridge) { - init(window.WebViewBridge) - } else { - window.addEventListener('webviewbridge:init', init) - } -}()) From 9c7e99c2cabfa9822bda3e6a13e7a0a84de678a7 Mon Sep 17 00:00:00 2001 From: eladgel Date: Sun, 9 Oct 2016 14:19:08 +0300 Subject: [PATCH 33/77] working like a mother pupper --- .idea/workspace.xml | 176 ++-- examples/rpc/.buckconfig | 6 - examples/rpc/.flowconfig | 58 -- examples/rpc/.gitignore | 41 - examples/rpc/.watchmanconfig | 1 - examples/rpc/android/app/BUCK | 66 -- examples/rpc/android/app/build.gradle | 140 --- examples/rpc/android/app/proguard-rules.pro | 66 -- .../android/app/src/main/AndroidManifest.xml | 31 - .../app/src/main/assets/WebViewBridge.js | 105 --- .../app/src/main/assets/WebViewBridgeRPC.js | 141 --- .../src/main/java/com/rpc/MainActivity.java | 15 - .../main/java/com/rpc/MainApplication.java | 37 - .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 3418 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 2206 -> 0 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 4842 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 7718 -> 0 bytes .../app/src/main/res/values/strings.xml | 3 - .../app/src/main/res/values/styles.xml | 8 - examples/rpc/android/build.gradle | 24 - examples/rpc/android/gradle.properties | 20 - .../android/gradle/wrapper/gradle-wrapper.jar | Bin 52266 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 - examples/rpc/android/gradlew | 164 ---- examples/rpc/android/gradlew.bat | 90 -- examples/rpc/android/keystores/BUCK | 8 - .../keystores/debug.keystore.properties | 4 - examples/rpc/android/settings.gradle | 4 - examples/rpc/app.js | 82 -- examples/rpc/index.android.js | 10 - examples/rpc/index.ios.js | 10 - .../rpc/ios/rpc.xcodeproj/project.pbxproj | 808 ------------------ .../xcshareddata/xcschemes/rpc.xcscheme | 115 --- examples/rpc/ios/rpc/AppDelegate.h | 16 - examples/rpc/ios/rpc/AppDelegate.m | 37 - .../rpc/ios/rpc/Base.lproj/LaunchScreen.xib | 42 - .../AppIcon.appiconset/Contents.json | 38 - examples/rpc/ios/rpc/Info.plist | 54 -- examples/rpc/ios/rpc/main.m | 18 - examples/rpc/ios/rpcTests/Info.plist | 24 - examples/rpc/ios/rpcTests/rpcTests.m | 70 -- examples/rpc/package.json | 13 - ios/RCTWebViewBridge.h | 7 +- ios/RCTWebViewBridge.m | 495 ++++++----- ios/RCTWebViewBridgeManager.h | 3 + ios/RCTWebViewBridgeManager.m | 49 +- lib/WebViewBridge/index.android.js | 392 --------- lib/WebViewBridge/index.ios.js | 578 ------------- lib/WebViewBridgeRPC/index.js | 53 -- lib/core/base64.js | 60 -- lib/core/rpc.js | 123 --- lib/index.js | 9 - 52 files changed, 416 insertions(+), 3904 deletions(-) delete mode 100644 examples/rpc/.buckconfig delete mode 100644 examples/rpc/.flowconfig delete mode 100644 examples/rpc/.gitignore delete mode 100644 examples/rpc/.watchmanconfig delete mode 100644 examples/rpc/android/app/BUCK delete mode 100644 examples/rpc/android/app/build.gradle delete mode 100644 examples/rpc/android/app/proguard-rules.pro delete mode 100644 examples/rpc/android/app/src/main/AndroidManifest.xml delete mode 100644 examples/rpc/android/app/src/main/assets/WebViewBridge.js delete mode 100644 examples/rpc/android/app/src/main/assets/WebViewBridgeRPC.js delete mode 100644 examples/rpc/android/app/src/main/java/com/rpc/MainActivity.java delete mode 100644 examples/rpc/android/app/src/main/java/com/rpc/MainApplication.java delete mode 100644 examples/rpc/android/app/src/main/res/mipmap-hdpi/ic_launcher.png delete mode 100644 examples/rpc/android/app/src/main/res/mipmap-mdpi/ic_launcher.png delete mode 100644 examples/rpc/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png delete mode 100644 examples/rpc/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png delete mode 100644 examples/rpc/android/app/src/main/res/values/strings.xml delete mode 100644 examples/rpc/android/app/src/main/res/values/styles.xml delete mode 100644 examples/rpc/android/build.gradle delete mode 100644 examples/rpc/android/gradle.properties delete mode 100644 examples/rpc/android/gradle/wrapper/gradle-wrapper.jar delete mode 100644 examples/rpc/android/gradle/wrapper/gradle-wrapper.properties delete mode 100755 examples/rpc/android/gradlew delete mode 100644 examples/rpc/android/gradlew.bat delete mode 100644 examples/rpc/android/keystores/BUCK delete mode 100644 examples/rpc/android/keystores/debug.keystore.properties delete mode 100644 examples/rpc/android/settings.gradle delete mode 100644 examples/rpc/app.js delete mode 100644 examples/rpc/index.android.js delete mode 100644 examples/rpc/index.ios.js delete mode 100644 examples/rpc/ios/rpc.xcodeproj/project.pbxproj delete mode 100644 examples/rpc/ios/rpc.xcodeproj/xcshareddata/xcschemes/rpc.xcscheme delete mode 100644 examples/rpc/ios/rpc/AppDelegate.h delete mode 100644 examples/rpc/ios/rpc/AppDelegate.m delete mode 100644 examples/rpc/ios/rpc/Base.lproj/LaunchScreen.xib delete mode 100644 examples/rpc/ios/rpc/Images.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 examples/rpc/ios/rpc/Info.plist delete mode 100644 examples/rpc/ios/rpc/main.m delete mode 100644 examples/rpc/ios/rpcTests/Info.plist delete mode 100644 examples/rpc/ios/rpcTests/rpcTests.m delete mode 100644 examples/rpc/package.json delete mode 100644 lib/WebViewBridge/index.android.js delete mode 100644 lib/WebViewBridge/index.ios.js delete mode 100644 lib/WebViewBridgeRPC/index.js delete mode 100644 lib/core/base64.js delete mode 100644 lib/core/rpc.js delete mode 100644 lib/index.js diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 8369c3bd..015ebec3 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -2,69 +2,58 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - @@ -85,7 +74,18 @@ - + + + + + + + + + + + + @@ -132,7 +132,7 @@ - + @@ -141,9 +141,33 @@ + + + + + + + + + + + + + + - + @@ -163,14 +187,14 @@ - - + + - + - + @@ -193,18 +217,26 @@ - + + + + + + + + - - + + - + - - + + + diff --git a/examples/rpc/.buckconfig b/examples/rpc/.buckconfig deleted file mode 100644 index 934256cb..00000000 --- a/examples/rpc/.buckconfig +++ /dev/null @@ -1,6 +0,0 @@ - -[android] - target = Google Inc.:Google APIs:23 - -[maven_repositories] - central = https://repo1.maven.org/maven2 diff --git a/examples/rpc/.flowconfig b/examples/rpc/.flowconfig deleted file mode 100644 index f565799e..00000000 --- a/examples/rpc/.flowconfig +++ /dev/null @@ -1,58 +0,0 @@ -[ignore] - -# We fork some components by platform. -.*/*[.]android.js - -# Ignore templates with `@flow` in header -.*/local-cli/generator.* - -# Ignore malformed json -.*/node_modules/y18n/test/.*\.json - -# Ignore the website subdir -/website/.* - -# Ignore BUCK generated dirs -/\.buckd/ - -# Ignore unexpected extra @providesModule -.*/node_modules/commoner/test/source/widget/share.js - -# Ignore duplicate module providers -# For RN Apps installed via npm, "Libraries" folder is inside node_modules/react-native but in the source repo it is in the root -.*/Libraries/react-native/React.js -.*/Libraries/react-native/ReactNative.js -.*/node_modules/jest-runtime/build/__tests__/.* - -[include] - -[libs] -node_modules/react-native/Libraries/react-native/react-native-interface.js -node_modules/react-native/flow -flow/ - -[options] -module.system=haste - -esproposal.class_static_fields=enable -esproposal.class_instance_fields=enable - -experimental.strict_type_args=true - -munge_underscores=true - -module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub' -module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' - -suppress_type=$FlowIssue -suppress_type=$FlowFixMe -suppress_type=$FixMe - -suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(3[0-2]\\|[1-2][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) -suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(3[0-2]\\|1[0-9]\\|[1-2][0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ -suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy - -unsafe.enable_getters_and_setters=true - -[version] -^0.32.0 diff --git a/examples/rpc/.gitignore b/examples/rpc/.gitignore deleted file mode 100644 index eb1535e4..00000000 --- a/examples/rpc/.gitignore +++ /dev/null @@ -1,41 +0,0 @@ -# OSX -# -.DS_Store - -# Xcode -# -build/ -*.pbxuser -!default.pbxuser -*.mode1v3 -!default.mode1v3 -*.mode2v3 -!default.mode2v3 -*.perspectivev3 -!default.perspectivev3 -xcuserdata -*.xccheckout -*.moved-aside -DerivedData -*.hmap -*.ipa -*.xcuserstate -project.xcworkspace - -# Android/IJ -# -*.iml -.idea -.gradle -local.properties - -# node.js -# -node_modules/ -npm-debug.log - -# BUCK -buck-out/ -\.buckd/ -android/app/libs -android/keystores/debug.keystore diff --git a/examples/rpc/.watchmanconfig b/examples/rpc/.watchmanconfig deleted file mode 100644 index 9e26dfee..00000000 --- a/examples/rpc/.watchmanconfig +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/examples/rpc/android/app/BUCK b/examples/rpc/android/app/BUCK deleted file mode 100644 index db1800ab..00000000 --- a/examples/rpc/android/app/BUCK +++ /dev/null @@ -1,66 +0,0 @@ -import re - -# To learn about Buck see [Docs](https://buckbuild.com/). -# To run your application with Buck: -# - install Buck -# - `npm start` - to start the packager -# - `cd android` -# - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` -# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck -# - `buck install -r android/app` - compile, install and run application -# - -lib_deps = [] -for jarfile in glob(['libs/*.jar']): - name = 'jars__' + re.sub(r'^.*/([^/]+)\.jar$', r'\1', jarfile) - lib_deps.append(':' + name) - prebuilt_jar( - name = name, - binary_jar = jarfile, - ) - -for aarfile in glob(['libs/*.aar']): - name = 'aars__' + re.sub(r'^.*/([^/]+)\.aar$', r'\1', aarfile) - lib_deps.append(':' + name) - android_prebuilt_aar( - name = name, - aar = aarfile, - ) - -android_library( - name = 'all-libs', - exported_deps = lib_deps -) - -android_library( - name = 'app-code', - srcs = glob([ - 'src/main/java/**/*.java', - ]), - deps = [ - ':all-libs', - ':build_config', - ':res', - ], -) - -android_build_config( - name = 'build_config', - package = 'com.rpc', -) - -android_resource( - name = 'res', - res = 'src/main/res', - package = 'com.rpc', -) - -android_binary( - name = 'app', - package_type = 'debug', - manifest = 'src/main/AndroidManifest.xml', - keystore = '//android/keystores:debug', - deps = [ - ':app-code', - ], -) diff --git a/examples/rpc/android/app/build.gradle b/examples/rpc/android/app/build.gradle deleted file mode 100644 index c34cb8df..00000000 --- a/examples/rpc/android/app/build.gradle +++ /dev/null @@ -1,140 +0,0 @@ -apply plugin: "com.android.application" - -import com.android.build.OutputFile - -/** - * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets - * and bundleReleaseJsAndAssets). - * These basically call `react-native bundle` with the correct arguments during the Android build - * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the - * bundle directly from the development server. Below you can see all the possible configurations - * and their defaults. If you decide to add a configuration block, make sure to add it before the - * `apply from: "../../node_modules/react-native/react.gradle"` line. - * - * project.ext.react = [ - * // the name of the generated asset file containing your JS bundle - * bundleAssetName: "index.android.bundle", - * - * // the entry file for bundle generation - * entryFile: "index.android.js", - * - * // whether to bundle JS and assets in debug mode - * bundleInDebug: false, - * - * // whether to bundle JS and assets in release mode - * bundleInRelease: true, - * - * // whether to bundle JS and assets in another build variant (if configured). - * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants - * // The configuration property can be in the following formats - * // 'bundleIn${productFlavor}${buildType}' - * // 'bundleIn${buildType}' - * // bundleInFreeDebug: true, - * // bundleInPaidRelease: true, - * // bundleInBeta: true, - * - * // the root of your project, i.e. where "package.json" lives - * root: "../../", - * - * // where to put the JS bundle asset in debug mode - * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", - * - * // where to put the JS bundle asset in release mode - * jsBundleDirRelease: "$buildDir/intermediates/assets/release", - * - * // where to put drawable resources / React Native assets, e.g. the ones you use via - * // require('./image.png')), in debug mode - * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", - * - * // where to put drawable resources / React Native assets, e.g. the ones you use via - * // require('./image.png')), in release mode - * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", - * - * // by default the gradle tasks are skipped if none of the JS files or assets change; this means - * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to - * // date; if you have any other folders that you want to ignore for performance reasons (gradle - * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ - * // for example, you might want to remove it from here. - * inputExcludes: ["android/**", "ios/**"], - * - * // override which node gets called and with what additional arguments - * nodeExecutableAndArgs: ["node"] - * - * // supply additional arguments to the packager - * extraPackagerArgs: [] - * ] - */ - -apply from: "../../node_modules/react-native/react.gradle" - -/** - * Set this to true to create two separate APKs instead of one: - * - An APK that only works on ARM devices - * - An APK that only works on x86 devices - * The advantage is the size of the APK is reduced by about 4MB. - * Upload all the APKs to the Play Store and people will download - * the correct one based on the CPU architecture of their device. - */ -def enableSeparateBuildPerCPUArchitecture = false - -/** - * Run Proguard to shrink the Java bytecode in release builds. - */ -def enableProguardInReleaseBuilds = false - -android { - compileSdkVersion 23 - buildToolsVersion "23.0.1" - - defaultConfig { - applicationId "com.rpc" - minSdkVersion 16 - targetSdkVersion 22 - versionCode 1 - versionName "1.0" - ndk { - abiFilters "armeabi-v7a", "x86" - } - } - splits { - abi { - reset() - enable enableSeparateBuildPerCPUArchitecture - universalApk false // If true, also generate a universal APK - include "armeabi-v7a", "x86" - } - } - buildTypes { - release { - minifyEnabled enableProguardInReleaseBuilds - proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" - } - } - // applicationVariants are e.g. debug, release - applicationVariants.all { variant -> - variant.outputs.each { output -> - // For each separate APK per architecture, set a unique version code as described here: - // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits - def versionCodes = ["armeabi-v7a":1, "x86":2] - def abi = output.getFilter(OutputFile.ABI) - if (abi != null) { // null for the universal-debug, universal-release variants - output.versionCodeOverride = - versionCodes.get(abi) * 1048576 + defaultConfig.versionCode - } - } - } -} - -dependencies { - compile fileTree(dir: "libs", include: ["*.jar"]) - compile "com.android.support:appcompat-v7:23.0.1" - compile project(':react-native-webview-bridge') - compile "com.facebook.react:react-native:+" // From node_modules -} - -// Run this once to be able to run the application with BUCK -// puts all compile dependencies into folder libs for BUCK to use -task copyDownloadableDepsToLibs(type: Copy) { - from configurations.compile - into 'libs' -} diff --git a/examples/rpc/android/app/proguard-rules.pro b/examples/rpc/android/app/proguard-rules.pro deleted file mode 100644 index 48361a90..00000000 --- a/examples/rpc/android/app/proguard-rules.pro +++ /dev/null @@ -1,66 +0,0 @@ -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Disabling obfuscation is useful if you collect stack traces from production crashes -# (unless you are using a system that supports de-obfuscate the stack traces). --dontobfuscate - -# React Native - -# Keep our interfaces so they can be used by other ProGuard rules. -# See http://sourceforge.net/p/proguard/bugs/466/ --keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip --keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters --keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip - -# Do not strip any method/class that is annotated with @DoNotStrip --keep @com.facebook.proguard.annotations.DoNotStrip class * --keep @com.facebook.common.internal.DoNotStrip class * --keepclassmembers class * { - @com.facebook.proguard.annotations.DoNotStrip *; - @com.facebook.common.internal.DoNotStrip *; -} - --keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * { - void set*(***); - *** get*(); -} - --keep class * extends com.facebook.react.bridge.JavaScriptModule { *; } --keep class * extends com.facebook.react.bridge.NativeModule { *; } --keepclassmembers,includedescriptorclasses class * { native ; } --keepclassmembers class * { @com.facebook.react.uimanager.UIProp ; } --keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp ; } --keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup ; } - --dontwarn com.facebook.react.** - -# okhttp - --keepattributes Signature --keepattributes *Annotation* --keep class okhttp3.** { *; } --keep interface okhttp3.** { *; } --dontwarn okhttp3.** - -# okio - --keep class sun.misc.Unsafe { *; } --dontwarn java.nio.file.* --dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement --dontwarn okio.** diff --git a/examples/rpc/android/app/src/main/AndroidManifest.xml b/examples/rpc/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index 8b5dc259..00000000 --- a/examples/rpc/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/examples/rpc/android/app/src/main/assets/WebViewBridge.js b/examples/rpc/android/app/src/main/assets/WebViewBridge.js deleted file mode 100644 index 92ed4b84..00000000 --- a/examples/rpc/android/app/src/main/assets/WebViewBridge.js +++ /dev/null @@ -1,105 +0,0 @@ -(function () { - var WebViewBridge = window.WebViewBridge ? window.WebViewBridge : {} - var queue = [] - var onMessageListeners = {} - - //base64 encode and decode polyfil - //modified version of https://github.com/davidchambers/Base64.js - //you can replace the b64Encode and b64Decode for atob and btoa. - var b64ch = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' - function b64Encode(input) { - var output = '' - for ( - var block, charCode, idx = 0, map = b64ch; - input.charAt(idx | 0) || (map = '=', idx % 1); - output += map.charAt(63 & block >> 8 - idx % 1 * 8) - ) { - charCode = input.charCodeAt(idx += 3/4) - block = block << 8 | charCode - } - return output - } - - function b64Decode(input) { - var str = input.replace(/=+$/, '') - var output = '' - for ( - var bc = 0, bs, buffer, idx = 0; - buffer = str.charAt(idx++); - ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, - bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0 - ) { - buffer = b64ch.indexOf(buffer) - } - return output - } - - function encode(input) { - if (typeof input !== 'string') { - try { - input = JSON.stringify(input) - } catch(e) {} - } - input = unescape(encodeURIComponent(input)) - return b64Encode(input) - } - - function decode(input) { - var result = decodeURIComponent(escape(b64Decode(input))) - try { - result = JSON.parse(result) - } catch(e){} - return result - } - - function signalNative() { - if (WebViewBridge.nativeAndroidSend) { - WebViewBridge.nativeAndroidSend(WebViewBridge.__fetch__()) - } else { - window.location = 'rnwb://message' + new Date().getTime() - } - } - - function dispatch(name, value) { - var event = new CustomEvent(name, { - detail: value, - bubbles: true - }); - - setTimeout(function () { - window.document.dispatchEvent(event) - }, 15) - } - - WebViewBridge.__dispatch__ = dispatch - WebViewBridge.__push__ = function (encoded) { - //we need to release native caller as soon as possible - //that's why we are wrap this on setTimeout - setTimeout(function () { - var fn = null - var decoded = decode(encoded) - Object.keys(onMessageListeners).forEach(function (onMessage) { - fn = onMessageListeners[onMessage] - fn(decoded) - }) - }, 15) - } - WebViewBridge.__fetch__ = function () { - var val = JSON.stringify(queue) - queue = [] - return val - } - WebViewBridge.send = function (input) { - queue.push(encode(input)) - setTimeout(signalNative, 15) - } - WebViewBridge.addMessageListener = function(fn) { - onMessageListeners[fn] = fn - } - WebViewBridge.removeMessageListener = function (fn) { - delete onMessageListeners[fn] - } - - window.WebViewBridge = WebViewBridge - dispatch('webviewbridge:init', WebViewBridge) -}()) diff --git a/examples/rpc/android/app/src/main/assets/WebViewBridgeRPC.js b/examples/rpc/android/app/src/main/assets/WebViewBridgeRPC.js deleted file mode 100644 index fb0e8205..00000000 --- a/examples/rpc/android/app/src/main/assets/WebViewBridgeRPC.js +++ /dev/null @@ -1,141 +0,0 @@ -(function () { - 'use strict' - - var ids = 0 - var invokers = {} - var responseCallbacks = {} - - //invoker has accept 2 arguments - //fn(args, result). result is a function which accept an argument. - //that argument is being used to send to caller as a result value. - //by using this function, we can support both sync and async operation - function register(sender, name, fn) { - invokers[name] = function (id, args) { - fn(args, function (result) { - sender({ - id: id, - type: 'resolve', - result: result - }) - }, function (err) { - sender({ - id: id, - type: 'reject', - result: err - }) - }) - } - } - - function invoke(sender, name, args, opt, callback) { - var id = ++ids - var target = { - callback: callback - } - - if (opt) { - opt = {} - } - - if (!opt.timeout) { - opt.timeout = 0 - } - - if (opt.timeout) { - target.timeoutHandler = setTimeout(function () { - onReject(id, 'timeout') - }, opt.timeout) - } - - responseCallbacks[id] = target - - sender({ - id: id, - type: 'invoke', - name: name, - args: args - }) - } - - function onInvoke(payload) { - var invoker = invokers[payload.name] - if (invoker) { - setTimeout(function () { - invoker(payload.id, payload.args) - }, 15) - } - } - - function onResolve(id, result) { - var target = responseCallbacks[id] - if (target) { - clearTimeout(target.timeoutHandler) - delete responseCallbacks[id] - setTimeout(function () { - target.callback(null, result) - }, 15) - } - } - - function onReject(id, result) { - var target = responseCallbacks[id] - if (target) { - clearTimeout(target.timeoutHandler) - delete responseCallbacks[id] - setTimeout(function () { - target(result, null) - }, 15) - } - } - - function onMessage(payload) { - if (typeof payload === 'string') { - return - } - - // there are two types of payload - // invoke: { type: 'payload', id, name, args } - // resolve: { type: 'resolve', id, result } - // reject: { type: 'reject', id, result } - switch(payload.type) { - case 'invoke': - onInvoke(payload) - break - case 'resolve': - onResolve(payload.id, payload.result) - break - case 'reject': - onReject(payload.id, payload.result) - default: - //ignore - } - } - - //init this method register and attaches rpc to WebViewBridge. - //you can either check whether WebViewBridge.rpc is availebe or - //simply register to `webviewbridge:rpc` event. - function init(WebViewBridge) { - var rpc = {} - var sender = WebViewBridge.send - - window.removeEventListener('webviewbridge:init', init) - WebViewBridge.addMessageListener(onMessage) - - rpc.register = function (name, fn) { - register(sender, name, fn) - } - - rpc.invoke = function (name, args, opt, callback) { - invoke(sender, name, args, opt, callback) - } - - WebViewBridge.rpc = rpc - WebViewBridge.__dispatch__('webviewbridge:rpc', rpc) - } - - if (window.WebViewBridge) { - init(window.WebViewBridge) - } else { - window.addEventListener('webviewbridge:init', init) - } -}()) diff --git a/examples/rpc/android/app/src/main/java/com/rpc/MainActivity.java b/examples/rpc/android/app/src/main/java/com/rpc/MainActivity.java deleted file mode 100644 index 95c98ab3..00000000 --- a/examples/rpc/android/app/src/main/java/com/rpc/MainActivity.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.rpc; - -import com.facebook.react.ReactActivity; - -public class MainActivity extends ReactActivity { - - /** - * Returns the name of the main component registered from JavaScript. - * This is used to schedule rendering of the component. - */ - @Override - protected String getMainComponentName() { - return "rpc"; - } -} diff --git a/examples/rpc/android/app/src/main/java/com/rpc/MainApplication.java b/examples/rpc/android/app/src/main/java/com/rpc/MainApplication.java deleted file mode 100644 index ad076dfc..00000000 --- a/examples/rpc/android/app/src/main/java/com/rpc/MainApplication.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.rpc; - -import android.app.Application; -import android.util.Log; - -import com.facebook.react.ReactApplication; -import com.facebook.react.ReactInstanceManager; -import com.facebook.react.ReactNativeHost; -import com.facebook.react.ReactPackage; -import com.facebook.react.shell.MainReactPackage; -import com.github.alinz.reactnativewebviewbridge.WebViewBridgePackage; - -import java.util.Arrays; -import java.util.List; - -public class MainApplication extends Application implements ReactApplication { - - private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { - @Override - protected boolean getUseDeveloperSupport() { - return BuildConfig.DEBUG; - } - - @Override - protected List getPackages() { - return Arrays.asList( - new MainReactPackage(), - new WebViewBridgePackage() - ); - } - }; - - @Override - public ReactNativeHost getReactNativeHost() { - return mReactNativeHost; - } -} diff --git a/examples/rpc/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/examples/rpc/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index cde69bcccec65160d92116f20ffce4fce0b5245c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3418 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 diff --git a/examples/rpc/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/examples/rpc/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index bfa42f0e7b91d006d22352c9ff2f134e504e3c1d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4842 zcmZ{oXE5C1x5t0WvTCfdv7&7fy$d2l*k#q|U5FAbL??P!61}%ovaIM)mL!5G(V|6J zAtDH(OY|Du^}l!K&fFLG%sJ2JIp@rG=9y>Ci)Wq~U2RobsvA@Q0MM$dq4lq5{hy#9 zzgp+B{O(-=?1<7r0l>Q?>N6X%s~lmgrmqD6fjj_!c?AF`S0&6U06Z51fWOuNAe#jM z%pSN#J-Mp}`ICpL=qp~?u~Jj$6(~K_%)9}Bn(;pY0&;M00H9x2N23h=CpR7kr8A9X zU%oh4-E@i!Ac}P+&%vOPQ3warO9l!SCN)ixGW54Jsh!`>*aU)#&Mg7;#O_6xd5%I6 zneGSZL3Kn-4B^>#T7pVaIHs3^PY-N^v1!W=%gzfioIWosZ!BN?_M)OOux&6HCyyMf z3ToZ@_h75A33KyC!T)-zYC-bp`@^1n;w3~N+vQ0#4V7!f|JPMlWWJ@+Tg~8>1$GzLlHGuxS)w&NAF*&Y;ef`T^w4HP7GK%6UA8( z{&ALM(%!w2U7WFWwq8v4H3|0cOjdt7$JLh(;U8VcTG;R-vmR7?21nA?@@b+XPgJbD z*Y@v&dTqo5Bcp-dIQQ4@?-m{=7>`LZ{g4jvo$CE&(+7(rp#WShT9&9y>V#ikmXFau03*^{&d(AId0Jg9G;tc7K_{ivzBjqHuJx08cx<8U`z2JjtOK3( zvtuduBHha>D&iu#))5RKXm>(|$m=_;e?7ZveYy=J$3wjL>xPCte-MDcVW<;ng`nf= z9);CVVZjI-&UcSAlhDB{%0v$wPd=w6MBwsVEaV!hw~8G(rs`lw@|#AAHbyA&(I-7Y zFE&1iIGORsaskMqSYfX33U%&17oTszdHPjr&Sx(`IQzoccST*}!cU!ZnJ+~duBM6f z{Lf8PITt%uWZ zTY09Jm5t<2+Un~yC-%DYEP>c-7?=+|reXO4Cd^neCQ{&aP@yODLN8}TQAJ8ogsnkb zM~O>~3&n6d+ee`V_m@$6V`^ltL&?uwt|-afgd7BQ9Kz|g{B@K#qQ#$o4ut`9lQsYfHofccNoqE+`V zQ&UXP{X4=&Z16O_wCk9SFBQPKyu?<&B2zDVhI6%B$12c^SfcRYIIv!s1&r|8;xw5t zF~*-cE@V$vaB;*+91`CiN~1l8w${?~3Uy#c|D{S$I? zb!9y)DbLJ3pZ>!*+j=n@kOLTMr-T2>Hj^I~lml-a26UP1_?#!5S_a&v zeZ86(21wU0)4(h&W0iE*HaDlw+-LngX=}es#X$u*1v9>qR&qUGfADc7yz6$WN`cx9 zzB#!5&F%AK=ed|-eV6kb;R>Atp2Rk=g3lU6(IVEP3!;0YNAmqz=x|-mE&8u5W+zo7 z-QfwS6uzp9K4wC-Te-1~u?zPb{RjjIVoL1bQ=-HK_a_muB>&3I z*{e{sE_sI$CzyK-x>7abBc+uIZf?#e8;K_JtJexgpFEBMq92+Fm0j*DziUMras`o= zTzby8_XjyCYHeE@q&Q_7x?i|V9XY?MnSK;cLV?k>vf?!N87)gFPc9#XB?p)bEWGs$ zH>f$8?U7In{9@vsd%#sY5u!I$)g^%ZyutkNBBJ0eHQeiR5!DlQbYZJ-@09;c?IP7A zx>P=t*xm1rOqr@ec>|ziw@3e$ymK7YSXtafMk30i?>>1lC>LLK1~JV1n6EJUGJT{6 zWP4A(129xkvDP09j<3#1$T6j6$mZaZ@vqUBBM4Pi!H>U8xvy`bkdSNTGVcfkk&y8% z=2nfA@3kEaubZ{1nwTV1gUReza>QX%_d}x&2`jE*6JZN{HZtXSr{{6v6`r47MoA~R zejyMpeYbJ$F4*+?*=Fm7E`S_rUC0v+dHTlj{JnkW-_eRa#9V`9o!8yv_+|lB4*+p1 zUI-t)X$J{RRfSrvh80$OW_Wwp>`4*iBr|oodPt*&A9!SO(x|)UgtVvETLuLZ<-vRp z&zAubgm&J8Pt647V?Qxh;`f6E#Zgx5^2XV($YMV7;Jn2kx6aJn8T>bo?5&;GM4O~| zj>ksV0U}b}wDHW`pgO$L@Hjy2`a)T}s@(0#?y3n zj;yjD76HU&*s!+k5!G4<3{hKah#gBz8HZ6v`bmURyDi(wJ!C7+F%bKnRD4=q{(Fl0 zOp*r}F`6~6HHBtq$afFuXsGAk58!e?O(W$*+3?R|cDO88<$~pg^|GRHN}yml3WkbL zzSH*jmpY=`g#ZX?_XT`>-`INZ#d__BJ)Ho^&ww+h+3>y8Z&T*EI!mtgEqiofJ@5&E z6M6a}b255hCw6SFJ4q(==QN6CUE3GYnfjFNE+x8T(+J!C!?v~Sbh`Sl_0CJ;vvXsP z5oZRiPM-Vz{tK(sJM~GI&VRbBOd0JZmGzqDrr9|?iPT(qD#M*RYb$>gZi*i)xGMD`NbmZt;ky&FR_2+YqpmFb`8b`ry;}D+y&WpUNd%3cfuUsb8 z7)1$Zw?bm@O6J1CY9UMrle_BUM<$pL=YI^DCz~!@p25hE&g62n{j$?UsyYjf#LH~b z_n!l6Z(J9daalVYSlA?%=mfp(!e+Hk%%oh`t%0`F`KR*b-Zb=7SdtDS4`&&S@A)f>bKC7vmRWwT2 zH}k+2Hd7@>jiHwz^GrOeU8Y#h?YK8>a*vJ#s|8-uX_IYp*$9Y=W_Edf%$V4>w;C3h z&>ZDGavV7UA@0QIQV$&?Z_*)vj{Q%z&(IW!b-!MVDGytRb4DJJV)(@WG|MbhwCx!2 z6QJMkl^4ju9ou8Xjb*pv=Hm8DwYsw23wZqQFUI)4wCMjPB6o8yG7@Sn^5%fmaFnfD zSxp8R-L({J{p&cR7)lY+PA9#8Bx87;mB$zXCW8VDh0&g#@Z@lktyArvzgOn&-zerA zVEa9h{EYvWOukwVUGWUB5xr4{nh}a*$v^~OEasKj)~HyP`YqeLUdN~f!r;0dV7uho zX)iSYE&VG67^NbcP5F*SIE@T#=NVjJ1=!Mn!^oeCg1L z?lv_%(ZEe%z*pGM<(UG{eF1T(#PMw}$n0aihzGoJAP^UceQMiBuE8Y`lZ|sF2_h_6 zQw*b*=;2Ey_Flpfgsr4PimZ~8G~R(vU}^Zxmri5)l?N>M_dWyCsjZw<+a zqjmL0l*}PXNGUOh)YxP>;ENiJTd|S^%BARx9D~%7x?F6u4K(Bx0`KK2mianotlX^9 z3z?MW7Coqy^ol0pH)Z3+GwU|Lyuj#7HCrqs#01ZF&KqEg!olHc$O#Wn>Ok_k2`zoD z+LYbxxVMf<(d2OkPIm8Xn>bwFsF6m8@i7PA$sdK~ZA4|ic?k*q2j1YQ>&A zjPO%H@H(h`t+irQqx+e)ll9LGmdvr1zXV;WTi}KCa>K82n90s|K zi`X}C*Vb12p?C-sp5maVDP5{&5$E^k6~BuJ^UxZaM=o+@(LXBWChJUJ|KEckEJTZL zI2K&Nd$U65YoF3_J6+&YU4uKGMq2W6ZQ%BG>4HnIM?V;;Ohes{`Ucs56ue^7@D7;4 z+EsFB)a_(%K6jhxND}n!UBTuF3wfrvll|mp7)3wi&2?LW$+PJ>2)2C-6c@O&lKAn zOm=$x*dn&dI8!QCb(ul|t3oDY^MjHqxl~lp{p@#C%Od-U4y@NQ4=`U!YjK$7b=V}D z%?E40*f8DVrvV2nV>`Z3f5yuz^??$#3qR#q6F($w>kmKK`x21VmX=9kb^+cPdBY2l zGkIZSf%C+`2nj^)j zo}g}v;5{nk<>%xj-2OqDbJ3S`7|tQWqdvJdgiL{1=w0!qS9$A`w9Qm7>N0Y*Ma%P_ zr@fR4>5u{mKwgZ33Xs$RD6(tcVH~Mas-87Fd^6M6iuV^_o$~ql+!eBIw$U)lzl`q9 z=L6zVsZzi0IIW=DT&ES9HajKhb5lz4yQxT-NRBLv_=2sn7WFX&Wp6Y!&}P+%`!A;s zrCwXO3}jrdA7mB`h~N~HT64TM{R$lNj*~ekqSP^n9P~z;P zWPlRPz0h6za8-P>!ARb+A1-r>8VF*xhrGa8W6J$p*wy`ULrD$CmYV7Gt^scLydQWbo7XN-o9X1i7;l+J_8Ncu zc=EX&dg`GRo4==cz2d_Rz28oLS`Suf6OCp~f{0-aQ`t5YZ=!CAMc6-RZw#}A%;s44 znf2`6gcgm=0SezTH9h+JzeR3Lcm;8?*@+?FDfguK^9)z(Z`I!RKrSAI?H~4et6GTkz07Qgq4B6%Q*8Y0yPc4x z8(^YwtZjYIeOvVLey#>@$UzIciJ#x0pJLFg=8UaZv%-&?Yzp7gWNIo_x^(d75=x2c zv|LQ`HrKP(8TqFxTiP5gdT2>aTN0S7XW*pilASS$UkJ2*n+==D)0mgTGxv43t61fr z47GkfMnD-zSH@|mZ26r*d3WEtr+l-xH@L}BM)~ThoMvKqGw=Ifc}BdkL$^wC}=(XSf4YpG;sA9#OSJf)V=rs#Wq$?Wj+nTlu$YXn yn3SQon5>kvtkl(BT2@T#Mvca!|08g9w{vm``2PjZHg=b<1c17-HkzPl9sXa)&-Ts$ diff --git a/examples/rpc/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/examples/rpc/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 324e72cdd7480cb983fa1bcc7ce686e51ef87fe7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7718 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 diff --git a/examples/rpc/android/app/src/main/res/values/strings.xml b/examples/rpc/android/app/src/main/res/values/strings.xml deleted file mode 100644 index 896c66e3..00000000 --- a/examples/rpc/android/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - rpc - diff --git a/examples/rpc/android/app/src/main/res/values/styles.xml b/examples/rpc/android/app/src/main/res/values/styles.xml deleted file mode 100644 index 319eb0ca..00000000 --- a/examples/rpc/android/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - diff --git a/examples/rpc/android/build.gradle b/examples/rpc/android/build.gradle deleted file mode 100644 index 7ab08063..00000000 --- a/examples/rpc/android/build.gradle +++ /dev/null @@ -1,24 +0,0 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. - -buildscript { - repositories { - jcenter() - } - dependencies { - classpath 'com.android.tools.build:gradle:2.2.0' - - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files - } -} - -allprojects { - repositories { - mavenLocal() - jcenter() - maven { - // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm - url "$rootDir/../node_modules/react-native/android" - } - } -} diff --git a/examples/rpc/android/gradle.properties b/examples/rpc/android/gradle.properties deleted file mode 100644 index 1fd964e9..00000000 --- a/examples/rpc/android/gradle.properties +++ /dev/null @@ -1,20 +0,0 @@ -# Project-wide Gradle settings. - -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. - -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html - -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. -# Default value: -Xmx10248m -XX:MaxPermSize=256m -# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 - -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true - -android.useDeprecatedNdk=true diff --git a/examples/rpc/android/gradle/wrapper/gradle-wrapper.jar b/examples/rpc/android/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index b5166dad4d90021f6a0b45268c0755719f1d5cd4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52266 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
izQycD<<50V$c*N*^@OdG*H91fYg5#Pj5}j& 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 z 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 diff --git a/examples/rpc/android/gradle/wrapper/gradle-wrapper.properties b/examples/rpc/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 6ef9a9a7..00000000 --- a/examples/rpc/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Fri Sep 30 01:45:24 EDT 2016 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/examples/rpc/android/gradlew b/examples/rpc/android/gradlew deleted file mode 100755 index 91a7e269..00000000 --- a/examples/rpc/android/gradlew +++ /dev/null @@ -1,164 +0,0 @@ -#!/usr/bin/env bash - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn ( ) { - echo "$*" -} - -die ( ) { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; -esac - -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") -} -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" - -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/examples/rpc/android/gradlew.bat b/examples/rpc/android/gradlew.bat deleted file mode 100644 index aec99730..00000000 --- a/examples/rpc/android/gradlew.bat +++ /dev/null @@ -1,90 +0,0 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/examples/rpc/android/keystores/BUCK b/examples/rpc/android/keystores/BUCK deleted file mode 100644 index 15da20e6..00000000 --- a/examples/rpc/android/keystores/BUCK +++ /dev/null @@ -1,8 +0,0 @@ -keystore( - name = 'debug', - store = 'debug.keystore', - properties = 'debug.keystore.properties', - visibility = [ - 'PUBLIC', - ], -) diff --git a/examples/rpc/android/keystores/debug.keystore.properties b/examples/rpc/android/keystores/debug.keystore.properties deleted file mode 100644 index 121bfb49..00000000 --- a/examples/rpc/android/keystores/debug.keystore.properties +++ /dev/null @@ -1,4 +0,0 @@ -key.store=debug.keystore -key.alias=androiddebugkey -key.store.password=android -key.alias.password=android diff --git a/examples/rpc/android/settings.gradle b/examples/rpc/android/settings.gradle deleted file mode 100644 index 62831da7..00000000 --- a/examples/rpc/android/settings.gradle +++ /dev/null @@ -1,4 +0,0 @@ -rootProject.name = 'rpc' - -include ':app', ':react-native-webview-bridge' -project(':react-native-webview-bridge').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webview-bridge/android') diff --git a/examples/rpc/app.js b/examples/rpc/app.js deleted file mode 100644 index 224d456d..00000000 --- a/examples/rpc/app.js +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Sample React Native App - * https://github.com/facebook/react-native - * @flow - */ - -import React, { Component } from 'react'; -import { - StyleSheet, - Text, - View -} from 'react-native'; - -import { WebViewBridge, WebViewBridgeRPC } from 'react-native-webview-bridge' - -const injectScript = ` - (function () { - - function test() { - var rpc = WebViewBridge.rpc - - rpc.invoke('onNativeCall', null, { timeout: 0 }, function (err, resp) { - alert(resp) - }) - - rpc.register('onWebViewCall', function (args, resolve, reject) { - resolve('this is web call') - }) - } - - if (window.WebViewBridge && window.WebViewBridge.rpc) { - test() - } else { - window.addEventListener('webviewbridge:rpc', function () { - test() - }) - } - }()); -`; - -export default class app extends Component { - constructor(props, context) { - super(props, context) - - this.bridgeRef = null - } - - componentDidMount() { - this.bridgeRef.register('onNativeCall', this.onNativeCall) - } - - onNativeCall = (args, resolve, reject) => { - resolve('native call') - - setTimeout(() => { - this.bridgeRef.invoke('onWebViewCall', null, { timeout: 0 }, function (err, resp) { - console.log(err, resp) - }) - }, 3000) - } - - render() { - return ( - this.bridgeRef = ref} - source={{uri: 'https://github.com/facebook/react-native'}} - style={{marginTop: 20}} - javaScriptEnabled={true} - injectedJavaScript={injectScript} - /> - ); - } -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - backgroundColor: '#F5FCFF', - } -}); diff --git a/examples/rpc/index.android.js b/examples/rpc/index.android.js deleted file mode 100644 index f0e056d8..00000000 --- a/examples/rpc/index.android.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Sample React Native App - * https://github.com/facebook/react-native - * @flow - */ - -import { AppRegistry } from 'react-native'; -import app from './app' - -AppRegistry.registerComponent('rpc', () => app); diff --git a/examples/rpc/index.ios.js b/examples/rpc/index.ios.js deleted file mode 100644 index f0e056d8..00000000 --- a/examples/rpc/index.ios.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Sample React Native App - * https://github.com/facebook/react-native - * @flow - */ - -import { AppRegistry } from 'react-native'; -import app from './app' - -AppRegistry.registerComponent('rpc', () => app); diff --git a/examples/rpc/ios/rpc.xcodeproj/project.pbxproj b/examples/rpc/ios/rpc.xcodeproj/project.pbxproj deleted file mode 100644 index c5f60efa..00000000 --- a/examples/rpc/ios/rpc.xcodeproj/project.pbxproj +++ /dev/null @@ -1,808 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; }; - 00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */; }; - 00C302E81ABCBA2D00DB3ED1 /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */; }; - 00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */; }; - 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */; }; - 00E356F31AD99517003FC87E /* rpcTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* rpcTests.m */; }; - 133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 78C398B91ACF4ADC00677621 /* libRCTLinking.a */; }; - 139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */; }; - 139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */; }; - 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; - 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; }; - 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; - 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; - 140ED2AC1D01E1AD002B40FF /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; }; - 146834051AC3E58100842450 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; }; - 41538B171D9B4E41002A1C00 /* WebViewBridgeRPC.js in Resources */ = {isa = PBXBuildFile; fileRef = 41538B151D9B4E41002A1C00 /* WebViewBridgeRPC.js */; }; - 41B4735E1D98EAD4002C71D7 /* libReact-Native-Webview-Bridge.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 418B30531D98E6EC00716CA2 /* libReact-Native-Webview-Bridge.a */; }; - 41C983301D9C71F400EEF6BE /* WebViewBridge.js in Resources */ = {isa = PBXBuildFile; fileRef = 41C9832F1D9C71F400EEF6BE /* WebViewBridge.js */; }; - 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 00C302AB1ABCB8CE00DB3ED1 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 134814201AA4EA6300B7C361; - remoteInfo = RCTActionSheet; - }; - 00C302B91ABCB90400DB3ED1 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 134814201AA4EA6300B7C361; - remoteInfo = RCTGeolocation; - }; - 00C302BF1ABCB91800DB3ED1 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 58B5115D1A9E6B3D00147676; - remoteInfo = RCTImage; - }; - 00C302DB1ABCB9D200DB3ED1 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 58B511DB1A9E6C8500147676; - remoteInfo = RCTNetwork; - }; - 00C302E31ABCB9EE00DB3ED1 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 832C81801AAF6DEF007FA2F7; - remoteInfo = RCTVibration; - }; - 00E356F41AD99517003FC87E /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 13B07F861A680F5B00A75B9A; - remoteInfo = rpc; - }; - 139105C01AF99BAD00B5F7CC /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 134814201AA4EA6300B7C361; - remoteInfo = RCTSettings; - }; - 139FDEF31B06529B00C62182 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 3C86DF461ADF2C930047B81A; - remoteInfo = RCTWebSocket; - }; - 146834031AC3E56700842450 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 83CBBA2E1A601D0E00E9B192; - remoteInfo = React; - }; - 418B30521D98E6EC00716CA2 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 41BC38FF1D986EDA00018E84 /* React-Native-Webview-Bridge.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 4114DC4C1C187C3A003CD988; - remoteInfo = "React-Native-Webview-Bridge"; - }; - 78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 134814201AA4EA6300B7C361; - remoteInfo = RCTLinking; - }; - 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 58B5119B1A9E6C1200147676; - remoteInfo = RCTText; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXFileReference section */ - 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = ""; }; - 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = "../node_modules/react-native/Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj"; sourceTree = ""; }; - 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = "../node_modules/react-native/Libraries/Geolocation/RCTGeolocation.xcodeproj"; sourceTree = ""; }; - 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = "../node_modules/react-native/Libraries/Image/RCTImage.xcodeproj"; sourceTree = ""; }; - 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = "../node_modules/react-native/Libraries/Network/RCTNetwork.xcodeproj"; sourceTree = ""; }; - 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = "../node_modules/react-native/Libraries/Vibration/RCTVibration.xcodeproj"; sourceTree = ""; }; - 00E356EE1AD99517003FC87E /* rpcTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = rpcTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 00E356F21AD99517003FC87E /* rpcTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = rpcTests.m; sourceTree = ""; }; - 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = "../node_modules/react-native/Libraries/Settings/RCTSettings.xcodeproj"; sourceTree = ""; }; - 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = "../node_modules/react-native/Libraries/WebSocket/RCTWebSocket.xcodeproj"; sourceTree = ""; }; - 13B07F961A680F5B00A75B9A /* rpc.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = rpc.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = rpc/AppDelegate.h; sourceTree = ""; }; - 13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = rpc/AppDelegate.m; sourceTree = ""; }; - 13B07FB21A68108700A75B9A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; - 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = rpc/Images.xcassets; sourceTree = ""; }; - 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = rpc/Info.plist; sourceTree = ""; }; - 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = rpc/main.m; sourceTree = ""; }; - 146833FF1AC3E56700842450 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = "../node_modules/react-native/React/React.xcodeproj"; sourceTree = ""; }; - 41538B151D9B4E41002A1C00 /* WebViewBridgeRPC.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = WebViewBridgeRPC.js; path = "../node_modules/react-native-webview-bridge/scripts/WebViewBridgeRPC.js"; sourceTree = ""; }; - 41BC38FF1D986EDA00018E84 /* React-Native-Webview-Bridge.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = "React-Native-Webview-Bridge.xcodeproj"; path = "../node_modules/react-native-webview-bridge/ios/React-Native-Webview-Bridge.xcodeproj"; sourceTree = ""; }; - 41C9832F1D9C71F400EEF6BE /* WebViewBridge.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = WebViewBridge.js; path = "../node_modules/react-native-webview-bridge/scripts/WebViewBridge.js"; sourceTree = ""; }; - 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = ""; }; - 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 00E356EB1AD99517003FC87E /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 140ED2AC1D01E1AD002B40FF /* libReact.a in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 41B4735E1D98EAD4002C71D7 /* libReact-Native-Webview-Bridge.a in Frameworks */, - 146834051AC3E58100842450 /* libReact.a in Frameworks */, - 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */, - 00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */, - 00C302E81ABCBA2D00DB3ED1 /* libRCTImage.a in Frameworks */, - 133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */, - 00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */, - 139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */, - 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */, - 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */, - 139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 00C302A81ABCB8CE00DB3ED1 /* Products */ = { - isa = PBXGroup; - children = ( - 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */, - ); - name = Products; - sourceTree = ""; - }; - 00C302B61ABCB90400DB3ED1 /* Products */ = { - isa = PBXGroup; - children = ( - 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */, - ); - name = Products; - sourceTree = ""; - }; - 00C302BC1ABCB91800DB3ED1 /* Products */ = { - isa = PBXGroup; - children = ( - 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */, - ); - name = Products; - sourceTree = ""; - }; - 00C302D41ABCB9D200DB3ED1 /* Products */ = { - isa = PBXGroup; - children = ( - 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */, - ); - name = Products; - sourceTree = ""; - }; - 00C302E01ABCB9EE00DB3ED1 /* Products */ = { - isa = PBXGroup; - children = ( - 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */, - ); - name = Products; - sourceTree = ""; - }; - 00E356EF1AD99517003FC87E /* rpcTests */ = { - isa = PBXGroup; - children = ( - 00E356F21AD99517003FC87E /* rpcTests.m */, - 00E356F01AD99517003FC87E /* Supporting Files */, - ); - path = rpcTests; - sourceTree = ""; - }; - 00E356F01AD99517003FC87E /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 00E356F11AD99517003FC87E /* Info.plist */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - 139105B71AF99BAD00B5F7CC /* Products */ = { - isa = PBXGroup; - children = ( - 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */, - ); - name = Products; - sourceTree = ""; - }; - 139FDEE71B06529A00C62182 /* Products */ = { - isa = PBXGroup; - children = ( - 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */, - ); - name = Products; - sourceTree = ""; - }; - 13B07FAE1A68108700A75B9A /* rpc */ = { - isa = PBXGroup; - children = ( - 41C9832F1D9C71F400EEF6BE /* WebViewBridge.js */, - 41538B151D9B4E41002A1C00 /* WebViewBridgeRPC.js */, - 008F07F21AC5B25A0029DE68 /* main.jsbundle */, - 13B07FAF1A68108700A75B9A /* AppDelegate.h */, - 13B07FB01A68108700A75B9A /* AppDelegate.m */, - 13B07FB51A68108700A75B9A /* Images.xcassets */, - 13B07FB61A68108700A75B9A /* Info.plist */, - 13B07FB11A68108700A75B9A /* LaunchScreen.xib */, - 13B07FB71A68108700A75B9A /* main.m */, - ); - name = rpc; - sourceTree = ""; - }; - 146834001AC3E56700842450 /* Products */ = { - isa = PBXGroup; - children = ( - 146834041AC3E56700842450 /* libReact.a */, - ); - name = Products; - sourceTree = ""; - }; - 418B304F1D98E6EC00716CA2 /* Products */ = { - isa = PBXGroup; - children = ( - 418B30531D98E6EC00716CA2 /* libReact-Native-Webview-Bridge.a */, - ); - name = Products; - sourceTree = ""; - }; - 78C398B11ACF4ADC00677621 /* Products */ = { - isa = PBXGroup; - children = ( - 78C398B91ACF4ADC00677621 /* libRCTLinking.a */, - ); - name = Products; - sourceTree = ""; - }; - 832341AE1AAA6A7D00B99B32 /* Libraries */ = { - isa = PBXGroup; - children = ( - 41BC38FF1D986EDA00018E84 /* React-Native-Webview-Bridge.xcodeproj */, - 146833FF1AC3E56700842450 /* React.xcodeproj */, - 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */, - 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */, - 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */, - 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */, - 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */, - 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */, - 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */, - 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */, - 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */, - ); - name = Libraries; - sourceTree = ""; - }; - 832341B11AAA6A8300B99B32 /* Products */ = { - isa = PBXGroup; - children = ( - 832341B51AAA6A8300B99B32 /* libRCTText.a */, - ); - name = Products; - sourceTree = ""; - }; - 83CBB9F61A601CBA00E9B192 = { - isa = PBXGroup; - children = ( - 13B07FAE1A68108700A75B9A /* rpc */, - 832341AE1AAA6A7D00B99B32 /* Libraries */, - 00E356EF1AD99517003FC87E /* rpcTests */, - 83CBBA001A601CBA00E9B192 /* Products */, - ); - indentWidth = 2; - sourceTree = ""; - tabWidth = 2; - }; - 83CBBA001A601CBA00E9B192 /* Products */ = { - isa = PBXGroup; - children = ( - 13B07F961A680F5B00A75B9A /* rpc.app */, - 00E356EE1AD99517003FC87E /* rpcTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 00E356ED1AD99517003FC87E /* rpcTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "rpcTests" */; - buildPhases = ( - 00E356EA1AD99517003FC87E /* Sources */, - 00E356EB1AD99517003FC87E /* Frameworks */, - 00E356EC1AD99517003FC87E /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 00E356F51AD99517003FC87E /* PBXTargetDependency */, - ); - name = rpcTests; - productName = rpcTests; - productReference = 00E356EE1AD99517003FC87E /* rpcTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - 13B07F861A680F5B00A75B9A /* rpc */ = { - isa = PBXNativeTarget; - buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "rpc" */; - buildPhases = ( - 13B07F871A680F5B00A75B9A /* Sources */, - 13B07F8C1A680F5B00A75B9A /* Frameworks */, - 13B07F8E1A680F5B00A75B9A /* Resources */, - 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = rpc; - productName = "Hello World"; - productReference = 13B07F961A680F5B00A75B9A /* rpc.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 83CBB9F71A601CBA00E9B192 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0610; - ORGANIZATIONNAME = Facebook; - TargetAttributes = { - 00E356ED1AD99517003FC87E = { - CreatedOnToolsVersion = 6.2; - TestTargetID = 13B07F861A680F5B00A75B9A; - }; - }; - }; - buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "rpc" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 83CBB9F61A601CBA00E9B192; - productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; - projectDirPath = ""; - projectReferences = ( - { - ProductGroup = 00C302A81ABCB8CE00DB3ED1 /* Products */; - ProjectRef = 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */; - }, - { - ProductGroup = 00C302B61ABCB90400DB3ED1 /* Products */; - ProjectRef = 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */; - }, - { - ProductGroup = 00C302BC1ABCB91800DB3ED1 /* Products */; - ProjectRef = 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */; - }, - { - ProductGroup = 78C398B11ACF4ADC00677621 /* Products */; - ProjectRef = 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */; - }, - { - ProductGroup = 00C302D41ABCB9D200DB3ED1 /* Products */; - ProjectRef = 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */; - }, - { - ProductGroup = 139105B71AF99BAD00B5F7CC /* Products */; - ProjectRef = 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */; - }, - { - ProductGroup = 832341B11AAA6A8300B99B32 /* Products */; - ProjectRef = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */; - }, - { - ProductGroup = 00C302E01ABCB9EE00DB3ED1 /* Products */; - ProjectRef = 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */; - }, - { - ProductGroup = 139FDEE71B06529A00C62182 /* Products */; - ProjectRef = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */; - }, - { - ProductGroup = 418B304F1D98E6EC00716CA2 /* Products */; - ProjectRef = 41BC38FF1D986EDA00018E84 /* React-Native-Webview-Bridge.xcodeproj */; - }, - { - ProductGroup = 146834001AC3E56700842450 /* Products */; - ProjectRef = 146833FF1AC3E56700842450 /* React.xcodeproj */; - }, - ); - projectRoot = ""; - targets = ( - 13B07F861A680F5B00A75B9A /* rpc */, - 00E356ED1AD99517003FC87E /* rpcTests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXReferenceProxy section */ - 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libRCTActionSheet.a; - remoteRef = 00C302AB1ABCB8CE00DB3ED1 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libRCTGeolocation.a; - remoteRef = 00C302B91ABCB90400DB3ED1 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libRCTImage.a; - remoteRef = 00C302BF1ABCB91800DB3ED1 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libRCTNetwork.a; - remoteRef = 00C302DB1ABCB9D200DB3ED1 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libRCTVibration.a; - remoteRef = 00C302E31ABCB9EE00DB3ED1 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libRCTSettings.a; - remoteRef = 139105C01AF99BAD00B5F7CC /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libRCTWebSocket.a; - remoteRef = 139FDEF31B06529B00C62182 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 146834041AC3E56700842450 /* libReact.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libReact.a; - remoteRef = 146834031AC3E56700842450 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 418B30531D98E6EC00716CA2 /* libReact-Native-Webview-Bridge.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = "libReact-Native-Webview-Bridge.a"; - remoteRef = 418B30521D98E6EC00716CA2 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 78C398B91ACF4ADC00677621 /* libRCTLinking.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libRCTLinking.a; - remoteRef = 78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 832341B51AAA6A8300B99B32 /* libRCTText.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libRCTText.a; - remoteRef = 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; -/* End PBXReferenceProxy section */ - -/* Begin PBXResourcesBuildPhase section */ - 00E356EC1AD99517003FC87E /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 13B07F8E1A680F5B00A75B9A /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 41538B171D9B4E41002A1C00 /* WebViewBridgeRPC.js in Resources */, - 41C983301D9C71F400EEF6BE /* WebViewBridge.js in Resources */, - 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, - 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Bundle React Native code and images"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "export NODE_BINARY=node\n../node_modules/react-native/packager/react-native-xcode.sh"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 00E356EA1AD99517003FC87E /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 00E356F31AD99517003FC87E /* rpcTests.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 13B07F871A680F5B00A75B9A /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */, - 13B07FC11A68108700A75B9A /* main.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 00E356F51AD99517003FC87E /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 13B07F861A680F5B00A75B9A /* rpc */; - targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin PBXVariantGroup section */ - 13B07FB11A68108700A75B9A /* LaunchScreen.xib */ = { - isa = PBXVariantGroup; - children = ( - 13B07FB21A68108700A75B9A /* Base */, - ); - name = LaunchScreen.xib; - path = rpc; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 00E356F61AD99517003FC87E /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - INFOPLIST_FILE = rpcTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_NAME = "$(TARGET_NAME)"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/rpc.app/rpc"; - }; - name = Debug; - }; - 00E356F71AD99517003FC87E /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - COPY_PHASE_STRIP = NO; - INFOPLIST_FILE = rpcTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_NAME = "$(TARGET_NAME)"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/rpc.app/rpc"; - }; - name = Release; - }; - 13B07F941A680F5B00A75B9A /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CURRENT_PROJECT_VERSION = 1; - DEAD_CODE_STRIPPING = NO; - HEADER_SEARCH_PATHS = ( - "$(inherited)", - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/../node_modules/react-native/React/**", - ); - INFOPLIST_FILE = rpc/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - OTHER_LDFLAGS = ( - "$(inherited)", - "-ObjC", - "-lc++", - ); - PRODUCT_NAME = rpc; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Debug; - }; - 13B07F951A680F5B00A75B9A /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CURRENT_PROJECT_VERSION = 1; - HEADER_SEARCH_PATHS = ( - "$(inherited)", - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/../node_modules/react-native/React/**", - ); - INFOPLIST_FILE = rpc/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - OTHER_LDFLAGS = ( - "$(inherited)", - "-ObjC", - "-lc++", - ); - PRODUCT_NAME = rpc; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Release; - }; - 83CBBA201A601CBA00E9B192 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - HEADER_SEARCH_PATHS = ( - "$(inherited)", - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/../node_modules/react-native/React/**", - ); - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - }; - name = Debug; - }; - 83CBBA211A601CBA00E9B192 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = YES; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - HEADER_SEARCH_PATHS = ( - "$(inherited)", - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, - "$(SRCROOT)/../node_modules/react-native/React/**", - ); - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "rpcTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 00E356F61AD99517003FC87E /* Debug */, - 00E356F71AD99517003FC87E /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "rpc" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 13B07F941A680F5B00A75B9A /* Debug */, - 13B07F951A680F5B00A75B9A /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "rpc" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 83CBBA201A601CBA00E9B192 /* Debug */, - 83CBBA211A601CBA00E9B192 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; -} diff --git a/examples/rpc/ios/rpc.xcodeproj/xcshareddata/xcschemes/rpc.xcscheme b/examples/rpc/ios/rpc.xcodeproj/xcshareddata/xcschemes/rpc.xcscheme deleted file mode 100644 index ab967633..00000000 --- a/examples/rpc/ios/rpc.xcodeproj/xcshareddata/xcschemes/rpc.xcscheme +++ /dev/null @@ -1,115 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/rpc/ios/rpc/AppDelegate.h b/examples/rpc/ios/rpc/AppDelegate.h deleted file mode 100644 index a9654d5e..00000000 --- a/examples/rpc/ios/rpc/AppDelegate.h +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -#import - -@interface AppDelegate : UIResponder - -@property (nonatomic, strong) UIWindow *window; - -@end diff --git a/examples/rpc/ios/rpc/AppDelegate.m b/examples/rpc/ios/rpc/AppDelegate.m deleted file mode 100644 index a7128926..00000000 --- a/examples/rpc/ios/rpc/AppDelegate.m +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -#import "AppDelegate.h" - -#import "RCTBundleURLProvider.h" -#import "RCTRootView.h" - -@implementation AppDelegate - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions -{ - NSURL *jsCodeLocation; - - jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil]; - - RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation - moduleName:@"rpc" - initialProperties:nil - launchOptions:launchOptions]; - rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1]; - - self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; - UIViewController *rootViewController = [UIViewController new]; - rootViewController.view = rootView; - self.window.rootViewController = rootViewController; - [self.window makeKeyAndVisible]; - return YES; -} - -@end diff --git a/examples/rpc/ios/rpc/Base.lproj/LaunchScreen.xib b/examples/rpc/ios/rpc/Base.lproj/LaunchScreen.xib deleted file mode 100644 index d4cfc96e..00000000 --- a/examples/rpc/ios/rpc/Base.lproj/LaunchScreen.xib +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/rpc/ios/rpc/Images.xcassets/AppIcon.appiconset/Contents.json b/examples/rpc/ios/rpc/Images.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 118c98f7..00000000 --- a/examples/rpc/ios/rpc/Images.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "29x29", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "40x40", - "scale" : "3x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "2x" - }, - { - "idiom" : "iphone", - "size" : "60x60", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/examples/rpc/ios/rpc/Info.plist b/examples/rpc/ios/rpc/Info.plist deleted file mode 100644 index e98ebb00..00000000 --- a/examples/rpc/ios/rpc/Info.plist +++ /dev/null @@ -1,54 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UIViewControllerBasedStatusBarAppearance - - NSLocationWhenInUseUsageDescription - - NSAppTransportSecurity - - - NSExceptionDomains - - localhost - - NSTemporaryExceptionAllowsInsecureHTTPLoads - - - - - - diff --git a/examples/rpc/ios/rpc/main.m b/examples/rpc/ios/rpc/main.m deleted file mode 100644 index 3d767fcb..00000000 --- a/examples/rpc/ios/rpc/main.m +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -#import - -#import "AppDelegate.h" - -int main(int argc, char * argv[]) { - @autoreleasepool { - return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); - } -} diff --git a/examples/rpc/ios/rpcTests/Info.plist b/examples/rpc/ios/rpcTests/Info.plist deleted file mode 100644 index 886825cc..00000000 --- a/examples/rpc/ios/rpcTests/Info.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - BNDL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - - diff --git a/examples/rpc/ios/rpcTests/rpcTests.m b/examples/rpc/ios/rpcTests/rpcTests.m deleted file mode 100644 index d7795172..00000000 --- a/examples/rpc/ios/rpcTests/rpcTests.m +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -#import -#import - -#import "RCTLog.h" -#import "RCTRootView.h" - -#define TIMEOUT_SECONDS 600 -#define TEXT_TO_LOOK_FOR @"Welcome to React Native!" - -@interface rpcTests : XCTestCase - -@end - -@implementation rpcTests - -- (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test -{ - if (test(view)) { - return YES; - } - for (UIView *subview in [view subviews]) { - if ([self findSubviewInView:subview matching:test]) { - return YES; - } - } - return NO; -} - -- (void)testRendersWelcomeScreen -{ - UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; - NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; - BOOL foundElement = NO; - - __block NSString *redboxError = nil; - RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { - if (level >= RCTLogLevelError) { - redboxError = message; - } - }); - - while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { - [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; - - foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { - if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { - return YES; - } - return NO; - }]; - } - - RCTSetLogFunction(RCTDefaultLogFunction); - - XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); - XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); -} - - -@end diff --git a/examples/rpc/package.json b/examples/rpc/package.json deleted file mode 100644 index a516884d..00000000 --- a/examples/rpc/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "rpc", - "version": "0.0.1", - "private": true, - "scripts": { - "start": "node node_modules/react-native/local-cli/cli.js start" - }, - "dependencies": { - "react": "15.3.2", - "react-native": "0.34.0", - "react-native-webview-bridge": "../.." - } -} diff --git a/ios/RCTWebViewBridge.h b/ios/RCTWebViewBridge.h index 5174ff9b..7c20fbfd 100644 --- a/ios/RCTWebViewBridge.h +++ b/ios/RCTWebViewBridge.h @@ -2,6 +2,9 @@ * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * + * Copyright (c) 2015-present, Ali Najafizadeh (github.com/alinz) + * All rights reserved + * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. @@ -34,14 +37,12 @@ shouldStartLoadForRequest:(NSMutableDictionary *)request @property (nonatomic, copy) NSDictionary *source; @property (nonatomic, assign) UIEdgeInsets contentInset; @property (nonatomic, assign) BOOL automaticallyAdjustContentInsets; +@property (nonatomic, assign) BOOL hideKeyboardAccessoryView; @property (nonatomic, copy) NSString *injectedJavaScript; -@property (nonatomic, assign) BOOL scalesPageToFit; -@property (nonatomic, assign) BOOL rpc; - (void)goForward; - (void)goBack; - (void)reload; -- (void)stopLoading; - (void)sendToBridge:(NSString *)message; - (NSString*) getElementHTML:(NSString*)elementId; diff --git a/ios/RCTWebViewBridge.m b/ios/RCTWebViewBridge.m index 6ac4bf2d..ea4ed43c 100644 --- a/ios/RCTWebViewBridge.m +++ b/ios/RCTWebViewBridge.m @@ -2,6 +2,9 @@ * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * + * Copyright (c) 2015-present, Ali Najafizadeh (github.com/alinz) + * All rights reserved + * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. @@ -18,12 +21,25 @@ #import "RCTUtils.h" #import "RCTView.h" #import "UIView+React.h" +#import //This is a very elegent way of defining multiline string in objective-c. //source: http://stackoverflow.com/a/23387659/828487 #define NSStringMultiline(...) [[NSString alloc] initWithCString:#__VA_ARGS__ encoding:NSUTF8StringEncoding] -static const NSString* RCTWebViewBridgeSchema = @"rnwb"; +//we don'e need this one since it has been defined in RCTWebView.m +//NSString *const RCTJSNavigationScheme = @"react-js-navigation"; +NSString *const RCTWebViewBridgeSchema = @"wvb"; + +// runtime trick to remove UIWebview keyboard default toolbar +// see: http://stackoverflow.com/questions/19033292/ios-7-uiwebview-keyboard-issue/19042279#19042279 +@interface _SwizzleHelper : NSObject @end +@implementation _SwizzleHelper +-(id)inputAccessoryView +{ + return nil; +} +@end @interface RCTWebViewBridge () @@ -37,298 +53,359 @@ @interface RCTWebViewBridge () @implementation RCTWebViewBridge { - UIWebView *_webView; - NSString *_injectedJavaScript; -} - -- (void)dealloc -{ - _webView.delegate = nil; + UIWebView *_webView; + NSString *_injectedJavaScript; } - (instancetype)initWithFrame:(CGRect)frame { - if ((self = [super initWithFrame:frame])) { - super.backgroundColor = [UIColor clearColor]; - _automaticallyAdjustContentInsets = YES; - _contentInset = UIEdgeInsetsZero; - _webView = [[UIWebView alloc] initWithFrame:self.bounds]; - _webView.delegate = self; - [self addSubview:_webView]; - } - return self; + if ((self = [super initWithFrame:frame])) { + super.backgroundColor = [UIColor clearColor]; + _automaticallyAdjustContentInsets = YES; + _contentInset = UIEdgeInsetsZero; + _webView = [[UIWebView alloc] initWithFrame:self.bounds]; + _webView.delegate = self; + [self addSubview:_webView]; + } + return self; } RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder) - (void)goForward { - [_webView goForward]; + [_webView goForward]; } - (void)goBack { - [_webView goBack]; + [_webView goBack]; } - (void)reload { - NSURLRequest *request = [RCTConvert NSURLRequest:self.source]; - if (request.URL && !_webView.request.URL.absoluteString.length) { - [_webView loadRequest:request]; - } - else { - [_webView reload]; - } + [_webView reload]; } - (NSString*) getElementHTML:(NSString*)elementId { NSString* JSString = [NSString stringWithFormat:@"document.getElementById(\"%@\").innerHTML",elementId]; return [_webView stringByEvaluatingJavaScriptFromString:JSString]; - } -- (void)stopLoading -{ - [_webView stopLoading]; } -- (void)setSource:(NSDictionary *)source + + +- (void)sendToBridge:(NSString *)message { - if (![_source isEqualToDictionary:source]) { - _source = [source copy]; - - // Check for a static html source first - NSString *html = [RCTConvert NSString:source[@"html"]]; - if (html) { - NSURL *baseURL = [RCTConvert NSURL:source[@"baseUrl"]]; - if (!baseURL) { - baseURL = [NSURL URLWithString:@"about:blank"]; - } - [_webView loadHTMLString:html baseURL:baseURL]; - return; - } + //we are warpping the send message in a function to make sure that if + //WebView is not injected, we don't crash the app. + NSString *format = NSStringMultiline( + (function(){ + if (WebViewBridge && WebViewBridge.__push__) { + WebViewBridge.__push__('%@'); + } + }()); + ); - NSURLRequest *request = [RCTConvert NSURLRequest:source]; - // Because of the way React works, as pages redirect, we actually end up - // passing the redirect urls back here, so we ignore them if trying to load - // the same url. We'll expose a call to 'reload' to allow a user to load - // the existing page. - if ([request.URL isEqual:_webView.request.URL]) { - return; - } - if (!request.URL) { - // Clear the webview - [_webView loadHTMLString:@"" baseURL:nil]; - return; - } - [_webView loadRequest:request]; - } + NSString *command = [NSString stringWithFormat: format, message]; + [_webView stringByEvaluatingJavaScriptFromString:command]; } -- (void)layoutSubviews +- (NSURL *)URL { - [super layoutSubviews]; - _webView.frame = self.bounds; + return _webView.request.URL; } -- (void)setContentInset:(UIEdgeInsets)contentInset +- (void)setSource:(NSDictionary *)source { - _contentInset = contentInset; - [RCTView autoAdjustInsetsForView:self - withScrollView:_webView.scrollView - updateOffset:NO]; + if (![_source isEqualToDictionary:source]) { + _source = [source copy]; + + // Check for a static html source first + NSString *html = [RCTConvert NSString:source[@"html"]]; + if (html) { + NSURL *baseURL = [RCTConvert NSURL:source[@"baseUrl"]]; + [_webView loadHTMLString:html baseURL:baseURL]; + return; + } + + NSURLRequest *request = [RCTConvert NSURLRequest:source]; + // Because of the way React works, as pages redirect, we actually end up + // passing the redirect urls back here, so we ignore them if trying to load + // the same url. We'll expose a call to 'reload' to allow a user to load + // the existing page. + if ([request.URL isEqual:_webView.request.URL]) { + return; + } + if (!request.URL) { + // Clear the webview + [_webView loadHTMLString:@"" baseURL:nil]; + return; + } + [_webView loadRequest:request]; + } } -- (void)setScalesPageToFit:(BOOL)scalesPageToFit +- (void)layoutSubviews { - if (_webView.scalesPageToFit != scalesPageToFit) { - _webView.scalesPageToFit = scalesPageToFit; - [_webView reload]; - } + [super layoutSubviews]; + _webView.frame = self.bounds; } -- (BOOL)scalesPageToFit +- (void)setContentInset:(UIEdgeInsets)contentInset { - return _webView.scalesPageToFit; + _contentInset = contentInset; + [RCTView autoAdjustInsetsForView:self + withScrollView:_webView.scrollView + updateOffset:NO]; } - (void)setBackgroundColor:(UIColor *)backgroundColor { - CGFloat alpha = CGColorGetAlpha(backgroundColor.CGColor); - self.opaque = _webView.opaque = (alpha == 1.0); - _webView.backgroundColor = backgroundColor; + CGFloat alpha = CGColorGetAlpha(backgroundColor.CGColor); + self.opaque = _webView.opaque = (alpha == 1.0); + _webView.backgroundColor = backgroundColor; } - (UIColor *)backgroundColor { - return _webView.backgroundColor; + return _webView.backgroundColor; } - (NSMutableDictionary *)baseEvent { - NSMutableDictionary *event = [[NSMutableDictionary alloc] initWithDictionary:@{ - @"url": _webView.request.URL.absoluteString ?: @"", - @"loading" : @(_webView.loading), - @"title": [_webView stringByEvaluatingJavaScriptFromString:@"document.title"], - @"canGoBack": @(_webView.canGoBack), - @"canGoForward" : @(_webView.canGoForward), - }]; - - return event; + NSMutableDictionary *event = [[NSMutableDictionary alloc] initWithDictionary:@{ + @"url": _webView.request.URL.absoluteString ?: @"", + @"loading" : @(_webView.loading), + @"title": [_webView stringByEvaluatingJavaScriptFromString:@"document.title"], + @"canGoBack": @(_webView.canGoBack), + @"canGoForward" : @(_webView.canGoForward), + }]; + + return event; } - (void)refreshContentInset { - [RCTView autoAdjustInsetsForView:self - withScrollView:_webView.scrollView - updateOffset:YES]; + [RCTView autoAdjustInsetsForView:self + withScrollView:_webView.scrollView + updateOffset:YES]; } -#pragma mark - UIWebViewDelegate methods - -- (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request - navigationType:(UIWebViewNavigationType)navigationType +-(void)setHideKeyboardAccessoryView:(BOOL)hideKeyboardAccessoryView { - BOOL isJSNavigation = [request.URL.scheme isEqualToString:RCTJSNavigationScheme]; + if (!hideKeyboardAccessoryView) { + return; + } - if (!isJSNavigation && [request.URL.scheme isEqualToString:RCTWebViewBridgeSchema]) { - NSString* message = [webView stringByEvaluatingJavaScriptFromString:@"WebViewBridge.__fetch__()"]; + UIView* subview; + for (UIView* view in _webView.scrollView.subviews) { + if([[view.class description] hasPrefix:@"UIWeb"]) + subview = view; + } - NSMutableDictionary *onBridgeMessageEvent = [[NSMutableDictionary alloc] initWithDictionary:@{ - @"messages": [self stringArrayJsonToArray: message] - }]; + if(subview == nil) return; - _onBridgeMessage(onBridgeMessageEvent); + NSString* name = [NSString stringWithFormat:@"%@_SwizzleHelper", subview.class.superclass]; + Class newClass = NSClassFromString(name); - isJSNavigation = YES; - } + if(newClass == nil) + { + newClass = objc_allocateClassPair(subview.class, [name cStringUsingEncoding:NSASCIIStringEncoding], 0); + if(!newClass) return; - static NSDictionary *navigationTypes; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - navigationTypes = @{ - @(UIWebViewNavigationTypeLinkClicked): @"click", - @(UIWebViewNavigationTypeFormSubmitted): @"formsubmit", - @(UIWebViewNavigationTypeBackForward): @"backforward", - @(UIWebViewNavigationTypeReload): @"reload", - @(UIWebViewNavigationTypeFormResubmitted): @"formresubmit", - @(UIWebViewNavigationTypeOther): @"other", - }; - }); - - // skip this for the JS Navigation handler - if (!isJSNavigation && _onShouldStartLoadWithRequest) { - NSMutableDictionary *event = [self baseEvent]; - [event addEntriesFromDictionary: @{ - @"url": (request.URL).absoluteString, - @"navigationType": navigationTypes[@(navigationType)] - }]; - if (![self.delegate webView:self - shouldStartLoadForRequest:event - withCallback:_onShouldStartLoadWithRequest]) { - return NO; - } - } + Method method = class_getInstanceMethod([_SwizzleHelper class], @selector(inputAccessoryView)); + class_addMethod(newClass, @selector(inputAccessoryView), method_getImplementation(method), method_getTypeEncoding(method)); - if (_onLoadingStart) { - // We have this check to filter out iframe requests and whatnot - BOOL isTopFrame = [request.URL isEqual:request.mainDocumentURL]; - if (isTopFrame) { - NSMutableDictionary *event = [self baseEvent]; - [event addEntriesFromDictionary: @{ - @"url": (request.URL).absoluteString, - @"navigationType": navigationTypes[@(navigationType)] - }]; - _onLoadingStart(event); - } - } + objc_registerClassPair(newClass); + } - // JS Navigation handler - return !isJSNavigation; + object_setClass(subview, newClass); } -- (void)webView:(__unused UIWebView *)webView didFailLoadWithError:(NSError *)error -{ - if (_onLoadingError) { - if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) { - // NSURLErrorCancelled is reported when a page has a redirect OR if you load - // a new URL in the WebView before the previous one came back. We can just - // ignore these since they aren't real errors. - // http://stackoverflow.com/questions/1024748/how-do-i-fix-nsurlerrordomain-error-999-in-iphone-3-0-os - return; - } - - NSMutableDictionary *event = [self baseEvent]; - [event addEntriesFromDictionary:@{ - @"domain": error.domain, - @"code": @(error.code), - @"description": error.localizedDescription, - }]; - _onLoadingError(event); - } -} +#pragma mark - UIWebViewDelegate methods -- (void)webViewDidFinishLoad:(UIWebView *)webView +- (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request + navigationType:(UIWebViewNavigationType)navigationType { - //injecting WebViewBridge bootstrap - [webView stringByEvaluatingJavaScriptFromString:[self webViewBridgeBootrstrap]]; - - //injecting WebViewBridgeRPC bootstrap - if (self.rpc) { - [webView stringByEvaluatingJavaScriptFromString:[self webViewBridgeRPCBootstrap]]; - } - - if (_injectedJavaScript != nil) { - NSString *jsEvaluationValue = [webView stringByEvaluatingJavaScriptFromString:_injectedJavaScript]; - - NSMutableDictionary *event = [self baseEvent]; - event[@"jsEvaluationValue"] = jsEvaluationValue; - - _onLoadingFinish(event); + BOOL isJSNavigation = [request.URL.scheme isEqualToString:RCTJSNavigationScheme]; + + if (!isJSNavigation && [request.URL.scheme isEqualToString:RCTWebViewBridgeSchema]) { + NSString* message = [webView stringByEvaluatingJavaScriptFromString:@"WebViewBridge.__fetch__()"]; + + NSMutableDictionary *onBridgeMessageEvent = [[NSMutableDictionary alloc] initWithDictionary:@{ + @"messages": [self stringArrayJsonToArray: message] + }]; + + _onBridgeMessage(onBridgeMessageEvent); + + isJSNavigation = YES; + } + + // skip this for the JS Navigation handler + if (!isJSNavigation && _onShouldStartLoadWithRequest) { + NSMutableDictionary *event = [self baseEvent]; + [event addEntriesFromDictionary: @{ + @"url": (request.URL).absoluteString, + @"navigationType": @(navigationType) + }]; + if (![self.delegate webView:self + shouldStartLoadForRequest:event + withCallback:_onShouldStartLoadWithRequest]) { + return NO; } - // we only need the final 'finishLoad' call so only fire the event when we're actually done loading. - else if (_onLoadingFinish && !webView.loading && ![webView.request.URL.absoluteString isEqualToString:@"about:blank"]) { - _onLoadingFinish([self baseEvent]); + } + + if (_onLoadingStart) { + // We have this check to filter out iframe requests and whatnot + BOOL isTopFrame = [request.URL isEqual:request.mainDocumentURL]; + if (isTopFrame) { + NSMutableDictionary *event = [self baseEvent]; + [event addEntriesFromDictionary: @{ + @"url": (request.URL).absoluteString, + @"navigationType": @(navigationType) + }]; + _onLoadingStart(event); } + } + + // JS Navigation handler + return !isJSNavigation; } -- (void)sendToBridge:(NSString *)message +- (void)webView:(__unused UIWebView *)webView didFailLoadWithError:(NSError *)error { - //we are warpping the send message in a function to make sure that if - //WebView is not injected, we don't crash the app. - NSString *format = NSStringMultiline( - (function(){ - if (WebViewBridge && WebViewBridge.__push__) { - WebViewBridge.__push__("%@"); - } - }()); - ); + if (_onLoadingError) { + if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) { + // NSURLErrorCancelled is reported when a page has a redirect OR if you load + // a new URL in the WebView before the previous one came back. We can just + // ignore these since they aren't real errors. + // http://stackoverflow.com/questions/1024748/how-do-i-fix-nsurlerrordomain-error-999-in-iphone-3-0-os + return; + } - NSString *command = [NSString stringWithFormat: format, message]; - [_webView stringByEvaluatingJavaScriptFromString:command]; + NSMutableDictionary *event = [self baseEvent]; + [event addEntriesFromDictionary:@{ + @"domain": error.domain, + @"code": @(error.code), + @"description": error.localizedDescription, + }]; + _onLoadingError(event); + } } -- (NSArray*)stringArrayJsonToArray:(NSString *)message +- (void)webViewDidFinishLoad:(UIWebView *)webView { - return [NSJSONSerialization JSONObjectWithData:[message dataUsingEncoding:NSUTF8StringEncoding] - options:NSJSONReadingAllowFragments - error:nil]; + //injecting WebViewBridge Script + NSString *webViewBridgeScriptContent = [self webViewBridgeScript]; + [webView stringByEvaluatingJavaScriptFromString:webViewBridgeScriptContent]; + ////////////////////////////////////////////////////////////////////////////// + + if (_injectedJavaScript != nil) { + NSString *jsEvaluationValue = [webView stringByEvaluatingJavaScriptFromString:_injectedJavaScript]; + + NSMutableDictionary *event = [self baseEvent]; + event[@"jsEvaluationValue"] = jsEvaluationValue; + + _onLoadingFinish(event); + } + // we only need the final 'finishLoad' call so only fire the event when we're actually done loading. + else if (_onLoadingFinish && !webView.loading && ![webView.request.URL.absoluteString isEqualToString:@"about:blank"]) { + _onLoadingFinish([self baseEvent]); + } } -- (NSString *)webViewBridgeBootrstrap +- (NSArray*)stringArrayJsonToArray:(NSString *)message { - NSString *path = [[NSBundle mainBundle] pathForResource:@"WebViewBridge" ofType:@"js"]; - assert(path != nil); - NSString* content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL]; - return content; + return [NSJSONSerialization JSONObjectWithData:[message dataUsingEncoding:NSUTF8StringEncoding] + options:NSJSONReadingAllowFragments + error:nil]; } -- (NSString *)webViewBridgeRPCBootstrap -{ - NSString *path = [[NSBundle mainBundle] pathForResource:@"WebViewBridgeRPC" ofType:@"js"]; - assert(path != nil); - NSString* content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL]; - return content; +//since there is no easy way to load the static lib resource in ios, +//we are loading the script from this method. +- (NSString *)webViewBridgeScript { + // NSBundle *bundle = [NSBundle mainBundle]; + // NSString *webViewBridgeScriptFile = [bundle pathForResource:@"webviewbridge" + // ofType:@"js"]; + // NSString *webViewBridgeScriptContent = [NSString stringWithContentsOfFile:webViewBridgeScriptFile + // encoding:NSUTF8StringEncoding + // error:nil]; + + return NSStringMultiline( + (function (window) { + 'use strict'; + + //Make sure that if WebViewBridge already in scope we don't override it. + if (window.WebViewBridge) { + return; + } + + var RNWBSchema = 'wvb'; + var sendQueue = []; + var receiveQueue = []; + var doc = window.document; + var customEvent = doc.createEvent('Event'); + + function callFunc(func, message) { + if ('function' === typeof func) { + func(message); + } + } + + function signalNative() { + window.location = RNWBSchema + '://message' + new Date().getTime(); + } + + //I made the private function ugly signiture so user doesn't called them accidently. + //if you do, then I have nothing to say. :( + var WebViewBridge = { + //this function will be called by native side to push a new message + //to webview. + __push__: function (message) { + receiveQueue.push(message); + //reason I need this setTmeout is to return this function as fast as + //possible to release the native side thread. + setTimeout(function () { + var message = receiveQueue.pop(); + callFunc(WebViewBridge.onMessage, message); + }, 15); //this magic number is just a random small value. I don't like 0. + }, + __fetch__: function () { + //since our sendQueue array only contains string, and our connection to native + //can only accept string, we need to convert array of strings into single string. + var messages = JSON.stringify(sendQueue); + + //we make sure that sendQueue is resets + sendQueue = []; + + //return the messages back to native side. + return messages; + }, + //make sure message is string. because only string can be sent to native, + //if you don't pass it as string, onError function will be called. + send: function (message) { + if ('string' !== typeof message) { + callFunc(WebViewBridge.onError, "message is type '" + typeof message + "', and it needs to be string"); + return; + } + + //we queue the messages to make sure that native can collects all of them in one shot. + sendQueue.push(message); + //signal the objective-c that there is a message in the queue + signalNative(); + }, + onMessage: null, + onError: null + }; + + window.WebViewBridge = WebViewBridge; + + //dispatch event + customEvent.initEvent('WebViewBridge', true, true); + doc.dispatchEvent(customEvent); + }(window)); + ); } @end diff --git a/ios/RCTWebViewBridgeManager.h b/ios/RCTWebViewBridgeManager.h index 8797862b..e0659a8d 100644 --- a/ios/RCTWebViewBridgeManager.h +++ b/ios/RCTWebViewBridgeManager.h @@ -2,6 +2,9 @@ * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * + * Copyright (c) 2015-present, Ali Najafizadeh (github.com/alinz) + * All rights reserved + * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. diff --git a/ios/RCTWebViewBridgeManager.m b/ios/RCTWebViewBridgeManager.m index c358b7c5..fc61a80b 100644 --- a/ios/RCTWebViewBridgeManager.m +++ b/ios/RCTWebViewBridgeManager.m @@ -2,6 +2,9 @@ * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * + * Copyright (c) 2015-present, Ali Najafizadeh (github.com/alinz) + * All rights reserved + * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. @@ -36,20 +39,17 @@ - (UIView *)view RCT_EXPORT_VIEW_PROPERTY(source, NSDictionary) RCT_REMAP_VIEW_PROPERTY(bounces, _webView.scrollView.bounces, BOOL) RCT_REMAP_VIEW_PROPERTY(scrollEnabled, _webView.scrollView.scrollEnabled, BOOL) -RCT_REMAP_VIEW_PROPERTY(decelerationRate, _webView.scrollView.decelerationRate, CGFloat) -RCT_EXPORT_VIEW_PROPERTY(scalesPageToFit, BOOL) +RCT_REMAP_VIEW_PROPERTY(scalesPageToFit, _webView.scalesPageToFit, BOOL) RCT_EXPORT_VIEW_PROPERTY(injectedJavaScript, NSString) RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets) RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL) +RCT_EXPORT_VIEW_PROPERTY(hideKeyboardAccessoryView, BOOL) RCT_EXPORT_VIEW_PROPERTY(onLoadingStart, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onLoadingFinish, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onLoadingError, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onShouldStartLoadWithRequest, RCTDirectEventBlock) -RCT_EXPORT_VIEW_PROPERTY(onBridgeMessage, RCTDirectEventBlock) RCT_REMAP_VIEW_PROPERTY(allowsInlineMediaPlayback, _webView.allowsInlineMediaPlayback, BOOL) -RCT_REMAP_VIEW_PROPERTY(mediaPlaybackRequiresUserAction, _webView.mediaPlaybackRequiresUserAction, BOOL) -RCT_REMAP_VIEW_PROPERTY(dataDetectorTypes, _webView.dataDetectorTypes, UIDataDetectorTypes) -RCT_EXPORT_VIEW_PROPERTY(rpc, BOOL) +RCT_EXPORT_VIEW_PROPERTY(onBridgeMessage, RCTDirectEventBlock) RCT_REMAP_VIEW_PROPERTY(keyboardDisplayRequiresUserAction, _webView.keyboardDisplayRequiresUserAction, BOOL) - (NSDictionary *)constantsToExport @@ -103,20 +103,23 @@ - (UIView *)view }]; } -RCT_EXPORT_METHOD(stopLoading:(nonnull NSNumber *)reactTag) +RCT_EXPORT_METHOD(getElementHTML:(nonnull NSNumber *)reactTag :(NSString*)elementId :(RCTResponseSenderBlock)callback) { - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RCTWebViewBridge *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RCTWebViewBridge class]]) { - RCTLogError(@"Invalid view returned from registry, expecting RCTWebViewBridge, got: %@", view); - } else { - [view stopLoading]; - } - }]; + [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { + RCTWebViewBridge *view = viewRegistry[reactTag]; + if (![view isKindOfClass:[RCTWebViewBridge class]]) { + RCTLogError(@"Invalid view returned from registry, expecting RCTWebViewBridge, got: %@", view); + } else { + NSString* html = [view getElementHTML:elementId]; + NSLog(@"Editor HTML is %@", html); + callback(@[[NSNull null], html]); + } + }]; } + RCT_EXPORT_METHOD(sendToBridge:(nonnull NSNumber *)reactTag - value:(NSString*)message) + value:(NSString*)message) { [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { RCTWebViewBridge *view = viewRegistry[reactTag]; @@ -128,20 +131,6 @@ - (UIView *)view }]; } -RCT_EXPORT_METHOD(getElementHTML:(nonnull NSNumber *)reactTag :(NSString*)elementId :(RCTResponseSenderBlock)callback) -{ - [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RCTWebViewBridge *view = viewRegistry[reactTag]; - if (![view isKindOfClass:[RCTWebViewBridge class]]) { - RCTLogError(@"Invalid view returned from registry, expecting RCTWebViewBridge, got: %@", view); - } else { - NSString* html = [view getElementHTML:elementId]; - NSLog(@"Editor HTML is %@", html); - callback(@[[NSNull null], html]); - } - }]; -} - #pragma mark - Exported synchronous methods - (BOOL)webView:(__unused RCTWebViewBridge *)webView diff --git a/lib/WebViewBridge/index.android.js b/lib/WebViewBridge/index.android.js deleted file mode 100644 index b11fd0d1..00000000 --- a/lib/WebViewBridge/index.android.js +++ /dev/null @@ -1,392 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule WebView - */ -'use strict'; - -import React, { PropTypes } from 'react' -import { - ActivityIndicator, - EdgeInsetsPropType, - StyleSheet, - Text, - UIManager, - View, - ScrollView, - NativeModules, - requireNativeComponent, - findNodeHandle, - DeviceEventEmitter -} from 'react-native' - -import invariant from 'fbjs/lib/invariant' -import keyMirror from 'fbjs/lib/keyMirror' - -import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource'; -import processDecelerationRate from 'react-native/Libraries/Components/ScrollView/processDecelerationRate' -import deprecatedPropType from 'react-native/Libraries/Utilities/deprecatedPropType' - -import { encode, decode } from '../core/base64' - -const { WebViewBridgeManager } = NativeModules - -var RCT_WEBVIEW_REF = 'webview'; - -var WebViewState = keyMirror({ - IDLE: null, - LOADING: null, - ERROR: null, -}); - -var defaultRenderLoading = () => ( - - - -); - -/** - * Renders a native WebView. - */ -class WebView extends React.Component { - static propTypes = { - ...View.propTypes, - renderError: PropTypes.func, - renderLoading: PropTypes.func, - onLoad: PropTypes.func, - onLoadEnd: PropTypes.func, - onLoadStart: PropTypes.func, - onError: PropTypes.func, - automaticallyAdjustContentInsets: PropTypes.bool, - contentInset: EdgeInsetsPropType, - onNavigationStateChange: PropTypes.func, - onContentSizeChange: PropTypes.func, - startInLoadingState: PropTypes.bool, // force WebView to show loadingView on first load - style: View.propTypes.style, - - html: deprecatedPropType( - PropTypes.string, - 'Use the `source` prop instead.' - ), - - url: deprecatedPropType( - PropTypes.string, - 'Use the `source` prop instead.' - ), - - /** - * Loads static html or a uri (with optional headers) in the WebView. - */ - source: PropTypes.oneOfType([ - PropTypes.shape({ - /* - * The URI to load in the WebView. Can be a local or remote file. - */ - uri: PropTypes.string, - /* - * The HTTP Method to use. Defaults to GET if not specified. - * NOTE: On Android, only GET and POST are supported. - */ - method: PropTypes.oneOf(['GET', 'POST']), - /* - * Additional HTTP headers to send with the request. - * NOTE: On Android, this can only be used with GET requests. - */ - headers: PropTypes.object, - /* - * The HTTP body to send with the request. This must be a valid - * UTF-8 string, and will be sent exactly as specified, with no - * additional encoding (e.g. URL-escaping or base64) applied. - * NOTE: On Android, this can only be used with POST requests. - */ - body: PropTypes.string, - }), - PropTypes.shape({ - /* - * A static HTML page to display in the WebView. - */ - html: PropTypes.string, - /* - * The base URL to be used for any relative links in the HTML. - */ - baseUrl: PropTypes.string, - }), - /* - * Used internally by packager. - */ - PropTypes.number, - ]), - - /** - * Used on Android only, JS is enabled by default for WebView on iOS - * @platform android - */ - javaScriptEnabled: PropTypes.bool, - - /** - * Used on Android only, controls whether DOM Storage is enabled or not - * @platform android - */ - domStorageEnabled: PropTypes.bool, - - /** - * Sets the JS to be injected when the webpage loads. - */ - injectedJavaScript: PropTypes.string, - - /** - * Sets whether the webpage scales to fit the view and the user can change the scale. - */ - scalesPageToFit: PropTypes.bool, - - /** - * Sets the user-agent for this WebView. The user-agent can also be set in native using - * WebViewConfig. This prop will overwrite that config. - */ - userAgent: PropTypes.string, - - /** - * Used to locate this view in end-to-end tests. - */ - testID: PropTypes.string, - - /** - * Determines whether HTML5 audio & videos require the user to tap before they can - * start playing. The default value is `false`. - */ - mediaPlaybackRequiresUserAction: PropTypes.bool, - - allowUniversalAccessFromFileURLs: PropTypes.bool, - allowFileAccessFromFileURLs: PropTypes.bool, - onBridgeMessage: PropTypes.func, - rpc: PropTypes.bool - }; - - static defaultProps = { - javaScriptEnabled : true, - scalesPageToFit: true, - }; - - state = { - viewState: WebViewState.IDLE, - lastErrorEvent: null, - startInLoadingState: true, - }; - - componentWillMount() { - DeviceEventEmitter.addListener('WebViewBridgeMessageEvent', this.onBridgeMessage); - - if (this.props.startInLoadingState) { - this.setState({viewState: WebViewState.LOADING}); - } - } - - componentWillUnmount() { - DeviceEventEmitter.removeListener('WebViewBridgeMessageEvent', this.onBridgeMessage); - } - - render() { - var otherView = null; - - if (this.state.viewState === WebViewState.LOADING) { - otherView = (this.props.renderLoading || defaultRenderLoading)(); - } else if (this.state.viewState === WebViewState.ERROR) { - var errorEvent = this.state.lastErrorEvent; - otherView = this.props.renderError && this.props.renderError( - errorEvent.domain, - errorEvent.code, - errorEvent.description); - } else if (this.state.viewState !== WebViewState.IDLE) { - console.error('RCTWebViewBridge invalid state encountered: ' + this.state.loading); - } - - var webViewStyles = [styles.container, this.props.style]; - if (this.state.viewState === WebViewState.LOADING || - this.state.viewState === WebViewState.ERROR) { - // if we're in either LOADING or ERROR states, don't show the webView - webViewStyles.push(styles.hidden); - } - - var source = this.props.source || {}; - if (this.props.html) { - source.html = this.props.html; - } else if (this.props.url) { - source.uri = this.props.url; - } - - if (source.method === 'POST' && source.headers) { - console.warn('WebView: `source.headers` is not supported when using POST.'); - } else if (source.method === 'GET' && source.body) { - console.warn('WebView: `source.body` is not supported when using GET.'); - } - - var webView = - ; - - return ( - - {webView} - {otherView} - - ); - } - - goForward = () => { - UIManager.dispatchViewManagerCommand( - this.getWebViewHandle(), - UIManager.RCTWebViewBridge.Commands.goForward, - null - ); - }; - - goBack = () => { - UIManager.dispatchViewManagerCommand( - this.getWebViewHandle(), - UIManager.RCTWebViewBridge.Commands.goBack, - null - ); - }; - - reload = () => { - UIManager.dispatchViewManagerCommand( - this.getWebViewHandle(), - UIManager.RCTWebViewBridge.Commands.reload, - null - ); - }; - - stopLoading = () => { - UIManager.dispatchViewManagerCommand( - this.getWebViewHandle(), - UIManager.RCTWebViewBridge.Commands.stopLoading, - null - ); - }; - - /** - * We return an event with a bunch of fields including: - * url, title, loading, canGoBack, canGoForward - */ - updateNavigationState = (event) => { - if (this.props.onNavigationStateChange) { - this.props.onNavigationStateChange(event.nativeEvent); - } - }; - - onBridgeMessage = (event: Event) => { - const { onBridgeMessage } = this.props; - const messages = JSON.parse(event.message) - if (onBridgeMessage) { - messages.forEach((message) => { - onBridgeMessage(decode(message)); - }); - } - }; - - sendToBridge = (message: any) => { - UIManager.dispatchViewManagerCommand( - this.getWebViewHandle(), - UIManager.RCTWebViewBridge.Commands.sendToBridge, - [encode(message)] - ); - }; - - getWebViewHandle = () => { - return findNodeHandle(this.refs[RCT_WEBVIEW_REF]); - }; - - onLoadingStart = (event) => { - var onLoadStart = this.props.onLoadStart; - onLoadStart && onLoadStart(event); - this.updateNavigationState(event); - }; - - onLoadingError = (event) => { - event.persist(); // persist this event because we need to store it - var {onError, onLoadEnd} = this.props; - onError && onError(event); - onLoadEnd && onLoadEnd(event); - console.warn('Encountered an error loading page', event.nativeEvent); - - this.setState({ - lastErrorEvent: event.nativeEvent, - viewState: WebViewState.ERROR - }); - }; - - onLoadingFinish = (event) => { - var { onLoad, onLoadEnd, rpc } = this.props; - - //we need to inject WebViewBridge and RPC once the page is loaded - UIManager.dispatchViewManagerCommand( - this.getWebViewHandle(), - UIManager.RCTWebViewBridge.Commands.injectWebViewBridge, - null - ); - - //check if RPC falg is also on - if (rpc) { - UIManager.dispatchViewManagerCommand( - this.getWebViewHandle(), - UIManager.RCTWebViewBridge.Commands.injectRPC, - null - ); - } - - onLoad && onLoad(event); - onLoadEnd && onLoadEnd(event); - this.setState({ - viewState: WebViewState.IDLE, - }); - this.updateNavigationState(event); - }; -} - -var RCTWebViewBridge = requireNativeComponent('RCTWebViewBridge', WebView); - -var styles = StyleSheet.create({ - container: { - flex: 1, - }, - hidden: { - height: 0, - flex: 0, // disable 'flex:1' when hiding a View - }, - loadingView: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - }, - loadingProgressBar: { - height: 20, - }, -}); - -module.exports = WebView; diff --git a/lib/WebViewBridge/index.ios.js b/lib/WebViewBridge/index.ios.js deleted file mode 100644 index 5a0fe2cd..00000000 --- a/lib/WebViewBridge/index.ios.js +++ /dev/null @@ -1,578 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * Copyright (c) 2016-present, Ali Najafizadeh - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule WebViewBridge - * @noflow - */ -'use strict'; - -import React, { PropTypes } from 'react' -import { - ActivityIndicator, - EdgeInsetsPropType, - StyleSheet, - Text, - UIManager, - View, - ScrollView, - NativeModules, - requireNativeComponent, - findNodeHandle -} from 'react-native' - -import invariant from 'fbjs/lib/invariant' -import keyMirror from 'fbjs/lib/keyMirror' - -import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource'; -import processDecelerationRate from 'react-native/Libraries/Components/ScrollView/processDecelerationRate' -import deprecatedPropType from 'react-native/Libraries/Utilities/deprecatedPropType' - -import { encode, decode } from '../core/base64' - -const { WebViewBridgeManager } = NativeModules - -var BGWASH = 'rgba(255,255,255,0.8)'; -var RCT_WEBVIEW_REF = 'webview'; - -var WebViewState = keyMirror({ - IDLE: null, - LOADING: null, - ERROR: null, -}); - -const NavigationType = keyMirror({ - click: true, - formsubmit: true, - backforward: true, - reload: true, - formresubmit: true, - other: true, -}); - -const JSNavigationScheme = 'react-js-navigation'; - -type ErrorEvent = { - domain: any, - code: any, - description: any, -} - -type Event = Object; - -const DataDetectorTypes = [ - 'phoneNumber', - 'link', - 'address', - 'calendarEvent', - 'none', - 'all', -]; - -var defaultRenderLoading = () => ( - - - -); -var defaultRenderError = (errorDomain, errorCode, errorDesc) => ( - - - Error loading page - - - {'Domain: ' + errorDomain} - - - {'Error Code: ' + errorCode} - - - {'Description: ' + errorDesc} - - -); - -/** - * `WebView` renders web content in a native view. - * - *``` - * import React, { Component } from 'react'; - * import { WebView } from 'react-native'; - * - * class MyWeb extends Component { - * render() { - * return ( - * - * ); - * } - * } - *``` - * - * You can use this component to navigate back and forth in the web view's - * history and configure various properties for the web content. - */ -class WebView extends React.Component { - static JSNavigationScheme = JSNavigationScheme; - static NavigationType = NavigationType; - - static propTypes = { - ...View.propTypes, - - html: deprecatedPropType( - PropTypes.string, - 'Use the `source` prop instead.' - ), - - url: deprecatedPropType( - PropTypes.string, - 'Use the `source` prop instead.' - ), - - /** - * Loads static html or a uri (with optional headers) in the WebView. - */ - source: PropTypes.oneOfType([ - PropTypes.shape({ - /* - * The URI to load in the `WebView`. Can be a local or remote file. - */ - uri: PropTypes.string, - /* - * The HTTP Method to use. Defaults to GET if not specified. - * NOTE: On Android, only GET and POST are supported. - */ - method: PropTypes.string, - /* - * Additional HTTP headers to send with the request. - * NOTE: On Android, this can only be used with GET requests. - */ - headers: PropTypes.object, - /* - * The HTTP body to send with the request. This must be a valid - * UTF-8 string, and will be sent exactly as specified, with no - * additional encoding (e.g. URL-escaping or base64) applied. - * NOTE: On Android, this can only be used with POST requests. - */ - body: PropTypes.string, - }), - PropTypes.shape({ - /* - * A static HTML page to display in the WebView. - */ - html: PropTypes.string, - /* - * The base URL to be used for any relative links in the HTML. - */ - baseUrl: PropTypes.string, - }), - /* - * Used internally by packager. - */ - PropTypes.number, - ]), - - /** - * Function that returns a view to show if there's an error. - */ - renderError: PropTypes.func, // view to show if there's an error - /** - * Function that returns a loading indicator. - */ - renderLoading: PropTypes.func, - /** - * Function that is invoked when the `WebView` has finished loading. - */ - onLoad: PropTypes.func, - /** - * Function that is invoked when the `WebView` load succeeds or fails. - */ - onLoadEnd: PropTypes.func, - /** - * Function that is invoked when the `WebView` starts loading. - */ - onLoadStart: PropTypes.func, - /** - * Function that is invoked when the `WebView` load fails. - */ - onError: PropTypes.func, - /** - * Boolean value that determines whether the web view bounces - * when it reaches the edge of the content. The default value is `true`. - * @platform ios - */ - bounces: PropTypes.bool, - /** - * A floating-point number that determines how quickly the scroll view - * decelerates after the user lifts their finger. You may also use the - * string shortcuts `"normal"` and `"fast"` which match the underlying iOS - * settings for `UIScrollViewDecelerationRateNormal` and - * `UIScrollViewDecelerationRateFast` respectively: - * - * - normal: 0.998 - * - fast: 0.99 (the default for iOS web view) - * @platform ios - */ - decelerationRate: ScrollView.propTypes.decelerationRate, - /** - * Boolean value that determines whether scrolling is enabled in the - * `WebView`. The default value is `true`. - * @platform ios - */ - scrollEnabled: PropTypes.bool, - /** - * Controls whether to adjust the content inset for web views that are - * placed behind a navigation bar, tab bar, or toolbar. The default value - * is `true`. - */ - automaticallyAdjustContentInsets: PropTypes.bool, - /** - * The amount by which the web view content is inset from the edges of - * the scroll view. Defaults to {top: 0, left: 0, bottom: 0, right: 0}. - */ - contentInset: EdgeInsetsPropType, - /** - * Function that is invoked when the `WebView` loading starts or ends. - */ - onNavigationStateChange: PropTypes.func, - /** - * Boolean value that forces the `WebView` to show the loading view - * on the first load. - */ - startInLoadingState: PropTypes.bool, - /** - * The style to apply to the `WebView`. - */ - style: View.propTypes.style, - - /** - * Determines the types of data converted to clickable URLs in the web view’s content. - * By default only phone numbers are detected. - * - * You can provide one type or an array of many types. - * - * Possible values for `dataDetectorTypes` are: - * - * - `'phoneNumber'` - * - `'link'` - * - `'address'` - * - `'calendarEvent'` - * - `'none'` - * - `'all'` - * - * @platform ios - */ - dataDetectorTypes: PropTypes.oneOfType([ - PropTypes.oneOf(DataDetectorTypes), - PropTypes.arrayOf(PropTypes.oneOf(DataDetectorTypes)), - ]), - - /** - * Boolean value to enable JavaScript in the `WebView`. Used on Android only - * as JavaScript is enabled by default on iOS. The default value is `true`. - * @platform android - */ - javaScriptEnabled: PropTypes.bool, - - /** - * Boolean value to control whether DOM Storage is enabled. Used only in - * Android. - * @platform android - */ - domStorageEnabled: PropTypes.bool, - - /** - * Set this to provide JavaScript that will be injected into the web page - * when the view loads. - */ - injectedJavaScript: PropTypes.string, - - /** - * Sets the user-agent for the `WebView`. - * @platform android - */ - userAgent: PropTypes.string, - - /** - * Boolean that controls whether the web content is scaled to fit - * the view and enables the user to change the scale. The default value - * is `true`. - */ - scalesPageToFit: PropTypes.bool, - - /** - * Function that allows custom handling of any web view requests. Return - * `true` from the function to continue loading the request and `false` - * to stop loading. - * @platform ios - */ - onShouldStartLoadWithRequest: PropTypes.func, - - /** - * Boolean that determines whether HTML5 videos play inline or use the - * native full-screen controller. The default value is `false`. - * - * **NOTE** : In order for video to play inline, not only does this - * property need to be set to `true`, but the video element in the HTML - * document must also include the `webkit-playsinline` attribute. - * @platform ios - */ - allowsInlineMediaPlayback: PropTypes.bool, - - /** - * Boolean that determines whether HTML5 audio and video requires the user - * to tap them before they start playing. The default value is `true`. - */ - mediaPlaybackRequiresUserAction: PropTypes.bool, - - onBridgeMessage: PropTypes.func, - rpc: PropTypes.bool - }; - - state = { - viewState: WebViewState.IDLE, - lastErrorEvent: (null: ?ErrorEvent), - startInLoadingState: true, - }; - - componentWillMount() { - if (this.props.startInLoadingState) { - this.setState({viewState: WebViewState.LOADING}); - } - } - - render() { - var otherView = null; - - if (this.state.viewState === WebViewState.LOADING) { - otherView = (this.props.renderLoading || defaultRenderLoading)(); - } else if (this.state.viewState === WebViewState.ERROR) { - var errorEvent = this.state.lastErrorEvent; - invariant( - errorEvent != null, - 'lastErrorEvent expected to be non-null' - ); - otherView = (this.props.renderError || defaultRenderError)( - errorEvent.domain, - errorEvent.code, - errorEvent.description - ); - } else if (this.state.viewState !== WebViewState.IDLE) { - console.error( - 'RCTWebViewBridge invalid state encountered: ' + this.state.loading - ); - } - - var webViewStyles = [styles.container, styles.webView, this.props.style]; - if (this.state.viewState === WebViewState.LOADING || - this.state.viewState === WebViewState.ERROR) { - // if we're in either LOADING or ERROR states, don't show the webView - webViewStyles.push(styles.hidden); - } - - var onShouldStartLoadWithRequest = this.props.onShouldStartLoadWithRequest && ((event: Event) => { - var shouldStart = this.props.onShouldStartLoadWithRequest && - this.props.onShouldStartLoadWithRequest(event.nativeEvent); - WebViewBridgeManager.startLoadWithResult(!!shouldStart, event.nativeEvent.lockIdentifier); - }); - - var decelerationRate = processDecelerationRate(this.props.decelerationRate); - - var source = this.props.source || {}; - if (this.props.html) { - source.html = this.props.html; - } else if (this.props.url) { - source.uri = this.props.url; - } - - var webView = - ; - - return ( - - {webView} - {otherView} - - ); - } - - /** - * Go forward one page in the web view's history. - */ - goForward = () => { - UIManager.dispatchViewManagerCommand( - this.getWebViewHandle(), - UIManager.RCTWebViewBridge.Commands.goForward, - null - ); - }; - - /** - * Go back one page in the web view's history. - */ - goBack = () => { - UIManager.dispatchViewManagerCommand( - this.getWebViewHandle(), - UIManager.RCTWebViewBridge.Commands.goBack, - null - ); - }; - - /** - * Reloads the current page. - */ - reload = () => { - this.setState({viewState: WebViewState.LOADING}); - UIManager.dispatchViewManagerCommand( - this.getWebViewHandle(), - UIManager.RCTWebViewBridge.Commands.reload, - null - ); - }; - - /** - * Stop loading the current page. - */ - stopLoading = () => { - UIManager.dispatchViewManagerCommand( - this.getWebViewHandle(), - UIManager.RCTWebViewBridge.Commands.stopLoading, - null - ); - }; - - /** - * We return an event with a bunch of fields including: - * url, title, loading, canGoBack, canGoForward - */ - _updateNavigationState = (event: Event) => { - if (this.props.onNavigationStateChange) { - this.props.onNavigationStateChange(event.nativeEvent); - } - }; - - onBridgeMessage = (event: Event) => { - const { onBridgeMessage } = this.props; - const messages = event.nativeEvent.messages; - if (onBridgeMessage) { - messages.forEach((message) => { - onBridgeMessage(decode(message)); - }); - } - }; - - sendToBridge = (message: any) => { - WebViewBridgeManager.sendToBridge(this.getWebViewHandle(), encode(message)); - }; - - /** - * Returns the native `WebView` node. - */ - getWebViewHandle = (): any => { - return findNodeHandle(this.refs[RCT_WEBVIEW_REF]); - }; - - _onLoadingStart = (event: Event) => { - var onLoadStart = this.props.onLoadStart; - onLoadStart && onLoadStart(event); - this._updateNavigationState(event); - }; - - _onLoadingError = (event: Event) => { - event.persist(); // persist this event because we need to store it - var {onError, onLoadEnd} = this.props; - onError && onError(event); - onLoadEnd && onLoadEnd(event); - console.warn('Encountered an error loading page', event.nativeEvent); - - this.setState({ - lastErrorEvent: event.nativeEvent, - viewState: WebViewState.ERROR - }); - }; - - _onLoadingFinish = (event: Event) => { - var {onLoad, onLoadEnd} = this.props; - onLoad && onLoad(event); - onLoadEnd && onLoadEnd(event); - this.setState({ - viewState: WebViewState.IDLE, - }); - this._updateNavigationState(event); - }; -} - -var RCTWebViewBridge = requireNativeComponent('RCTWebViewBridge', WebView, { - nativeOnly: { - onLoadingStart: true, - onLoadingError: true, - onLoadingFinish: true, - }, -}); - -var styles = StyleSheet.create({ - container: { - flex: 1, - }, - errorContainer: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - backgroundColor: BGWASH, - }, - errorText: { - fontSize: 14, - textAlign: 'center', - marginBottom: 2, - }, - errorTextTitle: { - fontSize: 15, - fontWeight: '500', - marginBottom: 10, - }, - hidden: { - height: 0, - flex: 0, // disable 'flex:1' when hiding a View - }, - loadingView: { - backgroundColor: BGWASH, - flex: 1, - justifyContent: 'center', - alignItems: 'center', - height: 100, - }, - webView: { - backgroundColor: '#ffffff', - } -}); - -module.exports = WebView; diff --git a/lib/WebViewBridgeRPC/index.js b/lib/WebViewBridgeRPC/index.js deleted file mode 100644 index 0a1df672..00000000 --- a/lib/WebViewBridgeRPC/index.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright (c) 2016-present, Ali Najafizadeh - * All rights reserved. - * - * @providesModule WebViewBridgeRPC - * @noflow - */ -import React, { Component } from 'react' - -import WebViewBridge from '../WebViewBridge' -import rpc from '../core/rpc' - -export default class WebViewBridgeRPC extends Component { - constructor(props, context) { - super(props, context) - this.bridgeRef = null - this.rpc = null - } - - __sender = (payload) => { - this.bridgeRef.sendToBridge(payload) - } - - getWebViewRef = () => { - return this.bridgeRef - } - - register = (name, fn) => { - this.rpc.register(this.__sender, name, fn) - } - - invoke = (name, args, opt, resolve) => { - this.rpc.invoke(this.__sender, name, args, opt, resolve) - } - - componentDidMount() { - this.rpc = rpc() - } - - componentWillMount() { - this.rpc = null - } - - render() { - return ( - this.bridgeRef = ref} - rpc={true} - onBridgeMessage={(payload) => this.rpc.onMessage(payload)}/> - ) - } -} diff --git a/lib/core/base64.js b/lib/core/base64.js deleted file mode 100644 index e5356d3c..00000000 --- a/lib/core/base64.js +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright (c) 2016-present, Ali Najafizadeh - * All rights reserved. - * - * base64 encode and decode polyfil - * both b64Encode and b64Decode are the modified version of - * https://github.com/davidchambers/Base64.js - */ - -var b64ch = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; - -function b64Encode(input) { - var output = ''; - for ( - var block, charCode, idx = 0, map = b64ch; - input.charAt(idx | 0) || (map = '=', idx % 1); - output += map.charAt(63 & block >> 8 - idx % 1 * 8) - ) { - charCode = input.charCodeAt(idx += 3/4); - block = block << 8 | charCode; - } - return output; -} - -function b64Decode(input) { - var str = input.replace(/=+$/, ''); - var output = ''; - for ( - var bc = 0, bs, buffer, idx = 0; - buffer = str.charAt(idx++); - ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, - bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0 - ) { - buffer = b64ch.indexOf(buffer); - } - return output; -} - -function encode(input) { - if (typeof input !== 'string') { - try { - input = JSON.stringify(input); - } catch(e) {} - } - input = unescape(encodeURIComponent(input)); - return b64Encode(input); -} - -function decode(input) { - var result = decodeURIComponent(escape(b64Decode(input))); - try { - result = JSON.parse(result); - } catch(e){} - return result; -} - -module.exports = { - encode: encode, - decode: decode -} diff --git a/lib/core/rpc.js b/lib/core/rpc.js deleted file mode 100644 index dfe16627..00000000 --- a/lib/core/rpc.js +++ /dev/null @@ -1,123 +0,0 @@ -/** - * Copyright (c) 2016-present, Ali Najafizadeh - * All rights reserved. - */ -function rpc() { - var ids = 0 - var invokers = {} - var responseCallbacks = {} - - //invoker has accept 2 arguments - //fn(args, result). result is a function which accept an argument. - //that argument is being used to send to caller as a result value. - //by using this function, we can support both sync and async operation - function register(sender, name, fn) { - invokers[name] = function (id, args) { - fn(args, function (result) { - sender({ - id: id, - type: 'resolve', - result: result - }) - }, function (err) { - sender({ - id: id, - type: 'reject', - result: err - }) - }) - } - } - - function invoke(sender, name, args, opt, callback) { - var id = ++ids - var target = { - callback: callback - } - - if (opt) { - opt = {} - } - - if (!opt.timeout) { - opt.timeout = 0 - } - - if (opt.timeout) { - target.timeoutHandler = setTimeout(function () { - onReject(id, 'timeout') - }, opt.timeout) - } - - responseCallbacks[id] = target - - sender({ - id: id, - type: 'invoke', - name: name, - args: args - }) - } - - function onInvoke(payload) { - var invoker = invokers[payload.name] - if (invoker) { - setTimeout(function () { - invoker(payload.id, payload.args) - }, 15) - } - } - - function onResolve(id, result) { - var target = responseCallbacks[id] - if (target) { - clearTimeout(target.timeoutHandler) - delete responseCallbacks[id] - setTimeout(function () { - target.callback(null, result) - }, 15) - } - } - - function onReject(id, result) { - var target = responseCallbacks[id] - if (target) { - clearTimeout(target.timeoutHandler) - delete responseCallbacks[id] - setTimeout(function () { - target(result, null) - }, 15) - } - } - - function onMessage(payload) { - if (typeof payload === 'string') { - return - } - - // there are two types of payload - // invoke: { type: 'payload', id, name, args } - // resolve: { type: 'resolve', id, result } - // reject: { type: 'reject', id, result } - switch(payload.type) { - case 'invoke': - onInvoke(payload) - break - case 'resolve': - onResolve(payload.id, payload.result) - break - case 'reject': - onReject(payload.id, payload.result) - default: - //ignore - } - } - - return { - register: register, - invoke: invoke, - onMessage: onMessage - } -} - -module.exports = rpc diff --git a/lib/index.js b/lib/index.js deleted file mode 100644 index e67da4d7..00000000 --- a/lib/index.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Copyright (c) 2016-present, Ali Najafizadeh - * All rights reserved. - * - * @providesModule WebViewBridgeRPC - * @noflow - */ -export { default as WebViewBridge } from './WebViewBridge' -export { default as WebViewBridgeRPC } from './WebViewBridgeRPC' From 28c6c3d5f28514307c6a57fcca91a59b2531a172 Mon Sep 17 00:00:00 2001 From: Guy Eldar Date: Tue, 18 Oct 2016 20:54:01 +0300 Subject: [PATCH 34/77] Update RCTWebViewBridge.m Replaced command with message when going to stringByEvaluatingJavaScriptFromString --- ios/RCTWebViewBridge.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/RCTWebViewBridge.m b/ios/RCTWebViewBridge.m index ea4ed43c..c603f9db 100644 --- a/ios/RCTWebViewBridge.m +++ b/ios/RCTWebViewBridge.m @@ -108,7 +108,7 @@ - (void)sendToBridge:(NSString *)message ); NSString *command = [NSString stringWithFormat: format, message]; - [_webView stringByEvaluatingJavaScriptFromString:command]; + [_webView stringByEvaluatingJavaScriptFromString:message]; } - (NSURL *)URL From 30821ea460615d897aef515bda2f618230d60019 Mon Sep 17 00:00:00 2001 From: Yedidya Kennard Date: Tue, 1 Nov 2016 10:48:56 +0200 Subject: [PATCH 35/77] Changed main to the correct source folder --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dcaa33ae..e5143f6d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "react-native-webview-bridge", "version": "0.33.0", "description": "React Native WebView Javascript Bridge", - "main": "lib", + "main": "webview-bridge", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, From 7e3171b847f44f12c4392082fdf9a73df0ed8fc5 Mon Sep 17 00:00:00 2001 From: Guy Eldar Date: Tue, 29 Nov 2016 13:14:54 +0200 Subject: [PATCH 36/77] Added getSelectedHTML method --- ios/RCTWebViewBridge.h | 1 + ios/RCTWebViewBridge.m | 6 +++++- ios/RCTWebViewBridgeManager.m | 13 +++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/ios/RCTWebViewBridge.h b/ios/RCTWebViewBridge.h index 7c20fbfd..eec620c9 100644 --- a/ios/RCTWebViewBridge.h +++ b/ios/RCTWebViewBridge.h @@ -45,5 +45,6 @@ shouldStartLoadForRequest:(NSMutableDictionary *)request - (void)reload; - (void)sendToBridge:(NSString *)message; - (NSString*) getElementHTML:(NSString*)elementId; +- (NSString*) getSelectedHTML; @end diff --git a/ios/RCTWebViewBridge.m b/ios/RCTWebViewBridge.m index c603f9db..cd6e01c2 100644 --- a/ios/RCTWebViewBridge.m +++ b/ios/RCTWebViewBridge.m @@ -93,7 +93,11 @@ - (NSString*) getElementHTML:(NSString*)elementId return [_webView stringByEvaluatingJavaScriptFromString:JSString]; } - +- (NSString*) getSelectedHTML { + NSString* JSString = [NSString stringWithFormat:@"document.getSelection()"]; + NSLog(@"selectedHTML is: %@",JSString); + return [_webView stringByEvaluatingJavaScriptFromString:JSString]; +} - (void)sendToBridge:(NSString *)message { diff --git a/ios/RCTWebViewBridgeManager.m b/ios/RCTWebViewBridgeManager.m index fc61a80b..9dac87f2 100644 --- a/ios/RCTWebViewBridgeManager.m +++ b/ios/RCTWebViewBridgeManager.m @@ -117,6 +117,19 @@ - (UIView *)view }]; } +RCT_EXPORT_METHOD(getSelectedHTML:(nonnull NSNumber *)reactTag :(NSString*)elementId :(RCTResponseSenderBlock)callback) +{ + [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { + RCTWebViewBridge *view = viewRegistry[reactTag]; + if (![view isKindOfClass:[RCTWebViewBridge class]]) { + RCTLogError(@"Invalid view returned from registry, expecting RCTWebViewBridge, got: %@", view); + } else { + NSString* html = [view getSelectedHTML]; + NSLog(@"Selected HTML is %@", html); + callback(@[[NSNull null], html]); + } + }]; +} RCT_EXPORT_METHOD(sendToBridge:(nonnull NSNumber *)reactTag value:(NSString*)message) From 0f366f8a2ad59d4b97c3c566bb832830e45e0d20 Mon Sep 17 00:00:00 2001 From: Guy Eldar Date: Tue, 29 Nov 2016 13:38:10 +0200 Subject: [PATCH 37/77] Adding getSelectedHTML method --- webview-bridge/index.ios.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/webview-bridge/index.ios.js b/webview-bridge/index.ios.js index 9784e4fb..4100e3f4 100644 --- a/webview-bridge/index.ios.js +++ b/webview-bridge/index.ios.js @@ -237,6 +237,11 @@ var WebViewBridge = React.createClass({ console.log("html from editor is " + html); }, + getSelectedHTML: function (elementId: string, callback) { + var html = WebViewBridgeManager.getSelectedHTML(this.getWebViewBridgeHandle(), elementId, callback); + console.log("html from editor is " + html); + }, + /** * We return an event with a bunch of fields including: * url, title, loading, canGoBack, canGoForward From b910f602c5592c4cbfce4085e67f39a06b1efded Mon Sep 17 00:00:00 2001 From: Guy Eldar Date: Thu, 16 Feb 2017 18:09:26 +0200 Subject: [PATCH 38/77] Changing the default to NO when coming back from BG --- ios/RCTWebViewBridgeManager.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ios/RCTWebViewBridgeManager.m b/ios/RCTWebViewBridgeManager.m index 9dac87f2..612c98f5 100644 --- a/ios/RCTWebViewBridgeManager.m +++ b/ios/RCTWebViewBridgeManager.m @@ -162,8 +162,8 @@ - (BOOL)webView:(__unused RCTWebViewBridge *)webView _shouldStartLoadLock = nil; return returnValue; } else { - RCTLogWarn(@"Did not receive response to shouldStartLoad in time, defaulting to YES"); - return YES; + RCTLogWarn(@"Did not receive response to shouldStartLoad in time, defaulting to NO"); + return NO; } } From 405d0ddfb1980ef9dd2dd53f9e7e6c6f355b662a Mon Sep 17 00:00:00 2001 From: eladgel Date: Tue, 15 Jan 2019 23:03:06 +0200 Subject: [PATCH 39/77] add requestFocus property --- .../reactnativewebviewbridge/WebViewBridgeManager.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgeManager.java b/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgeManager.java index c1250163..09fb1409 100644 --- a/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgeManager.java +++ b/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgeManager.java @@ -65,6 +65,13 @@ static private void evaluateJavascript(WebView root, String javascript) { root.loadUrl("javascript:" + javascript); } } + + @ReactProp(name = "requestFocus") + public void requestFocus(WebView root, boolean isRequestFocus) { + if(isRequestFocus) { + root.requestFocus(); + } + } @ReactProp(name = "allowFileAccessFromFileURLs") public void setAllowFileAccessFromFileURLs(WebView root, boolean allows) { @@ -75,4 +82,4 @@ public void setAllowFileAccessFromFileURLs(WebView root, boolean allows) { public void setAllowUniversalAccessFromFileURLs(WebView root, boolean allows) { root.getSettings().setAllowUniversalAccessFromFileURLs(allows); } -} \ No newline at end of file +} From f3440dd23fc216fdddf57a8c7b62dd064f9678ee Mon Sep 17 00:00:00 2001 From: eladgel Date: Tue, 26 Feb 2019 11:55:44 +0200 Subject: [PATCH 40/77] Update RCTWebViewBridgeManager.m --- ios/RCTWebViewBridgeManager.m | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/ios/RCTWebViewBridgeManager.m b/ios/RCTWebViewBridgeManager.m index 218cca0e..10b74a04 100644 --- a/ios/RCTWebViewBridgeManager.m +++ b/ios/RCTWebViewBridgeManager.m @@ -11,11 +11,25 @@ */ #import "RCTWebViewBridgeManager.h" +#import "RCTWebViewBridge.h" +#if __has_include() #import +#else +#import "RCTBridge.h" +#endif + +#if __has_include() #import -#import "RCTWebViewBridge.h" +#else +#import "RCTUIManager.h" +#endif + +#if __has_include() +#import +#else #import "UIView+React.h" +#endif @interface RCTWebViewBridgeManager () @@ -49,8 +63,8 @@ - (UIView *)view RCT_EXPORT_VIEW_PROPERTY(onLoadingError, RCTDirectEventBlock) RCT_EXPORT_VIEW_PROPERTY(onShouldStartLoadWithRequest, RCTDirectEventBlock) RCT_REMAP_VIEW_PROPERTY(allowsInlineMediaPlayback, _webView.allowsInlineMediaPlayback, BOOL) -RCT_REMAP_VIEW_PROPERTY(mediaPlaybackRequiresUserAction, _webView.mediaPlaybackRequiresUserAction, BOOL) RCT_EXPORT_VIEW_PROPERTY(onBridgeMessage, RCTDirectEventBlock) +RCT_REMAP_VIEW_PROPERTY(keyboardDisplayRequiresUserAction, _webView.keyboardDisplayRequiresUserAction, BOOL) - (NSDictionary *)constantsToExport { @@ -87,7 +101,7 @@ + (BOOL)requiresMainQueueSetup RCT_EXPORT_METHOD(goForward:(nonnull NSNumber *)reactTag) { [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { - RCTWebViewBridge *view = viewRegistry[reactTag]; + RCTWebViewBridge* view = viewRegistry[reactTag]; if (![view isKindOfClass:[RCTWebViewBridge class]]) { RCTLogError(@"Invalid view returned from registry, expecting RCTWebViewBridge, got: %@", view); } else { From 77c8b407666b433af32a58287c6a47d483ac59a9 Mon Sep 17 00:00:00 2001 From: eladgel Date: Tue, 26 Feb 2019 12:13:23 +0200 Subject: [PATCH 41/77] Update RCTWebViewBridge.h --- ios/RCTWebViewBridge.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ios/RCTWebViewBridge.h b/ios/RCTWebViewBridge.h index c7d34222..5262d3d2 100644 --- a/ios/RCTWebViewBridge.h +++ b/ios/RCTWebViewBridge.h @@ -10,8 +10,12 @@ * of patent rights can be found in the PATENTS file in the same directory. */ +#if __has_include() #import - +#else +#import "RCTView.h" +#endif + @class RCTWebViewBridge; /** From 10b7370f92410b5ae52b6e0ba3bd8853fbd9b85f Mon Sep 17 00:00:00 2001 From: eladgel Date: Tue, 26 Feb 2019 13:50:02 +0200 Subject: [PATCH 42/77] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 53ae3586..69dfa7b8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-webview-bridge", - "version": "0.40.1", + "version": "0.40.2", "description": "React Native WebView Javascript Bridge", "main": "webview-bridge", "directories": { From 2e5b24c1b98211e7bcdd97e541fa986ca07677d6 Mon Sep 17 00:00:00 2001 From: eladgel Date: Sun, 5 Jan 2020 12:36:19 +0200 Subject: [PATCH 43/77] Update build.gradle --- android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/build.gradle b/android/build.gradle index dfec22af..fb7c9ffd 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -30,5 +30,5 @@ repositories { } dependencies { - compile 'com.facebook.react:react-native:0.19.+' + api 'com.facebook.react:react-native:+' } From 05a1d535f6fa14edcc20ea7991714072340d454d Mon Sep 17 00:00:00 2001 From: eladgel Date: Sun, 5 Jan 2020 12:43:22 +0200 Subject: [PATCH 44/77] Update WebViewBridgePackage.java --- .../alinz/reactnativewebviewbridge/WebViewBridgePackage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgePackage.java b/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgePackage.java index de48b193..26792640 100644 --- a/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgePackage.java +++ b/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgePackage.java @@ -23,7 +23,7 @@ public List createViewManagers(ReactApplicationContext reactApplica ); } - @Override + //@Override public List> createJSModules() { return Arrays.asList(); } From e5ec3fb1883e0a08c8b7089bde84ca4736ab1c2a Mon Sep 17 00:00:00 2001 From: eladgel Date: Sun, 5 Jan 2020 13:21:11 +0200 Subject: [PATCH 45/77] Update build.gradle --- android/build.gradle | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index fb7c9ffd..863b463b 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,22 +1,22 @@ buildscript { - repositories { - jcenter() - } - - dependencies { - classpath 'com.android.tools.build:gradle:1.1.3' - } +// repositories { +// jcenter() +// } +// +// dependencies { +// classpath 'com.android.tools.build:gradle:3.5.0' +// } } apply plugin: 'com.android.library' android { - compileSdkVersion 23 - buildToolsVersion "23.0.1" + compileSdkVersion 28 + buildToolsVersion "28.0.3" defaultConfig { - minSdkVersion 16 - targetSdkVersion 23 + minSdkVersion 21 + targetSdkVersion 28 versionCode 1 versionName "1.0" } From 880310b8eee401e99f5e7f499e4366782c7c0485 Mon Sep 17 00:00:00 2001 From: eladgel Date: Wed, 1 Apr 2020 21:45:23 +0300 Subject: [PATCH 46/77] Update project.pbxproj --- ios/React-Native-Webview-Bridge.xcodeproj/project.pbxproj | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ios/React-Native-Webview-Bridge.xcodeproj/project.pbxproj b/ios/React-Native-Webview-Bridge.xcodeproj/project.pbxproj index 07c326d7..461379e9 100644 --- a/ios/React-Native-Webview-Bridge.xcodeproj/project.pbxproj +++ b/ios/React-Native-Webview-Bridge.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 4114DC5C1C187CCB003CD988 /* RCTWebViewBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 4114DC591C187CCB003CD988 /* RCTWebViewBridge.m */; }; 4114DC5D1C187CCB003CD988 /* RCTWebViewBridgeManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4114DC5B1C187CCB003CD988 /* RCTWebViewBridgeManager.m */; }; + D822A676238DABDC001CCF6E /* WKWebView+SynchronousEvaluateJavaScript.m in Sources */ = {isa = PBXBuildFile; fileRef = D822A675238DABDC001CCF6E /* WKWebView+SynchronousEvaluateJavaScript.m */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -29,6 +30,8 @@ 4114DC591C187CCB003CD988 /* RCTWebViewBridge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWebViewBridge.m; sourceTree = SOURCE_ROOT; }; 4114DC5A1C187CCB003CD988 /* RCTWebViewBridgeManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebViewBridgeManager.h; sourceTree = SOURCE_ROOT; }; 4114DC5B1C187CCB003CD988 /* RCTWebViewBridgeManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWebViewBridgeManager.m; sourceTree = SOURCE_ROOT; }; + D822A674238DABDC001CCF6E /* WKWebView+SynchronousEvaluateJavaScript.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "WKWebView+SynchronousEvaluateJavaScript.h"; sourceTree = SOURCE_ROOT; }; + D822A675238DABDC001CCF6E /* WKWebView+SynchronousEvaluateJavaScript.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "WKWebView+SynchronousEvaluateJavaScript.m"; sourceTree = SOURCE_ROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -65,6 +68,8 @@ 4114DC591C187CCB003CD988 /* RCTWebViewBridge.m */, 4114DC5A1C187CCB003CD988 /* RCTWebViewBridgeManager.h */, 4114DC5B1C187CCB003CD988 /* RCTWebViewBridgeManager.m */, + D822A674238DABDC001CCF6E /* WKWebView+SynchronousEvaluateJavaScript.h */, + D822A675238DABDC001CCF6E /* WKWebView+SynchronousEvaluateJavaScript.m */, ); path = "React-Native-Webview-Bridge"; sourceTree = ""; @@ -108,6 +113,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, ); mainGroup = 4114DC431C187C3A003CD988; @@ -125,6 +131,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D822A676238DABDC001CCF6E /* WKWebView+SynchronousEvaluateJavaScript.m in Sources */, 4114DC5D1C187CCB003CD988 /* RCTWebViewBridgeManager.m in Sources */, 4114DC5C1C187CCB003CD988 /* RCTWebViewBridge.m in Sources */, ); From 36573ef1740912e0044eaca4c8ccfd1ed7a6499f Mon Sep 17 00:00:00 2001 From: eladgel Date: Wed, 1 Apr 2020 21:46:15 +0300 Subject: [PATCH 47/77] Update RCTWebViewBridge.m --- ios/RCTWebViewBridge.m | 270 +++++++++++++++++++++++++++++------------ 1 file changed, 193 insertions(+), 77 deletions(-) diff --git a/ios/RCTWebViewBridge.m b/ios/RCTWebViewBridge.m index dad442a2..3c2f73d1 100644 --- a/ios/RCTWebViewBridge.m +++ b/ios/RCTWebViewBridge.m @@ -13,6 +13,8 @@ #import "RCTWebViewBridge.h" #import +#import +#import "WKWebView+SynchronousEvaluateJavaScript.h" #import #import @@ -41,7 +43,7 @@ -(id)inputAccessoryView } @end -@interface RCTWebViewBridge () +@interface RCTWebViewBridge () @property (nonatomic, copy) RCTDirectEventBlock onLoadingStart; @property (nonatomic, copy) RCTDirectEventBlock onLoadingFinish; @@ -53,7 +55,7 @@ @interface RCTWebViewBridge () @implementation RCTWebViewBridge { - UIWebView *_webView; + WKWebView *_webView; NSString *_injectedJavaScript; } @@ -63,9 +65,10 @@ - (instancetype)initWithFrame:(CGRect)frame super.backgroundColor = [UIColor clearColor]; _automaticallyAdjustContentInsets = YES; _contentInset = UIEdgeInsetsZero; - _webView = [[UIWebView alloc] initWithFrame:self.bounds]; - _webView.delegate = self; - _webView.mediaPlaybackRequiresUserAction = NO; + WKWebViewConfiguration *theConfiguration = [[WKWebViewConfiguration alloc] init]; + _webView = [[WKWebView alloc] initWithFrame:self.bounds configuration:theConfiguration]; + [_webView setNavigationDelegate:self]; +// _webView.mediaPlaybackRequiresUserAction = NO; [self addSubview:_webView]; } return self; @@ -121,7 +124,7 @@ - (void)sendToBridge:(NSString *)message - (NSURL *)URL { - return _webView.request.URL; + return _webView.URL; } - (void)setSource:(NSDictionary *)source @@ -142,7 +145,7 @@ - (void)setSource:(NSDictionary *)source // passing the redirect urls back here, so we ignore them if trying to load // the same url. We'll expose a call to 'reload' to allow a user to load // the existing page. - if ([request.URL isEqual:_webView.request.URL]) { + if ([request.URL isEqual:_webView.URL]) { return; } if (!request.URL) { @@ -183,7 +186,7 @@ - (UIColor *)backgroundColor - (NSMutableDictionary *)baseEvent { NSMutableDictionary *event = [[NSMutableDictionary alloc] initWithDictionary:@{ - @"url": _webView.request.URL.absoluteString ?: @"", + @"url": _webView.URL.absoluteString ?: @"", @"loading" : @(_webView.loading), @"title": [_webView stringByEvaluatingJavaScriptFromString:@"document.title"], @"canGoBack": @(_webView.canGoBack), @@ -231,98 +234,211 @@ -(void)setHideKeyboardAccessoryView:(BOOL)hideKeyboardAccessoryView object_setClass(subview, newClass); } -#pragma mark - UIWebViewDelegate methods - -- (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request - navigationType:(UIWebViewNavigationType)navigationType +#pragma mark - WKNavigationDelegate methods +- (void)webView:(WKWebView *)webView + decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction + decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { - BOOL isJSNavigation = [request.URL.scheme isEqualToString:RCTJSNavigationScheme]; + BOOL isJSNavigation = [navigationAction.request.URL.scheme isEqualToString:RCTJSNavigationScheme]; - if (!isJSNavigation && [request.URL.scheme isEqualToString:RCTWebViewBridgeSchema]) { - NSString* message = [webView stringByEvaluatingJavaScriptFromString:@"WebViewBridge.__fetch__()"]; + if (!isJSNavigation && [navigationAction.request.URL.scheme isEqualToString:RCTWebViewBridgeSchema]) { + NSString* message = [webView stringByEvaluatingJavaScriptFromString:@"WebViewBridge.__fetch__()"]; - NSMutableDictionary *onBridgeMessageEvent = [[NSMutableDictionary alloc] initWithDictionary:@{ - @"messages": [self stringArrayJsonToArray: message] - }]; + NSMutableDictionary *onBridgeMessageEvent = [[NSMutableDictionary alloc] initWithDictionary:@{ + @"messages": [self stringArrayJsonToArray: message] + }]; - _onBridgeMessage(onBridgeMessageEvent); + _onBridgeMessage(onBridgeMessageEvent); - isJSNavigation = YES; - } + isJSNavigation = YES; + } - // skip this for the JS Navigation handler - if (!isJSNavigation && _onShouldStartLoadWithRequest) { - NSMutableDictionary *event = [self baseEvent]; - [event addEntriesFromDictionary: @{ - @"url": (request.URL).absoluteString, - @"navigationType": @(navigationType) - }]; - if (![self.delegate webView:self - shouldStartLoadForRequest:event - withCallback:_onShouldStartLoadWithRequest]) { - return NO; + // skip this for the JS Navigation handler + if (!isJSNavigation && _onShouldStartLoadWithRequest) { + NSMutableDictionary *event = [self baseEvent]; + [event addEntriesFromDictionary: @{ + @"url": (navigationAction.request.URL).absoluteString, + @"navigationType": @(navigationAction.navigationType) + }]; + if (![self.delegate webView:self + shouldStartLoadForRequest:event + withCallback:_onShouldStartLoadWithRequest]) { + decisionHandler(WKNavigationActionPolicyCancel); + return; + } } - } - if (_onLoadingStart) { - // We have this check to filter out iframe requests and whatnot - BOOL isTopFrame = [request.URL isEqual:request.mainDocumentURL]; - if (isTopFrame) { - NSMutableDictionary *event = [self baseEvent]; - [event addEntriesFromDictionary: @{ - @"url": (request.URL).absoluteString, - @"navigationType": @(navigationType) - }]; - _onLoadingStart(event); + if (_onLoadingStart) { + // We have this check to filter out iframe requests and whatnot + BOOL isTopFrame = [navigationAction.request.URL isEqual:navigationAction.request.mainDocumentURL]; + if (isTopFrame) { + NSMutableDictionary *event = [self baseEvent]; + [event addEntriesFromDictionary: @{ + @"url": (navigationAction.request.URL).absoluteString, + @"navigationType": @(navigationAction.navigationType) + }]; + _onLoadingStart(event); + } } - } - // JS Navigation handler - return !isJSNavigation; + // JS Navigation handler + decisionHandler(isJSNavigation ? WKNavigationActionPolicyCancel : WKNavigationActionPolicyAllow); } -- (void)webView:(__unused UIWebView *)webView didFailLoadWithError:(NSError *)error +//- (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request +// navigationType:(UIWebViewNavigationType)navigationType +//{ +// BOOL isJSNavigation = [request.URL.scheme isEqualToString:RCTJSNavigationScheme]; +// +// if (!isJSNavigation && [request.URL.scheme isEqualToString:RCTWebViewBridgeSchema]) { +// NSString* message = [webView stringByEvaluatingJavaScriptFromString:@"WebViewBridge.__fetch__()"]; +// +// NSMutableDictionary *onBridgeMessageEvent = [[NSMutableDictionary alloc] initWithDictionary:@{ +// @"messages": [self stringArrayJsonToArray: message] +// }]; +// +// _onBridgeMessage(onBridgeMessageEvent); +// +// isJSNavigation = YES; +// } +// +// // skip this for the JS Navigation handler +// if (!isJSNavigation && _onShouldStartLoadWithRequest) { +// NSMutableDictionary *event = [self baseEvent]; +// [event addEntriesFromDictionary: @{ +// @"url": (request.URL).absoluteString, +// @"navigationType": @(navigationType) +// }]; +// if (![self.delegate webView:self +// shouldStartLoadForRequest:event +// withCallback:_onShouldStartLoadWithRequest]) { +// return NO; +// } +// } +// +// if (_onLoadingStart) { +// // We have this check to filter out iframe requests and whatnot +// BOOL isTopFrame = [request.URL isEqual:request.mainDocumentURL]; +// if (isTopFrame) { +// NSMutableDictionary *event = [self baseEvent]; +// [event addEntriesFromDictionary: @{ +// @"url": (request.URL).absoluteString, +// @"navigationType": @(navigationType) +// }]; +// _onLoadingStart(event); +// } +// } +// +// // JS Navigation handler +// return !isJSNavigation; +//} +// + +- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error { - if (_onLoadingError) { - if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) { - // NSURLErrorCancelled is reported when a page has a redirect OR if you load - // a new URL in the WebView before the previous one came back. We can just - // ignore these since they aren't real errors. - // http://stackoverflow.com/questions/1024748/how-do-i-fix-nsurlerrordomain-error-999-in-iphone-3-0-os - return; + if (_onLoadingError) { + if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) { + // NSURLErrorCancelled is reported when a page has a redirect OR if you load + // a new URL in the WebView before the previous one came back. We can just + // ignore these since they aren't real errors. + // http://stackoverflow.com/questions/1024748/how-do-i-fix-nsurlerrordomain-error-999-in-iphone-3-0-os + return; + } + + NSMutableDictionary *event = [self baseEvent]; + [event addEntriesFromDictionary:@{ + @"domain": error.domain, + @"code": @(error.code), + @"description": error.localizedDescription, + }]; + _onLoadingError(event); } +} - NSMutableDictionary *event = [self baseEvent]; - [event addEntriesFromDictionary:@{ - @"domain": error.domain, - @"code": @(error.code), - @"description": error.localizedDescription, - }]; - _onLoadingError(event); - } +- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error +{ + if (_onLoadingError) { + if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) { + // NSURLErrorCancelled is reported when a page has a redirect OR if you load + // a new URL in the WebView before the previous one came back. We can just + // ignore these since they aren't real errors. + // http://stackoverflow.com/questions/1024748/how-do-i-fix-nsurlerrordomain-error-999-in-iphone-3-0-os + return; + } + + NSMutableDictionary *event = [self baseEvent]; + [event addEntriesFromDictionary:@{ + @"domain": error.domain, + @"code": @(error.code), + @"description": error.localizedDescription, + }]; + _onLoadingError(event); + } } -- (void)webViewDidFinishLoad:(UIWebView *)webView +//- (void)webView:(__unused UIWebView *)webView didFailLoadWithError:(NSError *)error +//{ +// if (_onLoadingError) { +// if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) { +// // NSURLErrorCancelled is reported when a page has a redirect OR if you load +// // a new URL in the WebView before the previous one came back. We can just +// // ignore these since they aren't real errors. +// // http://stackoverflow.com/questions/1024748/how-do-i-fix-nsurlerrordomain-error-999-in-iphone-3-0-os +// return; +// } +// +// NSMutableDictionary *event = [self baseEvent]; +// [event addEntriesFromDictionary:@{ +// @"domain": error.domain, +// @"code": @(error.code), +// @"description": error.localizedDescription, +// }]; +// _onLoadingError(event); +// } +//} + +- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { - //injecting WebViewBridge Script - NSString *webViewBridgeScriptContent = [self webViewBridgeScript]; - [webView stringByEvaluatingJavaScriptFromString:webViewBridgeScriptContent]; - ////////////////////////////////////////////////////////////////////////////// + //injecting WebViewBridge Script + NSString *webViewBridgeScriptContent = [self webViewBridgeScript]; + [webView stringByEvaluatingJavaScriptFromString:webViewBridgeScriptContent]; + ////////////////////////////////////////////////////////////////////////////// - if (_injectedJavaScript != nil) { - NSString *jsEvaluationValue = [webView stringByEvaluatingJavaScriptFromString:_injectedJavaScript]; + if (_injectedJavaScript != nil) { + NSString *jsEvaluationValue = [webView stringByEvaluatingJavaScriptFromString:_injectedJavaScript]; - NSMutableDictionary *event = [self baseEvent]; - event[@"jsEvaluationValue"] = jsEvaluationValue; + NSMutableDictionary *event = [self baseEvent]; + event[@"jsEvaluationValue"] = jsEvaluationValue; - _onLoadingFinish(event); - } - // we only need the final 'finishLoad' call so only fire the event when we're actually done loading. - else if (_onLoadingFinish && !webView.loading && ![webView.request.URL.absoluteString isEqualToString:@"about:blank"]) { - _onLoadingFinish([self baseEvent]); - } + _onLoadingFinish(event); + } + // we only need the final 'finishLoad' call so only fire the event when we're actually done loading. + else if (_onLoadingFinish && !webView.loading && ![webView.URL.absoluteString isEqualToString:@"about:blank"]) { + _onLoadingFinish([self baseEvent]); + } } +//- (void)webViewDidFinishLoad:(UIWebView *)webView +//{ +// //injecting WebViewBridge Script +// NSString *webViewBridgeScriptContent = [self webViewBridgeScript]; +// [webView stringByEvaluatingJavaScriptFromString:webViewBridgeScriptContent]; +// ////////////////////////////////////////////////////////////////////////////// +// +// if (_injectedJavaScript != nil) { +// NSString *jsEvaluationValue = [webView stringByEvaluatingJavaScriptFromString:_injectedJavaScript]; +// +// NSMutableDictionary *event = [self baseEvent]; +// event[@"jsEvaluationValue"] = jsEvaluationValue; +// +// _onLoadingFinish(event); +// } +// // we only need the final 'finishLoad' call so only fire the event when we're actually done loading. +// else if (_onLoadingFinish && !webView.loading && ![webView.request.URL.absoluteString isEqualToString:@"about:blank"]) { +// _onLoadingFinish([self baseEvent]); +// } +//} + - (NSArray*)stringArrayJsonToArray:(NSString *)message { return [NSJSONSerialization JSONObjectWithData:[message dataUsingEncoding:NSUTF8StringEncoding] From 9d842a28ae9998572f323f9cde746356ecafb6a4 Mon Sep 17 00:00:00 2001 From: eladgel Date: Wed, 1 Apr 2020 21:47:20 +0300 Subject: [PATCH 48/77] Update RCTWebViewBridgeManager.m --- ios/RCTWebViewBridgeManager.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/RCTWebViewBridgeManager.m b/ios/RCTWebViewBridgeManager.m index 2d27b14d..2c5796c7 100644 --- a/ios/RCTWebViewBridgeManager.m +++ b/ios/RCTWebViewBridgeManager.m @@ -64,7 +64,7 @@ - (UIView *)view RCT_EXPORT_VIEW_PROPERTY(onShouldStartLoadWithRequest, RCTDirectEventBlock) RCT_REMAP_VIEW_PROPERTY(allowsInlineMediaPlayback, _webView.allowsInlineMediaPlayback, BOOL) RCT_EXPORT_VIEW_PROPERTY(onBridgeMessage, RCTDirectEventBlock) -RCT_REMAP_VIEW_PROPERTY(keyboardDisplayRequiresUserAction, _webView.keyboardDisplayRequiresUserAction, BOOL) +//RCT_REMAP_VIEW_PROPERTY(keyboardDisplayRequiresUserAction, _webView.keyboardDisplayRequiresUserAction, BOOL) - (NSDictionary *)constantsToExport { From 0ec60a1afc7c2cfc4aecd89b2c9dc513b87b6dc0 Mon Sep 17 00:00:00 2001 From: eladgel Date: Wed, 1 Apr 2020 21:47:57 +0300 Subject: [PATCH 49/77] Create WKWebView+SynchronousEvaluateJavaScript.h --- ios/WKWebView+SynchronousEvaluateJavaScript.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 ios/WKWebView+SynchronousEvaluateJavaScript.h diff --git a/ios/WKWebView+SynchronousEvaluateJavaScript.h b/ios/WKWebView+SynchronousEvaluateJavaScript.h new file mode 100644 index 00000000..47860265 --- /dev/null +++ b/ios/WKWebView+SynchronousEvaluateJavaScript.h @@ -0,0 +1,14 @@ +// +// WKWebView+ SynchronousEvaluateJavaScript.h +// React-Native-Webview-Bridge +// +// Created by Guy Eldar on 26/11/2019. +// Copyright © 2019 alinz. All rights reserved. +// + +#import +#import + +@interface WKWebView(SynchronousEvaluateJavaScript) +- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script; +@end From 0c1ae36b49f9648c984b1cad0f009fa19d2adc2e Mon Sep 17 00:00:00 2001 From: eladgel Date: Wed, 1 Apr 2020 21:48:19 +0300 Subject: [PATCH 50/77] Create WKWebView+SynchronousEvaluateJavaScript.m --- ios/WKWebView+SynchronousEvaluateJavaScript.m | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 ios/WKWebView+SynchronousEvaluateJavaScript.m diff --git a/ios/WKWebView+SynchronousEvaluateJavaScript.m b/ios/WKWebView+SynchronousEvaluateJavaScript.m new file mode 100644 index 00000000..ad327d3a --- /dev/null +++ b/ios/WKWebView+SynchronousEvaluateJavaScript.m @@ -0,0 +1,35 @@ +// +// WKWebView+ SynchronousEvaluateJavaScript.m +// React-Native-Webview-Bridge +// +// Created by Guy Eldar on 26/11/2019. +// Copyright © 2019 alinz. All rights reserved. +// + +#import "WKWebView+SynchronousEvaluateJavaScript.h" + +@implementation WKWebView(SynchronousEvaluateJavaScript) +- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script +{ + __block NSString *resultString = nil; + __block BOOL finished = NO; + + [self evaluateJavaScript:script completionHandler:^(id result, NSError *error) { + if (error == nil) { + if (result != nil) { + resultString = [NSString stringWithFormat:@"%@", result]; + } + } else { + NSLog(@"evaluateJavaScript error : %@", error.localizedDescription); + } + finished = YES; + }]; + + while (!finished) + { + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; + } + + return resultString; +} +@end From 4434b4526fee82c1cb51033ae7e118c31c82d002 Mon Sep 17 00:00:00 2001 From: eladgel Date: Mon, 13 Apr 2020 21:59:13 +0300 Subject: [PATCH 51/77] Update index.ios.js --- webview-bridge/index.ios.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/webview-bridge/index.ios.js b/webview-bridge/index.ios.js index ad6b96f4..cc9fc65b 100644 --- a/webview-bridge/index.ios.js +++ b/webview-bridge/index.ios.js @@ -21,7 +21,7 @@ var createReactClass = require('create-react-class'); var invariant = require('invariant'); var keyMirror = require('keymirror'); var resolveAssetSource = require('react-native/Libraries/Image/resolveAssetSource'); - +import { WebView } from "react-native-webview"; var { ActivityIndicator, EdgeInsetsPropType, @@ -29,7 +29,6 @@ var { Text, View, ViewPropTypes, - WebView, requireNativeComponent, UIManager, NativeModules: { From 88344ad1cd7db7c81f1b820697b1438dfaaca83b Mon Sep 17 00:00:00 2001 From: eladgel Date: Mon, 13 Apr 2020 21:59:55 +0300 Subject: [PATCH 52/77] Update index.android.js --- webview-bridge/index.android.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webview-bridge/index.android.js b/webview-bridge/index.android.js index 14e5e263..9a5f281e 100644 --- a/webview-bridge/index.android.js +++ b/webview-bridge/index.android.js @@ -20,6 +20,7 @@ var createReactClass = require('create-react-class'); var invariant = require('invariant'); var keyMirror = require('keymirror'); var resolveAssetSource = require('react-native/Libraries/Image/resolveAssetSource'); +import { WebView } from "react-native-webview"; var { ReactNativeViewAttributes, @@ -29,7 +30,6 @@ var { Text, View, ViewPropTypes, - WebView, requireNativeComponent, DeviceEventEmitter, NativeModules: { From d5788ec934061175977c84832da0e6854df0cbfe Mon Sep 17 00:00:00 2001 From: Elad Gelman Date: Mon, 20 Apr 2020 19:42:20 +0300 Subject: [PATCH 53/77] reverting --- webview-bridge/index.android.js | 1 - 1 file changed, 1 deletion(-) diff --git a/webview-bridge/index.android.js b/webview-bridge/index.android.js index dd415224..9e1bb884 100644 --- a/webview-bridge/index.android.js +++ b/webview-bridge/index.android.js @@ -22,7 +22,6 @@ var keyMirror = require('keymirror'); var resolveAssetSource = require('react-native/Libraries/Image/resolveAssetSource'); import { WebView } from "react-native-webview"; -var WebView = require('react-native-webview'); var { ReactNativeViewAttributes, UIManager, From 24df2f2f7f1599e9baade0fe4b98c3b09f2bf24a Mon Sep 17 00:00:00 2001 From: eladgel Date: Mon, 20 Apr 2020 21:25:50 +0300 Subject: [PATCH 54/77] Update index.ios.js --- webview-bridge/index.ios.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/webview-bridge/index.ios.js b/webview-bridge/index.ios.js index ef28fa4a..ffd59aef 100644 --- a/webview-bridge/index.ios.js +++ b/webview-bridge/index.ios.js @@ -21,12 +21,9 @@ var createReactClass = require('create-react-class'); var invariant = require('invariant'); var keyMirror = require('keymirror'); var resolveAssetSource = require('react-native/Libraries/Image/resolveAssetSource'); -<<<<<<< HEAD + import { WebView } from "react-native-webview"; -======= -var WebView = require('react-native-webview'); ->>>>>>> 8f303f50c23d67f68a4219a4d33a0bac9d9237b5 var { ActivityIndicator, EdgeInsetsPropType, From 36bd6fc008973fbd15cc801cd569b4d0aa190918 Mon Sep 17 00:00:00 2001 From: eladgel Date: Mon, 20 Apr 2020 21:26:15 +0300 Subject: [PATCH 55/77] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2e1cc1d4..ac242f6a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-webview-bridge", - "version": "0.40.2", + "version": "0.62.0", "description": "React Native WebView Javascript Bridge", "main": "webview-bridge", "directories": { From 7096d87a6da4bf030d286e5528419bd17b4ed33a Mon Sep 17 00:00:00 2001 From: eladgel Date: Mon, 20 Apr 2020 21:52:24 +0300 Subject: [PATCH 56/77] Update react-native-webview-bridge.podspec --- react-native-webview-bridge.podspec | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/react-native-webview-bridge.podspec b/react-native-webview-bridge.podspec index ade1912a..7f1b77a4 100644 --- a/react-native-webview-bridge.podspec +++ b/react-native-webview-bridge.podspec @@ -1,17 +1,17 @@ require 'json' -version = JSON.parse(File.read('package.json'))["version"] +package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) Pod::Spec.new do |s| s.name = "react-native-webview-bridge" - s.version = version + s.version = package['version'] s.homepage = "https://github.com/alinz/react-native-webview-bridge" s.summary = "A webview bridge for react-native" s.license = "MIT" s.author = { "alinz" => "a.najafizadeh@gmail.com" } s.ios.deployment_target = '9.0' s.tvos.deployment_target = '9.0' - s.source = { :git => "https://github.com/alinz/react-native-webview-bridge", :tag => "#{s.version}" } + s.source = { :git => "https://github.com/HoneyBook/react-native-webview-bridge"} s.source_files = 'ios/*.{h,m}' s.preserve_paths = "**/*.js" s.frameworks = 'UIKit', 'QuartzCore', 'Foundation' From 411cb25d3af514a6cb2fb9161c437416c3480066 Mon Sep 17 00:00:00 2001 From: eladgel Date: Mon, 20 Apr 2020 21:53:58 +0300 Subject: [PATCH 57/77] Update RCTWebViewBridge.m --- ios/RCTWebViewBridge.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/RCTWebViewBridge.m b/ios/RCTWebViewBridge.m index 3c2f73d1..952dd5b3 100644 --- a/ios/RCTWebViewBridge.m +++ b/ios/RCTWebViewBridge.m @@ -30,7 +30,7 @@ #define NSStringMultiline(...) [[NSString alloc] initWithCString:#__VA_ARGS__ encoding:NSUTF8StringEncoding] //we don'e need this one since it has been defined in RCTWebView.m -//NSString *const RCTJSNavigationScheme = @"react-js-navigation"; +NSString *const RCTJSNavigationScheme = @"react-js-navigation"; NSString *const RCTWebViewBridgeSchema = @"wvb"; // runtime trick to remove UIWebview keyboard default toolbar From ca4f144bc60a9e52b22c511b0eccedf86fc8f470 Mon Sep 17 00:00:00 2001 From: eladgel Date: Sat, 9 May 2020 23:14:38 +0300 Subject: [PATCH 58/77] Update WebViewBridgeManager.java --- .../WebViewBridgeManager.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgeManager.java b/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgeManager.java index 9b577222..10838d5d 100644 --- a/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgeManager.java +++ b/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgeManager.java @@ -6,17 +6,16 @@ import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.uimanager.ThemedReactContext; -import com.facebook.react.views.webview.ReactWebViewManager; import com.facebook.react.uimanager.annotations.ReactProp; +import com.reactnativecommunity.webview.RNCWebViewManager; import java.io.IOException; import java.io.InputStream; -import java.util.ArrayList; import java.util.Map; import javax.annotation.Nullable; -public class WebViewBridgeManager extends ReactWebViewManager { +public class WebViewBridgeManager extends RNCWebViewManager { private static final String REACT_CLASS = "RCTWebViewBridge"; public static final int COMMAND_INJECT_WEBVIEW_BRIDGE = 101; @@ -78,8 +77,8 @@ public void receiveCommand(WebView root, int commandId, @Nullable ReadableArray private static String inputStreamToString(InputStream input) throws IOException { StringBuilder builder = new StringBuilder(); int ch; - while((ch = input.read()) != -1){ - builder.append((char)ch); + while ((ch = input.read()) != -1) { + builder.append((char) ch); } input.close(); return builder.toString(); @@ -124,10 +123,10 @@ static private void evaluateJavascript(WebView root, String javascript) { root.loadUrl("javascript:" + javascript); } } - + @ReactProp(name = "requestFocus") public void requestFocus(WebView root, boolean isRequestFocus) { - if(isRequestFocus) { + if (isRequestFocus) { root.requestFocus(); } } From fb9ec1101c92c3c47a0a16619b30eb1dd508402d Mon Sep 17 00:00:00 2001 From: eladgel Date: Sat, 9 May 2020 23:15:57 +0300 Subject: [PATCH 59/77] Update build.gradle --- android/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/android/build.gradle b/android/build.gradle index 863b463b..f96d042c 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -31,4 +31,5 @@ repositories { dependencies { api 'com.facebook.react:react-native:+' + implementation project(path: ':react-native-webview') } From 579fe122feb3de20345e305dc20e3c4a554a362c Mon Sep 17 00:00:00 2001 From: eladgel Date: Sun, 10 May 2020 17:50:36 +0300 Subject: [PATCH 60/77] Update package.json --- package.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index ac242f6a..e1660a33 100644 --- a/package.json +++ b/package.json @@ -33,9 +33,7 @@ "create-react-class": "^15.6.0", "invariant": "^2.2.2", "keymirror": "0.1.1", - "prop-types": "^15.5.10" - }, - "peerDependencies": { - "react-native-webview": "^9.0.2" + "prop-types": "^15.5.10", + "react-native-webview": "^9.0.2" } } From bb74cd7ab496f88b46e1e995139e49102c562ec4 Mon Sep 17 00:00:00 2001 From: Elad Gelman Date: Sun, 10 May 2020 18:21:21 +0300 Subject: [PATCH 61/77] ready --- yarn.lock | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/yarn.lock b/yarn.lock index 846d6432..a4f1d311 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24,6 +24,11 @@ encoding@^0.1.11: dependencies: iconv-lite "~0.4.13" +escape-string-regexp@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + fbjs@^0.8.16, fbjs@^0.8.9: version "0.8.16" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db" @@ -40,6 +45,13 @@ iconv-lite@~0.4.13: version "0.4.19" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" +invariant@2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + invariant@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" @@ -96,6 +108,14 @@ prop-types@^15.5.10: loose-envify "^1.3.1" object-assign "^4.1.1" +react-native-webview@^9.0.2: + version "9.4.0" + resolved "https://registry.yarnpkg.com/react-native-webview/-/react-native-webview-9.4.0.tgz#108da34a6c7e1c032dcabc942b7e4947ca1d8028" + integrity sha512-BBOFUuza0p04+7fNi7TJmB0arpDJzGxHYwTCgI4vj5n/fl7u4jbm7ETp88mf7lo9lP6C6HGLo38KnEy1aXCQkg== + dependencies: + escape-string-regexp "2.0.0" + invariant "2.2.4" + setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" From a907bd31d7bfacf877abda9b7e7db2614ac2a051 Mon Sep 17 00:00:00 2001 From: Elad Gelman Date: Sun, 10 May 2020 18:25:29 +0300 Subject: [PATCH 62/77] done --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index e1660a33..d521933b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-webview-bridge", - "version": "0.62.0", + "version": "0.62.1", "description": "React Native WebView Javascript Bridge", "main": "webview-bridge", "directories": { @@ -34,6 +34,6 @@ "invariant": "^2.2.2", "keymirror": "0.1.1", "prop-types": "^15.5.10", - "react-native-webview": "^9.0.2" + "react-native-webview": "^9.0.2" } } From ac17ff3055cc530d184a73f024d60cf0bbdbec9d Mon Sep 17 00:00:00 2001 From: eladgel Date: Mon, 11 May 2020 00:28:31 +0300 Subject: [PATCH 63/77] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d521933b..ffa83796 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,6 @@ "invariant": "^2.2.2", "keymirror": "0.1.1", "prop-types": "^15.5.10", - "react-native-webview": "^9.0.2" + "react-native-webview": "^9.4.0" } } From 3a484f0f4603dc832a3552ec084b4c32e9cd5301 Mon Sep 17 00:00:00 2001 From: Elad Gelman Date: Mon, 11 May 2020 00:30:31 +0300 Subject: [PATCH 64/77] update --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index a4f1d311..4f737597 100644 --- a/yarn.lock +++ b/yarn.lock @@ -108,7 +108,7 @@ prop-types@^15.5.10: loose-envify "^1.3.1" object-assign "^4.1.1" -react-native-webview@^9.0.2: +react-native-webview@^9.4.0: version "9.4.0" resolved "https://registry.yarnpkg.com/react-native-webview/-/react-native-webview-9.4.0.tgz#108da34a6c7e1c032dcabc942b7e4947ca1d8028" integrity sha512-BBOFUuza0p04+7fNi7TJmB0arpDJzGxHYwTCgI4vj5n/fl7u4jbm7ETp88mf7lo9lP6C6HGLo38KnEy1aXCQkg== From f2a11f28538ef2b202a8a3b6a18b8f49b1d084ad Mon Sep 17 00:00:00 2001 From: Elad Gelman Date: Tue, 12 May 2020 09:25:34 +0300 Subject: [PATCH 65/77] update version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ffa83796..d360f7f6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-webview-bridge", - "version": "0.62.1", + "version": "0.62.2", "description": "React Native WebView Javascript Bridge", "main": "webview-bridge", "directories": { From ec8d371ea497b6507c153690682424a558ee03e0 Mon Sep 17 00:00:00 2001 From: Elad Gelman Date: Tue, 19 May 2020 19:36:09 +0300 Subject: [PATCH 66/77] fixed android webview brdige --- .../JavascriptBridge.java | 10 +-- .../WebViewBridgeManager.java | 76 +------------------ .../WebViewBridgePackage.java | 2 +- webview-bridge/index.android.js | 70 ++++++++--------- 4 files changed, 44 insertions(+), 114 deletions(-) diff --git a/android/src/main/java/com/github/alinz/reactnativewebviewbridge/JavascriptBridge.java b/android/src/main/java/com/github/alinz/reactnativewebviewbridge/JavascriptBridge.java index 61ddf727..e21901d7 100644 --- a/android/src/main/java/com/github/alinz/reactnativewebviewbridge/JavascriptBridge.java +++ b/android/src/main/java/com/github/alinz/reactnativewebviewbridge/JavascriptBridge.java @@ -17,13 +17,13 @@ public JavascriptBridge(WebView webView) { } @JavascriptInterface - public void nativeAndroidSend(String message) { + public void send(String message) { WritableMap event = Arguments.createMap(); event.putString("message", message); ReactContext reactContext = (ReactContext) this.webView.getContext(); - reactContext - .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit("WebViewBridgeMessageEvent", event); - + reactContext.getJSModule(RCTEventEmitter.class).receiveEvent( + this.webView.getId(), + "topChange", + event); } } diff --git a/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgeManager.java b/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgeManager.java index 10838d5d..0777bb66 100644 --- a/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgeManager.java +++ b/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgeManager.java @@ -1,16 +1,13 @@ package com.github.alinz.reactnativewebviewbridge; -import android.content.Context; import android.webkit.WebView; import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.annotations.ReactProp; import com.reactnativecommunity.webview.RNCWebViewManager; -import java.io.IOException; -import java.io.InputStream; +import java.util.ArrayList; import java.util.Map; import javax.annotation.Nullable; @@ -18,17 +15,7 @@ public class WebViewBridgeManager extends RNCWebViewManager { private static final String REACT_CLASS = "RCTWebViewBridge"; - public static final int COMMAND_INJECT_WEBVIEW_BRIDGE = 101; - public static final int COMMAND_INJECT_RPC = 102; - public static final int COMMAND_SEND_TO_BRIDGE = 103; - - private ReactApplicationContext reactApplicationContext; - - public WebViewBridgeManager(ReactApplicationContext reactApplicationContext) { - super(); - //we need to know the context because we need to load files from asset - this.reactApplicationContext = reactApplicationContext; - } + public static final int COMMAND_SEND_TO_BRIDGE = 101; @Override public String getName() { @@ -42,8 +29,6 @@ Map getCommandsMap() { Map commandsMap = super.getCommandsMap(); commandsMap.put("sendToBridge", COMMAND_SEND_TO_BRIDGE); - commandsMap.put("injectWebViewBridge", COMMAND_INJECT_WEBVIEW_BRIDGE); - commandsMap.put("injectRPC", COMMAND_INJECT_RPC); return commandsMap; } @@ -63,56 +48,13 @@ public void receiveCommand(WebView root, int commandId, @Nullable ReadableArray case COMMAND_SEND_TO_BRIDGE: sendToBridge(root, args.getString(0)); break; - case COMMAND_INJECT_WEBVIEW_BRIDGE: - injectWebViewBridgeScript(root); - break; - case COMMAND_INJECT_RPC: - injectWebViewBridgeRPCScript(root); - break; default: //do nothing!!!! } } - private static String inputStreamToString(InputStream input) throws IOException { - StringBuilder builder = new StringBuilder(); - int ch; - while ((ch = input.read()) != -1) { - builder.append((char) ch); - } - input.close(); - return builder.toString(); - } - - private static String loadAsset(String filename, final Context context) { - String output = null; - - try { - InputStream inputStream = context.getAssets().open(filename); - output = inputStreamToString(inputStream); - } catch (IOException e) { - e.printStackTrace(); - } - - return output; - } - - private void injectWebViewBridgeScript(WebView root) { - String injectContent = loadAsset("WebViewBridge.js", this.reactApplicationContext); - if (injectContent != null) { - evaluateJavascript(root, injectContent); - } - } - - private void injectWebViewBridgeRPCScript(WebView root) { - String injectContent = loadAsset("WebViewBridgeRPC.js", this.reactApplicationContext); - if (injectContent != null) { - evaluateJavascript(root, injectContent); - } - } - private void sendToBridge(WebView root, String message) { - String script = "(function(){ if (WebViewBridge && WebViewBridge.__push__) { WebViewBridge.__push__(\"" + message + "\"); } }());"; + String script = "WebViewBridge.onMessage('" + message + "');"; WebViewBridgeManager.evaluateJavascript(root, script); } @@ -124,18 +66,6 @@ static private void evaluateJavascript(WebView root, String javascript) { } } - @ReactProp(name = "requestFocus") - public void requestFocus(WebView root, boolean isRequestFocus) { - if (isRequestFocus) { - root.requestFocus(); - } - } - - @ReactProp(name = "injectedJavaScript") - public void setInjectedJavaScript(WebView root, @Nullable String injectedJavaScript) { - evaluateJavascript(root, injectedJavaScript); - } - @ReactProp(name = "allowFileAccessFromFileURLs") public void setAllowFileAccessFromFileURLs(WebView root, boolean allows) { root.getSettings().setAllowFileAccessFromFileURLs(allows); diff --git a/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgePackage.java b/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgePackage.java index 26792640..5cb0a4ef 100644 --- a/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgePackage.java +++ b/android/src/main/java/com/github/alinz/reactnativewebviewbridge/WebViewBridgePackage.java @@ -19,7 +19,7 @@ public List createNativeModules(ReactApplicationContext reactAppli @Override public List createViewManagers(ReactApplicationContext reactApplicationContext) { return Arrays.asList( - new WebViewBridgeManager(reactApplicationContext) + new WebViewBridgeManager() ); } diff --git a/webview-bridge/index.android.js b/webview-bridge/index.android.js index 9e1bb884..0176d7c5 100644 --- a/webview-bridge/index.android.js +++ b/webview-bridge/index.android.js @@ -20,8 +20,8 @@ var createReactClass = require('create-react-class'); var invariant = require('invariant'); var keyMirror = require('keymirror'); var resolveAssetSource = require('react-native/Libraries/Image/resolveAssetSource'); -import { WebView } from "react-native-webview"; +var WebView = require('react-native-webview'); var { ReactNativeViewAttributes, UIManager, @@ -69,7 +69,7 @@ var WebViewBridge = createReactClass({ }; }, - + componentDidMount: function() { DeviceEventEmitter.addListener("webViewBridgeMessage", (body) => { const { onBridgeMessage } = this.props; @@ -87,21 +87,21 @@ var WebViewBridge = createReactClass({ render: function() { var otherView = null; - if (this.state.viewState === WebViewBridgeState.LOADING) { + if (this.state.viewState === WebViewBridgeState.LOADING) { otherView = this.props.renderLoading && this.props.renderLoading(); } else if (this.state.viewState === WebViewBridgeState.ERROR) { var errorEvent = this.state.lastErrorEvent; otherView = this.props.renderError && this.props.renderError( - errorEvent.domain, - errorEvent.code, - errorEvent.description); + errorEvent.domain, + errorEvent.code, + errorEvent.description); } else if (this.state.viewState !== WebViewBridgeState.IDLE) { console.error('RCTWebViewBridge invalid state encountered: ' + this.state.loading); } var webViewStyles = [styles.container, this.props.style]; if (this.state.viewState === WebViewBridgeState.LOADING || - this.state.viewState === WebViewBridgeState.ERROR) { + this.state.viewState === WebViewBridgeState.ERROR) { // if we're in either LOADING or ERROR states, don't show the webView webViewStyles.push(styles.hidden); } @@ -119,24 +119,24 @@ var WebViewBridge = createReactClass({ let {source, ...props} = {...this.props}; var webView = - ; + ; return ( - - {webView} - {otherView} - + + {webView} + {otherView} + ); }, @@ -148,33 +148,33 @@ var WebViewBridge = createReactClass({ goForward: function() { UIManager.dispatchViewManagerCommand( - this.getWebViewBridgeHandle(), - UIManager.RCTWebViewBridge.Commands.goForward, - null + this.getWebViewBridgeHandle(), + UIManager.RCTWebViewBridge.Commands.goForward, + null ); }, goBack: function() { UIManager.dispatchViewManagerCommand( - this.getWebViewBridgeHandle(), - UIManager.RCTWebViewBridge.Commands.goBack, - null + this.getWebViewBridgeHandle(), + UIManager.RCTWebViewBridge.Commands.goBack, + null ); }, reload: function() { UIManager.dispatchViewManagerCommand( - this.getWebViewBridgeHandle(), - UIManager.RCTWebViewBridge.Commands.reload, - null + this.getWebViewBridgeHandle(), + UIManager.RCTWebViewBridge.Commands.reload, + null ); }, sendToBridge: function (message: string) { UIManager.dispatchViewManagerCommand( - this.getWebViewBridgeHandle(), - UIManager.RCTWebViewBridge.Commands.sendToBridge, - [message] + this.getWebViewBridgeHandle(), + UIManager.RCTWebViewBridge.Commands.sendToBridge, + [message] ); }, From ddb3410b8503e4d84711aeff8ec6e86c14fc2178 Mon Sep 17 00:00:00 2001 From: Elad Gelman Date: Tue, 19 May 2020 19:46:16 +0300 Subject: [PATCH 67/77] new version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d360f7f6..98757d06 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-webview-bridge", - "version": "0.62.2", + "version": "0.62.3", "description": "React Native WebView Javascript Bridge", "main": "webview-bridge", "directories": { From b4019535832b84cddffb65fb8454f345cfec0ebe Mon Sep 17 00:00:00 2001 From: Elad Gelman Date: Wed, 20 May 2020 23:28:38 +0300 Subject: [PATCH 68/77] updated --- ios/RCTWebViewBridge.m | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ios/RCTWebViewBridge.m b/ios/RCTWebViewBridge.m index 952dd5b3..5d859ae6 100644 --- a/ios/RCTWebViewBridge.m +++ b/ios/RCTWebViewBridge.m @@ -211,7 +211,7 @@ -(void)setHideKeyboardAccessoryView:(BOOL)hideKeyboardAccessoryView UIView* subview; for (UIView* view in _webView.scrollView.subviews) { - if([[view.class description] hasPrefix:@"UIWeb"]) + if([[view.class description] hasPrefix:@"WK"]) subview = view; } diff --git a/package.json b/package.json index 98757d06..bffdcc80 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-webview-bridge", - "version": "0.62.3", + "version": "0.62.4", "description": "React Native WebView Javascript Bridge", "main": "webview-bridge", "directories": { From 8138cce19acfc2701e0dffdcc2932cc189317659 Mon Sep 17 00:00:00 2001 From: Elad Gelman Date: Wed, 22 Jul 2020 09:07:17 -0700 Subject: [PATCH 69/77] updating react native webview --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index bffdcc80..be71daff 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,6 @@ "invariant": "^2.2.2", "keymirror": "0.1.1", "prop-types": "^15.5.10", - "react-native-webview": "^9.4.0" + "react-native-webview": "^10.3.2" } } diff --git a/yarn.lock b/yarn.lock index 4f737597..2c5176a3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -108,10 +108,10 @@ prop-types@^15.5.10: loose-envify "^1.3.1" object-assign "^4.1.1" -react-native-webview@^9.4.0: - version "9.4.0" - resolved "https://registry.yarnpkg.com/react-native-webview/-/react-native-webview-9.4.0.tgz#108da34a6c7e1c032dcabc942b7e4947ca1d8028" - integrity sha512-BBOFUuza0p04+7fNi7TJmB0arpDJzGxHYwTCgI4vj5n/fl7u4jbm7ETp88mf7lo9lP6C6HGLo38KnEy1aXCQkg== +react-native-webview@^10.3.2: + version "10.3.2" + resolved "https://registry.yarnpkg.com/react-native-webview/-/react-native-webview-10.3.2.tgz#c634946152099c95d521a3abc71065d1d642e192" + integrity sha512-4A8FKL/puonkqQ1FOKd+iPulqRXCG4inmIK4pQ60zv9Ua+YkBKLxxofQiCvRwIXSSgAXYT+AE3rOHr3bx4A/cw== dependencies: escape-string-regexp "2.0.0" invariant "2.2.4" From d19196729f3a64953ed362fbf6084d49bc9c5be7 Mon Sep 17 00:00:00 2001 From: eladgel Date: Wed, 22 Jul 2020 10:21:43 -0700 Subject: [PATCH 70/77] Update index.ios.js --- webview-bridge/index.ios.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webview-bridge/index.ios.js b/webview-bridge/index.ios.js index ffd59aef..340a78b6 100644 --- a/webview-bridge/index.ios.js +++ b/webview-bridge/index.ios.js @@ -22,7 +22,7 @@ var invariant = require('invariant'); var keyMirror = require('keymirror'); var resolveAssetSource = require('react-native/Libraries/Image/resolveAssetSource'); -import { WebView } from "react-native-webview"; +//import { WebView } from "react-native-webview"; var { ActivityIndicator, @@ -100,7 +100,7 @@ var WebViewBridge = createReactClass({ }, propTypes: { - ...WebView.propTypes, +// ...WebView.propTypes, /** * Will be called once the message is being sent from webview From 46c51ca2b0ddfc4d65f081c39fa4c4d0efad4184 Mon Sep 17 00:00:00 2001 From: eladgel Date: Wed, 22 Jul 2020 10:27:03 -0700 Subject: [PATCH 71/77] Update index.android.js --- webview-bridge/index.android.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webview-bridge/index.android.js b/webview-bridge/index.android.js index 0176d7c5..ca7fff1d 100644 --- a/webview-bridge/index.android.js +++ b/webview-bridge/index.android.js @@ -21,7 +21,7 @@ var invariant = require('invariant'); var keyMirror = require('keymirror'); var resolveAssetSource = require('react-native/Libraries/Image/resolveAssetSource'); -var WebView = require('react-native-webview'); +//var WebView = require('react-native-webview'); var { ReactNativeViewAttributes, UIManager, From 205d7c76a78099714aa1220c7cf16f0eae6514be Mon Sep 17 00:00:00 2001 From: yossigreenhb <92262487+yossigreenhb@users.noreply.github.com> Date: Thu, 10 Nov 2022 11:39:57 +0200 Subject: [PATCH 72/77] Update index.android.js --- webview-bridge/index.android.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/webview-bridge/index.android.js b/webview-bridge/index.android.js index ca7fff1d..641f4f01 100644 --- a/webview-bridge/index.android.js +++ b/webview-bridge/index.android.js @@ -25,11 +25,9 @@ var resolveAssetSource = require('react-native/Libraries/Image/resolveAssetSourc var { ReactNativeViewAttributes, UIManager, - EdgeInsetsPropType, StyleSheet, Text, View, - ViewPropTypes, requireNativeComponent, DeviceEventEmitter, NativeModules: { From ebe0684c9c8c9e849d77c4f09289db3f327c2799 Mon Sep 17 00:00:00 2001 From: yossigreenhb <92262487+yossigreenhb@users.noreply.github.com> Date: Thu, 10 Nov 2022 11:40:20 +0200 Subject: [PATCH 73/77] Update index.ios.js --- webview-bridge/index.ios.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/webview-bridge/index.ios.js b/webview-bridge/index.ios.js index 340a78b6..7a99214b 100644 --- a/webview-bridge/index.ios.js +++ b/webview-bridge/index.ios.js @@ -26,11 +26,9 @@ var resolveAssetSource = require('react-native/Libraries/Image/resolveAssetSourc var { ActivityIndicator, - EdgeInsetsPropType, StyleSheet, Text, View, - ViewPropTypes, requireNativeComponent, UIManager, NativeModules: { From b006c8e4de45921bbc8a8d70dc3ff82289b5f8e4 Mon Sep 17 00:00:00 2001 From: yossigreenhb <92262487+yossigreenhb@users.noreply.github.com> Date: Thu, 10 Nov 2022 11:57:27 +0200 Subject: [PATCH 74/77] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index be71daff..c36ff871 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-webview-bridge", - "version": "0.62.4", + "version": "0.62.5", "description": "React Native WebView Javascript Bridge", "main": "webview-bridge", "directories": { From 92d0bfb548eb72cada7e9b2126745bb34e485fe4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Nov 2022 09:58:17 +0000 Subject: [PATCH 75/77] Bump react-native-webview from 10.3.2 to 11.0.0 Bumps [react-native-webview](https://github.com/react-native-community/react-native-webview) from 10.3.2 to 11.0.0. - [Release notes](https://github.com/react-native-community/react-native-webview/releases) - [Changelog](https://github.com/react-native-webview/react-native-webview/blob/master/.releaserc) - [Commits](https://github.com/react-native-community/react-native-webview/compare/v10.3.2...v11.0.0) Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 16 +++++----------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index c36ff871..611a0cf2 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,6 @@ "invariant": "^2.2.2", "keymirror": "0.1.1", "prop-types": "^15.5.10", - "react-native-webview": "^10.3.2" + "react-native-webview": "^11.0.0" } } diff --git a/yarn.lock b/yarn.lock index 2c5176a3..1dfb6977 100644 --- a/yarn.lock +++ b/yarn.lock @@ -45,19 +45,13 @@ iconv-lite@~0.4.13: version "0.4.19" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" -invariant@2.2.4: +invariant@2.2.4, invariant@^2.2.2: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== dependencies: loose-envify "^1.0.0" -invariant@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" - dependencies: - loose-envify "^1.0.0" - is-stream@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" @@ -108,10 +102,10 @@ prop-types@^15.5.10: loose-envify "^1.3.1" object-assign "^4.1.1" -react-native-webview@^10.3.2: - version "10.3.2" - resolved "https://registry.yarnpkg.com/react-native-webview/-/react-native-webview-10.3.2.tgz#c634946152099c95d521a3abc71065d1d642e192" - integrity sha512-4A8FKL/puonkqQ1FOKd+iPulqRXCG4inmIK4pQ60zv9Ua+YkBKLxxofQiCvRwIXSSgAXYT+AE3rOHr3bx4A/cw== +react-native-webview@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/react-native-webview/-/react-native-webview-11.0.0.tgz#d667cbcc772e963fe88a4156290d25fe47ed43b0" + integrity sha512-gzID5ebCFVNkxreuabClymo6QkJbXCLEtiIUY9QAhxY58HsDUp8x0YzDVEROYyEJ4ghL71koAOeQRDNAgNnh1w== dependencies: escape-string-regexp "2.0.0" invariant "2.2.4" From 03a4249ef68b576edf9b15be75d526041cbaf84c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Jan 2023 16:43:41 +0000 Subject: [PATCH 76/77] Bump ua-parser-js from 0.7.17 to 0.7.33 Bumps [ua-parser-js](https://github.com/faisalman/ua-parser-js) from 0.7.17 to 0.7.33. - [Release notes](https://github.com/faisalman/ua-parser-js/releases) - [Changelog](https://github.com/faisalman/ua-parser-js/blob/master/changelog.md) - [Commits](https://github.com/faisalman/ua-parser-js/compare/0.7.17...0.7.33) --- updated-dependencies: - dependency-name: ua-parser-js dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index 2c5176a3..559f41df 100644 --- a/yarn.lock +++ b/yarn.lock @@ -121,8 +121,9 @@ setimmediate@^1.0.5: resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" ua-parser-js@^0.7.9: - version "0.7.17" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac" + version "0.7.33" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.33.tgz#1d04acb4ccef9293df6f70f2c3d22f3030d8b532" + integrity sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw== whatwg-fetch@>=0.10.0: version "2.0.3" From 1b2af02f13e65a70075ba744faeac434ab84e12c Mon Sep 17 00:00:00 2001 From: yossigreenhb <92262487+yossigreenhb@users.noreply.github.com> Date: Sun, 26 May 2024 14:59:11 +0300 Subject: [PATCH 77/77] Update build.gradle --- android/build.gradle | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index f96d042c..8b6e71a1 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -11,12 +11,12 @@ buildscript { apply plugin: 'com.android.library' android { - compileSdkVersion 28 - buildToolsVersion "28.0.3" + compileSdkVersion 34 + buildToolsVersion "34.0.0" defaultConfig { - minSdkVersion 21 - targetSdkVersion 28 + minSdkVersion 23 + targetSdkVersion 34 versionCode 1 versionName "1.0" }