From 3fe5e6c85a6d9bccb6fff6de9055d398c49520f7 Mon Sep 17 00:00:00 2001 From: SK JASIMUDDIN <94641150+JASIM0021@users.noreply.github.com> Date: Tue, 23 Jul 2024 09:41:06 -0700 Subject: [PATCH 1/4] fix: Link preview should not enlarge small images (#5789) Co-authored-by: Diego Mello --- app/containers/message/Urls.tsx | 44 +++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/app/containers/message/Urls.tsx b/app/containers/message/Urls.tsx index 3904ff9785..f2b26625d7 100644 --- a/app/containers/message/Urls.tsx +++ b/app/containers/message/Urls.tsx @@ -1,5 +1,5 @@ -import React, { useContext, useState } from 'react'; -import { StyleSheet, Text, View } from 'react-native'; +import React, { useContext, useEffect, useState } from 'react'; +import { Image, StyleSheet, Text, unstable_batchedUpdates, View } from 'react-native'; import Clipboard from '@react-native-clipboard/clipboard'; import FastImage from 'react-native-fast-image'; import { dequal } from 'dequal'; @@ -94,11 +94,27 @@ type TImageLoadedState = 'loading' | 'done' | 'error'; const Url = React.memo( ({ url, index, theme }: { url: IUrl; index: number; theme: TSupportedThemes }) => { const [imageLoadedState, setImageLoadedState] = useState('loading'); + const [imageDimensions, setImageDimensions] = useState({ width: 0, height: 0 }); const { baseUrl, user } = useContext(MessageContext); - - if (!url || url?.ignoreParse || imageLoadedState === 'error') { - return null; - } + let image = url.image || url.url; + image = image.includes('http') ? image : `${baseUrl}/${image}?rc_uid=${user.id}&rc_token=${user.token}`; + + useEffect(() => { + if (image) { + Image.getSize( + image, + (width, height) => { + unstable_batchedUpdates(() => { + setImageDimensions({ width, height }); + setImageLoadedState('done'); + }); + }, + () => { + setImageLoadedState('error'); + } + ); + } + }, [image]); const onPress = () => openLink(url.url, theme); @@ -109,9 +125,8 @@ const Url = React.memo( const hasContent = url.title || url.description; - let image = url.image || url.url; - if (image) { - image = image.includes('http') ? image : `${baseUrl}/${image}?rc_uid=${user.id}&rc_token=${user.token}`; + if (!url || url?.ignoreParse || imageLoadedState === 'error' || !imageDimensions.width || !imageDimensions.height) { + return null; } return ( @@ -128,14 +143,17 @@ const Url = React.memo( }, imageLoadedState === 'loading' && styles.loading ]} - background={Touchable.Ripple(themes[theme].surfaceNeutral)} - > + background={Touchable.Ripple(themes[theme].surfaceNeutral)}> <> {image ? ( setImageLoadedState('error')} onLoad={() => setImageLoadedState('done')} /> From 80b9c354e2bd8b940782e4780fef17f3b24343c8 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Tue, 23 Jul 2024 16:14:40 -0300 Subject: [PATCH 2/4] fix: Increase random E2EE password security (#5805) --- app/lib/encryption/encryption.ts | 4 +- app/lib/encryption/utils.ts | 7 +- ios/Podfile.lock | 4 +- ios/RocketChatRN.xcodeproj/project.pbxproj | 304 +++++++++--------- package.json | 2 +- .../react-native-simple-crypto+0.6.0.patch | 293 ----------------- yarn.lock | 6 +- 7 files changed, 165 insertions(+), 455 deletions(-) delete mode 100644 patches/react-native-simple-crypto+0.6.0.patch diff --git a/app/lib/encryption/encryption.ts b/app/lib/encryption/encryption.ts index 128233bd1c..01972b2be1 100644 --- a/app/lib/encryption/encryption.ts +++ b/app/lib/encryption/encryption.ts @@ -209,8 +209,8 @@ class Encryption { }; // Create a random password to local created keys - createRandomPassword = (server: string) => { - const password = randomPassword(); + createRandomPassword = async (server: string) => { + const password = await randomPassword(); UserPreferences.setString(`${server}-${E2E_RANDOM_PASSWORD_KEY}`, password); return password; }; diff --git a/app/lib/encryption/utils.ts b/app/lib/encryption/utils.ts index a36e62c4ca..dbc7447f2e 100644 --- a/app/lib/encryption/utils.ts +++ b/app/lib/encryption/utils.ts @@ -1,7 +1,7 @@ import ByteBuffer from 'bytebuffer'; import SimpleCrypto from 'react-native-simple-crypto'; -import { compareServerVersion, random } from '../methods/helpers'; +import { compareServerVersion } from '../methods/helpers'; import { fromByteArray, toByteArray } from './helpers/base64-js'; import { TSubscriptionModel } from '../../definitions'; import { store } from '../store/auxStore'; @@ -59,7 +59,10 @@ export const toString = (thing: string | ByteBuffer | Buffer | ArrayBuffer | Uin // @ts-ignore return new ByteBuffer.wrap(thing).toString('binary'); }; -export const randomPassword = (): string => `${random(3)}-${random(3)}-${random(3)}`.toLowerCase(); +export const randomPassword = async (): Promise => { + const random = await Promise.all(Array.from({ length: 4 }, () => SimpleCrypto.utils.getRandomValues(3))); + return `${random[0]}-${random[1]}-${random[2]}-${random[3]}`; +}; export const generateAESCTRKey = () => SimpleCrypto.utils.randomBytes(32); diff --git a/ios/Podfile.lock b/ios/Podfile.lock index d22d9c8eed..a963bbf161 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1053,7 +1053,7 @@ PODS: - React-Core - react-native-safe-area-context (3.2.0): - React-Core - - react-native-simple-crypto (0.6.0): + - react-native-simple-crypto (0.6.1): - OpenSSL-Universal (= 1.1.1100) - React - react-native-slider (4.5.0): @@ -1730,7 +1730,7 @@ SPEC CHECKSUMS: react-native-orientation-locker: f0ca1a8e5031dab6b74bfb4ab33a17ed2c2fcb0d react-native-restart: 733a51ad137f15b0f8dc34c4082e55af7da00979 react-native-safe-area-context: f0906bf8bc9835ac9a9d3f97e8bde2a997d8da79 - react-native-simple-crypto: 83eb246059b5bfce7e6a96bf24569a0a98e92d74 + react-native-simple-crypto: 663609d550ba052dd6ee5eef9954bac274736576 react-native-slider: 09e5a8b7e766d3b5ae24ec15c5c4ec2679ca0f8c react-native-webview: 9f111dfbcfc826084d6c507f569e5e03342ee1c1 React-nativeconfig: b4d4e9901d4cabb57be63053fd2aa6086eb3c85f diff --git a/ios/RocketChatRN.xcodeproj/project.pbxproj b/ios/RocketChatRN.xcodeproj/project.pbxproj index b3d3d0c02d..09c59dc5fa 100644 --- a/ios/RocketChatRN.xcodeproj/project.pbxproj +++ b/ios/RocketChatRN.xcodeproj/project.pbxproj @@ -283,7 +283,9 @@ 1EFEB5982493B6640072EDC0 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EFEB5972493B6640072EDC0 /* NotificationService.swift */; }; 1EFEB59C2493B6640072EDC0 /* NotificationService.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 1EFEB5952493B6640072EDC0 /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 24A2AEF2383D44B586D31C01 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 06BB44DD4855498082A744AD /* libz.tbd */; }; + 31F3B964C6AC566E0EF5AECA /* libPods-defaults-RocketChatRN.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 350CD3DA54F8CDCA236F25BB /* libPods-defaults-RocketChatRN.a */; }; 4C4C8603EF082F0A33A95522 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45D5C142B655F8EFD006792C /* ExpoModulesProvider.swift */; }; + 54EE6E5BF0FD714BF1E75A69 /* libPods-defaults-Rocket.Chat.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CB24210293E6F5D282BAF268 /* libPods-defaults-Rocket.Chat.a */; }; 65AD38372BFBDF4A00271B39 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 65AD38362BFBDF4A00271B39 /* PrivacyInfo.xcprivacy */; }; 65AD38382BFBDF4A00271B39 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 65AD38362BFBDF4A00271B39 /* PrivacyInfo.xcprivacy */; }; 65AD38392BFBDF4A00271B39 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 65AD38362BFBDF4A00271B39 /* PrivacyInfo.xcprivacy */; }; @@ -292,8 +294,7 @@ 65AD383C2BFBDF4A00271B39 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 65AD38362BFBDF4A00271B39 /* PrivacyInfo.xcprivacy */; }; 65B9A71A2AFC24190088956F /* ringtone.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 65B9A7192AFC24190088956F /* ringtone.mp3 */; }; 65B9A71B2AFC24190088956F /* ringtone.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 65B9A7192AFC24190088956F /* ringtone.mp3 */; }; - 6886EA443FB173DAC09A8CC1 /* libPods-defaults-ShareRocketChatRN.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 094A928A54F8F7D6362EBDE8 /* libPods-defaults-ShareRocketChatRN.a */; }; - 6A155BB8D100C38441B462E9 /* libPods-defaults-NotificationService.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5540801CC0131C98DD520BC4 /* libPods-defaults-NotificationService.a */; }; + 77030DD9D7DDAF90EFF935CB /* libPods-defaults-ShareRocketChatRN.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BB540D897AFCDCC5C961EEDD /* libPods-defaults-ShareRocketChatRN.a */; }; 7A006F14229C83B600803143 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7A006F13229C83B600803143 /* GoogleService-Info.plist */; }; 7A0D62D2242AB187006D5C06 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7A0D62D1242AB187006D5C06 /* LaunchScreen.storyboard */; }; 7A14FCED257FEB3A005BDCD4 /* Experimental.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7A14FCEC257FEB3A005BDCD4 /* Experimental.xcassets */; }; @@ -356,8 +357,7 @@ 7AE10C0728A59530003593CB /* Inter.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7AE10C0528A59530003593CB /* Inter.ttf */; }; 7AE10C0828A59530003593CB /* Inter.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7AE10C0528A59530003593CB /* Inter.ttf */; }; 85160EB6C143E0493FE5F014 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 194D9A8897F4A486C2C6F89A /* ExpoModulesProvider.swift */; }; - 91E821B2387EA7F67457F07F /* libPods-defaults-RocketChatRN.a in Frameworks */ = {isa = PBXBuildFile; fileRef = AF70C4EAFD06BC1061EFEA05 /* libPods-defaults-RocketChatRN.a */; }; - 9F520B3A05B8C19BB3A51B79 /* libPods-defaults-Rocket.Chat.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F3D58284790D6A3A4CE28473 /* libPods-defaults-Rocket.Chat.a */; }; + AD9AF13B0FADB6545AE46216 /* libPods-defaults-NotificationService.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F1F9B109DFF41F259095C748 /* libPods-defaults-NotificationService.a */; }; BC404914E86821389EEB543D /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 391C4F7AA7023CD41EEBD106 /* ExpoModulesProvider.swift */; }; D94D81FB9E10756FAA03F203 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 016747EF3B9FED8DE2C9DA14 /* ExpoModulesProvider.swift */; }; DD2BA30A89E64F189C2C24AC /* libWatermelonDB.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BA7E862283664608B3894E34 /* libWatermelonDB.a */; }; @@ -461,13 +461,13 @@ 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = ""; }; 016747EF3B9FED8DE2C9DA14 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-defaults-ShareRocketChatRN/ExpoModulesProvider.swift"; sourceTree = ""; }; 06BB44DD4855498082A744AD /* libz.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; - 094A928A54F8F7D6362EBDE8 /* libPods-defaults-ShareRocketChatRN.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-ShareRocketChatRN.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 0D20E5D2078C79720E1DBC03 /* Pods-defaults-Rocket.Chat.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-Rocket.Chat.release.xcconfig"; path = "Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat.release.xcconfig"; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* Rocket.Chat Experimental.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Rocket.Chat Experimental.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = RocketChatRN/AppDelegate.h; sourceTree = ""; }; 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = RocketChatRN/Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = RocketChatRN/Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = RocketChatRN/main.m; sourceTree = ""; }; + 1525225C45AF517E68DCDC2D /* Pods-defaults-ShareRocketChatRN.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-ShareRocketChatRN.debug.xcconfig"; path = "Target Support Files/Pods-defaults-ShareRocketChatRN/Pods-defaults-ShareRocketChatRN.debug.xcconfig"; sourceTree = ""; }; + 17115A1EE86DB89DBE1912EB /* Pods-defaults-Rocket.Chat.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-Rocket.Chat.debug.xcconfig"; path = "Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat.debug.xcconfig"; sourceTree = ""; }; 194D9A8897F4A486C2C6F89A /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-defaults-NotificationService/ExpoModulesProvider.swift"; sourceTree = ""; }; 1E01C81B2511208400FEF824 /* URL+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Extensions.swift"; sourceTree = ""; }; 1E01C8202511301400FEF824 /* PushResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushResponse.swift; sourceTree = ""; }; @@ -614,11 +614,12 @@ 1EFEB5972493B6640072EDC0 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; 1EFEB5992493B6640072EDC0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 1EFEB5A12493B67D0072EDC0 /* NotificationService.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NotificationService.entitlements; sourceTree = ""; }; + 307B625411539125ECBAA6EA /* Pods-defaults-RocketChatRN.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-RocketChatRN.debug.xcconfig"; path = "Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN.debug.xcconfig"; sourceTree = ""; }; + 350CD3DA54F8CDCA236F25BB /* libPods-defaults-RocketChatRN.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-RocketChatRN.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 391C4F7AA7023CD41EEBD106 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-defaults-Rocket.Chat/ExpoModulesProvider.swift"; sourceTree = ""; }; - 3AA8F7DEB0B31A5A6329FAF5 /* Pods-defaults-RocketChatRN.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-RocketChatRN.release.xcconfig"; path = "Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN.release.xcconfig"; sourceTree = ""; }; 45D5C142B655F8EFD006792C /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-defaults-RocketChatRN/ExpoModulesProvider.swift"; sourceTree = ""; }; - 5540801CC0131C98DD520BC4 /* libPods-defaults-NotificationService.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-NotificationService.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 60B2A6A31FC4588700BD58E5 /* RocketChatRN.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = RocketChatRN.entitlements; path = RocketChatRN/RocketChatRN.entitlements; sourceTree = ""; }; + 628DE8E64E84BCF109FC4969 /* Pods-defaults-NotificationService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-NotificationService.release.xcconfig"; path = "Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService.release.xcconfig"; sourceTree = ""; }; 65AD38362BFBDF4A00271B39 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 65B9A7192AFC24190088956F /* ringtone.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = ringtone.mp3; sourceTree = ""; }; 7A006F13229C83B600803143 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; @@ -633,16 +634,15 @@ 7AAB3E52257E6A6E00707CF6 /* Rocket.Chat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Rocket.Chat.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7ACD4853222860DE00442C55 /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; 7AE10C0528A59530003593CB /* Inter.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Inter.ttf; sourceTree = ""; }; - AF2F39716BB7E569448DF293 /* Pods-defaults-ShareRocketChatRN.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-ShareRocketChatRN.debug.xcconfig"; path = "Target Support Files/Pods-defaults-ShareRocketChatRN/Pods-defaults-ShareRocketChatRN.debug.xcconfig"; sourceTree = ""; }; - AF70C4EAFD06BC1061EFEA05 /* libPods-defaults-RocketChatRN.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-RocketChatRN.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - B0E90AAFBA1A95385AE7C19D /* Pods-defaults-RocketChatRN.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-RocketChatRN.debug.xcconfig"; path = "Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN.debug.xcconfig"; sourceTree = ""; }; - B1C2FD3D7D6F056737CCEAE0 /* Pods-defaults-NotificationService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-NotificationService.release.xcconfig"; path = "Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService.release.xcconfig"; sourceTree = ""; }; + 7BEF39A6613B871D0A3326E1 /* Pods-defaults-Rocket.Chat.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-Rocket.Chat.release.xcconfig"; path = "Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat.release.xcconfig"; sourceTree = ""; }; B37C79D9BD0742CE936B6982 /* libc++.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; + B41392725C2EA46A36F1FFB1 /* Pods-defaults-ShareRocketChatRN.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-ShareRocketChatRN.release.xcconfig"; path = "Target Support Files/Pods-defaults-ShareRocketChatRN/Pods-defaults-ShareRocketChatRN.release.xcconfig"; sourceTree = ""; }; + B5F46E5C11E602B3F99D4E40 /* Pods-defaults-NotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-NotificationService.debug.xcconfig"; path = "Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService.debug.xcconfig"; sourceTree = ""; }; BA7E862283664608B3894E34 /* libWatermelonDB.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libWatermelonDB.a; sourceTree = ""; }; - C2909259346452596AD27AD5 /* Pods-defaults-ShareRocketChatRN.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-ShareRocketChatRN.release.xcconfig"; path = "Target Support Files/Pods-defaults-ShareRocketChatRN/Pods-defaults-ShareRocketChatRN.release.xcconfig"; sourceTree = ""; }; - E2836E70B71C86AAA7EEB0F6 /* Pods-defaults-NotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-NotificationService.debug.xcconfig"; path = "Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService.debug.xcconfig"; sourceTree = ""; }; - F3D58284790D6A3A4CE28473 /* libPods-defaults-Rocket.Chat.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-Rocket.Chat.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - FE7279F2D432C9CAB7379496 /* Pods-defaults-Rocket.Chat.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-Rocket.Chat.debug.xcconfig"; path = "Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat.debug.xcconfig"; sourceTree = ""; }; + BB540D897AFCDCC5C961EEDD /* libPods-defaults-ShareRocketChatRN.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-ShareRocketChatRN.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + CB24210293E6F5D282BAF268 /* libPods-defaults-Rocket.Chat.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-Rocket.Chat.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + D944D97B3EF00C39202CC81D /* Pods-defaults-RocketChatRN.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-defaults-RocketChatRN.release.xcconfig"; path = "Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN.release.xcconfig"; sourceTree = ""; }; + F1F9B109DFF41F259095C748 /* libPods-defaults-NotificationService.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-NotificationService.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -663,7 +663,7 @@ 7ACD4897222860DE00442C55 /* JavaScriptCore.framework in Frameworks */, 24A2AEF2383D44B586D31C01 /* libz.tbd in Frameworks */, DD2BA30A89E64F189C2C24AC /* libWatermelonDB.a in Frameworks */, - 91E821B2387EA7F67457F07F /* libPods-defaults-RocketChatRN.a in Frameworks */, + 31F3B964C6AC566E0EF5AECA /* libPods-defaults-RocketChatRN.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -672,7 +672,7 @@ buildActionMask = 2147483647; files = ( 1E25743422CBA2CF005A877F /* JavaScriptCore.framework in Frameworks */, - 6886EA443FB173DAC09A8CC1 /* libPods-defaults-ShareRocketChatRN.a in Frameworks */, + 77030DD9D7DDAF90EFF935CB /* libPods-defaults-ShareRocketChatRN.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -694,7 +694,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 6A155BB8D100C38441B462E9 /* libPods-defaults-NotificationService.a in Frameworks */, + AD9AF13B0FADB6545AE46216 /* libPods-defaults-NotificationService.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -715,7 +715,7 @@ 7AAB3E3D257E6A6E00707CF6 /* JavaScriptCore.framework in Frameworks */, 7AAB3E3E257E6A6E00707CF6 /* libz.tbd in Frameworks */, 7AAB3E3F257E6A6E00707CF6 /* libWatermelonDB.a in Frameworks */, - 9F520B3A05B8C19BB3A51B79 /* libPods-defaults-Rocket.Chat.a in Frameworks */, + 54EE6E5BF0FD714BF1E75A69 /* libPods-defaults-Rocket.Chat.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1148,14 +1148,14 @@ 7AC2B09613AA7C3FEBAC9F57 /* Pods */ = { isa = PBXGroup; children = ( - E2836E70B71C86AAA7EEB0F6 /* Pods-defaults-NotificationService.debug.xcconfig */, - B1C2FD3D7D6F056737CCEAE0 /* Pods-defaults-NotificationService.release.xcconfig */, - FE7279F2D432C9CAB7379496 /* Pods-defaults-Rocket.Chat.debug.xcconfig */, - 0D20E5D2078C79720E1DBC03 /* Pods-defaults-Rocket.Chat.release.xcconfig */, - B0E90AAFBA1A95385AE7C19D /* Pods-defaults-RocketChatRN.debug.xcconfig */, - 3AA8F7DEB0B31A5A6329FAF5 /* Pods-defaults-RocketChatRN.release.xcconfig */, - AF2F39716BB7E569448DF293 /* Pods-defaults-ShareRocketChatRN.debug.xcconfig */, - C2909259346452596AD27AD5 /* Pods-defaults-ShareRocketChatRN.release.xcconfig */, + B5F46E5C11E602B3F99D4E40 /* Pods-defaults-NotificationService.debug.xcconfig */, + 628DE8E64E84BCF109FC4969 /* Pods-defaults-NotificationService.release.xcconfig */, + 17115A1EE86DB89DBE1912EB /* Pods-defaults-Rocket.Chat.debug.xcconfig */, + 7BEF39A6613B871D0A3326E1 /* Pods-defaults-Rocket.Chat.release.xcconfig */, + 307B625411539125ECBAA6EA /* Pods-defaults-RocketChatRN.debug.xcconfig */, + D944D97B3EF00C39202CC81D /* Pods-defaults-RocketChatRN.release.xcconfig */, + 1525225C45AF517E68DCDC2D /* Pods-defaults-ShareRocketChatRN.debug.xcconfig */, + B41392725C2EA46A36F1FFB1 /* Pods-defaults-ShareRocketChatRN.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -1251,10 +1251,10 @@ 7ACD4853222860DE00442C55 /* JavaScriptCore.framework */, B37C79D9BD0742CE936B6982 /* libc++.tbd */, 06BB44DD4855498082A744AD /* libz.tbd */, - 5540801CC0131C98DD520BC4 /* libPods-defaults-NotificationService.a */, - F3D58284790D6A3A4CE28473 /* libPods-defaults-Rocket.Chat.a */, - AF70C4EAFD06BC1061EFEA05 /* libPods-defaults-RocketChatRN.a */, - 094A928A54F8F7D6362EBDE8 /* libPods-defaults-ShareRocketChatRN.a */, + F1F9B109DFF41F259095C748 /* libPods-defaults-NotificationService.a */, + CB24210293E6F5D282BAF268 /* libPods-defaults-Rocket.Chat.a */, + 350CD3DA54F8CDCA236F25BB /* libPods-defaults-RocketChatRN.a */, + BB540D897AFCDCC5C961EEDD /* libPods-defaults-ShareRocketChatRN.a */, ); name = Frameworks; sourceTree = ""; @@ -1274,7 +1274,7 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "RocketChatRN" */; buildPhases = ( - 8C825AFA26B3E74DB80133EA /* [CP] Check Pods Manifest.lock */, + 3253E020357DE5DF3FF75C31 /* [CP] Check Pods Manifest.lock */, 7AA5C63E23E30D110005C4A7 /* Start Packager */, 589729E8381BA997CD19EF19 /* [Expo] Configure project */, 13B07F871A680F5B00A75B9A /* Sources */, @@ -1286,8 +1286,8 @@ 1ED0389C2B507B4F00C007D4 /* Embed Watch Content */, 7AAE9EB32891A0D20024F559 /* Upload source maps to Bugsnag */, 407D3EDE3DABEE15D27BD87D /* ShellScript */, - F973C9D58CCED6F67ADA2E0E /* [CP] Embed Pods Frameworks */, - BACE710E33AE584582B19BD0 /* [CP] Copy Pods Resources */, + 19D140A2EC74F6659DF82620 /* [CP] Embed Pods Frameworks */, + BD50494BAB9A4D228C650E81 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -1305,13 +1305,13 @@ isa = PBXNativeTarget; buildConfigurationList = 1EC6ACF322CB9FC300A41C61 /* Build configuration list for PBXNativeTarget "ShareRocketChatRN" */; buildPhases = ( - 277AAB0621E317E8191F8D3D /* [CP] Check Pods Manifest.lock */, + 36BEC951447AB2B3D07D4BCA /* [CP] Check Pods Manifest.lock */, 2C50632AB476A038AFCB1D43 /* [Expo] Configure project */, 1EC6ACAC22CB9FC300A41C61 /* Sources */, 1EC6ACAD22CB9FC300A41C61 /* Frameworks */, 1EC6ACAE22CB9FC300A41C61 /* Resources */, 1EFE4DC322CBF36300B766B7 /* ShellScript */, - E4BD2A0C2451C64208B69644 /* [CP] Copy Pods Resources */, + 9D37CD62B427968B0C843657 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -1360,12 +1360,12 @@ isa = PBXNativeTarget; buildConfigurationList = 1EFEB5A02493B6640072EDC0 /* Build configuration list for PBXNativeTarget "NotificationService" */; buildPhases = ( - 149B5E46E002534412D175D7 /* [CP] Check Pods Manifest.lock */, + 39E6E20ED4D99EB6D79F1BCA /* [CP] Check Pods Manifest.lock */, 86A998705576AFA7CE938617 /* [Expo] Configure project */, 1EFEB5912493B6640072EDC0 /* Sources */, 1EFEB5922493B6640072EDC0 /* Frameworks */, 1EFEB5932493B6640072EDC0 /* Resources */, - A0BF2C82F66B14B8F59589FE /* [CP] Copy Pods Resources */, + BA9071ABCBC2655C0EF5124C /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -1380,7 +1380,7 @@ isa = PBXNativeTarget; buildConfigurationList = 7AAB3E4F257E6A6E00707CF6 /* Build configuration list for PBXNativeTarget "Rocket.Chat" */; buildPhases = ( - 70428352B0FB6FE6DF9D6032 /* [CP] Check Pods Manifest.lock */, + A7C23C0F63F7E514868AE885 /* [CP] Check Pods Manifest.lock */, 7AAB3E13257E6A6E00707CF6 /* Start Packager */, 6723DBD924B66933E14E7EF7 /* [Expo] Configure project */, 7AAB3E14257E6A6E00707CF6 /* Sources */, @@ -1391,8 +1391,8 @@ 7AAB3E4B257E6A6E00707CF6 /* ShellScript */, 1ED1ECE32B8699DD00F6620C /* Embed Watch Content */, 7A10288726B1D15200E47EF8 /* Upload source maps to Bugsnag */, - 9BE1CEB599E7DFFA7C004B9F /* [CP] Embed Pods Frameworks */, - 9D65115AE1DFF56CD734F04F /* [CP] Copy Pods Resources */, + 81C37052831D3F058FF2B0D4 /* [CP] Embed Pods Frameworks */, + 88DC6B30E9DF0B361032CC1B /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -1586,26 +1586,24 @@ shellPath = /bin/sh; shellScript = ". ~/.nvm/nvm.sh\nexport EXTRA_PACKAGER_ARGS=\"--sourcemap-output $TMPDIR/$(md5 -qs \"$CONFIGURATION_BUILD_DIR\")-main.jsbundle.map\"\nexport NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh\n"; }; - 149B5E46E002534412D175D7 /* [CP] Check Pods Manifest.lock */ = { + 19D140A2EC74F6659DF82620 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", ); + name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-defaults-NotificationService-checkManifestLockResult.txt", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 1E1EA8082326CCE300E22452 /* ShellScript */ = { @@ -1642,7 +1640,48 @@ shellPath = /bin/sh; shellScript = ". ~/.nvm/nvm.sh\nexport EXTRA_PACKAGER_ARGS=\"--sourcemap-output $TMPDIR/$(md5 -qs \"$CONFIGURATION_BUILD_DIR\")-main.jsbundle.map\"\nexport NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh\n"; }; - 277AAB0621E317E8191F8D3D /* [CP] Check Pods Manifest.lock */ = { + 2C50632AB476A038AFCB1D43 /* [Expo] Configure project */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "[Expo] Configure project"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-defaults-ShareRocketChatRN/expo-configure-project.sh\"\n"; + }; + 3253E020357DE5DF3FF75C31 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-defaults-RocketChatRN-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 36BEC951447AB2B3D07D4BCA /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1664,24 +1703,27 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 2C50632AB476A038AFCB1D43 /* [Expo] Configure project */ = { + 39E6E20ED4D99EB6D79F1BCA /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); - name = "[Expo] Configure project"; + name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-defaults-NotificationService-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-defaults-ShareRocketChatRN/expo-configure-project.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; }; 407D3EDE3DABEE15D27BD87D /* ShellScript */ = { isa = PBXShellScriptBuildPhase; @@ -1739,28 +1781,6 @@ shellPath = /bin/sh; shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-defaults-Rocket.Chat/expo-configure-project.sh\"\n"; }; - 70428352B0FB6FE6DF9D6032 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-defaults-Rocket.Chat-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; 7A10288726B1D15200E47EF8 /* Upload source maps to Bugsnag */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -1866,68 +1886,46 @@ shellPath = /bin/sh; shellScript = "SOURCE_MAP=\"$TMPDIR/$(md5 -qs \"$CONFIGURATION_BUILD_DIR\")-main.jsbundle.map\" ../node_modules/@bugsnag/react-native/bugsnag-react-native-xcode.sh\n"; }; - 86A998705576AFA7CE938617 /* [Expo] Configure project */ = { + 81C37052831D3F058FF2B0D4 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - ); inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-frameworks.sh", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", ); - name = "[Expo] Configure project"; - outputFileListPaths = ( - ); + name = "[CP] Embed Pods Frameworks"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-defaults-NotificationService/expo-configure-project.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-frameworks.sh\"\n"; + showEnvVarsInLog = 0; }; - 8C825AFA26B3E74DB80133EA /* [CP] Check Pods Manifest.lock */ = { + 86A998705576AFA7CE938617 /* [Expo] Configure project */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", ); - name = "[CP] Check Pods Manifest.lock"; + name = "[Expo] Configure project"; outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-defaults-RocketChatRN-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 9BE1CEB599E7DFFA7C004B9F /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-frameworks.sh", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-frameworks.sh\"\n"; - showEnvVarsInLog = 0; + shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-defaults-NotificationService/expo-configure-project.sh\"\n"; }; - 9D65115AE1DFF56CD734F04F /* [CP] Copy Pods Resources */ = { + 88DC6B30E9DF0B361032CC1B /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -2003,13 +2001,13 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-resources.sh\"\n"; showEnvVarsInLog = 0; }; - A0BF2C82F66B14B8F59589FE /* [CP] Copy Pods Resources */ = { + 9D37CD62B427968B0C843657 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService-resources.sh", + "${PODS_ROOT}/Target Support Files/Pods-defaults-ShareRocketChatRN/Pods-defaults-ShareRocketChatRN-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreInternal/FirebaseCoreInternal_Privacy.bundle", @@ -2076,16 +2074,38 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-ShareRocketChatRN/Pods-defaults-ShareRocketChatRN-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + A7C23C0F63F7E514868AE885 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-defaults-Rocket.Chat-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - BACE710E33AE584582B19BD0 /* [CP] Copy Pods Resources */ = { + BA9071ABCBC2655C0EF5124C /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-resources.sh", + "${PODS_ROOT}/Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreInternal/FirebaseCoreInternal_Privacy.bundle", @@ -2152,16 +2172,16 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService-resources.sh\"\n"; showEnvVarsInLog = 0; }; - E4BD2A0C2451C64208B69644 /* [CP] Copy Pods Resources */ = { + BD50494BAB9A4D228C650E81 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-defaults-ShareRocketChatRN/Pods-defaults-ShareRocketChatRN-resources.sh", + "${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreExtension/FirebaseCoreExtension_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCoreInternal/FirebaseCoreInternal_Privacy.bundle", @@ -2228,27 +2248,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-ShareRocketChatRN/Pods-defaults-ShareRocketChatRN-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; - F973C9D58CCED6F67ADA2E0E /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenSSL-Universal/OpenSSL.framework/OpenSSL", - "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-resources.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -2628,7 +2628,7 @@ /* Begin XCBuildConfiguration section */ 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = B0E90AAFBA1A95385AE7C19D /* Pods-defaults-RocketChatRN.debug.xcconfig */; + baseConfigurationReference = 307B625411539125ECBAA6EA /* Pods-defaults-RocketChatRN.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -2689,7 +2689,7 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 3AA8F7DEB0B31A5A6329FAF5 /* Pods-defaults-RocketChatRN.release.xcconfig */; + baseConfigurationReference = D944D97B3EF00C39202CC81D /* Pods-defaults-RocketChatRN.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -2750,7 +2750,7 @@ }; 1EC6ACBC22CB9FC300A41C61 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = AF2F39716BB7E569448DF293 /* Pods-defaults-ShareRocketChatRN.debug.xcconfig */; + baseConfigurationReference = 1525225C45AF517E68DCDC2D /* Pods-defaults-ShareRocketChatRN.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(EMBEDDED_CONTENT_CONTAINS_SWIFT)"; APPLICATION_EXTENSION_API_ONLY = YES; @@ -2826,7 +2826,7 @@ }; 1EC6ACBD22CB9FC300A41C61 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C2909259346452596AD27AD5 /* Pods-defaults-ShareRocketChatRN.release.xcconfig */; + baseConfigurationReference = B41392725C2EA46A36F1FFB1 /* Pods-defaults-ShareRocketChatRN.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(EMBEDDED_CONTENT_CONTAINS_SWIFT)"; APPLICATION_EXTENSION_API_ONLY = YES; @@ -3110,7 +3110,7 @@ }; 1EFEB59D2493B6640072EDC0 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = E2836E70B71C86AAA7EEB0F6 /* Pods-defaults-NotificationService.debug.xcconfig */; + baseConfigurationReference = B5F46E5C11E602B3F99D4E40 /* Pods-defaults-NotificationService.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(EMBEDDED_CONTENT_CONTAINS_SWIFT)"; CLANG_ANALYZER_NONNULL = YES; @@ -3152,7 +3152,7 @@ }; 1EFEB59E2493B6640072EDC0 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = B1C2FD3D7D6F056737CCEAE0 /* Pods-defaults-NotificationService.release.xcconfig */; + baseConfigurationReference = 628DE8E64E84BCF109FC4969 /* Pods-defaults-NotificationService.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(EMBEDDED_CONTENT_CONTAINS_SWIFT)"; CLANG_ANALYZER_NONNULL = YES; @@ -3195,7 +3195,7 @@ }; 7AAB3E50257E6A6E00707CF6 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = FE7279F2D432C9CAB7379496 /* Pods-defaults-Rocket.Chat.debug.xcconfig */; + baseConfigurationReference = 17115A1EE86DB89DBE1912EB /* Pods-defaults-Rocket.Chat.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; @@ -3255,7 +3255,7 @@ }; 7AAB3E51257E6A6E00707CF6 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 0D20E5D2078C79720E1DBC03 /* Pods-defaults-Rocket.Chat.release.xcconfig */; + baseConfigurationReference = 7BEF39A6613B871D0A3326E1 /* Pods-defaults-Rocket.Chat.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; diff --git a/package.json b/package.json index c05c81786f..ea5c37c2fd 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,7 @@ "react-native-safe-area-context": "3.2.0", "react-native-screens": "3.29.0", "react-native-scrollable-tab-view": "ptomasroos/react-native-scrollable-tab-view", - "react-native-simple-crypto": "RocketChat/react-native-simple-crypto#feat.file-encryption", + "react-native-simple-crypto": "RocketChat/react-native-simple-crypto#fix.6.1.0", "react-native-skeleton-placeholder": "5.2.4", "react-native-slowlog": "1.0.2", "react-native-svg": "13.8.0", diff --git a/patches/react-native-simple-crypto+0.6.0.patch b/patches/react-native-simple-crypto+0.6.0.patch deleted file mode 100644 index 7cf2d18ff3..0000000000 --- a/patches/react-native-simple-crypto+0.6.0.patch +++ /dev/null @@ -1,293 +0,0 @@ -diff --git a/node_modules/react-native-simple-crypto/android/src/main/java/com/pedrouid/crypto/RCTCryptoPackage.java b/node_modules/react-native-simple-crypto/android/src/main/java/com/pedrouid/crypto/RCTCryptoPackage.java -index 2ee3cb4..1288fd6 100644 ---- a/node_modules/react-native-simple-crypto/android/src/main/java/com/pedrouid/crypto/RCTCryptoPackage.java -+++ b/node_modules/react-native-simple-crypto/android/src/main/java/com/pedrouid/crypto/RCTCryptoPackage.java -@@ -21,7 +21,8 @@ public class RCTCryptoPackage implements ReactPackage { - new RCTPbkdf2(reactContext), - new RCTRsa(reactContext), - new RCTRsaUtils(reactContext), -- new RandomBytesModule(reactContext) -+ new RandomBytesModule(reactContext), -+ new Util(reactContext) - ); - } - -diff --git a/node_modules/react-native-simple-crypto/android/src/main/java/com/pedrouid/crypto/Util.java b/node_modules/react-native-simple-crypto/android/src/main/java/com/pedrouid/crypto/Util.java -index caba3b5..32d480d 100644 ---- a/node_modules/react-native-simple-crypto/android/src/main/java/com/pedrouid/crypto/Util.java -+++ b/node_modules/react-native-simple-crypto/android/src/main/java/com/pedrouid/crypto/Util.java -@@ -3,11 +3,37 @@ package com.pedrouid.crypto; - import android.content.Context; - import android.net.Uri; - -+import com.facebook.react.bridge.Promise; -+import com.facebook.react.bridge.ReactApplicationContext; -+import com.facebook.react.bridge.ReactContextBaseJavaModule; -+import com.facebook.react.bridge.ReactMethod; -+ - import java.io.File; - import java.io.FileInputStream; - import java.io.InputStream; -+import java.security.MessageDigest; -+ -+public class Util extends ReactContextBaseJavaModule { -+ -+ public Util(ReactApplicationContext reactContext) { -+ super(reactContext); -+ } -+ -+ @Override -+ public String getName() { -+ return "Shared"; -+ } -+ -+ @ReactMethod -+ public void calculateFileChecksum(String filePath, Promise promise) { -+ try { -+ String result = calculateFileChecksum(getReactApplicationContext(),filePath ); -+ promise.resolve(result); -+ } catch (Exception e) { -+ promise.reject("-1", e.getMessage()); -+ } -+ } - --public class Util { - public static String bytesToHex(byte[] bytes) { - final char[] hexArray = "0123456789abcdef".toCharArray(); - char[] hexChars = new char[bytes.length * 2]; -@@ -39,4 +65,17 @@ public class Util { - return new FileInputStream(new File(inputFile)); // Handle plain file paths - } - } -+ -+ public static String calculateFileChecksum(Context context, String filePath) throws Exception { -+ InputStream inputStream = getInputStream(context, filePath); -+ MessageDigest digest = MessageDigest.getInstance("SHA-256"); -+ byte[] buffer = new byte[4096]; -+ int bytesRead; -+ while ((bytesRead = inputStream.read(buffer)) != -1) { -+ digest.update(buffer, 0, bytesRead); -+ } -+ inputStream.close(); -+ byte[] hash = digest.digest(); -+ return bytesToHex(hash); -+ } - } -diff --git a/node_modules/react-native-simple-crypto/index.d.ts b/node_modules/react-native-simple-crypto/index.d.ts -index 079c397..852c5c9 100644 ---- a/node_modules/react-native-simple-crypto/index.d.ts -+++ b/node_modules/react-native-simple-crypto/index.d.ts -@@ -77,6 +77,7 @@ declare module "react-native-simple-crypto" { - - export namespace utils { - export function randomBytes(bytes: number): Promise; -+ export function calculateFileChecksum(filePath: string): Promise; - export function convertArrayBufferToUtf8(input: ArrayBuffer): string; - export function convertUtf8ToArrayBuffer(input: string): ArrayBuffer; - export function convertArrayBufferToBase64(input: ArrayBuffer): string; -diff --git a/node_modules/react-native-simple-crypto/index.js b/node_modules/react-native-simple-crypto/index.js -index 6d4ed10..fb39cc6 100644 ---- a/node_modules/react-native-simple-crypto/index.js -+++ b/node_modules/react-native-simple-crypto/index.js -@@ -69,6 +69,10 @@ async function randomBytes(length) { - return convertBase64ToArrayBuffer(await NativeModules.RNRandomBytes.randomBytes(length)); - } - -+async function calculateFileChecksum(filePath) { -+ return NativeModules.Shared.calculateFileChecksum(filePath); -+} -+ - async function SHAWrapper(data, algorithm) { - if (typeof data === 'string') { - return NativeModules.Sha.shaUtf8(data, algorithm); -@@ -148,6 +152,7 @@ const RSA = { - - const utils = { - randomBytes, -+ calculateFileChecksum, - convertArrayBufferToUtf8, - convertUtf8ToArrayBuffer, - convertArrayBufferToBase64, -diff --git a/node_modules/react-native-simple-crypto/ios/.DS_Store b/node_modules/react-native-simple-crypto/ios/.DS_Store -new file mode 100644 -index 0000000..371b069 -Binary files /dev/null and b/node_modules/react-native-simple-crypto/ios/.DS_Store differ -diff --git a/node_modules/react-native-simple-crypto/ios/RCTCrypto/RCTShared.h b/node_modules/react-native-simple-crypto/ios/RCTCrypto/RCTShared.h -new file mode 100644 -index 0000000..55b52d1 ---- /dev/null -+++ b/node_modules/react-native-simple-crypto/ios/RCTCrypto/RCTShared.h -@@ -0,0 +1,5 @@ -+#import -+ -+@interface RCTShared : NSObject -+ -+@end -diff --git a/node_modules/react-native-simple-crypto/ios/RCTCrypto/RCTShared.m b/node_modules/react-native-simple-crypto/ios/RCTCrypto/RCTShared.m -new file mode 100644 -index 0000000..c011235 ---- /dev/null -+++ b/node_modules/react-native-simple-crypto/ios/RCTCrypto/RCTShared.m -@@ -0,0 +1,18 @@ -+#import "RCTShared.h" -+#import "Shared.h" -+ -+@implementation RCTShared -+ -+RCT_EXPORT_MODULE() -+ -+RCT_EXPORT_METHOD(calculateFileChecksum:(NSString *)filePath resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { -+ NSError *error = nil; -+ NSString *data = [Shared calculateFileChecksum:filePath]; -+ if (data == nil) { -+ reject(@"shared_checksum_fail", @"Checksum error", error); -+ } else { -+ resolve(data); -+ } -+} -+ -+@end -diff --git a/node_modules/react-native-simple-crypto/ios/RCTCrypto/lib/Aes.m b/node_modules/react-native-simple-crypto/ios/RCTCrypto/lib/Aes.m -index 067d962..b24e4dd 100644 ---- a/node_modules/react-native-simple-crypto/ios/RCTCrypto/lib/Aes.m -+++ b/node_modules/react-native-simple-crypto/ios/RCTCrypto/lib/Aes.m -@@ -55,7 +55,7 @@ - - NSString *normalizedFilePath = [filePath stringByReplacingOccurrencesOfString:@"file://" withString:@""]; - NSString *outputFileName = [@"processed_" stringByAppendingString:[normalizedFilePath lastPathComponent]]; -- NSString *outputFilePath = [[[normalizedFilePath stringByDeletingLastPathComponent] stringByAppendingPathComponent:outputFileName] stringByDeletingPathExtension]; -+ NSString *outputFilePath = [[normalizedFilePath stringByDeletingLastPathComponent] stringByAppendingPathComponent:outputFileName]; - NSInputStream *inputStream = [NSInputStream inputStreamWithFileAtPath:normalizedFilePath]; - NSOutputStream *outputStream = [NSOutputStream outputStreamToFileAtPath:outputFilePath append:NO]; - [inputStream open]; -@@ -67,6 +67,8 @@ - CCCryptorStatus status = CCCryptorCreateWithMode(operation, kCCModeCTR, kCCAlgorithmAES, ccNoPadding, ivData.bytes, keyData.bytes, keyData.length, NULL, 0, 0, kCCModeOptionCTR_BE, &cryptor); - if (status != kCCSuccess) { - NSLog(@"Failed to create cryptor: %d", status); -+ [inputStream close]; -+ [outputStream close]; - return nil; - } - -@@ -79,8 +81,25 @@ - [outputStream write:buffer maxLength:dataOutMoved]; - } else { - NSLog(@"Cryptor update failed: %d", status); -+ return nil; - break; - } -+ } else if (bytesRead < 0) { -+ NSLog(@"Input stream read error"); -+ status = kCCDecodeError; -+ return nil; -+ break; -+ } -+ } -+ -+ if (status == kCCSuccess) { -+ size_t finalBytesOut; -+ status = CCCryptorFinal(cryptor, buffer, bufferSize, &finalBytesOut); -+ if (status == kCCSuccess) { -+ [outputStream write:buffer maxLength:finalBytesOut]; -+ } else { -+ NSLog(@"Cryptor final failed: %d", status); -+ return nil; - } - } - -@@ -89,15 +108,20 @@ - [outputStream close]; - - if (status == kCCSuccess) { -- if (operation == kCCDecrypt) { -- NSFileManager *fileManager = [NSFileManager defaultManager]; -- // Overwrite the input file with the decrypted file -- [fileManager removeItemAtPath:normalizedFilePath error:nil]; -- [fileManager moveItemAtPath:outputFilePath toPath:normalizedFilePath error:nil]; -- return [NSString stringWithFormat:@"file://%@", normalizedFilePath]; -- } else { -- return [NSString stringWithFormat:@"file://%@", outputFilePath]; -+ NSURL *originalFileURL = [NSURL fileURLWithPath:normalizedFilePath]; -+ NSURL *outputFileURL = [NSURL fileURLWithPath:outputFilePath]; -+ NSError *error = nil; -+ [[NSFileManager defaultManager] replaceItemAtURL:originalFileURL -+ withItemAtURL:outputFileURL -+ backupItemName:nil -+ options:NSFileManagerItemReplacementUsingNewMetadataOnly -+ resultingItemURL:nil -+ error:&error]; -+ if (error) { -+ NSLog(@"Failed to replace original file: %@", error); -+ return nil; - } -+ return [NSString stringWithFormat:@"file://%@", normalizedFilePath]; - } else { - // Clean up temp file in case of failure - [[NSFileManager defaultManager] removeItemAtPath:outputFilePath error:nil]; -@@ -105,7 +129,6 @@ - } - } - -- - + (NSString *)encryptFile:(NSString *)filePath key:(NSString *)key iv:(NSString *)iv { - return [self processFile:filePath operation:kCCEncrypt key:key iv:iv]; - } -diff --git a/node_modules/react-native-simple-crypto/ios/RCTCrypto/lib/Shared.h b/node_modules/react-native-simple-crypto/ios/RCTCrypto/lib/Shared.h -index 398444b..0e8e078 100644 ---- a/node_modules/react-native-simple-crypto/ios/RCTCrypto/lib/Shared.h -+++ b/node_modules/react-native-simple-crypto/ios/RCTCrypto/lib/Shared.h -@@ -4,6 +4,5 @@ - + (NSString *) toHex: (NSData *)nsdata; - + (NSData *) fromHex: (NSString *)string; - + (NSString *)base64FromBase64URL:(NSString *)base64URL; --+ (NSString *)normalizeFilePath:(NSString *)filePath; --+ (NSString *)restoreFilePathSchemeIfNeeded:(NSString *)filePath originalPath:(NSString *)originalPath; -++ (NSString *)calculateFileChecksum:(NSString *)filePath; - @end -diff --git a/node_modules/react-native-simple-crypto/ios/RCTCrypto/lib/Shared.m b/node_modules/react-native-simple-crypto/ios/RCTCrypto/lib/Shared.m -index e97098b..a52d03b 100644 ---- a/node_modules/react-native-simple-crypto/ios/RCTCrypto/lib/Shared.m -+++ b/node_modules/react-native-simple-crypto/ios/RCTCrypto/lib/Shared.m -@@ -41,4 +41,42 @@ - return base64; - } - -++ (NSString *)calculateFileChecksum:(NSString *)filePath { -+ NSString *normalizedFilePath = [filePath stringByReplacingOccurrencesOfString:@"file://" withString:@""]; -+ NSInputStream *inputStream = [NSInputStream inputStreamWithFileAtPath:normalizedFilePath]; -+ [inputStream open]; -+ -+ if (!inputStream) { -+ NSLog(@"Failed to open file: %@", filePath); -+ return nil; -+ } -+ -+ CC_SHA256_CTX sha256; -+ CC_SHA256_Init(&sha256); -+ -+ uint8_t buffer[4096]; -+ NSInteger bytesRead = 0; -+ -+ while ((bytesRead = [inputStream read:buffer maxLength:sizeof(buffer)]) > 0) { -+ CC_SHA256_Update(&sha256, buffer, (CC_LONG)bytesRead); -+ } -+ -+ [inputStream close]; -+ -+ if (bytesRead < 0) { -+ NSLog(@"File read error: %@", filePath); -+ return nil; -+ } -+ -+ unsigned char hash[CC_SHA256_DIGEST_LENGTH]; -+ CC_SHA256_Final(hash, &sha256); -+ -+ NSMutableString *checksum = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2]; -+ for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) { -+ [checksum appendFormat:@"%02x", hash[i]]; -+ } -+ -+ return checksum; -+} -+ - @end diff --git a/yarn.lock b/yarn.lock index f6558eefe2..35cb65a7ae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12182,9 +12182,9 @@ react-native-scrollable-tab-view@ptomasroos/react-native-scrollable-tab-view: prop-types "^15.6.0" react-timer-mixin "^0.13.3" -react-native-simple-crypto@RocketChat/react-native-simple-crypto#feat.file-encryption: - version "0.6.0" - resolved "https://codeload.github.com/RocketChat/react-native-simple-crypto/tar.gz/476f0d2750abc1a9e74879ac3bacc7bec753c476" +react-native-simple-crypto@RocketChat/react-native-simple-crypto#fix.6.1.0: + version "0.6.1" + resolved "https://codeload.github.com/RocketChat/react-native-simple-crypto/tar.gz/467905c61df8132e2257b6408a072521fc5a3d27" dependencies: base64-js "^1.3.0" hex-lite "^1.5.0" From 1b7ab31c73cd4216d75f8d6b8cb18f681ae09d20 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Tue, 30 Jul 2024 09:29:59 -0300 Subject: [PATCH 3/4] fix: Media autodownload not working properly (#5807) --- app/containers/UIKit/Image.tsx | 3 +- .../message/Components/Attachments/Audio.tsx | 117 +------- .../message/Components/Attachments/Image.tsx | 251 ------------------ .../Components/Attachments/Image/Button.tsx | 24 ++ .../Attachments/Image/Container.tsx | 45 ++++ .../Components/Attachments/Image/Image.tsx | 45 ++++ .../Attachments/Image/definitions.ts | 21 ++ .../Components/Attachments/Image/index.tsx | 4 + .../message/Components/Attachments/Video.tsx | 180 +++---------- app/containers/message/hooks/useAudioUrl.tsx | 25 -- app/containers/message/hooks/useFile.tsx | 10 +- .../message/hooks/useMediaAutoDownload.tsx | 165 ++++++++++++ app/i18n/locales/en.json | 2 +- app/lib/constants/mediaAutoDownload.ts | 2 +- app/lib/encryption/encryption.ts | 47 ++-- app/lib/methods/autoDownloadPreference.ts | 6 +- app/lib/methods/handleMediaDownload.ts | 58 +++- app/lib/methods/loadThreadMessages.ts | 2 + app/views/MediaAutoDownloadView/index.tsx | 6 +- 19 files changed, 433 insertions(+), 580 deletions(-) delete mode 100644 app/containers/message/Components/Attachments/Image.tsx create mode 100644 app/containers/message/Components/Attachments/Image/Button.tsx create mode 100644 app/containers/message/Components/Attachments/Image/Container.tsx create mode 100644 app/containers/message/Components/Attachments/Image/Image.tsx create mode 100644 app/containers/message/Components/Attachments/Image/definitions.ts create mode 100644 app/containers/message/Components/Attachments/Image/index.tsx delete mode 100644 app/containers/message/hooks/useAudioUrl.tsx create mode 100644 app/containers/message/hooks/useMediaAutoDownload.tsx diff --git a/app/containers/UIKit/Image.tsx b/app/containers/UIKit/Image.tsx index 96c57a9315..6bcc84a6b5 100644 --- a/app/containers/UIKit/Image.tsx +++ b/app/containers/UIKit/Image.tsx @@ -30,8 +30,7 @@ export const Thumb = ({ element, size = 88 }: IThumb) => ( export const Media = ({ element }: IImage) => { const showAttachment = (attachment: IAttachment) => Navigation.navigate('AttachmentView', { attachment }); const imageUrl = element?.imageUrl ?? ''; - - return ; + return ; }; const genericImage = (element: IElement, context?: number) => { diff --git a/app/containers/message/Components/Attachments/Audio.tsx b/app/containers/message/Components/Attachments/Audio.tsx index bdb12fe520..f9f4e8830d 100644 --- a/app/containers/message/Components/Attachments/Audio.tsx +++ b/app/containers/message/Components/Attachments/Audio.tsx @@ -1,16 +1,12 @@ -import React, { useCallback, useContext, useEffect, useState } from 'react'; +import React, { useContext } from 'react'; import { StyleProp, TextStyle } from 'react-native'; -import { emitter } from '../../../../lib/methods/helpers'; -import Markdown from '../../../markdown'; -import MessageContext from '../../Context'; -import { TGetCustomEmoji } from '../../../../definitions/IEmoji'; import { IAttachment, IUserMessage } from '../../../../definitions'; -import { TDownloadState, downloadMediaFile, getMediaCache, isDownloadActive } from '../../../../lib/methods/handleMediaDownload'; -import { fetchAutoDownloadEnabled } from '../../../../lib/methods/autoDownloadPreference'; +import { TGetCustomEmoji } from '../../../../definitions/IEmoji'; import AudioPlayer from '../../../AudioPlayer'; -import { useAudioUrl } from '../../hooks/useAudioUrl'; -import { getAudioUrlToCache } from '../../../../lib/methods/getAudioUrl'; +import Markdown from '../../../markdown'; +import MessageContext from '../../Context'; +import { useMediaAutoDownload } from '../../hooks/useMediaAutoDownload'; interface IMessageAudioProps { file: IAttachment; @@ -19,113 +15,16 @@ interface IMessageAudioProps { getCustomEmoji: TGetCustomEmoji; author?: IUserMessage; msg?: string; - cdnPrefix?: string; } const MessageAudio = ({ file, getCustomEmoji, author, isReply, style, msg }: IMessageAudioProps) => { - const [downloadState, setDownloadState] = useState('loading'); - const [fileUri, setFileUri] = useState(''); - const { baseUrl, user, id, rid } = useContext(MessageContext); - const audioUrl = useAudioUrl({ audioUrl: file.audio_url }); - - const onPlayButtonPress = async () => { - if (downloadState === 'to-download') { - const isAudioCached = await handleGetMediaCache(); - if (isAudioCached) { - return; - } - handleDownload(); - } - }; - - const handleDownload = async () => { - setDownloadState('loading'); - try { - if (audioUrl) { - const audioUri = await downloadMediaFile({ - messageId: id, - downloadUrl: getAudioUrlToCache({ token: user.token, userId: user.id, url: audioUrl }), - type: 'audio', - mimeType: file.audio_type, - encryption: file.encryption, - originalChecksum: file.hashes?.sha256 - }); - setFileUri(audioUri); - setDownloadState('downloaded'); - } - } catch { - setDownloadState('to-download'); - } - }; - - const handleAutoDownload = async () => { - try { - if (audioUrl) { - const isCurrentUserAuthor = author?._id === user.id; - const isAutoDownloadEnabled = fetchAutoDownloadEnabled('audioPreferenceDownload'); - if (isAutoDownloadEnabled || isCurrentUserAuthor) { - await handleDownload(); - return; - } - setDownloadState('to-download'); - } - } catch { - // Do nothing - } - }; - - const handleGetMediaCache = async () => { - const cachedAudioResult = await getMediaCache({ - type: 'audio', - mimeType: file.audio_type, - urlToCache: audioUrl - }); - const result = cachedAudioResult?.exists && file.e2e !== 'pending'; - if (result) { - setFileUri(cachedAudioResult.uri); - setDownloadState('downloaded'); - } - return result; - }; - - const handleResumeDownload = () => { - emitter.on(`downloadMedia${id}`, downloadMediaListener); - }; - - useEffect(() => { - const handleCache = async () => { - const isAudioCached = await handleGetMediaCache(); - if (isAudioCached) { - return; - } - if (audioUrl && isDownloadActive(audioUrl)) { - handleResumeDownload(); - return; - } - await handleAutoDownload(); - }; - if (audioUrl) { - handleCache(); - } - }, [audioUrl]); - - const downloadMediaListener = useCallback((uri: string) => { - setFileUri(uri); - setDownloadState('downloaded'); - }, []); - - useEffect(() => () => { - emitter.off(`downloadMedia${id}`, downloadMediaListener); - }); - - if (!baseUrl) { - return null; - } + const { user, id, rid } = useContext(MessageContext); + const { status, onPress, url } = useMediaAutoDownload({ file, author }); return ( <> - + ); }; diff --git a/app/containers/message/Components/Attachments/Image.tsx b/app/containers/message/Components/Attachments/Image.tsx deleted file mode 100644 index 2fc9cf2ff0..0000000000 --- a/app/containers/message/Components/Attachments/Image.tsx +++ /dev/null @@ -1,251 +0,0 @@ -import React, { useCallback, useContext, useEffect, useState } from 'react'; -import { StyleProp, TextStyle, View } from 'react-native'; -import FastImage from 'react-native-fast-image'; - -import { emitter } from '../../../../lib/methods/helpers'; -import { IAttachment, IUserMessage } from '../../../../definitions'; -import { TGetCustomEmoji } from '../../../../definitions/IEmoji'; -import { fetchAutoDownloadEnabled } from '../../../../lib/methods/autoDownloadPreference'; -import { cancelDownload, downloadMediaFile, getMediaCache, isDownloadActive } from '../../../../lib/methods/handleMediaDownload'; -import { formatAttachmentUrl } from '../../../../lib/methods/helpers/formatAttachmentUrl'; -import { useTheme } from '../../../../theme'; -import Markdown from '../../../markdown'; -import BlurComponent from '../OverlayComponent'; -import MessageContext from '../../Context'; -import Touchable from '../../Touchable'; -import styles from '../../styles'; -import { isImageBase64 } from '../../../../lib/methods'; -import { isValidUrl } from '../../../../lib/methods/helpers/isValidUrl'; -import { useFile } from '../../hooks/useFile'; - -interface IMessageButton { - children: React.ReactElement; - disabled?: boolean; - onPress: () => void; -} - -interface IMessageImage { - file: IAttachment; - imageUrl?: string; - showAttachment?: (file: IAttachment) => void; - style?: StyleProp[]; - isReply?: boolean; - getCustomEmoji?: TGetCustomEmoji; - author?: IUserMessage; - msg?: string; -} - -const Button = React.memo(({ children, onPress, disabled }: IMessageButton) => { - const { colors } = useTheme(); - return ( - - {children} - - ); -}); - -export const MessageImage = React.memo( - ({ imgUri, cached, loading, encrypted = false }: { imgUri: string; cached: boolean; loading: boolean; encrypted: boolean }) => { - const { colors } = useTheme(); - const valid = isValidUrl(imgUri); - - if (encrypted && !loading && cached) { - return ( - <> - - - - ); - } - - return ( - <> - {valid ? ( - - ) : ( - - )} - {!cached ? ( - - ) : null} - - ); - } -); - -const ImageContainer = ({ - file, - imageUrl, - showAttachment, - getCustomEmoji, - style, - isReply, - author, - msg -}: IMessageImage): React.ReactElement | null => { - const { id, baseUrl, user } = useContext(MessageContext); - const [imageCached, setImageCached] = useFile(file, id); - const [cached, setCached] = useState(false); - const [loading, setLoading] = useState(true); - const { theme } = useTheme(); - const getUrl = (link?: string) => imageUrl || formatAttachmentUrl(link, user.id, user.token, baseUrl); - const img = getUrl(file.image_url); - // The param file.title_link is the one that point to image with best quality, however we still need to test the imageUrl - // And we cannot be certain whether the file.title_link actually exists. - const imgUrlToCache = getUrl(imageCached.title_link || imageCached.image_url); - - useEffect(() => { - const handleCache = async () => { - if (img) { - const isImageCached = await handleGetMediaCache(); - if (isImageCached) { - return; - } - if (isDownloadActive(imgUrlToCache)) { - handleResumeDownload(); - return; - } - await handleAutoDownload(); - setLoading(false); - } - }; - if (isImageBase64(imgUrlToCache)) { - setLoading(false); - setCached(true); - } else { - handleCache(); - } - - return () => { - emitter.off(`downloadMedia${id}`, downloadMediaListener); - }; - }, []); - - const downloadMediaListener = useCallback((imageUri: string) => { - updateImageCached(imageUri); - }, []); - - if (!img) { - return null; - } - - const handleAutoDownload = async () => { - const isCurrentUserAuthor = author?._id === user.id; - const isAutoDownloadEnabled = fetchAutoDownloadEnabled('imagesPreferenceDownload'); - if (isAutoDownloadEnabled || isCurrentUserAuthor) { - await handleDownload(); - } - }; - - const updateImageCached = (imgUri: string) => { - setImageCached({ - title_link: imgUri - }); - setCached(true); - }; - - const setDecrypted = () => { - if (imageCached.e2e === 'pending') { - setImageCached({ - e2e: 'done' - }); - } - }; - - const handleGetMediaCache = async () => { - const cachedImageResult = await getMediaCache({ - type: 'image', - mimeType: imageCached.image_type, - urlToCache: imgUrlToCache - }); - const result = !!cachedImageResult?.exists && imageCached.e2e !== 'pending'; - if (result) { - updateImageCached(cachedImageResult.uri); - } - return result; - }; - - const handleResumeDownload = () => { - emitter.on(`downloadMedia${id}`, downloadMediaListener); - }; - - const handleDownload = async () => { - try { - const imageUri = await downloadMediaFile({ - messageId: id, - downloadUrl: imgUrlToCache, - type: 'image', - mimeType: imageCached.image_type, - encryption: file.encryption, - originalChecksum: file.hashes?.sha256 - }); - setDecrypted(); - updateImageCached(imageUri); - } catch (e) { - setCached(false); - setLoading(false); - } - }; - - const onPress = async () => { - if (loading && isDownloadActive(imgUrlToCache)) { - cancelDownload(imgUrlToCache); - setLoading(false); - setCached(false); - return; - } - if (!cached && !loading) { - const isImageCached = await handleGetMediaCache(); - if (isImageCached && showAttachment) { - showAttachment(imageCached); - return; - } - if (isDownloadActive(imgUrlToCache)) { - handleResumeDownload(); - return; - } - setLoading(true); - handleDownload(); - return; - } - if (!showAttachment || !imageCached.title_link) { - return; - } - showAttachment(imageCached); - }; - - const image = ( - - ); - - if (msg) { - return ( - - - {image} - - ); - } - - return image; -}; - -ImageContainer.displayName = 'MessageImageContainer'; -MessageImage.displayName = 'MessageImage'; - -export default ImageContainer; diff --git a/app/containers/message/Components/Attachments/Image/Button.tsx b/app/containers/message/Components/Attachments/Image/Button.tsx new file mode 100644 index 0000000000..452bfaf15d --- /dev/null +++ b/app/containers/message/Components/Attachments/Image/Button.tsx @@ -0,0 +1,24 @@ +import React from 'react'; + +import { useTheme } from '../../../../../theme'; +import Touchable from '../../../Touchable'; +import styles from '../../../styles'; + +interface IMessageButton { + children: React.ReactElement; + disabled?: boolean; + onPress: () => void; +} + +export const Button = ({ children, onPress, disabled }: IMessageButton) => { + const { colors } = useTheme(); + return ( + + {children} + + ); +}; diff --git a/app/containers/message/Components/Attachments/Image/Container.tsx b/app/containers/message/Components/Attachments/Image/Container.tsx new file mode 100644 index 0000000000..abee3b5969 --- /dev/null +++ b/app/containers/message/Components/Attachments/Image/Container.tsx @@ -0,0 +1,45 @@ +import React, { useContext } from 'react'; +import { View } from 'react-native'; + +import { useTheme } from '../../../../../theme'; +import Markdown from '../../../../markdown'; +import { useMediaAutoDownload } from '../../../hooks/useMediaAutoDownload'; +import { Button } from './Button'; +import { MessageImage } from './Image'; +import { IImageContainer } from './definitions'; +import MessageContext from '../../../Context'; + +const ImageContainer = ({ + file, + showAttachment, + getCustomEmoji, + style, + isReply, + author, + msg +}: IImageContainer): React.ReactElement | null => { + const { user } = useContext(MessageContext); + const { theme } = useTheme(); + const { status, onPress, url, isEncrypted } = useMediaAutoDownload({ file, author, showAttachment }); + + const image = ( + + ); + + if (msg) { + return ( + + + {image} + + ); + } + + return image; +}; + +ImageContainer.displayName = 'MessageImageContainer'; + +export default ImageContainer; diff --git a/app/containers/message/Components/Attachments/Image/Image.tsx b/app/containers/message/Components/Attachments/Image/Image.tsx new file mode 100644 index 0000000000..45518908ee --- /dev/null +++ b/app/containers/message/Components/Attachments/Image/Image.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { View } from 'react-native'; +import FastImage from 'react-native-fast-image'; + +import { isValidUrl } from '../../../../../lib/methods/helpers/isValidUrl'; +import { useTheme } from '../../../../../theme'; +import styles from '../../../styles'; +import OverlayComponent from '../../OverlayComponent'; +import { IMessageImage } from './definitions'; + +export const MessageImage = React.memo(({ uri, status, encrypted = false }: IMessageImage) => { + const { colors } = useTheme(); + + if (encrypted && status === 'downloaded') { + return ( + <> + + + + ); + } + + return ( + <> + {isValidUrl(uri) && status === 'downloaded' ? ( + + ) : ( + + )} + {['loading', 'to-download'].includes(status) ? ( + + ) : null} + + ); +}); + +MessageImage.displayName = 'MessageImage'; diff --git a/app/containers/message/Components/Attachments/Image/definitions.ts b/app/containers/message/Components/Attachments/Image/definitions.ts new file mode 100644 index 0000000000..7dbb6ac4c8 --- /dev/null +++ b/app/containers/message/Components/Attachments/Image/definitions.ts @@ -0,0 +1,21 @@ +import { StyleProp, TextStyle } from 'react-native'; + +import { IAttachment, IUserMessage } from '../../../../../definitions'; +import { TGetCustomEmoji } from '../../../../../definitions/IEmoji'; +import { TDownloadState } from '../../../../../lib/methods/handleMediaDownload'; + +export interface IImageContainer { + file: IAttachment; + showAttachment?: (file: IAttachment) => void; + style?: StyleProp[]; + isReply?: boolean; + getCustomEmoji?: TGetCustomEmoji; + author?: IUserMessage; + msg?: string; +} + +export interface IMessageImage { + uri: string; + status: TDownloadState; + encrypted: boolean; +} diff --git a/app/containers/message/Components/Attachments/Image/index.tsx b/app/containers/message/Components/Attachments/Image/index.tsx new file mode 100644 index 0000000000..c1eb3a8441 --- /dev/null +++ b/app/containers/message/Components/Attachments/Image/index.tsx @@ -0,0 +1,4 @@ +import Container from './Container'; + +export * from './Image'; +export default Container; diff --git a/app/containers/message/Components/Attachments/Video.tsx b/app/containers/message/Components/Attachments/Video.tsx index 42c432db83..c114ae6885 100644 --- a/app/containers/message/Components/Attachments/Video.tsx +++ b/app/containers/message/Components/Attachments/Video.tsx @@ -1,26 +1,24 @@ -import React, { useCallback, useContext, useEffect, useState } from 'react'; +import React, { useContext } from 'react'; import { StyleProp, StyleSheet, Text, TextStyle, View } from 'react-native'; +import { IUserMessage } from '../../../../definitions'; import { IAttachment } from '../../../../definitions/IAttachment'; import { TGetCustomEmoji } from '../../../../definitions/IEmoji'; import I18n from '../../../../i18n'; import { themes } from '../../../../lib/constants'; -import { fetchAutoDownloadEnabled } from '../../../../lib/methods/autoDownloadPreference'; -import { cancelDownload, downloadMediaFile, getMediaCache, isDownloadActive } from '../../../../lib/methods/handleMediaDownload'; -import { emitter, fileDownload, isIOS } from '../../../../lib/methods/helpers'; +import { fileDownload, isIOS } from '../../../../lib/methods/helpers'; import EventEmitter from '../../../../lib/methods/helpers/events'; -import { formatAttachmentUrl } from '../../../../lib/methods/helpers/formatAttachmentUrl'; import { useTheme } from '../../../../theme'; import sharedStyles from '../../../../views/Styles'; +import { TIconsName } from '../../../CustomIcon'; import { LISTENER } from '../../../Toast'; import Markdown from '../../../markdown'; -import BlurComponent from '../OverlayComponent'; import MessageContext from '../../Context'; import Touchable from '../../Touchable'; +import { useMediaAutoDownload } from '../../hooks/useMediaAutoDownload'; import { DEFAULT_MESSAGE_HEIGHT } from '../../utils'; -import { TIconsName } from '../../../CustomIcon'; -import { useFile } from '../../hooks/useFile'; -import { IUserMessage } from '../../../../definitions'; +import BlurComponent from '../OverlayComponent'; +import { TDownloadState } from '../../../../lib/methods/handleMediaDownload'; const SUPPORTED_TYPES = ['video/quicktime', 'video/mp4', ...(isIOS ? [] : ['video/3gp', 'video/mkv'])]; const isTypeSupported = (type: string) => SUPPORTED_TYPES.indexOf(type) !== -1; @@ -64,16 +62,16 @@ const CancelIndicator = () => { ); }; -const Thumbnail = ({ loading, cached, encrypted = false }: { loading: boolean; cached: boolean; encrypted: boolean }) => { - let icon: TIconsName = cached ? 'play-filled' : 'arrow-down-circle'; - if (encrypted && !loading && cached) { +const Thumbnail = ({ status, encrypted = false }: { status: TDownloadState; encrypted: boolean }) => { + let icon: TIconsName = status === 'downloaded' ? 'play-filled' : 'arrow-down-circle'; + if (encrypted && status === 'downloaded') { icon = 'encrypted'; } return ( <> - - {loading ? : null} + + {status === 'loading' ? : null} ); }; @@ -87,159 +85,41 @@ const Video = ({ isReply, msg }: IMessageVideo): React.ReactElement | null => { - const { id, baseUrl, user } = useContext(MessageContext); - const [videoCached, setVideoCached] = useFile(file, id); - const [loading, setLoading] = useState(true); - const [cached, setCached] = useState(false); + const { user } = useContext(MessageContext); const { theme } = useTheme(); - const video = formatAttachmentUrl(file.video_url, user.id, user.token, baseUrl); - - useEffect(() => { - const handleVideoSearchAndDownload = async () => { - if (video) { - const isVideoCached = await handleGetMediaCache(); - if (isVideoCached) { - return; - } - if (isDownloadActive(video)) { - handleResumeDownload(); - return; - } - await handleAutoDownload(); - setLoading(false); + const { status, onPress, url, isEncrypted, currentFile } = useMediaAutoDownload({ file, author, showAttachment }); + + const _onPress = async () => { + if (currentFile.video_type && !isTypeSupported(currentFile.video_type)) { + if (isIOS) { + EventEmitter.emit(LISTENER, { message: I18n.t('Unsupported_format') }); + } else { + await downloadVideoToGallery(url); } - }; - handleVideoSearchAndDownload(); - - return () => { - emitter.off(`downloadMedia${id}`, downloadMediaListener); - }; - }, []); - - const downloadMediaListener = useCallback((uri: string) => { - updateVideoCached(uri); - setLoading(false); - }, []); - - if (!baseUrl) { - return null; - } - - const handleAutoDownload = async () => { - const isCurrentUserAuthor = author?._id === user.id; - const isAutoDownloadEnabled = fetchAutoDownloadEnabled('videoPreferenceDownload'); - if ((isAutoDownloadEnabled || isCurrentUserAuthor) && file.video_type && isTypeSupported(file.video_type)) { - await handleDownload(); return; } - setLoading(false); - }; - - const updateVideoCached = (videoUri: string) => { - setVideoCached({ video_url: videoUri }); - setCached(true); - setLoading(false); - }; - - const setDecrypted = () => { - if (videoCached.e2e === 'pending') { - setVideoCached({ - e2e: 'done' - }); - } - }; - - const handleGetMediaCache = async () => { - const cachedVideoResult = await getMediaCache({ - type: 'video', - mimeType: file.video_type, - urlToCache: video - }); - const result = !!cachedVideoResult?.exists && videoCached.e2e !== 'pending'; - if (result) { - updateVideoCached(cachedVideoResult.uri); - } - return result; - }; - - const handleResumeDownload = () => { - emitter.on(`downloadMedia${id}`, downloadMediaListener); + onPress(); }; - const handleDownload = async () => { + const downloadVideoToGallery = async (uri: string) => { try { - const videoUri = await downloadMediaFile({ - messageId: id, - downloadUrl: video, - type: 'video', - mimeType: file.video_type, - encryption: file.encryption, - originalChecksum: file.hashes?.sha256 - }); - setDecrypted(); - updateVideoCached(videoUri); - } catch { - setCached(false); - } - }; - - const onPress = async () => { - if (file.video_type && cached && isTypeSupported(file.video_type) && showAttachment && videoCached.video_url) { - showAttachment(videoCached); - return; - } - if (!loading && !cached && file.video_type && isTypeSupported(file.video_type)) { - const isVideoCached = await handleGetMediaCache(); - if (isVideoCached && showAttachment && videoCached.video_url) { - showAttachment(videoCached); - return; + const fileDownloaded = await fileDownload(uri, file); + if (fileDownloaded) { + EventEmitter.emit(LISTENER, { message: I18n.t('saved_to_gallery') }); } - if (isDownloadActive(video)) { - handleResumeDownload(); - return; - } - setLoading(true); - handleDownload(); - return; - } - if (loading && !cached) { - handleCancelDownload(); - return; - } - if (!isIOS && file.video_url) { - await downloadVideoToGallery(video); - return; - } - EventEmitter.emit(LISTENER, { message: I18n.t('Unsupported_format') }); - }; - - const handleCancelDownload = () => { - if (loading) { - cancelDownload(video); - setLoading(false); - } - }; - - const downloadVideoToGallery = async (uri: string) => { - setLoading(true); - const fileDownloaded = await fileDownload(uri, file); - setLoading(false); - - if (fileDownloaded) { - EventEmitter.emit(LISTENER, { message: I18n.t('saved_to_gallery') }); - return; + } catch (error) { + EventEmitter.emit(LISTENER, { message: I18n.t('error-save-video') }); } - EventEmitter.emit(LISTENER, { message: I18n.t('error-save-video') }); }; return ( <> - + ); diff --git a/app/containers/message/hooks/useAudioUrl.tsx b/app/containers/message/hooks/useAudioUrl.tsx deleted file mode 100644 index dc20475c30..0000000000 --- a/app/containers/message/hooks/useAudioUrl.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { useState, useEffect } from 'react'; - -import { useAppSelector } from '../../../lib/hooks'; -import { getAudioUrl } from '../../../lib/methods/getAudioUrl'; - -export const useAudioUrl = ({ audioUrl }: { audioUrl?: string }): string => { - const [filePath, setFilePath] = useState(''); - - const { cdnPrefix, baseUrl } = useAppSelector(state => ({ - cdnPrefix: state.settings.CDN_PREFIX as string, - baseUrl: state.server.server - })); - - useEffect(() => { - if (!audioUrl) { - return; - } - const url = getAudioUrl({ baseUrl, cdnPrefix, audioUrl }); - if (url) { - setFilePath(url); - } - }, [audioUrl, baseUrl, cdnPrefix]); - - return filePath; -}; diff --git a/app/containers/message/hooks/useFile.tsx b/app/containers/message/hooks/useFile.tsx index 2e5d34eb32..525b8b3eba 100644 --- a/app/containers/message/hooks/useFile.tsx +++ b/app/containers/message/hooks/useFile.tsx @@ -2,15 +2,19 @@ import { useEffect, useState } from 'react'; import { IAttachment } from '../../../definitions'; import { getMessageById } from '../../../lib/database/services/Message'; +import { getThreadMessageById } from '../../../lib/database/services/ThreadMessage'; export const useFile = (file: IAttachment, messageId: string) => { const [localFile, setLocalFile] = useState(file); const [isMessagePersisted, setIsMessagePersisted] = useState(!!messageId); useEffect(() => { const checkMessage = async () => { - const message = await getMessageById(messageId); - if (!message) { - setIsMessagePersisted(false); + const threadMessage = await getThreadMessageById(messageId); + if (!threadMessage) { + const message = await getMessageById(messageId); + if (!message) { + setIsMessagePersisted(false); + } } }; checkMessage(); diff --git a/app/containers/message/hooks/useMediaAutoDownload.tsx b/app/containers/message/hooks/useMediaAutoDownload.tsx new file mode 100644 index 0000000000..2d1aa5e25c --- /dev/null +++ b/app/containers/message/hooks/useMediaAutoDownload.tsx @@ -0,0 +1,165 @@ +import { useCallback, useContext, useEffect, useState } from 'react'; + +import { IAttachment, IUserMessage } from '../../../definitions'; +import { isImageBase64 } from '../../../lib/methods'; +import { fetchAutoDownloadEnabled } from '../../../lib/methods/autoDownloadPreference'; +import { + cancelDownload, + downloadMediaFile, + getMediaCache, + isDownloadActive, + MediaTypes, + TDownloadState +} from '../../../lib/methods/handleMediaDownload'; +import { emitter } from '../../../lib/methods/helpers'; +import { formatAttachmentUrl } from '../../../lib/methods/helpers/formatAttachmentUrl'; +import MessageContext from '../Context'; +import { useFile } from './useFile'; + +const getFileType = (file: IAttachment): MediaTypes | null => { + if (file.image_url) { + return 'image'; + } + if (file.video_url) { + return 'video'; + } + if (file.audio_url) { + return 'audio'; + } + return null; +}; + +const getFileProperty = (file: IAttachment, fileType: MediaTypes, property: 'url' | 'type') => { + if (fileType && file[`${fileType}_${property}`]) { + return file[`${fileType}_${property}`]; + } +}; + +export const useMediaAutoDownload = ({ + file, + author, + showAttachment +}: { + file: IAttachment; + author?: IUserMessage; + showAttachment?: Function; +}) => { + const fileType = getFileType(file) ?? 'image'; + const { id, baseUrl, user } = useContext(MessageContext); + const [status, setStatus] = useState('to-download'); + const [currentFile, setCurrentFile] = useFile(file, id); + const url = formatAttachmentUrl(file.title_link || getFileProperty(currentFile, fileType, 'url'), user.id, user.token, baseUrl); + const isEncrypted = currentFile.e2e === 'pending'; + + useEffect(() => { + const handleCache = async () => { + if (url) { + const isCached = await checkCache(); + if (isCached) { + return; + } + if (isDownloadActive(url)) { + resumeDownload(); + return; + } + await tryAutoDownload(); + } + }; + if (fileType === 'image' && isImageBase64(url)) { + setStatus('downloaded'); + } else { + handleCache(); + } + + return () => { + emitter.off(`downloadMedia${id}`, downloadMediaListener); + }; + }, []); + + const downloadMediaListener = useCallback((uri: string) => { + updateCurrentFile(uri); + }, []); + + const resumeDownload = () => { + emitter.on(`downloadMedia${id}`, downloadMediaListener); + }; + + const tryAutoDownload = async () => { + const isCurrentUserAuthor = author?._id === user.id; + const isAutoDownloadEnabled = fetchAutoDownloadEnabled(`${fileType}PreferenceDownload`); + if (isAutoDownloadEnabled || isCurrentUserAuthor) { + await download(); + } else { + setStatus('to-download'); + } + }; + + const download = async () => { + try { + setStatus('loading'); + const uri = await downloadMediaFile({ + messageId: id, + downloadUrl: url, + type: fileType, + mimeType: getFileProperty(currentFile, fileType, 'type'), + encryption: file.encryption, + originalChecksum: file.hashes?.sha256 + }); + setDecrypted(); + updateCurrentFile(uri); + } catch (e) { + setStatus('to-download'); + } + }; + + const updateCurrentFile = (uri: string) => { + setCurrentFile({ + title_link: uri + }); + setStatus('downloaded'); + }; + + const setDecrypted = () => { + if (isEncrypted) { + setCurrentFile({ + e2e: 'done' + }); + } + }; + + const checkCache = async () => { + const result = await getMediaCache({ + type: fileType, + mimeType: getFileProperty(currentFile, fileType, 'type'), + urlToCache: url + }); + if (result?.exists && !isEncrypted) { + updateCurrentFile(result.uri); + } + return result?.exists; + }; + + const onPress = () => { + if (status === 'loading') { + cancelDownload(url); + setStatus('to-download'); + return; + } + if (status === 'to-download') { + download(); + return; + } + if (!showAttachment || !currentFile.title_link || isEncrypted) { + return; + } + showAttachment(currentFile); + }; + + return { + status, + url, + onPress, + currentFile, + isEncrypted + }; +}; diff --git a/app/i18n/locales/en.json b/app/i18n/locales/en.json index 46b2cde2f0..67f4d083c8 100644 --- a/app/i18n/locales/en.json +++ b/app/i18n/locales/en.json @@ -317,7 +317,7 @@ "How_It_Works": "How it works", "I_Saved_My_E2E_Password": "I saved my E2E password", "Ignore": "Ignore", - "Images": "Images", + "Image": "Image", "Images_uploaded": "Images uploaded", "In_app": "In-app", "In_App_And_Desktop": "In-app and desktop", diff --git a/app/lib/constants/mediaAutoDownload.ts b/app/lib/constants/mediaAutoDownload.ts index f80a5dcd9c..f51f14a647 100644 --- a/app/lib/constants/mediaAutoDownload.ts +++ b/app/lib/constants/mediaAutoDownload.ts @@ -1,5 +1,5 @@ export type MediaDownloadOption = 'never' | 'wifi_mobile_data' | 'wifi'; -export const IMAGES_PREFERENCE_DOWNLOAD = 'imagesPreferenceDownload'; +export const IMAGE_PREFERENCE_DOWNLOAD = 'imagePreferenceDownload'; export const VIDEO_PREFERENCE_DOWNLOAD = 'videoPreferenceDownload'; export const AUDIO_PREFERENCE_DOWNLOAD = 'audioPreferenceDownload'; diff --git a/app/lib/encryption/encryption.ts b/app/lib/encryption/encryption.ts index 01972b2be1..7ccadbfe31 100644 --- a/app/lib/encryption/encryption.ts +++ b/app/lib/encryption/encryption.ts @@ -1,29 +1,19 @@ -import EJSON from 'ejson'; -import SimpleCrypto from 'react-native-simple-crypto'; +import { Model, Q } from '@nozbe/watermelondb'; import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; -import { Q, Model } from '@nozbe/watermelondb'; +import EJSON from 'ejson'; import { deleteAsync } from 'expo-file-system'; +import SimpleCrypto from 'react-native-simple-crypto'; -import UserPreferences from '../methods/userPreferences'; -import { getMessageById } from '../database/services/Message'; -import { getSubscriptionByRoomId } from '../database/services/Subscription'; -import database from '../database'; -import protectedFunction from '../methods/helpers/protectedFunction'; -import Deferred from './helpers/deferred'; -import log from '../methods/helpers/log'; -import { store } from '../store/auxStore'; -import { decryptAESCTR, joinVectorData, randomPassword, splitVectorData, toString, utf8ToBuffer } from './utils'; import { IMessage, + IServerAttachment, ISubscription, - TSendFileMessageFileInfo, TMessageModel, + TSendFileMessageFileInfo, TSubscriptionModel, TThreadMessageModel, - TThreadModel, - IServerAttachment + TThreadModel } from '../../definitions'; -import EncryptionRoom from './room'; import { E2E_BANNER_TYPE, E2E_MESSAGE_TYPE, @@ -32,9 +22,18 @@ import { E2E_RANDOM_PASSWORD_KEY, E2E_STATUS } from '../constants'; +import database from '../database'; +import { getSubscriptionByRoomId } from '../database/services/Subscription'; +import log from '../methods/helpers/log'; +import protectedFunction from '../methods/helpers/protectedFunction'; +import UserPreferences from '../methods/userPreferences'; import { Services } from '../services'; -import { IDecryptionFileQueue, TDecryptFile, TEncryptFile } from './definitions'; +import { store } from '../store/auxStore'; import { MAX_CONCURRENT_QUEUE } from './constants'; +import { IDecryptionFileQueue, TDecryptFile, TEncryptFile } from './definitions'; +import Deferred from './helpers/deferred'; +import EncryptionRoom from './room'; +import { decryptAESCTR, joinVectorData, randomPassword, splitVectorData, toString, utf8ToBuffer } from './utils'; class Encryption { ready: boolean; @@ -548,7 +547,6 @@ class Encryption { }; decryptFile: TDecryptFile = async (messageId, path, encryption, originalChecksum) => { - const messageRecord = await getMessageById(messageId); const decryptedFile = await decryptAESCTR(path, encryption.key.k, encryption.iv); if (decryptedFile) { const checksum = await SimpleCrypto.utils.calculateFileChecksum(decryptedFile); @@ -556,19 +554,6 @@ class Encryption { await deleteAsync(decryptedFile); return null; } - - if (messageRecord) { - const db = database.active; - await db.write(async () => { - await messageRecord.update(m => { - m.attachments = m.attachments?.map(att => ({ - ...att, - title_link: decryptedFile, - e2e: 'done' - })); - }); - }); - } } return decryptedFile; }; diff --git a/app/lib/methods/autoDownloadPreference.ts b/app/lib/methods/autoDownloadPreference.ts index 71078b161b..340114fdca 100644 --- a/app/lib/methods/autoDownloadPreference.ts +++ b/app/lib/methods/autoDownloadPreference.ts @@ -1,7 +1,7 @@ import { NetInfoStateType } from '@react-native-community/netinfo'; import { - IMAGES_PREFERENCE_DOWNLOAD, + IMAGE_PREFERENCE_DOWNLOAD, AUDIO_PREFERENCE_DOWNLOAD, VIDEO_PREFERENCE_DOWNLOAD, MediaDownloadOption @@ -9,7 +9,7 @@ import { import userPreferences from './userPreferences'; import { store } from '../store/auxStore'; -type TMediaType = typeof IMAGES_PREFERENCE_DOWNLOAD | typeof AUDIO_PREFERENCE_DOWNLOAD | typeof VIDEO_PREFERENCE_DOWNLOAD; +type TMediaType = typeof IMAGE_PREFERENCE_DOWNLOAD | typeof AUDIO_PREFERENCE_DOWNLOAD | typeof VIDEO_PREFERENCE_DOWNLOAD; export const fetchAutoDownloadEnabled = (mediaType: TMediaType) => { const { netInfoState } = store.getState().app; @@ -24,7 +24,7 @@ export const fetchAutoDownloadEnabled = (mediaType: TMediaType) => { } if (mediaDownloadPreference === null) { - if (mediaType === 'imagesPreferenceDownload') { + if (mediaType === 'imagePreferenceDownload') { return true; } if (mediaType === 'audioPreferenceDownload' || mediaType === 'videoPreferenceDownload') { diff --git a/app/lib/methods/handleMediaDownload.ts b/app/lib/methods/handleMediaDownload.ts index 7a31f7a1d9..367abf9832 100644 --- a/app/lib/methods/handleMediaDownload.ts +++ b/app/lib/methods/handleMediaDownload.ts @@ -1,13 +1,18 @@ import * as FileSystem from 'expo-file-system'; import * as mime from 'react-native-mime-types'; import { isEmpty } from 'lodash'; +import { Model } from '@nozbe/watermelondb'; -import { TAttachmentEncryption } from '../../definitions'; +import { IAttachment, TAttachmentEncryption, TMessageModel } from '../../definitions'; import { sanitizeLikeString } from '../database/utils'; import { store } from '../store/auxStore'; import log from './helpers/log'; import { emitter } from './helpers'; import { Encryption } from '../encryption'; +import { getMessageById } from '../database/services/Message'; +import { getThreadMessageById } from '../database/services/ThreadMessage'; +import database from '../database'; +import { getThreadById } from '../database/services/Thread'; export type MediaTypes = 'audio' | 'image' | 'video'; export type TDownloadState = 'to-download' | 'loading' | 'downloaded'; @@ -194,6 +199,55 @@ export async function cancelDownload(messageUrl: string): Promise { } } +const mapAttachments = ({ + attachments, + uri, + encryption +}: { + attachments?: IAttachment[]; + uri: string; + encryption: boolean; +}): TMessageModel['attachments'] => + attachments?.map(att => ({ + ...att, + title_link: uri, + e2e: encryption ? 'done' : undefined + })); + +const persistMessage = async (messageId: string, uri: string, encryption: boolean) => { + const db = database.active; + const batch: Model[] = []; + const messageRecord = await getMessageById(messageId); + if (messageRecord) { + batch.push( + messageRecord.prepareUpdate(m => { + m.attachments = mapAttachments({ attachments: m.attachments, uri, encryption }); + }) + ); + } + const threadRecord = await getThreadById(messageId); + if (threadRecord) { + batch.push( + threadRecord.prepareUpdate(m => { + m.attachments = mapAttachments({ attachments: m.attachments, uri, encryption }); + }) + ); + } + const threadMessageRecord = await getThreadMessageById(messageId); + if (threadMessageRecord) { + batch.push( + threadMessageRecord.prepareUpdate(m => { + m.attachments = mapAttachments({ attachments: m.attachments, uri, encryption }); + }) + ); + } + if (batch.length) { + await db.write(async () => { + await db.batch(...batch); + }); + } +}; + export function downloadMediaFile({ messageId, type, @@ -228,6 +282,8 @@ export function downloadMediaFile({ await Encryption.addFileToDecryptFileQueue(messageId, result.uri, encryption, originalChecksum); } + await persistMessage(messageId, result.uri, !!encryption); + emitter.emit(`downloadMedia${messageId}`, result.uri); return resolve(result.uri); } catch (e) { diff --git a/app/lib/methods/loadThreadMessages.ts b/app/lib/methods/loadThreadMessages.ts index 6a73891bab..9462fdea61 100644 --- a/app/lib/methods/loadThreadMessages.ts +++ b/app/lib/methods/loadThreadMessages.ts @@ -62,7 +62,9 @@ export function loadThreadMessages({ tmid, rid }: { tmid: string; rid: string }) const newThreadMessage = data.find((t: TThreadMessageModel) => t._id === threadMessage.id); return threadMessage.prepareUpdate( protectedFunction((tm: TThreadMessageModel) => { + const { attachments } = tm; Object.assign(tm, newThreadMessage); + tm.attachments = attachments; if (threadMessage.tmid) { tm.rid = threadMessage.tmid; } diff --git a/app/views/MediaAutoDownloadView/index.tsx b/app/views/MediaAutoDownloadView/index.tsx index 9ea3befab4..5dae262e26 100644 --- a/app/views/MediaAutoDownloadView/index.tsx +++ b/app/views/MediaAutoDownloadView/index.tsx @@ -9,7 +9,7 @@ import ListPicker from './ListPicker'; import { useUserPreferences } from '../../lib/methods/userPreferences'; import { AUDIO_PREFERENCE_DOWNLOAD, - IMAGES_PREFERENCE_DOWNLOAD, + IMAGE_PREFERENCE_DOWNLOAD, MediaDownloadOption, VIDEO_PREFERENCE_DOWNLOAD } from '../../lib/constants'; @@ -18,7 +18,7 @@ import { SettingsStackParamList } from '../../stacks/types'; const MediaAutoDownload = () => { const [imagesPreference, setImagesPreference] = useUserPreferences( - IMAGES_PREFERENCE_DOWNLOAD, + IMAGE_PREFERENCE_DOWNLOAD, 'wifi_mobile_data' ); const [videoPreference, setVideoPreference] = useUserPreferences(VIDEO_PREFERENCE_DOWNLOAD, 'wifi'); @@ -36,7 +36,7 @@ const MediaAutoDownload = () => { - + From 3933932dff922f29b51e9060ab1b6efd55138b92 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Wed, 31 Jul 2024 14:43:42 -0300 Subject: [PATCH 4/4] fix: image preview size (#5813) --- .../Attachments/Image/Container.tsx | 5 +- .../Components/Attachments/Image/Image.tsx | 57 +++- .../message/Components/Attachments/Reply.tsx | 6 +- .../message/Components/Attachments/Video.tsx | 25 +- .../message/Components/WidthAwareView.tsx | 26 ++ app/containers/message/Urls.tsx | 248 +++++++++--------- app/containers/message/styles.ts | 7 +- 7 files changed, 211 insertions(+), 163 deletions(-) create mode 100644 app/containers/message/Components/WidthAwareView.tsx diff --git a/app/containers/message/Components/Attachments/Image/Container.tsx b/app/containers/message/Components/Attachments/Image/Container.tsx index abee3b5969..12209c8eb5 100644 --- a/app/containers/message/Components/Attachments/Image/Container.tsx +++ b/app/containers/message/Components/Attachments/Image/Container.tsx @@ -8,6 +8,7 @@ import { Button } from './Button'; import { MessageImage } from './Image'; import { IImageContainer } from './definitions'; import MessageContext from '../../../Context'; +import { WidthAwareView } from '../../WidthAwareView'; const ImageContainer = ({ file, @@ -24,7 +25,9 @@ const ImageContainer = ({ const image = ( ); diff --git a/app/containers/message/Components/Attachments/Image/Image.tsx b/app/containers/message/Components/Attachments/Image/Image.tsx index 45518908ee..4bad3c143b 100644 --- a/app/containers/message/Components/Attachments/Image/Image.tsx +++ b/app/containers/message/Components/Attachments/Image/Image.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { View } from 'react-native'; +import React, { useContext, useEffect, useState } from 'react'; +import { View, ViewStyle, Image } from 'react-native'; import FastImage from 'react-native-fast-image'; import { isValidUrl } from '../../../../../lib/methods/helpers/isValidUrl'; @@ -7,34 +7,65 @@ import { useTheme } from '../../../../../theme'; import styles from '../../../styles'; import OverlayComponent from '../../OverlayComponent'; import { IMessageImage } from './definitions'; +import { WidthAwareContext } from '../../WidthAwareView'; export const MessageImage = React.memo(({ uri, status, encrypted = false }: IMessageImage) => { const { colors } = useTheme(); + const [imageDimensions, setImageDimensions] = useState({ width: 0, height: 0 }); + const maxSize = useContext(WidthAwareContext); + const showImage = isValidUrl(uri) && imageDimensions.width && status === 'downloaded'; + + useEffect(() => { + if (status === 'downloaded') { + Image.getSize(uri, (width, height) => { + setImageDimensions({ width, height }); + }); + } + }, [uri, status]); + + const width = Math.min(imageDimensions.width, maxSize) || 0; + const height = Math.min((imageDimensions.height * ((width * 100) / imageDimensions.width)) / 100, maxSize) || 0; + const imageStyle = { + width, + height + }; + + const containerStyle: ViewStyle = { + alignItems: 'center', + justifyContent: 'center', + ...(imageDimensions.width <= 64 && { width: 64 }), + ...(imageDimensions.height <= 64 && { height: 64 }) + }; + + const borderStyle: ViewStyle = { + borderColor: colors.strokeLight, + borderWidth: 1, + borderRadius: 4, + overflow: 'hidden' + }; if (encrypted && status === 'downloaded') { return ( <> - + ); } return ( <> - {isValidUrl(uri) && status === 'downloaded' ? ( - + {showImage ? ( + + + ) : ( - + )} - {['loading', 'to-download'].includes(status) ? ( + {['loading', 'to-download'].includes(status) || (status === 'downloaded' && !showImage) ? ( ) : null} diff --git a/app/containers/message/Components/Attachments/Reply.tsx b/app/containers/message/Components/Attachments/Reply.tsx index 822a2c30e1..eb374a5beb 100644 --- a/app/containers/message/Components/Attachments/Reply.tsx +++ b/app/containers/message/Components/Attachments/Reply.tsx @@ -223,9 +223,9 @@ const Reply = React.memo( openLink(url, theme); }; - let { strokeExtraLight } = themes[theme]; + let { strokeLight } = themes[theme]; if (attachment.color) { - strokeExtraLight = attachment.color; + strokeLight = attachment.color; } return ( @@ -239,7 +239,7 @@ const Reply = React.memo( index > 0 && styles.marginTop, msg && styles.marginBottom, { - borderColor: strokeExtraLight + borderColor: strokeLight } ]} background={Touchable.Ripple(themes[theme].surfaceNeutral)} diff --git a/app/containers/message/Components/Attachments/Video.tsx b/app/containers/message/Components/Attachments/Video.tsx index c114ae6885..23b72e6e46 100644 --- a/app/containers/message/Components/Attachments/Video.tsx +++ b/app/containers/message/Components/Attachments/Video.tsx @@ -5,7 +5,6 @@ import { IUserMessage } from '../../../../definitions'; import { IAttachment } from '../../../../definitions/IAttachment'; import { TGetCustomEmoji } from '../../../../definitions/IEmoji'; import I18n from '../../../../i18n'; -import { themes } from '../../../../lib/constants'; import { fileDownload, isIOS } from '../../../../lib/methods/helpers'; import EventEmitter from '../../../../lib/methods/helpers/events'; import { useTheme } from '../../../../theme'; @@ -16,22 +15,14 @@ import Markdown from '../../../markdown'; import MessageContext from '../../Context'; import Touchable from '../../Touchable'; import { useMediaAutoDownload } from '../../hooks/useMediaAutoDownload'; -import { DEFAULT_MESSAGE_HEIGHT } from '../../utils'; import BlurComponent from '../OverlayComponent'; import { TDownloadState } from '../../../../lib/methods/handleMediaDownload'; +import messageStyles from '../../styles'; const SUPPORTED_TYPES = ['video/quicktime', 'video/mp4', ...(isIOS ? [] : ['video/3gp', 'video/mkv'])]; const isTypeSupported = (type: string) => SUPPORTED_TYPES.indexOf(type) !== -1; const styles = StyleSheet.create({ - button: { - flex: 1, - borderRadius: 4, - height: DEFAULT_MESSAGE_HEIGHT, - marginBottom: 6, - alignItems: 'center', - justifyContent: 'center' - }, cancelContainer: { position: 'absolute', top: 8, @@ -63,6 +54,7 @@ const CancelIndicator = () => { }; const Thumbnail = ({ status, encrypted = false }: { status: TDownloadState; encrypted: boolean }) => { + const { colors } = useTheme(); let icon: TIconsName = status === 'downloaded' ? 'play-filled' : 'arrow-down-circle'; if (encrypted && status === 'downloaded') { icon = 'encrypted'; @@ -70,7 +62,11 @@ const Thumbnail = ({ status, encrypted = false }: { status: TDownloadState; encr return ( <> - + {status === 'loading' ? : null} ); @@ -86,7 +82,7 @@ const Video = ({ msg }: IMessageVideo): React.ReactElement | null => { const { user } = useContext(MessageContext); - const { theme } = useTheme(); + const { theme, colors } = useTheme(); const { status, onPress, url, isEncrypted, currentFile } = useMediaAutoDownload({ file, author, showAttachment }); const _onPress = async () => { @@ -115,10 +111,7 @@ const Video = ({ return ( <> - + diff --git a/app/containers/message/Components/WidthAwareView.tsx b/app/containers/message/Components/WidthAwareView.tsx new file mode 100644 index 0000000000..82103fd7be --- /dev/null +++ b/app/containers/message/Components/WidthAwareView.tsx @@ -0,0 +1,26 @@ +import { createContext, ReactElement, useState } from 'react'; +import { View, StyleSheet } from 'react-native'; + +export const WidthAwareContext = createContext(0); + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row' + } +}); + +export const WidthAwareView = ({ children }: { children: ReactElement }) => { + const [width, setWidth] = useState(0); + + return ( + { + if (ev.nativeEvent.layout.width) { + setWidth(ev.nativeEvent.layout.width); + } + }}> + {children} + + ); +}; diff --git a/app/containers/message/Urls.tsx b/app/containers/message/Urls.tsx index f2b26625d7..7e1ecaf5cd 100644 --- a/app/containers/message/Urls.tsx +++ b/app/containers/message/Urls.tsx @@ -1,5 +1,5 @@ -import React, { useContext, useEffect, useState } from 'react'; -import { Image, StyleSheet, Text, unstable_batchedUpdates, View } from 'react-native'; +import React, { ReactElement, useContext, useEffect, useState } from 'react'; +import { Image, StyleSheet, Text, View, ViewStyle } from 'react-native'; import Clipboard from '@react-native-clipboard/clipboard'; import FastImage from 'react-native-fast-image'; import { dequal } from 'dequal'; @@ -7,24 +7,20 @@ import { dequal } from 'dequal'; import Touchable from './Touchable'; import openLink from '../../lib/methods/helpers/openLink'; import sharedStyles from '../../views/Styles'; -import { themes } from '../../lib/constants'; -import { TSupportedThemes, useTheme, withTheme } from '../../theme'; +import { useTheme } from '../../theme'; import { LISTENER } from '../Toast'; import EventEmitter from '../../lib/methods/helpers/events'; import I18n from '../../i18n'; import MessageContext from './Context'; import { IUrl } from '../../definitions'; -import { DEFAULT_MESSAGE_HEIGHT } from './utils'; +import { WidthAwareContext, WidthAwareView } from './Components/WidthAwareView'; const styles = StyleSheet.create({ - button: { - marginTop: 6 - }, container: { flex: 1, flexDirection: 'column', - borderRadius: 4, - borderWidth: 1 + marginTop: 4, + gap: 4 }, textContainer: { flex: 1, @@ -41,18 +37,6 @@ const styles = StyleSheet.create({ fontSize: 16, ...sharedStyles.textRegular }, - marginTop: { - marginTop: 4 - }, - image: { - width: '100%', - height: DEFAULT_MESSAGE_HEIGHT, - borderTopLeftRadius: 4, - borderTopRightRadius: 4 - }, - imageWithoutContent: { - borderRadius: 4 - }, loading: { height: 0, borderWidth: 0, @@ -60,128 +44,144 @@ const styles = StyleSheet.create({ } }); -const UrlContent = React.memo( - ({ title, description }: { title: string; description: string }) => { - const { colors } = useTheme(); - return ( - - {title ? ( - - {title} - - ) : null} - {description ? ( - - {description} - - ) : null} - - ); - }, - (prevProps, nextProps) => { - if (prevProps.title !== nextProps.title) { - return false; +const UrlContent = ({ title, description }: { title: string; description: string }) => { + const { colors } = useTheme(); + return ( + + {title ? ( + + {title} + + ) : null} + {description ? ( + + {description} + + ) : null} + + ); +}; +const UrlImage = ({ image, hasContent }: { image: string; hasContent: boolean }) => { + const { colors } = useTheme(); + const [imageLoadedState, setImageLoadedState] = useState('loading'); + const [imageDimensions, setImageDimensions] = useState({ width: 0, height: 0 }); + const maxSize = useContext(WidthAwareContext); + + useEffect(() => { + if (image) { + Image.getSize( + image, + (width, height) => { + setImageDimensions({ width, height }); + }, + () => { + setImageLoadedState('error'); + } + ); } - if (prevProps.description !== nextProps.description) { - return false; + }, [image]); + + let imageStyle = {}; + let containerStyle: ViewStyle = {}; + + if (imageLoadedState === 'done') { + const width = Math.min(imageDimensions.width, maxSize) || 0; + const height = Math.min((imageDimensions.height * ((width * 100) / imageDimensions.width)) / 100, maxSize) || 0; + imageStyle = { + width, + height + }; + containerStyle = { + overflow: 'hidden', + alignItems: 'center', + justifyContent: 'center', + ...(imageDimensions.width <= 64 && { width: 64 }), + ...(imageDimensions.height <= 64 && { height: 64 }) + }; + if (!hasContent) { + containerStyle = { + ...containerStyle, + borderColor: colors.strokeLight, + borderWidth: 1, + borderRadius: 4 + }; } - return true; } -); + + return ( + + setImageLoadedState('error')} + onLoad={() => setImageLoadedState('done')} + /> + + ); +}; type TImageLoadedState = 'loading' | 'done' | 'error'; -const Url = React.memo( - ({ url, index, theme }: { url: IUrl; index: number; theme: TSupportedThemes }) => { - const [imageLoadedState, setImageLoadedState] = useState('loading'); - const [imageDimensions, setImageDimensions] = useState({ width: 0, height: 0 }); - const { baseUrl, user } = useContext(MessageContext); - let image = url.image || url.url; - image = image.includes('http') ? image : `${baseUrl}/${image}?rc_uid=${user.id}&rc_token=${user.token}`; - - useEffect(() => { - if (image) { - Image.getSize( - image, - (width, height) => { - unstable_batchedUpdates(() => { - setImageDimensions({ width, height }); - setImageLoadedState('done'); - }); - }, - () => { - setImageLoadedState('error'); - } - ); - } - }, [image]); - - const onPress = () => openLink(url.url, theme); - - const onLongPress = () => { - Clipboard.setString(url.url); - EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') }); - }; +const Url = ({ url }: { url: IUrl }) => { + const { colors, theme } = useTheme(); + const { baseUrl, user } = useContext(MessageContext); + let image = url.image || url.url; + image = image.includes('http') ? image : `${baseUrl}/${image}?rc_uid=${user.id}&rc_token=${user.token}`; - const hasContent = url.title || url.description; + const onPress = () => openLink(url.url, theme); - if (!url || url?.ignoreParse || imageLoadedState === 'error' || !imageDimensions.width || !imageDimensions.height) { - return null; - } + const onLongPress = () => { + Clipboard.setString(url.url); + EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') }); + }; - return ( - 0 && styles.marginTop, - styles.container, - { - backgroundColor: themes[theme].surfaceTint, - borderColor: themes[theme].strokeLight - }, - imageLoadedState === 'loading' && styles.loading - ]} - background={Touchable.Ripple(themes[theme].surfaceNeutral)}> - <> - {image ? ( - setImageLoadedState('error')} - onLoad={() => setImageLoadedState('done')} - /> - ) : null} - {hasContent ? : null} - - - ); - }, - (oldProps, newProps) => dequal(oldProps.url, newProps.url) && oldProps.theme === newProps.theme -); + const hasContent = !!(url.title || url.description); -const Urls = React.memo( - // TODO - didn't work - (React.ReactElement | null)[] | React.ReactElement | null - ({ urls }: { urls?: IUrl[] }): any => { - const { theme } = useTheme(); + if (!url || url?.ignoreParse) { + return null; + } + return ( + + <> + {image ? ( + + + + ) : null} + {hasContent ? : null} + + + ); +}; +const Urls = React.memo( + ({ urls }: { urls?: IUrl[] }): ReactElement[] | null => { if (!urls || urls.length === 0) { return null; } - return urls.map((url: IUrl, index: number) => ); + return urls.map((url: IUrl) => ); }, (oldProps, newProps) => dequal(oldProps.urls, newProps.urls) ); UrlContent.displayName = 'MessageUrlContent'; +UrlImage.displayName = 'MessageUrlImage'; Url.displayName = 'MessageUrl'; Urls.displayName = 'MessageUrls'; -export default withTheme(Urls); +export default Urls; diff --git a/app/containers/message/styles.ts b/app/containers/message/styles.ts index 12ae4f9604..c5c42d2030 100644 --- a/app/containers/message/styles.ts +++ b/app/containers/message/styles.ts @@ -102,12 +102,7 @@ export default StyleSheet.create({ width: '100%', minHeight: isTablet ? 300 : 200, borderRadius: 4, - borderWidth: 1, - overflow: 'hidden', - borderColor: 'rgba(0, 0, 0, 0.1)' - }, - imageBlurContainer: { - height: '100%' + overflow: 'hidden' }, imagePressed: { opacity: 0.5