@react-native-documents/picker 9.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/LICENSE.md +21 -0
  2. package/android/.gradle/8.9/checksums/checksums.lock +0 -0
  3. package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
  4. package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
  5. package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
  6. package/android/.gradle/8.9/gc.properties +0 -0
  7. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  8. package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
  9. package/android/.gradle/vcs-1/gc.properties +0 -0
  10. package/android/build.gradle +80 -0
  11. package/android/src/main/AndroidManifest.xml +3 -0
  12. package/android/src/main/java/com/reactnativedocumentpicker/CopyDestination.kt +12 -0
  13. package/android/src/main/java/com/reactnativedocumentpicker/DocumentMetadataBuilder.kt +79 -0
  14. package/android/src/main/java/com/reactnativedocumentpicker/FileOperations.kt +203 -0
  15. package/android/src/main/java/com/reactnativedocumentpicker/IntentFactory.kt +36 -0
  16. package/android/src/main/java/com/reactnativedocumentpicker/IsKnownTypeImpl.kt +40 -0
  17. package/android/src/main/java/com/reactnativedocumentpicker/MetadataGetter.kt +150 -0
  18. package/android/src/main/java/com/reactnativedocumentpicker/PickOptions.kt +63 -0
  19. package/android/src/main/java/com/reactnativedocumentpicker/PromiseWrapper.java +105 -0
  20. package/android/src/main/java/com/reactnativedocumentpicker/RNDocumentPickerModule.kt +352 -0
  21. package/android/src/main/java/com/reactnativedocumentpicker/RNDocumentPickerPackage.java +49 -0
  22. package/android/src/paper/java/com/reactnativedocumentpicker/NativeDocumentPickerSpec.java +69 -0
  23. package/ios/RCTConvert+RNDocumentPicker.h +8 -0
  24. package/ios/RCTConvert+RNDocumentPicker.mm +16 -0
  25. package/ios/RNDocumentPicker.h +19 -0
  26. package/ios/RNDocumentPicker.mm +128 -0
  27. package/ios/swift/DocPicker.swift +84 -0
  28. package/ios/swift/DocSaver.swift +41 -0
  29. package/ios/swift/DocumentMetadataBuilder.swift +69 -0
  30. package/ios/swift/FileOperations.swift +68 -0
  31. package/ios/swift/IsKnownTypeImpl.swift +42 -0
  32. package/ios/swift/LocalCopyResponse.swift +27 -0
  33. package/ios/swift/PickerBase.swift +78 -0
  34. package/ios/swift/PickerOptions.swift +44 -0
  35. package/ios/swift/PromiseSupport.swift +2 -0
  36. package/ios/swift/PromiseWrapper.swift +92 -0
  37. package/ios/swift/SaverOptions.swift +30 -0
  38. package/jest/build/jest/setup.js +70 -0
  39. package/jest/build/src/errors.js +47 -0
  40. package/jest/build/src/fileTypes.js +53 -0
  41. package/jest/build/src/index.js +22 -0
  42. package/jest/build/src/isKnownType.js +16 -0
  43. package/jest/build/src/keepLocalCopy.js +17 -0
  44. package/jest/build/src/pick.js +50 -0
  45. package/jest/build/src/pickDirectory.js +31 -0
  46. package/jest/build/src/release.js +22 -0
  47. package/jest/build/src/saveDocuments.js +40 -0
  48. package/jest/build/src/spec/NativeDocumentPicker.js +5 -0
  49. package/jest/build/src/types.js +4 -0
  50. package/jest/build/src/validateTypes.js +23 -0
  51. package/jest/build/tsconfig.tsbuildinfo +1 -0
  52. package/lib/commonjs/errors.js +53 -0
  53. package/lib/commonjs/errors.js.map +1 -0
  54. package/lib/commonjs/fileTypes.js +84 -0
  55. package/lib/commonjs/fileTypes.js.map +1 -0
  56. package/lib/commonjs/index.js +74 -0
  57. package/lib/commonjs/index.js.map +1 -0
  58. package/lib/commonjs/isKnownType.js +27 -0
  59. package/lib/commonjs/isKnownType.js.map +1 -0
  60. package/lib/commonjs/keepLocalCopy.js +34 -0
  61. package/lib/commonjs/keepLocalCopy.js.map +1 -0
  62. package/lib/commonjs/package.json +1 -0
  63. package/lib/commonjs/pick.js +93 -0
  64. package/lib/commonjs/pick.js.map +1 -0
  65. package/lib/commonjs/pickDirectory.js +71 -0
  66. package/lib/commonjs/pickDirectory.js.map +1 -0
  67. package/lib/commonjs/release.js +31 -0
  68. package/lib/commonjs/release.js.map +1 -0
  69. package/lib/commonjs/saveDocuments.js +55 -0
  70. package/lib/commonjs/saveDocuments.js.map +1 -0
  71. package/lib/commonjs/spec/NativeDocumentPicker.js +16 -0
  72. package/lib/commonjs/spec/NativeDocumentPicker.js.map +1 -0
  73. package/lib/commonjs/types.js +37 -0
  74. package/lib/commonjs/types.js.map +1 -0
  75. package/lib/commonjs/validateTypes.js +29 -0
  76. package/lib/commonjs/validateTypes.js.map +1 -0
  77. package/lib/module/errors.js +48 -0
  78. package/lib/module/errors.js.map +1 -0
  79. package/lib/module/fileTypes.js +81 -0
  80. package/lib/module/fileTypes.js.map +1 -0
  81. package/lib/module/index.js +13 -0
  82. package/lib/module/index.js.map +1 -0
  83. package/lib/module/isKnownType.js +24 -0
  84. package/lib/module/isKnownType.js.map +1 -0
  85. package/lib/module/keepLocalCopy.js +31 -0
  86. package/lib/module/keepLocalCopy.js.map +1 -0
  87. package/lib/module/package.json +1 -0
  88. package/lib/module/pick.js +90 -0
  89. package/lib/module/pick.js.map +1 -0
  90. package/lib/module/pickDirectory.js +68 -0
  91. package/lib/module/pickDirectory.js.map +1 -0
  92. package/lib/module/release.js +26 -0
  93. package/lib/module/release.js.map +1 -0
  94. package/lib/module/saveDocuments.js +52 -0
  95. package/lib/module/saveDocuments.js.map +1 -0
  96. package/lib/module/spec/NativeDocumentPicker.js +13 -0
  97. package/lib/module/spec/NativeDocumentPicker.js.map +1 -0
  98. package/lib/module/types.js +33 -0
  99. package/lib/module/types.js.map +1 -0
  100. package/lib/module/validateTypes.js +24 -0
  101. package/lib/module/validateTypes.js.map +1 -0
  102. package/lib/typescript/errors.d.ts +40 -0
  103. package/lib/typescript/errors.d.ts.map +1 -0
  104. package/lib/typescript/fileTypes.d.ts +94 -0
  105. package/lib/typescript/fileTypes.d.ts.map +1 -0
  106. package/lib/typescript/index.d.ts +13 -0
  107. package/lib/typescript/index.d.ts.map +1 -0
  108. package/lib/typescript/isKnownType.d.ts +41 -0
  109. package/lib/typescript/isKnownType.d.ts.map +1 -0
  110. package/lib/typescript/keepLocalCopy.d.ts +46 -0
  111. package/lib/typescript/keepLocalCopy.d.ts.map +1 -0
  112. package/lib/typescript/pick.d.ts +84 -0
  113. package/lib/typescript/pick.d.ts.map +1 -0
  114. package/lib/typescript/pickDirectory.d.ts +62 -0
  115. package/lib/typescript/pickDirectory.d.ts.map +1 -0
  116. package/lib/typescript/release.d.ts +24 -0
  117. package/lib/typescript/release.d.ts.map +1 -0
  118. package/lib/typescript/saveDocuments.d.ts +55 -0
  119. package/lib/typescript/saveDocuments.d.ts.map +1 -0
  120. package/lib/typescript/spec/NativeDocumentPicker.d.ts +29 -0
  121. package/lib/typescript/spec/NativeDocumentPicker.d.ts.map +1 -0
  122. package/lib/typescript/types.d.ts +95 -0
  123. package/lib/typescript/types.d.ts.map +1 -0
  124. package/lib/typescript/validateTypes.d.ts +3 -0
  125. package/lib/typescript/validateTypes.d.ts.map +1 -0
  126. package/package.json +92 -0
  127. package/react-native-document-picker.podspec +30 -0
  128. package/src/errors.ts +49 -0
  129. package/src/fileTypes.ts +92 -0
  130. package/src/index.ts +47 -0
  131. package/src/isKnownType.ts +48 -0
  132. package/src/keepLocalCopy.ts +51 -0
  133. package/src/pick.ts +151 -0
  134. package/src/pickDirectory.ts +93 -0
  135. package/src/release.ts +36 -0
  136. package/src/saveDocuments.ts +99 -0
  137. package/src/spec/NativeDocumentPicker.ts +31 -0
  138. package/src/types.ts +119 -0
  139. package/src/validateTypes.ts +26 -0
