From b82bf165059e6737768430902b3514dd28688d9a Mon Sep 17 00:00:00 2001 From: javad mnjd Date: Sat, 21 Jan 2023 06:11:37 -0800 Subject: [PATCH] Android: Resolves #6942: Improve filesystem sync performance (#7637) --- .../react-native-saf-x/android/build.gradle | 6 +- .../java/com/reactnativesafx/SafXModule.java | 215 +--- .../java/com/reactnativesafx/SafXPackage.java | 24 +- .../java/com/reactnativesafx/utils/Async.java | 31 + .../reactnativesafx/utils/DocumentHelper.java | 585 --------- .../reactnativesafx/utils/DocumentStat.java | 132 ++ .../utils/EfficientDocumentHelper.java | 1063 ++++++++++++++++ .../reactnativesafx/utils/GeneralHelper.java | 3 +- .../com/reactnativesafx/utils/UriHelper.java | 93 +- .../utils/exceptions/ExceptionFast.java | 12 + .../exceptions/FileNotFoundExceptionFast.java | 18 + .../utils/exceptions/IOExceptionFast.java | 14 + .../IllegalArgumentExceptionFast.java | 12 + .../exceptions/RenameFailedException.java | 48 + .../exceptions/SecurityExceptionFast.java | 12 + .../android/wrapper/gradle-wrapper.properties | 2 +- packages/react-native-saf-x/package.json | 13 +- packages/react-native-saf-x/src/index.ts | 8 +- yarn.lock | 1073 +---------------- 19 files changed, 1502 insertions(+), 1862 deletions(-) create mode 100644 packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/Async.java delete mode 100644 packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/DocumentHelper.java create mode 100644 packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/DocumentStat.java create mode 100644 packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/EfficientDocumentHelper.java create mode 100644 packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/exceptions/ExceptionFast.java create mode 100644 packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/exceptions/FileNotFoundExceptionFast.java create mode 100644 packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/exceptions/IOExceptionFast.java create mode 100644 packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/exceptions/IllegalArgumentExceptionFast.java create mode 100644 packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/exceptions/RenameFailedException.java create mode 100644 packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/exceptions/SecurityExceptionFast.java diff --git a/packages/react-native-saf-x/android/build.gradle b/packages/react-native-saf-x/android/build.gradle index 6c074a3d3..e0031ec86 100644 --- a/packages/react-native-saf-x/android/build.gradle +++ b/packages/react-native-saf-x/android/build.gradle @@ -3,11 +3,12 @@ buildscript { repositories { google() mavenCentral() - jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:4.2.2' + classpath("com.android.tools.build:gradle:7.2.1") + classpath("com.facebook.react:react-native-gradle-plugin") + classpath("de.undercouch:gradle-download-task:5.0.1") } } } @@ -50,7 +51,6 @@ repositories { } google() mavenCentral() - jcenter() } dependencies { diff --git a/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/SafXModule.java b/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/SafXModule.java index 7dff2b23b..70e664ae0 100644 --- a/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/SafXModule.java +++ b/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/SafXModule.java @@ -6,7 +6,6 @@ import android.os.Build.VERSION_CODES; import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; -import androidx.documentfile.provider.DocumentFile; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Promise; @@ -14,28 +13,19 @@ import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.WritableArray; -import com.facebook.react.bridge.WritableMap; import com.facebook.react.module.annotations.ReactModule; -import com.reactnativesafx.utils.DocumentHelper; -import com.reactnativesafx.utils.GeneralHelper; +import com.reactnativesafx.utils.EfficientDocumentHelper; import com.reactnativesafx.utils.UriHelper; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.OutputStream; -import java.util.Arrays; -import java.util.regex.Pattern; - @RequiresApi(api = VERSION_CODES.Q) @ReactModule(name = SafXModule.NAME) public class SafXModule extends ReactContextBaseJavaModule { public static final String NAME = "SafX"; - public static Pattern trailingSlash = Pattern.compile("[/\\\\]$"); - private final DocumentHelper documentHelper; + private final EfficientDocumentHelper efficientDocumentHelper; public SafXModule(ReactApplicationContext reactContext) { super(reactContext); - this.documentHelper = new DocumentHelper(reactContext); + this.efficientDocumentHelper = new EfficientDocumentHelper(reactContext); } @Override @@ -46,183 +36,84 @@ public class SafXModule extends ReactContextBaseJavaModule { @ReactMethod public void openDocumentTree(final boolean persist, final Promise promise) { - this.documentHelper.openDocumentTree(persist, promise); + this.efficientDocumentHelper.openDocumentTree(persist, promise); } @ReactMethod public void openDocument(final boolean persist, final boolean multiple, final Promise promise) { - this.documentHelper.openDocument(persist, multiple, promise); + this.efficientDocumentHelper.openDocument(persist, multiple, promise); } @ReactMethod public void createDocument( - final String data, - final String encoding, - final String initialName, - final String mimeType, - final Promise promise) { - this.documentHelper.createDocument(data, encoding, initialName, mimeType, promise); + final String data, + final String encoding, + final String initialName, + final String mimeType, + final Promise promise) { + this.efficientDocumentHelper.createDocument(data, encoding, initialName, mimeType, promise); } @ReactMethod public void hasPermission(String uriString, final Promise promise) { - if (this.documentHelper.hasPermission(uriString)) { - promise.resolve(true); - } else { - promise.resolve(false); - } + this.efficientDocumentHelper.hasPermission(uriString, promise); } @ReactMethod public void exists(String uriString, final Promise promise) { - try { - promise.resolve(this.documentHelper.exists(uriString)); - } catch (SecurityException e) { - promise.reject("EPERM", e.getLocalizedMessage()); - } catch (Exception e) { - promise.reject("ERROR", e.getLocalizedMessage()); - } + this.efficientDocumentHelper.exists(uriString, promise); } @ReactMethod public void readFile(String uriString, String encoding, final Promise promise) { - try { - DocumentFile file; - - try { - file = this.documentHelper.goToDocument(uriString, false, true); - } catch (FileNotFoundException e) { - promise.reject("ENOENT", "'" + uriString + "' does not exist"); - return; - } - if (encoding != null) { - if (encoding.equals("ascii")) { - WritableArray arr = - (WritableArray) this.documentHelper.readFromUri(file.getUri(), encoding); - promise.resolve((arr)); - } else { - promise.resolve(this.documentHelper.readFromUri(file.getUri(), encoding)); - } - } else { - promise.resolve(this.documentHelper.readFromUri(file.getUri(), "utf8")); - } - } catch (Exception e) { - promise.reject("EUNSPECIFIED", e.getLocalizedMessage()); - } + this.efficientDocumentHelper.readFile(uriString, encoding, promise); } @ReactMethod public void writeFile( - String uriString, - String data, - String encoding, - String mimeType, - boolean append, - final Promise promise) { - try { - DocumentFile file; - - try { - file = this.documentHelper.goToDocument(uriString, false, true); - } catch (FileNotFoundException e) { - file = this.documentHelper.createFile(uriString, mimeType); - } - - byte[] bytes = GeneralHelper.stringToBytes(data, encoding); - - try (OutputStream fout = - this.getReactApplicationContext() - .getContentResolver() - .openOutputStream(file.getUri(), append ? "wa" : "wt")) { - fout.write(bytes); - } - - promise.resolve(uriString); - } catch (Exception e) { - promise.reject("EUNSPECIFIED", e.getLocalizedMessage()); - } + String uriString, + String data, + String encoding, + String mimeType, + boolean append, + final Promise promise) { + this.efficientDocumentHelper.writeFile( + uriString, data, encoding, mimeType, append, promise + ); } @ReactMethod public void transferFile( - String srcUri, String destUri, boolean replaceIfDestExists, boolean copy, Promise promise) { - this.documentHelper.transferFile(srcUri, destUri, replaceIfDestExists, copy, promise); + String srcUri, String destUri, boolean replaceIfDestExists, boolean copy, Promise promise) { + this.efficientDocumentHelper.transferFile(srcUri, destUri, replaceIfDestExists, copy, promise); } @ReactMethod public void rename(String uriString, String newName, final Promise promise) { - try { - - DocumentFile doc; - try { - doc = this.documentHelper.goToDocument(uriString, false, true); - } catch (FileNotFoundException e) { - promise.reject("ENOENT", "'" + uriString + "' does not exist"); - return; - } - - if (doc.renameTo(newName)) { - promise.resolve(true); - } else { - promise.resolve(false); - } - } catch (Exception e) { - promise.reject("EUNSPECIFIED", e.getLocalizedMessage()); - } + this.efficientDocumentHelper.renameTo(uriString, newName, promise); } @ReactMethod public void unlink(String uriString, final Promise promise) { - try { - DocumentFile doc = this.documentHelper.goToDocument(uriString, false, true); - boolean result = doc.delete(); - if (!result) { - throw new Exception("Failed to unlink file. Unknown error."); - } - promise.resolve(true); - } catch (FileNotFoundException e) { - promise.resolve(true); - } catch (SecurityException e) { - promise.reject("EPERM", e.getLocalizedMessage()); - } catch (Exception e) { - promise.reject("EUNSPECIFIED", e.getLocalizedMessage()); - } + this.efficientDocumentHelper.unlink(uriString, promise); } @ReactMethod public void mkdir(String uriString, final Promise promise) { - try { - DocumentFile dir = this.documentHelper.mkdir(uriString); - DocumentHelper.resolveWithDocument(dir, uriString, promise); - } catch (IOException e) { - promise.reject("EEXIST", e.getLocalizedMessage()); - } catch (SecurityException e) { - promise.reject("EPERM", e.getLocalizedMessage()); - } catch (Exception e) { - promise.reject("EUNSPECIFIED", e.getLocalizedMessage()); - } + this.efficientDocumentHelper.mkdir(uriString, promise); } @ReactMethod public void createFile(String uriString, String mimeType, final Promise promise) { - try { - DocumentFile createdFile = this.documentHelper.createFile(uriString, mimeType); - DocumentHelper.resolveWithDocument(createdFile, uriString, promise); - } catch (IOException e) { - promise.reject("EEXIST", e.getLocalizedMessage()); - } catch (SecurityException e) { - promise.reject("EPERM", e.getLocalizedMessage()); - } catch (Exception e) { - promise.reject("EUNSPECIFIED", e.getLocalizedMessage()); - } + this.efficientDocumentHelper.createFile(uriString, mimeType, promise); } @ReactMethod public void getPersistedUriPermissions(final Promise promise) { String[] uriList = - getReactApplicationContext().getContentResolver().getPersistedUriPermissions().stream() - .map(uriPermission -> uriPermission.getUri().toString()) - .toArray(String[]::new); + getReactApplicationContext().getContentResolver().getPersistedUriPermissions().stream() + .map(uriPermission -> uriPermission.getUri().toString()) + .toArray(String[]::new); WritableArray wa = Arguments.fromArray(uriList); promise.resolve(wa); @@ -232,52 +123,20 @@ public class SafXModule extends ReactContextBaseJavaModule { public void releasePersistableUriPermission(String uriString, final Promise promise) { Uri uriToRevoke = Uri.parse(UriHelper.normalize(uriString)); final int takeFlags = - (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); this.getReactApplicationContext() - .getContentResolver() - .releasePersistableUriPermission(uriToRevoke, takeFlags); + .getContentResolver() + .releasePersistableUriPermission(uriToRevoke, takeFlags); promise.resolve(null); } @ReactMethod public void listFiles(String uriString, final Promise promise) { - try { - DocumentFile doc = this.documentHelper.goToDocument(uriString, false, true); - - WritableMap[] resolvedDocs = - Arrays.stream(doc.listFiles()) - .map( - docEntry -> - DocumentHelper.resolveWithDocument( - docEntry, - trailingSlash.matcher(uriString).replaceFirst("") - + "/" - + docEntry.getName(), - null)) - .toArray(WritableMap[]::new); - WritableArray resolveData = Arguments.fromJavaArgs(resolvedDocs); - promise.resolve(resolveData); - } catch (FileNotFoundException e) { - promise.reject("ENOENT", e.getLocalizedMessage()); - } catch (SecurityException e) { - promise.reject("EPERM", e.getLocalizedMessage()); - } catch (Exception e) { - promise.reject("EUNSPECIFIED", e.getLocalizedMessage()); - } + efficientDocumentHelper.listFiles(uriString, promise); } @ReactMethod public void stat(String uriString, final Promise promise) { - try { - DocumentFile doc = this.documentHelper.goToDocument(uriString, false, true); - - DocumentHelper.resolveWithDocument(doc, uriString, promise); - } catch (FileNotFoundException e) { - promise.reject("ENOENT", e.getLocalizedMessage()); - } catch (SecurityException e) { - promise.reject("EPERM", e.getLocalizedMessage()); - } catch (Exception e) { - promise.reject("EUNSPECIFIED", e.getLocalizedMessage()); - } + efficientDocumentHelper.stat(uriString, promise); } } diff --git a/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/SafXPackage.java b/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/SafXPackage.java index c0d363350..142e6664c 100644 --- a/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/SafXPackage.java +++ b/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/SafXPackage.java @@ -12,17 +12,17 @@ import java.util.Collections; import java.util.List; public class SafXPackage implements ReactPackage { - @NonNull - @Override - public List createNativeModules(@NonNull ReactApplicationContext reactContext) { - List modules = new ArrayList<>(); - modules.add(new SafXModule(reactContext)); - return modules; - } + @NonNull + @Override + public List createNativeModules(@NonNull ReactApplicationContext reactContext) { + List modules = new ArrayList<>(); + modules.add(new SafXModule(reactContext)); + return modules; + } - @NonNull - @Override - public List createViewManagers(@NonNull ReactApplicationContext reactContext) { - return Collections.emptyList(); - } + @NonNull + @Override + public List createViewManagers(@NonNull ReactApplicationContext reactContext) { + return Collections.emptyList(); + } } diff --git a/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/Async.java b/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/Async.java new file mode 100644 index 000000000..3d4cd730c --- /dev/null +++ b/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/Async.java @@ -0,0 +1,31 @@ +package com.reactnativesafx.utils; + +import android.os.Handler; +import android.os.Looper; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +// from https://stackoverflow.com/a/73666782/3542461 +public class Async { + + private static final ExecutorService executorService = Executors.newCachedThreadPool(); + + private static final Handler handler = new Handler(Looper.getMainLooper()); + + public static void execute(Task task) { + executorService.execute(() -> { + T t = task.doAsync(); + handler.post(() -> { + task.doSync(t); + }); + }); + } + + public interface Task { + T doAsync(); + + void doSync(T t); + } + +} diff --git a/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/DocumentHelper.java b/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/DocumentHelper.java deleted file mode 100644 index 8612f391a..000000000 --- a/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/DocumentHelper.java +++ /dev/null @@ -1,585 +0,0 @@ -package com.reactnativesafx.utils; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.content.ClipData; -import android.content.ContentResolver; -import android.content.Intent; -import android.content.UriPermission; -import android.net.Uri; -import android.os.Build; -import android.os.Build.VERSION_CODES; -import android.util.Base64; - -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; -import androidx.documentfile.provider.DocumentFile; -import androidx.documentfile.provider.DocumentFileHelper; - -import com.facebook.react.bridge.ActivityEventListener; -import com.facebook.react.bridge.Arguments; -import com.facebook.react.bridge.Promise; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.WritableArray; -import com.facebook.react.bridge.WritableMap; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.List; - -@RequiresApi(api = VERSION_CODES.Q) -public class DocumentHelper { - - private static final int DOCUMENT_TREE_REQUEST_CODE = 1; - private static final int DOCUMENT_REQUEST_CODE = 2; - private static final int DOCUMENT_CREATE_CODE = 3; - - private final ReactApplicationContext context; - private ActivityEventListener activityEventListener; - - public DocumentHelper(ReactApplicationContext context) { - this.context = context; - } - - public void openDocumentTree(final boolean persist, final Promise promise) { - try { - - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_OPEN_DOCUMENT_TREE); - - if (activityEventListener != null) { - context.removeActivityEventListener(activityEventListener); - activityEventListener = null; - } - - activityEventListener = - new ActivityEventListener() { - @SuppressLint("WrongConstant") - @Override - public void onActivityResult( - Activity activity, int requestCode, int resultCode, Intent intent) { - if (requestCode == DOCUMENT_TREE_REQUEST_CODE && resultCode == Activity.RESULT_OK) { - if (intent != null) { - Uri uri = intent.getData(); - if (persist) { - final int takeFlags = - intent.getFlags() - & (Intent.FLAG_GRANT_READ_URI_PERMISSION - | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - - context.getContentResolver().takePersistableUriPermission(uri, takeFlags); - } - - try { - DocumentFile doc = goToDocument(uri.toString(), false); - resolveWithDocument(doc, uri.toString(), promise); - } catch (Exception e) { - promise.reject("EUNSPECIFIED", e.getLocalizedMessage()); - } - } else { - promise.resolve(null); - } - } else { - promise.resolve(null); - } - context.removeActivityEventListener(activityEventListener); - activityEventListener = null; - } - - @Override - public void onNewIntent(Intent intent) {} - }; - - context.addActivityEventListener(activityEventListener); - - Activity activity = context.getCurrentActivity(); - if (activity != null) { - activity.startActivityForResult(intent, DOCUMENT_TREE_REQUEST_CODE); - } else { - promise.reject("ERROR", "Cannot get current activity, so cannot launch document picker"); - } - - } catch (Exception e) { - promise.reject("ERROR", e.getLocalizedMessage()); - } - } - - public void openDocument(final boolean persist, final boolean multiple, final Promise promise) { - try { - - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_OPEN_DOCUMENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); - if (multiple) { - intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); - } - intent.setType("*/*"); - - if (activityEventListener != null) { - context.removeActivityEventListener(activityEventListener); - activityEventListener = null; - } - - activityEventListener = - new ActivityEventListener() { - @SuppressLint("WrongConstant") - @Override - public void onActivityResult( - Activity activity, int requestCode, int resultCode, Intent intent) { - try { - WritableArray resolvedDocs = Arguments.createArray(); - if (requestCode == DOCUMENT_REQUEST_CODE - && resultCode == Activity.RESULT_OK - && intent != null) { - Uri uri = intent.getData(); - - if (uri != null) { - if (persist) { - final int takeFlags = - intent.getFlags() - & (Intent.FLAG_GRANT_READ_URI_PERMISSION - | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - - context.getContentResolver().takePersistableUriPermission(uri, takeFlags); - } - - DocumentFile doc = goToDocument(uri.toString(), false); - WritableMap docInfo = resolveWithDocument(doc, uri.toString(), null); - resolvedDocs.pushMap(docInfo); - } else if (multiple) { - ClipData clipData = intent.getClipData(); - if (clipData != null) { - for (int i = 0; i < clipData.getItemCount(); ++i) { - ClipData.Item item = clipData.getItemAt(i); - Uri clipUri = item.getUri(); - DocumentFile doc = goToDocument(clipUri.toString(), false); - WritableMap docInfo = resolveWithDocument(doc, clipUri.toString(), null); - resolvedDocs.pushMap(docInfo); - } - } else { - throw new Exception("Unexpected Error: ClipData was null"); - } - } else { - throw new Exception( - "Unexpected Error: Could not retrieve information about selected documents"); - } - } else { - promise.resolve(null); - return; - } - promise.resolve(resolvedDocs); - } catch (Exception e) { - promise.reject("EUNSPECIFIED", e.getLocalizedMessage()); - } finally { - context.removeActivityEventListener(activityEventListener); - activityEventListener = null; - } - } - - @Override - public void onNewIntent(Intent intent) {} - }; - - context.addActivityEventListener(activityEventListener); - - Activity activity = context.getCurrentActivity(); - if (activity != null) { - activity.startActivityForResult(intent, DOCUMENT_REQUEST_CODE); - } else { - promise.reject( - "EUNSPECIFIED", "Cannot get current activity, so cannot launch document picker"); - } - - } catch (Exception e) { - promise.reject("EUNSPECIFIED", e.getLocalizedMessage()); - } - } - - public void createDocument( - final String data, - final String encoding, - final String initialName, - final String mimeType, - final Promise promise) { - try { - - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_CREATE_DOCUMENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); - if (initialName != null) { - intent.putExtra(Intent.EXTRA_TITLE, initialName); - } - if (mimeType != null) { - intent.setType(mimeType); - } else { - intent.setType("*/*"); - } - - if (activityEventListener != null) { - context.removeActivityEventListener(activityEventListener); - activityEventListener = null; - } - - activityEventListener = - new ActivityEventListener() { - @Override - public void onActivityResult( - Activity activity, int requestCode, int resultCode, Intent intent) { - - if (requestCode == DOCUMENT_CREATE_CODE && resultCode == Activity.RESULT_OK) { - if (intent != null) { - Uri uri = intent.getData(); - - DocumentFile doc = DocumentFile.fromSingleUri(context, uri); - - try { - byte[] bytes = GeneralHelper.stringToBytes(data, encoding); - try (OutputStream os = context.getContentResolver().openOutputStream(uri)) { - os.write(bytes); - } - assert doc != null; - resolveWithDocument(doc, uri.toString(), promise); - } catch (Exception e) { - promise.reject("ERROR", e.getLocalizedMessage()); - } - } else { - promise.resolve(null); - } - } else { - promise.resolve(null); - } - - context.removeActivityEventListener(activityEventListener); - activityEventListener = null; - } - - @Override - public void onNewIntent(Intent intent) {} - }; - - context.addActivityEventListener(activityEventListener); - - Activity activity = context.getCurrentActivity(); - if (activity != null) { - activity.startActivityForResult(intent, DOCUMENT_CREATE_CODE); - } else { - promise.reject("ERROR", "Cannot get current activity, so cannot launch document picker"); - } - } catch (Exception e) { - promise.reject("ERROR", e.getLocalizedMessage()); - } - } - - @RequiresApi(api = Build.VERSION_CODES.Q) - @SuppressWarnings({"UnusedDeclaration", "UnusedAssignment"}) - public Object readFromUri(Uri uri, String encoding) throws IOException { - byte[] bytes; - int bytesRead; - int length; - - InputStream inputStream = context.getContentResolver().openInputStream(uri); - - length = inputStream.available(); - bytes = new byte[length]; - bytesRead = inputStream.read(bytes); - inputStream.close(); - - switch (encoding.toLowerCase()) { - case "base64": - return Base64.encodeToString(bytes, Base64.NO_WRAP); - case "ascii": - WritableArray asciiResult = Arguments.createArray(); - for (byte b : bytes) { - asciiResult.pushInt(b); - } - return asciiResult; - case "utf8": - default: - return new String(bytes); - } - } - - public boolean exists(final String uriString) throws SecurityException { - return this.exists(uriString, false); - } - - public boolean exists(final String uriString, final boolean shouldBeFile) - throws SecurityException { - try { - DocumentFile fileOrFolder = goToDocument(uriString, false); - if (shouldBeFile) { - return !fileOrFolder.isDirectory(); - } - return true; - } catch (IOException e) { - return false; - } - } - - public boolean hasPermission(String uriString) { - // list of all persisted permissions for our app - List uriList = context.getContentResolver().getPersistedUriPermissions(); - for (UriPermission uriPermission : uriList) { - if (permissionMatchesAndHasAccess(uriPermission, UriHelper.normalize(uriString))) { - return true; - } - } - return false; - } - - public boolean permissionMatchesAndHasAccess( - UriPermission permission, String normalizedUriString) { - String permittedUri = permission.getUri().toString(); - return (permittedUri.startsWith(normalizedUriString) - || normalizedUriString.startsWith(permittedUri)) - && permission.isReadPermission() - && permission.isWritePermission(); - } - - public static WritableMap resolveWithDocument( - @NonNull DocumentFile file, String SimplifiedUri, Promise promise) { - WritableMap fileMap = Arguments.createMap(); - fileMap.putString("uri", UriHelper.denormalize(SimplifiedUri)); - fileMap.putString("name", file.getName()); - fileMap.putString("type", file.isDirectory() ? "directory" : "file"); - if (file.isFile()) { - fileMap.putString("mime", file.getType()); - fileMap.putDouble("size", file.length()); - } - fileMap.putDouble("lastModified", file.lastModified()); - - if (promise != null) { - promise.resolve(fileMap); - } - return fileMap; - } - - public DocumentFile mkdir(String uriString) - throws IOException, SecurityException, IllegalArgumentException { - return this.mkdir(uriString, true); - } - - /** - * @return a DocumentFile that is created using DocumentFile.fromTreeUri() - */ - public DocumentFile mkdir(String uriString, boolean includeLastSegment) - throws IOException, SecurityException, IllegalArgumentException { - DocumentFile dir = goToDocument(uriString, true, includeLastSegment); - assert dir != null; - return dir; - } - - public DocumentFile createFile(String uriString) throws IOException, SecurityException { - return createFile(uriString, null); - } - - public DocumentFile createFile(String uriString, String mimeType) - throws IOException, SecurityException { - if (this.exists(uriString)) { - throw new IOException("a file or directory already exist at: " + uriString); - } - DocumentFile parentDirOfFile = this.mkdir(uriString, false); - // it should be safe because user cannot select sd root or primary root - // and any other path would have at least one '/' to provide a file name in a folder - String fileName = UriHelper.getLastSegment(uriString); - if (fileName.indexOf(':') != -1) { - throw new IOException( - "Invalid file name: Could not extract filename from uri string provided"); - } - - // maybe edited maybe not - String correctFileName = fileName; - - // only files with mime type are special, so we treat it special - if (mimeType != null && !mimeType.equals("")) { - int indexOfDot = fileName.indexOf('.'); - // len - 1 because there should be an extension that has at least 1 letter - if (indexOfDot != -1 && indexOfDot < fileName.length() - 1) { - correctFileName = fileName.substring(0, indexOfDot); - } - } - - DocumentFile createdFile = - parentDirOfFile.createFile( - mimeType != null && !mimeType.equals("") ? mimeType : "*/*", correctFileName); - if (createdFile == null) { - throw new IOException( - "File creation failed without any specific error for '" + fileName + "'"); - } - // some times setting mimetypes causes name changes, this is to prevent that. - if (!createdFile.getName().equals(fileName)) { - if (!createdFile.renameTo(fileName) || !createdFile.getName().equals(fileName)) { - createdFile.delete(); - throw new IOException( - "The created file name was not as expected: '" - + uriString - + "'" - + "but got: " - + createdFile.getUri()); - } - } - return createdFile; - } - - public DocumentFile goToDocument(String uriString, boolean createIfDirectoryNotExist) - throws SecurityException, IOException { - return goToDocument(uriString, createIfDirectoryNotExist, true); - } - - public DocumentFile goToDocument( - String unknownUriStr, boolean createIfDirectoryNotExist, boolean includeLastSegment) - throws SecurityException, IOException, IllegalArgumentException { - String unknownUriString = UriHelper.getUnifiedUri(unknownUriStr); - if (unknownUriString.startsWith(ContentResolver.SCHEME_FILE)) { - Uri uri = Uri.parse(unknownUriString); - if (uri == null) { - throw new IllegalArgumentException("Invalid Uri String"); - } - String path = - uri.getPath() - .substring( - 0, - includeLastSegment - ? uri.getPath().length() - : uri.getPath().length() - uri.getLastPathSegment().length()); - - if (createIfDirectoryNotExist) { - File targetFile = new File(path); - if (!targetFile.exists()) { - boolean madeFolder = targetFile.mkdirs(); - if (!madeFolder) { - throw new IOException("mkdir failed for Uri with `file` scheme"); - } - } - } - DocumentFile targetFile = DocumentFile.fromFile(new File(path)); - if (!targetFile.exists()) { - throw new FileNotFoundException( - "Cannot find the given document. File does not exist at '" + unknownUriString + "'"); - } - return targetFile; - } else if (!UriHelper.isContentDocumentTreeUri(unknownUriString)) { - // It's a document picked by user - DocumentFile doc = DocumentFile.fromSingleUri(context, Uri.parse(unknownUriString)); - if (doc != null) { - return doc; - } - throw new FileNotFoundException( - "Cannot find the given document. File does not exist at '" + unknownUriString + "'"); - } - - String uriString = UriHelper.normalize(unknownUriString); - String baseUri = ""; - String appendUri; - String[] strings = new String[0]; - - { - // Helps traversal and folder creation by knowing where to start traverse - List uriList = context.getContentResolver().getPersistedUriPermissions(); - for (UriPermission uriPermission : uriList) { - String uriPath = uriPermission.getUri().toString(); - if (this.permissionMatchesAndHasAccess(uriPermission, uriString)) { - baseUri = uriPath; - appendUri = Uri.decode(uriString.substring(uriPath.length())); - strings = appendUri.split("/"); - break; - } - } - } - - if (baseUri.equals("")) { - // It's possible that the file access is temporary - baseUri = uriString; - } - - Uri uri = Uri.parse(baseUri); - DocumentFile dir = DocumentFile.fromTreeUri(context, uri); - - int pathSegmentsToTraverseLength = includeLastSegment ? strings.length : strings.length - 1; - for (int i = 0; i < pathSegmentsToTraverseLength; i++) { - if (!strings[i].equals("")) { - assert dir != null; - DocumentFile childDoc = DocumentFileHelper.findFile(context, dir, strings[i]); - if (childDoc != null) { - if (childDoc.isDirectory()) { - dir = childDoc; - } else if (i == pathSegmentsToTraverseLength - 1) { - // we are at the last part to traverse, its our destination, doesn't matter if its a - // file or directory - dir = childDoc; - } else { - // child doc is a file - throw new IOException( - "There's a document with the same name as the one we are trying to traverse at: '" - + childDoc.getUri() - + "'"); - } - } else { - if (createIfDirectoryNotExist) { - dir = dir.createDirectory(strings[i]); - } else { - throw new FileNotFoundException( - "Cannot traverse to the pointed document. Directory '" - + strings[i] - + "'" - + " does not exist in '" - + dir.getUri() - + "'"); - } - } - } - } - - assert dir != null; - - if (!dir.canRead() || !dir.canWrite()) { - throw new SecurityException( - "You don't have read/write permission to access uri: " + uriString); - } - - return dir; - } - - public void transferFile( - String srcUri, String destUri, boolean replaceIfDestExists, boolean copy, Promise promise) { - try { - DocumentFile srcDoc = this.goToDocument(srcUri, false, true); - - if (srcDoc.isDirectory()) { - throw new IllegalArgumentException("Cannot move directories"); - } - - DocumentFile destDoc; - try { - destDoc = this.goToDocument(destUri, false, true); - if (!replaceIfDestExists) { - throw new IOException("a document with the same name already exists in destination"); - } - } catch (FileNotFoundException e) { - destDoc = this.createFile(destUri, srcDoc.getType()); - } - - try (InputStream inStream = - this.context.getContentResolver().openInputStream(srcDoc.getUri()); - OutputStream outStream = - this.context.getContentResolver().openOutputStream(destDoc.getUri(), "wt"); ) { - byte[] buffer = new byte[1024 * 4]; - int length; - while ((length = inStream.read(buffer)) > 0) { - outStream.write(buffer, 0, length); - } - } - - if (!copy) { - srcDoc.delete(); - } - - promise.resolve(resolveWithDocument(destDoc, destUri, promise)); - } catch (Exception e) { - promise.reject("EUNSPECIFIED", e.getLocalizedMessage()); - } - } -} diff --git a/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/DocumentStat.java b/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/DocumentStat.java new file mode 100644 index 000000000..a909d5799 --- /dev/null +++ b/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/DocumentStat.java @@ -0,0 +1,132 @@ +package com.reactnativesafx.utils; + +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.provider.DocumentsContract; +import android.webkit.MimeTypeMap; + +import androidx.annotation.RequiresApi; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.WritableMap; + +import java.io.File; + +@RequiresApi(api = Build.VERSION_CODES.N) +public class DocumentStat { + private final Uri uri; + private final Uri internalUri; + private final String displayName; + private final String mimeType; + private final Boolean isDirectory; + private final long size; + private final long lastModified; + private static final String encodedSlash = Uri.encode("/"); + private static final String PATH_DOCUMENT = "document"; + + /** + * Cursor columns must be in the following format: + * 0 - DocumentsContract.Document.COLUMN_DOCUMENT_ID + * 1 - DocumentsContract.Document.COLUMN_DISPLAY_NAME + * 2 - DocumentsContract.Document.COLUMN_MIME_TYPE + * 3 - DocumentsContract.Document.COLUMN_SIZE + * 4 - DocumentsContract.Document.COLUMN_LAST_MODIFIED + * + * @param uri if this is a tree uri, it must be in it's simplified form + * (before being processed for the library) + */ + public DocumentStat(Cursor c, final Uri uri) { + if (DocumentsContract.isTreeUri(uri)) { + final Uri tree = DocumentsContract.buildTreeDocumentUri(uri.getAuthority(), DocumentsContract.getTreeDocumentId(uri)); + this.internalUri = DocumentsContract.buildDocumentUriUsingTree(tree, c.getString(0)); + + if (uri.toString().contains("document/raw") || !c.getString(0).contains(DocumentsContract.getTreeDocumentId(uri))) { + this.uri = this.internalUri; + } else { + this.uri = DocumentsContract.buildTreeDocumentUri(uri.getAuthority(), c.getString(0)); + } + } else { + this.uri = uri; + this.internalUri = this.uri; + } + + + this.displayName = c.getString(1); + this.mimeType = c.getString(2); + this.size = c.getLong(3); + this.lastModified = c.getLong(4); + this.isDirectory = DocumentsContract.Document.MIME_TYPE_DIR.equals(this.mimeType); + } + + public DocumentStat(final File file) { + this.uri = Uri.fromFile(file); + this.internalUri = this.uri; + this.displayName = file.getName(); + this.lastModified = file.lastModified(); + this.size = file.length(); + this.isDirectory = file.isDirectory(); + if (this.isDirectory) { + this.mimeType = DocumentsContract.Document.MIME_TYPE_DIR; + } else { + this.mimeType = getTypeForName(file.getName()); + } + } + + public WritableMap getWritableMap() { + WritableMap fileMap = Arguments.createMap(); + fileMap.putString("uri", UriHelper.denormalize(uri)); + fileMap.putString("name", displayName); + if (isDirectory) { + fileMap.putString("type", "directory"); + } else { + fileMap.putString("type", "file"); + } + fileMap.putString("mime", mimeType); + fileMap.putDouble("size", size); + fileMap.putDouble("lastModified", lastModified); + return fileMap; + } + + public String getUri() { + return UriHelper.denormalize(uri); + } + + public String getDisplayName() { + return displayName; + } + + public String getMimeType() { + return mimeType; + } + + public Boolean isDirectory() { + return this.isDirectory; + } + + public long getSize() { + return size; + } + + public long getLastModified() { + return lastModified; + } + + private static String getTypeForName(String name) { + final int lastDot = name.lastIndexOf('.'); + if (lastDot >= 0) { + final String extension = name.substring(lastDot + 1).toLowerCase(); + final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); + if (mime != null) { + return mime; + } + } + + return "application/octet-stream"; + } + + + public Uri getInternalUri() { + return internalUri; + } +} diff --git a/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/EfficientDocumentHelper.java b/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/EfficientDocumentHelper.java new file mode 100644 index 000000000..a9527ea7a --- /dev/null +++ b/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/EfficientDocumentHelper.java @@ -0,0 +1,1063 @@ +package com.reactnativesafx.utils; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.ClipData; +import android.content.ContentResolver; +import android.content.Intent; +import android.content.UriPermission; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.provider.DocumentsContract; +import android.util.Base64; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; + +import com.facebook.react.bridge.ActivityEventListener; +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.WritableArray; +import com.reactnativesafx.utils.exceptions.ExceptionFast; +import com.reactnativesafx.utils.exceptions.FileNotFoundExceptionFast; +import com.reactnativesafx.utils.exceptions.IOExceptionFast; +import com.reactnativesafx.utils.exceptions.RenameFailedException; +import com.reactnativesafx.utils.exceptions.SecurityExceptionFast; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; + +@RequiresApi(api = Build.VERSION_CODES.N) +public class EfficientDocumentHelper { + private static final int DOCUMENT_TREE_REQUEST_CODE = 1; + private static final int DOCUMENT_REQUEST_CODE = 2; + private static final int DOCUMENT_CREATE_CODE = 3; + private final ReactApplicationContext context; + private ActivityEventListener activityEventListener; + + public EfficientDocumentHelper(ReactApplicationContext context) { + this.context = context; + } + + private void rejectWithException(final Exception e, final Promise promise) { + if (e instanceof FileNotFoundException) { + promise.reject("ENOENT", e.getLocalizedMessage()); + } else if (e instanceof SecurityException) { + promise.reject("EPERM", e.getLocalizedMessage()); + } else if (e instanceof IOException && e.getMessage() != null && e.getMessage().contains("already exist")) { + promise.reject("EEXIST", e.getLocalizedMessage()); + } else { + promise.reject("EUNSPECIFIED", e.getLocalizedMessage()); + } + } + + private boolean permissionMatchesAndHasAccess( + UriPermission permission, String normalizedUriString) { + String permittedUri = permission.getUri().toString(); + return (normalizedUriString.startsWith(permittedUri)) + && permission.isReadPermission() + && permission.isWritePermission(); + } + + private Uri getDocumentUri(String unknownUriStr, boolean createIfDirectoryNotExist, boolean includeLastSegment) throws IOException { + Uri uri = UriHelper.getUnifiedUri(unknownUriStr); + Uri baseUri = uri; + + if (uri.getScheme().equals(ContentResolver.SCHEME_FILE)) { + String path = + uri.getPath() + .substring( + 0, + includeLastSegment + ? uri.getPath().length() + : uri.getPath().length() - uri.getLastPathSegment().length()); + + File targetFile = new File(path); + if (createIfDirectoryNotExist) { + if (!targetFile.exists()) { + boolean madeFolder = targetFile.mkdirs(); + if (!madeFolder) { + throw new IOExceptionFast("mkdir failed for Uri with `file` scheme"); + } + } + } + + if (!targetFile.exists()) { + throw new FileNotFoundExceptionFast("file does not exist at: " + unknownUriStr); + } + + uri = Uri.fromFile(targetFile); + return uri; + } else if (UriHelper.isDocumentUri(uri)) { + // It's a document picked by user, nothing much we can do. operations limited. + DocumentStat stat = null; + + try { + stat = getStat(uri); + } catch (Exception ignored) { + if (createIfDirectoryNotExist) { + throw new IOExceptionFast("Unsupported uri: " + unknownUriStr); + } + } + + if (stat == null) { + throw new FileNotFoundExceptionFast("file does not exist at: " + unknownUriStr); + } + + return uri; + } else { + // It's a document tree based uri, can have library user's appended path, or not + String uriString = UriHelper.normalize(unknownUriStr); + baseUri = null; + String appendUri; + String[] strings = new String[0]; + + { + // Helps traversal and folder creation by knowing where to start traverse + List uriList = context.getContentResolver().getPersistedUriPermissions(); + for (UriPermission uriPermission : uriList) { + String uriPath = uriPermission.getUri().toString(); + if (permissionMatchesAndHasAccess(uriPermission, uriString)) { + baseUri = uriPermission.getUri(); + appendUri = Uri.decode(uriString.substring(uriPath.length())); + // sometimes appendUri can be empty string + // which then causes strings to be [""] + // this is perfectly fine, which means we don't check baseUri's existence + // as it can be troublesome + strings = appendUri.split("/"); + break; + } + } + } + + if (baseUri == null) { + // It's possible that the file access is temporary + baseUri = uri; + } + + uri = baseUri; + + int pathSegmentsToTraverseLength = includeLastSegment ? strings.length : strings.length - 1; + if (pathSegmentsToTraverseLength == 0) { + if (getStat(uri) == null) { + throw new FileNotFoundExceptionFast("file does not exist at: " + unknownUriStr); + } + } + for (int i = 0; i < pathSegmentsToTraverseLength; i++) { + if (!strings[i].equals("")) { + DocumentStat childStat = findFile(uri, strings[i]); + if (childStat != null) { + if (childStat.isDirectory()) { + uri = childStat.getInternalUri(); + } else if (i == pathSegmentsToTraverseLength - 1) { + // we are at the last part to traverse, its our destination, doesn't matter if its a + // file or directory + uri = childStat.getInternalUri(); + } else { + // child doc is a file + throw new IOExceptionFast( + "There's a document with the same name as the one we are trying to traverse at: '" + + childStat.getUri() + + "'"); + } + } else { + if (createIfDirectoryNotExist) { + uri = createDirectory(uri, strings[i]); + } else { + throw new FileNotFoundExceptionFast( + "Cannot traverse to the pointed document. Document '" + + strings[i] + + "'" + + " does not exist in '" + + uri + + "'"); + } + } + } + } + } + + String documentId = DocumentsContract.getTreeDocumentId(uri); + if (DocumentsContract.isDocumentUri(context, uri)) { + documentId = DocumentsContract.getDocumentId(uri); + } + return DocumentsContract.buildDocumentUriUsingTree(baseUri, documentId); + } + + private Uri buildDocumentUriUsingTree(Uri treeUri) { + String documentId = DocumentsContract.getTreeDocumentId(treeUri); + if (DocumentsContract.isDocumentUri(context, treeUri)) { + documentId = DocumentsContract.getDocumentId(treeUri); + } + return DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId); + } + + // from https://www.reddit.com/r/androiddev/comments/orytnx/fixing_treedocumentfilefindfile_lousy_performance/ + // with modifications + @Nullable + private DocumentStat findFile(@NonNull Uri treeUri, @NonNull String displayName) { + final ContentResolver resolver = context.getContentResolver(); + final Uri childrenUri = getChildrenDocumentFromTreeUri(treeUri); + + try (Cursor c = + resolver.query( + childrenUri, + queryColumns, + null, + null, + null)) { + if (c != null) { + while (c.moveToNext()) { + if (displayName.equals(c.getString(1))) { + return new DocumentStat(c, treeUri); + } + } + } + } + + return null; + } + + @Nullable + private DocumentStat getStat(@NonNull Uri uri) { + if (uri.getScheme().equals(ContentResolver.SCHEME_FILE)) { + File file = new File(uri.getPath()); + return new DocumentStat(file); + } + + final ContentResolver resolver = context.getContentResolver(); + try (Cursor c = + resolver.query( + uri, + queryColumns, + null, + null, + null)) { + if (c != null && c.moveToNext()) { + return new DocumentStat(c, uri); + } + } + + return null; + } + + private Uri createDirectory(@NonNull Uri parentTreeUri, @NonNull String name) throws IOException { + Uri createdDir = DocumentsContract.createDocument( + context.getContentResolver(), buildDocumentUriUsingTree(parentTreeUri), DocumentsContract.Document.MIME_TYPE_DIR, + name); + if (createdDir == null) { + throw new IOExceptionFast("Could not create directory in " + parentTreeUri + " with name " + name); + } + + return createdDir; + } + + private static final String[] queryColumns = new String[]{ + DocumentsContract.Document.COLUMN_DOCUMENT_ID, + DocumentsContract.Document.COLUMN_DISPLAY_NAME, + DocumentsContract.Document.COLUMN_MIME_TYPE, + DocumentsContract.Document.COLUMN_SIZE, + DocumentsContract.Document.COLUMN_LAST_MODIFIED, + }; + + private Uri getChildrenDocumentFromTreeUri(@NonNull Uri treeUri) { + String documentId = DocumentsContract.getTreeDocumentId(treeUri); + if (DocumentsContract.isDocumentUri(context, treeUri)) { + documentId = DocumentsContract.getDocumentId(treeUri); + } + return DocumentsContract.buildChildDocumentsUriUsingTree(treeUri, documentId); + } + + /** + * simply returning writable array is more efficient I guess + */ + @Nullable + private WritableArray queryListFiles(@NonNull Uri uri) { + if (uri.getScheme().equals(ContentResolver.SCHEME_FILE)) { + File parentFile = new File(uri.getPath()).getParentFile(); + if (!parentFile.canRead()) { + throw new SecurityExceptionFast("Permission Denial: Cannot read directory at " + uri.getPath()); + } + WritableArray stats = Arguments.createArray(); + for (File file : parentFile.listFiles()) { + stats.pushMap(new DocumentStat(file).getWritableMap()); + } + return stats; + } + + final ContentResolver resolver = context.getContentResolver(); + final Uri children = getChildrenDocumentFromTreeUri(uri); + + WritableArray stats = Arguments.createArray(); + + try (Cursor c = + resolver.query( + children, + queryColumns, + null, + null, + null)) { + if (c != null && c.getCount() > 0) { + while (c.moveToNext()) { + stats.pushMap(new DocumentStat(c, uri).getWritableMap()); + } + return stats; + } + } + return stats; + } + + private static void deleteRecursive(final File fileOrDirectory) throws IOException { + if (fileOrDirectory.isDirectory()) { + for (File child : fileOrDirectory.listFiles()) { + deleteRecursive(child); + } + } + + if (fileOrDirectory.exists()) { + final boolean result = fileOrDirectory.delete(); + if (!result) { + throw new IOExceptionFast("Cannot delete file at: " + fileOrDirectory.getAbsolutePath()); + } + } + } + + private void unlink(final Uri uri) throws IOException { + final boolean result = DocumentsContract.deleteDocument(context.getContentResolver(), uri); + if (!result) { + throw new IOExceptionFast("Cannot delete file at: " + uri); + } + } + + private Uri renameTo(final Uri uri, final String newName) throws IOException { + final Uri newUri = DocumentsContract.renameDocument(context.getContentResolver(), uri, newName); + if (newUri == null) { + throw new IOExceptionFast("Failed to rename file at: " + uri + " to " + newName); + } + final String renameResult = UriHelper.getFileName(newUri.toString()); + if (!renameResult.equals(newName)) { + throw new RenameFailedException(uri, newName, newUri, renameResult); + } + return newUri; + } + + private Uri createFile(final String unknownStr, final String mimeType) throws IOException { + Uri uri = null; + + try { + uri = getDocumentUri(unknownStr, false, true); + } catch (FileNotFoundException ignored) { + } + + if (uri != null) { + throw new IOExceptionFast("a file or directory already exist at: " + uri); + } + + uri = UriHelper.getUnifiedUri(unknownStr); + + if (uri.getScheme().equals(ContentResolver.SCHEME_FILE)) { + File file = new File(uri.getPath()); + file.getParentFile().mkdirs(); + boolean created = file.createNewFile(); + if (!created) { + throw new IOExceptionFast("could not create file at: " + unknownStr); + } + return uri; + } + + Uri parentDirOfFile = getDocumentUri(unknownStr, true, false); + + // it should be safe because user cannot select sd root or primary root + // and any other path would have at least one '/' to provide a file name in a folder + String fileName = UriHelper.getFileName(unknownStr); + if (fileName.indexOf(':') != -1) { + throw new IOExceptionFast( + "Invalid file name: Could not extract filename from uri string provided"); + } + + // maybe edited maybe not + String correctFileName = fileName; + + // only files with mime type are special, so we treat it special + if (mimeType != null && !mimeType.equals("")) { + int indexOfDot = fileName.indexOf('.'); + // len - 1 because there should be an extension that has at least 1 letter + if (indexOfDot != -1 && indexOfDot < fileName.length() - 1) { + correctFileName = fileName.substring(0, indexOfDot); + } + } + + Uri createdFile = DocumentsContract.createDocument( + context.getContentResolver(), + parentDirOfFile, + mimeType != null && !mimeType.equals("") ? mimeType : "*/*", + correctFileName + ); + + if (createdFile == null) { + throw new IOExceptionFast( + "File creation failed without any specific error for '" + fileName + "'"); + } + + String createdFileName = UriHelper.getFileName(createdFile.toString()); + + if (!createdFileName.equals(fileName)) { + // some times setting mimetypes causes name changes, this is to prevent that. + try { + createdFile = renameTo(createdFile, fileName); + } catch (RenameFailedException e) { + unlink(e.getResultUri()); + throw new IOExceptionFast( + "The created file name was not as expected: input name was '" + + e.getInputName() + + "' " + + "but got: '" + + e.getResultName()); + } + } + + return createdFile; + } + + // all public methods SHOULD be below here + // and MUST start an Async task + + public void listFiles(final String unknownStr, final Promise promise) { + Async.execute(new Async.Task() { + @Override + public Object doAsync() { + try { + Uri uri = getDocumentUri(unknownStr, false, true); + return queryListFiles(uri); + } catch (Exception e) { + return e; + } + } + + @Override + public void doSync(Object o) { + if (o instanceof Exception) { + rejectWithException((Exception) o, promise); + } else { + promise.resolve(o); + } + } + }); + } + + public void stat(final String unknownStr, final Promise promise) { + Async.execute(new Async.Task() { + @Override + public Object doAsync() { + try { + Uri uri = getDocumentUri(unknownStr, false, true); + return getStat(uri).getWritableMap(); + } catch (Exception e) { + return e; + } + } + + @Override + public void doSync(Object o) { + if (o instanceof Exception) { + rejectWithException((Exception) o, promise); + } else { + promise.resolve(o); + } + } + }); + } + + public void mkdir(final String unknownStr, final Promise promise) { + Async.execute(new Async.Task() { + @Override + public Object doAsync() { + try { + Uri uri = getDocumentUri(unknownStr, true, true); + return getStat(uri).getWritableMap(); + } catch (Exception e) { + return e; + } + } + + @Override + public void doSync(Object o) { + if (o instanceof Exception) { + rejectWithException((Exception) o, promise); + } else { + promise.resolve(o); + } + } + }); + } + + public void unlink(final String unknownStr, final Promise promise) { + Async.execute(new Async.Task() { + @Override + public Object doAsync() { + try { + Uri uri = getDocumentUri(unknownStr, false, true); + + // this if makes a difference in speed for internal files + if (uri.getScheme().equals(ContentResolver.SCHEME_FILE)) { + deleteRecursive(new File(uri.getPath())); // throws if it cannot delete the file + } else { + // fortunately we don't need recurse here + unlink(uri); + } + + return true; + } catch (FileNotFoundException e) { + return true; + } catch (Exception e) { + return e; + } + } + + @Override + public void doSync(Object o) { + if (o instanceof Exception) { + rejectWithException((Exception) o, promise); + } else { + promise.resolve(o); + } + } + }); + } + + public void createFile(final String unknownStr, final String mimeType, final Promise promise) { + Async.execute(new Async.Task() { + @Override + public Object doAsync() { + try { + Uri uri = createFile(unknownStr, mimeType); + return getStat(uri).getWritableMap(); + } catch (Exception e) { + return e; + } + } + + @Override + public void doSync(Object o) { + if (o instanceof Exception) { + rejectWithException((Exception) o, promise); + } else { + promise.resolve(o); + } + } + }); + } + + public void hasPermission(final String uriString, final Promise promise) { + Async.execute(new Async.Task() { + @Override + public Object doAsync() { + // list of all persisted permissions for our app + List uriList = context.getContentResolver().getPersistedUriPermissions(); + for (UriPermission uriPermission : uriList) { + if (permissionMatchesAndHasAccess(uriPermission, UriHelper.normalize(uriString))) { + return true; + } + } + return false; + } + + @Override + public void doSync(Object o) { + if (o instanceof Exception) { + rejectWithException((Exception) o, promise); + } else { + promise.resolve(o); + } + } + }); + } + + public void renameTo(final String unknownStr, final String newName, final Promise promise) { + Async.execute(new Async.Task() { + @Override + public Object doAsync() { + try { + Uri uri = getDocumentUri(unknownStr, false, true); + return getStat(renameTo(uri, newName)).getWritableMap(); + } catch (Exception e) { + return e; + } + + } + + @Override + public void doSync(Object o) { + if (o instanceof Exception) { + rejectWithException((Exception) o, promise); + } else { + promise.resolve(o); + } + } + }); + } + + public void exists(final String unknownStr, final Promise promise) { + Async.execute(new Async.Task() { + @Override + public Object doAsync() { + try { + Uri uri = getDocumentUri(unknownStr, false, true); + return uri != null; + } catch (FileNotFoundException e) { + return false; + } catch (Exception e) { + return e; + } + } + + @Override + public void doSync(Object o) { + if (o instanceof Exception) { + promise.resolve(false); + } else { + promise.resolve(o); + } + } + }); + } + + public void readFile(String unknownStr, final String encoding, final Promise promise) { + Async.execute(new Async.Task() { + @Override + public Object doAsync() { + try { + Uri uri = getDocumentUri(unknownStr, false, true); + DocumentStat stats = getStat(uri); + if (stats == null) { + throw new FileNotFoundExceptionFast("'" + unknownStr + "' does not exist"); + } + + byte[] bytes; + int bytesRead; + int length; + + InputStream inputStream = context.getContentResolver().openInputStream(uri); + + length = inputStream.available(); + bytes = new byte[length]; + bytesRead = inputStream.read(bytes); + inputStream.close(); + + String inferredEncoding = encoding; + if (inferredEncoding == null) { + inferredEncoding = ""; + } + + switch (inferredEncoding.toLowerCase()) { + case "base64": + return Base64.encodeToString(bytes, Base64.NO_WRAP); + case "ascii": + WritableArray asciiResult = Arguments.createArray(); + for (byte b : bytes) { + asciiResult.pushInt(b); + } + return asciiResult; + case "utf8": + default: + return new String(bytes); + } + } catch (Exception e) { + return e; + } + } + + @Override + public void doSync(Object o) { + if (o instanceof Exception) { + rejectWithException((Exception) o, promise); + } else { + promise.resolve(o); + } + } + }); + } + + public void writeFile( + String unknownStr, + String data, + String encoding, + String mimeType, + boolean append, + final Promise promise) { + Async.execute(new Async.Task() { + @Override + public Object doAsync() { + try { + Uri uri; + + try { + uri = getDocumentUri(unknownStr, false, true); + if (uri == null) { + throw new FileNotFoundExceptionFast(); + } + } catch (FileNotFoundException e) { + uri = createFile(unknownStr, mimeType); + } + + byte[] bytes = GeneralHelper.stringToBytes(data, encoding); + + try (OutputStream out = + context + .getContentResolver() + .openOutputStream(uri, append ? "wa" : "wt")) { + out.write(bytes); + } + + return null; + } catch (Exception e) { + return e; + } + } + + @Override + public void doSync(Object o) { + if (o instanceof Exception) { + rejectWithException((Exception) o, promise); + } else { + promise.resolve(o); + } + } + }); + } + + + public void transferFile( + String unknownSrcUri, String unknownDestUri, boolean replaceIfDestExists, boolean copy, Promise promise) { + Async.execute(new Async.Task() { + @Override + public Object doAsync() { + try { + Uri srcUri = getDocumentUri(unknownSrcUri, false, true); + DocumentStat srcStats = getStat(srcUri); + + if (srcStats == null) { + throw new FileNotFoundExceptionFast("Document at given Uri does not exist: " + unknownDestUri); + } + + Uri destUri; + try { + destUri = getDocumentUri(unknownDestUri, false, true); + if (!replaceIfDestExists) { + DocumentStat destStat = getStat(destUri); + if (destStat != null) { + throw new IOExceptionFast("a document with the same name already exists in destination"); + } else { + throw new FileNotFoundExceptionFast(); + } + } + } catch (FileNotFoundException e) { + destUri = createFile(unknownDestUri, srcStats.getMimeType()); + } + + try (InputStream inStream = + context.getContentResolver().openInputStream(srcUri); + OutputStream outStream = + context.getContentResolver().openOutputStream(destUri, "wt")) { + byte[] buffer = new byte[1024 * 4]; + int length; + while ((length = inStream.read(buffer)) > 0) { + outStream.write(buffer, 0, length); + } + } + + if (!copy) { + unlink(srcUri); + } + + DocumentStat destStat = getStat(destUri); + if (destStat == null) { + throw new IOExceptionFast("Unexpected error, destination file does not exist after write was completed, file: " + destUri); + } + return destStat.getWritableMap(); + } catch (Exception e) { + return e; + } + } + + @Override + public void doSync(Object o) { + if (o instanceof Exception) { + rejectWithException((Exception) o, promise); + } else { + promise.resolve(o); + } + } + }); + } + + public void openDocumentTree(final boolean persist, final Promise promise) { + try { + + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_OPEN_DOCUMENT_TREE); + + if (activityEventListener != null) { + context.removeActivityEventListener(activityEventListener); + activityEventListener = null; + } + + activityEventListener = + new ActivityEventListener() { + @SuppressLint("WrongConstant") + @Override + public void onActivityResult( + Activity activity, int requestCode, int resultCode, Intent intent) { + if (requestCode == DOCUMENT_TREE_REQUEST_CODE && resultCode == Activity.RESULT_OK) { + if (intent != null) { + Uri uri = intent.getData(); + if (persist) { + final int takeFlags = + intent.getFlags() + & (Intent.FLAG_GRANT_READ_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + + context.getContentResolver().takePersistableUriPermission(uri, takeFlags); + } + // stat is async + stat(uri.toString(), promise); + } else { + promise.resolve(null); + } + } else { + promise.resolve(null); + } + context.removeActivityEventListener(activityEventListener); + activityEventListener = null; + } + + @Override + public void onNewIntent(Intent intent) { + } + }; + + context.addActivityEventListener(activityEventListener); + + Activity activity = context.getCurrentActivity(); + if (activity != null) { + activity.startActivityForResult(intent, DOCUMENT_TREE_REQUEST_CODE); + } else { + context.removeActivityEventListener(activityEventListener); + throw new ExceptionFast("Cannot get current activity, so cannot launch document picker"); + } + + } catch (Exception e) { + rejectWithException(e, promise); + } + } + + public void openDocument(final boolean persist, final boolean multiple, final Promise promise) { + try { + + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + if (multiple) { + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); + } + intent.setType("*/*"); + + if (activityEventListener != null) { + context.removeActivityEventListener(activityEventListener); + activityEventListener = null; + } + + activityEventListener = + new ActivityEventListener() { + @SuppressLint("WrongConstant") + @Override + public void onActivityResult( + Activity activity, int requestCode, int resultCode, Intent intent) { + try { + if (requestCode == DOCUMENT_REQUEST_CODE + && resultCode == Activity.RESULT_OK + && intent != null) { + final Uri uri = intent.getData(); + final ClipData clipData = intent.getClipData(); + if (uri == null && clipData == null) { + throw new ExceptionFast( + "Unexpected Error: Could not retrieve information about selected documents"); + } + + if (persist) { + final int takeFlags = + intent.getFlags() + & (Intent.FLAG_GRANT_READ_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + + if (uri != null) { + context.getContentResolver().takePersistableUriPermission(uri, takeFlags); + } else { + for (int i = 0; i < clipData.getItemCount(); ++i) { + ClipData.Item item = clipData.getItemAt(i); + Uri clipUri = item.getUri(); + context.getContentResolver().takePersistableUriPermission(clipUri, takeFlags); + } + } + } + + Async.execute(new Async.Task() { + @Override + public Object doAsync() { + try { + WritableArray resolvedDocs = Arguments.createArray(); + if (uri != null) { + resolvedDocs.pushMap(getStat(uri).getWritableMap()); + } else { + for (int i = 0; i < clipData.getItemCount(); ++i) { + ClipData.Item item = clipData.getItemAt(i); + Uri clipUri = item.getUri(); + resolvedDocs.pushMap(getStat(clipUri).getWritableMap()); + } + } + return resolvedDocs; + } catch (Exception e) { + return e; + } + } + + @Override + public void doSync(Object o) { + if (o instanceof Exception) { + rejectWithException((Exception) o, promise); + } else { + promise.resolve(o); + } + } + }); + } else { + promise.resolve(null); + } + } catch (Exception e) { + rejectWithException(e, promise); + } finally { + context.removeActivityEventListener(activityEventListener); + activityEventListener = null; + } + } + + @Override + public void onNewIntent(Intent intent) { + } + }; + + context.addActivityEventListener(activityEventListener); + + Activity activity = context.getCurrentActivity(); + if (activity != null) { + activity.startActivityForResult(intent, DOCUMENT_REQUEST_CODE); + } else { + context.removeActivityEventListener(activityEventListener); + throw new ExceptionFast("Cannot get current activity, so cannot launch document picker"); + } + + } catch (Exception e) { + rejectWithException(e, promise); + } + } + + public void createDocument( + final String data, + final String encoding, + final String initialName, + final String mimeType, + final Promise promise) { + try { + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_CREATE_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + if (initialName != null) { + intent.putExtra(Intent.EXTRA_TITLE, initialName); + } + if (mimeType != null) { + intent.setType(mimeType); + } else { + intent.setType("*/*"); + } + + if (activityEventListener != null) { + context.removeActivityEventListener(activityEventListener); + activityEventListener = null; + } + + activityEventListener = + new ActivityEventListener() { + @Override + public void onActivityResult( + Activity activity, int requestCode, int resultCode, Intent intent) { + + if (requestCode == DOCUMENT_CREATE_CODE && resultCode == Activity.RESULT_OK) { + if (intent != null) { + final Uri uri = intent.getData(); + + Async.execute(new Async.Task() { + @Override + public Object doAsync() { + try { + byte[] bytes = GeneralHelper.stringToBytes(data, encoding); + try (OutputStream os = context.getContentResolver().openOutputStream(uri)) { + os.write(bytes); + } + + DocumentStat stat = getStat(uri); + if (stat == null) { + throw new IOExceptionFast("Could not get stats for the saved file"); + } + return stat.getWritableMap(); + } catch (Exception e) { + return e; + } + } + + @Override + public void doSync(Object o) { + if (o instanceof Exception) { + rejectWithException((Exception) o, promise); + } else { + promise.resolve(o); + } + } + }); + } else { + promise.resolve(null); + } + } else { + promise.resolve(null); + } + + context.removeActivityEventListener(activityEventListener); + activityEventListener = null; + } + + @Override + public void onNewIntent(Intent intent) { + } + }; + + context.addActivityEventListener(activityEventListener); + + Activity activity = context.getCurrentActivity(); + if (activity != null) { + activity.startActivityForResult(intent, DOCUMENT_CREATE_CODE); + } else { + context.removeActivityEventListener(activityEventListener); + throw new ExceptionFast("Cannot get current activity, so cannot launch document picker"); + } + } catch (Exception e) { + rejectWithException(e, promise); + } + } + +} diff --git a/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/GeneralHelper.java b/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/GeneralHelper.java index 113014418..15b823f5c 100644 --- a/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/GeneralHelper.java +++ b/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/GeneralHelper.java @@ -1,13 +1,14 @@ package com.reactnativesafx.utils; import android.util.Base64; + import java.nio.charset.StandardCharsets; public class GeneralHelper { /** * String to byte converter method * - * @param data Raw data in string format + * @param data Raw data in string format * @param encoding Decoder name * @return Converted data byte array */ diff --git a/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/UriHelper.java b/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/UriHelper.java index 37fdef579..9f0026f81 100644 --- a/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/UriHelper.java +++ b/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/UriHelper.java @@ -2,61 +2,92 @@ package com.reactnativesafx.utils; import android.content.ContentResolver; import android.net.Uri; -import android.os.Build.VERSION_CODES; +import android.os.Build; +import android.provider.DocumentsContract; import androidx.annotation.RequiresApi; -import java.util.regex.Pattern; +import com.reactnativesafx.utils.exceptions.IllegalArgumentExceptionFast; -@RequiresApi(api = VERSION_CODES.Q) +import java.util.List; + +@RequiresApi(api = Build.VERSION_CODES.N) public class UriHelper { - public static final String CONTENT_URI_PREFIX = "content://"; - public static final Pattern DOCUMENT_TREE_PREFIX = - Pattern.compile("^content://.*?/tree/.+?", Pattern.CASE_INSENSITIVE); + private static final String PATH_TREE = "tree"; + private static final String PATH_DOCUMENT = "document"; - public static String getLastSegment(String uriString) { + public static String getFileName(String uriStr) { + // it should be safe because user cannot select sd root or primary root + // and any other path would have at least one '/' to provide a file name in a folder + String fileName = Uri.parse(Uri.decode(uriStr)).getLastPathSegment(); + if (fileName.indexOf(':') != -1) { + throw new RuntimeException( + "Invalid file name: Could not extract filename from uri string provided"); + } - return Uri.parse(Uri.decode(uriString)).getLastPathSegment(); - } - - public static boolean isContentDocumentTreeUri(String uriString) { - return DOCUMENT_TREE_PREFIX.matcher(uriString).matches(); + return fileName; } public static String normalize(String uriString) { - if (isContentDocumentTreeUri(uriString)) { + return normalize(Uri.parse(uriString)); + } + + public static String normalize(Uri uri) { + if (DocumentsContract.isTreeUri(uri)) { // an abnormal uri example: // content://com.android.externalstorage.documents/tree/1707-3F0B%3Ajoplin/locks/2_2_fa4f9801e9a545a58f1a6c5d3a7cfded.json // normalized: // content://com.android.externalstorage.documents/tree/1707-3F0B%3Ajoplin%2Flocks%2F2_2_fa4f9801e9a545a58f1a6c5d3a7cfded.json - // uri parts: - String[] parts = Uri.decode(uriString).split(":"); - return parts[0] + ":" + parts[1] + Uri.encode(":" + parts[2]); + if (uri.getPath().indexOf(":") != -1) { + // uri parts: + String[] parts = Uri.decode(uri.toString()).split(":"); + return parts[0] + ":" + parts[1] + Uri.encode(":" + parts[2]); + } } - return uriString; + return uri.toString(); } - public static String denormalize(String uriString) { - if (isContentDocumentTreeUri(uriString)) { + public static String denormalize(Uri uri) { + if (DocumentsContract.isTreeUri(uri)) { // an normalized uri example: // content://com.android.externalstorage.documents/tree/1707-3F0B%3Ajoplin%2Flocks%2F2_2_fa4f9801e9a545a58f1a6c5d3a7cfded.json // denormalized: // content://com.android.externalstorage.documents/tree/1707-3F0B/Ajoplin/locks/2_2_fa4f9801e9a545a58f1a6c5d3a7cfded.json - return Uri.decode(normalize(uriString)); - } - return uriString; - } - - public static String getUnifiedUri(String uriString) throws IllegalArgumentException { - Uri uri = Uri.parse(uriString); - if (uri.getScheme() == null) { - uri = Uri.parse(ContentResolver.SCHEME_FILE + "://" + uriString); - } else if (!(uri.getScheme().equals(ContentResolver.SCHEME_FILE) - || uri.getScheme().equals(ContentResolver.SCHEME_CONTENT))) { - throw new IllegalArgumentException("Invalid Uri: Scheme not supported"); + return Uri.decode(normalize(uri)); } return uri.toString(); } + + public static Uri getUnifiedUri(String unknownUriStr) { + if (unknownUriStr == null || unknownUriStr.equals("")) { + throw new IllegalArgumentExceptionFast("Invalid Uri: No input was given"); + } + Uri uri = Uri.parse(unknownUriStr); + if (uri.getScheme() == null) { + uri = Uri.parse(ContentResolver.SCHEME_FILE + "://" + unknownUriStr); + } else if (!(uri.getScheme().equals(ContentResolver.SCHEME_FILE) + || uri.getScheme().equals(ContentResolver.SCHEME_CONTENT))) { + throw new IllegalArgumentExceptionFast("Invalid Uri: Scheme not supported"); + } + return uri; + } + + public static boolean isContentUri(Uri uri) { + return uri != null && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()); + } + + public static boolean isDocumentUri(Uri uri) { + if (isContentUri(uri)) { + final List paths = uri.getPathSegments(); + if (paths.size() >= 4) { + return PATH_TREE.equals(paths.get(0)) && PATH_DOCUMENT.equals(paths.get(2)); + } else if (paths.size() >= 2) { + return PATH_DOCUMENT.equals(paths.get(0)); + } + } + return false; + } + } diff --git a/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/exceptions/ExceptionFast.java b/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/exceptions/ExceptionFast.java new file mode 100644 index 000000000..d8bf2ad8e --- /dev/null +++ b/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/exceptions/ExceptionFast.java @@ -0,0 +1,12 @@ +package com.reactnativesafx.utils.exceptions; + +public class ExceptionFast extends Exception { + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } + + public ExceptionFast(final String message) { + super(message); + } +} diff --git a/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/exceptions/FileNotFoundExceptionFast.java b/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/exceptions/FileNotFoundExceptionFast.java new file mode 100644 index 000000000..b5ca0ba77 --- /dev/null +++ b/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/exceptions/FileNotFoundExceptionFast.java @@ -0,0 +1,18 @@ +package com.reactnativesafx.utils.exceptions; + +import java.io.FileNotFoundException; + +public class FileNotFoundExceptionFast extends FileNotFoundException { + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } + + public FileNotFoundExceptionFast(final String message) { + super(message); + } + + public FileNotFoundExceptionFast() { + super(); + } +} diff --git a/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/exceptions/IOExceptionFast.java b/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/exceptions/IOExceptionFast.java new file mode 100644 index 000000000..54c4f4701 --- /dev/null +++ b/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/exceptions/IOExceptionFast.java @@ -0,0 +1,14 @@ +package com.reactnativesafx.utils.exceptions; + +import java.io.IOException; + +public class IOExceptionFast extends IOException { + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } + + public IOExceptionFast(final String message) { + super(message); + } +} diff --git a/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/exceptions/IllegalArgumentExceptionFast.java b/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/exceptions/IllegalArgumentExceptionFast.java new file mode 100644 index 000000000..e534ae7d8 --- /dev/null +++ b/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/exceptions/IllegalArgumentExceptionFast.java @@ -0,0 +1,12 @@ +package com.reactnativesafx.utils.exceptions; + +public class IllegalArgumentExceptionFast extends IllegalArgumentException { + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } + + public IllegalArgumentExceptionFast(final String message) { + super(message); + } +} diff --git a/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/exceptions/RenameFailedException.java b/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/exceptions/RenameFailedException.java new file mode 100644 index 000000000..1bf4f4333 --- /dev/null +++ b/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/exceptions/RenameFailedException.java @@ -0,0 +1,48 @@ +package com.reactnativesafx.utils.exceptions; + +import android.net.Uri; + +import java.io.IOException; + +public class RenameFailedException extends IOException { + + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } + + private final Uri inputUri; + private final String inputName; + + private final Uri resultUri; + private final String resultName; + + + public RenameFailedException(final Uri inputUri, final String inputName, final Uri resultUri, final String resultName) { + super("Failed to rename file at: " + inputUri + " expected: '" + + inputName + + "'" + + "but got: " + + resultName); + this.inputUri = inputUri; + this.inputName = inputName; + this.resultUri = resultUri; + this.resultName = resultName; + } + + public Uri getInputUri() { + return inputUri; + } + + public String getInputName() { + return inputName; + } + + public Uri getResultUri() { + return resultUri; + } + + public String getResultName() { + return resultName; + } +} diff --git a/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/exceptions/SecurityExceptionFast.java b/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/exceptions/SecurityExceptionFast.java new file mode 100644 index 000000000..ea5850d32 --- /dev/null +++ b/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/exceptions/SecurityExceptionFast.java @@ -0,0 +1,12 @@ +package com.reactnativesafx.utils.exceptions; + +public class SecurityExceptionFast extends SecurityException { + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } + + public SecurityExceptionFast(final String message) { + super(message); + } +} diff --git a/packages/react-native-saf-x/android/wrapper/gradle-wrapper.properties b/packages/react-native-saf-x/android/wrapper/gradle-wrapper.properties index da9702f9e..ae04661ee 100644 --- a/packages/react-native-saf-x/android/wrapper/gradle-wrapper.properties +++ b/packages/react-native-saf-x/android/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/packages/react-native-saf-x/package.json b/packages/react-native-saf-x/package.json index 717ba10bb..fca475bf9 100644 --- a/packages/react-native-saf-x/package.json +++ b/packages/react-native-saf-x/package.json @@ -5,7 +5,6 @@ "main": "src/index", "react-native": "src/index", "source": "src/index", - "private": true, "files": [ "src", "lib", @@ -15,7 +14,10 @@ "react-native-saf-x.podspec", "!lib/typescript/example", "!android/build", - "!ios/build" + "!ios/build", + "!**/__tests__", + "!**/__fixtures__", + "!**/__mocks__" ], "scripts": { "build": "tsc --project tsconfig.json", @@ -30,8 +32,10 @@ "scoped", "storage", "SAF", - "storage-access-framework" + "storage-access-framework", + "fs" ], + "repository": "https://github.com/jd1378/react-native-saf-x", "author": "Javad Mnjd (https://github.com/jd1378)", "license": "AGPL-3.0-or-later", "homepage": "https://github.com/laurent22/joplin/tree/dev/packages/react-native-saf-x", @@ -40,10 +44,9 @@ }, "devDependencies": { "@babel/core": "7.16.0", - "@types/react": "16.14.21", "@types/react-native": "0.64.19", "react": "18.2.0", - "react-native": "0.66.1", + "react-native": "0.70.6", "typescript": "4.9.4" }, "peerDependencies": { diff --git a/packages/react-native-saf-x/src/index.ts b/packages/react-native-saf-x/src/index.ts index b6e0d576d..680edaa6f 100644 --- a/packages/react-native-saf-x/src/index.ts +++ b/packages/react-native-saf-x/src/index.ts @@ -50,11 +50,11 @@ interface SafxInterface { encoding?: Encoding, mimeType?: string, append?: boolean, - ): Promise; + ): Promise; createFile(uriString: string, mimeType?: String): Promise; unlink(uriString: string): Promise; mkdir(uriString: string): Promise; - rename(uriString: string, newName: string): Promise; + rename(uriString: string, newName: string): Promise; getPersistedUriPermissions(): Promise>; releasePersistableUriPermission(uriString: string): Promise; listFiles(uriString: string): Promise; @@ -72,8 +72,8 @@ export type DocumentFileDetail = { name: string; type: 'directory' | 'file'; lastModified: number; - mime?: string; - size?: number; + mime: string; + size: number; }; export type FileOperationOptions = { diff --git a/yarn.lock b/yarn.lock index 77d65f24b..8a49764a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1297,7 +1297,7 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:7.16.0, @babel/core@npm:^7.1.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.14.0": +"@babel/core@npm:7.16.0, @babel/core@npm:^7.12.3, @babel/core@npm:^7.14.0": version: 7.16.0 resolution: "@babel/core@npm:7.16.0" dependencies: @@ -2070,7 +2070,7 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.0.0, @babel/parser@npm:^7.1.0, @babel/parser@npm:^7.1.6, @babel/parser@npm:^7.14.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.16.0, @babel/parser@npm:^7.16.3": +"@babel/parser@npm:^7.0.0, @babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.16.0, @babel/parser@npm:^7.16.3": version: 7.16.4 resolution: "@babel/parser@npm:7.16.4" bin: @@ -2120,7 +2120,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-class-properties@npm:^7.0.0, @babel/plugin-proposal-class-properties@npm:^7.1.0": +"@babel/plugin-proposal-class-properties@npm:^7.0.0": version: 7.16.0 resolution: "@babel/plugin-proposal-class-properties@npm:7.16.0" dependencies: @@ -2156,7 +2156,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-nullish-coalescing-operator@npm:^7.0.0, @babel/plugin-proposal-nullish-coalescing-operator@npm:^7.1.0": +"@babel/plugin-proposal-nullish-coalescing-operator@npm:^7.0.0": version: 7.16.0 resolution: "@babel/plugin-proposal-nullish-coalescing-operator@npm:7.16.0" dependencies: @@ -2222,7 +2222,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-optional-chaining@npm:^7.0.0, @babel/plugin-proposal-optional-chaining@npm:^7.1.0": +"@babel/plugin-proposal-optional-chaining@npm:^7.0.0": version: 7.16.0 resolution: "@babel/plugin-proposal-optional-chaining@npm:7.16.0" dependencies: @@ -2587,7 +2587,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-flow-strip-types@npm:^7.0.0, @babel/plugin-transform-flow-strip-types@npm:^7.16.0": +"@babel/plugin-transform-flow-strip-types@npm:^7.0.0": version: 7.16.0 resolution: "@babel/plugin-transform-flow-strip-types@npm:7.16.0" dependencies: @@ -2656,7 +2656,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-modules-commonjs@npm:^7.0.0, @babel/plugin-transform-modules-commonjs@npm:^7.1.0": +"@babel/plugin-transform-modules-commonjs@npm:^7.0.0": version: 7.16.0 resolution: "@babel/plugin-transform-modules-commonjs@npm:7.16.0" dependencies: @@ -2695,17 +2695,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-object-assign@npm:^7.0.0": - version: 7.16.0 - resolution: "@babel/plugin-transform-object-assign@npm:7.16.0" - dependencies: - "@babel/helper-plugin-utils": ^7.14.5 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 7e997adfb7d05a5666a67d9280ffc4d594f1dc0f0cea7e1aaa4173d227eaa151b5cbde7d15f9bff5e00846a6201befdd00bd944bc44ccfd384e3a0fb1400243e - languageName: node - linkType: hard - "@babel/plugin-transform-object-super@npm:^7.0.0": version: 7.16.0 resolution: "@babel/plugin-transform-object-super@npm:7.16.0" @@ -2814,17 +2803,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-regenerator@npm:^7.0.0": - version: 7.16.0 - resolution: "@babel/plugin-transform-regenerator@npm:7.16.0" - dependencies: - regenerator-transform: ^0.14.2 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 32b1b43f8d55d9e78e87bbc6a19b0bb0ff968220e215e9a3984c0de140048c54c62cf46889bee16f987221eab112909318de391426df33cdbe3fd710480068f7 - languageName: node - linkType: hard - "@babel/plugin-transform-runtime@npm:^7.0.0": version: 7.16.4 resolution: "@babel/plugin-transform-runtime@npm:7.16.4" @@ -2886,19 +2864,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-typescript@npm:^7.16.0, @babel/plugin-transform-typescript@npm:^7.5.0": - version: 7.16.1 - resolution: "@babel/plugin-transform-typescript@npm:7.16.1" - dependencies: - "@babel/helper-create-class-features-plugin": ^7.16.0 - "@babel/helper-plugin-utils": ^7.14.5 - "@babel/plugin-syntax-typescript": ^7.16.0 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 1b1efe62e8de828d52b996429718663705cbefb9a7382d2849725b6318051fcbe9671e9e8f761a94fddf46ea159810c97d1b6282c644f69c98ebf5d4d2687ef6 - languageName: node - linkType: hard - "@babel/plugin-transform-typescript@npm:^7.18.6": version: 7.20.7 resolution: "@babel/plugin-transform-typescript@npm:7.20.7" @@ -2912,6 +2877,19 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-typescript@npm:^7.5.0": + version: 7.16.1 + resolution: "@babel/plugin-transform-typescript@npm:7.16.1" + dependencies: + "@babel/helper-create-class-features-plugin": ^7.16.0 + "@babel/helper-plugin-utils": ^7.14.5 + "@babel/plugin-syntax-typescript": ^7.16.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 1b1efe62e8de828d52b996429718663705cbefb9a7382d2849725b6318051fcbe9671e9e8f761a94fddf46ea159810c97d1b6282c644f69c98ebf5d4d2687ef6 + languageName: node + linkType: hard + "@babel/plugin-transform-unicode-regex@npm:^7.0.0": version: 7.16.0 resolution: "@babel/plugin-transform-unicode-regex@npm:7.16.0" @@ -2924,19 +2902,6 @@ __metadata: languageName: node linkType: hard -"@babel/preset-flow@npm:^7.0.0": - version: 7.16.0 - resolution: "@babel/preset-flow@npm:7.16.0" - dependencies: - "@babel/helper-plugin-utils": ^7.14.5 - "@babel/helper-validator-option": ^7.14.5 - "@babel/plugin-transform-flow-strip-types": ^7.16.0 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 4417293f64d171bd4f063b2acdf7e88850ef15e1c72b4730798f7feb813785e1318bad6ae4c6bdb8d78b43a349be3e343e9d7902dc7a5b95a91abd53efe14904 - languageName: node - linkType: hard - "@babel/preset-flow@npm:^7.13.13": version: 7.18.6 resolution: "@babel/preset-flow@npm:7.18.6" @@ -2950,19 +2915,6 @@ __metadata: languageName: node linkType: hard -"@babel/preset-typescript@npm:^7.1.0": - version: 7.16.0 - resolution: "@babel/preset-typescript@npm:7.16.0" - dependencies: - "@babel/helper-plugin-utils": ^7.14.5 - "@babel/helper-validator-option": ^7.14.5 - "@babel/plugin-transform-typescript": ^7.16.0 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 9b22316e96a34836c113f60c49d58023c8ba4219bcb0843a7685c04511486cf7c610e0d30551a1417809e2fd039884c847f6ede46abe2b8d520140e15fb36aaf - languageName: node - linkType: hard - "@babel/preset-typescript@npm:^7.13.0": version: 7.18.6 resolution: "@babel/preset-typescript@npm:7.18.6" @@ -2976,21 +2928,6 @@ __metadata: languageName: node linkType: hard -"@babel/register@npm:^7.0.0": - version: 7.16.0 - resolution: "@babel/register@npm:7.16.0" - dependencies: - clone-deep: ^4.0.1 - find-cache-dir: ^2.0.0 - make-dir: ^2.1.0 - pirates: ^4.0.0 - source-map-support: ^0.5.16 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 85f661da2692ec9afd156f880ebb941e17337e33f5eb387d8bf8071e37b77647b2be7e08871887622b139db24f560ba8e9eb8b5fcaa9a8138b27714f05548254 - languageName: node - linkType: hard - "@babel/register@npm:^7.13.16": version: 7.18.9 resolution: "@babel/register@npm:7.18.9" @@ -3006,7 +2943,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:7.16.3, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.9.2": +"@babel/runtime@npm:7.16.3, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.9.2": version: 7.16.3 resolution: "@babel/runtime@npm:7.16.3" dependencies: @@ -3253,18 +3190,6 @@ __metadata: languageName: node linkType: hard -"@cnakazawa/watch@npm:^1.0.3": - version: 1.0.4 - resolution: "@cnakazawa/watch@npm:1.0.4" - dependencies: - exec-sh: ^0.3.2 - minimist: ^1.2.0 - bin: - watch: cli.js - checksum: 88f395ca0af2f3c0665b8ce7bb29e83647ec5d141e8735712aeeee4117081555436712966b6957aa1c461f6f826a4d23b0034e379c443a10e919f81c8748bf29 - languageName: node - linkType: hard - "@codemirror/autocomplete@npm:^6.0.0": version: 6.4.0 resolution: "@codemirror/autocomplete@npm:6.4.0" @@ -5065,10 +4990,9 @@ __metadata: resolution: "@joplin/react-native-saf-x@workspace:packages/react-native-saf-x" dependencies: "@babel/core": 7.16.0 - "@types/react": 16.14.21 "@types/react-native": 0.64.19 react: 18.2.0 - react-native: 0.66.1 + react-native: 0.70.6 typescript: 4.9.4 peerDependencies: react: "*" @@ -6628,15 +6552,6 @@ __metadata: languageName: node linkType: hard -"@react-native-community/cli-debugger-ui@npm:^6.0.0-rc.0": - version: 6.0.0-rc.0 - resolution: "@react-native-community/cli-debugger-ui@npm:6.0.0-rc.0" - dependencies: - serve-static: ^1.13.1 - checksum: 8e044f8cf7744cd42a1459283d47ccc6a41746fd07760a1422f76744ed79cf258ffe69c3fa93f83a0438c68586af046573fa7e9687c87a464f063353366eae41 - languageName: node - linkType: hard - "@react-native-community/cli-debugger-ui@npm:^9.0.0": version: 9.0.0 resolution: "@react-native-community/cli-debugger-ui@npm:9.0.0" @@ -6670,19 +6585,6 @@ __metadata: languageName: node linkType: hard -"@react-native-community/cli-hermes@npm:^6.2.0": - version: 6.2.0 - resolution: "@react-native-community/cli-hermes@npm:6.2.0" - dependencies: - "@react-native-community/cli-platform-android": ^6.2.0 - "@react-native-community/cli-tools": ^6.2.0 - chalk: ^4.1.2 - hermes-profile-transformer: ^0.0.6 - ip: ^1.1.5 - checksum: 5af5bc65b586a58444019fdddb91265d3eef26e1a5d90f4c28d548a7953e02279852919dd218e29b7d1d25e0f2a01f682bcefb5ae22089128903cce3d1d7bfd8 - languageName: node - linkType: hard - "@react-native-community/cli-hermes@npm:^9.3.1": version: 9.3.1 resolution: "@react-native-community/cli-hermes@npm:9.3.1" @@ -6711,24 +6613,6 @@ __metadata: languageName: node linkType: hard -"@react-native-community/cli-platform-android@npm:^6.0.0, @react-native-community/cli-platform-android@npm:^6.2.0": - version: 6.2.0 - resolution: "@react-native-community/cli-platform-android@npm:6.2.0" - dependencies: - "@react-native-community/cli-tools": ^6.2.0 - chalk: ^4.1.2 - execa: ^1.0.0 - fs-extra: ^8.1.0 - glob: ^7.1.3 - jetifier: ^1.6.2 - lodash: ^4.17.15 - logkitty: ^0.7.1 - slash: ^3.0.0 - xmldoc: ^1.1.2 - checksum: 3624e3bfedfb9aa9b9eccff59afd57e5e67618f2988c8fcd35944249b575d966cf2f12373aba327de74fd8438cde5bc3d1d7348c3f539af7710ae8828cf6c2ce - languageName: node - linkType: hard - "@react-native-community/cli-platform-ios@npm:9.3.0, @react-native-community/cli-platform-ios@npm:^9.3.0": version: 9.3.0 resolution: "@react-native-community/cli-platform-ios@npm:9.3.0" @@ -6742,40 +6626,6 @@ __metadata: languageName: node linkType: hard -"@react-native-community/cli-platform-ios@npm:^6.0.0": - version: 6.2.0 - resolution: "@react-native-community/cli-platform-ios@npm:6.2.0" - dependencies: - "@react-native-community/cli-tools": ^6.2.0 - chalk: ^4.1.2 - glob: ^7.1.3 - js-yaml: ^3.13.1 - lodash: ^4.17.15 - ora: ^3.4.0 - plist: ^3.0.2 - xcode: ^2.0.0 - checksum: 827ce62aa978964817d21b5b7f77ce5e58a9d57ea9e96abc1d17a5dddc3f51eb4623c4619d149eb9a28c8799537649913d37a0eb4a8b931727547ee2d2a478e9 - languageName: node - linkType: hard - -"@react-native-community/cli-plugin-metro@npm:^6.2.0": - version: 6.2.0 - resolution: "@react-native-community/cli-plugin-metro@npm:6.2.0" - dependencies: - "@react-native-community/cli-server-api": ^6.2.0 - "@react-native-community/cli-tools": ^6.2.0 - chalk: ^4.1.2 - metro: ^0.66.1 - metro-config: ^0.66.1 - metro-core: ^0.66.1 - metro-react-native-babel-transformer: ^0.66.1 - metro-resolver: ^0.66.1 - metro-runtime: ^0.66.1 - readline: ^1.3.0 - checksum: 307cc73d8f19df90944c7a6e8befbeaf636a69dd46c1652467d324e1935192b4ad210d707ab418e93a61a4b940ff2d8aef7f402f9bfb708ce2bc2262913b54c3 - languageName: node - linkType: hard - "@react-native-community/cli-plugin-metro@npm:^9.2.1": version: 9.2.1 resolution: "@react-native-community/cli-plugin-metro@npm:9.2.1" @@ -6794,23 +6644,6 @@ __metadata: languageName: node linkType: hard -"@react-native-community/cli-server-api@npm:^6.2.0": - version: 6.2.0 - resolution: "@react-native-community/cli-server-api@npm:6.2.0" - dependencies: - "@react-native-community/cli-debugger-ui": ^6.0.0-rc.0 - "@react-native-community/cli-tools": ^6.2.0 - compression: ^1.7.1 - connect: ^3.6.5 - errorhandler: ^1.5.0 - nocache: ^2.1.0 - pretty-format: ^26.6.2 - serve-static: ^1.13.1 - ws: ^1.1.0 - checksum: 63519747ee135ef81205dad2c9a3c026829f10c32d817b6105b62a3571a99519ca3096c5ef286285188f174e98c1735fab4ed8532e32f1256347c36654660e6e - languageName: node - linkType: hard - "@react-native-community/cli-server-api@npm:^9.2.1": version: 9.2.1 resolution: "@react-native-community/cli-server-api@npm:9.2.1" @@ -6828,22 +6661,6 @@ __metadata: languageName: node linkType: hard -"@react-native-community/cli-tools@npm:^6.2.0": - version: 6.2.0 - resolution: "@react-native-community/cli-tools@npm:6.2.0" - dependencies: - appdirsjs: ^1.2.4 - chalk: ^4.1.2 - lodash: ^4.17.15 - mime: ^2.4.1 - node-fetch: ^2.6.0 - open: ^6.2.0 - semver: ^6.3.0 - shell-quote: 1.6.1 - checksum: 17bb1e2e22c4e3e8c6902da4d46fcba4ed365831b41f35e24ab28720a6e36998092203e694c2a53b21b37161c1bfe10f0d63bcc05d38d149093e28a0d336602d - languageName: node - linkType: hard - "@react-native-community/cli-tools@npm:^9.2.1": version: 9.2.1 resolution: "@react-native-community/cli-tools@npm:9.2.1" @@ -6861,15 +6678,6 @@ __metadata: languageName: node linkType: hard -"@react-native-community/cli-types@npm:^6.0.0": - version: 6.0.0 - resolution: "@react-native-community/cli-types@npm:6.0.0" - dependencies: - ora: ^3.4.0 - checksum: 7ef1a4e5e340facca6280bc8d9f36cd44b8c6d05ba2afa228fea9bd3eabfa4d0b273e3c42f5e1f65144e126eb4f95ef7ffbd785981d30286799d74f267863265 - languageName: node - linkType: hard - "@react-native-community/cli-types@npm:^9.1.0": version: 9.1.0 resolution: "@react-native-community/cli-types@npm:9.1.0" @@ -6906,49 +6714,6 @@ __metadata: languageName: node linkType: hard -"@react-native-community/cli@npm:^6.0.0": - version: 6.2.0 - resolution: "@react-native-community/cli@npm:6.2.0" - dependencies: - "@react-native-community/cli-debugger-ui": ^6.0.0-rc.0 - "@react-native-community/cli-hermes": ^6.2.0 - "@react-native-community/cli-plugin-metro": ^6.2.0 - "@react-native-community/cli-server-api": ^6.2.0 - "@react-native-community/cli-tools": ^6.2.0 - "@react-native-community/cli-types": ^6.0.0 - appdirsjs: ^1.2.4 - chalk: ^4.1.2 - command-exists: ^1.2.8 - commander: ^2.19.0 - cosmiconfig: ^5.1.0 - deepmerge: ^3.2.0 - envinfo: ^7.7.2 - execa: ^1.0.0 - find-up: ^4.1.0 - fs-extra: ^8.1.0 - glob: ^7.1.3 - graceful-fs: ^4.1.3 - joi: ^17.2.1 - leven: ^3.1.0 - lodash: ^4.17.15 - minimist: ^1.2.0 - node-stream-zip: ^1.9.1 - ora: ^3.4.0 - pretty-format: ^26.6.2 - prompts: ^2.4.0 - semver: ^6.3.0 - serve-static: ^1.13.1 - strip-ansi: ^5.2.0 - sudo-prompt: ^9.0.0 - wcwidth: ^1.0.1 - peerDependencies: - react-native: "*" - bin: - react-native: build/bin.js - checksum: 6025f2df793903ac3cb2f3be8d5b3bdc410e10bd20c481dc2a43d088bcec3f8f564e7950a4042cf347cb2599b5cd26352c2189e9ad8f82df7cded9894148bdf6 - languageName: node - linkType: hard - "@react-native-community/clipboard@npm:1.5.1": version: 1.5.1 resolution: "@react-native-community/clipboard@npm:1.5.1" @@ -7013,13 +6778,6 @@ __metadata: languageName: node linkType: hard -"@react-native/normalize-color@npm:1.0.0": - version: 1.0.0 - resolution: "@react-native/normalize-color@npm:1.0.0" - checksum: 22f1af279576ffd2609dc8362054dba2a7553d055f762f6dc7f6c9082c0e83c10260a99cb046d240fecde27a3858525a53d337f8c23d549b52f714dc599a1961 - languageName: node - linkType: hard - "@react-native/normalize-color@npm:2.0.0": version: 2.0.0 resolution: "@react-native/normalize-color@npm:2.0.0" @@ -7541,7 +7299,7 @@ __metadata: languageName: node linkType: hard -"@types/graceful-fs@npm:^4.1.2, @types/graceful-fs@npm:^4.1.3": +"@types/graceful-fs@npm:^4.1.3": version: 4.1.5 resolution: "@types/graceful-fs@npm:4.1.5" dependencies: @@ -7996,17 +7754,6 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:16.14.21": - version: 16.14.21 - resolution: "@types/react@npm:16.14.21" - dependencies: - "@types/prop-types": "*" - "@types/scheduler": "*" - csstype: ^3.0.2 - checksum: 9660ea0a2c7ad364295c1e5abd6b98a05fd372a0f06f97850f60fcc254bcec766d19b101c0acc382746a4e0b71848d88097a1db7124e3fd441c0472fb6b3f849 - languageName: node - linkType: hard - "@types/react@npm:16.14.34": version: 16.14.34 resolution: "@types/react@npm:16.14.34" @@ -9273,13 +9020,6 @@ __metadata: languageName: node linkType: hard -"array-filter@npm:~0.0.0": - version: 0.0.1 - resolution: "array-filter@npm:0.0.1" - checksum: 0e9afdf5e248c45821c6fe1232071a13a3811e1902c2c2a39d12e4495e8b0b25739fd95bffbbf9884b9693629621f6077b4ae16207b8f23d17710fc2465cebbb - languageName: node - linkType: hard - "array-find-index@npm:^1.0.1": version: 1.0.2 resolution: "array-find-index@npm:1.0.2" @@ -9352,20 +9092,6 @@ __metadata: languageName: node linkType: hard -"array-map@npm:~0.0.0": - version: 0.0.0 - resolution: "array-map@npm:0.0.0" - checksum: 30d73fdc99956c8bd70daea40db5a7d78c5c2c75a03c64fc77904885e79adf7d5a0595076534f4e58962d89435f0687182ac929e65634e3d19931698cbac8149 - languageName: node - linkType: hard - -"array-reduce@npm:~0.0.0": - version: 0.0.0 - resolution: "array-reduce@npm:0.0.0" - checksum: d6226325271f477e3dd65b4d40db8597735b8d08bebcca4972e52d3c173d6c697533664fa8865789ea2d076bdaf1989bab5bdfbb61598be92074a67f13057c3a - languageName: node - linkType: hard - "array-slice@npm:^1.0.0": version: 1.1.0 resolution: "array-slice@npm:1.1.0" @@ -9653,15 +9379,6 @@ __metadata: languageName: node linkType: hard -"async@npm:^2.4.0": - version: 2.6.3 - resolution: "async@npm:2.6.3" - dependencies: - lodash: ^4.17.14 - checksum: 5e5561ff8fca807e88738533d620488ac03a5c43fce6c937451f7e35f943d33ad06c24af3f681a48cca3d2b0002b3118faff0a128dc89438a9bf0226f712c499 - languageName: node - linkType: hard - "async@npm:^2.6.4": version: 2.6.4 resolution: "async@npm:2.6.4" @@ -10094,13 +9811,6 @@ __metadata: languageName: node linkType: hard -"big-integer@npm:1.6.x": - version: 1.6.51 - resolution: "big-integer@npm:1.6.51" - checksum: 3d444173d1b2e20747e2c175568bedeebd8315b0637ea95d75fd27830d3b8e8ba36c6af40374f36bdaea7b5de376dcada1b07587cb2a79a928fccdb6e6e3c518 - languageName: node - linkType: hard - "binary-extensions@npm:^1.0.0": version: 1.13.1 resolution: "binary-extensions@npm:1.13.1" @@ -10213,24 +9923,6 @@ __metadata: languageName: node linkType: hard -"bplist-creator@npm:0.1.0": - version: 0.1.0 - resolution: "bplist-creator@npm:0.1.0" - dependencies: - stream-buffers: 2.2.x - checksum: d4ccd88ea16c9d50c2e99f484a5f5bed34d172f6f704463585c0c9c993fd01ddb5b30d6ef486dd9393ffba3c686727f6296e8adf826ce01705bd3741477ce955 - languageName: node - linkType: hard - -"bplist-parser@npm:0.3.0": - version: 0.3.0 - resolution: "bplist-parser@npm:0.3.0" - dependencies: - big-integer: 1.6.x - checksum: f1c49e4850eabda94b63a1764507cfa33c4e85f6289164964de06cb781d753cca63ccde4c2334999b6fd58ac85cab11f716a1e2fcdc31cd2213f718439c5383c - languageName: node - linkType: hard - "brace-expansion@npm:^1.1.7": version: 1.1.11 resolution: "brace-expansion@npm:1.1.11" @@ -11098,15 +10790,6 @@ __metadata: languageName: node linkType: hard -"capture-exit@npm:^2.0.0": - version: 2.0.0 - resolution: "capture-exit@npm:2.0.0" - dependencies: - rsvp: ^4.8.4 - checksum: 0b9f10daca09e521da9599f34c8e7af14ad879c336e2bdeb19955b375398ae1c5bcc91ac9f2429944343057ee9ed028b1b2fb28816c384e0e55d70c439b226f4 - languageName: node - linkType: hard - "cardinal@npm:^2.1.1": version: 2.1.1 resolution: "cardinal@npm:2.1.1" @@ -11126,7 +10809,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:2.4.2, chalk@npm:^2.0.0, chalk@npm:^2.0.1, chalk@npm:^2.1.0, chalk@npm:^2.3.1, chalk@npm:^2.4.2": +"chalk@npm:2.4.2, chalk@npm:^2.0.0, chalk@npm:^2.1.0, chalk@npm:^2.3.1, chalk@npm:^2.4.2": version: 2.4.2 resolution: "chalk@npm:2.4.2" dependencies: @@ -11452,7 +11135,7 @@ __metadata: languageName: node linkType: hard -"cli-spinners@npm:^2.0.0, cli-spinners@npm:^2.5.0": +"cli-spinners@npm:^2.5.0": version: 2.6.1 resolution: "cli-spinners@npm:2.6.1" checksum: 423409baaa7a58e5104b46ca1745fbfc5888bbd0b0c5a626e052ae1387060839c8efd512fb127e25769b3dc9562db1dc1b5add6e0b93b7ef64f477feb6416a45 @@ -11790,13 +11473,6 @@ __metadata: languageName: node linkType: hard -"colors@npm:^1.1.2": - version: 1.4.0 - resolution: "colors@npm:1.4.0" - checksum: 98aa2c2418ad87dedf25d781be69dc5fc5908e279d9d30c34d8b702e586a0474605b3a189511482b9d5ed0d20c867515d22749537f7bc546256c6014f3ebdcec - languageName: node - linkType: hard - "columnify@npm:^1.5.4": version: 1.5.4 resolution: "columnify@npm:1.5.4" @@ -15683,13 +15359,6 @@ __metadata: languageName: node linkType: hard -"exec-sh@npm:^0.3.2": - version: 0.3.6 - resolution: "exec-sh@npm:0.3.6" - checksum: 0be4f06929c8e4834ea4812f29fe59e2dfcc1bc3fc4b4bb71acb38a500c3b394628a05ef7ba432520bc6c5ec4fadab00cc9c513c4ff6a32104965af302e998e0 - languageName: node - linkType: hard - "execa@npm:4.1.0": version: 4.1.0 resolution: "execa@npm:4.1.0" @@ -17885,13 +17554,6 @@ __metadata: languageName: node linkType: hard -"hermes-engine@npm:~0.9.0": - version: 0.9.0 - resolution: "hermes-engine@npm:0.9.0" - checksum: 384549c544de9efb07e9d84202f45bde00e62539a5b56fed2fd2763dbd02560eb37786e61349119966e3086e95a6b8e008822730180a679e229b43a611718f4c - languageName: node - linkType: hard - "hermes-estree@npm:0.8.0": version: 0.8.0 resolution: "hermes-estree@npm:0.8.0" @@ -17899,13 +17561,6 @@ __metadata: languageName: node linkType: hard -"hermes-parser@npm:0.4.7": - version: 0.4.7 - resolution: "hermes-parser@npm:0.4.7" - checksum: 1210d9139b048993938403e8bcf26249bac942512996449ed88522f3e1d31f206cfd54c3095fc279f51e9f4794dfaaab1a3dce828925a0aa784faeb6abe6863c - languageName: node - linkType: hard - "hermes-parser@npm:0.8.0": version: 0.8.0 resolution: "hermes-parser@npm:0.8.0" @@ -20014,31 +19669,6 @@ __metadata: languageName: node linkType: hard -"jest-haste-map@npm:^26.5.2": - version: 26.6.2 - resolution: "jest-haste-map@npm:26.6.2" - dependencies: - "@jest/types": ^26.6.2 - "@types/graceful-fs": ^4.1.2 - "@types/node": "*" - anymatch: ^3.0.3 - fb-watchman: ^2.0.0 - fsevents: ^2.1.2 - graceful-fs: ^4.2.4 - jest-regex-util: ^26.0.0 - jest-serializer: ^26.6.2 - jest-util: ^26.6.2 - jest-worker: ^26.6.2 - micromatch: ^4.0.2 - sane: ^4.0.3 - walker: ^1.0.7 - dependenciesMeta: - fsevents: - optional: true - checksum: 8ad5236d5646d2388d2bd58a57ea53698923434f43d59ea9ebdc58bce4d0b8544c8de2f7acaa9a6d73171f04460388b2b6d7d6b6c256aea4ebb8780140781596 - languageName: node - linkType: hard - "jest-haste-map@npm:^29.3.1": version: 29.3.1 resolution: "jest-haste-map@npm:29.3.1" @@ -20124,13 +19754,6 @@ __metadata: languageName: node linkType: hard -"jest-regex-util@npm:^26.0.0": - version: 26.0.0 - resolution: "jest-regex-util@npm:26.0.0" - checksum: 930a00665e8dfbedc29140678b4a54f021b41b895cf35050f76f557c1da3ac48ff42dd7b18ba2ccba6f4e518c6445d6753730d03ec7049901b93992db1ef0483 - languageName: node - linkType: hard - "jest-regex-util@npm:^27.0.6": version: 27.5.1 resolution: "jest-regex-util@npm:27.5.1" @@ -20231,16 +19854,6 @@ __metadata: languageName: node linkType: hard -"jest-serializer@npm:^26.6.2": - version: 26.6.2 - resolution: "jest-serializer@npm:26.6.2" - dependencies: - "@types/node": "*" - graceful-fs: ^4.2.4 - checksum: dbecfb0d01462fe486a0932cf1680cf6abb204c059db2a8f72c6c2a7c9842a82f6d256874112774cea700764ed8f38fc9e3db982456c138d87353e3390e746fe - languageName: node - linkType: hard - "jest-serializer@npm:^27.0.6": version: 27.5.1 resolution: "jest-serializer@npm:27.5.1" @@ -20283,20 +19896,6 @@ __metadata: languageName: node linkType: hard -"jest-util@npm:^26.6.2": - version: 26.6.2 - resolution: "jest-util@npm:26.6.2" - dependencies: - "@jest/types": ^26.6.2 - "@types/node": "*" - chalk: ^4.0.0 - graceful-fs: ^4.2.4 - is-ci: ^2.0.0 - micromatch: ^4.0.2 - checksum: 3c6a5fba05c4c6892cd3a9f66196ea8867087b77a5aa1a3f6cd349c785c3f1ca24abfd454664983aed1a165cab7846688e44fe8630652d666ba326b08625bc3d - languageName: node - linkType: hard - "jest-util@npm:^27.2.0": version: 27.5.1 resolution: "jest-util@npm:27.5.1" @@ -20369,17 +19968,6 @@ __metadata: languageName: node linkType: hard -"jest-worker@npm:^26.0.0, jest-worker@npm:^26.6.2": - version: 26.6.2 - resolution: "jest-worker@npm:26.6.2" - dependencies: - "@types/node": "*" - merge-stream: ^2.0.0 - supports-color: ^7.0.0 - checksum: f9afa3b88e3f12027901e4964ba3ff048285b5783b5225cab28fac25b4058cea8ad54001e9a1577ee2bed125fac3ccf5c80dc507b120300cc1bbcb368796533e - languageName: node - linkType: hard - "jest-worker@npm:^27.2.0": version: 27.5.1 resolution: "jest-worker@npm:27.5.1" @@ -20444,17 +20032,6 @@ __metadata: languageName: node linkType: hard -"jetifier@npm:^1.6.2": - version: 1.6.8 - resolution: "jetifier@npm:1.6.8" - bin: - jetifier: bin/jetify - jetifier-standalone: bin/jetifier-standalone - jetify: bin/jetify - checksum: 6cdecf7683bb2f6e89e48442365d8bac6244c74ffa286b1b45d97ffa2a833901d0f4b86d0b83d4babec2b71385104214248f1b8539d82e8909989adbf16d09b4 - languageName: node - linkType: hard - "jmespath@npm:0.16.0": version: 0.16.0 resolution: "jmespath@npm:0.16.0" @@ -20604,37 +20181,6 @@ __metadata: languageName: node linkType: hard -"jscodeshift@npm:^0.11.0": - version: 0.11.0 - resolution: "jscodeshift@npm:0.11.0" - dependencies: - "@babel/core": ^7.1.6 - "@babel/parser": ^7.1.6 - "@babel/plugin-proposal-class-properties": ^7.1.0 - "@babel/plugin-proposal-nullish-coalescing-operator": ^7.1.0 - "@babel/plugin-proposal-optional-chaining": ^7.1.0 - "@babel/plugin-transform-modules-commonjs": ^7.1.0 - "@babel/preset-flow": ^7.0.0 - "@babel/preset-typescript": ^7.1.0 - "@babel/register": ^7.0.0 - babel-core: ^7.0.0-bridge.0 - colors: ^1.1.2 - flow-parser: 0.* - graceful-fs: ^4.2.4 - micromatch: ^3.1.10 - neo-async: ^2.5.0 - node-dir: ^0.1.17 - recast: ^0.20.3 - temp: ^0.8.1 - write-file-atomic: ^2.3.0 - peerDependencies: - "@babel/preset-env": ^7.1.6 - bin: - jscodeshift: bin/jscodeshift.js - checksum: 647dc36a50d417b14659f81109685f9ea3924daf604d50d7d2b522c4a658d6abff921dedb4cf74a6d2173c1c48195f9e92cca3df1cb535c6d5f67455d35a19ce - languageName: node - linkType: hard - "jscodeshift@npm:^0.13.1": version: 0.13.1 resolution: "jscodeshift@npm:0.13.1" @@ -21758,15 +21304,6 @@ __metadata: languageName: node linkType: hard -"log-symbols@npm:^2.2.0": - version: 2.2.0 - resolution: "log-symbols@npm:2.2.0" - dependencies: - chalk: ^2.0.1 - checksum: 4c95e3b65f0352dbe91dc4989c10baf7a44e2ef5b0db7e6721e1476268e2b6f7090c3aa880d4f833a05c5c3ff18f4ec5215a09bd0099986d64a8186cfeb48ac8 - languageName: node - linkType: hard - "log-symbols@npm:^4.1.0": version: 4.1.0 resolution: "log-symbols@npm:4.1.0" @@ -22479,34 +22016,6 @@ __metadata: languageName: node linkType: hard -"metro-babel-register@npm:0.66.2": - version: 0.66.2 - resolution: "metro-babel-register@npm:0.66.2" - dependencies: - "@babel/core": ^7.14.0 - "@babel/plugin-proposal-nullish-coalescing-operator": ^7.0.0 - "@babel/plugin-proposal-optional-chaining": ^7.0.0 - "@babel/plugin-syntax-class-properties": ^7.0.0 - "@babel/plugin-transform-flow-strip-types": ^7.0.0 - "@babel/plugin-transform-modules-commonjs": ^7.0.0 - "@babel/register": ^7.0.0 - escape-string-regexp: ^1.0.5 - checksum: 03a467a8144837c6a75cdec19d384f24f1f4dfc6d4ffabe1ab82e3235d5b12c16483704990660b9f37f0a19d7b7e2754e284b63f9f4a67595e9197a5996dfae9 - languageName: node - linkType: hard - -"metro-babel-transformer@npm:0.66.2": - version: 0.66.2 - resolution: "metro-babel-transformer@npm:0.66.2" - dependencies: - "@babel/core": ^7.14.0 - hermes-parser: 0.4.7 - metro-source-map: 0.66.2 - nullthrows: ^1.1.1 - checksum: fbec39283db17c819c4ee7adbcdc9174468e767bd4093b3d0eb863c248b7c8d57e848baf45df8d26803442928ace11bb94a0bc0fde19603fed48389456a8ebe1 - languageName: node - linkType: hard - "metro-babel-transformer@npm:0.72.3": version: 0.72.3 resolution: "metro-babel-transformer@npm:0.72.3" @@ -22519,13 +22028,6 @@ __metadata: languageName: node linkType: hard -"metro-cache-key@npm:0.66.2": - version: 0.66.2 - resolution: "metro-cache-key@npm:0.66.2" - checksum: c252fc9f67a7a0ec46cb9bd689f1d8f3fc707eae51954b55d3d0c9f5ed97dcec1e48503515cea937cc6b3d02bf17d4169d1b1e3b9a249eb73bde009443e3f542 - languageName: node - linkType: hard - "metro-cache-key@npm:0.72.3": version: 0.72.3 resolution: "metro-cache-key@npm:0.72.3" @@ -22533,17 +22035,6 @@ __metadata: languageName: node linkType: hard -"metro-cache@npm:0.66.2": - version: 0.66.2 - resolution: "metro-cache@npm:0.66.2" - dependencies: - metro-core: 0.66.2 - mkdirp: ^0.5.1 - rimraf: ^2.5.4 - checksum: 10dcc142e2bbef97fbbb487fb922ac3c2cf9900428c726b26499b03476a2f8c18f5581e99d54edda104c4b3e76881c04b70325282056fc91d146a666644663ec - languageName: node - linkType: hard - "metro-cache@npm:0.72.3": version: 0.72.3 resolution: "metro-cache@npm:0.72.3" @@ -22554,20 +22045,6 @@ __metadata: languageName: node linkType: hard -"metro-config@npm:0.66.2, metro-config@npm:^0.66.1": - version: 0.66.2 - resolution: "metro-config@npm:0.66.2" - dependencies: - cosmiconfig: ^5.0.5 - jest-validate: ^26.5.2 - metro: 0.66.2 - metro-cache: 0.66.2 - metro-core: 0.66.2 - metro-runtime: 0.66.2 - checksum: 9ea773267723bf9eae6fd2c4aeff4d491a75eb18bc1e748748d97f4214b48b2b0d55e06b22ea941383474e51c26c5c0163cdad18c399b6fc8bd8998f77d1ef4b - languageName: node - linkType: hard - "metro-config@npm:0.72.3": version: 0.72.3 resolution: "metro-config@npm:0.72.3" @@ -22582,17 +22059,6 @@ __metadata: languageName: node linkType: hard -"metro-core@npm:0.66.2, metro-core@npm:^0.66.1": - version: 0.66.2 - resolution: "metro-core@npm:0.66.2" - dependencies: - jest-haste-map: ^26.5.2 - lodash.throttle: ^4.1.1 - metro-resolver: 0.66.2 - checksum: cfaacdac2cab60de8032bb7ffa100aea3e797b9cbda4fed314e6dc9defe72c428def57410d91f792e3130f13b426088791762f4952d92f828ca83c52d464b0a3 - languageName: node - linkType: hard - "metro-core@npm:0.72.3": version: 0.72.3 resolution: "metro-core@npm:0.72.3" @@ -22627,13 +22093,6 @@ __metadata: languageName: node linkType: hard -"metro-hermes-compiler@npm:0.66.2": - version: 0.66.2 - resolution: "metro-hermes-compiler@npm:0.66.2" - checksum: c1dc1627d006b9202c70f4ace8fc5ceaf5a786667bdf324adb7c7c3c796d0aa62dc4fc635a4073b78c79c206a87a3b0d78ae9f85bfda7b7f4bb7e926e63ca891 - languageName: node - linkType: hard - "metro-hermes-compiler@npm:0.72.3": version: 0.72.3 resolution: "metro-hermes-compiler@npm:0.72.3" @@ -22641,20 +22100,6 @@ __metadata: languageName: node linkType: hard -"metro-inspector-proxy@npm:0.66.2": - version: 0.66.2 - resolution: "metro-inspector-proxy@npm:0.66.2" - dependencies: - connect: ^3.6.5 - debug: ^2.2.0 - ws: ^1.1.5 - yargs: ^15.3.1 - bin: - metro-inspector-proxy: src/cli.js - checksum: 3f85acc4b353c7936a900ddd64e0d0fec6e7c1c2d8985e9fb5e509879b5e3f25fb6138223869a225bf3a021500cb1c9564962638be97d7c912654dce4c673d46 - languageName: node - linkType: hard - "metro-inspector-proxy@npm:0.72.3": version: 0.72.3 resolution: "metro-inspector-proxy@npm:0.72.3" @@ -22669,15 +22114,6 @@ __metadata: languageName: node linkType: hard -"metro-minify-uglify@npm:0.66.2": - version: 0.66.2 - resolution: "metro-minify-uglify@npm:0.66.2" - dependencies: - uglify-es: ^3.1.9 - checksum: 15ceb1b21cb06f2f369091e631f08b5f9e13c1001cab03ccda0610fe0845254627dbaade45f1c09dffbc5306736f97c42b593db33d4b909d4776b84127cfcf17 - languageName: node - linkType: hard - "metro-minify-uglify@npm:0.72.3": version: 0.72.3 resolution: "metro-minify-uglify@npm:0.72.3" @@ -22687,56 +22123,6 @@ __metadata: languageName: node linkType: hard -"metro-react-native-babel-preset@npm:0.66.2": - version: 0.66.2 - resolution: "metro-react-native-babel-preset@npm:0.66.2" - dependencies: - "@babel/core": ^7.14.0 - "@babel/plugin-proposal-class-properties": ^7.0.0 - "@babel/plugin-proposal-export-default-from": ^7.0.0 - "@babel/plugin-proposal-nullish-coalescing-operator": ^7.0.0 - "@babel/plugin-proposal-object-rest-spread": ^7.0.0 - "@babel/plugin-proposal-optional-catch-binding": ^7.0.0 - "@babel/plugin-proposal-optional-chaining": ^7.0.0 - "@babel/plugin-syntax-dynamic-import": ^7.0.0 - "@babel/plugin-syntax-export-default-from": ^7.0.0 - "@babel/plugin-syntax-flow": ^7.2.0 - "@babel/plugin-syntax-nullish-coalescing-operator": ^7.0.0 - "@babel/plugin-syntax-optional-chaining": ^7.0.0 - "@babel/plugin-transform-arrow-functions": ^7.0.0 - "@babel/plugin-transform-async-to-generator": ^7.0.0 - "@babel/plugin-transform-block-scoping": ^7.0.0 - "@babel/plugin-transform-classes": ^7.0.0 - "@babel/plugin-transform-computed-properties": ^7.0.0 - "@babel/plugin-transform-destructuring": ^7.0.0 - "@babel/plugin-transform-exponentiation-operator": ^7.0.0 - "@babel/plugin-transform-flow-strip-types": ^7.0.0 - "@babel/plugin-transform-for-of": ^7.0.0 - "@babel/plugin-transform-function-name": ^7.0.0 - "@babel/plugin-transform-literals": ^7.0.0 - "@babel/plugin-transform-modules-commonjs": ^7.0.0 - "@babel/plugin-transform-object-assign": ^7.0.0 - "@babel/plugin-transform-parameters": ^7.0.0 - "@babel/plugin-transform-react-display-name": ^7.0.0 - "@babel/plugin-transform-react-jsx": ^7.0.0 - "@babel/plugin-transform-react-jsx-self": ^7.0.0 - "@babel/plugin-transform-react-jsx-source": ^7.0.0 - "@babel/plugin-transform-regenerator": ^7.0.0 - "@babel/plugin-transform-runtime": ^7.0.0 - "@babel/plugin-transform-shorthand-properties": ^7.0.0 - "@babel/plugin-transform-spread": ^7.0.0 - "@babel/plugin-transform-sticky-regex": ^7.0.0 - "@babel/plugin-transform-template-literals": ^7.0.0 - "@babel/plugin-transform-typescript": ^7.5.0 - "@babel/plugin-transform-unicode-regex": ^7.0.0 - "@babel/template": ^7.0.0 - react-refresh: ^0.4.0 - peerDependencies: - "@babel/core": "*" - checksum: c490c73d3ae6674686253db110cc4ee4487a0d2a7b07acd09815abf6087b9358faaa69f8a6361b84024354c60b9f60afa44b57d4cf930f49a1b3cfed2a7ffb59 - languageName: node - linkType: hard - "metro-react-native-babel-preset@npm:0.72.3": version: 0.72.3 resolution: "metro-react-native-babel-preset@npm:0.72.3" @@ -22786,23 +22172,6 @@ __metadata: languageName: node linkType: hard -"metro-react-native-babel-transformer@npm:0.66.2, metro-react-native-babel-transformer@npm:^0.66.1": - version: 0.66.2 - resolution: "metro-react-native-babel-transformer@npm:0.66.2" - dependencies: - "@babel/core": ^7.14.0 - babel-preset-fbjs: ^3.4.0 - hermes-parser: 0.4.7 - metro-babel-transformer: 0.66.2 - metro-react-native-babel-preset: 0.66.2 - metro-source-map: 0.66.2 - nullthrows: ^1.1.1 - peerDependencies: - "@babel/core": "*" - checksum: b849306f06c1dabdc0fedc35e10646d4c0cdc752601e2f75cc20a2b341057861c661a62443a00cfa9eda220fe1705c0f8eb8f074716590d1acc6ed329f2b39ce - languageName: node - linkType: hard - "metro-react-native-babel-transformer@npm:0.72.3": version: 0.72.3 resolution: "metro-react-native-babel-transformer@npm:0.72.3" @@ -22820,15 +22189,6 @@ __metadata: languageName: node linkType: hard -"metro-resolver@npm:0.66.2, metro-resolver@npm:^0.66.1": - version: 0.66.2 - resolution: "metro-resolver@npm:0.66.2" - dependencies: - absolute-path: ^0.0.0 - checksum: c0e80230b698cbe38f748197d66c3ae6448a4965f464ed216dba4e3eaaaa71719a803548b372c53caed8fb88b1784c79af2057c1e678d356dcff8b96644d5e81 - languageName: node - linkType: hard - "metro-resolver@npm:0.72.3": version: 0.72.3 resolution: "metro-resolver@npm:0.72.3" @@ -22838,13 +22198,6 @@ __metadata: languageName: node linkType: hard -"metro-runtime@npm:0.66.2, metro-runtime@npm:^0.66.1": - version: 0.66.2 - resolution: "metro-runtime@npm:0.66.2" - checksum: 7b51abc53a04b7991ee0bbf5517d95b6f1305a4e8854c19afeed1087f8b15df25e67ecbd38fb5606909927d55757302089edbbf1e508b60accc5677aa671728b - languageName: node - linkType: hard - "metro-runtime@npm:0.72.3": version: 0.72.3 resolution: "metro-runtime@npm:0.72.3" @@ -22855,22 +22208,6 @@ __metadata: languageName: node linkType: hard -"metro-source-map@npm:0.66.2": - version: 0.66.2 - resolution: "metro-source-map@npm:0.66.2" - dependencies: - "@babel/traverse": ^7.14.0 - "@babel/types": ^7.0.0 - invariant: ^2.2.4 - metro-symbolicate: 0.66.2 - nullthrows: ^1.1.1 - ob1: 0.66.2 - source-map: ^0.5.6 - vlq: ^1.0.0 - checksum: 67959828b023079747d60ed41cc2263cdf883218c477799b26159c42c593737768e589498ebee7a3333080a96b2f97e0c01fd7371d8e59b6c5f8f5a4b250bbb6 - languageName: node - linkType: hard - "metro-source-map@npm:0.72.3": version: 0.72.3 resolution: "metro-source-map@npm:0.72.3" @@ -22887,22 +22224,6 @@ __metadata: languageName: node linkType: hard -"metro-symbolicate@npm:0.66.2": - version: 0.66.2 - resolution: "metro-symbolicate@npm:0.66.2" - dependencies: - invariant: ^2.2.4 - metro-source-map: 0.66.2 - nullthrows: ^1.1.1 - source-map: ^0.5.6 - through2: ^2.0.1 - vlq: ^1.0.0 - bin: - metro-symbolicate: src/index.js - checksum: 7a2cfee2d9cd9db6864f6dac5587891facab576ac5aed152981875b8150555ceeb3fcaa4a2d9647e8a3ec941b30c4812383f08af0154b889077187b5234d9367 - languageName: node - linkType: hard - "metro-symbolicate@npm:0.72.3": version: 0.72.3 resolution: "metro-symbolicate@npm:0.72.3" @@ -22919,19 +22240,6 @@ __metadata: languageName: node linkType: hard -"metro-transform-plugins@npm:0.66.2": - version: 0.66.2 - resolution: "metro-transform-plugins@npm:0.66.2" - dependencies: - "@babel/core": ^7.14.0 - "@babel/generator": ^7.14.0 - "@babel/template": ^7.0.0 - "@babel/traverse": ^7.14.0 - nullthrows: ^1.1.1 - checksum: 0d0f510e28579947092c83423239e27b652db01249f23f0a99a4978bb7b46a01c32161c0390b6caa59a0e5e5850293318b35c50b38a5773316dd12357aff7a08 - languageName: node - linkType: hard - "metro-transform-plugins@npm:0.72.3": version: 0.72.3 resolution: "metro-transform-plugins@npm:0.72.3" @@ -22945,27 +22253,6 @@ __metadata: languageName: node linkType: hard -"metro-transform-worker@npm:0.66.2": - version: 0.66.2 - resolution: "metro-transform-worker@npm:0.66.2" - dependencies: - "@babel/core": ^7.14.0 - "@babel/generator": ^7.14.0 - "@babel/parser": ^7.14.0 - "@babel/types": ^7.0.0 - babel-preset-fbjs: ^3.4.0 - metro: 0.66.2 - metro-babel-transformer: 0.66.2 - metro-cache: 0.66.2 - metro-cache-key: 0.66.2 - metro-hermes-compiler: 0.66.2 - metro-source-map: 0.66.2 - metro-transform-plugins: 0.66.2 - nullthrows: ^1.1.1 - checksum: 5d1ab3f9cb3b674c4f3d6148b8b08abf0cb93f30c3afe0e4f74b2149a272728ccbf515a582b5078a87ec1ea77840ca395974e06e082178f21d195858c5667aa1 - languageName: node - linkType: hard - "metro-transform-worker@npm:0.72.3": version: 0.72.3 resolution: "metro-transform-worker@npm:0.72.3" @@ -22987,68 +22274,6 @@ __metadata: languageName: node linkType: hard -"metro@npm:0.66.2, metro@npm:^0.66.1": - version: 0.66.2 - resolution: "metro@npm:0.66.2" - dependencies: - "@babel/code-frame": ^7.0.0 - "@babel/core": ^7.14.0 - "@babel/generator": ^7.14.0 - "@babel/parser": ^7.14.0 - "@babel/template": ^7.0.0 - "@babel/traverse": ^7.14.0 - "@babel/types": ^7.0.0 - absolute-path: ^0.0.0 - accepts: ^1.3.7 - async: ^2.4.0 - chalk: ^4.0.0 - ci-info: ^2.0.0 - connect: ^3.6.5 - debug: ^2.2.0 - denodeify: ^1.2.1 - error-stack-parser: ^2.0.6 - fs-extra: ^1.0.0 - graceful-fs: ^4.1.3 - hermes-parser: 0.4.7 - image-size: ^0.6.0 - invariant: ^2.2.4 - jest-haste-map: ^26.5.2 - jest-worker: ^26.0.0 - lodash.throttle: ^4.1.1 - metro-babel-register: 0.66.2 - metro-babel-transformer: 0.66.2 - metro-cache: 0.66.2 - metro-cache-key: 0.66.2 - metro-config: 0.66.2 - metro-core: 0.66.2 - metro-hermes-compiler: 0.66.2 - metro-inspector-proxy: 0.66.2 - metro-minify-uglify: 0.66.2 - metro-react-native-babel-preset: 0.66.2 - metro-resolver: 0.66.2 - metro-runtime: 0.66.2 - metro-source-map: 0.66.2 - metro-symbolicate: 0.66.2 - metro-transform-plugins: 0.66.2 - metro-transform-worker: 0.66.2 - mime-types: ^2.1.27 - mkdirp: ^0.5.1 - node-fetch: ^2.2.0 - nullthrows: ^1.1.1 - rimraf: ^2.5.4 - serialize-error: ^2.1.0 - source-map: ^0.5.6 - strip-ansi: ^6.0.0 - temp: 0.8.3 - throat: ^5.0.0 - ws: ^1.1.5 - yargs: ^15.3.1 - bin: - metro: src/cli.js - checksum: 0c677fe63105c341b2d3ceec169e5cb88d8fd5340679d717574297d1ea75638c751a0ed602020aff506150c35d73ad4057031397caddd95d24f48bb087e24c82 - languageName: node - linkType: hard - "metro@npm:0.72.3": version: 0.72.3 resolution: "metro@npm:0.72.3" @@ -23140,7 +22365,7 @@ __metadata: languageName: node linkType: hard -"micromatch@npm:^4.0.2, micromatch@npm:^4.0.4": +"micromatch@npm:^4.0.4": version: 4.0.4 resolution: "micromatch@npm:4.0.4" dependencies: @@ -23911,13 +23136,6 @@ __metadata: languageName: node linkType: hard -"nocache@npm:^2.1.0": - version: 2.1.0 - resolution: "nocache@npm:2.1.0" - checksum: 702ad516a7f8b21364c3e9b6ed982b0dfbcbdad9c28ed35331c4025025c729eb9d93523c3370947c5c8391ae33c6f69b67e35ef301d0e9424cee84f0b1d015c2 - languageName: node - linkType: hard - "nocache@npm:^3.0.1": version: 3.0.4 resolution: "nocache@npm:3.0.4" @@ -24190,13 +23408,6 @@ __metadata: languageName: node linkType: hard -"node-modules-regexp@npm:^1.0.0": - version: 1.0.0 - resolution: "node-modules-regexp@npm:1.0.0" - checksum: 99541903536c5ce552786f0fca7f06b88df595e62e423c21fa86a1674ee2363dad1f7482d1bec20b4bd9fa5f262f88e6e5cb788fc56411113f2fe2e97783a3a7 - languageName: node - linkType: hard - "node-notifier@npm:10.0.1": version: 10.0.1 resolution: "node-notifier@npm:10.0.1" @@ -24655,13 +23866,6 @@ __metadata: languageName: node linkType: hard -"ob1@npm:0.66.2": - version: 0.66.2 - resolution: "ob1@npm:0.66.2" - checksum: 18f4ddecd7631aef0cbbd1c11134cca26305e13e75324c51d0fcfb949aefbd3f18df33cdeb7e68b02e1d68a59163f3c7af3a08ff070ccfec6c91d3b67be2f8bb - languageName: node - linkType: hard - "ob1@npm:0.72.3": version: 0.72.3 resolution: "ob1@npm:0.72.3" @@ -25043,27 +24247,6 @@ __metadata: languageName: node linkType: hard -"options@npm:>=0.0.5": - version: 0.0.6 - resolution: "options@npm:0.0.6" - checksum: 8601fdc0a3e14987b7f2509676e5e5d8afe601c64600d9bad3a0aad7e8ed8631ad47e2fa155c63e4043832122d6f6e3251d276307a032d0bb50cc252980e3712 - languageName: node - linkType: hard - -"ora@npm:^3.4.0": - version: 3.4.0 - resolution: "ora@npm:3.4.0" - dependencies: - chalk: ^2.4.2 - cli-cursor: ^2.1.0 - cli-spinners: ^2.0.0 - log-symbols: ^2.2.0 - strip-ansi: ^5.2.0 - wcwidth: ^1.0.1 - checksum: f1f8e7f290b766276dcd19ddf2159a1971b1ec37eec4a5556b8f5e4afbe513a965ed65c183d38956724263b6a20989b3d8fb71b95ac4a2d6a01db2f1ed8899e4 - languageName: node - linkType: hard - "ora@npm:^5.1.0, ora@npm:^5.4.1": version: 5.4.1 resolution: "ora@npm:5.4.1" @@ -25946,15 +25129,6 @@ __metadata: languageName: node linkType: hard -"pirates@npm:^4.0.0": - version: 4.0.1 - resolution: "pirates@npm:4.0.1" - dependencies: - node-modules-regexp: ^1.0.0 - checksum: 091e232aac19f0049a681838fa9fcb4af824b5b1eb0e9325aa07b9d13245bfe3e4fa57a7766b9fdcd19cb89f2c15c688b46023be3047cb288023a0c079d3b2a3 - languageName: node - linkType: hard - "pirates@npm:^4.0.4, pirates@npm:^4.0.5": version: 4.0.5 resolution: "pirates@npm:4.0.5" @@ -26008,7 +25182,7 @@ __metadata: languageName: node linkType: hard -"plist@npm:^3.0.1, plist@npm:^3.0.2, plist@npm:^3.0.4": +"plist@npm:^3.0.1, plist@npm:^3.0.4": version: 3.0.4 resolution: "plist@npm:3.0.4" dependencies: @@ -26486,15 +25660,6 @@ __metadata: languageName: node linkType: hard -"promise@npm:^8.0.3": - version: 8.1.0 - resolution: "promise@npm:8.1.0" - dependencies: - asap: ~2.0.6 - checksum: 89b71a56154ed7d66a73236d8e8351a9c59adddba3929ecc845f75421ff37fc08ea0c67ad76cd5c0b0d81812c7d07a32bed27e7df5fcc960c6d68b0c1cd771f7 - languageName: node - linkType: hard - "prompts@npm:^2.0.1, prompts@npm:^2.4.0": version: 2.4.2 resolution: "prompts@npm:2.4.2" @@ -26908,16 +26073,6 @@ __metadata: languageName: node linkType: hard -"react-devtools-core@npm:^4.13.0": - version: 4.21.0 - resolution: "react-devtools-core@npm:4.21.0" - dependencies: - shell-quote: ^1.6.1 - ws: ^7 - checksum: 50550bf283a87fb370ed27bbb02743064aae4c6374d4050b65b2c6cddecbde7b2f820d2a77144710886f7ba7bb387a60f279fa345de0c5db09d239cebc2d0b60 - languageName: node - linkType: hard - "react-devtools-core@npm:^4.19.1": version: 4.26.1 resolution: "react-devtools-core@npm:4.26.1" @@ -27005,17 +26160,6 @@ __metadata: languageName: node linkType: hard -"react-native-codegen@npm:^0.0.7": - version: 0.0.7 - resolution: "react-native-codegen@npm:0.0.7" - dependencies: - flow-parser: ^0.121.0 - jscodeshift: ^0.11.0 - nullthrows: ^1.1.1 - checksum: d550d6e985a0d564f0ac470b19c9a2e4e78ffc0f5769ba46992a4d10130928093e9c2ada7eda0ec8bd976db4ba4c21ef410280e979226f11844aa0215335cde5 - languageName: node - linkType: hard - "react-native-codegen@npm:^0.70.6": version: 0.70.6 resolution: "react-native-codegen@npm:0.70.6" @@ -27284,49 +26428,6 @@ __metadata: languageName: node linkType: hard -"react-native@npm:0.66.1": - version: 0.66.1 - resolution: "react-native@npm:0.66.1" - dependencies: - "@jest/create-cache-key-function": ^27.0.1 - "@react-native-community/cli": ^6.0.0 - "@react-native-community/cli-platform-android": ^6.0.0 - "@react-native-community/cli-platform-ios": ^6.0.0 - "@react-native/assets": 1.0.0 - "@react-native/normalize-color": 1.0.0 - "@react-native/polyfills": 2.0.0 - abort-controller: ^3.0.0 - anser: ^1.4.9 - base64-js: ^1.1.2 - event-target-shim: ^5.0.1 - hermes-engine: ~0.9.0 - invariant: ^2.2.4 - jsc-android: ^250230.2.1 - metro-babel-register: 0.66.2 - metro-react-native-babel-transformer: 0.66.2 - metro-runtime: 0.66.2 - metro-source-map: 0.66.2 - nullthrows: ^1.1.1 - pretty-format: ^26.5.2 - promise: ^8.0.3 - prop-types: ^15.7.2 - react-devtools-core: ^4.13.0 - react-native-codegen: ^0.0.7 - react-refresh: ^0.4.0 - regenerator-runtime: ^0.13.2 - scheduler: ^0.20.2 - stacktrace-parser: ^0.1.3 - use-subscription: ^1.0.0 - whatwg-fetch: ^3.0.0 - ws: ^6.1.4 - peerDependencies: - react: 17.0.2 - bin: - react-native: cli.js - checksum: fe1701bb398d8f533c51aaf05dabb1c5d11caf80cb39c79fd38f4dbf13a27e8497080216ffdb19aa72f222d5bc75b32b8673bef504fab711f65a8d84dbdb5ddf - languageName: node - linkType: hard - "react-native@npm:0.70.6": version: 0.70.6 resolution: "react-native@npm:0.70.6" @@ -27777,7 +26878,7 @@ __metadata: languageName: node linkType: hard -"recast@npm:^0.20.3, recast@npm:^0.20.4": +"recast@npm:^0.20.4": version: 0.20.5 resolution: "recast@npm:0.20.5" dependencies: @@ -27917,15 +27018,6 @@ __metadata: languageName: node linkType: hard -"regenerator-transform@npm:^0.14.2": - version: 0.14.5 - resolution: "regenerator-transform@npm:0.14.5" - dependencies: - "@babel/runtime": ^7.8.4 - checksum: a467a3b652b4ec26ff964e9c5f1817523a73fc44cb928b8d21ff11aebeac5d10a84d297fe02cea9f282bcec81a0b0d562237da69ef0f40a0160b30a4fa98bc94 - languageName: node - linkType: hard - "regex-not@npm:^1.0.0, regex-not@npm:^1.0.2": version: 1.0.2 resolution: "regex-not@npm:1.0.2" @@ -28674,13 +27766,6 @@ __metadata: languageName: node linkType: hard -"rsvp@npm:^4.8.4": - version: 4.8.5 - resolution: "rsvp@npm:4.8.5" - checksum: 2d8ef30d8febdf05bdf856ccca38001ae3647e41835ca196bc1225333f79b94ae44def733121ca549ccc36209c9b689f6586905e2a043873262609744da8efc1 - languageName: node - linkType: hard - "run-async@npm:^2.0.0, run-async@npm:^2.2.0": version: 2.4.1 resolution: "run-async@npm:2.4.1" @@ -28781,25 +27866,6 @@ __metadata: languageName: node linkType: hard -"sane@npm:^4.0.3": - version: 4.1.0 - resolution: "sane@npm:4.1.0" - dependencies: - "@cnakazawa/watch": ^1.0.3 - anymatch: ^2.0.0 - capture-exit: ^2.0.0 - exec-sh: ^0.3.2 - execa: ^1.0.0 - fb-watchman: ^2.0.0 - micromatch: ^3.1.4 - minimist: ^1.1.1 - walker: ~1.0.5 - bin: - sane: ./src/cli.js - checksum: 97716502d456c0d38670a902a4ea943d196dcdf998d1e40532d8f3e24e25d7eddfd4c3579025a1eee8eac09a48dfd05fba61a2156c56704e7feaa450eb249f7c - languageName: node - linkType: hard - "sanitize-filename@npm:^1.6.3": version: 1.6.3 resolution: "sanitize-filename@npm:1.6.3" @@ -28840,7 +27906,7 @@ __metadata: languageName: node linkType: hard -"sax@npm:>=0.6.0, sax@npm:^1.2.1, sax@npm:^1.2.4": +"sax@npm:>=0.6.0, sax@npm:^1.2.4": version: 1.2.4 resolution: "sax@npm:1.2.4" checksum: d3df7d32b897a2c2f28e941f732c71ba90e27c24f62ee918bd4d9a8cfb3553f2f81e5493c7f0be94a11c1911b643a9108f231dd6f60df3fa9586b5d2e3e9e1fe @@ -29226,18 +28292,6 @@ __metadata: languageName: node linkType: hard -"shell-quote@npm:1.6.1": - version: 1.6.1 - resolution: "shell-quote@npm:1.6.1" - dependencies: - array-filter: ~0.0.0 - array-map: ~0.0.0 - array-reduce: ~0.0.0 - jsonify: ~0.0.0 - checksum: 982a4fdf2d474f0dc40885de4222f100ba457d7c75d46b532bf23b01774b8617bc62522c6825cb1fa7dd4c54c18e9dcbae7df2ca8983101841b6f2e6a7cacd2f - languageName: node - linkType: hard - "shell-quote@npm:^1.6.1": version: 1.7.3 resolution: "shell-quote@npm:1.7.3" @@ -29395,17 +28449,6 @@ __metadata: languageName: node linkType: hard -"simple-plist@npm:^1.0.0": - version: 1.3.0 - resolution: "simple-plist@npm:1.3.0" - dependencies: - bplist-creator: 0.1.0 - bplist-parser: 0.3.0 - plist: ^3.0.4 - checksum: 60955ac24ce73b9c5ba71c450bc9b1b90b4e21e862735e4fd33073e451bcb2dc543bf2d364527ae78c6b76354eb5674c5a1163ec78428598fdd8fde60ce24bb3 - languageName: node - linkType: hard - "simple-swizzle@npm:^0.2.2": version: 0.2.2 resolution: "simple-swizzle@npm:0.2.2" @@ -30090,13 +29133,6 @@ __metadata: languageName: node linkType: hard -"stream-buffers@npm:2.2.x": - version: 2.2.0 - resolution: "stream-buffers@npm:2.2.0" - checksum: 4587d9e8f050d689fb38b4295e73408401b16de8edecc12026c6f4ae92956705ecfd995ae3845d7fa3ebf19502d5754df9143d91447fd881d86e518f43882c1c - languageName: node - linkType: hard - "stream-combiner2@npm:^1.1.1": version: 1.1.1 resolution: "stream-combiner2@npm:1.1.1" @@ -31099,7 +30135,7 @@ __metadata: languageName: node linkType: hard -"temp@npm:^0.8.1, temp@npm:^0.8.4": +"temp@npm:^0.8.4": version: 0.8.4 resolution: "temp@npm:0.8.4" dependencies: @@ -32159,13 +31195,6 @@ __metadata: languageName: node linkType: hard -"ultron@npm:1.0.x": - version: 1.0.2 - resolution: "ultron@npm:1.0.2" - checksum: f98993b128c774b4769aeeb86030158efb9c2440d3ad91d722af05e7418ddbee6d6fd974c257702b997e5e8fe417ca349c40d16c8cebc8de4b4a2fd40e872309 - languageName: node - linkType: hard - "umask@npm:^1.1.0": version: 1.1.0 resolution: "umask@npm:1.1.0" @@ -32610,17 +31639,6 @@ __metadata: languageName: node linkType: hard -"use-subscription@npm:^1.0.0": - version: 1.5.1 - resolution: "use-subscription@npm:1.5.1" - dependencies: - object-assign: ^4.1.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0 - checksum: 96e64977a573244fd11350a3141b2cf57fb72dd9dd902f387c8a0a565d0a948bc81588bd7378c6ef6defc0d1119f37f73aac4a7a287c8443abd444bd4e7bbea8 - languageName: node - linkType: hard - "use-sync-external-store@npm:^1.0.0": version: 1.2.0 resolution: "use-sync-external-store@npm:1.2.0" @@ -33005,7 +32023,7 @@ __metadata: languageName: node linkType: hard -"walker@npm:^1.0.7, walker@npm:^1.0.8, walker@npm:~1.0.5": +"walker@npm:^1.0.7, walker@npm:^1.0.8": version: 1.0.8 resolution: "walker@npm:1.0.8" dependencies: @@ -33580,16 +32598,6 @@ __metadata: languageName: node linkType: hard -"ws@npm:^1.1.0, ws@npm:^1.1.5": - version: 1.1.5 - resolution: "ws@npm:1.1.5" - dependencies: - options: ">=0.0.5" - ultron: 1.0.x - checksum: d2dfb74fe4f9bfa3f6e9e1d583210ce3d0b29fe376cfd93491e80844494842527ca3e68209205c1d6bc85849a7f379f9cc34150dc9e4b08a82cb031f4fbabc7b - languageName: node - linkType: hard - "ws@npm:^6.1.4": version: 6.2.2 resolution: "ws@npm:6.2.2" @@ -33659,16 +32667,6 @@ __metadata: languageName: node linkType: hard -"xcode@npm:^2.0.0": - version: 2.1.0 - resolution: "xcode@npm:2.1.0" - dependencies: - simple-plist: ^1.0.0 - uuid: ^3.3.2 - checksum: aaa4569f96411f3a024abfa9fb27f2b1dfcf0544b91d2a8b63a36214042b4560dc455942abd9b95836cdd24386d4a6731faf339e32b496b46b4ca810a1dea0e1 - languageName: node - linkType: hard - "xdg-basedir@npm:^4.0.0": version: 4.0.0 resolution: "xdg-basedir@npm:4.0.0" @@ -33745,15 +32743,6 @@ __metadata: languageName: node linkType: hard -"xmldoc@npm:^1.1.2": - version: 1.1.2 - resolution: "xmldoc@npm:1.1.2" - dependencies: - sax: ^1.2.1 - checksum: ada5101e8221e87e3cf0f339a1bec213a7c91ad56fe453c27fc0f5b88feee67437a5604a08484f72041cd6104e23cf86c5000bc9bf658a01176b01b6daded429 - languageName: node - linkType: hard - "xtend@npm:^4.0.0, xtend@npm:^4.0.1, xtend@npm:^4.0.2, xtend@npm:~4.0.0, xtend@npm:~4.0.1": version: 4.0.2 resolution: "xtend@npm:4.0.2"