,即为不使用自定义ActionMode)
763 | *
764 | * 对页面小白条的初始化工作,存储顺序即为展现顺序,id必须唯一且最好用数字字符串,不要从10以内的数字开始
765 | *
766 | * @param idTitles key:每个item所对应的id,value:每个item的title
767 | */
768 | @ReactMethod
769 | public void setActionModeMenu(ReadableMap idTitles) {
770 | if (mMenuIdTitles != null) {
771 | mMenuIdTitles.clear();
772 | }
773 | if (idTitles == null) {
774 | return;
775 | }
776 | ReadableMapKeySetIterator it = idTitles.keySetIterator();
777 | while (it.hasNextKey()) {
778 | String key = it.nextKey();
779 | String title = idTitles.getString(key);
780 | int id = Integer.valueOf(key);
781 | mMenuIdTitles.put(id, title);
782 | }
783 | }
784 |
785 | /**
786 | * 焦点变化时调用,或者界面的规则一致时只调用一次即可(既没有设置自定义item也没有是设置白名单,即为不使用自定义ActionMode)
787 | *
788 | * 对页面小白条的初始化工作,加入进来的title,如果系统本身自带的title与之相同将不会被删除
789 | * 例如"复制",传入的列表内包含"复制",那么系统的将被保留,如果不包含,系统所带的"复制"将被删除
790 | *
791 | * @param titles 白名单列表
792 | */
793 | @ReactMethod
794 | public void setActionModeMenuWhitelist(ReadableArray titles) {
795 | if (mWhiteList != null) {
796 | mWhiteList.clear();
797 | }
798 | if (titles == null) {
799 | return;
800 | }
801 | for (Object title :
802 | titles.toArrayList()) {
803 | mWhiteList.add((String) title);
804 | }
805 | }
806 | }
807 |
--------------------------------------------------------------------------------
/android/src/main/java/im/shimo/react/webview/AdvancedWebViewPackage.java:
--------------------------------------------------------------------------------
1 | package im.shimo.react.webview;
2 |
3 | import com.facebook.react.ReactPackage;
4 | import com.facebook.react.bridge.JavaScriptModule;
5 | import com.facebook.react.bridge.NativeModule;
6 | import com.facebook.react.bridge.ReactApplicationContext;
7 | import com.facebook.react.uimanager.ViewManager;
8 |
9 | import java.util.Arrays;
10 | import java.util.Collections;
11 | import java.util.List;
12 |
13 | import ren.yale.android.cachewebviewlib.WebViewCacheInterceptor;
14 | import ren.yale.android.cachewebviewlib.WebViewCacheInterceptorInst;
15 | import ren.yale.android.cachewebviewlib.config.CacheExtensionConfig;
16 |
17 | public class AdvancedWebViewPackage implements ReactPackage {
18 | @Override
19 | public List createNativeModules(ReactApplicationContext reactApplicationContext) {
20 | return Collections.emptyList();
21 | }
22 |
23 | @Override
24 | public List createViewManagers(ReactApplicationContext reactApplicationContext) {
25 | //Initialize WebViewCacheInterceptor with config
26 | WebViewCacheInterceptorInst.getInstance().init(new WebViewCacheInterceptor.Builder(reactApplicationContext));
27 |
28 | return Arrays.asList(
29 | new AdvancedWebViewManager()
30 | );
31 | }
32 |
33 | public List> createJSModules() {
34 | return Collections.emptyList();
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/example/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["react-native"]
3 | }
4 |
--------------------------------------------------------------------------------
/example/.buckconfig:
--------------------------------------------------------------------------------
1 |
2 | [android]
3 | target = Google Inc.:Google APIs:23
4 |
5 | [maven_repositories]
6 | central = https://repo1.maven.org/maven2
7 |
--------------------------------------------------------------------------------
/example/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 | ; We fork some components by platform
3 | .*/*[.]android.js
4 |
5 | ; Ignore "BUCK" generated dirs
6 | /\.buckd/
7 |
8 | ; Ignore unexpected extra "@providesModule"
9 | .*/node_modules/.*/node_modules/fbjs/.*
10 |
11 | ; Ignore duplicate module providers
12 | ; For RN Apps installed via npm, "Libraries" folder is inside
13 | ; "node_modules/react-native" but in the source repo it is in the root
14 | .*/Libraries/react-native/React.js
15 |
16 | ; Ignore polyfills
17 | .*/Libraries/polyfills/.*
18 |
19 | ; Ignore metro
20 | .*/node_modules/metro/.*
21 |
22 | [include]
23 |
24 | [libs]
25 | node_modules/react-native/Libraries/react-native/react-native-interface.js
26 | node_modules/react-native/flow/
27 | node_modules/react-native/flow-github/
28 |
29 | [options]
30 | emoji=true
31 |
32 | module.system=haste
33 | module.system.haste.use_name_reducers=true
34 | # get basename
35 | module.system.haste.name_reducers='^.*/\([a-zA-Z0-9$_.-]+\.js\(\.flow\)?\)$' -> '\1'
36 | # strip .js or .js.flow suffix
37 | module.system.haste.name_reducers='^\(.*\)\.js\(\.flow\)?$' -> '\1'
38 | # strip .ios suffix
39 | module.system.haste.name_reducers='^\(.*\)\.ios$' -> '\1'
40 | module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1'
41 | module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1'
42 | module.system.haste.paths.blacklist=.*/__tests__/.*
43 | module.system.haste.paths.blacklist=.*/__mocks__/.*
44 | module.system.haste.paths.blacklist=/node_modules/react-native/Libraries/Animated/src/polyfills/.*
45 | module.system.haste.paths.whitelist=/node_modules/react-native/Libraries/.*
46 |
47 | munge_underscores=true
48 |
49 | 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'
50 |
51 | module.file_ext=.js
52 | module.file_ext=.jsx
53 | module.file_ext=.json
54 | module.file_ext=.native.js
55 |
56 | suppress_type=$FlowIssue
57 | suppress_type=$FlowFixMe
58 | suppress_type=$FlowFixMeProps
59 | suppress_type=$FlowFixMeState
60 |
61 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
62 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
63 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
64 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
65 |
66 | [version]
67 | ^0.75.0
68 |
--------------------------------------------------------------------------------
/example/.gitattributes:
--------------------------------------------------------------------------------
1 | *.pbxproj -text
2 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # Xcode
6 | #
7 | build/
8 | *.pbxuser
9 | !default.pbxuser
10 | *.mode1v3
11 | !default.mode1v3
12 | *.mode2v3
13 | !default.mode2v3
14 | *.perspectivev3
15 | !default.perspectivev3
16 | xcuserdata
17 | *.xccheckout
18 | *.moved-aside
19 | DerivedData
20 | *.hmap
21 | *.ipa
22 | *.xcuserstate
23 | project.xcworkspace
24 |
25 | # Android/IntelliJ
26 | #
27 | build/
28 | .idea
29 | .gradle
30 | local.properties
31 | *.iml
32 |
33 | # node.js
34 | #
35 | node_modules/
36 | npm-debug.log
37 | yarn-error.log
38 |
39 | # BUCK
40 | buck-out/
41 | \.buckd/
42 | *.keystore
43 |
44 | # fastlane
45 | #
46 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
47 | # screenshots whenever they are needed.
48 | # For more information about the recommended setup visit:
49 | # https://docs.fastlane.tools/best-practices/source-control/
50 |
51 | */fastlane/report.xml
52 | */fastlane/Preview.html
53 | */fastlane/screenshots
54 |
55 | # Bundle artifact
56 | *.jsbundle
57 |
--------------------------------------------------------------------------------
/example/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/example/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { AppRegistry, StyleSheet, WebView } from 'react-native';
3 |
4 | import OfflineCacheWebView from 'react-native-offline-cache-webview';
5 |
6 |
7 | export default class webview extends Component {
8 | render() {
9 |
10 | return (
11 |
16 | );
17 | }
18 | }
19 |
20 | const styles = StyleSheet.create({
21 | webview: {
22 | flex: 1
23 | }
24 | });
25 |
26 | AppRegistry.registerComponent('webview', () => webview);
27 |
--------------------------------------------------------------------------------
/example/android/app/BUCK:
--------------------------------------------------------------------------------
1 | # To learn about Buck see [Docs](https://buckbuild.com/).
2 | # To run your application with Buck:
3 | # - install Buck
4 | # - `npm start` - to start the packager
5 | # - `cd android`
6 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"`
7 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck
8 | # - `buck install -r android/app` - compile, install and run application
9 | #
10 |
11 | lib_deps = []
12 |
13 | for jarfile in glob(['libs/*.jar']):
14 | name = 'jars__' + jarfile[jarfile.rindex('/') + 1: jarfile.rindex('.jar')]
15 | lib_deps.append(':' + name)
16 | prebuilt_jar(
17 | name = name,
18 | binary_jar = jarfile,
19 | )
20 |
21 | for aarfile in glob(['libs/*.aar']):
22 | name = 'aars__' + aarfile[aarfile.rindex('/') + 1: aarfile.rindex('.aar')]
23 | lib_deps.append(':' + name)
24 | android_prebuilt_aar(
25 | name = name,
26 | aar = aarfile,
27 | )
28 |
29 | android_library(
30 | name = "all-libs",
31 | exported_deps = lib_deps,
32 | )
33 |
34 | android_library(
35 | name = "app-code",
36 | srcs = glob([
37 | "src/main/java/**/*.java",
38 | ]),
39 | deps = [
40 | ":all-libs",
41 | ":build_config",
42 | ":res",
43 | ],
44 | )
45 |
46 | android_build_config(
47 | name = "build_config",
48 | package = "com.example",
49 | )
50 |
51 | android_resource(
52 | name = "res",
53 | package = "com.example",
54 | res = "src/main/res",
55 | )
56 |
57 | android_binary(
58 | name = "app",
59 | keystore = "//android/keystores:debug",
60 | manifest = "src/main/AndroidManifest.xml",
61 | package_type = "debug",
62 | deps = [
63 | ":app-code",
64 | ],
65 | )
66 |
--------------------------------------------------------------------------------
/example/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: "com.android.application"
2 |
3 | import com.android.build.OutputFile
4 |
5 | /**
6 | * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
7 | * and bundleReleaseJsAndAssets).
8 | * These basically call `react-native bundle` with the correct arguments during the Android build
9 | * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
10 | * bundle directly from the development server. Below you can see all the possible configurations
11 | * and their defaults. If you decide to add a configuration block, make sure to add it before the
12 | * `apply from: "../../node_modules/react-native/react.gradle"` line.
13 | *
14 | * project.ext.react = [
15 | * // the name of the generated asset file containing your JS bundle
16 | * bundleAssetName: "index.android.bundle",
17 | *
18 | * // the entry file for bundle generation
19 | * entryFile: "index.android.js",
20 | *
21 | * // whether to bundle JS and assets in debug mode
22 | * bundleInDebug: false,
23 | *
24 | * // whether to bundle JS and assets in release mode
25 | * bundleInRelease: true,
26 | *
27 | * // whether to bundle JS and assets in another build variant (if configured).
28 | * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants
29 | * // The configuration property can be in the following formats
30 | * // 'bundleIn${productFlavor}${buildType}'
31 | * // 'bundleIn${buildType}'
32 | * // bundleInFreeDebug: true,
33 | * // bundleInPaidRelease: true,
34 | * // bundleInBeta: true,
35 | *
36 | * // whether to disable dev mode in custom build variants (by default only disabled in release)
37 | * // for example: to disable dev mode in the staging build type (if configured)
38 | * devDisabledInStaging: true,
39 | * // The configuration property can be in the following formats
40 | * // 'devDisabledIn${productFlavor}${buildType}'
41 | * // 'devDisabledIn${buildType}'
42 | *
43 | * // the root of your project, i.e. where "package.json" lives
44 | * root: "../../",
45 | *
46 | * // where to put the JS bundle asset in debug mode
47 | * jsBundleDirDebug: "$buildDir/intermediates/assets/debug",
48 | *
49 | * // where to put the JS bundle asset in release mode
50 | * jsBundleDirRelease: "$buildDir/intermediates/assets/release",
51 | *
52 | * // where to put drawable resources / React Native assets, e.g. the ones you use via
53 | * // require('./image.png')), in debug mode
54 | * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug",
55 | *
56 | * // where to put drawable resources / React Native assets, e.g. the ones you use via
57 | * // require('./image.png')), in release mode
58 | * resourcesDirRelease: "$buildDir/intermediates/res/merged/release",
59 | *
60 | * // by default the gradle tasks are skipped if none of the JS files or assets change; this means
61 | * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to
62 | * // date; if you have any other folders that you want to ignore for performance reasons (gradle
63 | * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/
64 | * // for example, you might want to remove it from here.
65 | * inputExcludes: ["android/**", "ios/**"],
66 | *
67 | * // override which node gets called and with what additional arguments
68 | * nodeExecutableAndArgs: ["node"],
69 | *
70 | * // supply additional arguments to the packager
71 | * extraPackagerArgs: []
72 | * ]
73 | */
74 |
75 | project.ext.react = [
76 | entryFile: "index.js"
77 | ]
78 |
79 | apply from: "../../node_modules/react-native/react.gradle"
80 |
81 | /**
82 | * Set this to true to create two separate APKs instead of one:
83 | * - An APK that only works on ARM devices
84 | * - An APK that only works on x86 devices
85 | * The advantage is the size of the APK is reduced by about 4MB.
86 | * Upload all the APKs to the Play Store and people will download
87 | * the correct one based on the CPU architecture of their device.
88 | */
89 | def enableSeparateBuildPerCPUArchitecture = false
90 |
91 | /**
92 | * Run Proguard to shrink the Java bytecode in release builds.
93 | */
94 | def enableProguardInReleaseBuilds = false
95 |
96 | android {
97 | compileSdkVersion rootProject.ext.compileSdkVersion
98 | buildToolsVersion rootProject.ext.buildToolsVersion
99 |
100 | defaultConfig {
101 | applicationId "com.example"
102 | minSdkVersion rootProject.ext.minSdkVersion
103 | targetSdkVersion rootProject.ext.targetSdkVersion
104 | versionCode 1
105 | versionName "1.0"
106 | ndk {
107 | abiFilters "armeabi-v7a", "x86"
108 | }
109 | }
110 | splits {
111 | abi {
112 | reset()
113 | enable enableSeparateBuildPerCPUArchitecture
114 | universalApk false // If true, also generate a universal APK
115 | include "armeabi-v7a", "x86"
116 | }
117 | }
118 | buildTypes {
119 | release {
120 | minifyEnabled enableProguardInReleaseBuilds
121 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
122 | }
123 | }
124 | // applicationVariants are e.g. debug, release
125 | applicationVariants.all { variant ->
126 | variant.outputs.each { output ->
127 | // For each separate APK per architecture, set a unique version code as described here:
128 | // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits
129 | def versionCodes = ["armeabi-v7a":1, "x86":2]
130 | def abi = output.getFilter(OutputFile.ABI)
131 | if (abi != null) { // null for the universal-debug, universal-release variants
132 | output.versionCodeOverride =
133 | versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
134 | }
135 | }
136 | }
137 | }
138 |
139 | dependencies {
140 | compile project(':react-native-offline-cache-webview')
141 | compile fileTree(dir: "libs", include: ["*.jar"])
142 | compile "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
143 | compile "com.facebook.react:react-native:+" // From node_modules
144 | }
145 |
146 | // Run this once to be able to run the application with BUCK
147 | // puts all compile dependencies into folder libs for BUCK to use
148 | task copyDownloadableDepsToLibs(type: Copy) {
149 | from configurations.compile
150 | into 'libs'
151 | }
152 |
--------------------------------------------------------------------------------
/example/android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/example/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
13 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/android/app/src/main/java/com/example/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import com.facebook.react.ReactActivity;
4 |
5 | public class MainActivity extends ReactActivity {
6 |
7 | /**
8 | * Returns the name of the main component registered from JavaScript.
9 | * This is used to schedule rendering of the component.
10 | */
11 | @Override
12 | protected String getMainComponentName() {
13 | return "example";
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/example/android/app/src/main/java/com/example/MainApplication.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import android.app.Application;
4 |
5 | import com.facebook.react.ReactApplication;
6 | import im.shimo.react.webview.AdvancedWebViewPackage;
7 | import im.shimo.react.webview.AdvancedWebViewPackage;
8 | import com.facebook.react.ReactNativeHost;
9 | import com.facebook.react.ReactPackage;
10 | import com.facebook.react.shell.MainReactPackage;
11 | import com.facebook.soloader.SoLoader;
12 |
13 | import java.util.Arrays;
14 | import java.util.List;
15 |
16 | public class MainApplication extends Application implements ReactApplication {
17 |
18 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
19 | @Override
20 | public boolean getUseDeveloperSupport() {
21 | return BuildConfig.DEBUG;
22 | }
23 |
24 | @Override
25 | protected List getPackages() {
26 | return Arrays.asList(
27 | new MainReactPackage(),
28 | new AdvancedWebViewPackage(),
29 | new AdvancedWebViewPackage()
30 | );
31 | }
32 |
33 | @Override
34 | protected String getJSMainModuleName() {
35 | return "index";
36 | }
37 | };
38 |
39 | @Override
40 | public ReactNativeHost getReactNativeHost() {
41 | return mReactNativeHost;
42 | }
43 |
44 | @Override
45 | public void onCreate() {
46 | super.onCreate();
47 | SoLoader.init(this, /* native exopackage */ false);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangtaii/react-native-offline-cache-webview/f754fb6304eef7c4004f40ec47093b894785f33e/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangtaii/react-native-offline-cache-webview/f754fb6304eef7c4004f40ec47093b894785f33e/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangtaii/react-native-offline-cache-webview/f754fb6304eef7c4004f40ec47093b894785f33e/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangtaii/react-native-offline-cache-webview/f754fb6304eef7c4004f40ec47093b894785f33e/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangtaii/react-native-offline-cache-webview/f754fb6304eef7c4004f40ec47093b894785f33e/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangtaii/react-native-offline-cache-webview/f754fb6304eef7c4004f40ec47093b894785f33e/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangtaii/react-native-offline-cache-webview/f754fb6304eef7c4004f40ec47093b894785f33e/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangtaii/react-native-offline-cache-webview/f754fb6304eef7c4004f40ec47093b894785f33e/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangtaii/react-native-offline-cache-webview/f754fb6304eef7c4004f40ec47093b894785f33e/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangtaii/react-native-offline-cache-webview/f754fb6304eef7c4004f40ec47093b894785f33e/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | example
3 |
4 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/android/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | maven {
7 | url 'https://maven.google.com/'
8 | name 'Google'
9 | }
10 | }
11 | dependencies {
12 | classpath 'com.android.tools.build:gradle:2.3.3'
13 |
14 | // NOTE: Do not place your application dependencies here; they belong
15 | // in the individual module build.gradle files
16 | }
17 | }
18 |
19 | allprojects {
20 | repositories {
21 | mavenLocal()
22 | jcenter()
23 | maven {
24 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
25 | url "$rootDir/../node_modules/react-native/android"
26 | }
27 | maven {
28 | url 'https://maven.google.com/'
29 | name 'Google'
30 | }
31 | }
32 | }
33 |
34 | ext {
35 | buildToolsVersion = "26.0.3"
36 | minSdkVersion = 16
37 | compileSdkVersion = 26
38 | targetSdkVersion = 26
39 | supportLibVersion = "26.1.0"
40 | }
41 |
--------------------------------------------------------------------------------
/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 |
20 | android.useDeprecatedNdk=true
21 |
--------------------------------------------------------------------------------
/example/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/example/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/example/android/keystores/BUCK:
--------------------------------------------------------------------------------
1 | keystore(
2 | name = "debug",
3 | properties = "debug.keystore.properties",
4 | store = "debug.keystore",
5 | visibility = [
6 | "PUBLIC",
7 | ],
8 | )
9 |
--------------------------------------------------------------------------------
/example/android/keystores/debug.keystore.properties:
--------------------------------------------------------------------------------
1 | key.store=debug.keystore
2 | key.alias=androiddebugkey
3 | key.store.password=android
4 | key.alias.password=android
5 |
--------------------------------------------------------------------------------
/example/android/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'example'
2 | include ':react-native-offline-cache-webview'
3 | project(':react-native-offline-cache-webview').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-offline-cache-webview/android')
4 | include ':app'
5 |
--------------------------------------------------------------------------------
/example/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "displayName": "example"
4 | }
--------------------------------------------------------------------------------
/example/index.js:
--------------------------------------------------------------------------------
1 | /** @format */
2 |
3 | import {AppRegistry} from 'react-native';
4 | import App from './App';
5 | import {name as appName} from './app.json';
6 |
7 | AppRegistry.registerComponent(appName, () => App);
8 |
--------------------------------------------------------------------------------
/example/ios/example-tvOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UIViewControllerBasedStatusBarAppearance
38 |
39 | NSLocationWhenInUseUsageDescription
40 |
41 | NSAppTransportSecurity
42 |
43 |
44 | NSExceptionDomains
45 |
46 | localhost
47 |
48 | NSExceptionAllowsInsecureHTTPLoads
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/example/ios/example-tvOSTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/example/ios/example.xcodeproj/xcshareddata/xcschemes/example-tvOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
43 |
49 |
50 |
51 |
52 |
53 |
58 |
59 |
61 |
67 |
68 |
69 |
70 |
71 |
77 |
78 |
79 |
80 |
81 |
82 |
92 |
94 |
100 |
101 |
102 |
103 |
104 |
105 |
111 |
113 |
119 |
120 |
121 |
122 |
124 |
125 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/example/ios/example.xcodeproj/xcshareddata/xcschemes/example.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
43 |
49 |
50 |
51 |
52 |
53 |
58 |
59 |
61 |
67 |
68 |
69 |
70 |
71 |
77 |
78 |
79 |
80 |
81 |
82 |
92 |
94 |
100 |
101 |
102 |
103 |
104 |
105 |
111 |
113 |
119 |
120 |
121 |
122 |
124 |
125 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/example/ios/example/AppDelegate.h:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2015-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | #import
9 |
10 | @interface AppDelegate : UIResponder
11 |
12 | @property (nonatomic, strong) UIWindow *window;
13 |
14 | @end
15 |
--------------------------------------------------------------------------------
/example/ios/example/AppDelegate.m:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2015-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | #import "AppDelegate.h"
9 |
10 | #import
11 | #import
12 |
13 | @implementation AppDelegate
14 |
15 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
16 | {
17 | NSURL *jsCodeLocation;
18 |
19 | jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
20 |
21 | RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
22 | moduleName:@"example"
23 | initialProperties:nil
24 | launchOptions:launchOptions];
25 | rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
26 |
27 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
28 | UIViewController *rootViewController = [UIViewController new];
29 | rootViewController.view = rootView;
30 | self.window.rootViewController = rootViewController;
31 | [self.window makeKeyAndVisible];
32 | return YES;
33 | }
34 |
35 | @end
36 |
--------------------------------------------------------------------------------
/example/ios/example/Base.lproj/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
21 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/example/ios/example/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | }
33 | ],
34 | "info" : {
35 | "version" : 1,
36 | "author" : "xcode"
37 | }
38 | }
--------------------------------------------------------------------------------
/example/ios/example/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/example/ios/example/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | example
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1
25 | LSRequiresIPhoneOS
26 |
27 | UILaunchStoryboardName
28 | LaunchScreen
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 |
39 | UIViewControllerBasedStatusBarAppearance
40 |
41 | NSLocationWhenInUseUsageDescription
42 |
43 | NSAppTransportSecurity
44 |
45 |
46 | NSExceptionDomains
47 |
48 | localhost
49 |
50 | NSExceptionAllowsInsecureHTTPLoads
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/example/ios/example/main.m:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2015-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | #import
9 |
10 | #import "AppDelegate.h"
11 |
12 | int main(int argc, char * argv[]) {
13 | @autoreleasepool {
14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/example/ios/exampleTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/example/ios/exampleTests/exampleTests.m:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2015-present, Facebook, Inc.
3 | *
4 | * This source code is licensed under the MIT license found in the
5 | * LICENSE file in the root directory of this source tree.
6 | */
7 |
8 | #import
9 | #import
10 |
11 | #import
12 | #import
13 |
14 | #define TIMEOUT_SECONDS 600
15 | #define TEXT_TO_LOOK_FOR @"Welcome to React Native!"
16 |
17 | @interface exampleTests : XCTestCase
18 |
19 | @end
20 |
21 | @implementation exampleTests
22 |
23 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test
24 | {
25 | if (test(view)) {
26 | return YES;
27 | }
28 | for (UIView *subview in [view subviews]) {
29 | if ([self findSubviewInView:subview matching:test]) {
30 | return YES;
31 | }
32 | }
33 | return NO;
34 | }
35 |
36 | - (void)testRendersWelcomeScreen
37 | {
38 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController];
39 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
40 | BOOL foundElement = NO;
41 |
42 | __block NSString *redboxError = nil;
43 | RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
44 | if (level >= RCTLogLevelError) {
45 | redboxError = message;
46 | }
47 | });
48 |
49 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) {
50 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
51 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
52 |
53 | foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) {
54 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) {
55 | return YES;
56 | }
57 | return NO;
58 | }];
59 | }
60 |
61 | RCTSetLogFunction(RCTDefaultLogFunction);
62 |
63 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError);
64 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS);
65 | }
66 |
67 |
68 | @end
69 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "version": "0.0.1",
4 | "private": true,
5 | "scripts": {
6 | "start": "node node_modules/react-native/local-cli/cli.js start",
7 | "test": "jest"
8 | },
9 | "dependencies": {
10 | "react": "16.4.1",
11 | "react-native": "0.56.0",
12 | "react-native-offline-cache-webview": "../"
13 | },
14 | "devDependencies": {
15 | "babel-jest": "23.4.2",
16 | "babel-preset-react-native": "^5",
17 | "jest": "23.4.2",
18 | "react-test-renderer": "16.4.1"
19 | },
20 | "jest": {
21 | "preset": "react-native"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ios/RNAdvancedWebView.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | @class RNAdvancedWebView;
5 |
6 | /**
7 | * Special scheme used to pass messages to the injectedJavaScript
8 | * code without triggering a page load. Usage:
9 | *
10 | * window.location.href = RCTJSNavigationScheme + '://hello'
11 | */
12 | extern NSString *const RCTJSNavigationScheme;
13 |
14 | @protocol RNAdvancedWebViewDelegate
15 |
16 | - (BOOL)webView:(RNAdvancedWebView *)webView
17 | shouldStartLoadForRequest:(NSMutableDictionary *)request
18 | withCallback:(RCTDirectEventBlock)callback;
19 |
20 | @end
21 |
22 | @interface RNAdvancedWebView : RCTView
23 |
24 | - (instancetype)initWithProcessPool:(WKProcessPool *)processPool;
25 |
26 | @property (nonatomic, weak) id delegate;
27 |
28 | @property (nonatomic, copy) NSDictionary *source;
29 | @property (nonatomic, assign) UIEdgeInsets contentInset;
30 | @property (nonatomic, assign) BOOL automaticallyAdjustContentInsets;
31 | @property (nonatomic, assign) BOOL openNewWindowInWebView;
32 | @property (nonatomic, assign) NSInteger contentInsetAdjustmentBehavior;
33 | @property (nonatomic, copy) NSString *injectedJavaScript;
34 |
35 | /**
36 | supported schemes, others will use openURL。 default is @[@"http", @"https", @"file", @"ftp", @"ws"]
37 | */
38 | @property (nonatomic, strong) NSArray *validSchemes;
39 |
40 | /**
41 | Whether support postMessage
42 | */
43 | @property (nonatomic, assign) BOOL messagingEnabled;
44 |
45 | @property (nonatomic, assign) BOOL hideAccessory;
46 |
47 | @property (nonatomic, assign) BOOL keyboardDisplayRequiresUserAction;
48 |
49 | @property (nonatomic, assign) BOOL disableKeyboardAdjust;
50 |
51 | - (void)goForward;
52 | - (void)goBack;
53 | - (void)reload;
54 | - (void)stopLoading;
55 |
56 | - (void)postMessage:(NSString *)message;
57 | - (void)injectJavaScript:(NSString *)script;
58 |
59 | - (BOOL)canGoBack;
60 | - (BOOL)canGoForward;
61 | - (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^)(id, NSError *error))completionHandler;
62 |
63 | @end
64 |
--------------------------------------------------------------------------------
/ios/RNAdvancedWebView.m:
--------------------------------------------------------------------------------
1 | #import "RNAdvancedWebView.h"
2 | #import
3 | #import "RNAdvancedWebView.h"
4 | #import
5 |
6 | #import
7 |
8 | #import
9 | #import
10 | #import
11 | #import
12 | #import
13 | #import
14 | #import
15 |
16 | #import
17 |
18 | #import
19 | #import
20 |
21 | NSString *const RNAdvancedWebJSNavigationScheme = @"react-js-navigation";
22 | NSString *const RNAdvancedWebViewJSPostMessageHost = @"postMessage";
23 | NSString *const RNAdvancedWebViewJSDataTransferSetHost = @"dataTransferSet";
24 | NSString *const RNAdvancedWebViewHtmlType = @"Apple Web Archive pasteboard type";
25 | // runtime trick to remove WKWebView keyboard default toolbar
26 | // see: http://stackoverflow.com/questions/19033292/ios-7-uiwebview-keyboard-issue/19042279#19042279
27 | @interface _SwizzleHelperWK : NSObject
28 | @end
29 |
30 | @implementation _SwizzleHelperWK
31 |
32 | -(id)inputAccessoryView
33 | {
34 | return nil;
35 | }
36 |
37 | @end
38 |
39 |
40 | @interface RNAdvancedWebView ()
41 |
42 | @property (nonatomic, copy) RCTDirectEventBlock onLoadingStart;
43 | @property (nonatomic, copy) RCTDirectEventBlock onLoadingFinish;
44 | @property (nonatomic, copy) RCTDirectEventBlock onLoadingError;
45 | @property (nonatomic, copy) RCTDirectEventBlock onShouldStartLoadWithRequest;
46 | @property (nonatomic, copy) RCTDirectEventBlock onProgress;
47 | @property (nonatomic, copy) RCTDirectEventBlock onMessage;
48 | @property (assign) BOOL sendCookies;
49 |
50 | @end
51 |
52 | @implementation RNAdvancedWebView
53 | {
54 | WKWebView *_webView;
55 | NSString *_injectedJavaScript;
56 | CGPoint _originOffset;
57 | BOOL _navigationFinished;
58 | NSMutableArray * _pendingMessages;
59 | }
60 |
61 | - (instancetype)initWithFrame:(CGRect)frame
62 | {
63 | self = [super initWithFrame:frame];
64 | if (self) {
65 | _hideAccessory = NO;
66 | _keyboardDisplayRequiresUserAction = NO;
67 | _contentInsetAdjustmentBehavior = 0;
68 | _validSchemes = @[@"http", @"https", @"file", @"ftp", @"ws"];
69 |
70 | _pendingMessages = [[NSMutableArray alloc] init];
71 |
72 | [[NSNotificationCenter defaultCenter] addObserver:self
73 | selector:@selector(keyboardWillChange:)
74 | name:UIKeyboardWillChangeFrameNotification object:nil];
75 | }
76 | return self;
77 | }
78 |
79 | RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
80 |
81 | - (instancetype)initWithProcessPool:(WKProcessPool *)processPool
82 | {
83 | if(self = [self initWithFrame:CGRectZero])
84 | {
85 | super.backgroundColor = [UIColor clearColor];
86 |
87 | _automaticallyAdjustContentInsets = YES;
88 | _contentInset = UIEdgeInsetsZero;
89 |
90 | WKWebViewConfiguration* config = [[WKWebViewConfiguration alloc] init];
91 | config.processPool = processPool;
92 | @try {
93 | [config setValue:[NSNumber numberWithBool:YES] forKey:@"allowUniversalAccessFromFileURLs"];
94 | } @catch (NSException *exception) {
95 | NSLog(@"%@", exception);
96 | }
97 |
98 | WKPreferences *preferences = [[WKPreferences alloc] init];
99 | @try {
100 | [preferences setValue:[NSNumber numberWithBool:YES] forKey:@"allowFileAccessFromFileURLs"];
101 | } @catch (NSException *exception) {
102 | NSLog(@"%@", exception);
103 | }
104 |
105 | config.preferences = preferences;
106 |
107 | _webView = [[WKWebView alloc] initWithFrame:self.bounds configuration:config];
108 | _webView.UIDelegate = self;
109 | _webView.navigationDelegate = self;
110 |
111 | [_webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
112 | [_webView.scrollView addObserver:self forKeyPath:@"contentInset" options:NSKeyValueObservingOptionNew context:nil];
113 |
114 | [[NSNotificationCenter defaultCenter] addObserver:self
115 | selector:@selector(pasteboardChangedNotification:)
116 | name:UIPasteboardChangedNotification
117 | object:nil];
118 | [[NSNotificationCenter defaultCenter] addObserver:self
119 | selector:@selector(pasteboardChangedNotification:)
120 | name:UIApplicationDidBecomeActiveNotification
121 | object:nil];
122 | [self injectDataTransferGetData];
123 | [self addSubview:_webView];
124 | }
125 | return self;
126 | }
127 |
128 | - (void)loadRequest:(NSURLRequest *)request
129 | {
130 | _navigationFinished = NO;
131 | if (request.URL && _sendCookies) {
132 | NSDictionary *cookies = [NSHTTPCookie requestHeaderFieldsWithCookies:[[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:request.URL]];
133 | if ([cookies objectForKey:@"Cookie"]) {
134 | NSMutableURLRequest *mutableRequest = request.mutableCopy;
135 | [mutableRequest addValue:cookies[@"Cookie"] forHTTPHeaderField:@"Cookie"];
136 | request = mutableRequest;
137 | }
138 | }
139 | [_webView loadRequest:request];
140 | }
141 |
142 | - (void)goForward
143 | {
144 | _navigationFinished = NO;
145 | [_webView goForward];
146 | }
147 |
148 | - (void)evaluateJavaScript:(NSString *)javaScriptString
149 | completionHandler:(void (^)(id, NSError *error))completionHandler
150 | {
151 | [_webView evaluateJavaScript:javaScriptString completionHandler:completionHandler];
152 | }
153 |
154 | - (void)goBack
155 | {
156 | _navigationFinished = NO;
157 | [_webView goBack];
158 | }
159 |
160 | - (BOOL)canGoBack
161 | {
162 | return [_webView canGoBack];
163 | }
164 |
165 | - (BOOL)canGoForward
166 | {
167 | return [_webView canGoForward];
168 | }
169 |
170 | - (void)reload
171 | {
172 | _navigationFinished = NO;
173 | NSURLRequest *request = [RCTConvert NSURLRequest:self.source];
174 | if (request.URL && !_webView.URL.absoluteString.length) {
175 | [self loadRequest:request];
176 | } else {
177 | [_webView reload];
178 | }
179 | }
180 |
181 | - (void)stopLoading
182 | {
183 | _navigationFinished = NO;
184 | [_webView stopLoading];
185 | }
186 |
187 | - (void)postMessage:(NSString *)message
188 | {
189 | if (_navigationFinished) {
190 | NSDictionary *eventInitDict = @{
191 | @"data": message,
192 | };
193 | NSString *source = [NSString
194 | stringWithFormat:@"document.dispatchEvent(new MessageEvent('message', %@));",
195 | RCTJSONStringify(eventInitDict, NULL)
196 | ];
197 | [_webView evaluateJavaScript:source completionHandler:nil];
198 | } else {
199 | [_pendingMessages addObject:message];
200 | }
201 |
202 | }
203 |
204 | - (void)injectJavaScript:(NSString *)script
205 | {
206 | [_webView evaluateJavaScript:script completionHandler:nil];
207 | }
208 |
209 | - (void)setSource:(NSDictionary *)source
210 | {
211 | // Decode query string and hash in local file path
212 | NSString *URLString = source[@"uri"] ?: source[@"url"];
213 | if ([URLString hasPrefix:@"/"] || [URLString hasPrefix:@"file:///"]) {
214 | source = @{
215 | @"uri": [URLString stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]
216 | };
217 |
218 | }
219 | if (![_source isEqualToDictionary:source]) {
220 | _source = [source copy];
221 | _sendCookies = [source[@"sendCookies"] boolValue];
222 | if ([source[@"customUserAgent"] length] != 0 && [_webView respondsToSelector:@selector(setCustomUserAgent:)]) {
223 | [_webView setCustomUserAgent:source[@"customUserAgent"]];
224 | }
225 |
226 | // Allow loading local files:
227 | //
228 | // Only works for iOS 9+. So iOS 8 will simply ignore those two values
229 | NSString *file = [RCTConvert NSString:source[@"file"]];
230 | NSString *allowingReadAccessToURL = [RCTConvert NSString:source[@"allowingReadAccessToURL"]];
231 |
232 | if (file && [_webView respondsToSelector:@selector(loadFileURL:allowingReadAccessToURL:)]) {
233 | NSURL *fileURL = [RCTConvert NSURL:file];
234 | NSURL *baseURL = [RCTConvert NSURL:allowingReadAccessToURL];
235 | [_webView loadFileURL:fileURL allowingReadAccessToURL:baseURL];
236 | return;
237 | }
238 |
239 | // Check for a static html source first
240 | NSString *html = [RCTConvert NSString:source[@"html"]];
241 | if (html) {
242 | NSURL *baseURL = [RCTConvert NSURL:source[@"baseUrl"]];
243 | if (!baseURL) {
244 | baseURL = [NSURL URLWithString:@"about:blank"];
245 | }
246 | [_webView loadHTMLString:html baseURL:baseURL];
247 | return;
248 | }
249 |
250 | NSURLRequest *request = [RCTConvert NSURLRequest:source];
251 |
252 | // Because of the way React works, as pages redirect, we actually end up
253 | // passing the redirect urls back here, so we ignore them if trying to load
254 | // the same url. We'll expose a call to 'reload' to allow a user to load
255 | // the existing page.
256 | if ([request.URL isEqual:_webView.URL]) {
257 | return;
258 | }
259 | if (!request.URL) {
260 | // Clear the webview
261 | [_webView loadHTMLString:@"" baseURL:nil];
262 | return;
263 | }
264 | [self loadRequest:request];
265 | }
266 | }
267 |
268 | - (void)layoutSubviews
269 | {
270 | [super layoutSubviews];
271 | _webView.frame = self.bounds;
272 | }
273 |
274 | - (void)setContentInset:(UIEdgeInsets)contentInset
275 | {
276 | _contentInset = contentInset;
277 | [RCTView autoAdjustInsetsForView:self
278 | withScrollView:_webView.scrollView
279 | updateOffset:NO];
280 | }
281 |
282 | - (void)setContentInsetAdjustmentBehavior:(NSInteger)contentInsetAdjustmentBehavior
283 | {
284 | if (_contentInsetAdjustmentBehavior == contentInsetAdjustmentBehavior) {
285 | return;
286 | }
287 | _contentInsetAdjustmentBehavior = contentInsetAdjustmentBehavior;
288 | if (@available(iOS 11.0, *)) {
289 | _webView.scrollView.contentInsetAdjustmentBehavior = (UIScrollViewContentInsetAdjustmentBehavior)_contentInsetAdjustmentBehavior;
290 | }
291 | }
292 |
293 | - (void)setBackgroundColor:(UIColor *)backgroundColor
294 | {
295 | CGFloat alpha = CGColorGetAlpha(backgroundColor.CGColor);
296 | self.opaque = _webView.opaque = _webView.scrollView.opaque = (alpha == 1.0);
297 | _webView.backgroundColor = _webView.scrollView.backgroundColor = backgroundColor;
298 | }
299 |
300 | - (UIColor *)backgroundColor
301 | {
302 | return _webView.backgroundColor;
303 | }
304 |
305 | - (NSMutableDictionary *)baseEvent
306 | {
307 | NSMutableDictionary *event = [[NSMutableDictionary alloc] initWithDictionary:@{
308 | @"url": _webView.URL.absoluteString ?: @"",
309 | @"loading" : @(_webView.loading),
310 | @"title": _webView.title,
311 | @"canGoBack": @(_webView.canGoBack),
312 | @"canGoForward" : @(_webView.canGoForward),
313 | }];
314 | return event;
315 | }
316 |
317 | - (void)refreshContentInset
318 | {
319 | [RCTView autoAdjustInsetsForView:self
320 | withScrollView:_webView.scrollView
321 | updateOffset:YES];
322 | }
323 |
324 | - (void)dealloc
325 | {
326 | _webView.scrollView.delegate = nil;
327 | _webView.UIDelegate = nil;
328 | _webView.navigationDelegate = nil;
329 | [_webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"about:blank"]]];
330 | [[NSNotificationCenter defaultCenter] removeObserver:self];
331 | @try {
332 | [_webView removeObserver:self forKeyPath:@"estimatedProgress"];
333 | [_webView.scrollView removeObserver:self forKeyPath:@"contentInset"];
334 | }
335 | @catch (NSException * __unused exception) {}
336 | _webView = nil;
337 | }
338 |
339 | #pragma mark - NSKeyValueObserving
340 |
341 | - (void)observeValueForKeyPath:(NSString *)keyPath
342 | ofObject:(id)object
343 | change:(NSDictionary *)change
344 | context:(void *)context
345 | {
346 | if ([keyPath isEqualToString:@"estimatedProgress"]) {
347 | if (_onProgress) {
348 | _onProgress(@{@"progress": [change objectForKey:NSKeyValueChangeNewKey]});
349 | }
350 | } else if ([keyPath isEqualToString:@"contentInset"]) {
351 | if (!_automaticallyAdjustContentInsets) {
352 | NSValue *value = [change objectForKey:NSKeyValueChangeNewKey];
353 | UIEdgeInsets contentInset = value.UIEdgeInsetsValue;
354 | if (!UIEdgeInsetsEqualToEdgeInsets(_contentInset, contentInset)) {
355 | [RCTView autoAdjustInsetsForView:self
356 | withScrollView:_webView.scrollView
357 | updateOffset:NO];
358 | }
359 | }
360 | }
361 | }
362 |
363 | #pragma mark - UIPastboard inject
364 |
365 | - (void)pasteboardChangedNotification:(NSNotification*)notification {
366 | dispatch_async(dispatch_get_main_queue(), ^{
367 | [self injectDataTransferGetData];
368 | });
369 | }
370 |
371 | // Set data from JavaScript to Pastboard
372 | - (void)setDataToPasteboard:(NSString *)dataString
373 | {
374 | NSRange range = [dataString rangeOfString:@","];
375 | NSString *type = [dataString substringToIndex:range.location];
376 | NSString *data = [dataString substringFromIndex:range.location + range.length];
377 | UIPasteboard *clipboard = [UIPasteboard generalPasteboard];
378 |
379 | if ([type isEqualToString:@"text/html"]) {
380 | NSMutableDictionary *resourceDictionary = [NSMutableDictionary dictionary];
381 |
382 | [resourceDictionary setObject:[data dataUsingEncoding:NSUTF8StringEncoding] forKey:@"WebResourceData"];
383 | [resourceDictionary setObject:@"" forKey:@"WebResourceFrameName"];
384 | [resourceDictionary setObject:@"text/html" forKey:@"WebResourceMIMEType"];
385 | [resourceDictionary setObject:@"UTF-8" forKey:@"WebResourceTextEncodingName"];
386 | [resourceDictionary setObject:[_webView.URL absoluteString] forKey:@"WebResourceURL"];
387 |
388 | NSDictionary *containerDictionary = [NSDictionary dictionaryWithObjectsAndKeys:resourceDictionary, @"WebMainResource", nil];
389 | NSDictionary *htmlItems = [NSDictionary dictionaryWithObjectsAndKeys:containerDictionary, RNAdvancedWebViewHtmlType, nil];
390 | [clipboard addItems: [NSArray arrayWithObjects: htmlItems, nil]];
391 | } else if ([type isEqualToString:@"text/plain"]) {
392 | clipboard.string = data;
393 | }
394 | }
395 |
396 | - (void)injectDataTransferGetData {
397 | UIPasteboard *pasteBoard = [UIPasteboard generalPasteboard];
398 | NSArray *> *items = [pasteBoard items];
399 | NSString *htmlString = @"";
400 | for (NSDictionary *item in items) {
401 | NSData *archiveData = [item objectForKey:RNAdvancedWebViewHtmlType];
402 | if (archiveData) {
403 | NSError* error = nil;
404 | id webArchive = [NSPropertyListSerialization propertyListWithData:(NSData *)archiveData
405 | options:NSPropertyListImmutable
406 | format:NULL error:&error];
407 | NSData *webResourceData = [[webArchive objectForKey:@"WebMainResource"] objectForKey:@"WebResourceData"];
408 | htmlString = [[NSString alloc] initWithData:webResourceData encoding:NSUTF8StringEncoding];
409 | htmlString = [htmlString stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"];
410 | }
411 | }
412 |
413 | // Override DataTransfer.prototype.getData
414 | NSString *dataTransferInjection = [NSString stringWithFormat:
415 | @"(function () {"
416 | "if (!window.originDataTransferGetData) {"
417 | " window.originDataTransferGetData = DataTransfer.prototype.getData;"
418 | "}"
419 | "DataTransfer.prototype.getData = function (type, data) {"
420 | " if (type === 'text/html') {"
421 | " return '%@';"
422 | " } else {"
423 | " return window.originDataTransferGetData.call(this, type, data);"
424 | " }"
425 | "};"
426 | "})();", htmlString];
427 | [_webView evaluateJavaScript:dataTransferInjection completionHandler:nil];
428 | }
429 |
430 | #pragma mark - WKNavigationDelegate
431 |
432 | - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation {
433 | if (_hideAccessory) {
434 | [self doHideAccessory];
435 | }
436 | if (!_keyboardDisplayRequiresUserAction) {
437 | [self doKeyboardDisplayAutomatically];
438 | }
439 | }
440 |
441 | - (void)webView:(__unused WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
442 | {
443 | NSURLRequest *request = navigationAction.request;
444 | NSURL* url = request.URL;
445 | NSString* scheme = url.scheme;
446 |
447 | BOOL isJSNavigation = [scheme isEqualToString:RNAdvancedWebJSNavigationScheme];
448 |
449 | if (isJSNavigation) {
450 | NSURL *url = request.URL;
451 | NSString *data = url.query;
452 | data = [data stringByReplacingOccurrencesOfString:@"+" withString:@" "];
453 | data = [data stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
454 | if ([url.host isEqualToString:RNAdvancedWebViewJSPostMessageHost]) {
455 |
456 | NSMutableDictionary *event = [self baseEvent];
457 | [event addEntriesFromDictionary: @{
458 | @"data": data,
459 | }];
460 | _onMessage(event);
461 | } else if ([url.host isEqualToString:RNAdvancedWebViewJSDataTransferSetHost]) {
462 | [self setDataToPasteboard:data];
463 | }
464 |
465 |
466 | decisionHandler(WKNavigationActionPolicyCancel);
467 | return;
468 | } else {
469 | if (_onShouldStartLoadWithRequest) {
470 | NSMutableDictionary *event = [self baseEvent];
471 | [event addEntriesFromDictionary: @{
472 | @"url": (request.URL).absoluteString,
473 | @"navigationType": @(navigationAction.navigationType)
474 | }];
475 | if (![self.delegate webView:self
476 | shouldStartLoadForRequest:event
477 | withCallback:_onShouldStartLoadWithRequest]) {
478 | decisionHandler(WKNavigationActionPolicyCancel);
479 | return;
480 | }
481 | }
482 |
483 | if ([self externalAppRequiredToOpenURL:url]) {
484 | if ([[UIApplication sharedApplication] canOpenURL:url]) {
485 | [[UIApplication sharedApplication] openURL:url];
486 | decisionHandler(WKNavigationActionPolicyCancel);
487 | return;
488 | }
489 | } else {
490 | if (!navigationAction.targetFrame) {
491 | [webView loadRequest:navigationAction.request];
492 | decisionHandler(WKNavigationActionPolicyCancel);
493 | return;
494 | }
495 | }
496 |
497 | if (_onLoadingStart) {
498 | // We have this check to filter out iframe requests and whatnot
499 | BOOL isTopFrame = [url isEqual:request.mainDocumentURL];
500 | if (isTopFrame) {
501 | NSMutableDictionary *event = [self baseEvent];
502 | [event addEntriesFromDictionary: @{
503 | @"url": url.absoluteString,
504 | @"navigationType": @(navigationAction.navigationType)
505 | }];
506 | _onLoadingStart(event);
507 | }
508 | }
509 | }
510 | decisionHandler(WKNavigationActionPolicyAllow);
511 | }
512 |
513 | - (void)webView:(__unused WKWebView *)webView didFailProvisionalNavigation:(__unused WKNavigation *)navigation withError:(NSError *)error
514 | {
515 | if (_onLoadingError) {
516 | if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) {
517 | // NSURLErrorCancelled is reported when a page has a redirect OR if you load
518 | // a new URL in the WebView before the previous one came back. We can just
519 | // ignore these since they aren't real errors.
520 | // http://stackoverflow.com/questions/1024748/how-do-i-fix-nsurlerrordomain-error-999-in-iphone-3-0-os
521 | return;
522 | }
523 |
524 | NSMutableDictionary *event = [self baseEvent];
525 | [event addEntriesFromDictionary:@{
526 | @"domain": error.domain,
527 | @"code": @(error.code),
528 | @"description": error.localizedDescription,
529 | }];
530 | _onLoadingError(event);
531 | }
532 | }
533 |
534 | - (void)webView:(WKWebView *)webView didFinishNavigation:(__unused WKNavigation *)navigation
535 | {
536 | _navigationFinished = YES;
537 | [_pendingMessages enumerateObjectsUsingBlock:^(NSString * message, NSUInteger idx, BOOL * _Nonnull stop) {
538 | [self postMessage:message];
539 | }];
540 | _pendingMessages = [[NSMutableArray alloc] init];
541 |
542 | if (self.messagingEnabled) {
543 | NSString *source = [NSString stringWithFormat:
544 | @"(function() {"
545 | "var isNative = window.postMessage.length !== 1;"
546 | "if (!isNative) {return}"
547 | "var messageStack = [];"
548 | "var executing = false;"
549 | "function executeStack() {"
550 | " var message = messageStack.shift();"
551 | " if (message) {"
552 | " executing = true;"
553 | " window.location = message;"
554 | " setTimeout(executeStack);"
555 | " } else {"
556 | " executing = false;"
557 | " }"
558 | "};"
559 | "window.originalPostMessage = window.postMessage;"
560 | "window.postMessage = function(data) {"
561 | " messageStack.push('%@://%@?' + encodeURIComponent(String(data)));"
562 | " if (!executing) executeStack();"
563 | "};"
564 | "document.dispatchEvent(new CustomEvent('ReactNativeContextReady'));"
565 | "})();", RNAdvancedWebJSNavigationScheme, RNAdvancedWebViewJSPostMessageHost
566 | ];
567 | [webView evaluateJavaScript:source completionHandler:nil];
568 | }
569 |
570 | // Polyfill for Clipboard API
571 | NSString *dataTransferInjection = [NSString stringWithFormat:
572 | @"(function () {;"
573 | "var messageStack = [];"
574 | "var executing = false;"
575 | "function executeStack() {"
576 | " var message = messageStack.shift();"
577 | " if (message) {"
578 | " executing = true;"
579 | " window.location = message;"
580 | " setTimeout(executeStack);"
581 | " } else {"
582 | " executing = false;"
583 | " }"
584 | "};"
585 | "DataTransfer.prototype.setData = function (type, data) {"
586 | " messageStack.push('%@://%@?' + encodeURIComponent(type + ',' + data));"
587 | " if (!executing) executeStack();"
588 | "};"
589 | "})();", RNAdvancedWebJSNavigationScheme, RNAdvancedWebViewJSDataTransferSetHost];
590 | [webView evaluateJavaScript:dataTransferInjection completionHandler:nil];
591 | if (_injectedJavaScript) {
592 | if (_onLoadingFinish) {
593 | [webView evaluateJavaScript:_injectedJavaScript completionHandler:^(id result, NSError *error) {
594 | NSMutableDictionary *event = [self baseEvent];
595 | event[@"jsEvaluationValue"] = [NSString stringWithFormat:@"%@", result];
596 | _onLoadingFinish(event);
597 | }];
598 | }
599 | }
600 | // we only need the final 'finishLoad' call so only fire the event when we're actually done loading.
601 | else if (_onLoadingFinish && !webView.loading && ![webView.URL.absoluteString isEqualToString:@"about:blank"]) {
602 | _onLoadingFinish([self baseEvent]);
603 | }
604 | }
605 |
606 | #pragma mark - WKUIDelegate
607 |
608 | - (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
609 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:message message:nil preferredStyle:UIAlertControllerStyleAlert];
610 |
611 | [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Close", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
612 | completionHandler();
613 | }]];
614 | UIViewController *presentingController = RCTPresentedViewController();
615 | [presentingController presentViewController:alertController animated:YES completion:nil];
616 | }
617 |
618 | - (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler {
619 |
620 | // TODO We have to think message to confirm "YES"
621 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:message message:nil preferredStyle:UIAlertControllerStyleAlert];
622 | [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
623 | completionHandler(YES);
624 | }]];
625 | [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
626 | completionHandler(NO);
627 | }]];
628 | UIViewController *presentingController = RCTPresentedViewController();
629 | [presentingController presentViewController:alertController animated:YES completion:nil];
630 | }
631 |
632 | - (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *))completionHandler {
633 |
634 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:prompt message:nil preferredStyle:UIAlertControllerStyleAlert];
635 | [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
636 | textField.text = defaultText;
637 | }];
638 |
639 | [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
640 | NSString *input = ((UITextField *)alertController.textFields.firstObject).text;
641 | completionHandler(input);
642 | }]];
643 |
644 | [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
645 | completionHandler(nil);
646 | }]];
647 | UIViewController *presentingController = RCTPresentedViewController();
648 | [presentingController presentViewController:alertController animated:YES completion:nil];
649 | }
650 |
651 | - (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures
652 | {
653 | NSString *scheme = navigationAction.request.URL.scheme;
654 | if ((navigationAction.targetFrame.isMainFrame || _openNewWindowInWebView) && ([scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"])) {
655 | [webView loadRequest:navigationAction.request];
656 | } else {
657 | UIApplication *app = [UIApplication sharedApplication];
658 | NSURL *url = navigationAction.request.URL;
659 | if ([app canOpenURL:url]) {
660 | [app openURL:url];
661 | }
662 | }
663 | return nil;
664 | }
665 |
666 | #pragma mark - Notification
667 |
668 | - (void)keyboardWillChange:(NSNotification*)aNotification
669 | {
670 | if (_disableKeyboardAdjust) {
671 | // Disable Keyboard push up WebView.
672 | _originOffset = _webView.scrollView.contentOffset;
673 | _webView.scrollView.delegate = self;
674 | }
675 | }
676 |
677 | #pragma mark - UIScrollViewDelegate
678 |
679 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
680 | if (scrollView == _webView.scrollView) {
681 | float height = scrollView.frame.size.height;
682 | if (_originOffset.y + height <= scrollView.contentSize.height) {
683 | // Reset WebView's scrollView
684 | scrollView.contentOffset = _originOffset;
685 | }
686 | scrollView.delegate = nil;
687 | }
688 | }
689 |
690 | #pragma mark - Private
691 |
692 | /**
693 | hide inputAccessoryView
694 | */
695 | -(void)doHideAccessory
696 | {
697 | UIView* subview;
698 | for (UIView* view in _webView.scrollView.subviews) {
699 | if([[view.class description] hasPrefix:@"WKContent"])
700 | subview = view;
701 | }
702 |
703 | if(subview == nil) return;
704 |
705 | NSString* name = [NSString stringWithFormat:@"%@_SwizzleHelperWK", subview.class.superclass];
706 | Class newClass = NSClassFromString(name);
707 |
708 | if(newClass == nil)
709 | {
710 | newClass = objc_allocateClassPair(subview.class, [name cStringUsingEncoding:NSASCIIStringEncoding], 0);
711 | if(!newClass) return;
712 | Method method = class_getInstanceMethod([_SwizzleHelperWK class], @selector(inputAccessoryView));
713 | class_addMethod(newClass, @selector(inputAccessoryView), method_getImplementation(method), method_getTypeEncoding(method));
714 | objc_registerClassPair(newClass);
715 | }
716 | object_setClass(subview, newClass);
717 | }
718 |
719 | - (void)doKeyboardDisplayAutomatically {
720 | Class class = NSClassFromString(@"WKContentView");
721 | NSOperatingSystemVersion iOS_11_3_0 = (NSOperatingSystemVersion){11, 3, 0};
722 |
723 | if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion: iOS_11_3_0]) {
724 | SEL selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:changingActivityState:userObject:");
725 | Method method = class_getInstanceMethod(class, selector);
726 | IMP original = method_getImplementation(method);
727 | IMP override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, BOOL arg3, id arg4) {
728 | ((void (*)(id, SEL, void*, BOOL, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3, arg4);
729 | });
730 | method_setImplementation(method, override);
731 | } else {
732 | SEL selector = sel_getUid("_startAssistingNode:userIsInteracting:blurPreviousNode:userObject:");
733 | Method method = class_getInstanceMethod(class, selector);
734 | IMP original = method_getImplementation(method);
735 | IMP override = imp_implementationWithBlock(^void(id me, void* arg0, BOOL arg1, BOOL arg2, id arg3) {
736 | ((void (*)(id, SEL, void*, BOOL, BOOL, id))original)(me, selector, arg0, TRUE, arg2, arg3);
737 | });
738 | method_setImplementation(method, override);
739 | }
740 | }
741 |
742 | /**
743 | Whether need external app to open url
744 | @param URL URL description
745 | @return return value description
746 | */
747 | - (BOOL)externalAppRequiredToOpenURL:(NSURL *)URL {
748 | NSString *scheme = URL.scheme;
749 | if (scheme.length) {
750 | return ![_validSchemes containsObject:URL.scheme];
751 | } else {
752 | return NO;
753 | }
754 | }
755 |
756 | @end
757 |
--------------------------------------------------------------------------------
/ios/RNAdvancedWebView.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 7F5EB4A9209AF8AE0029C840 /* UIScrollView+adjustedContentInset.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F5EB4A7209AF8AE0029C840 /* UIScrollView+adjustedContentInset.m */; };
11 | 7F6E55141E1CA7E000C11C87 /* RNAdvancedWebViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F6E55131E1CA7E000C11C87 /* RNAdvancedWebViewManager.m */; };
12 | 7F6E55161E1CA8C400C11C87 /* RNAdvancedWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F6E55151E1CA8C400C11C87 /* RNAdvancedWebView.m */; };
13 | 8E1F5D81210F243E00CFED9B /* Reachability.m in Sources */ = {isa = PBXBuildFile; fileRef = 8E1F5D71210F243E00CFED9B /* Reachability.m */; };
14 | 8E1F5D82210F243E00CFED9B /* NSString+Sha1.m in Sources */ = {isa = PBXBuildFile; fileRef = 8E1F5D72210F243E00CFED9B /* NSString+Sha1.m */; };
15 | 8E1F5D83210F243E00CFED9B /* RNCachingURLProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = 8E1F5D77210F243E00CFED9B /* RNCachingURLProtocol.m */; };
16 | /* End PBXBuildFile section */
17 |
18 | /* Begin PBXCopyFilesBuildPhase section */
19 | 1017892E1B283EE1006C41B8 /* CopyFiles */ = {
20 | isa = PBXCopyFilesBuildPhase;
21 | buildActionMask = 2147483647;
22 | dstPath = "include/$(PRODUCT_NAME)";
23 | dstSubfolderSpec = 16;
24 | files = (
25 | );
26 | runOnlyForDeploymentPostprocessing = 0;
27 | };
28 | /* End PBXCopyFilesBuildPhase section */
29 |
30 | /* Begin PBXFileReference section */
31 | 101789301B283EE1006C41B8 /* libRNAdvancedWebView.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNAdvancedWebView.a; sourceTree = BUILT_PRODUCTS_DIR; };
32 | 7F5EB4A7209AF8AE0029C840 /* UIScrollView+adjustedContentInset.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIScrollView+adjustedContentInset.m"; sourceTree = ""; };
33 | 7F5EB4A8209AF8AE0029C840 /* UIScrollView+adjustedContentInset.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIScrollView+adjustedContentInset.h"; sourceTree = ""; };
34 | 7F6E55121E1CA7E000C11C87 /* RNAdvancedWebViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNAdvancedWebViewManager.h; sourceTree = ""; };
35 | 7F6E55131E1CA7E000C11C87 /* RNAdvancedWebViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNAdvancedWebViewManager.m; sourceTree = ""; };
36 | 7F6E55151E1CA8C400C11C87 /* RNAdvancedWebView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNAdvancedWebView.m; sourceTree = ""; };
37 | 7F6E55171E1CA8D000C11C87 /* RNAdvancedWebView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNAdvancedWebView.h; sourceTree = ""; };
38 | 8E1F5D71210F243E00CFED9B /* Reachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Reachability.m; sourceTree = ""; };
39 | 8E1F5D72210F243E00CFED9B /* NSString+Sha1.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+Sha1.m"; sourceTree = ""; };
40 | 8E1F5D73210F243E00CFED9B /* RNCachingURLProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNCachingURLProtocol.h; sourceTree = ""; };
41 | 8E1F5D74210F243E00CFED9B /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
42 | 8E1F5D75210F243E00CFED9B /* Reachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Reachability.h; sourceTree = ""; };
43 | 8E1F5D76210F243E00CFED9B /* NSString+Sha1.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+Sha1.h"; sourceTree = ""; };
44 | 8E1F5D77210F243E00CFED9B /* RNCachingURLProtocol.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNCachingURLProtocol.m; sourceTree = ""; };
45 | 8E1F5D79210F243E00CFED9B /* RNCachingURLProtocol.iml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = RNCachingURLProtocol.iml; sourceTree = ""; };
46 | 8E1F5D7A210F243E00CFED9B /* xcode.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = xcode.xml; sourceTree = ""; };
47 | 8E1F5D7B210F243E00CFED9B /* encodings.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = encodings.xml; sourceTree = ""; };
48 | 8E1F5D7C210F243E00CFED9B /* vcs.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = vcs.xml; sourceTree = ""; };
49 | 8E1F5D7D210F243E00CFED9B /* modules.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = modules.xml; sourceTree = ""; };
50 | 8E1F5D7E210F243E00CFED9B /* misc.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = misc.xml; sourceTree = ""; };
51 | 8E1F5D80210F243E00CFED9B /* scope_settings.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = scope_settings.xml; sourceTree = ""; };
52 | /* End PBXFileReference section */
53 |
54 | /* Begin PBXFrameworksBuildPhase section */
55 | 1017892D1B283EE1006C41B8 /* Frameworks */ = {
56 | isa = PBXFrameworksBuildPhase;
57 | buildActionMask = 2147483647;
58 | files = (
59 | );
60 | runOnlyForDeploymentPostprocessing = 0;
61 | };
62 | /* End PBXFrameworksBuildPhase section */
63 |
64 | /* Begin PBXGroup section */
65 | 101789271B283EE1006C41B8 = {
66 | isa = PBXGroup;
67 | children = (
68 | 101789311B283EE1006C41B8 /* Products */,
69 | 7F5EB4A8209AF8AE0029C840 /* UIScrollView+adjustedContentInset.h */,
70 | 7F5EB4A7209AF8AE0029C840 /* UIScrollView+adjustedContentInset.m */,
71 | 7F6E55171E1CA8D000C11C87 /* RNAdvancedWebView.h */,
72 | 7F6E55151E1CA8C400C11C87 /* RNAdvancedWebView.m */,
73 | 7F6E55121E1CA7E000C11C87 /* RNAdvancedWebViewManager.h */,
74 | 7F6E55131E1CA7E000C11C87 /* RNAdvancedWebViewManager.m */,
75 | 8E1F5D70210F243E00CFED9B /* RNCachingURLProtocol */,
76 | );
77 | sourceTree = "";
78 | };
79 | 101789311B283EE1006C41B8 /* Products */ = {
80 | isa = PBXGroup;
81 | children = (
82 | 101789301B283EE1006C41B8 /* libRNAdvancedWebView.a */,
83 | );
84 | name = Products;
85 | sourceTree = "";
86 | };
87 | 8E1F5D70210F243E00CFED9B /* RNCachingURLProtocol */ = {
88 | isa = PBXGroup;
89 | children = (
90 | 8E1F5D71210F243E00CFED9B /* Reachability.m */,
91 | 8E1F5D72210F243E00CFED9B /* NSString+Sha1.m */,
92 | 8E1F5D73210F243E00CFED9B /* RNCachingURLProtocol.h */,
93 | 8E1F5D74210F243E00CFED9B /* README.md */,
94 | 8E1F5D75210F243E00CFED9B /* Reachability.h */,
95 | 8E1F5D76210F243E00CFED9B /* NSString+Sha1.h */,
96 | 8E1F5D77210F243E00CFED9B /* RNCachingURLProtocol.m */,
97 | 8E1F5D78210F243E00CFED9B /* .idea */,
98 | );
99 | path = RNCachingURLProtocol;
100 | sourceTree = "";
101 | };
102 | 8E1F5D78210F243E00CFED9B /* .idea */ = {
103 | isa = PBXGroup;
104 | children = (
105 | 8E1F5D79210F243E00CFED9B /* RNCachingURLProtocol.iml */,
106 | 8E1F5D7A210F243E00CFED9B /* xcode.xml */,
107 | 8E1F5D7B210F243E00CFED9B /* encodings.xml */,
108 | 8E1F5D7C210F243E00CFED9B /* vcs.xml */,
109 | 8E1F5D7D210F243E00CFED9B /* modules.xml */,
110 | 8E1F5D7E210F243E00CFED9B /* misc.xml */,
111 | 8E1F5D7F210F243E00CFED9B /* scopes */,
112 | );
113 | path = .idea;
114 | sourceTree = "";
115 | };
116 | 8E1F5D7F210F243E00CFED9B /* scopes */ = {
117 | isa = PBXGroup;
118 | children = (
119 | 8E1F5D80210F243E00CFED9B /* scope_settings.xml */,
120 | );
121 | path = scopes;
122 | sourceTree = "";
123 | };
124 | /* End PBXGroup section */
125 |
126 | /* Begin PBXNativeTarget section */
127 | 1017892F1B283EE1006C41B8 /* RNAdvancedWebView */ = {
128 | isa = PBXNativeTarget;
129 | buildConfigurationList = 101789441B283EE1006C41B8 /* Build configuration list for PBXNativeTarget "RNAdvancedWebView" */;
130 | buildPhases = (
131 | 1017892C1B283EE1006C41B8 /* Sources */,
132 | 1017892D1B283EE1006C41B8 /* Frameworks */,
133 | 1017892E1B283EE1006C41B8 /* CopyFiles */,
134 | );
135 | buildRules = (
136 | );
137 | dependencies = (
138 | );
139 | name = RNAdvancedWebView;
140 | productName = RNAdvancedWebView;
141 | productReference = 101789301B283EE1006C41B8 /* libRNAdvancedWebView.a */;
142 | productType = "com.apple.product-type.library.static";
143 | };
144 | /* End PBXNativeTarget section */
145 |
146 | /* Begin PBXProject section */
147 | 101789281B283EE1006C41B8 /* Project object */ = {
148 | isa = PBXProject;
149 | attributes = {
150 | LastUpgradeCheck = 0620;
151 | ORGANIZATIONNAME = shimo;
152 | TargetAttributes = {
153 | 1017892F1B283EE1006C41B8 = {
154 | CreatedOnToolsVersion = 6.2;
155 | };
156 | };
157 | };
158 | buildConfigurationList = 1017892B1B283EE1006C41B8 /* Build configuration list for PBXProject "RNAdvancedWebView" */;
159 | compatibilityVersion = "Xcode 3.2";
160 | developmentRegion = English;
161 | hasScannedForEncodings = 0;
162 | knownRegions = (
163 | en,
164 | );
165 | mainGroup = 101789271B283EE1006C41B8;
166 | productRefGroup = 101789311B283EE1006C41B8 /* Products */;
167 | projectDirPath = "";
168 | projectRoot = "";
169 | targets = (
170 | 1017892F1B283EE1006C41B8 /* RNAdvancedWebView */,
171 | );
172 | };
173 | /* End PBXProject section */
174 |
175 | /* Begin PBXSourcesBuildPhase section */
176 | 1017892C1B283EE1006C41B8 /* Sources */ = {
177 | isa = PBXSourcesBuildPhase;
178 | buildActionMask = 2147483647;
179 | files = (
180 | 7F6E55141E1CA7E000C11C87 /* RNAdvancedWebViewManager.m in Sources */,
181 | 7F6E55161E1CA8C400C11C87 /* RNAdvancedWebView.m in Sources */,
182 | 8E1F5D81210F243E00CFED9B /* Reachability.m in Sources */,
183 | 8E1F5D82210F243E00CFED9B /* NSString+Sha1.m in Sources */,
184 | 7F5EB4A9209AF8AE0029C840 /* UIScrollView+adjustedContentInset.m in Sources */,
185 | 8E1F5D83210F243E00CFED9B /* RNCachingURLProtocol.m in Sources */,
186 | );
187 | runOnlyForDeploymentPostprocessing = 0;
188 | };
189 | /* End PBXSourcesBuildPhase section */
190 |
191 | /* Begin XCBuildConfiguration section */
192 | 101789421B283EE1006C41B8 /* Debug */ = {
193 | isa = XCBuildConfiguration;
194 | buildSettings = {
195 | ALWAYS_SEARCH_USER_PATHS = NO;
196 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
197 | CLANG_CXX_LIBRARY = "libc++";
198 | CLANG_ENABLE_MODULES = YES;
199 | CLANG_ENABLE_OBJC_ARC = YES;
200 | CLANG_WARN_BOOL_CONVERSION = YES;
201 | CLANG_WARN_CONSTANT_CONVERSION = YES;
202 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
203 | CLANG_WARN_EMPTY_BODY = YES;
204 | CLANG_WARN_ENUM_CONVERSION = YES;
205 | CLANG_WARN_INT_CONVERSION = YES;
206 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
207 | CLANG_WARN_UNREACHABLE_CODE = YES;
208 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
209 | COPY_PHASE_STRIP = NO;
210 | ENABLE_STRICT_OBJC_MSGSEND = YES;
211 | GCC_C_LANGUAGE_STANDARD = gnu99;
212 | GCC_DYNAMIC_NO_PIC = NO;
213 | GCC_OPTIMIZATION_LEVEL = 0;
214 | GCC_PREPROCESSOR_DEFINITIONS = (
215 | "DEBUG=1",
216 | "$(inherited)",
217 | );
218 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
219 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
220 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
221 | GCC_WARN_UNDECLARED_SELECTOR = YES;
222 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
223 | GCC_WARN_UNUSED_FUNCTION = YES;
224 | GCC_WARN_UNUSED_VARIABLE = YES;
225 | HEADER_SEARCH_PATHS = (
226 | "$(SRCROOT)/../node_modules/react-native/React/**",
227 | "$(inherited)",
228 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
229 | );
230 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
231 | MTL_ENABLE_DEBUG_INFO = YES;
232 | ONLY_ACTIVE_ARCH = YES;
233 | SDKROOT = iphoneos;
234 | };
235 | name = Debug;
236 | };
237 | 101789431B283EE1006C41B8 /* Release */ = {
238 | isa = XCBuildConfiguration;
239 | buildSettings = {
240 | ALWAYS_SEARCH_USER_PATHS = NO;
241 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
242 | CLANG_CXX_LIBRARY = "libc++";
243 | CLANG_ENABLE_MODULES = YES;
244 | CLANG_ENABLE_OBJC_ARC = YES;
245 | CLANG_WARN_BOOL_CONVERSION = YES;
246 | CLANG_WARN_CONSTANT_CONVERSION = YES;
247 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
248 | CLANG_WARN_EMPTY_BODY = YES;
249 | CLANG_WARN_ENUM_CONVERSION = YES;
250 | CLANG_WARN_INT_CONVERSION = YES;
251 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
252 | CLANG_WARN_UNREACHABLE_CODE = YES;
253 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
254 | COPY_PHASE_STRIP = NO;
255 | ENABLE_NS_ASSERTIONS = NO;
256 | ENABLE_STRICT_OBJC_MSGSEND = YES;
257 | GCC_C_LANGUAGE_STANDARD = gnu99;
258 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
259 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
260 | GCC_WARN_UNDECLARED_SELECTOR = YES;
261 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
262 | GCC_WARN_UNUSED_FUNCTION = YES;
263 | GCC_WARN_UNUSED_VARIABLE = YES;
264 | HEADER_SEARCH_PATHS = (
265 | "$(SRCROOT)/../node_modules/react-native/React/**",
266 | "$(inherited)",
267 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
268 | );
269 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
270 | MTL_ENABLE_DEBUG_INFO = NO;
271 | SDKROOT = iphoneos;
272 | VALIDATE_PRODUCT = YES;
273 | };
274 | name = Release;
275 | };
276 | 101789451B283EE1006C41B8 /* Debug */ = {
277 | isa = XCBuildConfiguration;
278 | buildSettings = {
279 | HEADER_SEARCH_PATHS = (
280 | "$(SRCROOT)/../../node_modules/react-native/React/**",
281 | "$(inherited)",
282 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
283 | "$(SRCROOT)/../../react-native/React/**",
284 | );
285 | OTHER_LDFLAGS = "-ObjC";
286 | PRODUCT_NAME = "$(TARGET_NAME)";
287 | SKIP_INSTALL = YES;
288 | };
289 | name = Debug;
290 | };
291 | 101789461B283EE1006C41B8 /* Release */ = {
292 | isa = XCBuildConfiguration;
293 | buildSettings = {
294 | HEADER_SEARCH_PATHS = (
295 | "$(SRCROOT)/../../node_modules/react-native/React/**",
296 | "$(inherited)",
297 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
298 | "$(SRCROOT)/../../react-native/React/**",
299 | );
300 | OTHER_LDFLAGS = "-ObjC";
301 | PRODUCT_NAME = "$(TARGET_NAME)";
302 | SKIP_INSTALL = YES;
303 | };
304 | name = Release;
305 | };
306 | /* End XCBuildConfiguration section */
307 |
308 | /* Begin XCConfigurationList section */
309 | 1017892B1B283EE1006C41B8 /* Build configuration list for PBXProject "RNAdvancedWebView" */ = {
310 | isa = XCConfigurationList;
311 | buildConfigurations = (
312 | 101789421B283EE1006C41B8 /* Debug */,
313 | 101789431B283EE1006C41B8 /* Release */,
314 | );
315 | defaultConfigurationIsVisible = 0;
316 | defaultConfigurationName = Release;
317 | };
318 | 101789441B283EE1006C41B8 /* Build configuration list for PBXNativeTarget "RNAdvancedWebView" */ = {
319 | isa = XCConfigurationList;
320 | buildConfigurations = (
321 | 101789451B283EE1006C41B8 /* Debug */,
322 | 101789461B283EE1006C41B8 /* Release */,
323 | );
324 | defaultConfigurationIsVisible = 0;
325 | defaultConfigurationName = Release;
326 | };
327 | /* End XCConfigurationList section */
328 | };
329 | rootObject = 101789281B283EE1006C41B8 /* Project object */;
330 | }
331 |
--------------------------------------------------------------------------------
/ios/RNAdvancedWebViewManager.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @interface RNAdvancedWebViewManager : RCTViewManager
4 |
5 | @end
6 |
--------------------------------------------------------------------------------
/ios/RNAdvancedWebViewManager.m:
--------------------------------------------------------------------------------
1 | #import "RNAdvancedWebViewManager.h"
2 |
3 | #import "RNAdvancedWebView.h"
4 | #import
5 | #import
6 | #import
7 | #import
8 | #import
9 |
10 | #import
11 |
12 | #import "RNCachingURLProtocol.h"
13 |
14 |
15 | @interface RNAdvancedWebViewManager ()
16 |
17 | @end
18 |
19 | @implementation RNAdvancedWebViewManager
20 | {
21 | NSConditionLock *_shouldStartLoadLock;
22 | BOOL _shouldStartLoad;
23 | }
24 |
25 | RCT_EXPORT_MODULE()
26 |
27 | - (UIView *)view
28 | {
29 | [NSURLProtocol registerClass:[RNCachingURLProtocol class]];
30 |
31 | RNAdvancedWebView *webView = [[RNAdvancedWebView alloc] initWithProcessPool:[[WKProcessPool alloc] init]];
32 | webView.delegate = self;
33 | return webView;
34 | }
35 |
36 | RCT_EXPORT_VIEW_PROPERTY(source, NSDictionary)
37 | RCT_REMAP_VIEW_PROPERTY(bounces, _webView.scrollView.bounces, BOOL)
38 | RCT_REMAP_VIEW_PROPERTY(pagingEnabled, _webView.scrollView.pagingEnabled, BOOL)
39 | RCT_REMAP_VIEW_PROPERTY(scrollEnabled, _webView.scrollView.scrollEnabled, BOOL)
40 | RCT_REMAP_VIEW_PROPERTY(allowsBackForwardNavigationGestures, _webView.allowsBackForwardNavigationGestures, BOOL)
41 | RCT_REMAP_VIEW_PROPERTY(userAgent, _webView.customUserAgent, NSString)
42 | RCT_EXPORT_VIEW_PROPERTY(injectedJavaScript, NSString)
43 | RCT_EXPORT_VIEW_PROPERTY(openNewWindowInWebView, BOOL)
44 | RCT_EXPORT_VIEW_PROPERTY(contentInsetAdjustmentBehavior, NSInteger)
45 | RCT_EXPORT_VIEW_PROPERTY(disableKeyboardAdjust, BOOL)
46 | RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets)
47 | RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL)
48 | RCT_EXPORT_VIEW_PROPERTY(onLoadingStart, RCTDirectEventBlock)
49 | RCT_EXPORT_VIEW_PROPERTY(onLoadingFinish, RCTDirectEventBlock)
50 | RCT_EXPORT_VIEW_PROPERTY(onLoadingError, RCTDirectEventBlock)
51 | RCT_EXPORT_VIEW_PROPERTY(onShouldStartLoadWithRequest, RCTDirectEventBlock)
52 | RCT_EXPORT_VIEW_PROPERTY(onProgress, RCTDirectEventBlock)
53 | RCT_EXPORT_VIEW_PROPERTY(onMessage, RCTDirectEventBlock)
54 |
55 | #pragma mark - added
56 |
57 | RCT_EXPORT_VIEW_PROPERTY(messagingEnabled, BOOL)
58 | RCT_EXPORT_VIEW_PROPERTY(hideAccessory, BOOL)
59 | RCT_EXPORT_VIEW_PROPERTY(keyboardDisplayRequiresUserAction, BOOL)
60 | RCT_EXPORT_VIEW_PROPERTY(validSchemes, NSArray)
61 |
62 | RCT_EXPORT_METHOD(goBack:(nonnull NSNumber *)reactTag)
63 | {
64 | [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) {
65 | RNAdvancedWebView *view = viewRegistry[reactTag];
66 | if (![view isKindOfClass:[RNAdvancedWebView class]]) {
67 | RCTLogError(@"Invalid view returned from registry, expecting RNAdvancedWebView, got: %@", view);
68 | } else {
69 | [view goBack];
70 | }
71 | }];
72 | }
73 |
74 | RCT_EXPORT_METHOD(goForward:(nonnull NSNumber *)reactTag)
75 | {
76 | [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) {
77 | RNAdvancedWebView *view = viewRegistry[reactTag];
78 | if (![view isKindOfClass:[RNAdvancedWebView class]]) {
79 | RCTLogError(@"Invalid view returned from registry, expecting RNAdvancedWebView, got: %@", view);
80 | } else {
81 | [view goForward];
82 | }
83 | }];
84 | }
85 |
86 | RCT_EXPORT_METHOD(canGoBack:(nonnull NSNumber *)reactTag
87 | resolver:(RCTPromiseResolveBlock)resolve
88 | rejecter:(RCTPromiseRejectBlock)reject)
89 | {
90 | [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) {
91 | RNAdvancedWebView *view = viewRegistry[reactTag];
92 | resolve([NSNumber numberWithBool:[view canGoBack]]);
93 | }];
94 | }
95 |
96 | RCT_EXPORT_METHOD(canGoForward:(nonnull NSNumber *)reactTag
97 | resolver:(RCTPromiseResolveBlock)resolve
98 | rejecter:(RCTPromiseRejectBlock)reject)
99 | {
100 | [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) {
101 | RNAdvancedWebView *view = viewRegistry[reactTag];
102 | resolve([NSNumber numberWithBool:[view canGoForward]]);
103 | }];
104 | }
105 |
106 | RCT_EXPORT_METHOD(reload:(nonnull NSNumber *)reactTag)
107 | {
108 | [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) {
109 | RNAdvancedWebView *view = viewRegistry[reactTag];
110 | if (![view isKindOfClass:[RNAdvancedWebView class]]) {
111 | RCTLogError(@"Invalid view returned from registry, expecting RNAdvancedWebView, got: %@", view);
112 | } else {
113 | [view reload];
114 | }
115 | }];
116 | }
117 |
118 | RCT_EXPORT_METHOD(stopLoading:(nonnull NSNumber *)reactTag)
119 | {
120 | [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) {
121 | RNAdvancedWebView *view = viewRegistry[reactTag];
122 | if (![view isKindOfClass:[RNAdvancedWebView class]]) {
123 | RCTLogError(@"Invalid view returned from registry, expecting RNAdvancedWebView, got: %@", view);
124 | } else {
125 | [view stopLoading];
126 | }
127 | }];
128 | }
129 |
130 | RCT_EXPORT_METHOD(evaluateJavaScript:(nonnull NSNumber *)reactTag
131 | js:(NSString *)js
132 | resolver:(RCTPromiseResolveBlock)resolve
133 | rejecter:(RCTPromiseRejectBlock)reject)
134 | {
135 | [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) {
136 | RNAdvancedWebView *view = viewRegistry[reactTag];
137 | if (![view isKindOfClass:[RNAdvancedWebView class]]) {
138 | RCTLogError(@"Invalid view returned from registry, expecting RNAdvancedWebView, got: %@", view);
139 | } else {
140 | [view evaluateJavaScript:js completionHandler:^(id result, NSError *error) {
141 | if (error) {
142 | reject(@"js_error", @"Error occurred while evaluating Javascript", error);
143 | } else {
144 | resolve(result);
145 | }
146 | }];
147 | }
148 | }];
149 | }
150 |
151 | #pragma mark - missing methods
152 |
153 | RCT_EXPORT_METHOD(postMessage:(nonnull NSNumber *)reactTag message:(NSString *)message)
154 | {
155 | [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) {
156 | RNAdvancedWebView *view = viewRegistry[reactTag];
157 | if (![view isKindOfClass:[RNAdvancedWebView class]]) {
158 | RCTLogError(@"Invalid view returned from registry, expecting RNAdvancedWebView, got: %@", view);
159 | } else {
160 | [view postMessage:message];
161 | }
162 | }];
163 | }
164 |
165 | RCT_EXPORT_METHOD(injectJavaScript:(nonnull NSNumber *)reactTag script:(NSString *)script)
166 | {
167 | [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) {
168 | RNAdvancedWebView *view = viewRegistry[reactTag];
169 | if (![view isKindOfClass:[RNAdvancedWebView class]]) {
170 | RCTLogError(@"Invalid view returned from registry, expecting RNAdvancedWebView, got: %@", view);
171 | } else {
172 | [view injectJavaScript:script];
173 | }
174 | }];
175 | }
176 |
177 | #pragma mark - Exported synchronous methods
178 |
179 | - (BOOL)webView:(__unused RNAdvancedWebView *)webView
180 | shouldStartLoadForRequest:(NSMutableDictionary *)request
181 | withCallback:(RCTDirectEventBlock)callback
182 | {
183 | _shouldStartLoadLock = [[NSConditionLock alloc] initWithCondition:arc4random()];
184 | _shouldStartLoad = YES;
185 | request[@"lockIdentifier"] = @(_shouldStartLoadLock.condition);
186 | callback(request);
187 |
188 | // Block the main thread for a maximum of 250ms until the JS thread returns
189 | if ([_shouldStartLoadLock lockWhenCondition:0 beforeDate:[NSDate dateWithTimeIntervalSinceNow:.25]]) {
190 | BOOL returnValue = _shouldStartLoad;
191 | [_shouldStartLoadLock unlock];
192 | _shouldStartLoadLock = nil;
193 | return returnValue;
194 | } else {
195 | RCTLogWarn(@"Did not receive response to shouldStartLoad in time, defaulting to YES");
196 | return YES;
197 | }
198 | }
199 |
200 | RCT_EXPORT_METHOD(startLoadWithResult:(BOOL)result lockIdentifier:(NSInteger)lockIdentifier)
201 | {
202 | if ([_shouldStartLoadLock tryLockWhenCondition:lockIdentifier]) {
203 | _shouldStartLoad = result;
204 | [_shouldStartLoadLock unlockWithCondition:0];
205 | } else {
206 | RCTLogWarn(@"startLoadWithResult invoked with invalid lockIdentifier: "
207 | "got %zd, expected %zd", lockIdentifier, _shouldStartLoadLock.condition);
208 | }
209 | }
210 |
211 | @end
212 |
213 |
--------------------------------------------------------------------------------
/ios/RNCachingURLProtocol/NSString+Sha1.h:
--------------------------------------------------------------------------------
1 |
2 | #import
3 | #import
4 |
5 | /**
6 | * This extension contains several a helper
7 | * for creating a sha1 hash from instances of NSString
8 | */
9 | @interface NSString (Sha1)
10 |
11 | /**
12 | * Creates a SHA1 (hash) representation of NSString.
13 | *
14 | * @return NSString
15 | */
16 | - (NSString *)sha1;
17 |
18 |
19 | @end
20 |
--------------------------------------------------------------------------------
/ios/RNCachingURLProtocol/NSString+Sha1.m:
--------------------------------------------------------------------------------
1 |
2 | #import "NSString+Sha1.h"
3 |
4 | @implementation NSString (Sha1)
5 |
6 | - (NSString *)sha1
7 | {
8 | // see http://www.makebetterthings.com/iphone/how-to-get-md5-and-sha1-in-objective-c-ios-sdk/
9 | NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding];
10 | uint8_t digest[CC_SHA1_DIGEST_LENGTH];
11 |
12 | CC_SHA1(data.bytes, (CC_LONG)data.length, digest);
13 |
14 | NSMutableString *output = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
15 |
16 | for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) {
17 | [output appendFormat:@"%02x", digest[i]];
18 | }
19 |
20 | return output;
21 | }
22 |
23 |
24 | @end
25 |
--------------------------------------------------------------------------------
/ios/RNCachingURLProtocol/README.md:
--------------------------------------------------------------------------------
1 | # BACKGROUND
2 |
3 | RNCachingURLProtocol is a simple shim for the HTTP protocol (that’s not
4 | nearly as scary as it sounds). Anytime a URL is downloaded, the response is
5 | cached to disk. Anytime a URL is requested, if we’re online then things
6 | proceed normally. If we’re offline, then we retrieve the cached version.
7 |
8 | The point of RNCachingURLProtocol is mostly to demonstrate how this is done.
9 | The current implementation is extremely simple. In particular, it doesn’t
10 | worry about cleaning up the cache. The assumption is that you’re caching just
11 | a few simple things, like your “Latest News” page (which was the problem I
12 | was solving). It caches all HTTP traffic, so without some modifications, it’s
13 | not appropriate for an app that has a lot of HTTP connections (see
14 | MKNetworkKit for that). But if you need to cache some URLs and not others,
15 | that is easy to implement.
16 |
17 | You should also look at [AFCache](https://github.com/artifacts/AFCache) for a
18 | more powerful caching engine that is currently integrating the ideas of
19 | RNCachingURLProtocol.
20 |
21 | # USAGE
22 |
23 | 1. To build, you will need the Reachability code from Apple (included). That requires that you link with
24 | `SystemConfiguration.framework`.
25 |
26 | 2. At some point early in the program (usually `application:didFinishLaunchingWithOptions:`),
27 | call the following:
28 |
29 | `[NSURLProtocol registerClass:[RNCachingURLProtocol class]];`
30 |
31 | 3. There is no step 3.
32 |
33 | For more details see
34 | [Drop-in offline caching for UIWebView (and NSURLProtocol)](http://robnapier.net/blog/offline-uiwebview-nsurlprotocol-588).
35 |
36 | # EXAMPLE
37 |
38 | See the CachedWebView project for example usage.
39 |
40 | # LICENSE
41 |
42 | This code is licensed under the MIT License:
43 |
44 | Permission is hereby granted, free of charge, to any person obtaining a
45 | copy of this software and associated documentation files (the "Software"),
46 | to deal in the Software without restriction, including without limitation
47 | the rights to use, copy, modify, merge, publish, distribute, sublicense,
48 | and/or sell copies of the Software, and to permit persons to whom the
49 | Software is furnished to do so, subject to the following conditions:
50 |
51 | The above copyright notice and this permission notice shall be included in
52 | all copies or substantial portions of the Software.
53 |
54 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
55 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
56 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
57 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
58 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
59 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
60 | DEALINGS IN THE SOFTWARE.
61 |
--------------------------------------------------------------------------------
/ios/RNCachingURLProtocol/RNCachingURLProtocol.h:
--------------------------------------------------------------------------------
1 | //
2 | // RNCachingURLProtocol.h
3 | //
4 | // Created by Robert Napier on 1/10/12.
5 | // Copyright (c) 2012 Rob Napier. All rights reserved.
6 | //
7 | // This code is licensed under the MIT License:
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a
10 | // copy of this software and associated documentation files (the "Software"),
11 | // to deal in the Software without restriction, including without limitation
12 | // the rights to use, copy, modify, merge, publish, distribute, sublicense,
13 | // and/or sell copies of the Software, and to permit persons to whom the
14 | // Software is furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 | // DEALINGS IN THE SOFTWARE.
26 | //
27 |
28 | // RNCachingURLProtocol is a simple shim for the HTTP protocol (that’s not
29 | // nearly as scary as it sounds). Anytime a URL is download, the response is
30 | // cached to disk. Anytime a URL is requested, if we’re online then things
31 | // proceed normally. If we’re offline, then we retrieve the cached version.
32 | //
33 | // The point of RNCachingURLProtocol is mostly to demonstrate how this is done.
34 | // The current implementation is extremely simple. In particular, it doesn’t
35 | // worry about cleaning up the cache. The assumption is that you’re caching just
36 | // a few simple things, like your “Latest News” page (which was the problem I
37 | // was solving). It caches all HTTP traffic, so without some modifications, it’s
38 | // not appropriate for an app that has a lot of HTTP connections (see
39 | // MKNetworkKit for that). But if you need to cache some URLs and not others,
40 | // that is easy to implement.
41 | //
42 | // You should also look at [AFCache](https://github.com/artifacts/AFCache) for a
43 | // more powerful caching engine that is currently integrating the ideas of
44 | // RNCachingURLProtocol.
45 | //
46 | // A quick rundown of how to use it:
47 | //
48 | // 1. To build, you will need the Reachability code from Apple (included). That requires that you link with
49 | // `SystemConfiguration.framework`.
50 | //
51 | // 2. At some point early in the program (application:didFinishLaunchingWithOptions:),
52 | // call the following:
53 | //
54 | // `[NSURLProtocol registerClass:[RNCachingURLProtocol class]];`
55 | //
56 | // 3. There is no step 3.
57 | //
58 | // For more details see
59 | // [Drop-in offline caching for UIWebView (and NSURLProtocol)](http://robnapier.net/blog/offline-uiwebview-nsurlprotocol-588).
60 |
61 | #import
62 |
63 | @interface RNCachingURLProtocol : NSURLProtocol
64 |
65 | + (NSSet *)supportedSchemes;
66 | + (void)setSupportedSchemes:(NSSet *)supportedSchemes;
67 |
68 | - (NSString *)cachePathForRequest:(NSURLRequest *)aRequest;
69 | - (BOOL) useCache;
70 |
71 | @end
72 |
--------------------------------------------------------------------------------
/ios/RNCachingURLProtocol/RNCachingURLProtocol.m:
--------------------------------------------------------------------------------
1 | //
2 | // RNCachingURLProtocol.m
3 | //
4 | // Created by Robert Napier on 1/10/12.
5 | // Copyright (c) 2012 Rob Napier.
6 | //
7 | // This code is licensed under the MIT License:
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a
10 | // copy of this software and associated documentation files (the "Software"),
11 | // to deal in the Software without restriction, including without limitation
12 | // the rights to use, copy, modify, merge, publish, distribute, sublicense,
13 | // and/or sell copies of the Software, and to permit persons to whom the
14 | // Software is furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 | // DEALINGS IN THE SOFTWARE.
26 | //
27 |
28 | #import "RNCachingURLProtocol.h"
29 | #import "Reachability.h"
30 | #import "NSString+Sha1.h"
31 |
32 | #define WORKAROUND_MUTABLE_COPY_LEAK 1
33 |
34 | #if WORKAROUND_MUTABLE_COPY_LEAK
35 | // required to workaround http://openradar.appspot.com/11596316
36 | @interface NSURLRequest(MutableCopyWorkaround)
37 |
38 | - (id) mutableCopyWorkaround;
39 |
40 | @end
41 | #endif
42 |
43 | @interface RNCachedData : NSObject
44 | @property (nonatomic, readwrite, strong) NSData *data;
45 | @property (nonatomic, readwrite, strong) NSURLResponse *response;
46 | @property (nonatomic, readwrite, strong) NSURLRequest *redirectRequest;
47 | @end
48 |
49 | static NSString *RNCachingURLHeader = @"X-RNCache";
50 |
51 | @interface RNCachingURLProtocol () // iOS5-only
52 | @property (nonatomic, readwrite, strong) NSURLConnection *connection;
53 | @property (nonatomic, readwrite, strong) NSMutableData *data;
54 | @property (nonatomic, readwrite, strong) NSURLResponse *response;
55 | - (void)appendData:(NSData *)newData;
56 | @end
57 |
58 | static NSObject *RNCachingSupportedSchemesMonitor;
59 | static NSSet *RNCachingSupportedSchemes;
60 |
61 | @implementation RNCachingURLProtocol
62 | @synthesize connection = connection_;
63 | @synthesize data = data_;
64 | @synthesize response = response_;
65 |
66 | + (void)initialize
67 | {
68 | if (self == [RNCachingURLProtocol class])
69 | {
70 | static dispatch_once_t onceToken;
71 | dispatch_once(&onceToken, ^{
72 | RNCachingSupportedSchemesMonitor = [NSObject new];
73 | });
74 |
75 | [self setSupportedSchemes:[NSSet setWithObject:@"http"]];
76 | }
77 | }
78 |
79 | + (BOOL)canInitWithRequest:(NSURLRequest *)request
80 | {
81 | // only handle http requests we haven't marked with our header.
82 | if ([[self supportedSchemes] containsObject:[[request URL] scheme]] &&
83 | ([request valueForHTTPHeaderField:RNCachingURLHeader] == nil))
84 | {
85 | return YES;
86 | }
87 | return NO;
88 | }
89 |
90 | + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
91 | {
92 | return request;
93 | }
94 |
95 | - (NSString *)cachePathForRequest:(NSURLRequest *)aRequest
96 | {
97 | // This stores in the Caches directory, which can be deleted when space is low, but we only use it for offline access
98 | NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
99 | NSString *fileName = [[[aRequest URL] absoluteString] sha1];
100 |
101 | return [cachesPath stringByAppendingPathComponent:fileName];
102 | }
103 |
104 | - (void)startLoading
105 | {
106 | if (![self useCache]) {
107 | NSMutableURLRequest *connectionRequest =
108 | #if WORKAROUND_MUTABLE_COPY_LEAK
109 | [[self request] mutableCopyWorkaround];
110 | #else
111 | [[self request] mutableCopy];
112 | #endif
113 | // we need to mark this request with our header so we know not to handle it in +[NSURLProtocol canInitWithRequest:].
114 | [connectionRequest setValue:@"" forHTTPHeaderField:RNCachingURLHeader];
115 | NSURLConnection *connection = [NSURLConnection connectionWithRequest:connectionRequest
116 | delegate:self];
117 | [self setConnection:connection];
118 | }
119 | else {
120 | RNCachedData *cache = [NSKeyedUnarchiver unarchiveObjectWithFile:[self cachePathForRequest:[self request]]];
121 | if (cache) {
122 | NSData *data = [cache data];
123 | NSURLResponse *response = [cache response];
124 | NSURLRequest *redirectRequest = [cache redirectRequest];
125 | if (redirectRequest) {
126 | [[self client] URLProtocol:self wasRedirectedToRequest:redirectRequest redirectResponse:response];
127 | } else {
128 |
129 | [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; // we handle caching ourselves.
130 | [[self client] URLProtocol:self didLoadData:data];
131 | [[self client] URLProtocolDidFinishLoading:self];
132 | }
133 | }
134 | else {
135 | [[self client] URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCannotConnectToHost userInfo:nil]];
136 | }
137 | }
138 | }
139 |
140 | - (void)stopLoading
141 | {
142 | [[self connection] cancel];
143 | }
144 |
145 | // NSURLConnection delegates (generally we pass these on to our client)
146 |
147 | - (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response
148 | {
149 | // Thanks to Nick Dowell https://gist.github.com/1885821
150 | if (response != nil) {
151 | NSMutableURLRequest *redirectableRequest =
152 | #if WORKAROUND_MUTABLE_COPY_LEAK
153 | [request mutableCopyWorkaround];
154 | #else
155 | [request mutableCopy];
156 | #endif
157 | // We need to remove our header so we know to handle this request and cache it.
158 | // There are 3 requests in flight: the outside request, which we handled, the internal request,
159 | // which we marked with our header, and the redirectableRequest, which we're modifying here.
160 | // The redirectable request will cause a new outside request from the NSURLProtocolClient, which
161 | // must not be marked with our header.
162 | [redirectableRequest setValue:nil forHTTPHeaderField:RNCachingURLHeader];
163 |
164 | NSString *cachePath = [self cachePathForRequest:[self request]];
165 | RNCachedData *cache = [RNCachedData new];
166 | [cache setResponse:response];
167 | [cache setData:[self data]];
168 | [cache setRedirectRequest:redirectableRequest];
169 | [NSKeyedArchiver archiveRootObject:cache toFile:cachePath];
170 | [[self client] URLProtocol:self wasRedirectedToRequest:redirectableRequest redirectResponse:response];
171 | return redirectableRequest;
172 | } else {
173 | return request;
174 | }
175 | }
176 |
177 | - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
178 | {
179 | [[self client] URLProtocol:self didLoadData:data];
180 | [self appendData:data];
181 | }
182 |
183 | - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
184 | {
185 | [[self client] URLProtocol:self didFailWithError:error];
186 | [self setConnection:nil];
187 | [self setData:nil];
188 | [self setResponse:nil];
189 | }
190 |
191 | - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
192 | {
193 | [self setResponse:response];
194 | [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; // We cache ourselves.
195 | }
196 |
197 | - (void)connectionDidFinishLoading:(NSURLConnection *)connection
198 | {
199 | [[self client] URLProtocolDidFinishLoading:self];
200 |
201 | NSString *cachePath = [self cachePathForRequest:[self request]];
202 | RNCachedData *cache = [RNCachedData new];
203 | [cache setResponse:[self response]];
204 | [cache setData:[self data]];
205 | [NSKeyedArchiver archiveRootObject:cache toFile:cachePath];
206 |
207 | [self setConnection:nil];
208 | [self setData:nil];
209 | [self setResponse:nil];
210 | }
211 |
212 | - (BOOL) useCache
213 | {
214 | BOOL reachable = (BOOL) [[Reachability reachabilityWithHostName:[[[self request] URL] host]] currentReachabilityStatus] != NotReachable;
215 | return !reachable;
216 | }
217 |
218 | - (void)appendData:(NSData *)newData
219 | {
220 | if ([self data] == nil) {
221 | [self setData:[newData mutableCopy]];
222 | }
223 | else {
224 | [[self data] appendData:newData];
225 | }
226 | }
227 |
228 | + (NSSet *)supportedSchemes {
229 | NSSet *supportedSchemes;
230 | @synchronized(RNCachingSupportedSchemesMonitor)
231 | {
232 | supportedSchemes = RNCachingSupportedSchemes;
233 | }
234 | return supportedSchemes;
235 | }
236 |
237 | + (void)setSupportedSchemes:(NSSet *)supportedSchemes
238 | {
239 | @synchronized(RNCachingSupportedSchemesMonitor)
240 | {
241 | RNCachingSupportedSchemes = supportedSchemes;
242 | }
243 | }
244 |
245 | @end
246 |
247 | static NSString *const kDataKey = @"data";
248 | static NSString *const kResponseKey = @"response";
249 | static NSString *const kRedirectRequestKey = @"redirectRequest";
250 |
251 | @implementation RNCachedData
252 | @synthesize data = data_;
253 | @synthesize response = response_;
254 | @synthesize redirectRequest = redirectRequest_;
255 |
256 | - (void)encodeWithCoder:(NSCoder *)aCoder
257 | {
258 | [aCoder encodeObject:[self data] forKey:kDataKey];
259 | [aCoder encodeObject:[self response] forKey:kResponseKey];
260 | [aCoder encodeObject:[self redirectRequest] forKey:kRedirectRequestKey];
261 | }
262 |
263 | - (id)initWithCoder:(NSCoder *)aDecoder
264 | {
265 | self = [super init];
266 | if (self != nil) {
267 | [self setData:[aDecoder decodeObjectForKey:kDataKey]];
268 | [self setResponse:[aDecoder decodeObjectForKey:kResponseKey]];
269 | [self setRedirectRequest:[aDecoder decodeObjectForKey:kRedirectRequestKey]];
270 | }
271 |
272 | return self;
273 | }
274 |
275 | @end
276 |
277 | #if WORKAROUND_MUTABLE_COPY_LEAK
278 | @implementation NSURLRequest(MutableCopyWorkaround)
279 |
280 | - (id) mutableCopyWorkaround {
281 | NSMutableURLRequest *mutableURLRequest = [[NSMutableURLRequest alloc] initWithURL:[self URL]
282 | cachePolicy:[self cachePolicy]
283 | timeoutInterval:[self timeoutInterval]];
284 | [mutableURLRequest setAllHTTPHeaderFields:[self allHTTPHeaderFields]];
285 | if ([self HTTPBodyStream]) {
286 | [mutableURLRequest setHTTPBodyStream:[self HTTPBodyStream]];
287 | } else {
288 | [mutableURLRequest setHTTPBody:[self HTTPBody]];
289 | }
290 | [mutableURLRequest setHTTPMethod:[self HTTPMethod]];
291 |
292 | return mutableURLRequest;
293 | }
294 |
295 | @end
296 | #endif
297 |
--------------------------------------------------------------------------------
/ios/RNCachingURLProtocol/Reachability.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | Basic demonstration of how to use the SystemConfiguration Reachablity APIs.
7 | */
8 |
9 | #import
10 | #import
11 | #import
12 |
13 |
14 | typedef enum : NSInteger {
15 | NotReachable = 0,
16 | ReachableViaWiFi,
17 | ReachableViaWWAN
18 | } NetworkStatus;
19 |
20 | #pragma mark IPv6 Support
21 | //Reachability fully support IPv6. For full details, see ReadMe.md.
22 |
23 |
24 | extern NSString *kReachabilityChangedNotification;
25 |
26 |
27 | @interface Reachability : NSObject
28 |
29 | /*!
30 | * Use to check the reachability of a given host name.
31 | */
32 | + (instancetype)reachabilityWithHostName:(NSString *)hostName;
33 |
34 | /*!
35 | * Use to check the reachability of a given IP address.
36 | */
37 | + (instancetype)reachabilityWithAddress:(const struct sockaddr *)hostAddress;
38 |
39 | /*!
40 | * Checks whether the default route is available. Should be used by applications that do not connect to a particular host.
41 | */
42 | + (instancetype)reachabilityForInternetConnection;
43 |
44 |
45 | #pragma mark reachabilityForLocalWiFi
46 | //reachabilityForLocalWiFi has been removed from the sample. See ReadMe.md for more information.
47 | //+ (instancetype)reachabilityForLocalWiFi;
48 |
49 | /*!
50 | * Start listening for reachability notifications on the current run loop.
51 | */
52 | - (BOOL)startNotifier;
53 | - (void)stopNotifier;
54 |
55 | - (NetworkStatus)currentReachabilityStatus;
56 |
57 | /*!
58 | * WWAN may be available, but not active until a connection has been established. WiFi may require a connection for VPN on Demand.
59 | */
60 | - (BOOL)connectionRequired;
61 |
62 | @end
63 |
--------------------------------------------------------------------------------
/ios/RNCachingURLProtocol/Reachability.m:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | Basic demonstration of how to use the SystemConfiguration Reachablity APIs.
7 | */
8 |
9 | #import
10 | #import
11 | #import
12 | #import
13 | #import
14 |
15 | #import
16 |
17 | #import "Reachability.h"
18 |
19 | #pragma mark IPv6 Support
20 | //Reachability fully support IPv6. For full details, see ReadMe.md.
21 |
22 |
23 | NSString *kReachabilityChangedNotification = @"kNetworkReachabilityChangedNotification";
24 |
25 |
26 | #pragma mark - Supporting functions
27 |
28 | #define kShouldPrintReachabilityFlags 1
29 |
30 | static void PrintReachabilityFlags(SCNetworkReachabilityFlags flags, const char* comment)
31 | {
32 | #if kShouldPrintReachabilityFlags
33 |
34 | NSLog(@"Reachability Flag Status: %c%c %c%c%c%c%c%c%c %s\n",
35 | (flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-',
36 | (flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-',
37 |
38 | (flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-',
39 | (flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-',
40 | (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-',
41 | (flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-',
42 | (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-',
43 | (flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-',
44 | (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-',
45 | comment
46 | );
47 | #endif
48 | }
49 |
50 |
51 | static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info)
52 | {
53 | #pragma unused (target, flags)
54 | NSCAssert(info != NULL, @"info was NULL in ReachabilityCallback");
55 | NSCAssert([(__bridge NSObject*) info isKindOfClass: [Reachability class]], @"info was wrong class in ReachabilityCallback");
56 |
57 | Reachability* noteObject = (__bridge Reachability *)info;
58 | // Post a notification to notify the client that the network reachability changed.
59 | [[NSNotificationCenter defaultCenter] postNotificationName: kReachabilityChangedNotification object: noteObject];
60 | }
61 |
62 |
63 | #pragma mark - Reachability implementation
64 |
65 | @implementation Reachability
66 | {
67 | SCNetworkReachabilityRef _reachabilityRef;
68 | }
69 |
70 | + (instancetype)reachabilityWithHostName:(NSString *)hostName
71 | {
72 | Reachability* returnValue = NULL;
73 | SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL, [hostName UTF8String]);
74 | if (reachability != NULL)
75 | {
76 | returnValue= [[self alloc] init];
77 | if (returnValue != NULL)
78 | {
79 | returnValue->_reachabilityRef = reachability;
80 | }
81 | else {
82 | CFRelease(reachability);
83 | }
84 | }
85 | return returnValue;
86 | }
87 |
88 |
89 | + (instancetype)reachabilityWithAddress:(const struct sockaddr *)hostAddress
90 | {
91 | SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, hostAddress);
92 |
93 | Reachability* returnValue = NULL;
94 |
95 | if (reachability != NULL)
96 | {
97 | returnValue = [[self alloc] init];
98 | if (returnValue != NULL)
99 | {
100 | returnValue->_reachabilityRef = reachability;
101 | }
102 | else {
103 | CFRelease(reachability);
104 | }
105 | }
106 | return returnValue;
107 | }
108 |
109 |
110 | + (instancetype)reachabilityForInternetConnection
111 | {
112 | struct sockaddr_in zeroAddress;
113 | bzero(&zeroAddress, sizeof(zeroAddress));
114 | zeroAddress.sin_len = sizeof(zeroAddress);
115 | zeroAddress.sin_family = AF_INET;
116 |
117 | return [self reachabilityWithAddress: (const struct sockaddr *) &zeroAddress];
118 | }
119 |
120 | #pragma mark reachabilityForLocalWiFi
121 | //reachabilityForLocalWiFi has been removed from the sample. See ReadMe.md for more information.
122 | //+ (instancetype)reachabilityForLocalWiFi
123 |
124 |
125 |
126 | #pragma mark - Start and stop notifier
127 |
128 | - (BOOL)startNotifier
129 | {
130 | BOOL returnValue = NO;
131 | SCNetworkReachabilityContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
132 |
133 | if (SCNetworkReachabilitySetCallback(_reachabilityRef, ReachabilityCallback, &context))
134 | {
135 | if (SCNetworkReachabilityScheduleWithRunLoop(_reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode))
136 | {
137 | returnValue = YES;
138 | }
139 | }
140 |
141 | return returnValue;
142 | }
143 |
144 |
145 | - (void)stopNotifier
146 | {
147 | if (_reachabilityRef != NULL)
148 | {
149 | SCNetworkReachabilityUnscheduleFromRunLoop(_reachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
150 | }
151 | }
152 |
153 |
154 | - (void)dealloc
155 | {
156 | [self stopNotifier];
157 | if (_reachabilityRef != NULL)
158 | {
159 | CFRelease(_reachabilityRef);
160 | }
161 | }
162 |
163 |
164 | #pragma mark - Network Flag Handling
165 |
166 | - (NetworkStatus)networkStatusForFlags:(SCNetworkReachabilityFlags)flags
167 | {
168 | PrintReachabilityFlags(flags, "networkStatusForFlags");
169 | if ((flags & kSCNetworkReachabilityFlagsReachable) == 0)
170 | {
171 | // The target host is not reachable.
172 | return NotReachable;
173 | }
174 |
175 | NetworkStatus returnValue = NotReachable;
176 |
177 | if ((flags & kSCNetworkReachabilityFlagsConnectionRequired) == 0)
178 | {
179 | /*
180 | If the target host is reachable and no connection is required then we'll assume (for now) that you're on Wi-Fi...
181 | */
182 | returnValue = ReachableViaWiFi;
183 | }
184 |
185 | if ((((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) ||
186 | (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0))
187 | {
188 | /*
189 | ... and the connection is on-demand (or on-traffic) if the calling application is using the CFSocketStream or higher APIs...
190 | */
191 |
192 | if ((flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0)
193 | {
194 | /*
195 | ... and no [user] intervention is needed...
196 | */
197 | returnValue = ReachableViaWiFi;
198 | }
199 | }
200 |
201 | if ((flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN)
202 | {
203 | /*
204 | ... but WWAN connections are OK if the calling application is using the CFNetwork APIs.
205 | */
206 | returnValue = ReachableViaWWAN;
207 | }
208 |
209 | return returnValue;
210 | }
211 |
212 |
213 | - (BOOL)connectionRequired
214 | {
215 | NSAssert(_reachabilityRef != NULL, @"connectionRequired called with NULL reachabilityRef");
216 | SCNetworkReachabilityFlags flags;
217 |
218 | if (SCNetworkReachabilityGetFlags(_reachabilityRef, &flags))
219 | {
220 | return (flags & kSCNetworkReachabilityFlagsConnectionRequired);
221 | }
222 |
223 | return NO;
224 | }
225 |
226 |
227 | - (NetworkStatus)currentReachabilityStatus
228 | {
229 | NSAssert(_reachabilityRef != NULL, @"currentNetworkStatus called with NULL SCNetworkReachabilityRef");
230 | NetworkStatus returnValue = NotReachable;
231 | SCNetworkReachabilityFlags flags;
232 |
233 | if (SCNetworkReachabilityGetFlags(_reachabilityRef, &flags))
234 | {
235 | returnValue = [self networkStatusForFlags:flags];
236 | }
237 |
238 | return returnValue;
239 | }
240 |
241 |
242 | @end
243 |
--------------------------------------------------------------------------------
/ios/UIScrollView+adjustedContentInset.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIScrollView+adjustedContentInset.h
3 | // RNAdvancedWebView
4 | //
5 | // Created by Bell Zhong on 2018/5/3.
6 | // Copyright © 2018年 shimo. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface UIScrollView (adjustedContentInset)
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/ios/UIScrollView+adjustedContentInset.m:
--------------------------------------------------------------------------------
1 | //
2 | // UIScrollView+adjustedContentInset.m
3 | // RNAdvancedWebView
4 | //
5 | // Created by Bell Zhong on 2018/5/3.
6 | // Copyright © 2018年 shimo. All rights reserved.
7 | //
8 |
9 | #import "UIScrollView+adjustedContentInset.h"
10 | #import
11 |
12 | /**
13 | fix: `_webView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;` do not work
14 | */
15 | @implementation UIScrollView (adjustedContentInset)
16 |
17 | + (void)load {
18 | static dispatch_once_t onceToken;
19 | dispatch_once(&onceToken, ^{
20 | if (@available(iOS 11.0, *)) {
21 | Method originMethod = class_getInstanceMethod(self, @selector(adjustedContentInset));
22 | Method presentMethod = class_getInstanceMethod(self, @selector(shm_adjustedContentInset));
23 | method_exchangeImplementations(originMethod, presentMethod);
24 | }
25 | });
26 | }
27 |
28 | - (UIEdgeInsets)shm_adjustedContentInset {
29 | if (@available(iOS 11.0, *)) {
30 | if (self.contentInsetAdjustmentBehavior == UIScrollViewContentInsetAdjustmentNever && [self isKindOfClass:NSClassFromString(@"WKScrollView")]) {
31 | return UIEdgeInsetsZero;
32 | }
33 | }
34 | return [self shm_adjustedContentInset];
35 | }
36 |
37 | @end
38 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-offline-cache-webview",
3 | "version": "0.1.1",
4 | "description": "Offline cache enabled WebView",
5 | "repository": {
6 | "type": "git",
7 | "url": "git+https://github.com/zhangtaii/react-native-offline-cache-webview.git"
8 | },
9 | "license": "MIT",
10 | "main": "./WebView",
11 | "keywords": [
12 | "react-component",
13 | "react-native",
14 | "webview",
15 | "cookie",
16 | "jsbridge",
17 | "ios",
18 | "android",
19 | "offline",
20 | "cache",
21 | "website"
22 | ],
23 | "scripts": {
24 | "lint": "eslint ./"
25 | },
26 | "devDependencies": {
27 | "babel-eslint": "^6.1.2",
28 | "eslint": "^2.13.1",
29 | "eslint-plugin-react": "^4.3.0"
30 | },
31 | "peerDependencies": {
32 | "react-native": ">=0.46.0",
33 | "react": "16.0.0-alpha.12",
34 | "prop-types": "^15.5.8"
35 | },
36 | "bugs": {
37 | "url": "https://github.com/zhangtaii/react-native-offline-cache-webview/issues"
38 | },
39 | "homepage": "https://github.com/zhangtaii/react-native-offline-cache-webview#readme",
40 | "directories": {
41 | "example": "example"
42 | },
43 | "author": "zhangtaii"
44 | }
45 |
--------------------------------------------------------------------------------