[NEW] Support client certificates for SSL (two-way authentication) (Android) (#2624)
* wip: Android SSL Pinning * Use own SSLPinningModule * wip: Use Rocket.Chat own react-native * wip: Fresco Images using custom OkHttpClient * wip: react-native-webview onReceivedClientCertRequest * feat: Save Images of a SSL Pinning protected server * chore: SSLPinning package as a interface to iOS & Android implementations * chore: update glide * feat: load images under a client ssl certificate protected server * chore: remove patch * feat: Audio & Video under a SSL Client protected server * fix: Unpin certificate when change server * feat: Fast Image as a patch * chore: update fast-image * Fix merge Co-authored-by: Diego Mello <diegolmello@gmail.com>
This commit is contained in:
parent
9c3be7d3af
commit
155fc04aca
|
@ -21,6 +21,7 @@ import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPl
|
||||||
import com.facebook.react.ReactInstanceManager;
|
import com.facebook.react.ReactInstanceManager;
|
||||||
import com.facebook.react.bridge.ReactContext;
|
import com.facebook.react.bridge.ReactContext;
|
||||||
import com.facebook.react.modules.network.NetworkingModule;
|
import com.facebook.react.modules.network.NetworkingModule;
|
||||||
|
import com.facebook.react.modules.network.CustomClientBuilder;
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
public class ReactNativeFlipper {
|
public class ReactNativeFlipper {
|
||||||
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
|
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
|
||||||
|
@ -33,7 +34,7 @@ public class ReactNativeFlipper {
|
||||||
client.addPlugin(CrashReporterPlugin.getInstance());
|
client.addPlugin(CrashReporterPlugin.getInstance());
|
||||||
NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
|
NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
|
||||||
NetworkingModule.setCustomClientBuilder(
|
NetworkingModule.setCustomClientBuilder(
|
||||||
new NetworkingModule.CustomClientBuilder() {
|
new CustomClientBuilder() {
|
||||||
@Override
|
@Override
|
||||||
public void apply(OkHttpClient.Builder builder) {
|
public void apply(OkHttpClient.Builder builder) {
|
||||||
builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));
|
builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));
|
||||||
|
|
|
@ -12,8 +12,8 @@
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:theme="@style/AppTheme"
|
android:theme="@style/AppTheme"
|
||||||
android:networkSecurityConfig="@xml/network_security_config"
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
android:allowBackup="false"
|
|
||||||
android:requestLegacyExternalStorage="true"
|
android:requestLegacyExternalStorage="true"
|
||||||
|
android:allowBackup="false"
|
||||||
tools:replace="android:allowBackup"
|
tools:replace="android:allowBackup"
|
||||||
>
|
>
|
||||||
<activity
|
<activity
|
||||||
|
|
|
@ -38,6 +38,7 @@ public class MainApplication extends Application implements ReactApplication {
|
||||||
packages.add(new KeyboardInputPackage(MainApplication.this));
|
packages.add(new KeyboardInputPackage(MainApplication.this));
|
||||||
packages.add(new WatermelonDBPackage());
|
packages.add(new WatermelonDBPackage());
|
||||||
packages.add(new RNCViewPagerPackage());
|
packages.add(new RNCViewPagerPackage());
|
||||||
|
packages.add(new SSLPinningPackage());
|
||||||
List<ReactPackage> unimodules = Arrays.<ReactPackage>asList(
|
List<ReactPackage> unimodules = Arrays.<ReactPackage>asList(
|
||||||
new ModuleRegistryAdapter(mModuleRegistryProvider)
|
new ModuleRegistryAdapter(mModuleRegistryProvider)
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,193 @@
|
||||||
|
package chat.rocket.reactnative;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
|
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||||
|
import com.facebook.react.modules.network.NetworkingModule;
|
||||||
|
import com.facebook.react.modules.network.CustomClientBuilder;
|
||||||
|
import com.facebook.react.modules.network.ReactCookieJarContainer;
|
||||||
|
import com.facebook.react.modules.websocket.WebSocketModule;
|
||||||
|
import com.facebook.react.modules.fresco.ReactOkHttpNetworkFetcher;
|
||||||
|
import com.facebook.react.bridge.ReactMethod;
|
||||||
|
import com.facebook.react.bridge.Promise;
|
||||||
|
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import javax.net.ssl.X509ExtendedKeyManager;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import java.lang.InterruptedException;
|
||||||
|
import android.app.Activity;
|
||||||
|
import javax.net.ssl.KeyManager;
|
||||||
|
import android.security.KeyChain;
|
||||||
|
import android.security.KeyChainAliasCallback;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import com.RNFetchBlob.RNFetchBlob;
|
||||||
|
|
||||||
|
import com.reactnativecommunity.webview.RNCWebViewManager;
|
||||||
|
|
||||||
|
import com.dylanvann.fastimage.FastImageOkHttpUrlLoader;
|
||||||
|
|
||||||
|
import expo.modules.av.player.datasource.SharedCookiesDataSourceFactory;
|
||||||
|
|
||||||
|
public class SSLPinningModule extends ReactContextBaseJavaModule implements KeyChainAliasCallback {
|
||||||
|
|
||||||
|
private Promise promise;
|
||||||
|
private static String alias;
|
||||||
|
private static ReactApplicationContext reactContext;
|
||||||
|
|
||||||
|
public SSLPinningModule(ReactApplicationContext reactContext) {
|
||||||
|
super(reactContext);
|
||||||
|
this.reactContext = reactContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CustomClient implements CustomClientBuilder {
|
||||||
|
@Override
|
||||||
|
public void apply(OkHttpClient.Builder builder) {
|
||||||
|
if (alias != null) {
|
||||||
|
SSLSocketFactory sslSocketFactory = getSSLFactory(alias);
|
||||||
|
if (sslSocketFactory != null) {
|
||||||
|
builder.sslSocketFactory(sslSocketFactory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected OkHttpClient getOkHttpClient() {
|
||||||
|
OkHttpClient.Builder builder = new OkHttpClient.Builder()
|
||||||
|
.connectTimeout(0, TimeUnit.MILLISECONDS)
|
||||||
|
.readTimeout(0, TimeUnit.MILLISECONDS)
|
||||||
|
.writeTimeout(0, TimeUnit.MILLISECONDS)
|
||||||
|
.cookieJar(new ReactCookieJarContainer());
|
||||||
|
|
||||||
|
if (alias != null) {
|
||||||
|
SSLSocketFactory sslSocketFactory = getSSLFactory(alias);
|
||||||
|
if (sslSocketFactory != null) {
|
||||||
|
builder.sslSocketFactory(sslSocketFactory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "SSLPinning";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void alias(String alias) {
|
||||||
|
this.alias = alias;
|
||||||
|
|
||||||
|
this.promise.resolve(alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void setCertificate(String data, Promise promise) {
|
||||||
|
this.alias = data;
|
||||||
|
|
||||||
|
// HTTP Fetch react-native layer
|
||||||
|
NetworkingModule.setCustomClientBuilder(new CustomClient());
|
||||||
|
// Websocket react-native layer
|
||||||
|
WebSocketModule.setCustomClientBuilder(new CustomClient());
|
||||||
|
// Image networking react-native layer
|
||||||
|
ReactOkHttpNetworkFetcher.setOkHttpClient(getOkHttpClient());
|
||||||
|
// RNFetchBlob networking layer
|
||||||
|
RNFetchBlob.applyCustomOkHttpClient(getOkHttpClient());
|
||||||
|
// RNCWebView onReceivedClientCertRequest
|
||||||
|
RNCWebViewManager.setCertificateAlias(data);
|
||||||
|
// FastImage Glide network layer
|
||||||
|
FastImageOkHttpUrlLoader.setOkHttpClient(getOkHttpClient());
|
||||||
|
// Expo AV network layer
|
||||||
|
SharedCookiesDataSourceFactory.setOkHttpClient(getOkHttpClient());
|
||||||
|
|
||||||
|
promise.resolve(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void pickCertificate(Promise promise) {
|
||||||
|
Activity activity = getCurrentActivity();
|
||||||
|
|
||||||
|
this.promise = promise;
|
||||||
|
|
||||||
|
KeyChain.choosePrivateKeyAlias(activity,
|
||||||
|
this, // Callback
|
||||||
|
null, // Any key types.
|
||||||
|
null, // Any issuers.
|
||||||
|
null, // Any host
|
||||||
|
-1, // Any port
|
||||||
|
"RocketChat");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SSLSocketFactory getSSLFactory(final String alias) {
|
||||||
|
try {
|
||||||
|
final PrivateKey privKey = KeyChain.getPrivateKey(reactContext, alias);
|
||||||
|
final X509Certificate[] certChain = KeyChain.getCertificateChain(reactContext, alias);
|
||||||
|
|
||||||
|
final X509ExtendedKeyManager keyManager = new X509ExtendedKeyManager() {
|
||||||
|
@Override
|
||||||
|
public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) {
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String chooseServerAlias(String s, Principal[] principals, Socket socket) {
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public X509Certificate[] getCertificateChain(String s) {
|
||||||
|
return certChain;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getClientAliases(String s, Principal[] principals) {
|
||||||
|
return new String[]{alias};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getServerAliases(String s, Principal[] principals) {
|
||||||
|
return new String[]{alias};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PrivateKey getPrivateKey(String s) {
|
||||||
|
return privKey;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
final TrustManager[] trustAllCerts = new TrustManager[] {
|
||||||
|
new X509TrustManager() {
|
||||||
|
@Override
|
||||||
|
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
|
||||||
|
return certChain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
final SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||||
|
sslContext.init(new KeyManager[]{keyManager}, trustAllCerts, new java.security.SecureRandom());
|
||||||
|
SSLContext.setDefault(sslContext);
|
||||||
|
|
||||||
|
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
|
||||||
|
|
||||||
|
return sslSocketFactory;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package chat.rocket.reactnative;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.facebook.react.ReactPackage;
|
||||||
|
import com.facebook.react.bridge.NativeModule;
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
|
import com.facebook.react.uimanager.ViewManager;
|
||||||
|
import com.facebook.react.bridge.JavaScriptModule;
|
||||||
|
|
||||||
|
public class SSLPinningPackage implements ReactPackage {
|
||||||
|
@Override
|
||||||
|
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
||||||
|
return Arrays.<NativeModule>asList(new SSLPinningModule(reactContext));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,7 +12,7 @@ buildscript {
|
||||||
minSdkVersion = 23
|
minSdkVersion = 23
|
||||||
compileSdkVersion = 29
|
compileSdkVersion = 29
|
||||||
targetSdkVersion = 29
|
targetSdkVersion = 29
|
||||||
glideVersion = "4.9.0"
|
glideVersion = "4.11.0"
|
||||||
kotlin_version = "1.3.50"
|
kotlin_version = "1.3.50"
|
||||||
supportLibVersion = "28.0.0"
|
supportLibVersion = "28.0.0"
|
||||||
libre_build = !(isPlay.toBoolean())
|
libre_build = !(isPlay.toBoolean())
|
||||||
|
|
|
@ -23,11 +23,10 @@ export function selectServerFailure() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function serverRequest(server, certificate = null, username = null, fromServerHistory = false) {
|
export function serverRequest(server, username = null, fromServerHistory = false) {
|
||||||
return {
|
return {
|
||||||
type: SERVER.REQUEST,
|
type: SERVER.REQUEST,
|
||||||
server,
|
server,
|
||||||
certificate,
|
|
||||||
username,
|
username,
|
||||||
fromServerHistory
|
fromServerHistory
|
||||||
};
|
};
|
||||||
|
|
|
@ -15,6 +15,7 @@ import database from './database';
|
||||||
import log from '../utils/log';
|
import log from '../utils/log';
|
||||||
import { isIOS, getBundleId } from '../utils/deviceInfo';
|
import { isIOS, getBundleId } from '../utils/deviceInfo';
|
||||||
import fetch from '../utils/fetch';
|
import fetch from '../utils/fetch';
|
||||||
|
import SSLPinning from '../utils/sslPinning';
|
||||||
|
|
||||||
import { encryptionInit } from '../actions/encryption';
|
import { encryptionInit } from '../actions/encryption';
|
||||||
import { setUser, setLoginServices, loginRequest } from '../actions/login';
|
import { setUser, setLoginServices, loginRequest } from '../actions/login';
|
||||||
|
@ -63,6 +64,7 @@ import { sanitizeLikeString } from './database/utils';
|
||||||
const TOKEN_KEY = 'reactnativemeteor_usertoken';
|
const TOKEN_KEY = 'reactnativemeteor_usertoken';
|
||||||
const CURRENT_SERVER = 'currentServer';
|
const CURRENT_SERVER = 'currentServer';
|
||||||
const SORT_PREFS_KEY = 'RC_SORT_PREFS_KEY';
|
const SORT_PREFS_KEY = 'RC_SORT_PREFS_KEY';
|
||||||
|
const CERTIFICATE_KEY = 'RC_CERTIFICATE_KEY';
|
||||||
export const THEME_PREFERENCES_KEY = 'RC_THEME_PREFERENCES_KEY';
|
export const THEME_PREFERENCES_KEY = 'RC_THEME_PREFERENCES_KEY';
|
||||||
export const CRASH_REPORT_KEY = 'RC_CRASH_REPORT_KEY';
|
export const CRASH_REPORT_KEY = 'RC_CRASH_REPORT_KEY';
|
||||||
export const ANALYTICS_EVENTS_KEY = 'RC_ANALYTICS_EVENTS_KEY';
|
export const ANALYTICS_EVENTS_KEY = 'RC_ANALYTICS_EVENTS_KEY';
|
||||||
|
@ -74,6 +76,7 @@ const STATUSES = ['offline', 'online', 'away', 'busy'];
|
||||||
const RocketChat = {
|
const RocketChat = {
|
||||||
TOKEN_KEY,
|
TOKEN_KEY,
|
||||||
CURRENT_SERVER,
|
CURRENT_SERVER,
|
||||||
|
CERTIFICATE_KEY,
|
||||||
callJitsi,
|
callJitsi,
|
||||||
async subscribeRooms() {
|
async subscribeRooms() {
|
||||||
if (!this.roomsSub) {
|
if (!this.roomsSub) {
|
||||||
|
@ -312,6 +315,13 @@ const RocketChat = {
|
||||||
async shareExtensionInit(server) {
|
async shareExtensionInit(server) {
|
||||||
database.setShareDB(server);
|
database.setShareDB(server);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const certificate = await UserPreferences.getStringAsync(`${ RocketChat.CERTIFICATE_KEY }-${ server }`);
|
||||||
|
await SSLPinning.setCertificate(certificate, server);
|
||||||
|
} catch {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
if (this.shareSDK) {
|
if (this.shareSDK) {
|
||||||
this.shareSDK.disconnect();
|
this.shareSDK.disconnect();
|
||||||
this.shareSDK = null;
|
this.shareSDK = null;
|
||||||
|
|
|
@ -14,12 +14,12 @@ import { setUser } from '../actions/login';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
import database from '../lib/database';
|
import database from '../lib/database';
|
||||||
import log, { logServerVersion } from '../utils/log';
|
import log, { logServerVersion } from '../utils/log';
|
||||||
import { extractHostname } from '../utils/server';
|
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
import { BASIC_AUTH_KEY, setBasicAuth } from '../utils/fetch';
|
import { BASIC_AUTH_KEY, setBasicAuth } from '../utils/fetch';
|
||||||
import { appStart, ROOT_INSIDE, ROOT_OUTSIDE } from '../actions/app';
|
import { appStart, ROOT_INSIDE, ROOT_OUTSIDE } from '../actions/app';
|
||||||
import UserPreferences from '../lib/userPreferences';
|
import UserPreferences from '../lib/userPreferences';
|
||||||
import { encryptionStop } from '../actions/encryption';
|
import { encryptionStop } from '../actions/encryption';
|
||||||
|
import SSLPinning from '../utils/sslPinning';
|
||||||
|
|
||||||
import { inquiryReset } from '../ee/omnichannel/actions/inquiry';
|
import { inquiryReset } from '../ee/omnichannel/actions/inquiry';
|
||||||
|
|
||||||
|
@ -68,6 +68,10 @@ const getServerInfo = function* getServerInfo({ server, raiseError = true }) {
|
||||||
|
|
||||||
const handleSelectServer = function* handleSelectServer({ server, version, fetchVersion }) {
|
const handleSelectServer = function* handleSelectServer({ server, version, fetchVersion }) {
|
||||||
try {
|
try {
|
||||||
|
// SSL Pinning - Read certificate alias and set it to be used by network requests
|
||||||
|
const certificate = yield UserPreferences.getStringAsync(`${ RocketChat.CERTIFICATE_KEY }-${ server }`);
|
||||||
|
yield SSLPinning.setCertificate(certificate, server);
|
||||||
|
|
||||||
yield put(inquiryReset());
|
yield put(inquiryReset());
|
||||||
yield put(encryptionStop());
|
yield put(encryptionStop());
|
||||||
const serversDB = database.servers;
|
const serversDB = database.servers;
|
||||||
|
@ -138,13 +142,11 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleServerRequest = function* handleServerRequest({
|
const handleServerRequest = function* handleServerRequest({ server, username, fromServerHistory }) {
|
||||||
server, certificate, username, fromServerHistory
|
|
||||||
}) {
|
|
||||||
try {
|
try {
|
||||||
if (certificate) {
|
// SSL Pinning - Read certificate alias and set it to be used by network requests
|
||||||
yield UserPreferences.setMapAsync(extractHostname(server), certificate);
|
const certificate = yield UserPreferences.getStringAsync(`${ RocketChat.CERTIFICATE_KEY }-${ server }`);
|
||||||
}
|
yield SSLPinning.setCertificate(certificate, server);
|
||||||
|
|
||||||
const serverInfo = yield getServerInfo({ server });
|
const serverInfo = yield getServerInfo({ server });
|
||||||
const serversDB = database.servers;
|
const serversDB = database.servers;
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
import { NativeModules, Platform, Alert } from 'react-native';
|
||||||
|
import DocumentPicker from 'react-native-document-picker';
|
||||||
|
import * as FileSystem from 'expo-file-system';
|
||||||
|
|
||||||
|
import { extractHostname } from './server';
|
||||||
|
import UserPreferences from '../lib/userPreferences';
|
||||||
|
import I18n from '../i18n';
|
||||||
|
|
||||||
|
const { SSLPinning } = NativeModules;
|
||||||
|
|
||||||
|
const RCSSLPinning = Platform.select({
|
||||||
|
ios: {
|
||||||
|
pickCertificate: () => new Promise(async(resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const res = await DocumentPicker.pick({
|
||||||
|
type: ['com.rsa.pkcs-12']
|
||||||
|
});
|
||||||
|
const { uri, name } = res;
|
||||||
|
Alert.prompt(
|
||||||
|
I18n.t('Certificate_password'),
|
||||||
|
I18n.t('Whats_the_password_for_your_certificate'),
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: 'OK',
|
||||||
|
onPress: async(password) => {
|
||||||
|
try {
|
||||||
|
const certificatePath = `${ FileSystem.documentDirectory }/${ name }`;
|
||||||
|
|
||||||
|
await FileSystem.copyAsync({ from: uri, to: certificatePath });
|
||||||
|
|
||||||
|
const certificate = {
|
||||||
|
path: certificatePath.replace('file://', ''), // file:// isn't allowed by obj-C
|
||||||
|
password
|
||||||
|
};
|
||||||
|
|
||||||
|
await UserPreferences.setMapAsync(name, certificate);
|
||||||
|
|
||||||
|
resolve(name);
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'secure-text'
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
setCertificate: async(alias, server) => {
|
||||||
|
if (alias) {
|
||||||
|
const certificate = await UserPreferences.getMapAsync(alias);
|
||||||
|
await UserPreferences.setMapAsync(extractHostname(server), certificate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
android: {
|
||||||
|
pickCertificate: () => SSLPinning?.pickCertificate(),
|
||||||
|
setCertificate: alias => SSLPinning?.setCertificate(alias)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default RCSSLPinning;
|
|
@ -4,7 +4,7 @@ import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import CameraRoll from '@react-native-community/cameraroll';
|
import CameraRoll from '@react-native-community/cameraroll';
|
||||||
import * as mime from 'react-native-mime-types';
|
import * as mime from 'react-native-mime-types';
|
||||||
import { FileSystem } from 'react-native-unimodules';
|
import RNFetchBlob from 'rn-fetch-blob';
|
||||||
import { Video } from 'expo-av';
|
import { Video } from 'expo-av';
|
||||||
import SHA256 from 'js-sha256';
|
import SHA256 from 'js-sha256';
|
||||||
import { withSafeAreaInsets } from 'react-native-safe-area-context';
|
import { withSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
|
@ -119,9 +119,9 @@ class AttachmentView extends React.Component {
|
||||||
this.setState({ loading: true });
|
this.setState({ loading: true });
|
||||||
try {
|
try {
|
||||||
const extension = image_url ? `.${ mime.extension(image_type) || 'jpg' }` : `.${ mime.extension(video_type) || 'mp4' }`;
|
const extension = image_url ? `.${ mime.extension(image_type) || 'jpg' }` : `.${ mime.extension(video_type) || 'mp4' }`;
|
||||||
const file = `${ FileSystem.documentDirectory + SHA256(url) + extension }`;
|
const path = `${ RNFetchBlob.fs.dirs.DocumentDir + SHA256(url) + extension }`;
|
||||||
const { uri } = await FileSystem.downloadAsync(mediaAttachment, file);
|
const file = await RNFetchBlob.config({ path }).fetch('GET', mediaAttachment).then(res => res.path());
|
||||||
await CameraRoll.save(uri, { album: 'Rocket.Chat' });
|
await CameraRoll.save(file, { album: 'Rocket.Chat' });
|
||||||
EventEmitter.emit(LISTENER, { message: I18n.t('saved_to_gallery') });
|
EventEmitter.emit(LISTENER, { message: I18n.t('saved_to_gallery') });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
EventEmitter.emit(LISTENER, { message: I18n.t(image_url ? 'error-save-image' : 'error-save-video') });
|
EventEmitter.emit(LISTENER, { message: I18n.t(image_url ? 'error-save-image' : 'error-save-video') });
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {
|
import {
|
||||||
Text, Keyboard, StyleSheet, View, Alert, BackHandler
|
Text, Keyboard, StyleSheet, View, BackHandler
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import * as FileSystem from 'expo-file-system';
|
|
||||||
import DocumentPicker from 'react-native-document-picker';
|
|
||||||
import { Base64 } from 'js-base64';
|
import { Base64 } from 'js-base64';
|
||||||
import parse from 'url-parse';
|
import parse from 'url-parse';
|
||||||
import { Q } from '@nozbe/watermelondb';
|
import { Q } from '@nozbe/watermelondb';
|
||||||
|
@ -20,9 +18,8 @@ import Button from '../../containers/Button';
|
||||||
import OrSeparator from '../../containers/OrSeparator';
|
import OrSeparator from '../../containers/OrSeparator';
|
||||||
import FormContainer, { FormContainerInner } from '../../containers/FormContainer';
|
import FormContainer, { FormContainerInner } from '../../containers/FormContainer';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import { isIOS } from '../../utils/deviceInfo';
|
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
import log, { logEvent, events } from '../../utils/log';
|
import { logEvent, events } from '../../utils/log';
|
||||||
import { animateNextTransition } from '../../utils/layoutAnimation';
|
import { animateNextTransition } from '../../utils/layoutAnimation';
|
||||||
import { withTheme } from '../../theme';
|
import { withTheme } from '../../theme';
|
||||||
import { setBasicAuth, BASIC_AUTH_KEY } from '../../utils/fetch';
|
import { setBasicAuth, BASIC_AUTH_KEY } from '../../utils/fetch';
|
||||||
|
@ -31,6 +28,8 @@ import { showConfirmationAlert } from '../../utils/info';
|
||||||
import database from '../../lib/database';
|
import database from '../../lib/database';
|
||||||
import ServerInput from './ServerInput';
|
import ServerInput from './ServerInput';
|
||||||
import { sanitizeLikeString } from '../../lib/database/utils';
|
import { sanitizeLikeString } from '../../lib/database/utils';
|
||||||
|
import SSLPinning from '../../utils/sslPinning';
|
||||||
|
import RocketChat from '../../lib/rocketchat';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
title: {
|
title: {
|
||||||
|
@ -178,32 +177,23 @@ class NewServerView extends React.Component {
|
||||||
logEvent(events.NEWSERVER_CONNECT_TO_WORKSPACE);
|
logEvent(events.NEWSERVER_CONNECT_TO_WORKSPACE);
|
||||||
const { text, certificate } = this.state;
|
const { text, certificate } = this.state;
|
||||||
const { connectServer } = this.props;
|
const { connectServer } = this.props;
|
||||||
let cert = null;
|
|
||||||
|
|
||||||
this.setState({ connectingOpen: false });
|
this.setState({ connectingOpen: false });
|
||||||
|
|
||||||
if (certificate) {
|
|
||||||
const certificatePath = `${ FileSystem.documentDirectory }/${ certificate.name }`;
|
|
||||||
try {
|
|
||||||
await FileSystem.copyAsync({ from: certificate.path, to: certificatePath });
|
|
||||||
} catch (e) {
|
|
||||||
logEvent(events.NEWSERVER_CONNECT_TO_WORKSPACE_F);
|
|
||||||
log(e);
|
|
||||||
}
|
|
||||||
cert = {
|
|
||||||
path: this.uriToPath(certificatePath), // file:// isn't allowed by obj-C
|
|
||||||
password: certificate.password
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (text) {
|
if (text) {
|
||||||
Keyboard.dismiss();
|
Keyboard.dismiss();
|
||||||
const server = this.completeUrl(text);
|
const server = this.completeUrl(text);
|
||||||
|
|
||||||
|
// Save info - SSL Pinning
|
||||||
|
await UserPreferences.setStringAsync(`${ RocketChat.CERTIFICATE_KEY }-${ server }`, certificate);
|
||||||
|
|
||||||
|
// Save info - HTTP Basic Authentication
|
||||||
await this.basicAuth(server, text);
|
await this.basicAuth(server, text);
|
||||||
|
|
||||||
if (fromServerHistory) {
|
if (fromServerHistory) {
|
||||||
connectServer(server, cert, username, true);
|
connectServer(server, username, true);
|
||||||
} else {
|
} else {
|
||||||
connectServer(server, cert);
|
connectServer(server);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -230,25 +220,10 @@ class NewServerView extends React.Component {
|
||||||
|
|
||||||
chooseCertificate = async() => {
|
chooseCertificate = async() => {
|
||||||
try {
|
try {
|
||||||
const res = await DocumentPicker.pick({
|
const certificate = await SSLPinning.pickCertificate();
|
||||||
type: ['com.rsa.pkcs-12']
|
this.setState({ certificate });
|
||||||
});
|
} catch {
|
||||||
const { uri: path, name } = res;
|
// Do nothing
|
||||||
Alert.prompt(
|
|
||||||
I18n.t('Certificate_password'),
|
|
||||||
I18n.t('Whats_the_password_for_your_certificate'),
|
|
||||||
[
|
|
||||||
{
|
|
||||||
text: 'OK',
|
|
||||||
onPress: password => this.saveCertificate({ path, name, password })
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'secure-text'
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
if (!DocumentPicker.isCancel(e)) {
|
|
||||||
log(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -327,7 +302,7 @@ class NewServerView extends React.Component {
|
||||||
{ color: themes[theme].tintColor }
|
{ color: themes[theme].tintColor }
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{certificate ? certificate.name : I18n.t('Apply_Your_Certificate')}
|
{certificate ?? I18n.t('Apply_Your_Certificate')}
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
@ -379,7 +354,7 @@ class NewServerView extends React.Component {
|
||||||
testID='new-server-view-open'
|
testID='new-server-view-open'
|
||||||
/>
|
/>
|
||||||
</FormContainerInner>
|
</FormContainerInner>
|
||||||
{isIOS ? this.renderCertificatePicker() : null}
|
{this.renderCertificatePicker()}
|
||||||
</FormContainer>
|
</FormContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -392,7 +367,7 @@ const mapStateToProps = state => ({
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
connectServer: (server, certificate, username, fromServerHistory) => dispatch(serverRequest(server, certificate, username, fromServerHistory)),
|
connectServer: (...params) => dispatch(serverRequest(...params)),
|
||||||
selectServer: server => dispatch(selectServerRequest(server)),
|
selectServer: server => dispatch(selectServerRequest(server)),
|
||||||
inviteLinksClear: () => dispatch(inviteLinksClearAction())
|
inviteLinksClear: () => dispatch(inviteLinksClearAction())
|
||||||
});
|
});
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
"@react-navigation/drawer": "5.8.5",
|
"@react-navigation/drawer": "5.8.5",
|
||||||
"@react-navigation/native": "5.7.0",
|
"@react-navigation/native": "5.7.0",
|
||||||
"@react-navigation/stack": "5.7.0",
|
"@react-navigation/stack": "5.7.0",
|
||||||
"@rocket.chat/react-native-fast-image": "^8.1.5",
|
"@rocket.chat/react-native-fast-image": "^8.2.0",
|
||||||
"@rocket.chat/sdk": "RocketChat/Rocket.Chat.js.SDK#mobile",
|
"@rocket.chat/sdk": "RocketChat/Rocket.Chat.js.SDK#mobile",
|
||||||
"@rocket.chat/ui-kit": "0.13.0",
|
"@rocket.chat/ui-kit": "0.13.0",
|
||||||
"bugsnag-react-native": "2.23.10",
|
"bugsnag-react-native": "2.23.10",
|
||||||
|
@ -68,7 +68,7 @@
|
||||||
"prop-types": "15.7.2",
|
"prop-types": "15.7.2",
|
||||||
"react": "16.13.1",
|
"react": "16.13.1",
|
||||||
"react-fast-compare": "^3.2.0",
|
"react-fast-compare": "^3.2.0",
|
||||||
"react-native": "^0.63.1",
|
"react-native": "RocketChat/react-native#0.63.2",
|
||||||
"react-native-animatable": "^1.3.3",
|
"react-native-animatable": "^1.3.3",
|
||||||
"react-native-appearance": "0.3.4",
|
"react-native-appearance": "0.3.4",
|
||||||
"react-native-background-timer": "2.2.0",
|
"react-native-background-timer": "2.2.0",
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
diff --git a/node_modules/expo-av/android/src/main/java/expo/modules/av/player/datasource/SharedCookiesDataSourceFactory.java b/node_modules/expo-av/android/src/main/java/expo/modules/av/player/datasource/SharedCookiesDataSourceFactory.java
|
||||||
|
index 9d26d80..d019bea 100644
|
||||||
|
--- a/node_modules/expo-av/android/src/main/java/expo/modules/av/player/datasource/SharedCookiesDataSourceFactory.java
|
||||||
|
+++ b/node_modules/expo-av/android/src/main/java/expo/modules/av/player/datasource/SharedCookiesDataSourceFactory.java
|
||||||
|
@@ -16,13 +16,21 @@ import okhttp3.OkHttpClient;
|
||||||
|
public class SharedCookiesDataSourceFactory implements DataSource.Factory {
|
||||||
|
private final DataSource.Factory mDataSourceFactory;
|
||||||
|
|
||||||
|
+ public static OkHttpClient client;
|
||||||
|
+
|
||||||
|
+ public static void setOkHttpClient(OkHttpClient okHttpClient) {
|
||||||
|
+ client = okHttpClient;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
public SharedCookiesDataSourceFactory(Context reactApplicationContext, ModuleRegistry moduleRegistry, String userAgent, Map<String, Object> requestHeaders, TransferListener transferListener) {
|
||||||
|
CookieHandler cookieHandler = moduleRegistry.getModule(CookieHandler.class);
|
||||||
|
- OkHttpClient.Builder builder = new OkHttpClient.Builder();
|
||||||
|
- if (cookieHandler != null) {
|
||||||
|
- builder.cookieJar(new JavaNetCookieJar(cookieHandler));
|
||||||
|
+ if (this.client == null) {
|
||||||
|
+ OkHttpClient.Builder builder = new OkHttpClient.Builder();
|
||||||
|
+ if (cookieHandler != null) {
|
||||||
|
+ builder.cookieJar(new JavaNetCookieJar(cookieHandler));
|
||||||
|
+ }
|
||||||
|
+ this.client = builder.build();
|
||||||
|
}
|
||||||
|
- OkHttpClient client = builder.build();
|
||||||
|
mDataSourceFactory = new DefaultDataSourceFactory(reactApplicationContext, transferListener, new CustomHeadersOkHttpDataSourceFactory(client, userAgent, requestHeaders));
|
||||||
|
}
|
||||||
|
|
|
@ -1,406 +0,0 @@
|
||||||
diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.js b/node_modules/react-native/Libraries/Components/TextInput/TextInput.js
|
|
||||||
index 478af12..d3cd45c 100644
|
|
||||||
--- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.js
|
|
||||||
+++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.js
|
|
||||||
@@ -700,6 +700,7 @@ export type Props = $ReadOnly<{|
|
|
||||||
|
|
||||||
type ImperativeMethods = $ReadOnly<{|
|
|
||||||
clear: () => void,
|
|
||||||
+ setTextAndSelection: () => void,
|
|
||||||
isFocused: () => boolean,
|
|
||||||
getNativeRef: () => ?React.ElementRef<HostComponent<mixed>>,
|
|
||||||
|}>;
|
|
||||||
@@ -947,6 +948,18 @@ function InternalTextInput(props: Props): React.Node {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
+ function setTextAndSelection(_text, _selection): void {
|
|
||||||
+ if (inputRef.current != null) {
|
|
||||||
+ viewCommands.setTextAndSelection(
|
|
||||||
+ inputRef.current,
|
|
||||||
+ mostRecentEventCount,
|
|
||||||
+ _text,
|
|
||||||
+ _selection?.start ?? -1,
|
|
||||||
+ _selection?.end ?? -1,
|
|
||||||
+ );
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
// TODO: Fix this returning true on null === null, when no input is focused
|
|
||||||
function isFocused(): boolean {
|
|
||||||
return TextInputState.currentlyFocusedInput() === inputRef.current;
|
|
||||||
@@ -985,6 +998,7 @@ function InternalTextInput(props: Props): React.Node {
|
|
||||||
*/
|
|
||||||
if (ref) {
|
|
||||||
ref.clear = clear;
|
|
||||||
+ ref.setTextAndSelection = setTextAndSelection;
|
|
||||||
ref.isFocused = isFocused;
|
|
||||||
ref.getNativeRef = getNativeRef;
|
|
||||||
}
|
|
||||||
diff --git a/node_modules/react-native/Libraries/Image/RCTUIImageViewAnimated.m b/node_modules/react-native/Libraries/Image/RCTUIImageViewAnimated.m
|
|
||||||
index af4becd..55bc2c8 100644
|
|
||||||
--- a/node_modules/react-native/Libraries/Image/RCTUIImageViewAnimated.m
|
|
||||||
+++ b/node_modules/react-native/Libraries/Image/RCTUIImageViewAnimated.m
|
|
||||||
@@ -275,6 +275,8 @@ - (void)displayLayer:(CALayer *)layer
|
|
||||||
if (_currentFrame) {
|
|
||||||
layer.contentsScale = self.animatedImageScale;
|
|
||||||
layer.contents = (__bridge id)_currentFrame.CGImage;
|
|
||||||
+ } else {
|
|
||||||
+ [super displayLayer:layer];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
diff --git a/node_modules/react-native/Libraries/Network/RCTHTTPRequestHandler.mm b/node_modules/react-native/Libraries/Network/RCTHTTPRequestHandler.mm
|
|
||||||
index 274f381..c7749ef 100644
|
|
||||||
--- a/node_modules/react-native/Libraries/Network/RCTHTTPRequestHandler.mm
|
|
||||||
+++ b/node_modules/react-native/Libraries/Network/RCTHTTPRequestHandler.mm
|
|
||||||
@@ -8,11 +8,13 @@
|
|
||||||
#import <React/RCTHTTPRequestHandler.h>
|
|
||||||
|
|
||||||
#import <mutex>
|
|
||||||
-
|
|
||||||
+#import <MMKV/MMKV.h>
|
|
||||||
#import <React/RCTNetworking.h>
|
|
||||||
#import <ReactCommon/RCTTurboModule.h>
|
|
||||||
+#import <SDWebImage/SDWebImageDownloader.h>
|
|
||||||
|
|
||||||
#import "RCTNetworkPlugins.h"
|
|
||||||
+#import "SecureStorage.h"
|
|
||||||
|
|
||||||
@interface RCTHTTPRequestHandler () <NSURLSessionDataDelegate, RCTTurboModule>
|
|
||||||
|
|
||||||
@@ -58,6 +60,103 @@ - (BOOL)canHandleRequest:(NSURLRequest *)request
|
|
||||||
return [schemes containsObject:request.URL.scheme.lowercaseString];
|
|
||||||
}
|
|
||||||
|
|
||||||
+-(NSURLCredential *)getUrlCredential:(NSURLAuthenticationChallenge *)challenge path:(NSString *)path password:(NSString *)password
|
|
||||||
+{
|
|
||||||
+ NSString *authMethod = [[challenge protectionSpace] authenticationMethod];
|
|
||||||
+ SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
|
|
||||||
+
|
|
||||||
+ if ([authMethod isEqualToString:NSURLAuthenticationMethodServerTrust] || path == nil || password == nil) {
|
|
||||||
+ return [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
|
|
||||||
+ } else if (path && password) {
|
|
||||||
+ NSMutableArray *policies = [NSMutableArray array];
|
|
||||||
+ [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)challenge.protectionSpace.host)];
|
|
||||||
+ SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
|
|
||||||
+
|
|
||||||
+ SecTrustResultType result;
|
|
||||||
+ SecTrustEvaluate(serverTrust, &result);
|
|
||||||
+
|
|
||||||
+ if (![[NSFileManager defaultManager] fileExistsAtPath:path])
|
|
||||||
+ {
|
|
||||||
+ return [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ NSData *p12data = [NSData dataWithContentsOfFile:path];
|
|
||||||
+ NSDictionary* options = @{ (id)kSecImportExportPassphrase:password };
|
|
||||||
+ CFArrayRef rawItems = NULL;
|
|
||||||
+ OSStatus status = SecPKCS12Import((__bridge CFDataRef)p12data,
|
|
||||||
+ (__bridge CFDictionaryRef)options,
|
|
||||||
+ &rawItems);
|
|
||||||
+
|
|
||||||
+ if (status != noErr) {
|
|
||||||
+ return [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ NSArray* items = (NSArray*)CFBridgingRelease(rawItems);
|
|
||||||
+ NSDictionary* firstItem = nil;
|
|
||||||
+ if ((status == errSecSuccess) && ([items count]>0)) {
|
|
||||||
+ firstItem = items[0];
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ SecIdentityRef identity = (SecIdentityRef)CFBridgingRetain(firstItem[(id)kSecImportItemIdentity]);
|
|
||||||
+ SecCertificateRef certificate = NULL;
|
|
||||||
+ if (identity) {
|
|
||||||
+ SecIdentityCopyCertificate(identity, &certificate);
|
|
||||||
+ if (certificate) { CFRelease(certificate); }
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ NSMutableArray *certificates = [[NSMutableArray alloc] init];
|
|
||||||
+ [certificates addObject:CFBridgingRelease(certificate)];
|
|
||||||
+
|
|
||||||
+ [SDWebImageDownloader sharedDownloader].config.urlCredential = [NSURLCredential credentialWithIdentity:identity certificates:certificates persistence:NSURLCredentialPersistenceNone];
|
|
||||||
+
|
|
||||||
+ return [NSURLCredential credentialWithIdentity:identity certificates:certificates persistence:NSURLCredentialPersistenceNone];
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ return [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+- (NSString *)stringToHex:(NSString *)string
|
|
||||||
+{
|
|
||||||
+ char *utf8 = (char *)[string UTF8String];
|
|
||||||
+ NSMutableString *hex = [NSMutableString string];
|
|
||||||
+ while (*utf8) [hex appendFormat:@"%02X", *utf8++ & 0x00FF];
|
|
||||||
+
|
|
||||||
+ return [[NSString stringWithFormat:@"%@", hex] lowercaseString];
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+-(void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler {
|
|
||||||
+
|
|
||||||
+ NSString *host = challenge.protectionSpace.host;
|
|
||||||
+
|
|
||||||
+ // Read the clientSSL info from MMKV
|
|
||||||
+ __block NSDictionary *clientSSL;
|
|
||||||
+ SecureStorage *secureStorage = [[SecureStorage alloc] init];
|
|
||||||
+
|
|
||||||
+ // https://github.com/ammarahm-ed/react-native-mmkv-storage/blob/master/src/loader.js#L31
|
|
||||||
+ [secureStorage getSecureKey:[self stringToHex:@"com.MMKV.default"] callback:^(NSArray *response) {
|
|
||||||
+ // Error happened
|
|
||||||
+ if ([response objectAtIndex:0] != [NSNull null]) {
|
|
||||||
+ return;
|
|
||||||
+ }
|
|
||||||
+ NSString *key = [response objectAtIndex:1];
|
|
||||||
+ NSData *cryptKey = [key dataUsingEncoding:NSUTF8StringEncoding];
|
|
||||||
+ MMKV *mmkv = [MMKV mmkvWithID:@"default" cryptKey:cryptKey mode:MMKVMultiProcess];
|
|
||||||
+
|
|
||||||
+ clientSSL = [mmkv getObjectOfClass:[NSDictionary class] forKey:host];
|
|
||||||
+ }];
|
|
||||||
+
|
|
||||||
+ NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
|
|
||||||
+
|
|
||||||
+ if (clientSSL != (id)[NSNull null]) {
|
|
||||||
+ NSString *path = [clientSSL objectForKey:@"path"];
|
|
||||||
+ NSString *password = [clientSSL objectForKey:@"password"];
|
|
||||||
+ credential = [self getUrlCredential:challenge path:path password:password];
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+
|
|
||||||
- (NSURLSessionDataTask *)sendRequest:(NSURLRequest *)request
|
|
||||||
withDelegate:(id<RCTURLRequestDelegate>)delegate
|
|
||||||
{
|
|
||||||
diff --git a/node_modules/react-native/Libraries/WebSocket/RCTSRWebSocket.m b/node_modules/react-native/Libraries/WebSocket/RCTSRWebSocket.m
|
|
||||||
index b967c14..5ffc258 100644
|
|
||||||
--- a/node_modules/react-native/Libraries/WebSocket/RCTSRWebSocket.m
|
|
||||||
+++ b/node_modules/react-native/Libraries/WebSocket/RCTSRWebSocket.m
|
|
||||||
@@ -24,6 +24,9 @@
|
|
||||||
#import <React/RCTAssert.h>
|
|
||||||
#import <React/RCTLog.h>
|
|
||||||
|
|
||||||
+#import <MMKV/MMKV.h>
|
|
||||||
+#import "SecureStorage.h"
|
|
||||||
+
|
|
||||||
typedef NS_ENUM(NSInteger, RCTSROpCode) {
|
|
||||||
RCTSROpCodeTextFrame = 0x1,
|
|
||||||
RCTSROpCodeBinaryFrame = 0x2,
|
|
||||||
@@ -478,6 +481,38 @@ - (void)didConnect
|
|
||||||
[self _readHTTPHeader];
|
|
||||||
}
|
|
||||||
|
|
||||||
+- (void)setClientSSL:(NSString *)path password:(NSString *)password options:(NSMutableDictionary *)options;
|
|
||||||
+{
|
|
||||||
+ if ([[NSFileManager defaultManager] fileExistsAtPath:path])
|
|
||||||
+ {
|
|
||||||
+ NSData *pkcs12data = [[NSData alloc] initWithContentsOfFile:path];
|
|
||||||
+ NSDictionary* certOptions = @{ (id)kSecImportExportPassphrase:password };
|
|
||||||
+ CFArrayRef keyref = NULL;
|
|
||||||
+ OSStatus sanityChesk = SecPKCS12Import((__bridge CFDataRef)pkcs12data,
|
|
||||||
+ (__bridge CFDictionaryRef)certOptions,
|
|
||||||
+ &keyref);
|
|
||||||
+ if (sanityChesk == noErr) {
|
|
||||||
+ CFDictionaryRef identityDict = CFArrayGetValueAtIndex(keyref, 0);
|
|
||||||
+ SecIdentityRef identityRef = (SecIdentityRef)CFDictionaryGetValue(identityDict, kSecImportItemIdentity);
|
|
||||||
+ SecCertificateRef cert = NULL;
|
|
||||||
+ OSStatus status = SecIdentityCopyCertificate(identityRef, &cert);
|
|
||||||
+ if (!status) {
|
|
||||||
+ NSArray *certificates = [[NSArray alloc] initWithObjects:(__bridge id)identityRef, (__bridge id)cert, nil];
|
|
||||||
+ [options setObject:certificates forKey:(NSString *)kCFStreamSSLCertificates];
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+- (NSString *)stringToHex:(NSString *)string
|
|
||||||
+{
|
|
||||||
+ char *utf8 = (char *)[string UTF8String];
|
|
||||||
+ NSMutableString *hex = [NSMutableString string];
|
|
||||||
+ while (*utf8) [hex appendFormat:@"%02X", *utf8++ & 0x00FF];
|
|
||||||
+
|
|
||||||
+ return [[NSString stringWithFormat:@"%@", hex] lowercaseString];
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
- (void)_initializeStreams
|
|
||||||
{
|
|
||||||
assert(_url.port.unsignedIntValue <= UINT32_MAX);
|
|
||||||
@@ -515,6 +550,31 @@ - (void)_initializeStreams
|
|
||||||
RCTLogInfo(@"SocketRocket: In debug mode. Allowing connection to any root cert");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
+ // Read the clientSSL info from MMKV
|
|
||||||
+ __block NSDictionary *clientSSL;
|
|
||||||
+ SecureStorage *secureStorage = [[SecureStorage alloc] init];
|
|
||||||
+
|
|
||||||
+ // https://github.com/ammarahm-ed/react-native-mmkv-storage/blob/master/src/loader.js#L31
|
|
||||||
+ [secureStorage getSecureKey:[self stringToHex:@"com.MMKV.default"] callback:^(NSArray *response) {
|
|
||||||
+ // Error happened
|
|
||||||
+ if ([response objectAtIndex:0] != [NSNull null]) {
|
|
||||||
+ return;
|
|
||||||
+ }
|
|
||||||
+ NSString *key = [response objectAtIndex:1];
|
|
||||||
+ NSData *cryptKey = [key dataUsingEncoding:NSUTF8StringEncoding];
|
|
||||||
+ MMKV *mmkv = [MMKV mmkvWithID:@"default" cryptKey:cryptKey mode:MMKVMultiProcess];
|
|
||||||
+
|
|
||||||
+ clientSSL = [mmkv getObjectOfClass:[NSDictionary class] forKey:host];
|
|
||||||
+ }];
|
|
||||||
+
|
|
||||||
+ if (clientSSL != (id)[NSNull null]) {
|
|
||||||
+ NSString *path = [clientSSL objectForKey:@"path"];
|
|
||||||
+ NSString *password = [clientSSL objectForKey:@"password"];
|
|
||||||
+
|
|
||||||
+ [self setClientSSL:path password:password options:SSLOptions];
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+
|
|
||||||
[_outputStream setProperty:SSLOptions
|
|
||||||
forKey:(__bridge id)kCFStreamPropertySSLSettings];
|
|
||||||
}
|
|
||||||
@@ -594,6 +654,7 @@ - (void)closeWithCode:(NSInteger)code reason:(NSString *)reason
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
+ [self.delegate webSocket:self didCloseWithCode:code reason:reason wasClean:YES];
|
|
||||||
[self _sendFrameWithOpcode:RCTSROpCodeConnectionClose data:payload];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
diff --git a/node_modules/react-native/React/Base/RCTKeyCommands.h b/node_modules/react-native/React/Base/RCTKeyCommands.h
|
|
||||||
index 983348e..95742f4 100644
|
|
||||||
--- a/node_modules/react-native/React/Base/RCTKeyCommands.h
|
|
||||||
+++ b/node_modules/react-native/React/Base/RCTKeyCommands.h
|
|
||||||
@@ -18,6 +18,12 @@
|
|
||||||
modifierFlags:(UIKeyModifierFlags)flags
|
|
||||||
action:(void (^)(UIKeyCommand *command))block;
|
|
||||||
|
|
||||||
+- (void)registerKeyCommand:(NSString *)input
|
|
||||||
+ modifierFlags:(UIKeyModifierFlags)flags
|
|
||||||
+ discoverabilityTitle:(NSString *)discoverabilityTitle
|
|
||||||
+ action:(void (^)(UIKeyCommand *))block;
|
|
||||||
+
|
|
||||||
+
|
|
||||||
/**
|
|
||||||
* Unregister a single-press keyboard command.
|
|
||||||
*/
|
|
||||||
diff --git a/node_modules/react-native/React/Base/RCTKeyCommands.m b/node_modules/react-native/React/Base/RCTKeyCommands.m
|
|
||||||
index d48ba93..387d551 100644
|
|
||||||
--- a/node_modules/react-native/React/Base/RCTKeyCommands.m
|
|
||||||
+++ b/node_modules/react-native/React/Base/RCTKeyCommands.m
|
|
||||||
@@ -12,8 +12,6 @@
|
|
||||||
#import "RCTDefines.h"
|
|
||||||
#import "RCTUtils.h"
|
|
||||||
|
|
||||||
-#if RCT_DEV
|
|
||||||
-
|
|
||||||
@interface RCTKeyCommand : NSObject <NSCopying>
|
|
||||||
|
|
||||||
@property (nonatomic, strong) UIKeyCommand *keyCommand;
|
|
||||||
@@ -115,7 +113,9 @@ - (void)RCT_handleKeyCommand:(UIKeyCommand *)key
|
|
||||||
// NOTE: throttle the key handler because on iOS 9 the handleKeyCommand:
|
|
||||||
// method gets called repeatedly if the command key is held down.
|
|
||||||
static NSTimeInterval lastCommand = 0;
|
|
||||||
- if (CACurrentMediaTime() - lastCommand > 0.5) {
|
|
||||||
+ if (CACurrentMediaTime() - lastCommand > 0.5 ||
|
|
||||||
+ [key.input isEqualToString:@"UIKeyInputUpArrow"] || // repeat command if is scroll
|
|
||||||
+ [key.input isEqualToString:@"UIKeyInputDownArrow"]) {
|
|
||||||
for (RCTKeyCommand *command in [RCTKeyCommands sharedInstance].commands) {
|
|
||||||
if ([command.keyCommand.input isEqualToString:key.input] &&
|
|
||||||
command.keyCommand.modifierFlags == key.modifierFlags) {
|
|
||||||
@@ -178,6 +178,8 @@ - (void)RCT_handleDoublePressKeyCommand:(UIKeyCommand *)key
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
+#if RCT_DEV
|
|
||||||
+
|
|
||||||
@implementation RCTKeyCommands
|
|
||||||
|
|
||||||
+ (void)initialize
|
|
||||||
@@ -220,6 +222,23 @@ - (void)registerKeyCommandWithInput:(NSString *)input
|
|
||||||
[_commands addObject:keyCommand];
|
|
||||||
}
|
|
||||||
|
|
||||||
+- (void)registerKeyCommand:(NSString *)input
|
|
||||||
+ modifierFlags:(UIKeyModifierFlags)flags
|
|
||||||
+ discoverabilityTitle:(NSString *)discoverabilityTitle
|
|
||||||
+ action:(void (^)(UIKeyCommand *))block
|
|
||||||
+{
|
|
||||||
+ RCTAssertMainQueue();
|
|
||||||
+
|
|
||||||
+ UIKeyCommand *command = [UIKeyCommand keyCommandWithInput:input
|
|
||||||
+ modifierFlags:flags
|
|
||||||
+ action:@selector(RCT_handleKeyCommand:)
|
|
||||||
+ discoverabilityTitle:discoverabilityTitle];
|
|
||||||
+
|
|
||||||
+ RCTKeyCommand *keyCommand = [[RCTKeyCommand alloc] initWithKeyCommand:command block:block];
|
|
||||||
+ [_commands removeObject:keyCommand];
|
|
||||||
+ [_commands addObject:keyCommand];
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
- (void)unregisterKeyCommandWithInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags
|
|
||||||
{
|
|
||||||
RCTAssertMainQueue();
|
|
||||||
@@ -289,9 +308,48 @@ - (BOOL)isDoublePressKeyCommandRegisteredForInput:(NSString *)input modifierFlag
|
|
||||||
|
|
||||||
@implementation RCTKeyCommands
|
|
||||||
|
|
||||||
++ (void)initialize
|
|
||||||
+{
|
|
||||||
+ // swizzle UIResponder
|
|
||||||
+ RCTSwapInstanceMethods([UIResponder class],
|
|
||||||
+ @selector(keyCommands),
|
|
||||||
+ @selector(RCT_keyCommands));
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+ (instancetype)sharedInstance
|
|
||||||
{
|
|
||||||
- return nil;
|
|
||||||
+ static RCTKeyCommands *sharedInstance;
|
|
||||||
+ static dispatch_once_t onceToken;
|
|
||||||
+ dispatch_once(&onceToken, ^{
|
|
||||||
+ sharedInstance = [self new];
|
|
||||||
+ });
|
|
||||||
+
|
|
||||||
+ return sharedInstance;
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+- (instancetype)init
|
|
||||||
+{
|
|
||||||
+ if ((self = [super init])) {
|
|
||||||
+ _commands = [NSMutableSet new];
|
|
||||||
+ }
|
|
||||||
+ return self;
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+- (void)registerKeyCommand:(NSString *)input
|
|
||||||
+ modifierFlags:(UIKeyModifierFlags)flags
|
|
||||||
+ discoverabilityTitle:(NSString *)discoverabilityTitle
|
|
||||||
+ action:(void (^)(UIKeyCommand *))block
|
|
||||||
+{
|
|
||||||
+ RCTAssertMainQueue();
|
|
||||||
+
|
|
||||||
+ UIKeyCommand *command = [UIKeyCommand keyCommandWithInput:input
|
|
||||||
+ modifierFlags:flags
|
|
||||||
+ action:@selector(RCT_handleKeyCommand:)
|
|
||||||
+ discoverabilityTitle:discoverabilityTitle];
|
|
||||||
+
|
|
||||||
+ RCTKeyCommand *keyCommand = [[RCTKeyCommand alloc] initWithKeyCommand:command block:block];
|
|
||||||
+ [_commands removeObject:keyCommand];
|
|
||||||
+ [_commands addObject:keyCommand];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)registerKeyCommandWithInput:(NSString *)input
|
|
||||||
@@ -302,6 +360,13 @@ - (void)registerKeyCommandWithInput:(NSString *)input
|
|
||||||
|
|
||||||
- (void)unregisterKeyCommandWithInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags
|
|
||||||
{
|
|
||||||
+ RCTAssertMainQueue();
|
|
||||||
+ for (RCTKeyCommand *command in _commands.allObjects) {
|
|
||||||
+ if ([command matchesInput:input flags:flags]) {
|
|
||||||
+ [_commands removeObject:command];
|
|
||||||
+ break;
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)isKeyCommandRegisteredForInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags
|
|
|
@ -1,3 +1,104 @@
|
||||||
|
diff --git a/node_modules/react-native-webview/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java b/node_modules/react-native-webview/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java
|
||||||
|
index ab869cf..2aa7a9e 100644
|
||||||
|
--- a/node_modules/react-native-webview/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java
|
||||||
|
+++ b/node_modules/react-native-webview/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java
|
||||||
|
@@ -84,6 +84,12 @@ import java.util.Map;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
+import java.security.cert.X509Certificate;
|
||||||
|
+import java.security.PrivateKey;
|
||||||
|
+import android.webkit.ClientCertRequest;
|
||||||
|
+import android.os.AsyncTask;
|
||||||
|
+import android.security.KeyChain;
|
||||||
|
+
|
||||||
|
/**
|
||||||
|
* Manages instances of {@link WebView}
|
||||||
|
* <p>
|
||||||
|
@@ -140,6 +146,8 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
||||||
|
protected @Nullable String mUserAgent = null;
|
||||||
|
protected @Nullable String mUserAgentWithApplicationName = null;
|
||||||
|
|
||||||
|
+ private static String certificateAlias = null;
|
||||||
|
+
|
||||||
|
public RNCWebViewManager() {
|
||||||
|
mWebViewConfig = new WebViewConfig() {
|
||||||
|
public void configWebView(WebView webView) {
|
||||||
|
@@ -151,6 +159,10 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
||||||
|
mWebViewConfig = webViewConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ public static void setCertificateAlias(String alias) {
|
||||||
|
+ certificateAlias = alias;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
protected static void dispatchEvent(WebView webView, Event event) {
|
||||||
|
ReactContext reactContext = (ReactContext) webView.getContext();
|
||||||
|
EventDispatcher eventDispatcher =
|
||||||
|
@@ -562,7 +574,7 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
||||||
|
@Override
|
||||||
|
protected void addEventEmitters(ThemedReactContext reactContext, WebView view) {
|
||||||
|
// Do not register default touch emitter and let WebView implementation handle touches
|
||||||
|
- view.setWebViewClient(new RNCWebViewClient());
|
||||||
|
+ view.setWebViewClient(new RNCWebViewClient(reactContext));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@@ -742,12 +754,54 @@ public class RNCWebViewManager extends SimpleViewManager<WebView> {
|
||||||
|
|
||||||
|
protected static class RNCWebViewClient extends WebViewClient {
|
||||||
|
|
||||||
|
+ protected ReactContext reactContext;
|
||||||
|
protected boolean mLastLoadFailed = false;
|
||||||
|
protected @Nullable
|
||||||
|
ReadableArray mUrlPrefixesForDefaultIntent;
|
||||||
|
protected RNCWebView.ProgressChangedFilter progressChangedFilter = null;
|
||||||
|
protected @Nullable String ignoreErrFailedForThisURL = null;
|
||||||
|
|
||||||
|
+ public RNCWebViewClient(ReactContext reactContext) {
|
||||||
|
+ this.reactContext = reactContext;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ @Override
|
||||||
|
+ public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) {
|
||||||
|
+ class SslStuff {
|
||||||
|
+ PrivateKey privKey;
|
||||||
|
+ X509Certificate[] certChain;
|
||||||
|
+
|
||||||
|
+ public SslStuff(PrivateKey privKey, X509Certificate[] certChain) {
|
||||||
|
+ this.privKey = privKey;
|
||||||
|
+ this.certChain = certChain;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if (certificateAlias != null) {
|
||||||
|
+ AsyncTask<Void, Void, SslStuff> task = new AsyncTask<Void, Void, SslStuff>() {
|
||||||
|
+ @Override
|
||||||
|
+ protected SslStuff doInBackground(Void... params) {
|
||||||
|
+ try {
|
||||||
|
+ PrivateKey privKey = KeyChain.getPrivateKey(reactContext, certificateAlias);
|
||||||
|
+ X509Certificate[] certChain = KeyChain.getCertificateChain(reactContext, certificateAlias);
|
||||||
|
+
|
||||||
|
+ return new SslStuff(privKey, certChain);
|
||||||
|
+ } catch (Exception e) {
|
||||||
|
+ return null;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ @Override
|
||||||
|
+ protected void onPostExecute(SslStuff sslStuff) {
|
||||||
|
+ if (sslStuff != null) {
|
||||||
|
+ request.proceed(sslStuff.privKey, sslStuff.certChain);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ };
|
||||||
|
+ task.execute();
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
public void setIgnoreErrFailedForThisURL(@Nullable String url) {
|
||||||
|
ignoreErrFailedForThisURL = url;
|
||||||
|
}
|
||||||
diff --git a/node_modules/react-native-webview/apple/RNCWebView.m b/node_modules/react-native-webview/apple/RNCWebView.m
|
diff --git a/node_modules/react-native-webview/apple/RNCWebView.m b/node_modules/react-native-webview/apple/RNCWebView.m
|
||||||
index 02b4238..04bad05 100644
|
index 02b4238..04bad05 100644
|
||||||
--- a/node_modules/react-native-webview/apple/RNCWebView.m
|
--- a/node_modules/react-native-webview/apple/RNCWebView.m
|
||||||
|
|
|
@ -1,3 +1,27 @@
|
||||||
|
diff --git a/node_modules/rn-fetch-blob/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java b/node_modules/rn-fetch-blob/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java
|
||||||
|
index 602d51d..920d975 100644
|
||||||
|
--- a/node_modules/rn-fetch-blob/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java
|
||||||
|
+++ b/node_modules/rn-fetch-blob/android/src/main/java/com/RNFetchBlob/RNFetchBlob.java
|
||||||
|
@@ -38,7 +38,7 @@ import static com.RNFetchBlob.RNFetchBlobConst.GET_CONTENT_INTENT;
|
||||||
|
|
||||||
|
public class RNFetchBlob extends ReactContextBaseJavaModule {
|
||||||
|
|
||||||
|
- private final OkHttpClient mClient;
|
||||||
|
+ static private OkHttpClient mClient;
|
||||||
|
|
||||||
|
static ReactApplicationContext RCTContext;
|
||||||
|
private static LinkedBlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>();
|
||||||
|
@@ -75,6 +75,10 @@ public class RNFetchBlob extends ReactContextBaseJavaModule {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
+ public static void applyCustomOkHttpClient(OkHttpClient client) {
|
||||||
|
+ mClient = client;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "RNFetchBlob";
|
||||||
diff --git a/node_modules/rn-fetch-blob/ios/RNFetchBlobRequest.m b/node_modules/rn-fetch-blob/ios/RNFetchBlobRequest.m
|
diff --git a/node_modules/rn-fetch-blob/ios/RNFetchBlobRequest.m b/node_modules/rn-fetch-blob/ios/RNFetchBlobRequest.m
|
||||||
index cdbe6b1..bee6228 100644
|
index cdbe6b1..bee6228 100644
|
||||||
--- a/node_modules/rn-fetch-blob/ios/RNFetchBlobRequest.m
|
--- a/node_modules/rn-fetch-blob/ios/RNFetchBlobRequest.m
|
||||||
|
|
15
yarn.lock
15
yarn.lock
|
@ -2233,10 +2233,10 @@
|
||||||
resolved "https://registry.yarnpkg.com/@redux-saga/types/-/types-1.1.0.tgz#0e81ce56b4883b4b2a3001ebe1ab298b84237204"
|
resolved "https://registry.yarnpkg.com/@redux-saga/types/-/types-1.1.0.tgz#0e81ce56b4883b4b2a3001ebe1ab298b84237204"
|
||||||
integrity sha512-afmTuJrylUU/0OtqzaRkbyYFFNgCF73Bvel/sw90pvGrWIZ+vyoIJqA6eMSoA6+nb443kTmulmBtC9NerXboNg==
|
integrity sha512-afmTuJrylUU/0OtqzaRkbyYFFNgCF73Bvel/sw90pvGrWIZ+vyoIJqA6eMSoA6+nb443kTmulmBtC9NerXboNg==
|
||||||
|
|
||||||
"@rocket.chat/react-native-fast-image@^8.1.5":
|
"@rocket.chat/react-native-fast-image@^8.2.0":
|
||||||
version "8.1.5"
|
version "8.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/@rocket.chat/react-native-fast-image/-/react-native-fast-image-8.1.5.tgz#325d80ebb351fb024436093b3e2add280696aba3"
|
resolved "https://registry.yarnpkg.com/@rocket.chat/react-native-fast-image/-/react-native-fast-image-8.2.0.tgz#4f48858f95f40afcb10b39cee9b1239c150d6c51"
|
||||||
integrity sha512-ZjSt7NXiCkJ9KQr4b/b+mYgiwDAIGHfHdChgEU020C9sBbhSk6VxslqnfdZoAjxRW7doWMbhWkoYMjx2TnsGRw==
|
integrity sha512-NF5KlFt642ZucP/KHnYGBNYLD6O7bcrZMKfRQlH5Y3/1xpnPX1g4wuygtiV7XArMU1FopQT+qmCUPPj8IMDTcw==
|
||||||
|
|
||||||
"@rocket.chat/sdk@RocketChat/Rocket.Chat.js.SDK#mobile":
|
"@rocket.chat/sdk@RocketChat/Rocket.Chat.js.SDK#mobile":
|
||||||
version "1.0.0-mobile"
|
version "1.0.0-mobile"
|
||||||
|
@ -12990,10 +12990,9 @@ react-native-windows@^0.62.0-0:
|
||||||
uuid "^3.3.2"
|
uuid "^3.3.2"
|
||||||
xml-parser "^1.2.1"
|
xml-parser "^1.2.1"
|
||||||
|
|
||||||
react-native@^0.63.1:
|
react-native@RocketChat/react-native#0.63.2:
|
||||||
version "0.63.1"
|
version "0.63.2"
|
||||||
resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.63.1.tgz#af814c47fc6b9e938b1c3b11e9d1555b7e8c669b"
|
resolved "https://codeload.github.com/RocketChat/react-native/tar.gz/21bf1860837f2dc9d671727eb72e76a35e85599a"
|
||||||
integrity sha512-7SYBgLSu9p6uKPZIUEcAPGUe8a07UGi/2TdCWqkIazH6/2B93yuvDULAzyDT2hhSJPxUAvb6tGowoWVnZQQVtw==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.0.0"
|
"@babel/runtime" "^7.0.0"
|
||||||
"@react-native-community/cli" "^4.7.0"
|
"@react-native-community/cli" "^4.7.0"
|
||||||
|
|
Loading…
Reference in New Issue