diff --git a/android/app/build.gradle b/android/app/build.gradle index 160e678cfe..782d47dd62 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -144,7 +144,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode VERSIONCODE as Integer - versionName "4.12.1" + versionName "4.13.0" vectorDrawables.useSupportLibrary = true if (isPlay) { manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String] diff --git a/android/app/src/debug/java/chat/rocket/reactnative/ReactNativeFlipper.java b/android/app/src/debug/java/chat/rocket/reactnative/ReactNativeFlipper.java index 42eaf35c82..6ed1a4798a 100644 --- a/android/app/src/debug/java/chat/rocket/reactnative/ReactNativeFlipper.java +++ b/android/app/src/debug/java/chat/rocket/reactnative/ReactNativeFlipper.java @@ -21,6 +21,7 @@ import com.facebook.react.ReactInstanceManager; import com.facebook.react.bridge.ReactContext; import com.facebook.react.modules.network.NetworkingModule; +import com.facebook.react.modules.network.CustomClientBuilder; import okhttp3.OkHttpClient; public class ReactNativeFlipper { public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { @@ -33,7 +34,7 @@ public static void initializeFlipper(Context context, ReactInstanceManager react client.addPlugin(CrashReporterPlugin.getInstance()); NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin(); NetworkingModule.setCustomClientBuilder( - new NetworkingModule.CustomClientBuilder() { + new CustomClientBuilder() { @Override public void apply(OkHttpClient.Builder builder) { builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin)); diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 81171a699a..9815a9c9ed 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -12,6 +12,7 @@ android:icon="@mipmap/ic_launcher" android:theme="@style/AppTheme" android:networkSecurityConfig="@xml/network_security_config" + android:requestLegacyExternalStorage="true" android:allowBackup="false" tools:replace="android:allowBackup" > diff --git a/android/app/src/main/java/chat/rocket/reactnative/MainApplication.java b/android/app/src/main/java/chat/rocket/reactnative/MainApplication.java index 480089dbac..abad7458ac 100644 --- a/android/app/src/main/java/chat/rocket/reactnative/MainApplication.java +++ b/android/app/src/main/java/chat/rocket/reactnative/MainApplication.java @@ -38,6 +38,7 @@ protected List getPackages() { packages.add(new KeyboardInputPackage(MainApplication.this)); packages.add(new WatermelonDBPackage()); packages.add(new RNCViewPagerPackage()); + packages.add(new SSLPinningPackage()); List unimodules = Arrays.asList( new ModuleRegistryAdapter(mModuleRegistryProvider) ); diff --git a/android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningModule.java b/android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningModule.java new file mode 100644 index 0000000000..804e526016 --- /dev/null +++ b/android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningModule.java @@ -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; + } + } +} diff --git a/android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningPackage.java b/android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningPackage.java new file mode 100644 index 0000000000..f9d46cb35e --- /dev/null +++ b/android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningPackage.java @@ -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 createNativeModules(ReactApplicationContext reactContext) { + return Arrays.asList(new SSLPinningModule(reactContext)); + } + + @Override + public List createViewManagers(ReactApplicationContext reactContext) { + return Collections.emptyList(); + } +} diff --git a/android/build.gradle b/android/build.gradle index 02e17a043b..970864807b 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -12,7 +12,7 @@ buildscript { minSdkVersion = 23 compileSdkVersion = 29 targetSdkVersion = 29 - glideVersion = "4.9.0" + glideVersion = "4.11.0" kotlin_version = "1.3.50" supportLibVersion = "28.0.0" libre_build = !(isPlay.toBoolean()) diff --git a/app/actions/server.js b/app/actions/server.js index 01fb8c3e4d..a81e20f4db 100644 --- a/app/actions/server.js +++ b/app/actions/server.js @@ -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 { type: SERVER.REQUEST, server, - certificate, username, fromServerHistory }; diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js index d514ce9e4a..15f0209567 100644 --- a/app/lib/rocketchat.js +++ b/app/lib/rocketchat.js @@ -15,6 +15,7 @@ import database from './database'; import log from '../utils/log'; import { isIOS, getBundleId } from '../utils/deviceInfo'; import fetch from '../utils/fetch'; +import SSLPinning from '../utils/sslPinning'; import { encryptionInit } from '../actions/encryption'; import { setUser, setLoginServices, loginRequest } from '../actions/login'; @@ -63,6 +64,7 @@ import { sanitizeLikeString } from './database/utils'; const TOKEN_KEY = 'reactnativemeteor_usertoken'; const CURRENT_SERVER = 'currentServer'; 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 CRASH_REPORT_KEY = 'RC_CRASH_REPORT_KEY'; export const ANALYTICS_EVENTS_KEY = 'RC_ANALYTICS_EVENTS_KEY'; @@ -74,6 +76,7 @@ const STATUSES = ['offline', 'online', 'away', 'busy']; const RocketChat = { TOKEN_KEY, CURRENT_SERVER, + CERTIFICATE_KEY, callJitsi, async subscribeRooms() { if (!this.roomsSub) { @@ -312,6 +315,13 @@ const RocketChat = { async shareExtensionInit(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) { this.shareSDK.disconnect(); this.shareSDK = null; diff --git a/app/sagas/selectServer.js b/app/sagas/selectServer.js index 572600cf13..6ffc68bbc0 100644 --- a/app/sagas/selectServer.js +++ b/app/sagas/selectServer.js @@ -14,12 +14,12 @@ import { setUser } from '../actions/login'; import RocketChat from '../lib/rocketchat'; import database from '../lib/database'; import log, { logServerVersion } from '../utils/log'; -import { extractHostname } from '../utils/server'; import I18n from '../i18n'; import { BASIC_AUTH_KEY, setBasicAuth } from '../utils/fetch'; import { appStart, ROOT_INSIDE, ROOT_OUTSIDE } from '../actions/app'; import UserPreferences from '../lib/userPreferences'; import { encryptionStop } from '../actions/encryption'; +import SSLPinning from '../utils/sslPinning'; 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 }) { 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(encryptionStop()); const serversDB = database.servers; @@ -138,13 +142,11 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch } }; -const handleServerRequest = function* handleServerRequest({ - server, certificate, username, fromServerHistory -}) { +const handleServerRequest = function* handleServerRequest({ server, username, fromServerHistory }) { try { - if (certificate) { - yield UserPreferences.setMapAsync(extractHostname(server), certificate); - } + // 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); const serverInfo = yield getServerInfo({ server }); const serversDB = database.servers; diff --git a/app/static/images/logo.png b/app/static/images/logo.png index ed46c8477d..4ea18f530a 100644 Binary files a/app/static/images/logo.png and b/app/static/images/logo.png differ diff --git a/app/utils/sslPinning.js b/app/utils/sslPinning.js new file mode 100644 index 0000000000..00d807d4eb --- /dev/null +++ b/app/utils/sslPinning.js @@ -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; diff --git a/app/views/AttachmentView.js b/app/views/AttachmentView.js index 701e49ed7d..0246b8397f 100644 --- a/app/views/AttachmentView.js +++ b/app/views/AttachmentView.js @@ -4,7 +4,7 @@ import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import CameraRoll from '@react-native-community/cameraroll'; 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 SHA256 from 'js-sha256'; import { withSafeAreaInsets } from 'react-native-safe-area-context'; @@ -119,9 +119,9 @@ class AttachmentView extends React.Component { this.setState({ loading: true }); try { const extension = image_url ? `.${ mime.extension(image_type) || 'jpg' }` : `.${ mime.extension(video_type) || 'mp4' }`; - const file = `${ FileSystem.documentDirectory + SHA256(url) + extension }`; - const { uri } = await FileSystem.downloadAsync(mediaAttachment, file); - await CameraRoll.save(uri, { album: 'Rocket.Chat' }); + const path = `${ RNFetchBlob.fs.dirs.DocumentDir + SHA256(url) + extension }`; + const file = await RNFetchBlob.config({ path }).fetch('GET', mediaAttachment).then(res => res.path()); + await CameraRoll.save(file, { album: 'Rocket.Chat' }); EventEmitter.emit(LISTENER, { message: I18n.t('saved_to_gallery') }); } catch (e) { EventEmitter.emit(LISTENER, { message: I18n.t(image_url ? 'error-save-image' : 'error-save-video') }); diff --git a/app/views/NewServerView/index.js b/app/views/NewServerView/index.js index 693bd4262b..192468ed33 100644 --- a/app/views/NewServerView/index.js +++ b/app/views/NewServerView/index.js @@ -1,11 +1,9 @@ import React from 'react'; import PropTypes from 'prop-types'; import { - Text, Keyboard, StyleSheet, View, Alert, BackHandler + Text, Keyboard, StyleSheet, View, BackHandler } from 'react-native'; 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 parse from 'url-parse'; import { Q } from '@nozbe/watermelondb'; @@ -20,9 +18,8 @@ import Button from '../../containers/Button'; import OrSeparator from '../../containers/OrSeparator'; import FormContainer, { FormContainerInner } from '../../containers/FormContainer'; import I18n from '../../i18n'; -import { isIOS } from '../../utils/deviceInfo'; import { themes } from '../../constants/colors'; -import log, { logEvent, events } from '../../utils/log'; +import { logEvent, events } from '../../utils/log'; import { animateNextTransition } from '../../utils/layoutAnimation'; import { withTheme } from '../../theme'; import { setBasicAuth, BASIC_AUTH_KEY } from '../../utils/fetch'; @@ -31,6 +28,8 @@ import { showConfirmationAlert } from '../../utils/info'; import database from '../../lib/database'; import ServerInput from './ServerInput'; import { sanitizeLikeString } from '../../lib/database/utils'; +import SSLPinning from '../../utils/sslPinning'; +import RocketChat from '../../lib/rocketchat'; const styles = StyleSheet.create({ title: { @@ -178,32 +177,23 @@ class NewServerView extends React.Component { logEvent(events.NEWSERVER_CONNECT_TO_WORKSPACE); const { text, certificate } = this.state; const { connectServer } = this.props; - let cert = null; 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) { Keyboard.dismiss(); 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); + if (fromServerHistory) { - connectServer(server, cert, username, true); + connectServer(server, username, true); } else { - connectServer(server, cert); + connectServer(server); } } } @@ -230,25 +220,10 @@ class NewServerView extends React.Component { chooseCertificate = async() => { try { - const res = await DocumentPicker.pick({ - type: ['com.rsa.pkcs-12'] - }); - const { uri: path, name } = res; - 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); - } + const certificate = await SSLPinning.pickCertificate(); + this.setState({ certificate }); + } catch { + // Do nothing } } @@ -327,7 +302,7 @@ class NewServerView extends React.Component { { color: themes[theme].tintColor } ]} > - {certificate ? certificate.name : I18n.t('Apply_Your_Certificate')} + {certificate ?? I18n.t('Apply_Your_Certificate')} @@ -379,7 +354,7 @@ class NewServerView extends React.Component { testID='new-server-view-open' /> - {isIOS ? this.renderCertificatePicker() : null} + {this.renderCertificatePicker()} ); } @@ -392,7 +367,7 @@ const mapStateToProps = state => ({ }); const mapDispatchToProps = dispatch => ({ - connectServer: (server, certificate, username, fromServerHistory) => dispatch(serverRequest(server, certificate, username, fromServerHistory)), + connectServer: (...params) => dispatch(serverRequest(...params)), selectServer: server => dispatch(selectServerRequest(server)), inviteLinksClear: () => dispatch(inviteLinksClearAction()) }); diff --git a/ios/RocketChatRN.xcodeproj/project.pbxproj b/ios/RocketChatRN.xcodeproj/project.pbxproj index 648bbcfdca..b9004681a9 100644 --- a/ios/RocketChatRN.xcodeproj/project.pbxproj +++ b/ios/RocketChatRN.xcodeproj/project.pbxproj @@ -1396,7 +1396,7 @@ INFOPLIST_FILE = NotificationService/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 4.12.1; + MARKETING_VERSION = 4.13.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = chat.rocket.reactnative.NotificationService; @@ -1433,7 +1433,7 @@ INFOPLIST_FILE = NotificationService/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 4.12.1; + MARKETING_VERSION = 4.13.0; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = chat.rocket.reactnative.NotificationService; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/ios/RocketChatRN/Info.plist b/ios/RocketChatRN/Info.plist index 7f15574964..e0cf557119 100644 --- a/ios/RocketChatRN/Info.plist +++ b/ios/RocketChatRN/Info.plist @@ -21,7 +21,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 4.12.1 + 4.13.0 CFBundleSignature ???? CFBundleURLTypes diff --git a/ios/ShareRocketChatRN/Info.plist b/ios/ShareRocketChatRN/Info.plist index 74ae15732c..60da51fe8c 100644 --- a/ios/ShareRocketChatRN/Info.plist +++ b/ios/ShareRocketChatRN/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 4.12.1 + 4.13.0 CFBundleVersion 1 NSAppTransportSecurity diff --git a/package.json b/package.json index bcd353755f..6a5cd112ff 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "@react-navigation/drawer": "5.8.5", "@react-navigation/native": "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/ui-kit": "0.13.0", "bugsnag-react-native": "2.23.10", @@ -68,7 +68,7 @@ "prop-types": "15.7.2", "react": "16.13.1", "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-appearance": "0.3.4", "react-native-background-timer": "2.2.0", diff --git a/patches/expo-av+8.2.1.patch b/patches/expo-av+8.2.1.patch new file mode 100644 index 0000000000..cb497f4b89 --- /dev/null +++ b/patches/expo-av+8.2.1.patch @@ -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 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)); + } + diff --git a/patches/react-native+0.63.1.patch b/patches/react-native+0.63.1.patch deleted file mode 100644 index 6d3ce0c3e8..0000000000 --- a/patches/react-native+0.63.1.patch +++ /dev/null @@ -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>, - |}>; -@@ -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 - - #import -- -+#import - #import - #import -+#import - - #import "RCTNetworkPlugins.h" -+#import "SecureStorage.h" - - @interface RCTHTTPRequestHandler () - -@@ -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)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 - #import - -+#import -+#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 - - @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 diff --git a/patches/react-native-webview+10.3.2.patch b/patches/react-native-webview+10.3.2.patch index a47d8fbb60..68412ee27d 100644 --- a/patches/react-native-webview+10.3.2.patch +++ b/patches/react-native-webview+10.3.2.patch @@ -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} + *

+@@ -140,6 +146,8 @@ public class RNCWebViewManager extends SimpleViewManager { + 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 { + 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 { + @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 { + + 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 task = new AsyncTask() { ++ @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 index 02b4238..04bad05 100644 --- a/node_modules/react-native-webview/apple/RNCWebView.m diff --git a/patches/rn-fetch-blob+0.12.0.patch b/patches/rn-fetch-blob+0.12.0.patch index b28e8ae8fb..c1e809423e 100644 --- a/patches/rn-fetch-blob+0.12.0.patch +++ b/patches/rn-fetch-blob+0.12.0.patch @@ -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 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 index cdbe6b1..bee6228 100644 --- a/node_modules/rn-fetch-blob/ios/RNFetchBlobRequest.m diff --git a/yarn.lock b/yarn.lock index 1995ad98c8..7d0639777b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2233,10 +2233,10 @@ resolved "https://registry.yarnpkg.com/@redux-saga/types/-/types-1.1.0.tgz#0e81ce56b4883b4b2a3001ebe1ab298b84237204" integrity sha512-afmTuJrylUU/0OtqzaRkbyYFFNgCF73Bvel/sw90pvGrWIZ+vyoIJqA6eMSoA6+nb443kTmulmBtC9NerXboNg== -"@rocket.chat/react-native-fast-image@^8.1.5": - version "8.1.5" - resolved "https://registry.yarnpkg.com/@rocket.chat/react-native-fast-image/-/react-native-fast-image-8.1.5.tgz#325d80ebb351fb024436093b3e2add280696aba3" - integrity sha512-ZjSt7NXiCkJ9KQr4b/b+mYgiwDAIGHfHdChgEU020C9sBbhSk6VxslqnfdZoAjxRW7doWMbhWkoYMjx2TnsGRw== +"@rocket.chat/react-native-fast-image@^8.2.0": + version "8.2.0" + resolved "https://registry.yarnpkg.com/@rocket.chat/react-native-fast-image/-/react-native-fast-image-8.2.0.tgz#4f48858f95f40afcb10b39cee9b1239c150d6c51" + integrity sha512-NF5KlFt642ZucP/KHnYGBNYLD6O7bcrZMKfRQlH5Y3/1xpnPX1g4wuygtiV7XArMU1FopQT+qmCUPPj8IMDTcw== "@rocket.chat/sdk@RocketChat/Rocket.Chat.js.SDK#mobile": version "1.0.0-mobile" @@ -12990,10 +12990,9 @@ react-native-windows@^0.62.0-0: uuid "^3.3.2" xml-parser "^1.2.1" -react-native@^0.63.1: - version "0.63.1" - resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.63.1.tgz#af814c47fc6b9e938b1c3b11e9d1555b7e8c669b" - integrity sha512-7SYBgLSu9p6uKPZIUEcAPGUe8a07UGi/2TdCWqkIazH6/2B93yuvDULAzyDT2hhSJPxUAvb6tGowoWVnZQQVtw== +react-native@RocketChat/react-native#0.63.2: + version "0.63.2" + resolved "https://codeload.github.com/RocketChat/react-native/tar.gz/21bf1860837f2dc9d671727eb72e76a35e85599a" dependencies: "@babel/runtime" "^7.0.0" "@react-native-community/cli" "^4.7.0"