diff --git a/android/app/build.gradle b/android/app/build.gradle
index 955b3b010..08e41cb74 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -88,8 +88,8 @@ android {
applicationId 'com.internxt.cloud'
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
- versionCode 112
- versionName "1.8.3"
+ versionCode 115
+ versionName "1.8.4"
buildConfigField("boolean", "REACT_NATIVE_UNSTABLE_USE_RUNTIME_SCHEDULER_ALWAYS", (findProperty("reactNative.unstable_useRuntimeSchedulerAlways") ?: true).toString())
missingDimensionStrategy "react-native-capture-protection", "fullMediaCapture"
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index 75bc0256a..653b696b1 100644
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -2,6 +2,6 @@
Internxt
cover
false
- 1.8.3
+ 1.8.4
automatic
\ No newline at end of file
diff --git a/app.config.ts b/app.config.ts
index 395babdaf..77523f329 100644
--- a/app.config.ts
+++ b/app.config.ts
@@ -44,6 +44,7 @@ const appConfig: ExpoConfig & { extra: AppEnv & { NODE_ENV: AppStage; RELEASE_ID
associatedDomains: ['webcredentials:www.internxt.com'],
buildNumber: env[stage].IOS_BUILD_NUMBER.toString(),
infoPlist: {
+ UIDesignRequiresCompatibility: true,
NSFaceIDUsageDescription: 'Protect the app access to secure the available files',
NSCameraUsageDescription:
'Allow $(PRODUCT_NAME) to access your camera to upload a newly captured photo to the storage service',
diff --git a/ios/Internxt.xcodeproj/project.pbxproj b/ios/Internxt.xcodeproj/project.pbxproj
index b3a347cd3..8f48d9133 100644
--- a/ios/Internxt.xcodeproj/project.pbxproj
+++ b/ios/Internxt.xcodeproj/project.pbxproj
@@ -7,28 +7,28 @@
objects = {
/* Begin PBXBuildFile section */
+ 0A3640DCB02F44AABAD84072 /* noop-file.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B5CA2D57E4C403BBC990224 /* noop-file.swift */; };
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; };
96905EF65AED1B983A6B3ABC /* libPods-Internxt.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 58EEBF8E8E6FB1BC6CAF49B5 /* libPods-Internxt.a */; };
- A2F1FB5E224D420F8533FE07 /* noop-file.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36F9C1695EAA4E749324272B /* noop-file.swift */; };
B18059E884C0ABDD17F3DC3D /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */; };
BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
+ 0722A93BC221445392598F00 /* Internxt-Bridging-Header.h */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.c.h; name = "Internxt-Bridging-Header.h"; path = "Internxt/Internxt-Bridging-Header.h"; sourceTree = ""; };
13B07F961A680F5B00A75B9A /* Internxt.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Internxt.app; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = Internxt/AppDelegate.h; sourceTree = ""; };
13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = Internxt/AppDelegate.mm; sourceTree = ""; };
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Internxt/Images.xcassets; sourceTree = ""; };
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Internxt/Info.plist; sourceTree = ""; };
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Internxt/main.m; sourceTree = ""; };
- 334F4DDFA16147FD84D2D5F4 /* Internxt-Bridging-Header.h */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.c.h; name = "Internxt-Bridging-Header.h"; path = "Internxt/Internxt-Bridging-Header.h"; sourceTree = ""; };
- 36F9C1695EAA4E749324272B /* noop-file.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; name = "noop-file.swift"; path = "Internxt/noop-file.swift"; sourceTree = ""; };
58EEBF8E8E6FB1BC6CAF49B5 /* libPods-Internxt.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Internxt.a"; sourceTree = BUILT_PRODUCTS_DIR; };
6C2E3173556A471DD304B334 /* Pods-Internxt.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Internxt.debug.xcconfig"; path = "Target Support Files/Pods-Internxt/Pods-Internxt.debug.xcconfig"; sourceTree = ""; };
7A4D352CD337FB3A3BF06240 /* Pods-Internxt.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Internxt.release.xcconfig"; path = "Target Support Files/Pods-Internxt/Pods-Internxt.release.xcconfig"; sourceTree = ""; };
+ 8B5CA2D57E4C403BBC990224 /* noop-file.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; name = "noop-file.swift"; path = "Internxt/noop-file.swift"; sourceTree = ""; };
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = Internxt/SplashScreen.storyboard; sourceTree = ""; };
BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = ""; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
@@ -57,8 +57,8 @@
13B07FB61A68108700A75B9A /* Info.plist */,
13B07FB71A68108700A75B9A /* main.m */,
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */,
- 36F9C1695EAA4E749324272B /* noop-file.swift */,
- 334F4DDFA16147FD84D2D5F4 /* Internxt-Bridging-Header.h */,
+ 8B5CA2D57E4C403BBC990224 /* noop-file.swift */,
+ 0722A93BC221445392598F00 /* Internxt-Bridging-Header.h */,
);
name = Internxt;
sourceTree = "";
@@ -144,7 +144,7 @@
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Internxt" */;
buildPhases = (
08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */,
- 3251DD91593037F32E56E32E /* [Expo] Configure project */,
+ 4C0344FC9074B35620BFC8BA /* [Expo] Configure project */,
13B07F871A680F5B00A75B9A /* Sources */,
13B07F8C1A680F5B00A75B9A /* Frameworks */,
13B07F8E1A680F5B00A75B9A /* Resources */,
@@ -169,8 +169,8 @@
LastUpgradeCheck = 1130;
TargetAttributes = {
13B07F861A680F5B00A75B9A = {
- DevelopmentTeam = JR4S3SY396;
LastSwiftMigration = 1250;
+ DevelopmentTeam = "JR4S3SY396";
ProvisioningStyle = Automatic;
};
};
@@ -244,7 +244,7 @@
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;
};
- 3251DD91593037F32E56E32E /* [Expo] Configure project */ = {
+ 4C0344FC9074B35620BFC8BA /* [Expo] Configure project */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
@@ -309,7 +309,7 @@
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */,
13B07FC11A68108700A75B9A /* main.m in Sources */,
B18059E884C0ABDD17F3DC3D /* ExpoModulesProvider.swift in Sources */,
- A2F1FB5E224D420F8533FE07 /* noop-file.swift in Sources */,
+ 0A3640DCB02F44AABAD84072 /* noop-file.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -323,10 +323,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Internxt/Internxt.entitlements;
- CODE_SIGN_IDENTITY = "Apple Development";
- CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
- DEVELOPMENT_TEAM = JR4S3SY396;
ENABLE_BITCODE = NO;
GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)",
@@ -349,6 +346,9 @@
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
+ DEVELOPMENT_TEAM = "JR4S3SY396";
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
};
name = Debug;
};
@@ -359,10 +359,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Internxt/Internxt.entitlements;
- CODE_SIGN_IDENTITY = "Apple Development";
- CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
- DEVELOPMENT_TEAM = JR4S3SY396;
INFOPLIST_FILE = Internxt/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.4;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
@@ -379,6 +376,9 @@
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
+ DEVELOPMENT_TEAM = "JR4S3SY396";
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
};
name = Release;
};
diff --git a/ios/Internxt/AppDelegate.mm b/ios/Internxt/AppDelegate.mm
index d4eb869c3..37d415b57 100644
--- a/ios/Internxt/AppDelegate.mm
+++ b/ios/Internxt/AppDelegate.mm
@@ -24,7 +24,7 @@ - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
- (NSURL *)getBundleURL
{
#if DEBUG
- return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
+ return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@".expo/.virtual-metro-entry"];
#else
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
diff --git a/ios/Internxt/Info.plist b/ios/Internxt/Info.plist
index 5df62bdf3..f839b372e 100644
--- a/ios/Internxt/Info.plist
+++ b/ios/Internxt/Info.plist
@@ -19,7 +19,7 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 1.8.3
+ 1.8.4
CFBundleSignature
????
CFBundleURLTypes
@@ -56,6 +56,8 @@
Allow $(PRODUCT_NAME) to access your photos to sync your device camera roll with our Photos cloud service
RCTRootViewBackgroundColor
4294967295
+ UIDesignRequiresCompatibility
+
UILaunchStoryboardName
SplashScreen
UIRequiredDeviceCapabilities
diff --git a/ios/Internxt/Supporting/Expo.plist b/ios/Internxt/Supporting/Expo.plist
index 84df42871..52b61e8d0 100644
--- a/ios/Internxt/Supporting/Expo.plist
+++ b/ios/Internxt/Supporting/Expo.plist
@@ -9,7 +9,7 @@
EXUpdatesLaunchWaitMs
0
EXUpdatesRuntimeVersion
- 1.8.3
+ 1.8.4
EXUpdatesURL
https://u.expo.dev/680f4feb-6315-4a50-93ec-36dcd0b831d2
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 4a0005f26..e03948518 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -89,17 +89,17 @@ PODS:
- React-Core
- jail-monkey (2.8.3):
- React-Core
- - libwebp (1.3.2):
- - libwebp/demux (= 1.3.2)
- - libwebp/mux (= 1.3.2)
- - libwebp/sharpyuv (= 1.3.2)
- - libwebp/webp (= 1.3.2)
- - libwebp/demux (1.3.2):
+ - libwebp (1.5.0):
+ - libwebp/demux (= 1.5.0)
+ - libwebp/mux (= 1.5.0)
+ - libwebp/sharpyuv (= 1.5.0)
+ - libwebp/webp (= 1.5.0)
+ - libwebp/demux (1.5.0):
- libwebp/webp
- - libwebp/mux (1.3.2):
+ - libwebp/mux (1.5.0):
- libwebp/demux
- - libwebp/sharpyuv (1.3.2)
- - libwebp/webp (1.3.2):
+ - libwebp/sharpyuv (1.5.0)
+ - libwebp/webp (1.5.0):
- libwebp/sharpyuv
- RCT-Folly (2022.05.16.00):
- boost
@@ -1543,7 +1543,7 @@ SPEC CHECKSUMS:
IDZSwiftCommonCrypto: aefd3487b88dc3d7a1de2553188c720ac3194940
internxt-mobile-sdk: 821a26ae1521019b968b5c2bc716ca5498e8a7d1
jail-monkey: 066e0af74e67cbf432fbb4d214b046ef6dccf910
- libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009
+ libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
RCT-Folly: 7169b2b1c44399c76a47b5deaaba715eeeb476c0
RCTRequired: ca1d7414aba0b27efcfa2ccd37637edb1ab77d96
RCTTypeSafety: 678e344fb976ff98343ca61dc62e151f3a042292
diff --git a/package.json b/package.json
index 3a1d162be..226b36604 100644
--- a/package.json
+++ b/package.json
@@ -1,10 +1,11 @@
{
"name": "drive-mobile",
- "version": "v1.8.3",
+ "version": "v1.8.4",
"private": true,
"license": "GNU",
"scripts": {
"prepare": "husky install",
+ "postinstall": "patch-package",
"start": "expo start --dev-client",
"android": "expo run:android",
"ios": "expo run:ios",
@@ -166,8 +167,10 @@
"jest": "^29.3.1",
"jest-expo": "~50.0.4",
"metro-react-native-babel-transformer": "^0.77.0",
+ "patch-package": "^8.0.1",
"path": "^0.12.7",
"postcss": "^8.2.15",
+ "postinstall-postinstall": "^2.1.0",
"prettier": "^2.3.2",
"react-native-svg-transformer": "^0.14.3",
"react-test-renderer": "^18.2.0",
@@ -179,5 +182,8 @@
},
"engines": {
"node": ">=16.16.0"
+ },
+ "resolutions": {
+ "pbkdf2": "^3.1.3"
}
}
diff --git a/patches/expo-device+5.9.4.patch b/patches/expo-device+5.9.4.patch
new file mode 100644
index 000000000..c6485a6ce
--- /dev/null
+++ b/patches/expo-device+5.9.4.patch
@@ -0,0 +1,21 @@
+diff --git a/node_modules/expo-device/ios/UIDevice.swift b/node_modules/expo-device/ios/UIDevice.swift
+index 7d58ee8..9c2969e 100644
+--- a/node_modules/expo-device/ios/UIDevice.swift
++++ b/node_modules/expo-device/ios/UIDevice.swift
+@@ -184,9 +184,13 @@ public extension UIDevice {
+ // swiftlint:enable closure_body_length
+
+ // Credit: https://github.com/developerinsider/isJailBroken/blob/master/IsJailBroken/Extension/UIDevice%2BJailBroken.swift
+- var isSimulator: Bool {
+- return TARGET_OS_SIMULATOR != 0
+- }
++ var isSimulator: Bool {
++ #if targetEnvironment(simulator)
++ return true
++ #else
++ return false
++ #endif
++ }
+
+ var isJailbroken: Bool {
+ if UIDevice.current.isSimulator {
diff --git a/src/components/DriveNavigableItem/index.tsx b/src/components/DriveNavigableItem/index.tsx
index f5b836bb0..53eebbb73 100644
--- a/src/components/DriveNavigableItem/index.tsx
+++ b/src/components/DriveNavigableItem/index.tsx
@@ -3,7 +3,7 @@ import prettysize from 'prettysize';
import React from 'react';
import { TouchableHighlight, View } from 'react-native';
import { useTailwind } from 'tailwind-rn';
-import { FolderIcon, getFileTypeIcon } from '../../helpers';
+import { checkIsFolder, FolderIcon, getFileTypeIcon } from '../../helpers';
import { getDisplayName } from '../../helpers/itemNames';
import useGetColor from '../../hooks/useColor';
import globalStyle from '../../styles/global';
@@ -13,7 +13,9 @@ import AppText from '../AppText';
const DriveNavigableItem: React.FC = ({ isLoading, disabled, ...props }) => {
const tailwind = useTailwind();
const getColor = useGetColor();
- const isFolder = !props.data.fileId;
+
+ const isFolder = checkIsFolder(props.data);
+
const iconSize = 40;
const IconFile = getFileTypeIcon(props.data.type || '');
diff --git a/src/components/modals/DriveItemInfoModal/index.tsx b/src/components/modals/DriveItemInfoModal/index.tsx
index 65ae7744a..04f7ab282 100644
--- a/src/components/modals/DriveItemInfoModal/index.tsx
+++ b/src/components/modals/DriveItemInfoModal/index.tsx
@@ -28,7 +28,7 @@ import AppText from 'src/components/AppText';
import { SLEEP_BECAUSE_MAYBE_BACKEND_IS_NOT_RETURNING_FRESHLY_MODIFIED_OR_CREATED_ITEMS_YET } from 'src/helpers/services';
import { useTailwind } from 'tailwind-rn';
import strings from '../../../../assets/lang/strings';
-import { FolderIcon, getFileTypeIcon } from '../../../helpers';
+import { checkIsFolder, FolderIcon, getFileSize, getFileTypeIcon, isEmptyFile } from '../../../helpers';
import useGetColor from '../../../hooks/useColor';
import { MAX_SIZE_TO_DOWNLOAD } from '../../../services/drive/constants';
import { useAppDispatch, useAppSelector } from '../../../store/hooks';
@@ -56,7 +56,7 @@ function DriveItemInfoModal(): JSX.Element {
return <>>;
}
- const isFolder = !item.fileId;
+ const isFolder = checkIsFolder(item);
const handleRenameItem = () => {
dispatch(uiActions.setShowItemModal(false));
@@ -165,7 +165,8 @@ function DriveItemInfoModal(): JSX.Element {
const handleExportFile = async () => {
try {
- if (!item.fileId) {
+ const fileSize = getFileSize(item);
+ if (!item.fileId && fileSize !== 0) {
throw new Error('Item fileID not found');
}
const canDownloadFile = isFileDownloadable();
@@ -185,15 +186,20 @@ function DriveItemInfoModal(): JSX.Element {
return decryptedFilePath;
}
- setDownloadProgress({ totalBytes: 0, progress: 0, bytesReceived: 0 });
- setExporting(true);
- const downloadPath = await downloadItem(
- item.fileId,
- item.bucket as string,
- decryptedFilePath,
- parseInt(item.size?.toString() ?? '0'),
- );
- setExporting(false);
+ let downloadPath: string;
+
+ if (isEmptyFile(item)) {
+ await drive.file.createEmptyDownloadedFile(decryptedFilePath);
+ downloadPath = decryptedFilePath;
+ } else {
+ if (!item.fileId) {
+ throw new Error('Item fileID not found for non-empty file');
+ }
+ setDownloadProgress({ totalBytes: 0, progress: 0, bytesReceived: 0 });
+ setExporting(true);
+ downloadPath = await downloadItem(item.fileId, item.bucket as string, decryptedFilePath, fileSize);
+ setExporting(false);
+ }
await fs.shareFile({
title: item.name,
fileUri: downloadPath,
@@ -204,18 +210,24 @@ function DriveItemInfoModal(): JSX.Element {
errorService.reportError(error);
} finally {
setExporting(false);
+ dispatch(uiActions.setShowItemModal(false));
}
};
+
const handleAbortDownload = () => {
setExporting(false);
+ dispatch(uiActions.setShowItemModal(false));
+
if (!downloadAbortableRef.current) return;
downloadAbortableRef.current('User requested abort');
};
+
const handleAndroidDownloadFile = async () => {
try {
setDownloadProgress({ totalBytes: 0, progress: 0, bytesReceived: 0 });
- if (!item.fileId) {
+ const fileSize = getFileSize(item);
+ if (!item.fileId && fileSize !== 0) {
throw new Error('Item fileID not found');
}
const canDownloadFile = isFileDownloadable();
@@ -233,14 +245,13 @@ function DriveItemInfoModal(): JSX.Element {
// 2. If the file doesn't exists, download it
if (!existsDecrypted) {
- setExporting(true);
- await downloadItem(
- item.fileId,
- item.bucket as string,
- decryptedFilePath,
- parseInt(item.size?.toString() ?? '0'),
- );
- setExporting(false);
+ if (isEmptyFile(item)) {
+ await drive.file.createEmptyDownloadedFile(decryptedFilePath);
+ } else {
+ setExporting(true);
+ await downloadItem(item.fileId as string, item.bucket as string, decryptedFilePath, fileSize);
+ setExporting(false);
+ }
}
// 3. Copy the decrypted file (is a tmp, so this will dissapear, that's why we copy it)
@@ -259,7 +270,8 @@ function DriveItemInfoModal(): JSX.Element {
const handleiOSSaveToFiles = async () => {
try {
setDownloadProgress({ totalBytes: 0, progress: 0, bytesReceived: 0 });
- if (!item.fileId) {
+ const fileSize = getFileSize(item);
+ if (!item.fileId && fileSize !== 0) {
throw new Error('Item fileID not found');
}
const canDownloadFile = isFileDownloadable();
@@ -277,14 +289,14 @@ function DriveItemInfoModal(): JSX.Element {
// 2. If the file doesn't exists, download it
if (!existsDecrypted) {
- setExporting(true);
- await downloadItem(
- item.fileId,
- item.bucket as string,
- decryptedFilePath,
- parseInt(item.size?.toString() ?? '0'),
- );
- setExporting(false);
+ if (isEmptyFile(item)) {
+ await drive.file.createEmptyDownloadedFile(decryptedFilePath);
+ } else {
+ setExporting(true);
+
+ await downloadItem(item.fileId as string, item.bucket as string, decryptedFilePath, fileSize);
+ setExporting(false);
+ }
}
// 3. Share to iOS files app
@@ -293,6 +305,8 @@ function DriveItemInfoModal(): JSX.Element {
fileUri: decryptedFilePath,
saveToiOSFiles: true,
});
+
+ notifications.success(strings.messages.driveDownloadSuccess);
} catch (error) {
notifications.error(strings.errors.generic.message);
logger.error('Error on handleiOSSaveToFiles function:', JSON.stringify(error));
@@ -364,7 +378,7 @@ function DriveItemInfoModal(): JSX.Element {
}
if (
- item?.size &&
+ (item?.size || item?.size === 0) &&
downloadProgress?.bytesReceived &&
downloadProgress?.bytesReceived >= parseInt(item?.size?.toString())
) {
diff --git a/src/components/modals/MoveItemsModal/index.tsx b/src/components/modals/MoveItemsModal/index.tsx
index 1dc427059..1d664d33f 100644
--- a/src/components/modals/MoveItemsModal/index.tsx
+++ b/src/components/modals/MoveItemsModal/index.tsx
@@ -25,6 +25,7 @@ import Portal from '@burstware/react-native-portal';
import { useDrive } from '@internxt-mobile/hooks/drive';
import { useNavigation } from '@react-navigation/native';
import { useTailwind } from 'tailwind-rn';
+import { checkIsFile, checkIsFolder } from '../../../helpers';
import useGetColor from '../../../hooks/useColor';
import { logger } from '../../../services/common';
import notificationsService from '../../../services/NotificationsService';
@@ -82,7 +83,7 @@ function MoveItemsModal(): JSX.Element {
status: DriveItemStatus.Idle,
data: {
bucket: child.bucket,
- isFolder: 'fileId' in child ? false : true,
+ isFolder: checkIsFolder(child as any),
thumbnails: (child as DriveFileData).thumbnails,
currentThumbnail: null,
createdAt: child.createdAt,
@@ -106,7 +107,7 @@ function MoveItemsModal(): JSX.Element {
[sortMode, destinationFolderContentResponse],
);
- const isFolder = !!(itemToMove && !itemToMove.fileId);
+ const isFolder = checkIsFolder(itemToMove);
const canGoBack = currentFolderIsRootFolder ? false : true;
const onMoveButtonPressed = () => {
setConfirmModalOpen(true);
@@ -170,6 +171,7 @@ function MoveItemsModal(): JSX.Element {
plainName: itemToMove.name,
folderId: destinationFolderContentResponse.id,
folderUuid: destinationFolderId,
+ isFolder,
};
// Added any because itemToMove is not typed correctly
driveCtx.addItemToTree(destinationFolderId, itemForDestination as any, isFolder);
@@ -380,7 +382,7 @@ function MoveItemsModal(): JSX.Element {
return (
>;
}
- const isFolder = !item.fileId;
+ const isFolder = checkIsFolder(item);
const handleCopyLink = async () => {
const existingLink = await driveUseCases.generateShareLink({
diff --git a/src/components/modals/SharedLinkSettingsModal/SharedLinkSettingsModal.tsx b/src/components/modals/SharedLinkSettingsModal/SharedLinkSettingsModal.tsx
index ee3daa09d..3ffa515c5 100644
--- a/src/components/modals/SharedLinkSettingsModal/SharedLinkSettingsModal.tsx
+++ b/src/components/modals/SharedLinkSettingsModal/SharedLinkSettingsModal.tsx
@@ -11,6 +11,7 @@ import AppText from 'src/components/AppText';
import { useAppDispatch, useAppSelector } from 'src/store/hooks';
import { driveActions } from 'src/store/slices/drive';
import { useTailwind } from 'tailwind-rn';
+import { checkIsFolder } from '../../../helpers';
import BottomModal from '../BottomModal';
import { GeneratingLinkModal } from '../common/GeneratingLinkModal';
import { animations } from './animations';
@@ -120,7 +121,7 @@ export const SharedLinkSettingsModal: React.FC = (
return;
}
- const isFolder = item?.fileId ? false : true;
+ const isFolder = checkIsFolder(item);
// A share link already exists, obtain it
if (item?.token && item?.code) {
diff --git a/src/components/modals/SignOutModal/index.tsx b/src/components/modals/SignOutModal/index.tsx
index 180144c32..10574a1e6 100644
--- a/src/components/modals/SignOutModal/index.tsx
+++ b/src/components/modals/SignOutModal/index.tsx
@@ -1,17 +1,16 @@
-import React from 'react';
import { View } from 'react-native';
+import { useNavigation } from '@react-navigation/native';
+import { useTailwind } from 'tailwind-rn';
import strings from '../../../../assets/lang/strings';
import { useAppDispatch, useAppSelector } from '../../../store/hooks';
+import { authSelectors, authThunks } from '../../../store/slices/auth';
import { uiActions } from '../../../store/slices/ui';
-import CenterModal from '../CenterModal';
+import { RootScreenNavigationProp } from '../../../types/navigation';
import AppButton from '../../AppButton';
import AppText from '../../AppText';
-import { authSelectors, authThunks } from '../../../store/slices/auth';
-import { useNavigation } from '@react-navigation/native';
-import { RootScreenNavigationProp } from '../../../types/navigation';
-import { useTailwind } from 'tailwind-rn';
import UserProfilePicture from '../../UserProfilePicture';
+import CenterModal from '../CenterModal';
function SignOutModal(): JSX.Element {
const tailwind = useTailwind();
@@ -27,7 +26,7 @@ function SignOutModal(): JSX.Element {
onClosed();
};
const onSignOutButtonPressed = () => {
- dispatch(authThunks.signOutThunk());
+ dispatch(authThunks.signOutThunk({ reason: 'manual' }));
navigation.replace('SignIn');
onClosed();
};
@@ -35,7 +34,7 @@ function SignOutModal(): JSX.Element {
return (
-
+
{strings.modals.SignOutModal.title}
diff --git a/src/contexts/Drive/Drive.context.tsx b/src/contexts/Drive/Drive.context.tsx
index ffc030b8f..e8e92a542 100644
--- a/src/contexts/Drive/Drive.context.tsx
+++ b/src/contexts/Drive/Drive.context.tsx
@@ -9,6 +9,7 @@ import { AppStateStatus, NativeEventSubscription } from 'react-native';
import { driveFolderService } from '@internxt-mobile/services/drive/folder';
import { Thumbnail } from '@internxt/sdk/dist/drive/storage/types';
+import { mapFileWithIsFolder, mapFolderWithIsFolder } from 'src/helpers/driveItemMappers';
export type DriveFoldersTreeNode = {
name: string;
@@ -139,8 +140,8 @@ export const DriveContextProvider: React.FC = ({ chil
return {
thereAreMoreFiles,
thereAreMoreFolders,
- folders: foldersInFolder.folders.map((folder) => {
- const driveFolder = {
+ folders: foldersInFolder.folders.map((folder) =>
+ mapFolderWithIsFolder({
...folder,
updatedAt: folder.updatedAt.toString(),
createdAt: folder.createdAt.toString(),
@@ -152,13 +153,10 @@ export const DriveContextProvider: React.FC = ({ chil
userId: folder.userId,
// @ts-expect-error - API is returning status, missing from SDK
status: folder.status,
- isFolder: true,
- };
-
- return driveFolder;
- }),
- files: filesInFolder.files.map((file) => {
- const driveFile = {
+ }),
+ ),
+ files: filesInFolder.files.map((file) =>
+ mapFileWithIsFolder({
...file,
uuid: file.uuid,
id: file.id,
@@ -173,10 +171,8 @@ export const DriveContextProvider: React.FC = ({ chil
size: typeof file.size === 'bigint' ? Number(file.size) : file.size,
folderId: file.folderId,
thumbnails: file.thumbnails ?? [],
- };
-
- return driveFile;
- }),
+ }),
+ ),
};
};
@@ -189,24 +185,25 @@ export const DriveContextProvider: React.FC = ({ chil
const folderContent = await driveFolderService.getFolderContentByUuid(folderId);
return {
- folders: folderContent.children.map((folder) => ({
- uuid: folder.uuid,
- plainName: folder.plainName || folder.plain_name || '',
- id: folder.id,
- bucket: folder.bucket || null,
- createdAt: folder.createdAt,
- deleted: false,
- name: folder.plainName ?? folder.plain_name ?? (folder.name || ''),
- parentId: folder.parentId || folder.parent_id || null,
- parentUuid: folderId,
- updatedAt: folder.updatedAt,
- userId: folder.userId,
- // @ts-expect-error - API is returning status, missing from SDK
- status: folder.status,
- isFolder: true,
- })),
- files: folderContent.files.map(
- (file): DriveFileForTree => ({
+ folders: folderContent.children.map((folder) =>
+ mapFolderWithIsFolder({
+ uuid: folder.uuid,
+ plainName: folder.plainName || folder.plain_name || '',
+ id: folder.id,
+ bucket: folder.bucket || null,
+ createdAt: folder.createdAt,
+ deleted: false,
+ name: folder.plainName ?? folder.plain_name ?? (folder.name || ''),
+ parentId: folder.parentId || folder.parent_id || null,
+ parentUuid: folderId,
+ updatedAt: folder.updatedAt,
+ userId: folder.userId,
+ // @ts-expect-error - API is returning status, missing from SDK
+ status: folder.status,
+ }),
+ ),
+ files: folderContent.files.map((file) =>
+ mapFileWithIsFolder({
uuid: file.uuid,
plainName: file.plainName || file.plain_name || '',
bucket: file.bucket,
diff --git a/src/helpers/driveItemMappers.spec.ts b/src/helpers/driveItemMappers.spec.ts
new file mode 100644
index 000000000..0f79ad9a3
--- /dev/null
+++ b/src/helpers/driveItemMappers.spec.ts
@@ -0,0 +1,407 @@
+import { SharedFiles, SharedFolders } from '@internxt/sdk/dist/drive/share/types';
+import { DriveFileData } from '@internxt/sdk/dist/drive/storage/types';
+import { TrashItem } from '../services/drive/trash';
+import {
+ mapFileWithIsFolder,
+ mapFilesWithIsFolder,
+ mapFolderWithIsFolder,
+ mapFoldersWithIsFolder,
+ mapRecentFile,
+ mapSharedFile,
+ mapSharedFolder,
+ mapTrashFile,
+ mapTrashFolder,
+} from './driveItemMappers';
+
+describe('Drive item mappers', () => {
+ describe('Generic mappers', () => {
+ describe('mapFileWithIsFolder', () => {
+ it('when mapping a file object, then it adds isFolder: false', () => {
+ const file = {
+ id: 1,
+ name: 'document.pdf',
+ type: 'pdf',
+ size: 1024,
+ };
+
+ const result = mapFileWithIsFolder(file);
+
+ expect(result).toEqual({
+ id: 1,
+ name: 'document.pdf',
+ type: 'pdf',
+ size: 1024,
+ isFolder: false,
+ });
+ expect(result.isFolder).toBe(false);
+ });
+
+ it('when mapping a file with existing properties, then it preserves all properties', () => {
+ const file = {
+ id: 123,
+ uuid: 'abc-123',
+ fileId: 'file-456',
+ bucket: 'bucket-789',
+ createdAt: '2025-12-18',
+ };
+
+ const result = mapFileWithIsFolder(file);
+
+ expect(result).toMatchObject(file);
+ expect(result.isFolder).toBe(false);
+ });
+ });
+
+ describe('mapFolderWithIsFolder', () => {
+ it('when mapping a folder object, then it adds isFolder: true', () => {
+ const folder = {
+ id: 1,
+ name: 'My Folder',
+ parentId: 2,
+ };
+
+ const result = mapFolderWithIsFolder(folder);
+
+ expect(result).toEqual({
+ id: 1,
+ name: 'My Folder',
+ parentId: 2,
+ isFolder: true,
+ });
+ expect(result.isFolder).toBe(true);
+ });
+
+ it('when mapping a folder with existing properties, then it preserves all properties', () => {
+ const folder = {
+ id: 456,
+ uuid: 'folder-uuid-789',
+ parentUuid: 'parent-uuid-123',
+ createdAt: '2025-12-18',
+ };
+
+ const result = mapFolderWithIsFolder(folder);
+
+ expect(result).toMatchObject(folder);
+ expect(result.isFolder).toBe(true);
+ });
+ });
+
+ describe('mapFilesWithIsFolder', () => {
+ it('when mapping an array of files, then all have isFolder: false', () => {
+ const files = [
+ { id: 1, name: 'file1.txt' },
+ { id: 2, name: 'file2.jpg' },
+ { id: 3, name: 'file3.pdf' },
+ ];
+
+ const result = mapFilesWithIsFolder(files);
+
+ expect(result).toHaveLength(3);
+ result.forEach((file) => {
+ expect(file.isFolder).toBe(false);
+ });
+ });
+
+ it('when mapping an empty array, then it returns an empty array', () => {
+ const result = mapFilesWithIsFolder([]);
+
+ expect(result).toEqual([]);
+ });
+ });
+
+ describe('mapFoldersWithIsFolder', () => {
+ it('when mapping an array of folders, then all have isFolder: true', () => {
+ const folders = [
+ { id: 1, name: 'Folder 1' },
+ { id: 2, name: 'Folder 2' },
+ { id: 3, name: 'Folder 3' },
+ ];
+
+ const result = mapFoldersWithIsFolder(folders);
+
+ expect(result).toHaveLength(3);
+ result.forEach((folder) => {
+ expect(folder.isFolder).toBe(true);
+ });
+ });
+
+ it('when mapping an empty array, then it returns an empty array', () => {
+ const result = mapFoldersWithIsFolder([]);
+
+ expect(result).toEqual([]);
+ });
+ });
+ });
+
+ describe('Recent file mapper', () => {
+ describe('mapRecentFile', () => {
+ it('when mapping a recent file with plainName, then it uses plainName as name', () => {
+ const recentFile = {
+ id: 1,
+ fileId: 'file-123',
+ name: 'encrypted-name',
+ plainName: 'My Document.pdf',
+ type: 'pdf',
+ size: 2048,
+ } as unknown as DriveFileData;
+
+ const result = mapRecentFile(recentFile);
+
+ expect(result.name).toBe('My Document.pdf');
+ expect(result.isFolder).toBe(false);
+ });
+
+ it('when mapping a recent file without plainName, then it uses name', () => {
+ const recentFile = {
+ id: 2,
+ fileId: 'file-456',
+ name: 'document.txt',
+ plainName: null,
+ type: 'txt',
+ size: 512,
+ } as unknown as DriveFileData;
+
+ const result = mapRecentFile(recentFile);
+
+ expect(result.name).toBe('document.txt');
+ expect(result.isFolder).toBe(false);
+ });
+
+ it('when mapping a recent file, then it preserves all original properties', () => {
+ const recentFile = {
+ id: 3,
+ fileId: 'file-789',
+ name: 'name',
+ plainName: 'plainName',
+ type: 'jpg',
+ size: 1024,
+ uuid: 'uuid-123',
+ bucket: 'bucket-456',
+ createdAt: '2025-12-18',
+ updatedAt: '2025-12-18',
+ } as unknown as DriveFileData;
+
+ const result = mapRecentFile(recentFile);
+
+ expect(result).toMatchObject({
+ id: 3,
+ fileId: 'file-789',
+ type: 'jpg',
+ size: 1024,
+ uuid: 'uuid-123',
+ bucket: 'bucket-456',
+ });
+ expect(result.name).toBe('plainName');
+ expect(result.isFolder).toBe(false);
+ });
+ });
+ });
+
+ describe('Trash item mappers', () => {
+ describe('mapTrashFile', () => {
+ it('when mapping a trash file, then it uses plainName and sets isFolder: false', () => {
+ const trashFile = {
+ id: 1,
+ name: 'encrypted-name',
+ plainName: 'Deleted File.pdf',
+ type: 'pdf',
+ size: 1024,
+ } as unknown as TrashItem;
+
+ const result = mapTrashFile(trashFile);
+
+ expect(result.name).toBe('Deleted File.pdf');
+ expect(result.isFolder).toBe(false);
+ });
+
+ it('when mapping a trash file, then it preserves all properties', () => {
+ const trashFile = {
+ id: 2,
+ uuid: 'trash-file-uuid',
+ plainName: 'File.txt',
+ type: 'txt',
+ size: 512,
+ deletedAt: '2025-12-18',
+ } as unknown as TrashItem;
+
+ const result = mapTrashFile(trashFile);
+
+ expect(result).toMatchObject({
+ id: 2,
+ uuid: 'trash-file-uuid',
+ type: 'txt',
+ size: 512,
+ deletedAt: '2025-12-18',
+ });
+ expect(result.name).toBe('File.txt');
+ expect(result.isFolder).toBe(false);
+ });
+ });
+
+ describe('mapTrashFolder', () => {
+ it('when mapping a trash folder, then it sets isFolder: true', () => {
+ const trashFolder = {
+ id: 1,
+ name: 'Deleted Folder',
+ type: 'folder',
+ parentUuid: 'parent-uuid',
+ } as unknown as TrashItem;
+
+ const result = mapTrashFolder(trashFolder);
+
+ expect(result.isFolder).toBe(true);
+ });
+
+ it('when mapping a trash folder, then it preserves all properties', () => {
+ const trashFolder = {
+ id: 3,
+ uuid: 'trash-folder-uuid',
+ name: 'Old Folder',
+ parentUuid: 'parent-uuid-123',
+ deletedAt: '2025-12-18',
+ } as unknown as TrashItem;
+
+ const result = mapTrashFolder(trashFolder);
+
+ expect(result).toMatchObject({
+ id: 3,
+ uuid: 'trash-folder-uuid',
+ name: 'Old Folder',
+ parentUuid: 'parent-uuid-123',
+ deletedAt: '2025-12-18',
+ });
+ expect(result.isFolder).toBe(true);
+ });
+ });
+ });
+
+ describe('Shared item mappers', () => {
+ describe('mapSharedFile', () => {
+ it('when mapping a shared file, then it sets isFolder: false', () => {
+ const sharedFile = {
+ id: 1,
+ name: 'Shared Document.pdf',
+ type: 'pdf',
+ size: 2048,
+ } as unknown as SharedFiles;
+
+ const result = mapSharedFile(sharedFile);
+
+ expect(result.isFolder).toBe(false);
+ });
+
+ it('when mapping a shared file, then it preserves all properties', () => {
+ const sharedFile = {
+ id: 2,
+ uuid: 'shared-file-uuid',
+ name: 'Report.xlsx',
+ type: 'xlsx',
+ size: 4096,
+ views: 10,
+ } as unknown as SharedFiles;
+
+ const result = mapSharedFile(sharedFile);
+
+ expect(result).toMatchObject({
+ id: 2,
+ uuid: 'shared-file-uuid',
+ name: 'Report.xlsx',
+ type: 'xlsx',
+ size: 4096,
+ views: 10,
+ });
+ expect(result.isFolder).toBe(false);
+ });
+ });
+
+ describe('mapSharedFolder', () => {
+ it('when mapping a shared folder, then it sets isFolder: true', () => {
+ const sharedFolder = {
+ id: 1,
+ name: 'Shared Folder',
+ } as unknown as SharedFolders;
+
+ const result = mapSharedFolder(sharedFolder);
+
+ expect(result.isFolder).toBe(true);
+ });
+
+ it('when mapping a shared folder, then it preserves all properties', () => {
+ const sharedFolder = {
+ id: 3,
+ uuid: 'shared-folder-uuid',
+ name: 'Team Documents',
+ views: 25,
+ } as unknown as SharedFolders;
+
+ const result = mapSharedFolder(sharedFolder);
+
+ expect(result).toMatchObject({
+ id: 3,
+ uuid: 'shared-folder-uuid',
+ name: 'Team Documents',
+ views: 25,
+ });
+ expect(result.isFolder).toBe(true);
+ });
+ });
+ });
+
+ describe('Edge cases', () => {
+ it('when mapping a file with type "folder", then isFolder is still false', () => {
+ const file = {
+ id: 1,
+ name: 'archive.folder',
+ type: 'folder',
+ size: 2048,
+ };
+
+ const result = mapFileWithIsFolder(file);
+
+ expect(result.isFolder).toBe(false);
+ expect(result.type).toBe('folder');
+ });
+
+ it('when mapping objects with existing isFolder field, then it gets overwritten', () => {
+ const fileWithWrongFlag = {
+ id: 1,
+ name: 'file.txt',
+ isFolder: true,
+ } as any;
+
+ const result = mapFileWithIsFolder(fileWithWrongFlag);
+
+ expect(result.isFolder).toBe(false);
+ });
+
+ it('when mapping large arrays, then all items are processed correctly', () => {
+ const files = Array.from({ length: 100 }, (_, i) => ({
+ id: i,
+ name: `file-${i}.txt`,
+ }));
+
+ const result = mapFilesWithIsFolder(files);
+
+ expect(result).toHaveLength(100);
+ expect(result.every((f) => f.isFolder === false)).toBe(true);
+ });
+ });
+
+ describe('Type consistency', () => {
+ it('when using mapFileWithIsFolder, then TypeScript infers isFolder as literal false', () => {
+ const file = { id: 1, name: 'test.txt' };
+ const result = mapFileWithIsFolder(file);
+
+ const isFolderType: false = result.isFolder;
+ expect(isFolderType).toBe(false);
+ });
+
+ it('when using mapFolderWithIsFolder, then TypeScript infers isFolder as literal true', () => {
+ const folder = { id: 1, name: 'test folder' };
+ const result = mapFolderWithIsFolder(folder);
+
+ const isFolderType: true = result.isFolder;
+ expect(isFolderType).toBe(true);
+ });
+ });
+});
diff --git a/src/helpers/driveItemMappers.ts b/src/helpers/driveItemMappers.ts
new file mode 100644
index 000000000..3083dd149
--- /dev/null
+++ b/src/helpers/driveItemMappers.ts
@@ -0,0 +1,73 @@
+import { SharedFiles, SharedFolders } from '@internxt/sdk/dist/drive/share/types';
+import { DriveFileData } from '@internxt/sdk/dist/drive/storage/types';
+import { TrashItem } from '../services/drive/trash';
+
+/**
+ * Adds isFolder field to a file item
+ */
+export const mapFileWithIsFolder = (file: T): T & { isFolder: false } => ({
+ ...file,
+ isFolder: false,
+});
+
+/**
+ * Adds isFolder field to a folder item
+ */
+export const mapFolderWithIsFolder = (folder: T): T & { isFolder: true } => ({
+ ...folder,
+ isFolder: true,
+});
+
+/**
+ * Maps an array of files adding isFolder: false to each
+ */
+export const mapFilesWithIsFolder = (files: T[]): (T & { isFolder: false })[] =>
+ files.map(mapFileWithIsFolder);
+
+/**
+ * Maps an array of folders adding isFolder: true to each
+ */
+export const mapFoldersWithIsFolder = (folders: T[]): (T & { isFolder: true })[] =>
+ folders.map(mapFolderWithIsFolder);
+
+/**
+ * Maps recent files (which are always files)
+ */
+export const mapRecentFile = (file: DriveFileData) => ({
+ ...file,
+ name: file.plainName ?? file.name,
+ isFolder: false,
+});
+
+/**
+ * Maps trash files
+ */
+export const mapTrashFile = (file: TrashItem) => ({
+ ...file,
+ name: file.plainName,
+ isFolder: false,
+});
+
+/**
+ * Maps trash folders
+ */
+export const mapTrashFolder = (folder: TrashItem) => ({
+ ...folder,
+ isFolder: true,
+});
+
+/**
+ * Maps shared files
+ */
+export const mapSharedFile = (file: SharedFiles) => ({
+ ...file,
+ isFolder: false,
+});
+
+/**
+ * Maps shared folders
+ */
+export const mapSharedFolder = (folder: SharedFolders) => ({
+ ...folder,
+ isFolder: true,
+});
diff --git a/src/helpers/driveItems.spec.ts b/src/helpers/driveItems.spec.ts
new file mode 100644
index 000000000..090042ccd
--- /dev/null
+++ b/src/helpers/driveItems.spec.ts
@@ -0,0 +1,452 @@
+import { TrashItem } from '../services/drive/trash/driveTrash.service';
+import { DriveItemData, DriveItemFocused } from '../types/drive';
+import { checkIsFile, checkIsFolder, getFileSize, isEmptyFile } from './driveItems';
+
+describe('Drive item classification', () => {
+ describe('Identifying folders', () => {
+ describe('Using isFolder field (preferred)', () => {
+ it('when an item has isFolder true, then it is recognized as a folder', () => {
+ const folder: Partial = {
+ id: 1,
+ type: 'jpg',
+ name: 'My Folder',
+ isFolder: true,
+ };
+
+ expect(checkIsFolder(folder as DriveItemData)).toBe(true);
+ });
+
+ it('when an item has isFolder false, then it is not recognized as a folder', () => {
+ const file: Partial = {
+ id: 1,
+ type: 'folder',
+ name: 'My File',
+ isFolder: false,
+ };
+
+ expect(checkIsFolder(file as DriveItemData)).toBe(false);
+ });
+
+ it('when a file with folder extension has isFolder false, then it is correctly identified as file', () => {
+ const file: Partial = {
+ id: 1,
+ type: 'folder',
+ name: 'archive.folder',
+ isFolder: false,
+ };
+
+ expect(checkIsFolder(file as DriveItemData)).toBe(false);
+ });
+ });
+
+ describe('Fallback to parentUuid detection', () => {
+ it('when an item has a parent folder reference, then it is recognized as a folder', () => {
+ const folder: Partial = {
+ id: 1,
+ name: 'My Folder',
+ parentUuid: 'parent-uuid-123',
+ };
+
+ expect(checkIsFolder(folder as DriveItemData)).toBe(true);
+ });
+
+ it('when an item has parentUuid without isFolder field, then it is recognized as a folder', () => {
+ const folder: Partial = {
+ id: 1,
+ name: 'My Folder',
+ parentUuid: 'parent-uuid-123',
+ };
+
+ expect(checkIsFolder(folder as DriveItemData)).toBe(true);
+ });
+ });
+
+ it('when an item is a regular file, then it is not recognized as a folder', () => {
+ const file: Partial = {
+ id: 1,
+ type: 'jpg',
+ name: 'image.jpg',
+ fileId: 'file-123',
+ folderUuid: 'folder-uuid-456',
+ size: 1024,
+ };
+
+ expect(checkIsFolder(file as DriveItemData)).toBe(false);
+ });
+
+ it('when an item is a file without extension, then it is not recognized as a folder', () => {
+ const file = {
+ id: 1,
+ type: null,
+ name: 'file-no-extension',
+ fileId: 'file-123',
+ folderUuid: 'folder-uuid-456',
+ size: 512,
+ } as unknown;
+
+ expect(checkIsFolder(file as DriveItemData)).toBe(false);
+ });
+
+ it('when no item is provided, then it is not recognized as a folder', () => {
+ expect(checkIsFolder(null as any)).toBe(false);
+ expect(checkIsFolder(undefined as any)).toBe(false);
+ });
+
+ describe('Trashed items', () => {
+ it('when a trashed item is a folder with parentUuid, then it is recognized as a folder', () => {
+ const trashFolder = {
+ id: 1,
+ type: 'folder',
+ name: 'Deleted Folder',
+ parentUuid: 'parent-uuid',
+ isFolder: true,
+ } as unknown;
+
+ expect(checkIsFolder(trashFolder as TrashItem)).toBe(true);
+ });
+
+ it('when a trashed item is a file without parentUuid, then it is not recognized as a folder', () => {
+ const trashFile = {
+ id: 1,
+ type: 'pdf',
+ name: 'document.pdf',
+ folderUuid: 'folder-uuid',
+ isFolder: false,
+ } as unknown;
+
+ expect(checkIsFolder(trashFile as TrashItem)).toBe(false);
+ });
+ });
+ });
+
+ describe('Detecting empty files', () => {
+ it('when a file has zero bytes as number, then it is detected as empty', () => {
+ const emptyFile: Partial = {
+ id: 1,
+ name: 'empty.bin',
+ type: 'bin',
+ size: 0,
+ };
+
+ expect(isEmptyFile(emptyFile as DriveItemData)).toBe(true);
+ });
+
+ it('when a file has zero bytes as string, then it is detected as empty', () => {
+ const emptyFile = {
+ id: 1,
+ name: 'empty.bin',
+ type: 'bin',
+ size: '0',
+ } as unknown;
+
+ expect(isEmptyFile(emptyFile as DriveItemData)).toBe(true);
+ });
+
+ it('when a file has content, then it is not detected as empty', () => {
+ const fileWithContent: Partial = {
+ id: 1,
+ name: 'document.pdf',
+ type: 'pdf',
+ size: 1024,
+ };
+
+ expect(isEmptyFile(fileWithContent as DriveItemData)).toBe(false);
+ });
+
+ it('when a file has content as string, then it is not detected as empty', () => {
+ const fileWithContent = {
+ id: 1,
+ name: 'image.jpg',
+ type: 'jpg',
+ size: '2048',
+ } as unknown;
+
+ expect(isEmptyFile(fileWithContent as DriveItemData)).toBe(false);
+ });
+
+ it('when file size is not available, then it is not detected as empty', () => {
+ const fileWithoutSize = {
+ id: 1,
+ name: 'file.txt',
+ type: 'txt',
+ } as DriveItemData;
+
+ const fileWithNullSize = {
+ id: 1,
+ name: 'file.txt',
+ type: 'txt',
+ size: null,
+ } as unknown as DriveItemData;
+
+ expect(isEmptyFile(fileWithoutSize)).toBe(false);
+ expect(isEmptyFile(fileWithNullSize)).toBe(false);
+ });
+
+ it('when no item is provided, then it is not detected as empty', () => {
+ expect(isEmptyFile(null as any)).toBe(false);
+ });
+
+ it('when an item has no size information, then it is not detected as empty', () => {
+ const item: any = {
+ id: 1,
+ name: 'item',
+ };
+
+ expect(isEmptyFile(item)).toBe(false);
+ });
+ });
+
+ describe('Getting file size', () => {
+ it('when file size is stored as number, then the numeric size is returned', () => {
+ const file: Partial = {
+ id: 1,
+ name: 'file.txt',
+ size: 1024,
+ };
+
+ expect(getFileSize(file as DriveItemData)).toBe(1024);
+ });
+
+ it('when file size is stored as string, then the numeric size is returned', () => {
+ const file = {
+ id: 1,
+ name: 'file.txt',
+ size: '2048',
+ } as unknown;
+
+ expect(getFileSize(file as DriveItemData)).toBe(2048);
+ });
+
+ it('when file has zero bytes as number, then zero is returned', () => {
+ const file: Partial = {
+ id: 1,
+ name: 'empty.bin',
+ size: 0,
+ };
+
+ expect(getFileSize(file as DriveItemData)).toBe(0);
+ });
+
+ it('when file has zero bytes as string, then zero is returned', () => {
+ const file = {
+ id: 1,
+ name: 'empty.bin',
+ size: '0',
+ } as unknown;
+
+ expect(getFileSize(file as DriveItemData)).toBe(0);
+ });
+
+ it('when file size is not available, then zero is returned', () => {
+ const fileWithUndefinedSize = {
+ id: 1,
+ name: 'file.txt',
+ } as DriveItemData;
+
+ const fileWithNullSize = {
+ id: 1,
+ name: 'file.txt',
+ size: null,
+ } as unknown as DriveItemData;
+
+ expect(getFileSize(fileWithUndefinedSize)).toBe(0);
+ expect(getFileSize(fileWithNullSize)).toBe(0);
+ });
+
+ it('when no item is provided, then zero is returned', () => {
+ expect(getFileSize(null as any)).toBe(0);
+ });
+
+ it('when an item has no size information, then zero is returned', () => {
+ const item: any = {
+ id: 1,
+ name: 'item',
+ };
+
+ expect(getFileSize(item)).toBe(0);
+ });
+
+ it('when file is very large, then the correct size is returned', () => {
+ const largeFile = {
+ id: 1,
+ name: 'large-file.bin',
+ size: '2147483648',
+ } as unknown;
+
+ expect(getFileSize(largeFile as DriveItemData)).toBe(2147483648);
+ });
+ });
+
+ describe('Identifying files', () => {
+ it('when an item is a regular file, then it is recognized as a file', () => {
+ const file: Partial = {
+ id: 1,
+ type: 'pdf',
+ name: 'document.pdf',
+ fileId: 'file-123',
+ folderUuid: 'folder-uuid-456',
+ size: 1024,
+ };
+
+ expect(checkIsFile(file as DriveItemData)).toBe(true);
+ });
+
+ it('when an item is a folder, then it is not recognized as a file', () => {
+ const folder: Partial = {
+ id: 1,
+ type: 'folder',
+ name: 'My Folder',
+ parentUuid: 'parent-uuid-123',
+ isFolder: true,
+ };
+
+ expect(checkIsFile(folder as DriveItemData)).toBe(false);
+ });
+
+ it('when an item is an empty file, then it is recognized as a file', () => {
+ const emptyFile: Partial = {
+ id: 1,
+ type: 'bin',
+ name: 'empty-file.bin',
+ folderUuid: 'folder-uuid-456',
+ size: 0,
+ };
+
+ expect(checkIsFile(emptyFile as DriveItemData)).toBe(true);
+ });
+
+ it('when an item is a file without extension, then it is recognized as a file', () => {
+ const file = {
+ id: 1,
+ type: null,
+ name: 'file-no-extension',
+ fileId: 'file-123',
+ folderUuid: 'folder-uuid-456',
+ size: 512,
+ } as unknown;
+
+ expect(checkIsFile(file as DriveItemData)).toBe(true);
+ });
+
+ describe('Focused items', () => {
+ it('when a focused item is a file, then it is recognized as a file', () => {
+ const focusedFile: DriveItemFocused = {
+ id: 1,
+ type: 'jpg',
+ name: 'photo.jpg',
+ fileId: 'file-123',
+ folderUuid: 'folder-uuid',
+ size: 2048,
+ updatedAt: '2025-12-18T00:00:00Z',
+ isFolder: false,
+ };
+
+ expect(checkIsFile(focusedFile)).toBe(true);
+ });
+
+ it('when a focused item is a folder, then it is not recognized as a file', () => {
+ const focusedFolder: DriveItemFocused = {
+ id: 1,
+ type: 'folder',
+ name: 'Documents',
+ parentUuid: 'parent-uuid',
+ updatedAt: '2025-12-18T00:00:00Z',
+ isFolder: true,
+ };
+
+ expect(checkIsFile(focusedFolder)).toBe(false);
+ });
+ });
+ });
+
+ describe('Real-world scenarios', () => {
+ it('when distinguishing between empty file and empty folder, then each is correctly identified', () => {
+ const emptyFile: Partial = {
+ id: 1,
+ type: 'bin',
+ name: 'empty-file.bin',
+ folderUuid: 'folder-uuid',
+ size: 0,
+ isFolder: false,
+ };
+
+ const emptyFolder: Partial = {
+ id: 2,
+ type: 'folder',
+ name: 'Empty Folder',
+ parentUuid: 'parent-uuid',
+ size: 0,
+ isFolder: true,
+ };
+
+ expect(checkIsFile(emptyFile as DriveItemData)).toBe(true);
+ expect(checkIsFolder(emptyFile as DriveItemData)).toBe(false);
+ expect(isEmptyFile(emptyFile as DriveItemData)).toBe(true);
+ expect(getFileSize(emptyFile as DriveItemData)).toBe(0);
+
+ expect(checkIsFile(emptyFolder as DriveItemData)).toBe(false);
+ expect(checkIsFolder(emptyFolder as DriveItemData)).toBe(true);
+ expect(isEmptyFile(emptyFolder as DriveItemData)).toBe(true);
+ expect(getFileSize(emptyFolder as DriveItemData)).toBe(0);
+ });
+
+ it('when processing a large file from production, then it is correctly identified', () => {
+ const realFile = {
+ bucket: 'd871da4c5aacc64e106b0afb',
+ createdAt: '2025-12-11T09:09:01.673Z',
+ fileId: '693a8a2c7ccfc45e1feb5e30',
+ folderUuid: '2fdb127e-fdd6-4687-9051-53761920e5d2',
+ id: 1076106294,
+ name: '1gb',
+ size: '1073741824',
+ type: null,
+ updatedAt: '2025-12-11T09:09:02.000Z',
+ uuid: 'd4918a26-8ee8-46fd-9cf2-d49a662d84d7',
+ isFolder: false,
+ } as unknown;
+
+ expect(checkIsFile(realFile as DriveItemData)).toBe(true);
+ expect(checkIsFolder(realFile as DriveItemData)).toBe(false);
+ expect(isEmptyFile(realFile as DriveItemData)).toBe(false);
+ expect(getFileSize(realFile as DriveItemData)).toBe(1073741824);
+ });
+
+ it('when processing a folder from production, then it is correctly identified', () => {
+ const realFolder: Partial = {
+ createdAt: '2025-06-10T06:52:56.000Z',
+ id: 120640427,
+ name: 'test folder',
+ parentUuid: '2fdb127e-fdd6-4687-9051-53761920e5d2',
+ size: 0,
+ type: 'folder',
+ updatedAt: '2025-09-30T08:24:17.000Z',
+ uuid: 'bc7307f2-7e69-4005-8546-f888b1270a12',
+ isFolder: true,
+ };
+
+ expect(checkIsFile(realFolder as DriveItemData)).toBe(false);
+ expect(checkIsFolder(realFolder as DriveItemData)).toBe(true);
+ expect(getFileSize(realFolder as DriveItemData)).toBe(0);
+ });
+
+ it('when processing a file with folder extension from backend, then isFolder field takes precedence', () => {
+ const fileWithFolderType = {
+ bucket: 'd871da4c5aacc64e106b0afb',
+ createdAt: '2025-12-11T09:09:01.673Z',
+ fileId: '693a8a2c7ccfc45e1feb5e30',
+ folderUuid: '2fdb127e-fdd6-4687-9051-53761920e5d2',
+ id: 1076106294,
+ name: 'archive.folder',
+ size: '2048',
+ type: 'folder',
+ updatedAt: '2025-12-11T09:09:02.000Z',
+ uuid: 'd4918a26-8ee8-46fd-9cf2-d49a662d84d7',
+ isFolder: false,
+ } as unknown;
+
+ expect(checkIsFile(fileWithFolderType as DriveItemData)).toBe(true);
+ expect(checkIsFolder(fileWithFolderType as DriveItemData)).toBe(false);
+ expect(getFileSize(fileWithFolderType as DriveItemData)).toBe(2048);
+ });
+ });
+});
diff --git a/src/helpers/driveItems.ts b/src/helpers/driveItems.ts
new file mode 100644
index 000000000..2bef08fe5
--- /dev/null
+++ b/src/helpers/driveItems.ts
@@ -0,0 +1,65 @@
+import { DriveFileData } from '@internxt/sdk/dist/drive/storage/types';
+import { TrashItem } from '../services/drive/trash';
+import { DriveItemData, DriveItemDataProps, DriveItemFocused } from '../types/drive';
+
+/**
+ * Checks if a Drive item is a folder
+ *
+ * @param item - Drive item to check
+ * @returns true if the item is a folder, false if it's a file
+ */
+export const checkIsFolder = (
+ item: DriveItemData | DriveItemDataProps | DriveItemFocused | DriveFileData | TrashItem,
+): boolean => {
+ if (!item) return false;
+
+ if ('isFolder' in item) {
+ return !!item.isFolder;
+ }
+ // Only folders have parentUuid
+ if ('parentUuid' in item && item.parentUuid) return true;
+
+ return false;
+};
+
+/**
+ * Checks if a file has zero bytes (empty file)
+ *
+ * @param item - Drive item to check
+ * @returns true if the file size is 0, false otherwise
+ */
+export const isEmptyFile = (item: DriveItemData | DriveItemFocused): boolean => {
+ if (!item || !('size' in item)) return false;
+
+ const { size } = item;
+ if (size === undefined || size === null) return false;
+
+ const sizeNumber = Number(size);
+ return sizeNumber === 0;
+};
+
+/**
+ * Gets the numeric size of a file
+ *
+ * @param item - Drive item
+ * @returns The size as a number, or 0 if size is undefined/null
+ */
+export const getFileSize = (item: DriveItemData | DriveItemFocused): number => {
+ if (!item || !('size' in item)) return 0;
+
+ const { size } = item;
+
+ if (size === undefined || size === null) return 0;
+
+ return typeof size === 'number' ? size : Number(size);
+};
+
+/**
+ * Checks if a file is a file (not a folder)
+ *
+ * @param item - Drive item to check
+ * @returns true if the item is a file, false if it's a folder
+ */
+export const checkIsFile = (item: DriveItemData | DriveItemFocused): boolean => {
+ return !checkIsFolder(item);
+};
diff --git a/src/helpers/index.ts b/src/helpers/index.ts
index eb4c8d1be..3822879ee 100644
--- a/src/helpers/index.ts
+++ b/src/helpers/index.ts
@@ -3,3 +3,5 @@ export * from './normalize';
export * from './crypt/crypt';
export * from './filetypes';
export * from './update';
+export * from './driveItems';
+export * from './driveItemMappers';
diff --git a/src/navigation/TabExplorerNavigator.tsx b/src/navigation/TabExplorerNavigator.tsx
index 294085c77..2c61d4ccb 100644
--- a/src/navigation/TabExplorerNavigator.tsx
+++ b/src/navigation/TabExplorerNavigator.tsx
@@ -54,7 +54,7 @@ export default function TabExplorerNavigator(props: RootStackScreenProps<'TabExp
} catch {
const isDeletingAccount = await asyncStorageService.getItem(AsyncStorageKey.IsDeletingAccount);
if (isDeletingAccount) {
- dispatch(authThunks.signOutThunk());
+ dispatch(authThunks.signOutThunk({ reason: 'manual' }));
props.navigation.replace('DeactivatedAccount');
}
}
diff --git a/src/plugins/AxiosPlugin.ts b/src/plugins/AxiosPlugin.ts
index b5e747d88..acccadca5 100644
--- a/src/plugins/AxiosPlugin.ts
+++ b/src/plugins/AxiosPlugin.ts
@@ -8,7 +8,7 @@ const axiosPlugin: AppPlugin = {
axios.interceptors.response.use(undefined, (err) => {
if (err.response) {
if (err.response.status === 401) {
- store.dispatch(authThunks.signOutThunk());
+ store.dispatch(authThunks.signOutThunk({ reason: 'unauthorized' }));
}
}
diff --git a/src/screens/SettingsScreen/index.tsx b/src/screens/SettingsScreen/index.tsx
index 8a57d1e05..765716e6a 100644
--- a/src/screens/SettingsScreen/index.tsx
+++ b/src/screens/SettingsScreen/index.tsx
@@ -11,7 +11,8 @@ import {
Trash,
} from 'phosphor-react-native';
import { useEffect, useRef, useState } from 'react';
-import { Linking, Platform, ScrollView, Switch, View } from 'react-native';
+import { Linking, Platform, ScrollView, View } from 'react-native';
+import AppSwitch from '../../components/AppSwitch';
import { storageSelectors } from 'src/store/slices/storage';
import { Language } from 'src/types';
@@ -288,7 +289,7 @@ function SettingsScreen({ navigation }: SettingsScreenProps<'SettingsHome'>): JS
- ): JS
- {
const driveListItem: DriveListItem = {
status: DriveItemStatus.Idle,
@@ -129,8 +130,16 @@ export function DriveFolderScreen({ navigation }: DriveScreenProps<'DriveFolder'
const driveSortedItems = useMemo(
() =>
driveUploadingItems
- .concat(folderContent.filter((item) => !item.data.fileId).sort(drive.file.getSortFunction(sortMode)))
- .concat(folderContent.filter((item) => item.data.fileId).sort(drive.file.getSortFunction(sortMode))),
+ .concat(
+ folderContent
+ .filter((item) => item.data.isFolder)
+ .sort(drive.file.getSortFunction(sortMode)),
+ )
+ .concat(
+ folderContent
+ .filter((item) => !item.data.isFolder)
+ .sort(drive.file.getSortFunction(sortMode)),
+ ),
[sortMode, driveUploadingItems, folderContent],
);
diff --git a/src/screens/drive/RecentsScreen/RecentsScreen.tsx b/src/screens/drive/RecentsScreen/RecentsScreen.tsx
index 24e4db456..8ee988e3d 100644
--- a/src/screens/drive/RecentsScreen/RecentsScreen.tsx
+++ b/src/screens/drive/RecentsScreen/RecentsScreen.tsx
@@ -14,6 +14,7 @@ import { DriveFileData } from '@internxt/sdk/dist/drive/storage/types';
import EmptyRecentsImage from 'assets/images/screens/empty-recents.svg';
import NoResultsImage from 'assets/images/screens/no-results.svg';
import { useTailwind } from 'tailwind-rn';
+import { checkIsFolder } from '../../../helpers';
import { DriveItemStatus, DriveListType, DriveListViewMode } from '../../../types/drive';
interface RecentsScreenProps {
@@ -77,7 +78,7 @@ export function RecentsScreen({
viewMode={DriveListViewMode.List}
data={{
...item,
- isFolder: item.fileId ? false : true,
+ isFolder: checkIsFolder(item),
}}
progress={-1}
/>
diff --git a/src/services/AuthService.ts b/src/services/AuthService.ts
index 62b66dcfd..2fec8c8ea 100644
--- a/src/services/AuthService.ts
+++ b/src/services/AuthService.ts
@@ -1,3 +1,4 @@
+import { logger } from '@internxt-mobile/services/common';
import { internxtMobileSDKConfig } from '@internxt/mobile-sdk';
import { Keys, Password, TwoFactorAuthQR } from '@internxt/sdk';
import { StorageTypes } from '@internxt/sdk/dist/drive';
@@ -154,7 +155,8 @@ class AuthService {
}
}
- public async signout(): Promise {
+ public async signout(reason: 'manual' | 'unauthorized' | 'token_expired'): Promise {
+ logger.info(`User logged out - Reason: ${reason}`);
analytics.track(AnalyticsEventKey.UserLogout);
await asyncStorageService.clearStorage();
await internxtMobileSDKConfig.destroy();
diff --git a/src/services/drive/file/driveFile.service.ts b/src/services/drive/file/driveFile.service.ts
index 97649ad9f..984474f07 100644
--- a/src/services/drive/file/driveFile.service.ts
+++ b/src/services/drive/file/driveFile.service.ts
@@ -259,6 +259,21 @@ class DriveFileService {
return measureThumbnail(fileSystemService.pathToUri(destination));
}
+ /**
+ * Creates an empty file directly at the destination path
+ * Used for files with size 0 that don't need network download
+ *
+ * @param downloadPath The path where the empty file should be created
+ * @returns
+ */
+ async createEmptyDownloadedFile(downloadPath: string) {
+ await fs.createEmptyFile(downloadPath);
+
+ return {
+ downloadPath,
+ };
+ }
+
/**
* Download and decrypt a file to a given filesystem path
*
@@ -337,8 +352,10 @@ class DriveFileService {
const path = this.getDecryptedFilePath(filename, type);
const exists = await fs.exists(path);
if (!exists) return false;
+
const stat = await fs.statRNFS(path);
- return exists && stat.size !== 0;
+
+ return exists && stat.isFile();
}
getName(filename: string, type?: string) {
diff --git a/src/services/drive/trash/driveTrash.service.ts b/src/services/drive/trash/driveTrash.service.ts
index 07401019e..eda9d3f25 100644
--- a/src/services/drive/trash/driveTrash.service.ts
+++ b/src/services/drive/trash/driveTrash.service.ts
@@ -1,6 +1,7 @@
import { SdkManager } from '@internxt-mobile/services/common';
import { AddItemsToTrashPayload, FetchTrashContentResponse } from '@internxt/sdk/dist/drive/storage/types';
import { DeleteItemsPermanentlyPayload } from '@internxt/sdk/dist/drive/trash/types';
+import { mapTrashFile, mapTrashFolder } from '../../../helpers/driveItemMappers';
import { driveFileService } from '../file';
import { driveFolderService } from '../folder';
@@ -27,11 +28,7 @@ class DriveTrashService {
);
return {
- items: result.map((file) => ({
- ...file,
- // Use the plainName as file name
- name: file.plainName,
- })),
+ items: result.map(mapTrashFile),
hasMore: TRASH_ITEMS_LIMIT === result.length,
};
}
@@ -45,7 +42,7 @@ class DriveTrashService {
);
return {
- items: result,
+ items: result.map(mapTrashFolder),
hasMore: TRASH_ITEMS_LIMIT === result.length,
};
}
diff --git a/src/store/slices/auth/index.ts b/src/store/slices/auth/index.ts
index 0bba27e45..72603a2b1 100644
--- a/src/store/slices/auth/index.ts
+++ b/src/store/slices/auth/index.ts
@@ -173,7 +173,7 @@ export const refreshTokensThunk = createAsyncThunk(
- 'auth/signOut',
- async (_, { dispatch }) => {
- authService.signout().catch(errorService.reportError);
- drive.clear().catch(errorService.reportError);
- dispatch(uiActions.resetState());
- dispatch(authActions.resetState());
- dispatch(driveActions.resetState());
- dispatch(authActions.setLoggedIn(false));
- authService.emitLogoutEvent();
- },
-);
+export const signOutThunk = createAsyncThunk<
+ void,
+ { reason: 'manual' | 'unauthorized' | 'token_expired' },
+ { state: RootState }
+>('auth/signOut', async (payload, { dispatch }) => {
+ const reason = payload.reason;
+ authService.signout(reason).catch(errorService.reportError);
+ drive.clear().catch(errorService.reportError);
+ dispatch(uiActions.resetState());
+ dispatch(authActions.resetState());
+ dispatch(driveActions.resetState());
+ dispatch(authActions.setLoggedIn(false));
+ authService.emitLogoutEvent();
+});
export const refreshUserThunk = createAsyncThunk(
'auth/refreshUser',
diff --git a/src/store/slices/drive/index.ts b/src/store/slices/drive/index.ts
index 0fa267367..1814e7ce0 100644
--- a/src/store/slices/drive/index.ts
+++ b/src/store/slices/drive/index.ts
@@ -1,10 +1,10 @@
-import { DriveFileData, DriveFolderData } from '@internxt/sdk/dist/drive/storage/types';
+import { DriveFileData } from '@internxt/sdk/dist/drive/storage/types';
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
import { logger } from '@internxt-mobile/services/common';
import drive from '@internxt-mobile/services/drive';
import { items } from '@internxt/lib';
-import { isValidFilename } from 'src/helpers';
+import { checkIsFolder, isValidFilename, mapRecentFile } from 'src/helpers';
import authService from 'src/services/AuthService';
import errorService from 'src/services/ErrorService';
import { ErrorCodes } from 'src/types/errors';
@@ -112,10 +112,7 @@ const initializeThunk = createAsyncThunk(
const getRecentsThunk = createAsyncThunk('drive/getRecents', async (_, { dispatch }) => {
dispatch(driveActions.setRecentsStatus(ThunkOperationStatus.LOADING));
const recents = await drive.recents.getRecents();
- const recentsParsed = recents.map((recent) => ({
- ...recent,
- name: recent.plainName ?? recent.name,
- }));
+ const recentsParsed = recents.map(mapRecentFile);
dispatch(driveActions.setRecents(recentsParsed));
});
@@ -124,7 +121,7 @@ const cancelDownloadThunk = createAsyncThunk('
});
const validateDownload = (size: number | undefined): number | null => {
- if (!size) return null;
+ if (!size || size === 0) return null;
const sizeInBytes = parseInt(size.toString());
if (sizeInBytes > MAX_SIZE_TO_DOWNLOAD['10GB']) {
@@ -251,6 +248,7 @@ const downloadFileThunk = createAsyncThunk<
const destinationPath = drive.file.getDecryptedFilePath(name, type);
logger.info(`Download destination path: ${destinationPath} `);
const fileAlreadyExists = await drive.file.existsDecrypted(name, type);
+
try {
if (!isValidFilename(name)) {
throw new Error('This file name is not valid');
@@ -263,7 +261,14 @@ const downloadFileThunk = createAsyncThunk<
analytics.trackStart(fileInfo);
downloadProgressCallback(0);
- await download({ fileId, to: destinationPath });
+ const fileSizeNumber = Number(size);
+
+ if (fileSizeNumber === 0) {
+ logger.info('File has size 0, creating empty file directly');
+ await drive.file.createEmptyDownloadedFile(destinationPath);
+ } else {
+ await download({ fileId, to: destinationPath });
+ }
}
const uri = fileSystemService.pathToUri(destinationPath);
@@ -434,7 +439,7 @@ export const driveSlice = createSlice({
}
}
},
- selectItem: (state, action: PayloadAction) => {
+ selectItem: (state, action: PayloadAction) => {
const isAlreadySelected =
state.selectedItems.filter((element) => {
const elementIsFolder = !element.fileId;
@@ -444,7 +449,7 @@ export const driveSlice = createSlice({
state.selectedItems = isAlreadySelected ? state.selectedItems : [...state.selectedItems, action.payload];
},
- deselectItem(state, action: PayloadAction) {
+ deselectItem(state, action: PayloadAction) {
const itemsWithoutRemovedItem = state.selectedItems.filter((element) => {
const elementIsFolder = !element.fileId;
@@ -621,14 +626,18 @@ export const driveSelectors = {
},
id: f.id.toString(),
})),
- items: items.map((f) => ({
- status: DriveItemStatus.Idle,
- data: {
- ...f,
- isFolder: f.fileId ? false : true,
- },
- id: f.id.toString(),
- })),
+ items: items.map((f) => {
+ const isFolder = checkIsFolder(f);
+
+ return {
+ status: DriveItemStatus.Idle,
+ data: {
+ ...f,
+ isFolder,
+ },
+ id: f.id.toString(),
+ };
+ }),
};
},
};
diff --git a/src/types/drive.ts b/src/types/drive.ts
index b2a957fb9..094386b50 100644
--- a/src/types/drive.ts
+++ b/src/types/drive.ts
@@ -1,4 +1,3 @@
-import { DocumentPickerResponse } from 'react-native-document-picker';
import { SharedFiles, SharedFolders } from '@internxt/sdk/dist/drive/share/types';
import {
DriveFileData,
@@ -7,6 +6,7 @@ import {
FolderChild,
Thumbnail,
} from '@internxt/sdk/dist/drive/storage/types';
+import { DocumentPickerResponse } from 'react-native-document-picker';
const GB = 1024 * 1024 * 1024;
export const UPLOAD_FILE_SIZE_LIMIT = 5 * GB;
@@ -29,9 +29,9 @@ export interface DriveNavigationStackItem {
}
export type DriveNavigationStack = DriveNavigationStackItem[];
-export type DriveItemData = DriveFileData & DriveFolderData & { uuid?: string };
+export type DriveItemData = DriveFileData & DriveFolderData & { uuid?: string; isFolder: boolean };
-export type DriveFile = DriveFileData & { uuid?: string };
+export type DriveFile = DriveFileData & { uuid?: string; isFolder: boolean };
export type getModifiedItemsStatus = 'EXISTS' | 'TRASHED' | 'REMOVED';
@@ -296,6 +296,7 @@ export type DriveFileForTree = Omit<
> & {
uuid: string;
plainName: string;
+ isFolder: boolean;
};
export type DriveFolderForTree = Omit<
@@ -306,6 +307,7 @@ export type DriveFolderForTree = Omit<
folderUuid?: string;
plainName: string;
status: DriveFileForTree['status'];
+ isFolder: boolean;
};
export interface DownloadedThumbnail {
diff --git a/src/useCases/drive/loadSharedItems.ts b/src/useCases/drive/loadSharedItems.ts
index 3406bf478..a72fbf765 100644
--- a/src/useCases/drive/loadSharedItems.ts
+++ b/src/useCases/drive/loadSharedItems.ts
@@ -3,6 +3,7 @@ import { ListShareLinksItem, SharedFiles, SharedFolders } from '@internxt/sdk/di
import { DriveFileData, DriveFolderData } from '@internxt/sdk/dist/drive/storage/types';
import { UseCaseResult } from '../../types';
import errorService from '../../services/ErrorService';
+import { mapSharedFile, mapSharedFolder } from '../../helpers/driveItemMappers';
const ITEMS_PER_PAGE = 50;
@@ -42,10 +43,10 @@ export const getSharedItems = async ({
shouldGetFiles ? drive.share.getSharedFiles({ page, perPage: ITEMS_PER_PAGE }) : null,
]);
- const sharedFoldersList = sharedFolders?.folders ?? [];
- const sharedFilesList = sharedFiles?.files ?? [];
+ const sharedFoldersList = (sharedFolders?.folders ?? []).map(mapSharedFolder);
+ const sharedFilesList = (sharedFiles?.files ?? []).map(mapSharedFile);
- const sharedItems = [...sharedFoldersList, ...sharedFilesList] as (SharedFiles & SharedFolders)[];
+ const sharedItems = [...sharedFoldersList, ...sharedFilesList] as (SharedFiles & SharedFolders & { isFolder: boolean })[];
return {
success: true,
diff --git a/src/useCases/drive/trash.ts b/src/useCases/drive/trash.ts
index 76835d20c..366a49c65 100644
--- a/src/useCases/drive/trash.ts
+++ b/src/useCases/drive/trash.ts
@@ -7,6 +7,7 @@ import { notifications } from '@internxt-mobile/services/NotificationsService';
import { DriveEventKey, DriveItemStatus, DriveListItem } from '@internxt-mobile/types/drive';
import { NotificationType, UseCaseResult } from '@internxt-mobile/types/index';
import strings from 'assets/lang/strings';
+import { checkIsFolder } from '../../helpers';
type GetDriveTrashItemsOptions = {
page: number;
@@ -36,7 +37,7 @@ export const getDriveTrashItems = async ({
]);
const trashItems = trashFolders.items.concat(trashFiles.items).map((trashItem) => {
- const isFolder = !trashItem.fileId ? true : false;
+ const isFolder = checkIsFolder(trashItem);
return {
status: DriveItemStatus.Idle,
diff --git a/yarn.lock b/yarn.lock
index aba68fd40..fc7eefe66 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3668,6 +3668,11 @@
resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.10.tgz#a1337ca426aa61cef9fe15b5b28e340a72f6fa99"
integrity sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==
+"@yarnpkg/lockfile@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
+ integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==
+
abab@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291"
@@ -4614,7 +4619,7 @@ cacache@^15.3.0:
tar "^6.0.2"
unique-filename "^1.1.1"
-call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2:
+call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6"
integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==
@@ -4633,6 +4638,24 @@ call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7:
get-intrinsic "^1.2.4"
set-function-length "^1.2.1"
+call-bind@^1.0.8:
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c"
+ integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==
+ dependencies:
+ call-bind-apply-helpers "^1.0.0"
+ es-define-property "^1.0.0"
+ get-intrinsic "^1.2.4"
+ set-function-length "^1.2.2"
+
+call-bound@^1.0.3, call-bound@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a"
+ integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==
+ dependencies:
+ call-bind-apply-helpers "^1.0.2"
+ get-intrinsic "^1.3.0"
+
caller-callsite@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134"
@@ -4814,7 +4837,7 @@ ci-info@^2.0.0:
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==
-ci-info@^3.2.0, ci-info@^3.3.0:
+ci-info@^3.2.0, ci-info@^3.3.0, ci-info@^3.7.0:
version "3.9.0"
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4"
integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==
@@ -5171,7 +5194,7 @@ create-ecdh@^4.0.0:
bn.js "^4.1.0"
elliptic "^6.5.3"
-create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0:
+create-hash@^1.1.0, create-hash@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196"
integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==
@@ -5182,7 +5205,7 @@ create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0:
ripemd160 "^2.0.1"
sha.js "^2.4.0"
-create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4, create-hmac@^1.1.7:
+create-hmac@^1.1.0, create-hmac@^1.1.7:
version "1.1.7"
resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff"
integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==
@@ -6781,7 +6804,7 @@ find-up@^5.0.0, find-up@~5.0.0:
locate-path "^6.0.0"
path-exists "^4.0.0"
-find-yarn-workspace-root@~2.0.0:
+find-yarn-workspace-root@^2.0.0, find-yarn-workspace-root@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd"
integrity sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==
@@ -6839,6 +6862,13 @@ for-each@^0.3.3:
dependencies:
is-callable "^1.1.3"
+for-each@^0.3.5:
+ version "0.3.5"
+ resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47"
+ integrity sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==
+ dependencies:
+ is-callable "^1.2.7"
+
for-in@^0.1.3:
version "0.1.8"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1"
@@ -6944,6 +6974,15 @@ fs-extra@9.0.0:
jsonfile "^6.0.1"
universalify "^1.0.0"
+fs-extra@^10.0.0:
+ version "10.1.0"
+ resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf"
+ integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==
+ dependencies:
+ graceful-fs "^4.2.0"
+ jsonfile "^6.0.1"
+ universalify "^2.0.0"
+
fs-extra@^4.0.2, fs-extra@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94"
@@ -7035,7 +7074,7 @@ get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@
has-symbols "^1.0.3"
hasown "^2.0.0"
-get-intrinsic@^1.2.6:
+get-intrinsic@^1.2.6, get-intrinsic@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01"
integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==
@@ -7354,6 +7393,16 @@ hash-base@^3.0.0:
readable-stream "^3.6.0"
safe-buffer "^5.2.0"
+hash-base@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.2.tgz#79d72def7611c3f6e3c3b5730652638001b10a74"
+ integrity sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg==
+ dependencies:
+ inherits "^2.0.4"
+ readable-stream "^2.3.8"
+ safe-buffer "^5.2.1"
+ to-buffer "^1.2.1"
+
hash-base@~3.0:
version "3.0.4"
resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918"
@@ -7948,6 +7997,13 @@ is-typed-array@^1.1.13:
dependencies:
which-typed-array "^1.1.14"
+is-typed-array@^1.1.14:
+ version "1.1.15"
+ resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.15.tgz#4bfb4a45b61cee83a5a46fba778e4e8d59c0ce0b"
+ integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==
+ dependencies:
+ which-typed-array "^1.1.16"
+
is-typedarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
@@ -8670,6 +8726,17 @@ json-stable-stringify-without-jsonify@^1.0.1:
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
+json-stable-stringify@^1.0.2:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz#8903cfac42ea1a0f97f35d63a4ce0518f0cc6a70"
+ integrity sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==
+ dependencies:
+ call-bind "^1.0.8"
+ call-bound "^1.0.4"
+ isarray "^2.0.5"
+ jsonify "^0.0.1"
+ object-keys "^1.1.1"
+
json-stringify-safe@~5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
@@ -8696,6 +8763,11 @@ jsonfile@^6.0.1:
optionalDependencies:
graceful-fs "^4.1.6"
+jsonify@^0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.1.tgz#2aa3111dae3d34a0f151c63f3a45d995d9420978"
+ integrity sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==
+
jsprim@^1.2.2:
version "1.4.2"
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb"
@@ -8737,6 +8809,13 @@ kind-of@^6.0.2, kind-of@^6.0.3:
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
+klaw-sync@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/klaw-sync/-/klaw-sync-6.0.0.tgz#1fd2cfd56ebb6250181114f0a581167099c2b28c"
+ integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==
+ dependencies:
+ graceful-fs "^4.1.11"
+
kleur@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
@@ -10001,7 +10080,7 @@ open@^6.2.0:
dependencies:
is-wsl "^1.1.0"
-open@^7.0.3:
+open@^7.0.3, open@^7.4.2:
version "7.4.2"
resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321"
integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==
@@ -10232,6 +10311,26 @@ password-prompt@^1.0.4:
ansi-escapes "^4.3.2"
cross-spawn "^7.0.3"
+patch-package@^8.0.1:
+ version "8.0.1"
+ resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-8.0.1.tgz#79d02f953f711e06d1f8949c8a13e5d3d7ba1a60"
+ integrity sha512-VsKRIA8f5uqHQ7NGhwIna6Bx6D9s/1iXlA1hthBVBEbkq+t4kXD0HHt+rJhf/Z+Ci0F/HCB2hvn0qLdLG+Qxlw==
+ dependencies:
+ "@yarnpkg/lockfile" "^1.1.0"
+ chalk "^4.1.2"
+ ci-info "^3.7.0"
+ cross-spawn "^7.0.3"
+ find-yarn-workspace-root "^2.0.0"
+ fs-extra "^10.0.0"
+ json-stable-stringify "^1.0.2"
+ klaw-sync "^6.0.0"
+ minimist "^1.2.6"
+ open "^7.4.2"
+ semver "^7.5.3"
+ slash "^2.0.0"
+ tmp "^0.2.4"
+ yaml "^2.2.2"
+
path-browserify@0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a"
@@ -10300,23 +10399,17 @@ path@^0.12.7:
process "^0.11.1"
util "^0.10.3"
-pbkdf2@3.0.8:
- version "3.0.8"
- resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.8.tgz#2f8abf16ebecc82277945d748aba1d78761f61e2"
- integrity sha512-Bf7yBd61ChnMqPqf+PxHm34Iiq9M9Bkd/+JqzosPOqwG6FiTixtkpCs4PNd38+6/VYRvAxGe/GgPb4Q4GktFzg==
+pbkdf2@3.0.8, pbkdf2@^3.1.2, pbkdf2@^3.1.3:
+ version "3.1.5"
+ resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.5.tgz#444a59d7a259a95536c56e80c89de31cc01ed366"
+ integrity sha512-Q3CG/cYvCO1ye4QKkuH7EXxs3VC/rI1/trd+qX2+PolbaKG0H+bgcZzrTt96mMyRtejk+JMCiLUn3y29W8qmFQ==
dependencies:
- create-hmac "^1.1.2"
-
-pbkdf2@^3.1.2:
- version "3.1.2"
- resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075"
- integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==
- dependencies:
- create-hash "^1.1.2"
- create-hmac "^1.1.4"
- ripemd160 "^2.0.1"
- safe-buffer "^5.0.1"
- sha.js "^2.4.8"
+ create-hash "^1.2.0"
+ create-hmac "^1.1.7"
+ ripemd160 "^2.0.3"
+ safe-buffer "^5.2.1"
+ sha.js "^2.4.12"
+ to-buffer "^1.2.1"
performance-now@^2.1.0:
version "2.1.0"
@@ -10458,6 +10551,11 @@ postcss@^8.2.15, postcss@^8.4.23, postcss@~8.4.32:
picocolors "^1.1.0"
source-map-js "^1.2.1"
+postinstall-postinstall@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz#4f7f77441ef539d1512c40bd04c71b06a4704ca3"
+ integrity sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ==
+
prebuild-install@^7.0.1:
version "7.1.2"
resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.2.tgz#a5fd9986f5a6251fbc47e1e5c65de71e68c0a056"
@@ -11619,6 +11717,14 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
hash-base "^3.0.0"
inherits "^2.0.1"
+ripemd160@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.3.tgz#9be54e4ba5e3559c8eee06a25cd7648bbccdf5a8"
+ integrity sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==
+ dependencies:
+ hash-base "^3.1.2"
+ inherits "^2.0.4"
+
rn-fetch-blob@=0.11.2:
version "0.11.2"
resolved "https://registry.yarnpkg.com/rn-fetch-blob/-/rn-fetch-blob-0.11.2.tgz#bdc483bf1b0c3810d457983494a11fbada446679"
@@ -11826,7 +11932,7 @@ set-blocking@^2.0.0:
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==
-set-function-length@^1.2.1:
+set-function-length@^1.2.1, set-function-length@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449"
integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==
@@ -11866,6 +11972,15 @@ sha.js@^2.4.0, sha.js@^2.4.8:
inherits "^2.0.1"
safe-buffer "^5.0.1"
+sha.js@^2.4.12:
+ version "2.4.12"
+ resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.12.tgz#eb8b568bf383dfd1867a32c3f2b74eb52bdbf23f"
+ integrity sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==
+ dependencies:
+ inherits "^2.0.4"
+ safe-buffer "^5.2.1"
+ to-buffer "^1.2.0"
+
shallow-clone@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-0.1.2.tgz#5909e874ba77106d73ac414cfec1ffca87d97060"
@@ -11991,6 +12106,11 @@ slash@^1.0.0:
resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
integrity sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==
+slash@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44"
+ integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==
+
slash@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
@@ -12745,6 +12865,11 @@ tmp@^0.0.33:
dependencies:
os-tmpdir "~1.0.2"
+tmp@^0.2.4:
+ version "0.2.5"
+ resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.5.tgz#b06bcd23f0f3c8357b426891726d16015abfd8f8"
+ integrity sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==
+
tmpl@1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
@@ -12755,6 +12880,15 @@ to-arraybuffer@^1.0.0:
resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
integrity sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA==
+to-buffer@^1.2.0, to-buffer@^1.2.1:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.2.2.tgz#ffe59ef7522ada0a2d1cb5dfe03bb8abc3cdc133"
+ integrity sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==
+ dependencies:
+ isarray "^2.0.5"
+ safe-buffer "^5.2.1"
+ typed-array-buffer "^1.0.3"
+
to-fast-properties@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
@@ -12977,6 +13111,15 @@ typed-array-buffer@^1.0.2:
es-errors "^1.3.0"
is-typed-array "^1.1.13"
+typed-array-buffer@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz#a72395450a4869ec033fd549371b47af3a2ee536"
+ integrity sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==
+ dependencies:
+ call-bound "^1.0.3"
+ es-errors "^1.3.0"
+ is-typed-array "^1.1.14"
+
typed-array-byte-length@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67"
@@ -13426,6 +13569,19 @@ which-typed-array@^1.1.14, which-typed-array@^1.1.15:
gopd "^1.0.1"
has-tostringtag "^1.0.2"
+which-typed-array@^1.1.16:
+ version "1.1.19"
+ resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.19.tgz#df03842e870b6b88e117524a4b364b6fc689f956"
+ integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==
+ dependencies:
+ available-typed-arrays "^1.0.7"
+ call-bind "^1.0.8"
+ call-bound "^1.0.4"
+ for-each "^0.3.5"
+ get-proto "^1.0.1"
+ gopd "^1.2.0"
+ has-tostringtag "^1.0.2"
+
which@^1.2.9, which@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
@@ -13640,6 +13796,11 @@ yaml@^2.2.1, yaml@^2.3.4:
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.5.1.tgz#c9772aacf62cb7494a95b0c4f1fb065b563db130"
integrity sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==
+yaml@^2.2.2:
+ version "2.8.1"
+ resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.1.tgz#1870aa02b631f7e8328b93f8bc574fac5d6c4d79"
+ integrity sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==
+
yargs-parser@^18.1.2, yargs-parser@^18.1.3:
version "18.1.3"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"