diff --git a/android/app/build.gradle b/android/app/build.gradle
index 1e849a36e7..917aa93959 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -93,7 +93,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode VERSIONCODE as Integer
- versionName "4.50.0"
+ versionName "4.50.1"
vectorDrawables.useSupportLibrary = true
if (!isFoss) {
manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String]
diff --git a/android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningModule.java b/android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningModule.java
index 1e0ab23b7c..2e7f639fac 100644
--- a/android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningModule.java
+++ b/android/app/src/main/java/chat/rocket/reactnative/networking/SSLPinningModule.java
@@ -39,6 +39,7 @@
import com.dylanvann.fastimage.FastImageOkHttpUrlLoader;
import expo.modules.av.player.datasource.SharedCookiesDataSourceFactory;
+import expo.modules.filesystem.FileSystemModule;
public class SSLPinningModule extends ReactContextBaseJavaModule implements KeyChainAliasCallback {
@@ -113,6 +114,8 @@ public void setCertificate(String data, Promise promise) {
FastImageOkHttpUrlLoader.setOkHttpClient(getOkHttpClient());
// Expo AV network layer
SharedCookiesDataSourceFactory.setOkHttpClient(getOkHttpClient());
+ // Expo File System network layer
+ FileSystemModule.setOkHttpClient(getOkHttpClient());
promise.resolve(null);
}
diff --git a/app/containers/MessageComposer/MessageComposer.test.tsx b/app/containers/MessageComposer/MessageComposer.test.tsx
index f1326120cd..5cdc418c58 100644
--- a/app/containers/MessageComposer/MessageComposer.test.tsx
+++ b/app/containers/MessageComposer/MessageComposer.test.tsx
@@ -100,8 +100,10 @@ describe('MessageComposer', () => {
const onSendMessage = jest.fn();
render();
expect(screen.getByTestId('message-composer-send-audio')).toBeOnTheScreen();
+ expect(screen.queryByTestId('message-composer-send')).not.toBeOnTheScreen();
await user.type(screen.getByTestId('message-composer-input'), 'test');
+ expect(screen.getByTestId('message-composer-input')).not.toBe('');
expect(screen.queryByTestId('message-composer-send-audio')).not.toBeOnTheScreen();
expect(screen.getByTestId('message-composer-send')).toBeOnTheScreen();
diff --git a/app/containers/MessageComposer/components/ComposerInput.tsx b/app/containers/MessageComposer/components/ComposerInput.tsx
index 029c650c48..096f799a84 100644
--- a/app/containers/MessageComposer/components/ComposerInput.tsx
+++ b/app/containers/MessageComposer/components/ComposerInput.tsx
@@ -152,7 +152,8 @@ export const ComposerInput = memo(
}));
const setInput: TSetInput = (text, selection) => {
- textRef.current = text;
+ const message = text.trim();
+ textRef.current = message;
if (inputRef.current) {
inputRef.current.setNativeProps({ text });
}
@@ -163,7 +164,7 @@ export const ComposerInput = memo(
selectionRef.current = selection;
}, 50);
}
- setMicOrSend(text.length === 0 ? 'mic' : 'send');
+ setMicOrSend(message.length === 0 ? 'mic' : 'send');
};
const focus = () => {
diff --git a/app/containers/MessageComposer/constants.ts b/app/containers/MessageComposer/constants.ts
index 1786de36f2..c35268c42e 100644
--- a/app/containers/MessageComposer/constants.ts
+++ b/app/containers/MessageComposer/constants.ts
@@ -6,13 +6,15 @@ export const IMAGE_PICKER_CONFIG = {
cropping: true,
avoidEmptySpaceAroundImage: false,
freeStyleCropEnabled: true,
- forceJpg: true
+ forceJpg: true,
+ includeExif: true
};
export const LIBRARY_PICKER_CONFIG: Options = {
multiple: true,
compressVideoPreset: 'Passthrough',
- mediaType: 'any'
+ mediaType: 'any',
+ includeExif: true
};
export const VIDEO_PICKER_CONFIG: Options = {
diff --git a/app/containers/UIKit/Image.tsx b/app/containers/UIKit/Image.tsx
index 42ebc31bca..96c57a9315 100644
--- a/app/containers/UIKit/Image.tsx
+++ b/app/containers/UIKit/Image.tsx
@@ -3,7 +3,7 @@ import { StyleSheet, View } from 'react-native';
import FastImage from 'react-native-fast-image';
import { BlockContext } from '@rocket.chat/ui-kit';
-import ImageContainer from '../message/Image';
+import ImageContainer from '../message/Components/Attachments/Image';
import Navigation from '../../lib/navigation/appNavigation';
import { IThumb, IImage, IElement } from './interfaces';
import { IAttachment } from '../../definitions';
diff --git a/app/containers/message/Components/Attachments/AttachedActions.tsx b/app/containers/message/Components/Attachments/AttachedActions.tsx
new file mode 100644
index 0000000000..f0185427b9
--- /dev/null
+++ b/app/containers/message/Components/Attachments/AttachedActions.tsx
@@ -0,0 +1,47 @@
+import React, { useContext } from 'react';
+
+import Button from '../../../Button';
+import MessageContext from '../../Context';
+import { IAttachment, TGetCustomEmoji } from '../../../../definitions';
+import openLink from '../../../../lib/methods/helpers/openLink';
+import Markdown from '../../../markdown';
+
+export type TElement = {
+ type: string;
+ msg?: string;
+ url?: string;
+ text: string;
+};
+
+const AttachedActions = ({ attachment, getCustomEmoji }: { attachment: IAttachment; getCustomEmoji: TGetCustomEmoji }) => {
+ const { onAnswerButtonPress } = useContext(MessageContext);
+
+ if (!attachment.actions) {
+ return null;
+ }
+
+ const attachedButtons = attachment.actions.map((element: TElement) => {
+ const onPress = () => {
+ if (element.msg) {
+ onAnswerButtonPress(element.msg);
+ }
+
+ if (element.url) {
+ openLink(element.url);
+ }
+ };
+
+ if (element.type === 'button') {
+ return ;
+ }
+
+ return null;
+ });
+ return (
+ <>
+
+ {attachedButtons}
+ >
+ );
+};
+export default AttachedActions;
diff --git a/app/containers/message/Attachments.tsx b/app/containers/message/Components/Attachments/Attachments.tsx
similarity index 65%
rename from app/containers/message/Attachments.tsx
rename to app/containers/message/Components/Attachments/Attachments.tsx
index a35aa0f5b0..de845f634f 100644
--- a/app/containers/message/Attachments.tsx
+++ b/app/containers/message/Components/Attachments/Attachments.tsx
@@ -1,57 +1,16 @@
import React, { useContext } from 'react';
import { dequal } from 'dequal';
-import { IMessageAttachments } from './interfaces';
import Image from './Image';
import Audio from './Audio';
import Video from './Video';
import { Reply } from './components';
-import Button from '../Button';
-import MessageContext from './Context';
-import { IAttachment, TGetCustomEmoji } from '../../definitions';
-import CollapsibleQuote from './Components/CollapsibleQuote';
-import openLink from '../../lib/methods/helpers/openLink';
-import Markdown from '../markdown';
-import { getMessageFromAttachment } from './utils';
-
-export type TElement = {
- type: string;
- msg?: string;
- url?: string;
- text: string;
-};
-
-const AttachedActions = ({ attachment, getCustomEmoji }: { attachment: IAttachment; getCustomEmoji: TGetCustomEmoji }) => {
- const { onAnswerButtonPress } = useContext(MessageContext);
-
- if (!attachment.actions) {
- return null;
- }
-
- const attachedButtons = attachment.actions.map((element: TElement) => {
- const onPress = () => {
- if (element.msg) {
- onAnswerButtonPress(element.msg);
- }
-
- if (element.url) {
- openLink(element.url);
- }
- };
-
- if (element.type === 'button') {
- return ;
- }
-
- return null;
- });
- return (
- <>
-
- {attachedButtons}
- >
- );
-};
+import CollapsibleQuote from './CollapsibleQuote';
+import AttachedActions from './AttachedActions';
+import MessageContext from '../../Context';
+import { IMessageAttachments } from '../../interfaces';
+import { IAttachment } from '../../../../definitions';
+import { getMessageFromAttachment } from '../../utils';
const Attachments: React.FC = React.memo(
({ attachments, timeFormat, showAttachment, style, getCustomEmoji, isReply, author }: IMessageAttachments) => {
@@ -101,6 +60,7 @@ const Attachments: React.FC = React.memo(
getCustomEmoji={getCustomEmoji}
style={style}
isReply={isReply}
+ author={author}
msg={msg}
/>
);
diff --git a/app/containers/message/Audio.tsx b/app/containers/message/Components/Attachments/Audio.tsx
similarity index 64%
rename from app/containers/message/Audio.tsx
rename to app/containers/message/Components/Attachments/Audio.tsx
index 516343ff67..bdb12fe520 100644
--- a/app/containers/message/Audio.tsx
+++ b/app/containers/message/Components/Attachments/Audio.tsx
@@ -1,21 +1,16 @@
-import React, { useContext, useEffect, useState } from 'react';
+import React, { useCallback, useContext, useEffect, useState } from 'react';
import { StyleProp, TextStyle } from 'react-native';
-import Markdown from '../markdown';
-import MessageContext from './Context';
-import { TGetCustomEmoji } from '../../definitions/IEmoji';
-import { IAttachment, IUserMessage } from '../../definitions';
-import {
- TDownloadState,
- downloadMediaFile,
- getMediaCache,
- isDownloadActive,
- resumeMediaFile
-} from '../../lib/methods/handleMediaDownload';
-import { fetchAutoDownloadEnabled } from '../../lib/methods/autoDownloadPreference';
-import AudioPlayer from '../AudioPlayer';
-import { useAudioUrl } from './hooks/useAudioUrl';
-import { getAudioUrlToCache } from '../../lib/methods/getAudioUrl';
+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 AudioPlayer from '../../../AudioPlayer';
+import { useAudioUrl } from '../../hooks/useAudioUrl';
+import { getAudioUrlToCache } from '../../../../lib/methods/getAudioUrl';
interface IMessageAudioProps {
file: IAttachment;
@@ -31,7 +26,6 @@ const MessageAudio = ({ file, getCustomEmoji, author, isReply, style, msg }: IMe
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 () => {
@@ -48,12 +42,15 @@ const MessageAudio = ({ file, getCustomEmoji, author, isReply, style, msg }: IMe
setDownloadState('loading');
try {
if (audioUrl) {
- const audio = await downloadMediaFile({
+ const audioUri = await downloadMediaFile({
+ messageId: id,
downloadUrl: getAudioUrlToCache({ token: user.token, userId: user.id, url: audioUrl }),
type: 'audio',
- mimeType: file.audio_type
+ mimeType: file.audio_type,
+ encryption: file.encryption,
+ originalChecksum: file.hashes?.sha256
});
- setFileUri(audio);
+ setFileUri(audioUri);
setDownloadState('downloaded');
}
} catch {
@@ -83,26 +80,16 @@ const MessageAudio = ({ file, getCustomEmoji, author, isReply, style, msg }: IMe
mimeType: file.audio_type,
urlToCache: audioUrl
});
- if (cachedAudioResult?.exists) {
+ const result = cachedAudioResult?.exists && file.e2e !== 'pending';
+ if (result) {
setFileUri(cachedAudioResult.uri);
setDownloadState('downloaded');
}
- return !!cachedAudioResult?.exists;
+ return result;
};
- const handleResumeDownload = async () => {
- try {
- setDownloadState('loading');
- if (audioUrl) {
- const videoUri = await resumeMediaFile({
- downloadUrl: audioUrl
- });
- setFileUri(videoUri);
- setDownloadState('downloaded');
- }
- } catch (e) {
- setDownloadState('to-download');
- }
+ const handleResumeDownload = () => {
+ emitter.on(`downloadMedia${id}`, downloadMediaListener);
};
useEffect(() => {
@@ -122,6 +109,15 @@ const MessageAudio = ({ file, getCustomEmoji, author, isReply, style, msg }: IMe
}
}, [audioUrl]);
+ const downloadMediaListener = useCallback((uri: string) => {
+ setFileUri(uri);
+ setDownloadState('downloaded');
+ }, []);
+
+ useEffect(() => () => {
+ emitter.off(`downloadMedia${id}`, downloadMediaListener);
+ });
+
if (!baseUrl) {
return null;
}
diff --git a/app/containers/message/Components/CollapsibleQuote/CollapsibleQuote.stories.tsx b/app/containers/message/Components/Attachments/CollapsibleQuote/CollapsibleQuote.stories.tsx
similarity index 95%
rename from app/containers/message/Components/CollapsibleQuote/CollapsibleQuote.stories.tsx
rename to app/containers/message/Components/Attachments/CollapsibleQuote/CollapsibleQuote.stories.tsx
index 1b837d04e5..c6cfa91ddf 100644
--- a/app/containers/message/Components/CollapsibleQuote/CollapsibleQuote.stories.tsx
+++ b/app/containers/message/Components/Attachments/CollapsibleQuote/CollapsibleQuote.stories.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import { View } from 'react-native';
-import MessageContext from '../../Context';
+import MessageContext from '../../../Context';
import CollapsibleQuote from '.';
const testAttachment = {
diff --git a/app/containers/message/Components/CollapsibleQuote/CollapsibleQuote.test.tsx b/app/containers/message/Components/Attachments/CollapsibleQuote/CollapsibleQuote.test.tsx
similarity index 98%
rename from app/containers/message/Components/CollapsibleQuote/CollapsibleQuote.test.tsx
rename to app/containers/message/Components/Attachments/CollapsibleQuote/CollapsibleQuote.test.tsx
index 021d01cff7..bbe9a27354 100644
--- a/app/containers/message/Components/CollapsibleQuote/CollapsibleQuote.test.tsx
+++ b/app/containers/message/Components/Attachments/CollapsibleQuote/CollapsibleQuote.test.tsx
@@ -1,7 +1,7 @@
import { fireEvent, render, within } from '@testing-library/react-native';
import React from 'react';
-import MessageContext from '../../Context';
+import MessageContext from '../../../Context';
import CollapsibleQuote from '.';
const testAttachment = {
diff --git a/app/containers/message/Components/CollapsibleQuote/index.tsx b/app/containers/message/Components/Attachments/CollapsibleQuote/index.tsx
similarity index 87%
rename from app/containers/message/Components/CollapsibleQuote/index.tsx
rename to app/containers/message/Components/Attachments/CollapsibleQuote/index.tsx
index e7239bfcfd..20d9cb8b4b 100644
--- a/app/containers/message/Components/CollapsibleQuote/index.tsx
+++ b/app/containers/message/Components/Attachments/CollapsibleQuote/index.tsx
@@ -3,16 +3,16 @@ import { dequal } from 'dequal';
import React, { useContext, useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';
-import { themes } from '../../../../lib/constants';
-import { IAttachment } from '../../../../definitions/IAttachment';
-import { TGetCustomEmoji } from '../../../../definitions/IEmoji';
-import { CustomIcon } from '../../../CustomIcon';
-import { useTheme } from '../../../../theme';
-import sharedStyles from '../../../../views/Styles';
-import Markdown from '../../../markdown';
-import MessageContext from '../../Context';
-import Touchable from '../../Touchable';
-import { BUTTON_HIT_SLOP } from '../../utils';
+import { themes } from '../../../../../lib/constants';
+import { IAttachment } from '../../../../../definitions/IAttachment';
+import { TGetCustomEmoji } from '../../../../../definitions/IEmoji';
+import { CustomIcon } from '../../../../CustomIcon';
+import { useTheme } from '../../../../../theme';
+import sharedStyles from '../../../../../views/Styles';
+import Markdown from '../../../../markdown';
+import MessageContext from '../../../Context';
+import Touchable from '../../../Touchable';
+import { BUTTON_HIT_SLOP } from '../../../utils';
const styles = StyleSheet.create({
button: {
@@ -156,8 +156,7 @@ const CollapsibleQuote = React.memo(
}
]}
background={Touchable.Ripple(themes[theme].surfaceNeutral)}
- hitSlop={BUTTON_HIT_SLOP}
- >
+ hitSlop={BUTTON_HIT_SLOP}>
diff --git a/app/containers/message/Image.tsx b/app/containers/message/Components/Attachments/Image.tsx
similarity index 54%
rename from app/containers/message/Image.tsx
rename to app/containers/message/Components/Attachments/Image.tsx
index 695ef8c033..2fc9cf2ff0 100644
--- a/app/containers/message/Image.tsx
+++ b/app/containers/message/Components/Attachments/Image.tsx
@@ -1,25 +1,22 @@
-import React, { useContext, useEffect, useState } from 'react';
+import React, { useCallback, useContext, useEffect, useState } from 'react';
import { StyleProp, TextStyle, View } from 'react-native';
import FastImage from 'react-native-fast-image';
-import { IAttachment, IUserMessage } from '../../definitions';
-import { TGetCustomEmoji } from '../../definitions/IEmoji';
-import { fetchAutoDownloadEnabled } from '../../lib/methods/autoDownloadPreference';
-import {
- cancelDownload,
- downloadMediaFile,
- getMediaCache,
- isDownloadActive,
- resumeMediaFile
-} from '../../lib/methods/handleMediaDownload';
-import { formatAttachmentUrl } from '../../lib/methods/helpers/formatAttachmentUrl';
-import { useTheme } from '../../theme';
-import Markdown from '../markdown';
-import BlurComponent from './Components/OverlayComponent';
-import MessageContext from './Context';
-import Touchable from './Touchable';
-import styles from './styles';
-import { isImageBase64 } from '../../lib/methods';
+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;
@@ -45,28 +42,44 @@ const Button = React.memo(({ children, onPress, disabled }: IMessageButton) => {
disabled={disabled}
onPress={onPress}
style={styles.imageContainer}
- background={Touchable.Ripple(colors.surfaceNeutral)}
- >
+ background={Touchable.Ripple(colors.surfaceNeutral)}>
{children}
);
});
-export const MessageImage = React.memo(({ imgUri, cached, loading }: { imgUri: string; cached: boolean; loading: boolean }) => {
- const { colors } = useTheme();
- return (
- <>
-
- {!cached ? (
-
- ) : null}
- >
- );
-});
+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,
@@ -78,11 +91,11 @@ const ImageContainer = ({
author,
msg
}: IMessageImage): React.ReactElement | null => {
- const [imageCached, setImageCached] = useState(file);
+ 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 { baseUrl, user } = useContext(MessageContext);
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
@@ -100,8 +113,8 @@ const ImageContainer = ({
handleResumeDownload();
return;
}
- setLoading(false);
await handleAutoDownload();
+ setLoading(false);
}
};
if (isImageBase64(imgUrlToCache)) {
@@ -110,6 +123,14 @@ const ImageContainer = ({
} else {
handleCache();
}
+
+ return () => {
+ emitter.off(`downloadMedia${id}`, downloadMediaListener);
+ };
+ }, []);
+
+ const downloadMediaListener = useCallback((imageUri: string) => {
+ updateImageCached(imageUri);
}, []);
if (!img) {
@@ -125,52 +146,51 @@ const ImageContainer = ({
};
const updateImageCached = (imgUri: string) => {
- setImageCached(prev => ({
- ...prev,
+ 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
});
- if (cachedImageResult?.exists) {
+ const result = !!cachedImageResult?.exists && imageCached.e2e !== 'pending';
+ if (result) {
updateImageCached(cachedImageResult.uri);
- setLoading(false);
}
- return !!cachedImageResult?.exists;
+ return result;
};
- const handleResumeDownload = async () => {
- try {
- setLoading(true);
- const imageUri = await resumeMediaFile({
- downloadUrl: imgUrlToCache
- });
- updateImageCached(imageUri);
- } catch (e) {
- setCached(false);
- } finally {
- setLoading(false);
- }
+ const handleResumeDownload = () => {
+ emitter.on(`downloadMedia${id}`, downloadMediaListener);
};
const handleDownload = async () => {
try {
- setLoading(true);
const imageUri = await downloadMediaFile({
+ messageId: id,
downloadUrl: imgUrlToCache,
type: 'image',
- mimeType: imageCached.image_type
+ mimeType: imageCached.image_type,
+ encryption: file.encryption,
+ originalChecksum: file.hashes?.sha256
});
+ setDecrypted();
updateImageCached(imageUri);
} catch (e) {
setCached(false);
- } finally {
setLoading(false);
}
};
@@ -192,31 +212,37 @@ const ImageContainer = ({
handleResumeDownload();
return;
}
+ setLoading(true);
handleDownload();
return;
}
- if (!showAttachment) {
+ if (!showAttachment || !imageCached.title_link) {
return;
}
showAttachment(imageCached);
};
+ const image = (
+
+ );
+
if (msg) {
return (
-
+ {image}
);
}
- return (
-
- );
+ return image;
};
ImageContainer.displayName = 'MessageImageContainer';
diff --git a/app/containers/message/Reply.tsx b/app/containers/message/Components/Attachments/Reply.tsx
similarity index 88%
rename from app/containers/message/Reply.tsx
rename to app/containers/message/Components/Attachments/Reply.tsx
index 22c2e28fb7..822a2c30e1 100644
--- a/app/containers/message/Reply.tsx
+++ b/app/containers/message/Components/Attachments/Reply.tsx
@@ -4,19 +4,19 @@ import React, { useContext, useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import FastImage from 'react-native-fast-image';
-import { IAttachment, TGetCustomEmoji } from '../../definitions';
-import { themes } from '../../lib/constants';
-import { fileDownloadAndPreview } from '../../lib/methods/helpers';
-import { formatAttachmentUrl } from '../../lib/methods/helpers/formatAttachmentUrl';
-import openLink from '../../lib/methods/helpers/openLink';
-import { TSupportedThemes, useTheme } from '../../theme';
-import sharedStyles from '../../views/Styles';
-import RCActivityIndicator from '../ActivityIndicator';
-import Markdown from '../markdown';
+import { IAttachment, TGetCustomEmoji } from '../../../../definitions';
+import { themes } from '../../../../lib/constants';
+import { fileDownloadAndPreview } from '../../../../lib/methods/helpers';
+import { formatAttachmentUrl } from '../../../../lib/methods/helpers/formatAttachmentUrl';
+import openLink from '../../../../lib/methods/helpers/openLink';
+import { TSupportedThemes, useTheme } from '../../../../theme';
+import sharedStyles from '../../../../views/Styles';
+import RCActivityIndicator from '../../../ActivityIndicator';
+import Markdown from '../../../markdown';
import { Attachments } from './components';
-import MessageContext from './Context';
-import Touchable from './Touchable';
-import messageStyles from './styles';
+import MessageContext from '../../Context';
+import Touchable from '../../Touchable';
+import messageStyles from '../../styles';
const styles = StyleSheet.create({
button: {
@@ -202,9 +202,9 @@ const Reply = React.memo(
({ attachment, timeFormat, index, getCustomEmoji, msg, showAttachment }: IMessageReply) => {
const [loading, setLoading] = useState(false);
const { theme } = useTheme();
- const { baseUrl, user } = useContext(MessageContext);
+ const { baseUrl, user, id, e2e, isEncrypted } = useContext(MessageContext);
- if (!attachment) {
+ if (!attachment || (isEncrypted && !e2e)) {
return null;
}
@@ -216,7 +216,7 @@ const Reply = React.memo(
if (attachment.type === 'file' && attachment.title_link) {
setLoading(true);
url = formatAttachmentUrl(attachment.title_link, user.id, user.token, baseUrl);
- await fileDownloadAndPreview(url, attachment);
+ await fileDownloadAndPreview(url, attachment, id);
setLoading(false);
return;
}
diff --git a/app/containers/message/Video.tsx b/app/containers/message/Components/Attachments/Video.tsx
similarity index 54%
rename from app/containers/message/Video.tsx
rename to app/containers/message/Components/Attachments/Video.tsx
index ff4d52eedd..42c432db83 100644
--- a/app/containers/message/Video.tsx
+++ b/app/containers/message/Components/Attachments/Video.tsx
@@ -1,30 +1,26 @@
-import React, { useContext, useEffect, useState } from 'react';
+import React, { useCallback, useContext, useEffect, useState } from 'react';
import { StyleProp, StyleSheet, Text, TextStyle, View } from 'react-native';
-import FastImage from 'react-native-fast-image';
-
-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,
- resumeMediaFile
-} from '../../lib/methods/handleMediaDownload';
-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 { LISTENER } from '../Toast';
-import Markdown from '../markdown';
-import BlurComponent from './Components/OverlayComponent';
-import MessageContext from './Context';
-import Touchable from './Touchable';
-import { DEFAULT_MESSAGE_HEIGHT } from './utils';
+
+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 EventEmitter from '../../../../lib/methods/helpers/events';
+import { formatAttachmentUrl } from '../../../../lib/methods/helpers/formatAttachmentUrl';
+import { useTheme } from '../../../../theme';
+import sharedStyles from '../../../../views/Styles';
+import { LISTENER } from '../../../Toast';
+import Markdown from '../../../markdown';
+import BlurComponent from '../OverlayComponent';
+import MessageContext from '../../Context';
+import Touchable from '../../Touchable';
+import { DEFAULT_MESSAGE_HEIGHT } from '../../utils';
+import { TIconsName } from '../../../CustomIcon';
+import { useFile } from '../../hooks/useFile';
+import { IUserMessage } from '../../../../definitions';
const SUPPORTED_TYPES = ['video/quicktime', 'video/mp4', ...(isIOS ? [] : ['video/3gp', 'video/mkv'])];
const isTypeSupported = (type: string) => SUPPORTED_TYPES.indexOf(type) !== -1;
@@ -46,11 +42,6 @@ const styles = StyleSheet.create({
text: {
...sharedStyles.textRegular,
fontSize: 12
- },
- thumbnailImage: {
- borderRadius: 4,
- width: '100%',
- height: '100%'
}
});
@@ -58,6 +49,7 @@ interface IMessageVideo {
file: IAttachment;
showAttachment?: (file: IAttachment) => void;
getCustomEmoji: TGetCustomEmoji;
+ author?: IUserMessage;
style?: StyleProp[];
isReply?: boolean;
msg?: string;
@@ -72,20 +64,33 @@ const CancelIndicator = () => {
);
};
-// TODO: Wait backend send the thumbnailUrl as prop
-const Thumbnail = ({ loading, thumbnailUrl, cached }: { loading: boolean; thumbnailUrl?: string; cached: boolean }) => (
- <>
- {thumbnailUrl ? : null}
-
- {loading ? : null}
- >
-);
-
-const Video = ({ file, showAttachment, getCustomEmoji, style, isReply, msg }: IMessageVideo): React.ReactElement | null => {
- const [videoCached, setVideoCached] = useState(file);
+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) {
+ icon = 'encrypted';
+ }
+
+ return (
+ <>
+
+ {loading ? : null}
+ >
+ );
+};
+
+const Video = ({
+ file,
+ showAttachment,
+ getCustomEmoji,
+ author,
+ style,
+ 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 { baseUrl, user } = useContext(MessageContext);
const { theme } = useTheme();
const video = formatAttachmentUrl(file.video_url, user.id, user.token, baseUrl);
@@ -101,9 +106,19 @@ const Video = ({ file, showAttachment, getCustomEmoji, style, isReply, msg }: IM
return;
}
await handleAutoDownload();
+ setLoading(false);
}
};
handleVideoSearchAndDownload();
+
+ return () => {
+ emitter.off(`downloadMedia${id}`, downloadMediaListener);
+ };
+ }, []);
+
+ const downloadMediaListener = useCallback((uri: string) => {
+ updateVideoCached(uri);
+ setLoading(false);
}, []);
if (!baseUrl) {
@@ -111,8 +126,9 @@ const Video = ({ file, showAttachment, getCustomEmoji, style, isReply, msg }: IM
}
const handleAutoDownload = async () => {
+ const isCurrentUserAuthor = author?._id === user.id;
const isAutoDownloadEnabled = fetchAutoDownloadEnabled('videoPreferenceDownload');
- if (isAutoDownloadEnabled && file.video_type && isTypeSupported(file.video_type)) {
+ if ((isAutoDownloadEnabled || isCurrentUserAuthor) && file.video_type && isTypeSupported(file.video_type)) {
await handleDownload();
return;
}
@@ -120,11 +136,17 @@ const Video = ({ file, showAttachment, getCustomEmoji, style, isReply, msg }: IM
};
const updateVideoCached = (videoUri: string) => {
- setVideoCached(prev => ({
- ...prev,
- video_url: videoUri
- }));
+ setVideoCached({ video_url: videoUri });
setCached(true);
+ setLoading(false);
+ };
+
+ const setDecrypted = () => {
+ if (videoCached.e2e === 'pending') {
+ setVideoCached({
+ e2e: 'done'
+ });
+ }
};
const handleGetMediaCache = async () => {
@@ -133,51 +155,42 @@ const Video = ({ file, showAttachment, getCustomEmoji, style, isReply, msg }: IM
mimeType: file.video_type,
urlToCache: video
});
- if (cachedVideoResult?.exists) {
+ const result = !!cachedVideoResult?.exists && videoCached.e2e !== 'pending';
+ if (result) {
updateVideoCached(cachedVideoResult.uri);
- setLoading(false);
}
- return !!cachedVideoResult?.exists;
+ return result;
};
- const handleResumeDownload = async () => {
- try {
- setLoading(true);
- const videoUri = await resumeMediaFile({
- downloadUrl: video
- });
- updateVideoCached(videoUri);
- } catch (e) {
- setCached(false);
- } finally {
- setLoading(false);
- }
+ const handleResumeDownload = () => {
+ emitter.on(`downloadMedia${id}`, downloadMediaListener);
};
const handleDownload = async () => {
- setLoading(true);
try {
const videoUri = await downloadMediaFile({
+ messageId: id,
downloadUrl: video,
type: 'video',
- mimeType: file.video_type
+ mimeType: file.video_type,
+ encryption: file.encryption,
+ originalChecksum: file.hashes?.sha256
});
+ setDecrypted();
updateVideoCached(videoUri);
} catch {
setCached(false);
- } finally {
- setLoading(false);
}
};
const onPress = async () => {
- if (file.video_type && cached && isTypeSupported(file.video_type) && showAttachment) {
+ 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) {
+ if (isVideoCached && showAttachment && videoCached.video_url) {
showAttachment(videoCached);
return;
}
@@ -185,6 +198,7 @@ const Video = ({ file, showAttachment, getCustomEmoji, style, isReply, msg }: IM
handleResumeDownload();
return;
}
+ setLoading(true);
handleDownload();
return;
}
@@ -224,9 +238,8 @@ const Video = ({ file, showAttachment, getCustomEmoji, style, isReply, msg }: IM
-
+ background={Touchable.Ripple(themes[theme].surfaceNeutral)}>
+
>
);
diff --git a/app/containers/message/components.ts b/app/containers/message/Components/Attachments/components.ts
similarity index 100%
rename from app/containers/message/components.ts
rename to app/containers/message/Components/Attachments/components.ts
diff --git a/app/containers/message/Components/Attachments/index.ts b/app/containers/message/Components/Attachments/index.ts
new file mode 100644
index 0000000000..b103242946
--- /dev/null
+++ b/app/containers/message/Components/Attachments/index.ts
@@ -0,0 +1,3 @@
+import Attachments from './Attachments';
+
+export default Attachments;
diff --git a/app/containers/message/Message.tsx b/app/containers/message/Message.tsx
index e4322b2665..93efa8237e 100644
--- a/app/containers/message/Message.tsx
+++ b/app/containers/message/Message.tsx
@@ -7,7 +7,7 @@ import User from './User';
import styles from './styles';
import RepliedThread from './RepliedThread';
import MessageAvatar from './MessageAvatar';
-import Attachments from './Attachments';
+import Attachments from './Components/Attachments';
import Urls from './Urls';
import Thread from './Thread';
import Blocks from './Blocks';
diff --git a/app/containers/message/hooks/useFile.tsx b/app/containers/message/hooks/useFile.tsx
new file mode 100644
index 0000000000..2e5d34eb32
--- /dev/null
+++ b/app/containers/message/hooks/useFile.tsx
@@ -0,0 +1,26 @@
+import { useEffect, useState } from 'react';
+
+import { IAttachment } from '../../../definitions';
+import { getMessageById } from '../../../lib/database/services/Message';
+
+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);
+ }
+ };
+ checkMessage();
+ }, [messageId]);
+
+ const manageForwardedFile = (f: Partial) => {
+ if (isMessagePersisted) {
+ return;
+ }
+ setLocalFile(prev => ({ ...prev, ...f }));
+ };
+ return [isMessagePersisted ? file : localFile, manageForwardedFile] as const;
+};
diff --git a/app/containers/message/index.tsx b/app/containers/message/index.tsx
index 0024abebc8..ce706a9bdc 100644
--- a/app/containers/message/index.tsx
+++ b/app/containers/message/index.tsx
@@ -429,9 +429,9 @@ class MessageContainer extends React.Component
+ translateLanguage: canTranslateMessage ? autoTranslateLanguage : undefined,
+ isEncrypted: this.isEncrypted
+ }}>
{/* @ts-ignore*/}
;
+ content?: IMessageE2EEContent;
}
export interface IShareAttachment {
@@ -69,4 +84,9 @@ export interface IShareAttachment {
canUpload: boolean;
error?: any;
uri: string;
+ width?: number;
+ height?: number;
+ exif?: {
+ Orientation: string;
+ };
}
diff --git a/app/definitions/IMessage.ts b/app/definitions/IMessage.ts
index 996ec0c4f9..5271140322 100644
--- a/app/definitions/IMessage.ts
+++ b/app/definitions/IMessage.ts
@@ -80,6 +80,11 @@ interface IMessageFile {
type: string;
}
+export type IMessageE2EEContent = {
+ algorithm: 'rc.v1.aes-sha2';
+ ciphertext: string; // Encrypted subset JSON of IMessage
+};
+
export interface IMessageFromServer {
_id: string;
rid: string;
@@ -107,6 +112,7 @@ export interface IMessageFromServer {
username: string;
};
score?: number;
+ content?: IMessageE2EEContent;
}
export interface ILoadMoreMessage {
diff --git a/app/definitions/IUpload.ts b/app/definitions/IUpload.ts
index a2935575b3..9bd76ab7c7 100644
--- a/app/definitions/IUpload.ts
+++ b/app/definitions/IUpload.ts
@@ -14,6 +14,16 @@ export interface IUpload {
error?: boolean;
subscription?: { id: string };
msg?: string;
+ width?: number;
+ height?: number;
}
-export type TUploadModel = IUpload & Model;
+export type TUploadModel = IUpload &
+ Model & {
+ asPlain: () => IUpload;
+ };
+
+export type TSendFileMessageFileInfo = Pick<
+ IUpload,
+ 'rid' | 'path' | 'name' | 'tmid' | 'description' | 'size' | 'type' | 'msg' | 'width' | 'height'
+>;
diff --git a/app/definitions/rest/v1/rooms.ts b/app/definitions/rest/v1/rooms.ts
index ebcb29cd41..0343a298a4 100644
--- a/app/definitions/rest/v1/rooms.ts
+++ b/app/definitions/rest/v1/rooms.ts
@@ -45,3 +45,7 @@ export type RoomsEndpoints = {
POST: (params: { roomId: string; notifications: IRoomNotifications }) => {};
};
};
+
+export type TRoomsMediaResponse = {
+ file: { _id: string; url: string };
+};
diff --git a/app/i18n/locales/en.json b/app/i18n/locales/en.json
index 09f4e7d619..46b2cde2f0 100644
--- a/app/i18n/locales/en.json
+++ b/app/i18n/locales/en.json
@@ -254,6 +254,7 @@
"Enable_writing_in_room": "Enable writing in room",
"Enabled_E2E_Encryption_for_this_room": "enabled E2E encryption for this room",
"Encrypted": "Encrypted",
+ "Encrypted_file": "Encrypted file",
"Encrypted_message": "Encrypted message",
"encrypted_room_description": "Enter your end-to-end encryption password to access.",
"encrypted_room_title": "{{room_name}} is encrypted",
@@ -265,6 +266,7 @@
"Enter_the_code": "Enter the code we just emailed you.",
"Enter_workspace_URL": "Enter workspace URL",
"Error_Download_file": "Error while downloading file",
+ "Error_play_video": "There was an error while playing this video",
"Error_uploading": "Error uploading",
"error-action-not-allowed": "{{action}} is not allowed",
"error-avatar-invalid-url": "Invalid avatar URL: {{url}}",
diff --git a/app/i18n/locales/pt-BR.json b/app/i18n/locales/pt-BR.json
index aabe6935c8..7af4a558ed 100644
--- a/app/i18n/locales/pt-BR.json
+++ b/app/i18n/locales/pt-BR.json
@@ -249,6 +249,7 @@
"Enable_writing_in_room": "Permitir escrita na sala",
"Enabled_E2E_Encryption_for_this_room": "habilitou criptografia para essa sala",
"Encrypted": "Criptografado",
+ "Encrypted_file": "Arquivo criptografado",
"Encrypted_message": "Mensagem criptografada",
"encrypted_room_description": "Insira sua senha de criptografia de ponta a ponta para acessar.",
"encrypted_room_title": "{{room_name}} está criptografado",
@@ -260,6 +261,7 @@
"Enter_the_code": "Insira o código que acabamos de enviar por e-mail.",
"Enter_workspace_URL": "Digite a URL da sua workspace",
"Error_Download_file": "Erro ao baixar o arquivo",
+ "Error_play_video": "Houve um erro ao reproduzir esse vídeo",
"Error_uploading": "Erro subindo",
"error-action-not-allowed": "{{action}} não é permitido",
"error-avatar-invalid-url": "URL inválida de avatar: {{url}}",
diff --git a/app/lib/database/model/Message.js b/app/lib/database/model/Message.js
index 5e452b43ca..336f9c3935 100644
--- a/app/lib/database/model/Message.js
+++ b/app/lib/database/model/Message.js
@@ -84,6 +84,8 @@ export default class Message extends Model {
@json('md', sanitizer) md;
+ @json('content', sanitizer) content;
+
@field('comment') comment;
asPlain() {
@@ -125,6 +127,7 @@ export default class Message extends Model {
e2e: this.e2e,
tshow: this.tshow,
md: this.md,
+ content: this.content,
comment: this.comment
};
}
diff --git a/app/lib/database/model/Thread.js b/app/lib/database/model/Thread.js
index 947493b6b7..a04e589bbd 100644
--- a/app/lib/database/model/Thread.js
+++ b/app/lib/database/model/Thread.js
@@ -74,6 +74,8 @@ export default class Thread extends Model {
@json('translations', sanitizer) translations;
+ @json('content', sanitizer) content;
+
@field('e2e') e2e;
@field('draft_message') draftMessage;
@@ -112,7 +114,8 @@ export default class Thread extends Model {
autoTranslate: this.autoTranslate,
translations: this.translations,
e2e: this.e2e,
- draftMessage: this.draftMessage
+ draftMessage: this.draftMessage,
+ content: this.content
};
}
}
diff --git a/app/lib/database/model/ThreadMessage.js b/app/lib/database/model/ThreadMessage.js
index 0d2ad16bf8..8bb364b7ed 100644
--- a/app/lib/database/model/ThreadMessage.js
+++ b/app/lib/database/model/ThreadMessage.js
@@ -78,6 +78,8 @@ export default class ThreadMessage extends Model {
@field('e2e') e2e;
+ @json('content', sanitizer) content;
+
asPlain() {
return {
id: this.id,
@@ -112,7 +114,8 @@ export default class ThreadMessage extends Model {
autoTranslate: this.autoTranslate,
translations: this.translations,
draftMessage: this.draftMessage,
- e2e: this.e2e
+ e2e: this.e2e,
+ content: this.content
};
}
}
diff --git a/app/lib/database/model/Upload.js b/app/lib/database/model/Upload.js
index a96e2c9954..bfb91655ea 100644
--- a/app/lib/database/model/Upload.js
+++ b/app/lib/database/model/Upload.js
@@ -29,4 +29,18 @@ export default class Upload extends Model {
@field('progress') progress;
@field('error') error;
+
+ asPlain() {
+ return {
+ id: this.id,
+ rid: this.subscription.id,
+ path: this.path,
+ name: this.name,
+ tmid: this.tmid,
+ description: this.description,
+ size: this.size,
+ type: this.type,
+ store: this.store
+ };
+ }
}
diff --git a/app/lib/database/model/migrations.js b/app/lib/database/model/migrations.js
index 37b0b5123c..a40ceb0a5b 100644
--- a/app/lib/database/model/migrations.js
+++ b/app/lib/database/model/migrations.js
@@ -293,6 +293,23 @@ export default schemaMigrations({
columns: [{ name: 'disable_notifications', type: 'boolean', isOptional: true }]
})
]
+ },
+ {
+ toVersion: 25,
+ steps: [
+ addColumns({
+ table: 'messages',
+ columns: [{ name: 'content', type: 'string', isOptional: true }]
+ }),
+ addColumns({
+ table: 'threads',
+ columns: [{ name: 'content', type: 'string', isOptional: true }]
+ }),
+ addColumns({
+ table: 'thread_messages',
+ columns: [{ name: 'content', type: 'string', isOptional: true }]
+ })
+ ]
}
]
});
diff --git a/app/lib/database/schema/app.js b/app/lib/database/schema/app.js
index b0a99752bc..73220f11de 100644
--- a/app/lib/database/schema/app.js
+++ b/app/lib/database/schema/app.js
@@ -1,7 +1,7 @@
import { appSchema, tableSchema } from '@nozbe/watermelondb';
export default appSchema({
- version: 24,
+ version: 25,
tables: [
tableSchema({
name: 'subscriptions',
@@ -125,6 +125,7 @@ export default appSchema({
{ name: 'e2e', type: 'string', isOptional: true },
{ name: 'tshow', type: 'boolean', isOptional: true },
{ name: 'md', type: 'string', isOptional: true },
+ { name: 'content', type: 'string', isOptional: true },
{ name: 'comment', type: 'string', isOptional: true }
]
}),
@@ -163,6 +164,7 @@ export default appSchema({
{ name: 'auto_translate', type: 'boolean', isOptional: true },
{ name: 'translations', type: 'string', isOptional: true },
{ name: 'e2e', type: 'string', isOptional: true },
+ { name: 'content', type: 'string', isOptional: true },
{ name: 'draft_message', type: 'string', isOptional: true }
]
}),
@@ -200,7 +202,8 @@ export default appSchema({
{ name: 'unread', type: 'boolean', isOptional: true },
{ name: 'auto_translate', type: 'boolean', isOptional: true },
{ name: 'translations', type: 'string', isOptional: true },
- { name: 'e2e', type: 'string', isOptional: true }
+ { name: 'e2e', type: 'string', isOptional: true },
+ { name: 'content', type: 'string', isOptional: true }
]
}),
tableSchema({
diff --git a/app/lib/database/services/Upload.ts b/app/lib/database/services/Upload.ts
new file mode 100644
index 0000000000..4da1ece9f8
--- /dev/null
+++ b/app/lib/database/services/Upload.ts
@@ -0,0 +1,16 @@
+import database from '..';
+import { TAppDatabase } from '../interfaces';
+import { UPLOADS_TABLE } from '../model';
+
+const getCollection = (db: TAppDatabase) => db.get(UPLOADS_TABLE);
+
+export const getUploadByPath = async (path: string) => {
+ const db = database.active;
+ const uploadCollection = getCollection(db);
+ try {
+ const result = await uploadCollection.find(path);
+ return result;
+ } catch (error) {
+ return null;
+ }
+};
diff --git a/app/lib/encryption/constants.ts b/app/lib/encryption/constants.ts
index 15da8e6efa..88901431c8 100644
--- a/app/lib/encryption/constants.ts
+++ b/app/lib/encryption/constants.ts
@@ -1 +1,2 @@
export const LEARN_MORE_E2EE_URL = 'https://go.rocket.chat/i/e2ee-guide';
+export const MAX_CONCURRENT_QUEUE = 5;
diff --git a/app/lib/encryption/definitions.ts b/app/lib/encryption/definitions.ts
new file mode 100644
index 0000000000..ba50fc8c7f
--- /dev/null
+++ b/app/lib/encryption/definitions.ts
@@ -0,0 +1,29 @@
+import { TAttachmentEncryption, TSendFileMessageFileInfo } from '../../definitions';
+
+export type TGetContentResult = {
+ algorithm: 'rc.v1.aes-sha2';
+ ciphertext: string;
+};
+
+export type TGetContent = (_id: string, fileUrl: string) => Promise;
+
+export type TEncryptFileResult = Promise<{
+ file: TSendFileMessageFileInfo;
+ getContent?: TGetContent;
+ fileContent?: TGetContentResult;
+}>;
+
+export type TEncryptFile = (rid: string, file: TSendFileMessageFileInfo) => TEncryptFileResult;
+
+export type TDecryptFile = (
+ messageId: string,
+ path: string,
+ encryption: TAttachmentEncryption,
+ originalChecksum: string
+) => Promise;
+
+export interface IDecryptionFileQueue {
+ params: Parameters;
+ resolve: (value: string | null | PromiseLike) => void;
+ reject: (reason?: any) => void;
+}
diff --git a/app/lib/encryption/encryption.ts b/app/lib/encryption/encryption.ts
index 7cdf4b9a0c..128233bd1c 100644
--- a/app/lib/encryption/encryption.ts
+++ b/app/lib/encryption/encryption.ts
@@ -2,16 +2,28 @@ import EJSON from 'ejson';
import SimpleCrypto from 'react-native-simple-crypto';
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import { Q, Model } from '@nozbe/watermelondb';
+import { deleteAsync } from 'expo-file-system';
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 { joinVectorData, randomPassword, splitVectorData, toString, utf8ToBuffer } from './utils';
+import { decryptAESCTR, joinVectorData, randomPassword, splitVectorData, toString, utf8ToBuffer } from './utils';
+import {
+ IMessage,
+ ISubscription,
+ TSendFileMessageFileInfo,
+ TMessageModel,
+ TSubscriptionModel,
+ TThreadMessageModel,
+ TThreadModel,
+ IServerAttachment
+} from '../../definitions';
import EncryptionRoom from './room';
-import { IMessage, ISubscription, TMessageModel, TSubscriptionModel, TThreadMessageModel, TThreadModel } from '../../definitions';
import {
E2E_BANNER_TYPE,
E2E_MESSAGE_TYPE,
@@ -21,6 +33,8 @@ import {
E2E_STATUS
} from '../constants';
import { Services } from '../services';
+import { IDecryptionFileQueue, TDecryptFile, TEncryptFile } from './definitions';
+import { MAX_CONCURRENT_QUEUE } from './constants';
class Encryption {
ready: boolean;
@@ -33,10 +47,16 @@ class Encryption {
provideKeyToUser: Function;
handshake: Function;
decrypt: Function;
+ decryptFileContent: Function;
encrypt: Function;
+ encryptText: Function;
+ encryptFile: TEncryptFile;
+ encryptUpload: Function;
importRoomKey: Function;
};
};
+ decryptionFileQueue: IDecryptionFileQueue[];
+ decryptionFileQueueActiveCount: number;
constructor() {
this.userId = '';
@@ -51,6 +71,8 @@ class Encryption {
.catch(() => {
this.ready = false;
});
+ this.decryptionFileQueue = [];
+ this.decryptionFileQueueActiveCount = 0;
}
// Initialize Encryption client
@@ -68,9 +90,9 @@ class Encryption {
};
get establishing() {
- const { banner } = store.getState().encryption;
+ const { banner, enabled } = store.getState().encryption;
// If the password was not inserted yet
- if (!banner || banner === E2E_BANNER_TYPE.REQUEST_PASSWORD) {
+ if (!enabled || banner === E2E_BANNER_TYPE.REQUEST_PASSWORD) {
// We can't decrypt/encrypt, so, reject this try
return Promise.reject();
}
@@ -275,7 +297,7 @@ class Encryption {
];
toDecrypt = (await Promise.all(
toDecrypt.map(async message => {
- const { t, msg, tmsg } = message;
+ const { t, msg, tmsg, attachments, content } = message;
let newMessage: TMessageModel = {} as TMessageModel;
if (message.subscription) {
const { id: rid } = message.subscription;
@@ -284,7 +306,9 @@ class Encryption {
t,
rid,
msg: msg as string,
- tmsg
+ tmsg,
+ attachments,
+ content
});
}
@@ -316,17 +340,9 @@ class Encryption {
try {
// Find all rooms that can have a lastMessage encrypted
// If we select only encrypted rooms we can miss some room that changed their encrypted status
- const subsEncrypted = await subCollection.query(Q.where('e2e_key_id', Q.notEq(null))).fetch();
- // We can't do this on database level since lastMessage is not a database object
- const subsToDecrypt = subsEncrypted.filter(
- (sub: ISubscription) =>
- // Encrypted message
- sub?.lastMessage?.t === E2E_MESSAGE_TYPE &&
- // Message pending decrypt
- sub?.lastMessage?.e2e === E2E_STATUS.PENDING
- );
+ const subsEncrypted = await subCollection.query(Q.where('e2e_key_id', Q.notEq(null)), Q.where('encrypted', true)).fetch();
await Promise.all(
- subsToDecrypt.map(async (sub: TSubscriptionModel) => {
+ subsEncrypted.map(async (sub: TSubscriptionModel) => {
const { rid, lastMessage } = sub;
const newSub = await this.decryptSubscription({ rid, lastMessage });
try {
@@ -342,7 +358,7 @@ class Encryption {
);
await db.write(async () => {
- await db.batch(...subsToDecrypt);
+ await db.batch(...subsEncrypted);
});
} catch (e) {
log(e);
@@ -436,6 +452,11 @@ class Encryption {
};
};
+ encryptText = async (rid: string, text: string) => {
+ const roomE2E = await this.getRoomInstance(rid);
+ return roomE2E.encryptText(text);
+ };
+
// Encrypt a message
encryptMessage = async (message: IMessage) => {
const { rid } = message;
@@ -470,7 +491,7 @@ class Encryption {
};
// Decrypt a message
- decryptMessage = async (message: Pick) => {
+ decryptMessage = async (message: Pick) => {
const { t, e2e } = message;
// Prevent create a new instance if this room was encrypted sometime ago
@@ -495,12 +516,105 @@ class Encryption {
return roomE2E.decrypt(message);
};
+ decryptFileContent = async (file: IServerAttachment) => {
+ const roomE2E = await this.getRoomInstance(file.rid);
+
+ if (!roomE2E) {
+ return file;
+ }
+
+ return roomE2E.decryptFileContent(file);
+ };
+
+ encryptFile = async (rid: string, file: TSendFileMessageFileInfo) => {
+ const subscription = await getSubscriptionByRoomId(rid);
+ if (!subscription) {
+ throw new Error('Subscription not found');
+ }
+
+ if (!subscription.encrypted) {
+ // Send a non encrypted message
+ return { file };
+ }
+
+ // If the client is not ready
+ if (!this.ready) {
+ // Wait for ready status
+ await this.establishing;
+ }
+
+ const roomE2E = await this.getRoomInstance(rid);
+ return roomE2E.encryptFile(rid, file);
+ };
+
+ 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);
+ if (checksum !== originalChecksum) {
+ 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;
+ };
+
+ addFileToDecryptFileQueue: TDecryptFile = (messageId, path, encryption, originalChecksum) =>
+ new Promise((resolve, reject) => {
+ this.decryptionFileQueue.push({
+ params: [messageId, path, encryption, originalChecksum],
+ resolve,
+ reject
+ });
+ this.processFileQueue();
+ });
+
+ async processFileQueue() {
+ if (this.decryptionFileQueueActiveCount >= MAX_CONCURRENT_QUEUE || this.decryptionFileQueue.length === 0) {
+ return;
+ }
+ const queueItem = this.decryptionFileQueue.shift();
+ // FIXME: TS not getting decryptionFileQueue is not empty. TS 5.5 fix?
+ if (!queueItem) {
+ return;
+ }
+ const { params, resolve, reject } = queueItem;
+ this.decryptionFileQueueActiveCount += 1;
+
+ try {
+ const result = await this.decryptFile(...params);
+ resolve(result);
+ } catch (error) {
+ reject(error);
+ } finally {
+ this.decryptionFileQueueActiveCount -= 1;
+ this.processFileQueue();
+ }
+ }
+
// Decrypt multiple messages
decryptMessages = (messages: Partial[]) =>
Promise.all(messages.map((m: Partial) => this.decryptMessage(m as IMessage)));
// Decrypt multiple subscriptions
decryptSubscriptions = (subscriptions: ISubscription[]) => Promise.all(subscriptions.map(s => this.decryptSubscription(s)));
+
+ // Decrypt multiple files
+ decryptFiles = (files: IServerAttachment[]) => Promise.all(files.map(f => this.decryptFileContent(f)));
}
const encryption = new Encryption();
diff --git a/app/lib/encryption/room.ts b/app/lib/encryption/room.ts
index 2088e70a20..10cd50fda0 100644
--- a/app/lib/encryption/room.ts
+++ b/app/lib/encryption/room.ts
@@ -3,9 +3,10 @@ import { Base64 } from 'js-base64';
import SimpleCrypto from 'react-native-simple-crypto';
import ByteBuffer from 'bytebuffer';
import parse from 'url-parse';
+import { sha256 } from 'js-sha256';
import getSingleMessage from '../methods/getSingleMessage';
-import { IMessage, IUser } from '../../definitions';
+import { IAttachment, IMessage, IUpload, TSendFileMessageFileInfo, IUser, IServerAttachment } from '../../definitions';
import Deferred from './helpers/deferred';
import { debounce } from '../methods/helpers';
import database from '../database';
@@ -15,6 +16,10 @@ import {
bufferToB64,
bufferToB64URI,
bufferToUtf8,
+ encryptAESCTR,
+ exportAESCTR,
+ generateAESCTRKey,
+ getFileExtension,
joinVectorData,
splitVectorData,
toString,
@@ -28,6 +33,7 @@ import { mapMessageFromAPI } from './helpers/mapMessageFromApi';
import { mapMessageFromDB } from './helpers/mapMessageFromDB';
import { createQuoteAttachment } from './helpers/createQuoteAttachment';
import { getMessageById } from '../database/services/Message';
+import { TEncryptFileResult, TGetContent } from './definitions';
export default class EncryptionRoom {
ready: boolean;
@@ -238,7 +244,15 @@ export default class EncryptionRoom {
...message,
t: E2E_MESSAGE_TYPE,
e2e: E2E_STATUS.PENDING,
- msg
+ msg,
+ content: {
+ algorithm: 'rc.v1.aes-sha2' as const,
+ ciphertext: await this.encryptText(
+ EJSON.stringify({
+ msg: message.msg
+ })
+ )
+ }
};
} catch {
// Do nothing
@@ -247,8 +261,151 @@ export default class EncryptionRoom {
return message;
};
+ // Encrypt upload
+ encryptUpload = async (message: IUpload) => {
+ if (!this.ready) {
+ return message;
+ }
+
+ try {
+ let description = '';
+
+ if (message.description) {
+ description = await this.encryptText(EJSON.stringify({ text: message.description }));
+ }
+
+ return {
+ ...message,
+ t: E2E_MESSAGE_TYPE,
+ e2e: E2E_STATUS.PENDING,
+ description
+ };
+ } catch {
+ // Do nothing
+ }
+
+ return message;
+ };
+
+ encryptFile = async (rid: string, file: TSendFileMessageFileInfo): TEncryptFileResult => {
+ const { path } = file;
+ const vector = await SimpleCrypto.utils.randomBytes(16);
+ const key = await generateAESCTRKey();
+ const exportedKey = await exportAESCTR(key);
+ const iv = bufferToB64(vector);
+ const checksum = await SimpleCrypto.utils.calculateFileChecksum(path);
+ const encryptedFile = await encryptAESCTR(path, exportedKey.k, iv);
+
+ const getContent: TGetContent = async (_id, fileUrl) => {
+ const attachments: IAttachment[] = [];
+ let att: IAttachment = {
+ title: file.name,
+ type: 'file',
+ description: file.description,
+ title_link: fileUrl,
+ title_link_download: true,
+ encryption: {
+ key: exportedKey,
+ iv: bufferToB64(vector)
+ },
+ hashes: {
+ sha256: checksum
+ }
+ };
+ if (file.type && /^image\/.+/.test(file.type)) {
+ att = {
+ ...att,
+ image_url: fileUrl,
+ image_type: file.type,
+ image_size: file.size,
+ ...(file.width &&
+ file.height && {
+ image_dimensions: {
+ width: file.width,
+ height: file.height
+ }
+ })
+ };
+ } else if (file.type && /^audio\/.+/.test(file.type)) {
+ att = {
+ ...att,
+ audio_url: fileUrl,
+ audio_type: file.type,
+ audio_size: file.size
+ };
+ } else if (file.type && /^video\/.+/.test(file.type)) {
+ att = {
+ ...att,
+ video_url: fileUrl,
+ video_type: file.type,
+ video_size: file.size
+ };
+ } else {
+ att = {
+ ...att,
+ size: file.size,
+ format: getFileExtension(file.path)
+ };
+ }
+ attachments.push(att);
+
+ const files = [
+ {
+ _id,
+ name: file.name,
+ type: file.type,
+ size: file.size
+ }
+ ];
+
+ const data = EJSON.stringify({
+ attachments,
+ files,
+ file: files[0]
+ });
+
+ return {
+ algorithm: 'rc.v1.aes-sha2',
+ ciphertext: await Encryption.encryptText(rid, data)
+ };
+ };
+
+ const fileContentData = {
+ type: file.type,
+ typeGroup: file.type?.split('/')[0],
+ name: file.name,
+ encryption: {
+ key: exportedKey,
+ iv
+ },
+ hashes: {
+ sha256: checksum
+ }
+ };
+
+ const fileContent = {
+ algorithm: 'rc.v1.aes-sha2' as const,
+ ciphertext: await Encryption.encryptText(rid, EJSON.stringify(fileContentData))
+ };
+
+ return {
+ file: {
+ ...file,
+ type: 'file',
+ name: sha256(file.name ?? 'File message'),
+ path: encryptedFile
+ },
+ getContent,
+ fileContent
+ };
+ };
+
// Decrypt text
decryptText = async (msg: string | ArrayBuffer) => {
+ if (!msg) {
+ return null;
+ }
+
msg = b64ToBuffer(msg.slice(12) as string);
const [vector, cipherText] = splitVectorData(msg);
@@ -259,6 +416,25 @@ export default class EncryptionRoom {
return m.text;
};
+ decryptFileContent = async (data: IServerAttachment) => {
+ if (data.content?.algorithm === 'rc.v1.aes-sha2') {
+ const content = await this.decryptContent(data.content.ciphertext);
+ Object.assign(data, content);
+ }
+ return data;
+ };
+
+ decryptContent = async (contentBase64: string) => {
+ if (!contentBase64) {
+ return null;
+ }
+
+ const contentBuffer = b64ToBuffer(contentBase64.slice(12) as string);
+ const [vector, cipherText] = splitVectorData(contentBuffer);
+ const decrypted = await SimpleCrypto.AES.decrypt(cipherText, this.roomKey, vector);
+ return EJSON.parse(bufferToUtf8(decrypted));
+ };
+
// Decrypt messages
decrypt = async (message: IMessage) => {
if (!this.ready) {
@@ -270,19 +446,31 @@ export default class EncryptionRoom {
// If message type is e2e and it's encrypted still
if (t === E2E_MESSAGE_TYPE && e2e !== E2E_STATUS.DONE) {
- let { msg, tmsg } = message;
+ const { msg, tmsg } = message;
// Decrypt msg
- msg = await this.decryptText(msg as string);
+ if (msg) {
+ message.msg = await this.decryptText(msg);
+ }
// Decrypt tmsg
if (tmsg) {
- tmsg = await this.decryptText(tmsg);
+ message.tmsg = await this.decryptText(tmsg);
+ }
+
+ if (message.content?.algorithm === 'rc.v1.aes-sha2') {
+ const content = await this.decryptContent(message.content.ciphertext);
+ message = {
+ ...message,
+ ...content,
+ attachments: content.attachments?.map((att: IAttachment) => ({
+ ...att,
+ e2e: 'pending'
+ }))
+ };
}
const decryptedMessage: IMessage = {
...message,
- tmsg,
- msg,
e2e: 'done'
};
diff --git a/app/lib/encryption/utils.ts b/app/lib/encryption/utils.ts
index 3df3ce48a3..a36e62c4ca 100644
--- a/app/lib/encryption/utils.ts
+++ b/app/lib/encryption/utils.ts
@@ -1,9 +1,10 @@
import ByteBuffer from 'bytebuffer';
import SimpleCrypto from 'react-native-simple-crypto';
-import { random } from '../methods/helpers';
+import { compareServerVersion, random } from '../methods/helpers';
import { fromByteArray, toByteArray } from './helpers/base64-js';
import { TSubscriptionModel } from '../../definitions';
+import { store } from '../store/auxStore';
const BASE64URI = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
@@ -60,6 +61,39 @@ export const toString = (thing: string | ByteBuffer | Buffer | ArrayBuffer | Uin
};
export const randomPassword = (): string => `${random(3)}-${random(3)}-${random(3)}`.toLowerCase();
+export const generateAESCTRKey = () => SimpleCrypto.utils.randomBytes(32);
+
+interface IExportedKey {
+ kty: string;
+ alg: string;
+ k: string;
+ ext: boolean;
+ key_ops: string[];
+}
+
+export const exportAESCTR = (key: ArrayBuffer): IExportedKey => {
+ // Web Crypto format of a Secret Key
+ const exportedKey = {
+ // Type of Secret Key
+ kty: 'oct',
+ // Algorithm
+ alg: 'A256CTR',
+ // Base64URI encoded array of bytes
+ k: bufferToB64URI(key),
+ // Specific Web Crypto properties
+ ext: true,
+ key_ops: ['encrypt', 'decrypt']
+ };
+
+ return exportedKey;
+};
+
+export const encryptAESCTR = (path: string, key: string, vector: string): Promise =>
+ SimpleCrypto.AES.encryptFile(path, key, vector);
+
+export const decryptAESCTR = (path: string, key: string, vector: string): Promise =>
+ SimpleCrypto.AES.decryptFile(path, key, vector);
+
// Missing room encryption key
export const isMissingRoomE2EEKey = ({
encryptionEnabled,
@@ -69,7 +103,13 @@ export const isMissingRoomE2EEKey = ({
encryptionEnabled: boolean;
roomEncrypted: TSubscriptionModel['encrypted'];
E2EKey: TSubscriptionModel['E2EKey'];
-}): boolean => (encryptionEnabled && roomEncrypted && !E2EKey) ?? false;
+}): boolean => {
+ const serverVersion = store.getState().server.version;
+ if (compareServerVersion(serverVersion, 'lowerThan', '6.10.0')) {
+ return false;
+ }
+ return (encryptionEnabled && roomEncrypted && !E2EKey) ?? false;
+};
// Encrypted room, but user session is not encrypted
export const isE2EEDisabledEncryptedRoom = ({
@@ -78,7 +118,13 @@ export const isE2EEDisabledEncryptedRoom = ({
}: {
encryptionEnabled: boolean;
roomEncrypted: TSubscriptionModel['encrypted'];
-}): boolean => (!encryptionEnabled && roomEncrypted) ?? false;
+}): boolean => {
+ const serverVersion = store.getState().server.version;
+ if (compareServerVersion(serverVersion, 'lowerThan', '6.10.0')) {
+ return false;
+ }
+ return (!encryptionEnabled && roomEncrypted) ?? false;
+};
export const hasE2EEWarning = ({
encryptionEnabled,
@@ -97,3 +143,18 @@ export const hasE2EEWarning = ({
}
return false;
};
+
+// https://github.com/RocketChat/Rocket.Chat/blob/7a57f3452fd26a603948b70af8f728953afee53f/apps/meteor/lib/utils/getFileExtension.ts#L1
+export const getFileExtension = (fileName?: string): string => {
+ if (!fileName) {
+ return 'file';
+ }
+
+ const arr = fileName.split('.');
+
+ if (arr.length < 2 || (arr[0] === '' && arr.length === 2)) {
+ return 'file';
+ }
+
+ return arr.pop()?.toLocaleUpperCase() || 'file';
+};
diff --git a/app/lib/methods/handleMediaDownload.ts b/app/lib/methods/handleMediaDownload.ts
index 28abab2d6b..7a31f7a1d9 100644
--- a/app/lib/methods/handleMediaDownload.ts
+++ b/app/lib/methods/handleMediaDownload.ts
@@ -2,25 +2,25 @@ import * as FileSystem from 'expo-file-system';
import * as mime from 'react-native-mime-types';
import { isEmpty } from 'lodash';
+import { TAttachmentEncryption } 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';
export type MediaTypes = 'audio' | 'image' | 'video';
-
export type TDownloadState = 'to-download' | 'loading' | 'downloaded';
-
const defaultType = {
audio: 'mp3',
image: 'jpg',
video: 'mp4'
};
-
export const LOCAL_DOCUMENT_DIRECTORY = FileSystem.documentDirectory;
const serverUrlParsedAsPath = (serverURL: string) => `${sanitizeLikeString(serverURL)}/`;
-const sanitizeFileName = (value: string) => {
+export const sanitizeFileName = (value: string) => {
const extension = value.substring(value.lastIndexOf('.') + 1);
const toSanitize = value.substring(0, value.lastIndexOf('.'));
return `${sanitizeLikeString(toSanitize)}.${extension}`;
@@ -195,13 +195,19 @@ export async function cancelDownload(messageUrl: string): Promise {
}
export function downloadMediaFile({
+ messageId,
type,
mimeType,
- downloadUrl
+ downloadUrl,
+ encryption,
+ originalChecksum
}: {
+ messageId: string;
type: MediaTypes;
mimeType?: string;
downloadUrl: string;
+ encryption?: TAttachmentEncryption;
+ originalChecksum?: string;
}): Promise {
return new Promise(async (resolve, reject) => {
let downloadKey = '';
@@ -213,29 +219,19 @@ export function downloadMediaFile({
downloadKey = mediaDownloadKey(downloadUrl);
downloadQueue[downloadKey] = FileSystem.createDownloadResumable(downloadUrl, path);
const result = await downloadQueue[downloadKey].downloadAsync();
- if (result?.uri) {
- return resolve(result.uri);
+
+ if (!result) {
+ return reject();
}
- return reject();
- } catch {
- return reject();
- } finally {
- delete downloadQueue[downloadKey];
- }
- });
-}
-export function resumeMediaFile({ downloadUrl }: { downloadUrl: string }): Promise {
- return new Promise(async (resolve, reject) => {
- let downloadKey = '';
- try {
- downloadKey = mediaDownloadKey(downloadUrl);
- const result = await downloadQueue[downloadKey].resumeAsync();
- if (result?.uri) {
- return resolve(result.uri);
+ if (encryption && originalChecksum) {
+ await Encryption.addFileToDecryptFileQueue(messageId, result.uri, encryption, originalChecksum);
}
- return reject();
- } catch {
+
+ emitter.emit(`downloadMedia${messageId}`, result.uri);
+ return resolve(result.uri);
+ } catch (e) {
+ console.error(e);
return reject();
} finally {
delete downloadQueue[downloadKey];
diff --git a/app/lib/methods/helpers/emitter.ts b/app/lib/methods/helpers/emitter.ts
index 3cc7f6c11c..a79368ac4b 100644
--- a/app/lib/methods/helpers/emitter.ts
+++ b/app/lib/methods/helpers/emitter.ts
@@ -2,7 +2,11 @@ import mitt from 'mitt';
import { TMarkdownStyle } from '../../../containers/MessageComposer/interfaces';
-export type TEmitterEvents = {
+type TDynamicMediaDownloadEvents = {
+ [key: `downloadMedia${string}`]: string;
+};
+
+export type TEmitterEvents = TDynamicMediaDownloadEvents & {
toolbarMention: undefined;
addMarkdown: {
style: TMarkdownStyle;
diff --git a/app/lib/methods/helpers/fileDownload.ts b/app/lib/methods/helpers/fileDownload.ts
index af255452db..5573864e1b 100644
--- a/app/lib/methods/helpers/fileDownload.ts
+++ b/app/lib/methods/helpers/fileDownload.ts
@@ -5,25 +5,39 @@ import { LISTENER } from '../../../containers/Toast';
import { IAttachment } from '../../../definitions';
import i18n from '../../../i18n';
import EventEmitter from './events';
+import { Encryption } from '../../encryption';
+import { sanitizeFileName } from '../handleMediaDownload';
export const getLocalFilePathFromFile = (localPath: string, attachment: IAttachment): string => `${localPath}${attachment.title}`;
export const fileDownload = async (url: string, attachment?: IAttachment, fileName?: string): Promise => {
let path = `${FileSystem.documentDirectory}`;
if (fileName) {
- path = `${path}${fileName}`;
+ path = `${path}${sanitizeFileName(fileName)}`;
}
- if (attachment) {
- path = `${path}${attachment.title}`;
+ if (attachment?.title) {
+ path = `${path}${sanitizeFileName(attachment.title)}`;
}
const file = await FileSystem.downloadAsync(url, path);
return file.uri;
};
-export const fileDownloadAndPreview = async (url: string, attachment: IAttachment): Promise => {
+export const fileDownloadAndPreview = async (url: string, attachment: IAttachment, messageId: string): Promise => {
try {
- const file = await fileDownload(url, attachment);
- FileViewer.open(file, {
+ let file = url;
+ // If url starts with file://, we assume it's a local file and we don't download/decrypt it
+ if (!file.startsWith('file://')) {
+ file = await fileDownload(file, attachment);
+
+ if (attachment.encryption) {
+ if (!attachment.hashes?.sha256) {
+ throw new Error('Missing checksum');
+ }
+ await Encryption.addFileToDecryptFileQueue(messageId, file, attachment.encryption, attachment.hashes?.sha256);
+ }
+ }
+
+ await FileViewer.open(file, {
showOpenWithDialog: true,
showAppsSuggestions: true
});
diff --git a/app/lib/methods/helpers/fileUpload.ts b/app/lib/methods/helpers/fileUpload.ts
deleted file mode 100644
index d711d095bc..0000000000
--- a/app/lib/methods/helpers/fileUpload.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-export interface IFileUpload {
- name: string;
- uri?: string;
- type?: string;
- filename?: string;
- data?: any;
-}
-
-export class Upload {
- public xhr: XMLHttpRequest;
- public formData: FormData;
-
- constructor() {
- this.xhr = new XMLHttpRequest();
- this.formData = new FormData();
- }
-
- public setupRequest(url: string, headers: { [key: string]: string }): void {
- this.xhr.open('POST', url);
- Object.keys(headers).forEach(key => {
- this.xhr.setRequestHeader(key, headers[key]);
- });
- }
-
- public appendFile(item: IFileUpload): void {
- if (item.uri) {
- this.formData.append(item.name, {
- uri: item.uri,
- type: item.type,
- name: item.filename
- } as any);
- } else {
- this.formData.append(item.name, item.data);
- }
- }
-
- public then(callback: (param: { respInfo: XMLHttpRequest }) => void): void {
- this.xhr.onload = () => callback({ respInfo: this.xhr });
- this.xhr.send(this.formData);
- }
-
- public catch(callback: ((this: XMLHttpRequest, ev: ProgressEvent) => any) | null): void {
- this.xhr.onerror = callback;
- }
-
- public uploadProgress(callback: (param: number, arg1: number) => any): void {
- this.xhr.upload.onprogress = ({ total, loaded }) => callback(loaded, total);
- }
-
- public cancel(): Promise {
- this.xhr.abort();
- return Promise.resolve();
- }
-}
-
-class FileUpload {
- public uploadFile(url: string, headers: { [x: string]: string }, data: IFileUpload[]) {
- const upload = new Upload();
- upload.setupRequest(url, headers);
- data.forEach(item => upload.appendFile(item));
- return upload;
- }
-}
-
-const fileUpload = new FileUpload();
-export default fileUpload;
diff --git a/app/lib/methods/helpers/fileUpload/Upload.android.ts b/app/lib/methods/helpers/fileUpload/Upload.android.ts
new file mode 100644
index 0000000000..e26182bec4
--- /dev/null
+++ b/app/lib/methods/helpers/fileUpload/Upload.android.ts
@@ -0,0 +1,92 @@
+import * as FileSystem from 'expo-file-system';
+
+import { TRoomsMediaResponse } from '../../../../definitions/rest/v1/rooms';
+import { IFormData } from './definitions';
+
+export class Upload {
+ private uploadUrl: string;
+ private file: {
+ uri: string;
+ type: string | undefined;
+ name: string | undefined;
+ } | null;
+ private headers: { [key: string]: string };
+ private formData: any;
+ private uploadTask: FileSystem.UploadTask | null;
+ private isCancelled: boolean;
+ private progressCallback?: (loaded: number, total: number) => void;
+
+ constructor() {
+ this.uploadUrl = '';
+ this.file = null;
+ this.headers = {};
+ this.formData = {};
+ this.uploadTask = null;
+ this.isCancelled = false;
+ }
+
+ public setupRequest(
+ url: string,
+ headers: { [key: string]: string },
+ progressCallback?: (loaded: number, total: number) => void
+ ): void {
+ this.uploadUrl = url;
+ this.headers = headers;
+ this.progressCallback = progressCallback;
+ }
+
+ public appendFile(item: IFormData): void {
+ if (item.uri) {
+ this.file = { uri: item.uri, type: item.type, name: item.filename };
+ } else {
+ this.formData[item.name] = item.data;
+ }
+ }
+
+ public send(): Promise {
+ return new Promise(async (resolve, reject) => {
+ try {
+ if (!this.file) {
+ return reject();
+ }
+ this.uploadTask = FileSystem.createUploadTask(
+ this.uploadUrl,
+ this.file.uri,
+ {
+ headers: this.headers,
+ httpMethod: 'POST',
+ uploadType: FileSystem.FileSystemUploadType.MULTIPART,
+ fieldName: 'file',
+ mimeType: this.file.type,
+ parameters: this.formData
+ },
+ data => {
+ if (data.totalBytesSent && data.totalBytesExpectedToSend && this.progressCallback) {
+ this.progressCallback(data.totalBytesSent, data.totalBytesExpectedToSend);
+ }
+ }
+ );
+
+ const response = await this.uploadTask.uploadAsync();
+ if (response && response.status >= 200 && response.status < 400) {
+ resolve(JSON.parse(response.body));
+ } else {
+ reject(new Error(`Error: ${response?.status}`));
+ }
+ } catch (error) {
+ if (this.isCancelled) {
+ reject(new Error('Upload cancelled'));
+ } else {
+ reject(error);
+ }
+ }
+ });
+ }
+
+ public cancel(): void {
+ this.isCancelled = true;
+ if (this.uploadTask) {
+ this.uploadTask.cancelAsync();
+ }
+ }
+}
diff --git a/app/lib/methods/helpers/fileUpload/Upload.ts b/app/lib/methods/helpers/fileUpload/Upload.ts
new file mode 100644
index 0000000000..911c7b12d9
--- /dev/null
+++ b/app/lib/methods/helpers/fileUpload/Upload.ts
@@ -0,0 +1,70 @@
+import { TRoomsMediaResponse } from '../../../../definitions/rest/v1/rooms';
+import { IFormData } from './definitions';
+
+export class Upload {
+ private xhr: XMLHttpRequest;
+ private formData: FormData;
+ private isCancelled: boolean;
+
+ constructor() {
+ this.xhr = new XMLHttpRequest();
+ this.formData = new FormData();
+ this.isCancelled = false;
+ }
+
+ public setupRequest(
+ url: string,
+ headers: { [key: string]: string },
+ progressCallback?: (loaded: number, total: number) => void
+ ): void {
+ this.xhr.open('POST', url);
+ Object.keys(headers).forEach(key => {
+ this.xhr.setRequestHeader(key, headers[key]);
+ });
+
+ if (progressCallback) {
+ this.xhr.upload.onprogress = ({ loaded, total }) => progressCallback(loaded, total);
+ }
+ }
+
+ public appendFile(item: IFormData): void {
+ if (item.uri) {
+ this.formData.append(item.name, {
+ uri: item.uri,
+ type: item.type,
+ name: item.filename
+ } as any);
+ } else {
+ this.formData.append(item.name, item.data);
+ }
+ }
+
+ public send(): Promise {
+ return new Promise((resolve, reject) => {
+ this.xhr.onload = () => {
+ if (this.xhr.status >= 200 && this.xhr.status < 400) {
+ resolve(JSON.parse(this.xhr.responseText));
+ } else {
+ reject(new Error(`Error: ${this.xhr.statusText}`));
+ }
+ };
+
+ this.xhr.onerror = () => {
+ reject(new Error('Network Error'));
+ };
+
+ this.xhr.onabort = () => {
+ if (this.isCancelled) {
+ reject(new Error('Upload Cancelled'));
+ }
+ };
+
+ this.xhr.send(this.formData);
+ });
+ }
+
+ public cancel(): void {
+ this.isCancelled = true;
+ this.xhr.abort();
+ }
+}
diff --git a/app/lib/methods/helpers/fileUpload/definitions.ts b/app/lib/methods/helpers/fileUpload/definitions.ts
new file mode 100644
index 0000000000..85554e7717
--- /dev/null
+++ b/app/lib/methods/helpers/fileUpload/definitions.ts
@@ -0,0 +1,14 @@
+import { TRoomsMediaResponse } from '../../../../definitions/rest/v1/rooms';
+
+export interface IFormData {
+ name: string;
+ uri?: string;
+ type?: string;
+ filename?: string;
+ data?: any;
+}
+
+export interface IFileUpload {
+ send(): Promise;
+ cancel(): void;
+}
diff --git a/app/lib/methods/helpers/fileUpload/index.ts b/app/lib/methods/helpers/fileUpload/index.ts
new file mode 100644
index 0000000000..8387b40660
--- /dev/null
+++ b/app/lib/methods/helpers/fileUpload/index.ts
@@ -0,0 +1,28 @@
+import { TRoomsMediaResponse } from '../../../../definitions/rest/v1/rooms';
+import { Upload } from './Upload';
+import { IFormData } from './definitions';
+
+class FileUpload {
+ private upload: Upload;
+
+ constructor(
+ url: string,
+ headers: { [key: string]: string },
+ data: IFormData[],
+ progressCallback?: (loaded: number, total: number) => void
+ ) {
+ this.upload = new Upload();
+ this.upload.setupRequest(url, headers, progressCallback);
+ data.forEach(item => this.upload.appendFile(item));
+ }
+
+ public send(): Promise {
+ return this.upload.send();
+ }
+
+ public cancel(): void {
+ this.upload.cancel();
+ }
+}
+
+export default FileUpload;
diff --git a/app/lib/methods/index.ts b/app/lib/methods/index.ts
index 4ac4100839..e084f27344 100644
--- a/app/lib/methods/index.ts
+++ b/app/lib/methods/index.ts
@@ -26,7 +26,7 @@ export * from './logout';
export * from './readMessages';
export * from './roomTypeToApiType';
export * from './search';
-export * from './sendFileMessage';
+export { sendFileMessage } from './sendFileMessage';
export * from './sendMessage';
export * from './setUser';
export * from './triggerActions';
diff --git a/app/lib/methods/sendFileMessage.ts b/app/lib/methods/sendFileMessage.ts
deleted file mode 100644
index 43a2aa6cf3..0000000000
--- a/app/lib/methods/sendFileMessage.ts
+++ /dev/null
@@ -1,181 +0,0 @@
-import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
-import { settings as RocketChatSettings } from '@rocket.chat/sdk';
-import isEmpty from 'lodash/isEmpty';
-import { Alert } from 'react-native';
-
-import { IUpload, IUser, TUploadModel } from '../../definitions';
-import i18n from '../../i18n';
-import database from '../database';
-import type { IFileUpload, Upload } from './helpers/fileUpload';
-import FileUpload from './helpers/fileUpload';
-import log from './helpers/log';
-
-const uploadQueue: { [index: string]: Upload } = {};
-
-const getUploadPath = (path: string, rid: string) => `${path}-${rid}`;
-
-export function isUploadActive(path: string, rid: string): boolean {
- return !!uploadQueue[getUploadPath(path, rid)];
-}
-
-export async function cancelUpload(item: TUploadModel, rid: string): Promise {
- const uploadPath = getUploadPath(item.path, rid);
- if (!isEmpty(uploadQueue[uploadPath])) {
- try {
- await uploadQueue[uploadPath].cancel();
- } catch {
- // Do nothing
- }
- delete uploadQueue[uploadPath];
- }
- if (item.id) {
- try {
- const db = database.active;
- await db.write(async () => {
- await item.destroyPermanently();
- });
- } catch (e) {
- log(e);
- }
- }
-}
-
-export function sendFileMessage(
- rid: string,
- fileInfo: IUpload,
- tmid: string | undefined,
- server: string,
- user: Partial>,
- isForceTryAgain?: boolean
-): Promise {
- return new Promise(async (resolve, reject) => {
- try {
- const { id, token } = user;
-
- const uploadUrl = `${server}/api/v1/rooms.upload/${rid}`;
-
- fileInfo.rid = rid;
-
- const db = database.active;
- const uploadsCollection = db.get('uploads');
- const uploadPath = getUploadPath(fileInfo.path, rid);
- let uploadRecord: TUploadModel;
- try {
- uploadRecord = await uploadsCollection.find(uploadPath);
- if (uploadRecord.id && !isForceTryAgain) {
- return Alert.alert(i18n.t('FileUpload_Error'), i18n.t('Upload_in_progress'));
- }
- } catch (error) {
- try {
- await db.write(async () => {
- uploadRecord = await uploadsCollection.create(u => {
- u._raw = sanitizedRaw({ id: uploadPath }, uploadsCollection.schema);
- Object.assign(u, fileInfo);
- if (tmid) {
- u.tmid = tmid;
- }
- if (u.subscription) {
- u.subscription.id = rid;
- }
- });
- });
- } catch (e) {
- return log(e);
- }
- }
-
- const formData: IFileUpload[] = [];
- formData.push({
- name: 'file',
- type: fileInfo.type,
- filename: fileInfo.name || 'fileMessage',
- uri: fileInfo.path
- });
-
- if (fileInfo.description) {
- formData.push({
- name: 'description',
- data: fileInfo.description
- });
- }
-
- if (fileInfo.msg) {
- formData.push({
- name: 'msg',
- data: fileInfo.msg
- });
- }
-
- if (tmid) {
- formData.push({
- name: 'tmid',
- data: tmid
- });
- }
-
- const headers = {
- ...RocketChatSettings.customHeaders,
- 'Content-Type': 'multipart/form-data',
- 'X-Auth-Token': token,
- 'X-User-Id': id
- };
-
- uploadQueue[uploadPath] = FileUpload.uploadFile(uploadUrl, headers, formData);
-
- uploadQueue[uploadPath].uploadProgress(async (loaded: number, total: number) => {
- try {
- await db.write(async () => {
- await uploadRecord.update(u => {
- u.progress = Math.floor((loaded / total) * 100);
- });
- });
- } catch (e) {
- log(e);
- }
- });
-
- uploadQueue[uploadPath].then(async response => {
- if (response.respInfo.status >= 200 && response.respInfo.status < 400) {
- try {
- await db.write(async () => {
- await uploadRecord.destroyPermanently();
- });
- resolve();
- } catch (e) {
- log(e);
- }
- } else {
- try {
- await db.write(async () => {
- await uploadRecord.update(u => {
- u.error = true;
- });
- });
- } catch (e) {
- log(e);
- }
- try {
- reject(response);
- } catch (e) {
- reject(e);
- }
- }
- });
-
- uploadQueue[uploadPath].catch(async error => {
- try {
- await db.write(async () => {
- await uploadRecord.update(u => {
- u.error = true;
- });
- });
- } catch (e) {
- log(e);
- }
- reject(error);
- });
- } catch (e) {
- log(e);
- }
- });
-}
diff --git a/app/lib/methods/sendFileMessage/index.ts b/app/lib/methods/sendFileMessage/index.ts
new file mode 100644
index 0000000000..255b3f7e76
--- /dev/null
+++ b/app/lib/methods/sendFileMessage/index.ts
@@ -0,0 +1,21 @@
+import { IUpload, TSendFileMessageFileInfo, IUser } from '../../../definitions';
+import { store } from '../../store/auxStore';
+import { compareServerVersion } from '../helpers';
+import { sendFileMessage as sendFileMessageV1 } from './sendFileMessage';
+import { sendFileMessageV2 } from './sendFileMessageV2';
+
+export const sendFileMessage = (
+ rid: string,
+ fileInfo: TSendFileMessageFileInfo,
+ tmid: string | undefined,
+ server: string,
+ user: Partial>,
+ isForceTryAgain?: boolean
+): Promise => {
+ const { version: serverVersion } = store.getState().server;
+ if (compareServerVersion(serverVersion, 'lowerThan', '6.10.0')) {
+ return sendFileMessageV1(rid, fileInfo as IUpload, tmid, server, user, isForceTryAgain);
+ }
+
+ return sendFileMessageV2(rid, fileInfo, tmid, server, user, isForceTryAgain);
+};
diff --git a/app/lib/methods/sendFileMessage/sendFileMessage.ts b/app/lib/methods/sendFileMessage/sendFileMessage.ts
new file mode 100644
index 0000000000..b9a7ffd9a2
--- /dev/null
+++ b/app/lib/methods/sendFileMessage/sendFileMessage.ts
@@ -0,0 +1,116 @@
+import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
+import { settings as RocketChatSettings } from '@rocket.chat/sdk';
+import { Alert } from 'react-native';
+
+import { IUser, TSendFileMessageFileInfo, TUploadModel } from '../../../definitions';
+import i18n from '../../../i18n';
+import database from '../../database';
+import FileUpload from '../helpers/fileUpload';
+import log from '../helpers/log';
+import { copyFileToCacheDirectoryIfNeeded, getUploadPath, persistUploadError, uploadQueue } from './utils';
+import { IFormData } from '../helpers/fileUpload/definitions';
+
+export async function sendFileMessage(
+ rid: string,
+ fileInfo: TSendFileMessageFileInfo,
+ tmid: string | undefined,
+ server: string,
+ user: Partial>,
+ isForceTryAgain?: boolean
+): Promise {
+ let uploadPath: string | null = '';
+ try {
+ const { id, token } = user;
+ const uploadUrl = `${server}/api/v1/rooms.upload/${rid}`;
+ fileInfo.rid = rid;
+
+ const db = database.active;
+ const uploadsCollection = db.get('uploads');
+ uploadPath = getUploadPath(fileInfo.path, rid);
+ let uploadRecord: TUploadModel;
+ try {
+ uploadRecord = await uploadsCollection.find(uploadPath);
+ if (uploadRecord.id && !isForceTryAgain) {
+ return Alert.alert(i18n.t('FileUpload_Error'), i18n.t('Upload_in_progress'));
+ }
+ } catch (error) {
+ try {
+ await db.write(async () => {
+ uploadRecord = await uploadsCollection.create(u => {
+ u._raw = sanitizedRaw({ id: uploadPath }, uploadsCollection.schema);
+ Object.assign(u, fileInfo);
+ if (tmid) {
+ u.tmid = tmid;
+ }
+ if (u.subscription) {
+ u.subscription.id = rid;
+ }
+ });
+ });
+ } catch (e) {
+ return log(e);
+ }
+ }
+
+ fileInfo.path = await copyFileToCacheDirectoryIfNeeded(fileInfo.path, fileInfo.name);
+
+ const formData: IFormData[] = [];
+ formData.push({
+ name: 'file',
+ type: fileInfo.type,
+ filename: fileInfo.name || 'fileMessage',
+ uri: fileInfo.path
+ });
+
+ if (fileInfo.description) {
+ formData.push({
+ name: 'description',
+ data: fileInfo.description
+ });
+ }
+
+ if (fileInfo.msg) {
+ formData.push({
+ name: 'msg',
+ data: fileInfo.msg
+ });
+ }
+
+ if (tmid) {
+ formData.push({
+ name: 'tmid',
+ data: tmid
+ });
+ }
+
+ const headers = {
+ ...RocketChatSettings.customHeaders,
+ 'Content-Type': 'multipart/form-data',
+ 'X-Auth-Token': token,
+ 'X-User-Id': id
+ };
+
+ uploadQueue[uploadPath] = new FileUpload(uploadUrl, headers, formData, async (loaded, total) => {
+ try {
+ await db.write(async () => {
+ await uploadRecord?.update(u => {
+ u.progress = Math.floor((loaded / total) * 100);
+ });
+ });
+ } catch (e) {
+ console.error(e);
+ }
+ });
+ await uploadQueue[uploadPath].send();
+ await db.write(async () => {
+ await uploadRecord?.destroyPermanently();
+ });
+ } catch (e) {
+ if (uploadPath && !uploadQueue[uploadPath]) {
+ console.log('Upload cancelled');
+ } else {
+ await persistUploadError(fileInfo.path, rid);
+ throw e;
+ }
+ }
+}
diff --git a/app/lib/methods/sendFileMessage/sendFileMessageV2.ts b/app/lib/methods/sendFileMessage/sendFileMessageV2.ts
new file mode 100644
index 0000000000..1f207461cf
--- /dev/null
+++ b/app/lib/methods/sendFileMessage/sendFileMessageV2.ts
@@ -0,0 +1,94 @@
+import { settings as RocketChatSettings } from '@rocket.chat/sdk';
+
+import { TSendFileMessageFileInfo, IUser, TUploadModel } from '../../../definitions';
+import database from '../../database';
+import { Encryption } from '../../encryption';
+import { copyFileToCacheDirectoryIfNeeded, createUploadRecord, persistUploadError, uploadQueue } from './utils';
+import FileUpload from '../helpers/fileUpload';
+import { IFormData } from '../helpers/fileUpload/definitions';
+
+export async function sendFileMessageV2(
+ rid: string,
+ fileInfo: TSendFileMessageFileInfo,
+ tmid: string | undefined,
+ server: string,
+ user: Partial>,
+ isForceTryAgain?: boolean
+): Promise {
+ let uploadPath: string | null = '';
+ let uploadRecord: TUploadModel | null;
+ try {
+ const { id, token } = user;
+ const headers = {
+ ...RocketChatSettings.customHeaders,
+ 'Content-Type': 'multipart/form-data',
+ 'X-Auth-Token': token,
+ 'X-User-Id': id
+ };
+ const db = database.active;
+
+ [uploadPath, uploadRecord] = await createUploadRecord({ rid, fileInfo, tmid, isForceTryAgain });
+ if (!uploadPath || !uploadRecord) {
+ throw new Error("Couldn't create upload record");
+ }
+ const { file, getContent, fileContent } = await Encryption.encryptFile(rid, fileInfo);
+ file.path = await copyFileToCacheDirectoryIfNeeded(file.path, file.name);
+
+ const formData: IFormData[] = [];
+ formData.push({
+ name: 'file',
+ type: file.type,
+ filename: file.name,
+ uri: file.path
+ });
+ if (fileContent) {
+ formData.push({
+ name: 'content',
+ data: JSON.stringify(fileContent)
+ });
+ }
+
+ uploadQueue[uploadPath] = new FileUpload(`${server}/api/v1/rooms.media/${rid}`, headers, formData, async (loaded, total) => {
+ try {
+ await db.write(async () => {
+ await uploadRecord?.update(u => {
+ u.progress = Math.floor((loaded / total) * 100);
+ });
+ });
+ } catch (e) {
+ console.error(e);
+ }
+ });
+ const response = await uploadQueue[uploadPath].send();
+
+ let content;
+ if (getContent) {
+ content = await getContent(response.file._id, response.file.url);
+ }
+ await fetch(`${server}/api/v1/rooms.mediaConfirm/${rid}/${response.file._id}`, {
+ method: 'POST',
+ headers: {
+ ...headers,
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ msg: file.msg || undefined,
+ tmid: tmid || undefined,
+ description: file.description || undefined,
+ t: content ? 'e2e' : undefined,
+ content
+ })
+ });
+ await db.write(async () => {
+ await uploadRecord?.destroyPermanently();
+ });
+ } catch (e: any) {
+ console.error(e);
+ if (uploadPath && !uploadQueue[uploadPath]) {
+ console.log('Upload cancelled');
+ } else {
+ await persistUploadError(fileInfo.path, rid);
+ throw e;
+ }
+ }
+}
diff --git a/app/lib/methods/sendFileMessage/utils.ts b/app/lib/methods/sendFileMessage/utils.ts
new file mode 100644
index 0000000000..3d73df4b1d
--- /dev/null
+++ b/app/lib/methods/sendFileMessage/utils.ts
@@ -0,0 +1,112 @@
+import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
+import isEmpty from 'lodash/isEmpty';
+import { Alert } from 'react-native';
+import * as FileSystem from 'expo-file-system';
+
+import { getUploadByPath } from '../../database/services/Upload';
+import { IUpload, TUploadModel } from '../../../definitions';
+import i18n from '../../../i18n';
+import database from '../../database';
+import log from '../helpers/log';
+import { IFileUpload } from '../helpers/fileUpload/definitions';
+
+export const uploadQueue: { [index: string]: IFileUpload } = {};
+
+export const getUploadPath = (path: string, rid: string) => `${path}-${rid}`;
+
+export function isUploadActive(path: string, rid: string): boolean {
+ return !!uploadQueue[getUploadPath(path, rid)];
+}
+
+export async function cancelUpload(item: TUploadModel, rid: string): Promise {
+ const uploadPath = getUploadPath(item.path, rid);
+ if (!isEmpty(uploadQueue[uploadPath])) {
+ try {
+ await uploadQueue[uploadPath].cancel();
+ } catch {
+ // Do nothing
+ }
+ delete uploadQueue[uploadPath];
+ }
+ if (item.id) {
+ try {
+ const db = database.active;
+ await db.write(async () => {
+ await item.destroyPermanently();
+ });
+ } catch (e) {
+ log(e);
+ }
+ }
+}
+
+export const persistUploadError = async (path: string, rid: string) => {
+ try {
+ const db = database.active;
+ const uploadRecord = await getUploadByPath(getUploadPath(path, rid));
+ if (!uploadRecord) {
+ return;
+ }
+ await db.write(async () => {
+ await uploadRecord.update(u => {
+ u.error = true;
+ });
+ });
+ } catch {
+ // Do nothing
+ }
+};
+
+export const createUploadRecord = async ({
+ rid,
+ fileInfo,
+ tmid,
+ isForceTryAgain
+}: {
+ rid: string;
+ fileInfo: IUpload;
+ tmid: string | undefined;
+ isForceTryAgain?: boolean;
+}) => {
+ const db = database.active;
+ const uploadsCollection = db.get('uploads');
+ const uploadPath = getUploadPath(fileInfo.path, rid);
+ let uploadRecord: TUploadModel | null = null;
+ try {
+ uploadRecord = await uploadsCollection.find(uploadPath);
+ if (uploadRecord.id && !isForceTryAgain) {
+ Alert.alert(i18n.t('FileUpload_Error'), i18n.t('Upload_in_progress'));
+ return [null, null];
+ }
+ } catch (error) {
+ try {
+ await db.write(async () => {
+ uploadRecord = await uploadsCollection.create(u => {
+ u._raw = sanitizedRaw({ id: uploadPath }, uploadsCollection.schema);
+ Object.assign(u, fileInfo);
+ if (tmid) {
+ u.tmid = tmid;
+ }
+ if (u.subscription) {
+ u.subscription.id = rid;
+ }
+ });
+ });
+ } catch (e) {
+ throw e;
+ }
+ }
+ return [uploadPath, uploadRecord] as const;
+};
+
+export const copyFileToCacheDirectoryIfNeeded = async (path: string, name?: string) => {
+ if (!path.startsWith('file://') && name) {
+ if (!FileSystem.cacheDirectory) {
+ throw new Error('No cache dir');
+ }
+ const newPath = `${FileSystem.cacheDirectory}/${name}`;
+ await FileSystem.copyAsync({ from: path, to: newPath });
+ return newPath;
+ }
+ return path;
+};
diff --git a/app/lib/methods/subscriptions/rooms.ts b/app/lib/methods/subscriptions/rooms.ts
index cc79ccb2ed..105842eb2f 100644
--- a/app/lib/methods/subscriptions/rooms.ts
+++ b/app/lib/methods/subscriptions/rooms.ts
@@ -399,14 +399,16 @@ export default function subscribeRooms() {
// If it's from a encrypted room
if (message?.t === E2E_MESSAGE_TYPE) {
- // Decrypt this message content
- const { msg } = await Encryption.decryptMessage({ ...message, rid });
- // If it's a direct the content is the message decrypted
- if (room.t === 'd') {
- notification.text = msg;
- // If it's a private group we should add the sender name
- } else {
- notification.text = `${getSenderName(sender)}: ${msg}`;
+ if (message.msg) {
+ // Decrypt this message content
+ const { msg } = await Encryption.decryptMessage({ ...message, rid });
+ // If it's a direct the content is the message decrypted
+ if (room.t === 'd') {
+ notification.text = msg;
+ // If it's a private group we should add the sender name
+ } else {
+ notification.text = `${getSenderName(sender)}: ${msg}`;
+ }
}
}
} catch (e) {
diff --git a/app/sagas/login.js b/app/sagas/login.js
index 3aa28293ce..0d2b26b344 100644
--- a/app/sagas/login.js
+++ b/app/sagas/login.js
@@ -183,6 +183,7 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
getUserPresence(user.id);
const server = yield select(getServer);
+ yield put(encryptionInit());
yield put(roomsRequest());
yield fork(fetchPermissionsFork);
yield fork(fetchCustomEmojisFork);
@@ -192,7 +193,6 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
yield fork(fetchUsersPresenceFork);
yield fork(fetchEnterpriseModulesFork, { user });
yield fork(subscribeSettingsFork);
- yield put(encryptionInit());
yield fork(fetchUsersRoles);
setLanguage(user?.language);
diff --git a/app/views/AttachmentView.tsx b/app/views/AttachmentView.tsx
index ffec75a119..85379093a9 100644
--- a/app/views/AttachmentView.tsx
+++ b/app/views/AttachmentView.tsx
@@ -18,7 +18,7 @@ import { IAttachment } from '../definitions';
import I18n from '../i18n';
import { useAppSelector } from '../lib/hooks';
import { useAppNavigation, useAppRoute } from '../lib/hooks/navigation';
-import { formatAttachmentUrl, isAndroid, fileDownload } from '../lib/methods/helpers';
+import { formatAttachmentUrl, isAndroid, fileDownload, showErrorAlert } from '../lib/methods/helpers';
import EventEmitter from '../lib/methods/helpers/events';
import { getUserSelector } from '../selectors/login';
import { TNavigation } from '../stacks/stackType';
@@ -69,7 +69,7 @@ const RenderContent = ({
);
}
if (attachment.video_url) {
- const url = formatAttachmentUrl(attachment.video_url, user.id, user.token, baseUrl);
+ const url = formatAttachmentUrl(attachment.title_link || attachment.video_url, user.id, user.token, baseUrl);
const uri = encodeURI(url);
return (
);
diff --git a/app/views/RoomView/index.tsx b/app/views/RoomView/index.tsx
index f1882c86c7..87f7b854e9 100644
--- a/app/views/RoomView/index.tsx
+++ b/app/views/RoomView/index.tsx
@@ -462,6 +462,9 @@ class RoomView extends React.Component {
const teamMain = 'teamMain' in room ? room?.teamMain : false;
const omnichannelPermissions = { canForwardGuest, canReturnQueue, canPlaceLivechatOnHold };
const iSubRoom = room as ISubscription;
+ const e2eeWarning = !!(
+ 'encrypted' in room && hasE2EEWarning({ encryptionEnabled, E2EKey: room.E2EKey, roomEncrypted: room.encrypted })
+ );
navigation.setOptions({
headerShown: true,
headerTitleAlign: 'left',
@@ -502,7 +505,7 @@ class RoomView extends React.Component {
onPress={this.goRoomActionsView}
testID={`room-view-title-${title}`}
sourceType={sourceType}
- disabled={!!tmid}
+ disabled={e2eeWarning}
/>
),
headerRight: () => (
@@ -520,9 +523,7 @@ class RoomView extends React.Component {
showActionSheet={this.showActionSheet}
departmentId={departmentId}
notificationsDisabled={iSubRoom?.disableNotifications}
- hasE2EEWarning={
- 'encrypted' in room && hasE2EEWarning({ encryptionEnabled, E2EKey: room.E2EKey, roomEncrypted: room.encrypted })
- }
+ hasE2EEWarning={e2eeWarning}
/>
)
});
@@ -1382,8 +1383,7 @@ class RoomView extends React.Component {
+ enabled={!loading}>
{I18n.t('Resume')}
@@ -1398,8 +1398,7 @@ class RoomView extends React.Component {
+ enabled={!loading}>
{I18n.t(this.isOmnichannel ? 'Take_it' : 'Join')}
@@ -1488,8 +1487,7 @@ class RoomView extends React.Component {
onSendMessage: this.handleSendMessage,
setQuotesAndText: this.setQuotesAndText,
getText: this.getText
- }}
- >
+ }}>
diff --git a/app/views/ShareListView/index.tsx b/app/views/ShareListView/index.tsx
index bdfb0acf05..30dc719d63 100644
--- a/app/views/ShareListView/index.tsx
+++ b/app/views/ShareListView/index.tsx
@@ -26,8 +26,6 @@ import ShareListHeader from './Header';
import { IApplicationState, TServerModel, TSubscriptionModel } from '../../definitions';
import { ShareInsideStackParamList } from '../../definitions/navigationTypes';
import { getRoomAvatar, isAndroid, isIOS, askAndroidMediaPermissions } from '../../lib/methods/helpers';
-import { encryptionInit } from '../../actions/encryption';
-import { isE2EEDisabledEncryptedRoom, isMissingRoomE2EEKey } from '../../lib/encryption/utils';
interface IDataFromShare {
value: string;
@@ -64,7 +62,6 @@ interface IShareListViewProps extends INavigationOption {
token: string;
userId: string;
theme: TSupportedThemes;
- encryptionEnabled: boolean;
dispatch: Dispatch;
}
@@ -102,9 +99,7 @@ class ShareListView extends React.Component {
}
async componentDidMount() {
- const { dispatch } = this.props;
try {
- dispatch(encryptionInit());
const data = (await ShareExtension.data()) as IDataFromShare[];
if (isAndroid) {
await this.askForPermission(data);
@@ -140,8 +135,8 @@ class ShareListView extends React.Component {
}
componentDidUpdate(previousProps: IShareListViewProps) {
- const { server, encryptionEnabled } = this.props;
- if (previousProps.server !== server || previousProps.encryptionEnabled !== encryptionEnabled) {
+ const { server } = this.props;
+ if (previousProps.server !== server) {
this.getSubscriptions();
}
}
@@ -155,16 +150,13 @@ class ShareListView extends React.Component {
return true;
}
- const { server, userId, encryptionEnabled } = this.props;
+ const { server, userId } = this.props;
if (server !== nextProps.server) {
return true;
}
if (userId !== nextProps.userId) {
return true;
}
- if (encryptionEnabled !== nextProps.encryptionEnabled) {
- return true;
- }
const { searchResults } = this.state;
if (nextState.searching) {
@@ -232,7 +224,6 @@ class ShareListView extends React.Component {
};
query = async (text?: string) => {
- const { encryptionEnabled } = this.props;
const db = database.active;
const defaultWhereClause = [
Q.where('archived', false),
@@ -252,10 +243,7 @@ class ShareListView extends React.Component {
return data
.map(item => {
- if (isMissingRoomE2EEKey({ encryptionEnabled, roomEncrypted: item.encrypted, E2EKey: item.E2EKey })) {
- return null;
- }
- if (isE2EEDisabledEncryptedRoom({ encryptionEnabled, roomEncrypted: item.encrypted })) {
+ if (item.encrypted) {
return null;
}
@@ -461,8 +449,7 @@ class ShareListView extends React.Component {
+ contentContainerStyle={[styles.container, styles.centered, { backgroundColor: themes[theme].surfaceRoom }]}>
{I18n.t('Read_External_Permission')}
@@ -495,11 +482,10 @@ class ShareListView extends React.Component {
};
}
-const mapStateToProps = ({ share, encryption }: IApplicationState) => ({
+const mapStateToProps = ({ share }: IApplicationState) => ({
userId: share.user && (share.user.id as string),
token: share.user && (share.user.token as string),
- server: share.server.server as string,
- encryptionEnabled: encryption.enabled
+ server: share.server.server as string
});
export default connect(mapStateToProps)(withTheme(ShareListView));
diff --git a/app/views/ShareView/index.tsx b/app/views/ShareView/index.tsx
index 8a66192fc1..770990b8ef 100644
--- a/app/views/ShareView/index.tsx
+++ b/app/views/ShareView/index.tsx
@@ -262,25 +262,32 @@ class ShareView extends Component {
// Send attachment
if (attachments.length) {
await Promise.all(
- attachments.map(({ filename: name, mime: type, description, size, path, canUpload }) => {
- if (canUpload) {
- return sendFileMessage(
- room.rid,
- {
- name,
- description,
- size,
- type,
- path,
- store: 'Uploads',
- msg
- },
- this.getThreadId(thread),
- server,
- { id: user.id, token: user.token }
- );
+ attachments.map(({ filename: name, mime: type, description, size, path, canUpload, height, width, exif }) => {
+ if (!canUpload) {
+ return Promise.resolve();
}
- return Promise.resolve();
+
+ if (exif?.Orientation && ['5', '6', '7', '8'].includes(exif?.Orientation)) {
+ [width, height] = [height, width];
+ }
+
+ return sendFileMessage(
+ room.rid,
+ {
+ rid: room.rid,
+ name,
+ description,
+ size,
+ type,
+ path,
+ msg,
+ height,
+ width
+ },
+ this.getThreadId(thread),
+ server,
+ { id: user.id, token: user.token }
+ );
})
);
diff --git a/e2e/tests/assorted/01-e2eencryption.spec.ts b/e2e/tests/assorted/01-e2eencryption.spec.ts
index 34237e4c07..a82b79a08e 100644
--- a/e2e/tests/assorted/01-e2eencryption.spec.ts
+++ b/e2e/tests/assorted/01-e2eencryption.spec.ts
@@ -290,9 +290,10 @@ describe('E2E Encryption', () => {
await waitFor(element(by[textMatcher](mockedMessageText)).atIndex(0))
.not.toExist()
.withTimeout(2000);
- await waitFor(element(by.id('room-view-encrypted-room')))
- .toBeVisible()
- .withTimeout(2000);
+ // await waitFor(element(by.id('room-view-encrypted-room')))
+ // .toBeVisible()
+ // .withTimeout(2000);
+ await expect(element(by.label('Encrypted message')).atIndex(0)).toExist();
});
it('should enter new e2e password and messages should be decrypted', async () => {
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 080ddbf1ee..d22d9c8eed 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.5.1):
+ - react-native-simple-crypto (0.6.0):
- 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: a26121696064628b6cb92f52f653353114deb7f4
+ react-native-simple-crypto: 83eb246059b5bfce7e6a96bf24569a0a98e92d74
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 b81e4ebf1a..b3e98c2a14 100644
--- a/ios/RocketChatRN.xcodeproj/project.pbxproj
+++ b/ios/RocketChatRN.xcodeproj/project.pbxproj
@@ -283,7 +283,6 @@
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 */; };
- 302B9413A311E70BD4A14F54 /* libPods-defaults-RocketChatRN.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FF47BEEE842D2F9845242654 /* libPods-defaults-RocketChatRN.a */; };
4C4C8603EF082F0A33A95522 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45D5C142B655F8EFD006792C /* ExpoModulesProvider.swift */; };
65AD38372BFBDF4A00271B39 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 65AD38362BFBDF4A00271B39 /* PrivacyInfo.xcprivacy */; };
65AD38382BFBDF4A00271B39 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 65AD38362BFBDF4A00271B39 /* PrivacyInfo.xcprivacy */; };
@@ -293,7 +292,8 @@
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 */; };
- 6DE39323F736AA7E7F5F7E38 /* libPods-defaults-Rocket.Chat.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CE29D2F7ACF0AA64F6CAB659 /* libPods-defaults-Rocket.Chat.a */; };
+ 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 */; };
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,11 +356,11 @@
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 */; };
- A1977689B98493566D47CA01 /* libPods-defaults-ShareRocketChatRN.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DA288FA8320C66C838905F82 /* libPods-defaults-ShareRocketChatRN.a */; };
+ 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 */; };
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 */; };
- EA78D0177C46030D2F761085 /* libPods-defaults-NotificationService.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AA83CF0F2AF169E1CAB296C /* libPods-defaults-NotificationService.a */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -460,8 +460,9 @@
/* Begin PBXFileReference section */
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 = ""; };
- 04DBFA18E5CB8091145694D2 /* 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 = ""; };
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 = ""; };
@@ -614,10 +615,9 @@
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 = ""; };
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 = ""; };
- 3A08290344DB0E36797B30EB /* 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 = ""; };
+ 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 = ""; };
- 462CD9CE3B4EEC990739E5A3 /* 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 = ""; };
- 4AA83CF0F2AF169E1CAB296C /* libPods-defaults-NotificationService.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-NotificationService.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+ 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 = ""; };
65AD38362BFBDF4A00271B39 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; };
65B9A7192AFC24190088956F /* ringtone.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = ringtone.mp3; sourceTree = ""; };
@@ -633,16 +633,16 @@
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 = ""; };
- 7EBEA609A446827DA1DDFBAF /* 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 = ""; };
- 8E0121E756BC969D38D57C5F /* 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 = ""; };
+ 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 = ""; };
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; };
BA7E862283664608B3894E34 /* libWatermelonDB.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libWatermelonDB.a; sourceTree = ""; };
- CE29D2F7ACF0AA64F6CAB659 /* libPods-defaults-Rocket.Chat.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-Rocket.Chat.a"; sourceTree = BUILT_PRODUCTS_DIR; };
- D1C523DD34E60A3EEEACA74E /* 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 = ""; };
- D2DD00F3756357E01A0A6673 /* 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 = ""; };
- DA288FA8320C66C838905F82 /* libPods-defaults-ShareRocketChatRN.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-ShareRocketChatRN.a"; sourceTree = BUILT_PRODUCTS_DIR; };
- F43EBFEEE7BC9EFBF0AB6717 /* 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 = ""; };
- FF47BEEE842D2F9845242654 /* libPods-defaults-RocketChatRN.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-defaults-RocketChatRN.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+ 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 = ""; };
/* 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 */,
- 302B9413A311E70BD4A14F54 /* libPods-defaults-RocketChatRN.a in Frameworks */,
+ 91E821B2387EA7F67457F07F /* libPods-defaults-RocketChatRN.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -672,7 +672,7 @@
buildActionMask = 2147483647;
files = (
1E25743422CBA2CF005A877F /* JavaScriptCore.framework in Frameworks */,
- A1977689B98493566D47CA01 /* libPods-defaults-ShareRocketChatRN.a in Frameworks */,
+ 6886EA443FB173DAC09A8CC1 /* libPods-defaults-ShareRocketChatRN.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -694,7 +694,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- EA78D0177C46030D2F761085 /* libPods-defaults-NotificationService.a in Frameworks */,
+ 6A155BB8D100C38441B462E9 /* 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 */,
- 6DE39323F736AA7E7F5F7E38 /* libPods-defaults-Rocket.Chat.a in Frameworks */,
+ 9F520B3A05B8C19BB3A51B79 /* libPods-defaults-Rocket.Chat.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1148,14 +1148,14 @@
7AC2B09613AA7C3FEBAC9F57 /* Pods */ = {
isa = PBXGroup;
children = (
- D1C523DD34E60A3EEEACA74E /* Pods-defaults-NotificationService.debug.xcconfig */,
- D2DD00F3756357E01A0A6673 /* Pods-defaults-NotificationService.release.xcconfig */,
- 7EBEA609A446827DA1DDFBAF /* Pods-defaults-Rocket.Chat.debug.xcconfig */,
- 3A08290344DB0E36797B30EB /* Pods-defaults-Rocket.Chat.release.xcconfig */,
- F43EBFEEE7BC9EFBF0AB6717 /* Pods-defaults-RocketChatRN.debug.xcconfig */,
- 462CD9CE3B4EEC990739E5A3 /* Pods-defaults-RocketChatRN.release.xcconfig */,
- 04DBFA18E5CB8091145694D2 /* Pods-defaults-ShareRocketChatRN.debug.xcconfig */,
- 8E0121E756BC969D38D57C5F /* Pods-defaults-ShareRocketChatRN.release.xcconfig */,
+ 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 */,
);
path = Pods;
sourceTree = "";
@@ -1251,10 +1251,10 @@
7ACD4853222860DE00442C55 /* JavaScriptCore.framework */,
B37C79D9BD0742CE936B6982 /* libc++.tbd */,
06BB44DD4855498082A744AD /* libz.tbd */,
- 4AA83CF0F2AF169E1CAB296C /* libPods-defaults-NotificationService.a */,
- CE29D2F7ACF0AA64F6CAB659 /* libPods-defaults-Rocket.Chat.a */,
- FF47BEEE842D2F9845242654 /* libPods-defaults-RocketChatRN.a */,
- DA288FA8320C66C838905F82 /* libPods-defaults-ShareRocketChatRN.a */,
+ 5540801CC0131C98DD520BC4 /* libPods-defaults-NotificationService.a */,
+ F3D58284790D6A3A4CE28473 /* libPods-defaults-Rocket.Chat.a */,
+ AF70C4EAFD06BC1061EFEA05 /* libPods-defaults-RocketChatRN.a */,
+ 094A928A54F8F7D6362EBDE8 /* libPods-defaults-ShareRocketChatRN.a */,
);
name = Frameworks;
sourceTree = "";
@@ -1274,7 +1274,7 @@
isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "RocketChatRN" */;
buildPhases = (
- F575EB7932CE80166A95BDD4 /* [CP] Check Pods Manifest.lock */,
+ 8C825AFA26B3E74DB80133EA /* [CP] Check Pods Manifest.lock */,
7AA5C63E23E30D110005C4A7 /* Start Packager */,
589729E8381BA997CD19EF19 /* [Expo] Configure project */,
13B07F871A680F5B00A75B9A /* Sources */,
@@ -1285,8 +1285,9 @@
1E1EA8082326CCE300E22452 /* ShellScript */,
1ED0389C2B507B4F00C007D4 /* Embed Watch Content */,
7AAE9EB32891A0D20024F559 /* Upload source maps to Bugsnag */,
- 407D3EDE3DABEE15D27BD87D /* [CP] Embed Pods Frameworks */,
- 04A07763B5A24215D7C5EC6C /* [CP] Copy Pods Resources */,
+ 407D3EDE3DABEE15D27BD87D /* ShellScript */,
+ F973C9D58CCED6F67ADA2E0E /* [CP] Embed Pods Frameworks */,
+ BACE710E33AE584582B19BD0 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -1304,13 +1305,13 @@
isa = PBXNativeTarget;
buildConfigurationList = 1EC6ACF322CB9FC300A41C61 /* Build configuration list for PBXNativeTarget "ShareRocketChatRN" */;
buildPhases = (
- 815C24FF8E0F178A98DB633C /* [CP] Check Pods Manifest.lock */,
+ 277AAB0621E317E8191F8D3D /* [CP] Check Pods Manifest.lock */,
2C50632AB476A038AFCB1D43 /* [Expo] Configure project */,
1EC6ACAC22CB9FC300A41C61 /* Sources */,
1EC6ACAD22CB9FC300A41C61 /* Frameworks */,
1EC6ACAE22CB9FC300A41C61 /* Resources */,
1EFE4DC322CBF36300B766B7 /* ShellScript */,
- 7C498F4051035B569451DC83 /* [CP] Copy Pods Resources */,
+ E4BD2A0C2451C64208B69644 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -1359,12 +1360,12 @@
isa = PBXNativeTarget;
buildConfigurationList = 1EFEB5A02493B6640072EDC0 /* Build configuration list for PBXNativeTarget "NotificationService" */;
buildPhases = (
- 5C2C1F03DE470B0A39FD6D9A /* [CP] Check Pods Manifest.lock */,
+ 149B5E46E002534412D175D7 /* [CP] Check Pods Manifest.lock */,
86A998705576AFA7CE938617 /* [Expo] Configure project */,
1EFEB5912493B6640072EDC0 /* Sources */,
1EFEB5922493B6640072EDC0 /* Frameworks */,
1EFEB5932493B6640072EDC0 /* Resources */,
- E9403E993A6E17A98DE2DD5C /* [CP] Copy Pods Resources */,
+ A0BF2C82F66B14B8F59589FE /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -1379,7 +1380,7 @@
isa = PBXNativeTarget;
buildConfigurationList = 7AAB3E4F257E6A6E00707CF6 /* Build configuration list for PBXNativeTarget "Rocket.Chat" */;
buildPhases = (
- 2FA2915545FB6CB2115BC763 /* [CP] Check Pods Manifest.lock */,
+ 70428352B0FB6FE6DF9D6032 /* [CP] Check Pods Manifest.lock */,
7AAB3E13257E6A6E00707CF6 /* Start Packager */,
6723DBD924B66933E14E7EF7 /* [Expo] Configure project */,
7AAB3E14257E6A6E00707CF6 /* Sources */,
@@ -1390,8 +1391,8 @@
7AAB3E4B257E6A6E00707CF6 /* ShellScript */,
1ED1ECE32B8699DD00F6620C /* Embed Watch Content */,
7A10288726B1D15200E47EF8 /* Upload source maps to Bugsnag */,
- E0BF6A696A09DE09D098D15C /* [CP] Embed Pods Frameworks */,
- D07CE096DF607F485CE3B899 /* [CP] Copy Pods Resources */,
+ 9BE1CEB599E7DFFA7C004B9F /* [CP] Embed Pods Frameworks */,
+ 9D65115AE1DFF56CD734F04F /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -1583,82 +1584,28 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "export 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";
+ 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";
};
- 04A07763B5A24215D7C5EC6C /* [CP] Copy Pods Resources */ = {
+ 149B5E46E002534412D175D7 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
+ inputFileListPaths = (
+ );
inputPaths = (
- "${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",
- "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCrashlytics/FirebaseCrashlytics_Privacy.bundle",
- "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations/FirebaseInstallations_Privacy.bundle",
- "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport_Privacy.bundle",
- "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle",
- "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle",
- "${PODS_CONFIGURATION_BUILD_DIR}/PromisesSwift/Promises_Privacy.bundle",
- "${PODS_CONFIGURATION_BUILD_DIR}/RNDeviceInfo/RNDeviceInfoPrivacyInfo.bundle",
- "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle",
- "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf",
- "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf",
- "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf",
- "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Feather.ttf",
- "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf",
- "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf",
- "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Regular.ttf",
- "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf",
- "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Fontisto.ttf",
- "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Foundation.ttf",
- "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf",
- "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf",
- "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf",
- "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Octicons.ttf",
- "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf",
- "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf",
- "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle",
- "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle",
- "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle",
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
);
- name = "[CP] Copy Pods Resources";
outputPaths = (
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCore_Privacy.bundle",
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreExtension_Privacy.bundle",
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreInternal_Privacy.bundle",
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCrashlytics_Privacy.bundle",
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseInstallations_Privacy.bundle",
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleDataTransport_Privacy.bundle",
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle",
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle",
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Promises_Privacy.bundle",
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNDeviceInfoPrivacyInfo.bundle",
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle",
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AntDesign.ttf",
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf",
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EvilIcons.ttf",
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Feather.ttf",
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome.ttf",
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Brands.ttf",
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Regular.ttf",
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Solid.ttf",
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Fontisto.ttf",
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Foundation.ttf",
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Ionicons.ttf",
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialCommunityIcons.ttf",
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialIcons.ttf",
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Octicons.ttf",
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf",
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf",
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle",
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle",
- "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle",
+ "$(DERIVED_FILE_DIR)/Pods-defaults-NotificationService-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-resources.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;
};
1E1EA8082326CCE300E22452 /* ShellScript */ = {
@@ -1693,50 +1640,50 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "export 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";
+ 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";
};
- 2C50632AB476A038AFCB1D43 /* [Expo] Configure project */ = {
+ 277AAB0621E317E8191F8D3D /* [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-ShareRocketChatRN-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;
};
- 2FA2915545FB6CB2115BC763 /* [CP] Check Pods Manifest.lock */ = {
+ 2C50632AB476A038AFCB1D43 /* [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-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;
+ 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";
};
- 407D3EDE3DABEE15D27BD87D /* [CP] Embed Pods Frameworks */ = {
+ 407D3EDE3DABEE15D27BD87D /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -1746,10 +1693,8 @@
"${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",
+ "$(DERIVED_FILE_DIR)/Pods-defaults-NotificationService-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@@ -1775,46 +1720,46 @@
shellPath = /bin/sh;
shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-defaults-RocketChatRN/expo-configure-project.sh\"\n";
};
- 5C2C1F03DE470B0A39FD6D9A /* [CP] Check Pods Manifest.lock */ = {
+ 6723DBD924B66933E14E7EF7 /* [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-NotificationService-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;
+ 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";
};
- 6723DBD924B66933E14E7EF7 /* [Expo] Configure project */ = {
+ 70428352B0FB6FE6DF9D6032 /* [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-Rocket.Chat-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-Rocket.Chat/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;
};
7A10288726B1D15200E47EF8 /* Upload source maps to Bugsnag */ = {
isa = PBXShellScriptBuildPhase;
@@ -1921,13 +1866,74 @@
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";
};
- 7C498F4051035B569451DC83 /* [CP] Copy Pods Resources */ = {
+ 86A998705576AFA7CE938617 /* [Expo] Configure project */ = {
isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
+ inputFileListPaths = (
+ );
inputPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-defaults-ShareRocketChatRN/Pods-defaults-ShareRocketChatRN-resources.sh",
+ );
+ 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-NotificationService/expo-configure-project.sh\"\n";
+ };
+ 8C825AFA26B3E74DB80133EA /* [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;
+ };
+ 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;
+ };
+ 9D65115AE1DFF56CD734F04F /* [CP] Copy Pods Resources */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-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",
@@ -1994,57 +2000,16 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-ShareRocketChatRN/Pods-defaults-ShareRocketChatRN-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 815C24FF8E0F178A98DB633C /* [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-ShareRocketChatRN-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";
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-resources.sh\"\n";
showEnvVarsInLog = 0;
};
- 86A998705576AFA7CE938617 /* [Expo] Configure project */ = {
+ A0BF2C82F66B14B8F59589FE /* [CP] Copy Pods Resources */ = {
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-NotificationService/expo-configure-project.sh\"\n";
- };
- D07CE096DF607F485CE3B899 /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-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",
@@ -2111,36 +2076,92 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-resources.sh\"\n";
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-NotificationService/Pods-defaults-NotificationService-resources.sh\"\n";
showEnvVarsInLog = 0;
};
- E0BF6A696A09DE09D098D15C /* [CP] Embed Pods Frameworks */ = {
+ BACE710E33AE584582B19BD0 /* [CP] Copy Pods Resources */ = {
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",
+ "${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",
+ "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCrashlytics/FirebaseCrashlytics_Privacy.bundle",
+ "${PODS_CONFIGURATION_BUILD_DIR}/FirebaseInstallations/FirebaseInstallations_Privacy.bundle",
+ "${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport_Privacy.bundle",
+ "${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle",
+ "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle",
+ "${PODS_CONFIGURATION_BUILD_DIR}/PromisesSwift/Promises_Privacy.bundle",
+ "${PODS_CONFIGURATION_BUILD_DIR}/RNDeviceInfo/RNDeviceInfoPrivacyInfo.bundle",
+ "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle",
+ "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf",
+ "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf",
+ "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf",
+ "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Feather.ttf",
+ "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf",
+ "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf",
+ "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Regular.ttf",
+ "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf",
+ "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Fontisto.ttf",
+ "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Foundation.ttf",
+ "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf",
+ "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf",
+ "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf",
+ "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Octicons.ttf",
+ "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf",
+ "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf",
+ "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle",
+ "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle",
+ "${PODS_CONFIGURATION_BUILD_DIR}/nanopb/nanopb_Privacy.bundle",
);
- name = "[CP] Embed Pods Frameworks";
+ name = "[CP] Copy Pods Resources";
outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCore_Privacy.bundle",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreExtension_Privacy.bundle",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCoreInternal_Privacy.bundle",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCrashlytics_Privacy.bundle",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseInstallations_Privacy.bundle",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleDataTransport_Privacy.bundle",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Promises_Privacy.bundle",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNDeviceInfoPrivacyInfo.bundle",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/QBImagePicker.bundle",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AntDesign.ttf",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EvilIcons.ttf",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Feather.ttf",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome.ttf",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Brands.ttf",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Regular.ttf",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Solid.ttf",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Fontisto.ttf",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Foundation.ttf",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Ionicons.ttf",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialCommunityIcons.ttf",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialIcons.ttf",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Octicons.ttf",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/nanopb_Privacy.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-Rocket.Chat/Pods-defaults-Rocket.Chat-frameworks.sh\"\n";
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-defaults-RocketChatRN/Pods-defaults-RocketChatRN-resources.sh\"\n";
showEnvVarsInLog = 0;
};
- E9403E993A6E17A98DE2DD5C /* [CP] Copy Pods Resources */ = {
+ E4BD2A0C2451C64208B69644 /* [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",
@@ -2207,29 +2228,27 @@
);
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;
};
- F575EB7932CE80166A95BDD4 /* [CP] Check Pods Manifest.lock */ = {
+ F973C9D58CCED6F67ADA2E0E /* [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-RocketChatRN-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;
};
/* End PBXShellScriptBuildPhase section */
@@ -2609,7 +2628,7 @@
/* Begin XCBuildConfiguration section */
13B07F941A680F5B00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = F43EBFEEE7BC9EFBF0AB6717 /* Pods-defaults-RocketChatRN.debug.xcconfig */;
+ baseConfigurationReference = B0E90AAFBA1A95385AE7C19D /* Pods-defaults-RocketChatRN.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
APPLICATION_EXTENSION_API_ONLY = NO;
@@ -2670,7 +2689,7 @@
};
13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 462CD9CE3B4EEC990739E5A3 /* Pods-defaults-RocketChatRN.release.xcconfig */;
+ baseConfigurationReference = 3AA8F7DEB0B31A5A6329FAF5 /* Pods-defaults-RocketChatRN.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
APPLICATION_EXTENSION_API_ONLY = NO;
@@ -2731,7 +2750,7 @@
};
1EC6ACBC22CB9FC300A41C61 /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 04DBFA18E5CB8091145694D2 /* Pods-defaults-ShareRocketChatRN.debug.xcconfig */;
+ baseConfigurationReference = AF2F39716BB7E569448DF293 /* Pods-defaults-ShareRocketChatRN.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(EMBEDDED_CONTENT_CONTAINS_SWIFT)";
APPLICATION_EXTENSION_API_ONLY = YES;
@@ -2807,7 +2826,7 @@
};
1EC6ACBD22CB9FC300A41C61 /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 8E0121E756BC969D38D57C5F /* Pods-defaults-ShareRocketChatRN.release.xcconfig */;
+ baseConfigurationReference = C2909259346452596AD27AD5 /* Pods-defaults-ShareRocketChatRN.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(EMBEDDED_CONTENT_CONTAINS_SWIFT)";
APPLICATION_EXTENSION_API_ONLY = YES;
@@ -3091,7 +3110,7 @@
};
1EFEB59D2493B6640072EDC0 /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = D1C523DD34E60A3EEEACA74E /* Pods-defaults-NotificationService.debug.xcconfig */;
+ baseConfigurationReference = E2836E70B71C86AAA7EEB0F6 /* Pods-defaults-NotificationService.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(EMBEDDED_CONTENT_CONTAINS_SWIFT)";
CLANG_ANALYZER_NONNULL = YES;
@@ -3116,7 +3135,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
- MARKETING_VERSION = 4.50.0;
+ MARKETING_VERSION = 4.50.1;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
@@ -3133,7 +3152,7 @@
};
1EFEB59E2493B6640072EDC0 /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = D2DD00F3756357E01A0A6673 /* Pods-defaults-NotificationService.release.xcconfig */;
+ baseConfigurationReference = B1C2FD3D7D6F056737CCEAE0 /* Pods-defaults-NotificationService.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(EMBEDDED_CONTENT_CONTAINS_SWIFT)";
CLANG_ANALYZER_NONNULL = YES;
@@ -3160,7 +3179,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
- MARKETING_VERSION = 4.50.0;
+ MARKETING_VERSION = 4.50.1;
MTL_FAST_MATH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = chat.rocket.reactnative.NotificationService;
@@ -3176,7 +3195,7 @@
};
7AAB3E50257E6A6E00707CF6 /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 7EBEA609A446827DA1DDFBAF /* Pods-defaults-Rocket.Chat.debug.xcconfig */;
+ baseConfigurationReference = FE7279F2D432C9CAB7379496 /* Pods-defaults-Rocket.Chat.debug.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
APPLICATION_EXTENSION_API_ONLY = NO;
@@ -3236,7 +3255,7 @@
};
7AAB3E51257E6A6E00707CF6 /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 3A08290344DB0E36797B30EB /* Pods-defaults-Rocket.Chat.release.xcconfig */;
+ baseConfigurationReference = 0D20E5D2078C79720E1DBC03 /* Pods-defaults-Rocket.Chat.release.xcconfig */;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
APPLICATION_EXTENSION_API_ONLY = NO;
diff --git a/ios/RocketChatRN/Info.plist b/ios/RocketChatRN/Info.plist
index eaf00d0655..a45e228c7b 100644
--- a/ios/RocketChatRN/Info.plist
+++ b/ios/RocketChatRN/Info.plist
@@ -28,7 +28,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 4.50.0
+ 4.50.1
CFBundleSignature
????
CFBundleURLTypes
diff --git a/ios/ShareRocketChatRN/Info.plist b/ios/ShareRocketChatRN/Info.plist
index 092a14cd21..76dee41473 100644
--- a/ios/ShareRocketChatRN/Info.plist
+++ b/ios/ShareRocketChatRN/Info.plist
@@ -26,7 +26,7 @@
CFBundlePackageType
XPC!
CFBundleShortVersionString
- 4.50.0
+ 4.50.1
CFBundleVersion
1
KeychainGroup
diff --git a/package.json b/package.json
index 7e64c12402..b78e3f9eb6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "rocket-chat-reactnative",
- "version": "4.50.0",
+ "version": "4.50.1",
"private": true,
"scripts": {
"start": "react-native start",
@@ -66,7 +66,7 @@
"expo-av": "^13.10.5",
"expo-camera": "^14.1.1",
"expo-document-picker": "11.10.1",
- "expo-file-system": "^16.0.8",
+ "expo-file-system": "16.0.8",
"expo-haptics": "^12.8.1",
"expo-keep-awake": "^12.8.2",
"expo-local-authentication": "^13.8.0",
@@ -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#0.5.2",
+ "react-native-simple-crypto": "RocketChat/react-native-simple-crypto#feat.file-encryption",
"react-native-skeleton-placeholder": "5.2.4",
"react-native-slowlog": "1.0.2",
"react-native-svg": "13.8.0",
@@ -213,7 +213,7 @@
"otp.js": "1.2.0",
"patch-package": "8.0.0",
"prettier": "2.8.8",
- "react-dom": "^18.2.0",
+ "react-dom": "18.2.0",
"react-native-dotenv": "3.4.8",
"react-test-renderer": "18.2.0",
"reactotron-redux": "3.1.3",
diff --git a/patches/expo-file-system+16.0.8.patch b/patches/expo-file-system+16.0.8.patch
new file mode 100644
index 0000000000..e25b94deb3
--- /dev/null
+++ b/patches/expo-file-system+16.0.8.patch
@@ -0,0 +1,26 @@
+diff --git a/node_modules/expo-file-system/android/src/main/java/expo/modules/filesystem/FileSystemModule.kt b/node_modules/expo-file-system/android/src/main/java/expo/modules/filesystem/FileSystemModule.kt
+index 59c4ce3..1f9186d 100644
+--- a/node_modules/expo-file-system/android/src/main/java/expo/modules/filesystem/FileSystemModule.kt
++++ b/node_modules/expo-file-system/android/src/main/java/expo/modules/filesystem/FileSystemModule.kt
+@@ -82,11 +82,20 @@ private fun slashifyFilePath(path: String?): String? {
+ open class FileSystemModule : Module() {
+ private val context: Context
+ get() = appContext.reactContext ?: throw Exceptions.AppContextLost()
+- private var client: OkHttpClient? = null
+ private var dirPermissionsRequest: Promise? = null
+ private val taskHandlers: MutableMap = HashMap()
+ private val moduleCoroutineScope = CoroutineScope(Dispatchers.Default)
+
++ companion object {
++ @JvmStatic
++ var client: OkHttpClient? = null
++
++ @JvmStatic
++ fun setOkHttpClient(okHttpClient: OkHttpClient) {
++ client = okHttpClient
++ }
++ }
++
+ @SuppressLint("WrongConstant", "DiscouragedApi")
+ override fun definition() = ModuleDefinition {
+ Name("ExponentFileSystem")
diff --git a/patches/react-native-simple-crypto+0.6.0.patch b/patches/react-native-simple-crypto+0.6.0.patch
new file mode 100644
index 0000000000..7cf2d18ff3
--- /dev/null
+++ b/patches/react-native-simple-crypto+0.6.0.patch
@@ -0,0 +1,293 @@
+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 ab51328f94..f6558eefe2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7697,7 +7697,7 @@ expo-document-picker@11.10.1:
resolved "https://registry.yarnpkg.com/expo-document-picker/-/expo-document-picker-11.10.1.tgz#03394d77842a2fd7cb0a784a60098ee1ddd1012e"
integrity sha512-A1MiLfyXQ+KxanRO5lYxYQy3ryV+25JHe5Ai/BLV+FJU0QXByUF+Y/dn35WVPx5gpdZXC8UJ4ejg5SKSoeconw==
-expo-file-system@^16.0.8, expo-file-system@~16.0.0, expo-file-system@~16.0.8:
+expo-file-system@16.0.8, expo-file-system@~16.0.0, expo-file-system@~16.0.8:
version "16.0.8"
resolved "https://registry.yarnpkg.com/expo-file-system/-/expo-file-system-16.0.8.tgz#13c79a8e06e42a8e76e9297df6920597a011d989"
integrity sha512-yDbVT0TUKd7ewQjaY5THum2VRFx2n/biskGhkUmLh3ai21xjIVtaeIzHXyv9ir537eVgt4ReqDNWi7jcXjdUcA==
@@ -11878,7 +11878,7 @@ react-devtools-core@^4.27.7:
shell-quote "^1.6.1"
ws "^7"
-react-dom@^18.2.0:
+react-dom@18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
@@ -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#0.5.2:
- version "0.5.1"
- resolved "https://codeload.github.com/RocketChat/react-native-simple-crypto/tar.gz/6fb730b028c70eab37a4d4b6bcadfd23721a97c0"
+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"
dependencies:
base64-js "^1.3.0"
hex-lite "^1.5.0"