@@ -0,0 +1,128 @@
1
+ // LICENSE: see License.md in the package root
2
+
3
+
4
+ #import "RNDocumentPicker.h"
5
+
6
+ #import "RCTConvert+RNDocumentPicker.h"
7
+ // this header file is generated by Xcode: https://developer.apple.com/documentation/swift/importing-swift-into-objective-c
8
+ // if it cannot be found, try cleaning the build folder and Xcode derived data folder
9
+ #import "react_native_document_picker-Swift.h"
10
+ // for UIModalPresentationStyle conversion
11
+ // remove after https://github.com/facebook/react-native/commit/2d547a3252b328251e49dabfeec85f8d46c85411 is released
12
+ #import <React/RCTModalHostViewManager.h>
13
+
14
+ @interface RNDocumentPicker ()
15
+ @end
16
+
17
+ @implementation RNDocumentPicker {
18
+ DocPicker *docPicker;
19
+ DocSaver *docSaver;
20
+ }
21
+
22
+ - (instancetype)init {
23
+ if ((self = [super init])) {
24
+ docPicker = [DocPicker new];
25
+ docSaver = [DocSaver new];
26
+ }
27
+ return self;
28
+ }
29
+
30
+ + (BOOL)requiresMainQueueSetup {
31
+ return NO;
32
+ }
33
+
34
+ RCT_EXPORT_MODULE()
35
+
36
+ RCT_EXPORT_METHOD(pick:
37
+ (NSDictionary *) options
38
+ resolve:
39
+ (RCTPromiseResolveBlock) resolve
40
+ reject:
41
+ (RCTPromiseRejectBlock) reject)
42
+ {
43
+ // https://stackoverflow.com/questions/5270519/what-is-difference-between-uimodaltransitionstyle-and-uimodalpresentationstyle
44
+ UIModalPresentationStyle presentationStyle = [RCTConvert UIModalPresentationStyle:options[@"presentationStyle"]];
45
+ UIModalTransitionStyle transitionStyle = [RCTConvert UIModalTransitionStyle:options[@"transitionStyle"]];
46
+ NSArray *allowedUTIs = [RCTConvert NSArray:options[@"type"]];
47
+ BOOL allowMultiple = [RCTConvert BOOL:options[@"allowMultiSelection"]];
48
+ BOOL showExtensions = [RCTConvert BOOL:options[@"showFileExtensions"]];
49
+ NSString *mode = options[@"mode"];
50
+ NSString *initialDir = options[@"initialDirectoryUrl"];
51
+ BOOL requestLongTermAccess = [RCTConvert BOOL:options[@"requestLongTermAccess"]];
52
+
53
+ PickerOptions *pickerOptions = [[PickerOptions alloc] initWithTypes:allowedUTIs
54
+ mode:mode
55
+ initialDirectoryUrl:initialDir
56
+ allowMultiSelection:allowMultiple
57
+ shouldShowFileExtensions:showExtensions
58
+ transitionStyle:transitionStyle
59
+ presentationStyle:presentationStyle
60
+ requestLongTermAccess:requestLongTermAccess];
61
+
62
+ [docPicker presentWithOptions:pickerOptions resolve:resolve reject:reject];
63
+ }
64
+
65
+ RCT_EXPORT_METHOD(pickDirectory:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
66
+ void (^resolveWithDirectoryUri)(NSArray<NSDictionary *> *) = ^void(NSArray<NSDictionary *> *pickedValues) {
67
+ NSDictionary* firstObject = pickedValues.firstObject;
68
+ resolve(firstObject);
69
+ };
70
+
71
+ [self pick:options resolve:resolveWithDirectoryUri reject:reject];
72
+ }
73
+
74
+ RCT_EXPORT_METHOD(keepLocalCopy:
75
+ (NSDictionary *) options
76
+ resolve:
77
+ (RCTPromiseResolveBlock) resolve
78
+ reject:
79
+ (RCTPromiseRejectBlock) reject) {
80
+ NSArray *uris = options[@"files"];
81
+ NSString *destination = options[@"destination"];
82
+
83
+ [[FileOperations class] keepLocalCopyAtUniqueDestinationFrom:uris destinationPreset:destination resolve:resolve];
84
+ }
85
+
86
+ RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSDictionary *, isKnownType:(NSString *)kind value:(NSString *)value) {
87
+ NSDictionary* result = [[IsKnownTypeImpl class] checkType:kind value:value];
88
+ return result;
89
+ }
90
+
91
+ RCT_EXPORT_METHOD(writeDocuments:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
92
+ UIModalPresentationStyle presentationStyle = [RCTConvert UIModalPresentationStyle:options[@"presentationStyle"]];
93
+ UIModalTransitionStyle transitionStyle = [RCTConvert UIModalTransitionStyle:options[@"transitionStyle"]];
94
+ BOOL showExtensions = [RCTConvert BOOL:options[@"showFileExtensions"]];
95
+ BOOL asCopy = [RCTConvert BOOL:options[@"copy"]];
96
+
97
+ NSString *initialDir = options[@"initialDirectoryUri"];
98
+ NSArray<NSString*> *documentUrl = options[@"sourceUris"];
99
+
100
+ SaverOptions* saverOptions = [[SaverOptions alloc] initWithSourceUrlStrings:documentUrl asCopy:asCopy initialDirectoryUrl:initialDir shouldShowFileExtensions:showExtensions transitionStyle:transitionStyle presentationStyle:presentationStyle];
101
+
102
+ [docSaver presentWithOptions:saverOptions resolve:resolve reject:reject];
103
+ }
104
+
105
+ RCT_EXPORT_METHOD(releaseSecureAccess:(NSArray *)uris resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
106
+ [docPicker stopAccessingOpenedUrls:uris];
107
+ resolve([NSNull null]);
108
+ }
109
+
110
+ RCT_EXPORT_METHOD(releaseLongTermAccess:(NSArray *)uris resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
111
+ resolve([NSNull null]);
112
+ }
113
+
114
+ // Thanks to this guard, we won't compile this code when we build for the old architecture.
115
+ #ifdef RCT_NEW_ARCH_ENABLED
116
+
117
+ - (void)saveDocument:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
118
+ // not needed on iOS
119
+ }
120
+
121
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
122
+ (const facebook::react::ObjCTurboModule::InitParams &)params {
123
+ return std::make_shared<facebook::react::NativeDocumentPickerSpecJSI>(params);
124
+ }
125
+
126
+ #endif
127
+
128
+ @end
@@ -0,0 +1,84 @@
1
+ // LICENSE: see License.md in the package root
2
+
3
+ import Foundation
4
+ import UniformTypeIdentifiers
5
+ import MobileCoreServices
6
+
7
+ @objc public class DocPicker: PickerWithMetadataImpl {
8
+
9
+ var currentOptions: PickerOptions? = nil
10
+
11
+ @objc public func present(options: PickerOptions, resolve: @escaping RNDPPromiseResolveBlock, reject: @escaping RNDPPromiseRejectBlock) {
12
+ // TODO fix callsite param
13
+ if (!promiseWrapper.trySetPromiseRejectingIncoming(resolve, rejecter: reject, fromCallSite: "pick")) {
14
+ return;
15
+ }
16
+ currentOptions = options;
17
+ DispatchQueue.main.async {
18
+ let documentPicker = UIDocumentPickerViewController(forOpeningContentTypes: options.allowedTypes, asCopy: options.modeAsCopy())
19
+
20
+ documentPicker.modalPresentationStyle = options.presentationStyle
21
+ documentPicker.allowsMultipleSelection = options.allowMultiSelection
22
+ documentPicker.modalTransitionStyle = options.transitionStyle
23
+ // documentPicker.directoryURL = options.initialDirectoryUrl
24
+ // documentPicker.shouldShowFileExtensions = options.shouldShowFileExtensions
25
+
26
+ self.presentInternal(documentPicker: documentPicker)
27
+ }
28
+ }
29
+
30
+ public func getMetadataFor(url: URL) throws -> DocumentMetadataBuilder {
31
+ if (currentOptions?.isOpenMode() == true) {
32
+ return try self.getOpenedDocumentInfo(url: url, requestLongTermAccess: currentOptions?.requestLongTermAccess ?? false)
33
+ } else {
34
+ return try self.getAnyModeMetadata(url: url)
35
+ }
36
+ }
37
+
38
+ private func getAnyModeMetadata(url: URL) throws -> DocumentMetadataBuilder {
39
+ let resourceValues = try url.resourceValues(forKeys: [.fileSizeKey, .nameKey, .isDirectoryKey, .contentTypeKey])
40
+
41
+ return DocumentMetadataBuilder(forUri: url, resourceValues: resourceValues)
42
+ }
43
+
44
+ enum KeepLocalCopyError: Error {
45
+ case sourceAccessError
46
+ }
47
+
48
+ func getOpenedDocumentInfo(url: URL, requestLongTermAccess: Bool) throws -> DocumentMetadataBuilder {
49
+ guard url.startAccessingSecurityScopedResource() else {
50
+ throw KeepLocalCopyError.sourceAccessError
51
+ }
52
+
53
+ // url.stopAccessingSecurityScopedResource() must be called later
54
+ openedUrls.append(url)
55
+
56
+ // Use file coordination for reading and writing any of the URL’s content.
57
+ var error: NSError? = nil
58
+ var success = false
59
+ var metadataBuilder: DocumentMetadataBuilder = DocumentMetadataBuilder(forUri: url)
60
+
61
+ NSFileCoordinator().coordinate(readingItemAt: url, error: &error) { (url) in
62
+ do {
63
+ metadataBuilder = try self.getAnyModeMetadata(url: url)
64
+ success = true
65
+ } catch {
66
+ metadataBuilder.setMetadataReadingError(error)
67
+ }
68
+
69
+ if (requestLongTermAccess == true) {
70
+ do {
71
+ let bookmarkData = try url.bookmarkData(options: .minimalBookmark, includingResourceValuesForKeys: nil, relativeTo: nil)
72
+ metadataBuilder.setBookmark(bookmarkData)
73
+ } catch {
74
+ metadataBuilder.setBookmarkError(error)
75
+ }
76
+ }
77
+ }
78
+ if let err = error, success == false {
79
+ throw err
80
+ }
81
+ return metadataBuilder
82
+ }
83
+
84
+ }
@@ -0,0 +1,41 @@
1
+ //
2
+ // DocSaver.swift
3
+ // react-native-document-picker
4
+ //
5
+ // Created by Vojtech Novak on 25.05.2024.
6
+ //
7
+
8
+ // LICENSE: see License.md in the package root
9
+
10
+ import Foundation
11
+ import UniformTypeIdentifiers
12
+ import MobileCoreServices
13
+
14
+ @objc public class DocSaver: PickerWithMetadataImpl {
15
+
16
+ @objc public func present(options: SaverOptions, resolve: @escaping (Any?) -> Void, reject: @escaping (String?, String?, Error?) -> Void) {
17
+ if (!promiseWrapper.trySetPromiseRejectingIncoming(resolve, rejecter: reject, fromCallSite: "saveDocuments")) {
18
+ return;
19
+ }
20
+ DispatchQueue.main.async {
21
+ let documentPicker = UIDocumentPickerViewController(forExporting: options.sourceUrls, asCopy: options.asCopy)
22
+
23
+ documentPicker.modalPresentationStyle = options.presentationStyle
24
+ documentPicker.modalTransitionStyle = options.transitionStyle
25
+ // documentPicker.directoryURL = options.initialDirectoryUrl
26
+ // documentPicker.shouldShowFileExtensions = options.shouldShowFileExtensions
27
+
28
+ self.presentInternal(documentPicker: documentPicker)
29
+ }
30
+ }
31
+
32
+ public func getMetadataFor(url: URL) throws -> DocumentMetadataBuilder {
33
+ let name = url.lastPathComponent.removingPercentEncoding
34
+
35
+ var resourceValues = URLResourceValues()
36
+ resourceValues.name = name
37
+
38
+ return DocumentMetadataBuilder(forUri: url, resourceValues: resourceValues)
39
+ }
40
+
41
+ }
@@ -0,0 +1,69 @@
1
+ // LICENSE: see License.md in the package root
2
+
3
+ import Foundation
4
+ import UniformTypeIdentifiers
5
+
6
+ public class DocumentMetadataBuilder {
7
+ private let uri: URL
8
+ private let resourceValues: URLResourceValues?
9
+ private var bookmarkData: Data?
10
+ private var metadataError: Error?
11
+ private var bookmarkError: Error?
12
+
13
+ init(forUri uri: URL) {
14
+ self.uri = uri
15
+ self.resourceValues = nil
16
+ }
17
+
18
+ init(forUri uri: URL, resourceValues: URLResourceValues) {
19
+ self.uri = uri
20
+ self.resourceValues = resourceValues
21
+ }
22
+
23
+ convenience init(forUri uri: URL, error: Error) {
24
+ self.init(forUri: uri)
25
+ self.metadataError = error
26
+ }
27
+
28
+ func setBookmark(_ bookmark: Data) {
29
+ self.bookmarkData = bookmark
30
+ }
31
+
32
+ func setBookmarkError(_ bookmarkError: Error) {
33
+ self.bookmarkError = bookmarkError
34
+ }
35
+
36
+ func setMetadataReadingError(_ error: Error) {
37
+ self.metadataError = error
38
+ }
39
+
40
+ func build() -> [String: Any?] {
41
+ var dictionary: [String: Any?] = [:]
42
+ if (resourceValues?.isDirectory ?? false == false) {
43
+ let utTypeFromFile: UTType? = resourceValues?.contentType
44
+ let utType: UTType? = utTypeFromFile ?? UTType(filenameExtension: uri.pathExtension)
45
+
46
+ dictionary = [
47
+ "name": resourceValues?.name,
48
+ "size": resourceValues?.fileSize,
49
+ "type": utType?.preferredMIMEType,
50
+ "nativeType": utType?.identifier,
51
+ "error": metadataError?.localizedDescription,
52
+ "isVirtual": false,
53
+ "convertibleToMimeTypes": nil
54
+ ]
55
+ }
56
+
57
+ dictionary["uri"] = uri.absoluteString
58
+
59
+ if let bookmark = bookmarkData {
60
+ dictionary["bookmarkStatus"] = "success"
61
+ dictionary["bookmark"] = bookmark.base64EncodedString()
62
+ } else if let bookmarkError = bookmarkError {
63
+ dictionary["bookmarkStatus"] = "error"
64
+ dictionary["bookmarkError"] = bookmarkError.localizedDescription
65
+ }
66
+
67
+ return dictionary
68
+ }
69
+ }
@@ -0,0 +1,68 @@
1
+ // LICENSE: see License.md in the package root
2
+
3
+ import Foundation
4
+
5
+ @objc public class FileOperations: NSObject {
6
+
7
+ @objc public static func keepLocalCopyAtUniqueDestination(from: Array<Dictionary<String, String>>, destinationPreset: String, resolve: @escaping RNDPPromiseResolveBlock) {
8
+ Task {
9
+ let results = await moveFiles(from: from, destinationPreset: destinationPreset)
10
+ resolve(results)
11
+ }
12
+ }
13
+
14
+ static func moveFiles(from: Array<Dictionary<String, String>>, destinationPreset: String) async -> [[String: String?]] {
15
+ let destinationRootDir = getDirectoryForFileDestination(destinationPreset)
16
+ let uniqueSubDirName = UUID().uuidString
17
+ let destinationDir: URL = destinationRootDir.appendingPathComponent("\(uniqueSubDirName)/", isDirectory: true)
18
+ // TODO do we need all of this Task dance?
19
+
20
+ return await withTaskGroup(of: LocalCopyResponse.self) { group in
21
+ var results: Array<Dictionary<String, String?>> = [[String: String?]]()
22
+
23
+ for dictionary in from {
24
+ group.addTask {
25
+ do {
26
+ guard let uriString = dictionary["uri"], let uri = URL(string: uriString) else {
27
+ return LocalCopyResponse.error(sourceUri: dictionary["uri"], copyError: "Invalid URI")
28
+ }
29
+ guard let fileName = dictionary["fileName"] else {
30
+ return LocalCopyResponse.error(sourceUri: uri.absoluteString, copyError: "Invalid fileName")
31
+ }
32
+
33
+ let destinationUrl = try moveToDestination(from: uri, usingFilename: fileName, destinationDir: destinationDir)
34
+ return LocalCopyResponse.success(sourceUri: uri.absoluteString, localUri: destinationUrl.absoluteString)
35
+ } catch {
36
+ return LocalCopyResponse.error(sourceUri: dictionary["uri"]!, copyError: error.localizedDescription)
37
+ }
38
+ }
39
+ }
40
+
41
+ for await result in group {
42
+ results.append(result.dictionaryRepresentation)
43
+ }
44
+
45
+ return results
46
+ }
47
+ }
48
+
49
+ static func moveToDestination(from: URL, usingFilename fileName: String, destinationDir: URL) throws -> URL {
50
+ let destinationFile = destinationDir.appendingPathComponent(fileName).standardized
51
+
52
+ guard destinationFile.path.hasPrefix(destinationDir.standardized.path) else {
53
+ throw NSError(domain: "PathTraversalPrevention", code: 400, userInfo: [NSLocalizedDescriptionKey: "The copied file is attempting to write outside of the target directory."])
54
+ }
55
+
56
+ try FileManager.default.createDirectory(at: destinationDir, withIntermediateDirectories: true, attributes: nil)
57
+ try FileManager.default.moveItem(at: from, to: destinationFile)
58
+
59
+ return destinationFile
60
+ }
61
+
62
+ static func getDirectoryForFileDestination(_ copyToDirectory: String) -> URL {
63
+ if copyToDirectory == "documentDirectory" {
64
+ return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
65
+ }
66
+ return FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
67
+ }
68
+ }
@@ -0,0 +1,42 @@
1
+ //
2
+ // IsKnownTypeImpl.swift
3
+ // react-native-document-picker
4
+ //
5
+ // Created by Vojtech Novak on 26.05.2024.
6
+ // LICENSE: see License.md in the package root
7
+ //
8
+
9
+ import Foundation
10
+ import UniformTypeIdentifiers
11
+
12
+ @objc public class IsKnownTypeImpl: NSObject {
13
+
14
+ @objc public static func checkType(_ kind: String, value: String) -> NSDictionary {
15
+ let dict = getTypeResult(kind, value: value)
16
+ return NSDictionary(dictionary: dict as [AnyHashable: Any])
17
+ }
18
+
19
+ static func getTypeResult(_ kind: String, value: String) -> Dictionary<String, Any?> {
20
+ if let utType = createUTType(kind: kind, value: value), utType.isDeclared == true {
21
+ return ["isKnown": true,
22
+ "UTType": utType.identifier,
23
+ "preferredFilenameExtension": utType.preferredFilenameExtension,
24
+ "mimeType": utType.preferredMIMEType]
25
+ }
26
+ return ["isKnown": false, "UTType": nil, "preferredFilenameExtension": nil, "mimeType": nil]
27
+ }
28
+
29
+ static func createUTType(kind: String, value: String) -> UTType? {
30
+ switch kind {
31
+ case "UTType":
32
+ return UTType(value)
33
+ case "mimeType":
34
+ return UTType(mimeType: value)
35
+ case "extension":
36
+ return UTType(filenameExtension: value)
37
+ default:
38
+ return nil
39
+ }
40
+ }
41
+ }
42
+
@@ -0,0 +1,27 @@
1
+ // LICENSE: see License.md in the package root
2
+
3
+ import Foundation
4
+
5
+ // export type LocalCopyResponse =
6
+ // | {
7
+ // status: 'success'
8
+ // sourceUri: string
9
+ // localUri: string
10
+ // }
11
+ // | { status: 'error'; sourceUri: string; copyError: string }
12
+
13
+ enum LocalCopyResponse {
14
+ case success(sourceUri: String, localUri: String)
15
+ case error(sourceUri: String?, copyError: String)
16
+
17
+ var dictionaryRepresentation: [String: String?] {
18
+ switch self {
19
+ case .success(let sourceUri, let localUri):
20
+ return ["sourceUri": sourceUri, "localUri": localUri, "status": "success"]
21
+ case .error(let sourceUri, let copyError):
22
+ var result = ["copyError": copyError, "status": "error"]
23
+ result["sourceUri"] = sourceUri ?? nil
24
+ return result
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,78 @@
1
+ //
2
+ // DocSaver.swift
3
+ // react-native-document-picker
4
+ //
5
+ // Created by Vojtech Novak on 25.05.2024.
6
+ //
7
+
8
+ // LICENSE: see License.md in the package root
9
+
10
+ import Foundation
11
+ import UniformTypeIdentifiers
12
+ import MobileCoreServices
13
+
14
+ public protocol GetsMetadataProtocol {
15
+ func getMetadataFor(url: URL) throws -> DocumentMetadataBuilder
16
+ }
17
+
18
+ // https://stackoverflow.com/a/51333906/2070942
19
+ public typealias PickerWithMetadataImpl = PickerBase & GetsMetadataProtocol
20
+
21
+ public class PickerBase: NSObject, UIDocumentPickerDelegate, UIAdaptivePresentationControllerDelegate {
22
+ let promiseWrapper = PromiseWrapper()
23
+ var openedUrls: Array<URL> = []
24
+
25
+ func presentInternal(documentPicker: UIDocumentPickerViewController) {
26
+ documentPicker.delegate = self
27
+ documentPicker.presentationController?.delegate = self;
28
+
29
+ if let viewController = RCTPresentedViewController() {
30
+ viewController.present(documentPicker, animated: true, completion: nil)
31
+ } else {
32
+ let error = NSError(domain: NSCocoaErrorDomain, code: 0, userInfo: nil)
33
+ promiseWrapper.reject("RCTPresentedViewController was nil", withCode: "PRESENTER_IS_NULL", withError: error)
34
+ }
35
+ }
36
+
37
+ public func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
38
+ DispatchQueue.global(qos: .userInitiated).async {
39
+ // this doesn't run on the main thread
40
+ let documentsInfo = urls.compactMap(self.createDocumentMetadata).compactMap { $0.build() }
41
+ self.promiseWrapper.resolve(documentsInfo)
42
+ }
43
+ // https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/DocumentPickerProgrammingGuide/AccessingDocuments/AccessingDocuments.html#//apple_ref/doc/uid/TP40014451-CH2-SW4 "Accessing Files Outside Your Sandbox"
44
+ // https://developer.apple.com/documentation/uikit/view_controllers/providing_access_to_directories
45
+ }
46
+
47
+ private func createDocumentMetadata(for url: URL) -> DocumentMetadataBuilder? {
48
+ guard let subclassThatGetsMetadata = self as? GetsMetadataProtocol else {
49
+ let error = NSError(domain: NSCocoaErrorDomain, code: 0, userInfo: nil)
50
+ self.promiseWrapper.reject("PickerBase", withCode: "BAD_CLASS", withError: error)
51
+ return nil
52
+ }
53
+
54
+ do {
55
+ return try subclassThatGetsMetadata.getMetadataFor(url: url)
56
+ } catch {
57
+ return DocumentMetadataBuilder(forUri: url, error: error)
58
+ }
59
+ }
60
+
61
+ public func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
62
+ promiseWrapper.rejectAsUserCancelledOperation()
63
+ }
64
+
65
+ public func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
66
+ promiseWrapper.rejectAsUserCancelledOperation()
67
+ }
68
+
69
+ @objc public func stopAccessingOpenedUrls(_ urlStrings: [String]) {
70
+ let incomingUrls = Set(urlStrings.compactMap { URL(string: $0) })
71
+ openedUrls.removeAll { url in
72
+ guard incomingUrls.contains(url) else { return false }
73
+ url.stopAccessingSecurityScopedResource()
74
+ return true
75
+ }
76
+ }
77
+
78
+ }
@@ -0,0 +1,44 @@
1
+ // LICENSE: see License.md in the package root
2
+
3
+ import Foundation
4
+ import UIKit
5
+ import UniformTypeIdentifiers
6
+
7
+ @objc public class PickerOptions: NSObject {
8
+ let allowedTypes: Array<UTType>
9
+ let mode: String // "import" or "open"
10
+ let allowMultiSelection: Bool
11
+ let transitionStyle: UIModalTransitionStyle
12
+ let presentationStyle: UIModalPresentationStyle
13
+ let initialDirectoryUrl: URL?
14
+ let shouldShowFileExtensions: Bool
15
+ let requestLongTermAccess: Bool
16
+
17
+ @objc public init(types: Array<String>, mode: String = "import", initialDirectoryUrl: String? = nil, allowMultiSelection: Bool, shouldShowFileExtensions: Bool, transitionStyle: UIModalTransitionStyle = .coverVertical, presentationStyle: UIModalPresentationStyle = .fullScreen, requestLongTermAccess: Bool = false) {
18
+ // TODO check if types were valid
19
+ allowedTypes = types.compactMap {
20
+ UTType($0)
21
+ }
22
+ self.allowMultiSelection = allowMultiSelection
23
+ self.transitionStyle = transitionStyle
24
+ self.presentationStyle = presentationStyle
25
+ self.mode = mode
26
+ if let unwrappedUrl = initialDirectoryUrl, let url = URL(string: unwrappedUrl) {
27
+ self.initialDirectoryUrl = url
28
+ } else {
29
+ self.initialDirectoryUrl = nil
30
+ }
31
+ self.shouldShowFileExtensions = shouldShowFileExtensions
32
+ self.requestLongTermAccess = requestLongTermAccess
33
+ }
34
+
35
+ // asCopy: if true, the picker will give you access to a local copy of the document, otherwise you will have access to the original document
36
+ public func modeAsCopy() -> Bool {
37
+ return self.mode == "import"
38
+ }
39
+
40
+ public func isOpenMode() -> Bool {
41
+ return self.mode == "open"
42
+ }
43
+
44
+ }
@@ -0,0 +1,2 @@
1
+ public typealias RNDPPromiseResolveBlock = ((Any?) -> Void)
2
+ public typealias RNDPPromiseRejectBlock = (String?, String?, Error?) -> Void
@@ -0,0 +1,92 @@
1
+ // LICENSE: see License.md in the package root
2
+
3
+ import Foundation
4
+
5
+ class PromiseWrapper {
6
+ private var promiseResolve: RNDPPromiseResolveBlock?
7
+ private var promiseReject: RNDPPromiseRejectBlock?
8
+ private var nameOfCallInProgress: String?
9
+
10
+ private let E_DOCUMENT_PICKER_CANCELED = "E_DOCUMENT_PICKER_CANCELED"
11
+ private let ASYNC_OP_IN_PROGRESS = "ASYNC_OP_IN_PROGRESS"
12
+
13
+ func setPromiseRejectingPrevious(_ resolve: @escaping RNDPPromiseResolveBlock,
14
+ rejecter reject: @escaping RNDPPromiseRejectBlock,
15
+ fromCallSite callsite: String) {
16
+ if let previousReject = promiseReject {
17
+ rejectPreviousPromiseBecauseNewOneIsInProgress(previousReject, requestedOperation: callsite)
18
+ }
19
+ promiseResolve = resolve
20
+ promiseReject = reject
21
+ nameOfCallInProgress = callsite
22
+ }
23
+
24
+ func trySetPromiseRejectingIncoming(_ resolve: @escaping RNDPPromiseResolveBlock,
25
+ rejecter reject: @escaping RNDPPromiseRejectBlock,
26
+ fromCallSite callsite: String) -> Bool {
27
+ if promiseReject != nil {
28
+ rejectNewPromiseBecauseOldOneIsInProgress(reject, requestedOperation: callsite)
29
+ return false
30
+ }
31
+ promiseResolve = resolve
32
+ promiseReject = reject
33
+ nameOfCallInProgress = callsite
34
+ return true
35
+ }
36
+
37
+ func resolve(_ result: Any?) {
38
+ guard let resolver = promiseResolve else {
39
+ print("cannot resolve promise because it's null")
40
+ return
41
+ }
42
+ resetMembers()
43
+ resolver(result)
44
+ }
45
+
46
+ func reject(_ message: String, withError error: NSError) {
47
+ let errorCode = String(error.code)
48
+ reject(message, withCode: errorCode, withError: error)
49
+ }
50
+
51
+ func reject(_ message: String, withCode errorCode: String, withError error: NSError) {
52
+ guard let rejecter = promiseReject else {
53
+ print("cannot reject promise because it's null")
54
+ return
55
+ }
56
+ let errorMessage = "RNDPPromiseWrapper: \(message), \(error.description)"
57
+ resetMembers()
58
+ rejecter(errorCode, errorMessage, error)
59
+ }
60
+
61
+ func rejectAsUserCancelledOperation() {
62
+ let error = NSError(domain: NSCocoaErrorDomain,
63
+ code: NSUserCancelledError,
64
+ userInfo: nil)
65
+ reject("user canceled the document picker",
66
+ withCode: E_DOCUMENT_PICKER_CANCELED,
67
+ withError: error)
68
+ }
69
+
70
+ private func resetMembers() {
71
+ promiseResolve = nil
72
+ promiseReject = nil
73
+ nameOfCallInProgress = nil
74
+ }
75
+
76
+ // TODO error messages
77
+ private func rejectPreviousPromiseBecauseNewOneIsInProgress(_ reject: RNDPPromiseRejectBlock,
78
+ requestedOperation callSiteName: String) {
79
+ let msg = "Warning: previous promise did not settle and was overwritten. " +
80
+ "You've called \"\(callSiteName)\" while \"\(nameOfCallInProgress ?? "")\" " +
81
+ "was already in progress and has not completed yet."
82
+ reject(ASYNC_OP_IN_PROGRESS, msg, nil)
83
+ }
84
+
85
+ private func rejectNewPromiseBecauseOldOneIsInProgress(_ reject: RNDPPromiseRejectBlock,
86
+ requestedOperation callSiteName: String) {
87
+ let msg = "Warning: previous promise did not settle and you attempted to overwrite it. " +
88
+ "You've called \"\(callSiteName)\" while \"\(nameOfCallInProgress ?? "")\" " +
89
+ "was already in progress and has not completed yet."
90
+ reject(ASYNC_OP_IN_PROGRESS, msg, nil)
91
+ }
92
+ }