react-native-cloud-storage 2.3.0 → 3.0.0

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 (88) hide show
  1. package/README.md +1 -1
  2. package/android/build.gradle +1 -14
  3. package/android/src/main/java/com/voicekit/CloudStorageLocalFileSystemModule.kt +16 -20
  4. package/android/src/main/java/com/voicekit/CloudStoragePackage.kt +23 -8
  5. package/dist/commonjs/cloud-storage.js +66 -31
  6. package/dist/commonjs/cloud-storage.js.map +1 -1
  7. package/dist/commonjs/specs/NativeCloudStorageCloudKitIOS.js +9 -0
  8. package/dist/commonjs/specs/NativeCloudStorageCloudKitIOS.js.map +1 -0
  9. package/dist/commonjs/specs/NativeCloudStorageLocalFileSystem.js +9 -0
  10. package/dist/commonjs/specs/NativeCloudStorageLocalFileSystem.js.map +1 -0
  11. package/dist/commonjs/storages/cloudkit.js +5 -3
  12. package/dist/commonjs/storages/cloudkit.js.map +1 -1
  13. package/dist/commonjs/storages/google-drive/client.js +3 -2
  14. package/dist/commonjs/storages/google-drive/client.js.map +1 -1
  15. package/dist/commonjs/storages/google-drive/index.js +99 -77
  16. package/dist/commonjs/storages/google-drive/index.js.map +1 -1
  17. package/dist/commonjs/types/main.js.map +1 -1
  18. package/dist/commonjs/utils/constants.js +2 -1
  19. package/dist/commonjs/utils/constants.js.map +1 -1
  20. package/dist/commonjs/utils/local-fs.js +3 -2
  21. package/dist/commonjs/utils/local-fs.js.map +1 -1
  22. package/dist/commonjs/utils/native.js.map +1 -1
  23. package/dist/module/cloud-storage.js +68 -33
  24. package/dist/module/cloud-storage.js.map +1 -1
  25. package/dist/module/specs/NativeCloudStorageCloudKitIOS.js +5 -0
  26. package/dist/module/specs/NativeCloudStorageCloudKitIOS.js.map +1 -0
  27. package/dist/module/specs/NativeCloudStorageLocalFileSystem.js +5 -0
  28. package/dist/module/specs/NativeCloudStorageLocalFileSystem.js.map +1 -0
  29. package/dist/module/storages/cloudkit.js +3 -2
  30. package/dist/module/storages/cloudkit.js.map +1 -1
  31. package/dist/module/storages/google-drive/client.js +3 -2
  32. package/dist/module/storages/google-drive/client.js.map +1 -1
  33. package/dist/module/storages/google-drive/index.js +99 -77
  34. package/dist/module/storages/google-drive/index.js.map +1 -1
  35. package/dist/module/types/main.js.map +1 -1
  36. package/dist/module/utils/constants.js +2 -1
  37. package/dist/module/utils/constants.js.map +1 -1
  38. package/dist/module/utils/local-fs.js +2 -2
  39. package/dist/module/utils/local-fs.js.map +1 -1
  40. package/dist/module/utils/native.js.map +1 -1
  41. package/dist/typescript/cloud-storage.d.ts +7 -0
  42. package/dist/typescript/cloud-storage.d.ts.map +1 -1
  43. package/dist/typescript/specs/NativeCloudStorageCloudKitIOS.d.ts +30 -0
  44. package/dist/typescript/specs/NativeCloudStorageCloudKitIOS.d.ts.map +1 -0
  45. package/dist/typescript/specs/NativeCloudStorageLocalFileSystem.d.ts +24 -0
  46. package/dist/typescript/specs/NativeCloudStorageLocalFileSystem.d.ts.map +1 -0
  47. package/dist/typescript/storages/cloudkit.d.ts +3 -0
  48. package/dist/typescript/storages/cloudkit.d.ts.map +1 -1
  49. package/dist/typescript/storages/google-drive/client.d.ts +1 -1
  50. package/dist/typescript/storages/google-drive/client.d.ts.map +1 -1
  51. package/dist/typescript/storages/google-drive/index.d.ts +7 -1
  52. package/dist/typescript/storages/google-drive/index.d.ts.map +1 -1
  53. package/dist/typescript/types/main.d.ts +8 -0
  54. package/dist/typescript/types/main.d.ts.map +1 -1
  55. package/dist/typescript/types/native.d.ts +6 -43
  56. package/dist/typescript/types/native.d.ts.map +1 -1
  57. package/dist/typescript/utils/constants.d.ts.map +1 -1
  58. package/dist/typescript/utils/local-fs.d.ts +1 -2
  59. package/dist/typescript/utils/local-fs.d.ts.map +1 -1
  60. package/dist/typescript/utils/native.d.ts +1 -1
  61. package/dist/typescript/utils/native.d.ts.map +1 -1
  62. package/ios/CloudStorage-Bridging-Header.h +0 -1
  63. package/ios/CloudStorageCloudKit.swift +15 -14
  64. package/ios/CloudStorageLocalFileSystem.swift +7 -6
  65. package/ios/RCTCloudStorageCloudKit.mm +209 -0
  66. package/ios/RCTCloudStorageLocalFileSystem.mm +149 -0
  67. package/ios/Utils/CloudKitUtils.swift +8 -2
  68. package/ios/Utils/FileUtils.swift +2 -4
  69. package/ios/Utils/Promise.swift +1 -0
  70. package/ios/Utils/Types.swift +1 -0
  71. package/ios/react_native_cloud_storage.h +6 -0
  72. package/package.json +30 -17
  73. package/react-native-cloud-storage.podspec +2 -0
  74. package/src/cloud-storage.ts +90 -42
  75. package/src/specs/NativeCloudStorageCloudKitIOS.ts +33 -0
  76. package/src/specs/NativeCloudStorageLocalFileSystem.ts +28 -0
  77. package/src/storages/cloudkit.ts +10 -2
  78. package/src/storages/google-drive/client.ts +2 -1
  79. package/src/storages/google-drive/index.ts +126 -120
  80. package/src/types/main.ts +9 -0
  81. package/src/types/native.ts +10 -54
  82. package/src/utils/constants.ts +1 -0
  83. package/src/utils/local-fs.ts +2 -2
  84. package/src/utils/native.ts +1 -1
  85. package/ios/CloudStorageCloudKit.m +0 -24
  86. package/ios/CloudStorageEventEmitter.m +0 -16
  87. package/ios/CloudStorageEventEmitter.swift +0 -30
  88. package/ios/CloudStorageLocalFileSystem.m +0 -15
@@ -116,7 +116,7 @@ export default class GoogleDriveApiClient {
116
116
  return body.join('');
117
117
  }
118
118
 
119
- public async listFiles(space: GoogleDriveFileSpace): Promise<GoogleDriveFile[]> {
119
+ public async listFiles(space: GoogleDriveFileSpace, query?: string): Promise<GoogleDriveFile[]> {
120
120
  const files: GoogleDriveFile[] = [];
121
121
  let pageToken: string | undefined;
122
122
  const fields = ['id', 'kind', 'mimeType', 'name', 'parents', 'spaces', 'size', 'createdTime', 'modifiedTime'];
@@ -125,6 +125,7 @@ export default class GoogleDriveApiClient {
125
125
  fields: `files(${fields.join(',')}),nextPageToken`,
126
126
  spaces: space,
127
127
  pageToken,
128
+ q: query,
128
129
  };
129
130
  const response = await this.request<GoogleDriveListOperationResponse>(`/files`, {
130
131
  queryParameters,
@@ -49,13 +49,23 @@ export default class GoogleDrive implements NativeStorage {
49
49
  case 'documents': {
50
50
  return 'drive';
51
51
  }
52
+ case 'documents_legacy': {
53
+ return 'drive';
54
+ }
52
55
  case 'app_data': {
53
56
  return 'appDataFolder';
54
57
  }
55
58
  }
56
59
  }
57
60
 
58
- private resolvePathToDirectories(path: string): { directories: string[]; filename: string } {
61
+ private isRootPath(path: string): boolean {
62
+ return path === '' || path === '/';
63
+ }
64
+
65
+ private resolvePathToDirectories(path: string): {
66
+ directories: string[];
67
+ filename: string;
68
+ } {
59
69
  if (path.startsWith('/')) path = path.slice(1);
60
70
  if (path.endsWith('/')) path = path.slice(0, -1);
61
71
  const directories = path.split('/');
@@ -63,86 +73,90 @@ export default class GoogleDrive implements NativeStorage {
63
73
  return { directories, filename: actualFilename };
64
74
  }
65
75
 
66
- private findParentDirectoryId(files: GoogleDriveFile[], directoryTree: string[]): string | null {
67
- const possibleTopDirectories = files
68
- .filter((f) => f.mimeType === MimeTypes.FOLDER)
69
- .filter((f) => f.name === directoryTree[0]);
76
+ private escapeDriveQueryValue(value: string): string {
77
+ return value.replaceAll('\\', String.raw`\\`).replaceAll("'", String.raw`\'`);
78
+ }
70
79
 
71
- let topDirectoryId: string | undefined;
72
- if (possibleTopDirectories.length === 0) return null;
73
- else if (possibleTopDirectories.length === 1) {
74
- topDirectoryId = possibleTopDirectories[0]!.id;
75
- } else {
76
- /* when multiple directories carry the same name, we need to check every one of them if their parent id exists in
77
- the files array - if it does not, it means that the directory is a child of the root directory and the one we're
78
- looking for */
79
- for (const possibleTopDirectory of possibleTopDirectories) {
80
- if (!files.some((f) => f.id === possibleTopDirectory!.parents![0] && f.mimeType === MimeTypes.FOLDER)) {
81
- topDirectoryId = possibleTopDirectory!.id;
82
- break;
83
- }
84
- }
80
+ private async getQueryRootParentId(scope: NativeStorageScope): Promise<string> {
81
+ if (scope === 'app_data') {
82
+ return this.getRootDirectory(scope);
85
83
  }
86
84
 
87
- if (!topDirectoryId) {
88
- throw new CloudStorageError(
89
- `Could not find top directory with name ${directoryTree[0]}`,
90
- NativeCloudStorageErrorCode.DIRECTORY_NOT_FOUND
91
- );
92
- }
85
+ return this.getRootDirectoryId(scope);
86
+ }
93
87
 
94
- // now, we traverse the directories array and get the id of the last directory from the files array
95
- let currentDirectoryId = topDirectoryId;
96
- for (let index = 1; index < directoryTree.length; index++) {
97
- const currentDirectory = files.find((f) => f.id === currentDirectoryId);
98
- if (!currentDirectory)
99
- throw new CloudStorageError(
100
- `Could not find directory with id ${currentDirectoryId}`,
101
- NativeCloudStorageErrorCode.DIRECTORY_NOT_FOUND
102
- );
103
- const nextDirectory = files.find((f) => f.name === directoryTree[index] && f.parents![0] === currentDirectoryId);
104
- if (!nextDirectory)
105
- throw new CloudStorageError(
106
- `Could not find directory with name ${directoryTree[index]}`,
107
- NativeCloudStorageErrorCode.DIRECTORY_NOT_FOUND
108
- );
109
- currentDirectoryId = nextDirectory.id;
88
+ private async findDirectoryByNameAndParent(
89
+ name: string,
90
+ parentId: string,
91
+ scope: NativeStorageScope
92
+ ): Promise<GoogleDriveFile[]> {
93
+ const escapedName = this.escapeDriveQueryValue(name);
94
+ const escapedParentId = this.escapeDriveQueryValue(parentId);
95
+ const query = `name = '${escapedName}' and '${escapedParentId}' in parents and mimeType = '${MimeTypes.FOLDER}' and trashed = false`;
96
+ return this.drive.listFiles(this.getRootDirectory(scope), query);
97
+ }
98
+
99
+ private async findFilesByNameAndParent(
100
+ name: string,
101
+ parentId: string,
102
+ scope: NativeStorageScope
103
+ ): Promise<GoogleDriveFile[]> {
104
+ const escapedName = this.escapeDriveQueryValue(name);
105
+ const escapedParentId = this.escapeDriveQueryValue(parentId);
106
+ const query = `name = '${escapedName}' and '${escapedParentId}' in parents and trashed = false`;
107
+ return this.drive.listFiles(this.getRootDirectory(scope), query);
108
+ }
109
+
110
+ private async findFilesByParent(parentId: string, scope: NativeStorageScope): Promise<GoogleDriveFile[]> {
111
+ const escapedParentId = this.escapeDriveQueryValue(parentId);
112
+ const query = `'${escapedParentId}' in parents and trashed = false`;
113
+ return this.drive.listFiles(this.getRootDirectory(scope), query);
114
+ }
115
+
116
+ private async findParentDirectoryId(directoryTree: string[], scope: NativeStorageScope): Promise<string | null> {
117
+ let parentDirectoryId = await this.getQueryRootParentId(scope);
118
+
119
+ for (const directoryName of directoryTree) {
120
+ const directories = await this.findDirectoryByNameAndParent(directoryName, parentDirectoryId, scope);
121
+ if (directories.length === 0) {
122
+ return null;
123
+ }
124
+
125
+ parentDirectoryId = directories[0]!.id;
110
126
  }
111
127
 
112
- return currentDirectoryId;
128
+ return parentDirectoryId;
113
129
  }
114
130
 
115
131
  /**
116
132
  * Gets the Google Drive ID of the root directory for the given scope.
117
133
  * @param scope The scope to get the root directory for.
118
- * @returns A promise that resolves to the ID of the root directory or null if it could not be found.
134
+ * @returns A promise that resolves to the ID of the root directory.
119
135
  */
120
- private async getRootDirectoryId(scope: NativeStorageScope): Promise<string | null> {
136
+ private async getRootDirectoryId(scope: NativeStorageScope): Promise<string> {
137
+ if (scope !== 'app_data') {
138
+ return 'root';
139
+ }
140
+
121
141
  const files = await this.drive.listFiles(this.getRootDirectory(scope));
122
142
  for (const file of files) {
123
- if (!files.some((f) => f.id === file.parents![0])) return file.parents![0] ?? null;
143
+ const parentId = file.parents?.[0];
144
+ if (parentId && !files.some((candidate) => candidate.id === parentId)) {
145
+ return parentId;
146
+ }
124
147
  }
125
148
 
126
- return null;
149
+ return this.getRootDirectory(scope);
127
150
  }
128
151
 
129
- private checkIfMultipleFilesWithSameName(
130
- path: string,
131
- files: GoogleDriveFile[],
132
- filename: string,
133
- parentDirectoryId: string | null
134
- ) {
152
+ private checkIfMultipleFilesWithSameName(path: string, files: GoogleDriveFile[]) {
135
153
  const { strictFilenames } = this.options;
136
154
 
137
- const possibleFiles: GoogleDriveFile[] = parentDirectoryId
138
- ? files.filter((f) => f.name === filename && f.parents![0] === parentDirectoryId)
139
- : files.filter((f) => f.name === filename && !files.some((f2) => f2.id === f.parents![0]));
140
-
141
- if (possibleFiles.length <= 1) return;
155
+ if (files.length <= 1) return;
142
156
 
143
157
  if (strictFilenames) {
144
158
  throw new CloudStorageError(
145
- `Multiple files with the same name found at path ${path}: ${possibleFiles.map((f) => f.id).join(', ')}`,
159
+ `Multiple files with the same name found at path ${path}: ${files.map((f) => f.id).join(', ')}`,
146
160
  NativeCloudStorageErrorCode.MULTIPLE_FILES_SAME_NAME
147
161
  );
148
162
  }
@@ -154,30 +168,29 @@ export default class GoogleDrive implements NativeStorage {
154
168
  throwIf: 'directory' | 'file' | false = false
155
169
  ): Promise<string> {
156
170
  try {
157
- const files = await this.drive.listFiles(this.getRootDirectory(scope));
171
+ if (this.isRootPath(path)) {
172
+ if (throwIf === 'directory') {
173
+ throw new CloudStorageError(`Path ${path} is a directory`, NativeCloudStorageErrorCode.PATH_IS_DIRECTORY);
174
+ }
158
175
 
159
- if (path === '' || path === '/') {
160
176
  const rootDirectoryId = await this.getRootDirectoryId(scope);
161
- if (!rootDirectoryId)
162
- throw new CloudStorageError(
163
- `Root directory in scope ${scope} not found`,
164
- NativeCloudStorageErrorCode.DIRECTORY_NOT_FOUND
165
- );
177
+ if (scope !== 'app_data') {
178
+ await this.drive.getFile(rootDirectoryId);
179
+ }
180
+
166
181
  return rootDirectoryId;
167
182
  }
168
183
 
169
184
  const { directories, filename } = this.resolvePathToDirectories(path);
170
- const parentDirectoryId = this.findParentDirectoryId(files, directories);
171
- let file: GoogleDriveFile | undefined;
185
+ const parentDirectoryId = await this.findParentDirectoryId(directories, scope);
172
186
  if (parentDirectoryId === null) {
173
- this.checkIfMultipleFilesWithSameName(path, files, filename, null);
174
- /* when the file is supposed to be in the root directory, we need to get the file where the name is the filename
175
- and the first parent has an id which does not exist in the files array */
176
- file = files.find((f) => f.name === filename && !files.some((f2) => f2.id === f.parents![0]));
177
- } else {
178
- this.checkIfMultipleFilesWithSameName(path, files, filename, parentDirectoryId);
179
- file = files.find((f) => f.name === filename && f.parents![0] === parentDirectoryId);
187
+ throw new CloudStorageError(`File not found`, NativeCloudStorageErrorCode.FILE_NOT_FOUND);
180
188
  }
189
+
190
+ const files = await this.findFilesByNameAndParent(filename, parentDirectoryId, scope);
191
+ this.checkIfMultipleFilesWithSameName(path, files);
192
+
193
+ const file = files[0];
181
194
  if (!file) throw new CloudStorageError(`File not found`, NativeCloudStorageErrorCode.FILE_NOT_FOUND);
182
195
  if (file.mimeType === MimeTypes.FOLDER && throwIf === 'directory') {
183
196
  throw new CloudStorageError(`Path ${path} is a directory`, NativeCloudStorageErrorCode.PATH_IS_DIRECTORY);
@@ -217,7 +230,7 @@ export default class GoogleDrive implements NativeStorage {
217
230
  let fileId: string | undefined;
218
231
  let previousContent = '';
219
232
  try {
220
- fileId = await this.getFileId(path, scope);
233
+ fileId = await this.getFileId(path, scope, 'directory');
221
234
  previousContent = await this.drive.getFileText(fileId);
222
235
  } catch (error: unknown) {
223
236
  if (error instanceof CloudStorageError && error.code === NativeCloudStorageErrorCode.FILE_NOT_FOUND) {
@@ -233,17 +246,16 @@ export default class GoogleDrive implements NativeStorage {
233
246
  mimeType: MimeTypes.TEXT,
234
247
  });
235
248
  } else {
236
- const files = await this.drive.listFiles(this.getRootDirectory(scope));
237
249
  const { directories, filename } = this.resolvePathToDirectories(path);
238
- const parentDirectoryId = this.findParentDirectoryId(files, directories);
250
+ const parentDirectoryId = await this.findParentDirectoryId(directories, scope);
251
+ if (parentDirectoryId === null) {
252
+ throw new CloudStorageError(`Directory not found`, NativeCloudStorageErrorCode.DIRECTORY_NOT_FOUND);
253
+ }
254
+
239
255
  await this.drive.createFile(
240
256
  {
241
257
  name: filename,
242
- parents: parentDirectoryId
243
- ? [parentDirectoryId]
244
- : scope === 'app_data'
245
- ? [this.getRootDirectory(scope)]
246
- : undefined,
258
+ parents: [parentDirectoryId],
247
259
  },
248
260
  {
249
261
  body: data,
@@ -257,7 +269,7 @@ export default class GoogleDrive implements NativeStorage {
257
269
  let fileId: string | undefined;
258
270
  if (overwrite) {
259
271
  try {
260
- fileId = await this.getFileId(path, scope);
272
+ fileId = await this.getFileId(path, scope, 'directory');
261
273
  } catch (error: unknown) {
262
274
  if (error instanceof CloudStorageError && error.code === NativeCloudStorageErrorCode.FILE_NOT_FOUND) {
263
275
  /* do nothing, simply create the file */
@@ -267,7 +279,7 @@ export default class GoogleDrive implements NativeStorage {
267
279
  }
268
280
  } else {
269
281
  try {
270
- await this.getFileId(path, scope);
282
+ await this.getFileId(path, scope, 'directory');
271
283
  throw new CloudStorageError(`File ${path} already exists`, NativeCloudStorageErrorCode.FILE_ALREADY_EXISTS);
272
284
  } catch (error: unknown) {
273
285
  if (error instanceof CloudStorageError && error.code === NativeCloudStorageErrorCode.FILE_NOT_FOUND) {
@@ -284,17 +296,16 @@ export default class GoogleDrive implements NativeStorage {
284
296
  mimeType: MimeTypes.TEXT,
285
297
  });
286
298
  } else {
287
- const files = await this.drive.listFiles(this.getRootDirectory(scope));
288
299
  const { directories, filename } = this.resolvePathToDirectories(path);
289
- const parentDirectoryId = this.findParentDirectoryId(files, directories);
300
+ const parentDirectoryId = await this.findParentDirectoryId(directories, scope);
301
+ if (parentDirectoryId === null) {
302
+ throw new CloudStorageError(`Directory not found`, NativeCloudStorageErrorCode.DIRECTORY_NOT_FOUND);
303
+ }
304
+
290
305
  await this.drive.createFile(
291
306
  {
292
307
  name: filename,
293
- parents: parentDirectoryId
294
- ? [parentDirectoryId]
295
- : scope === 'app_data'
296
- ? [this.getRootDirectory(scope)]
297
- : undefined,
308
+ parents: [parentDirectoryId],
298
309
  },
299
310
  {
300
311
  body: data,
@@ -305,25 +316,25 @@ export default class GoogleDrive implements NativeStorage {
305
316
  }
306
317
 
307
318
  async listFiles(path: string, scope: NativeStorageScope): Promise<string[]> {
308
- const allFiles = await this.drive.listFiles(this.getRootDirectory(scope));
309
- if (path === '') {
310
- const rootDirectoryId = await this.getRootDirectoryId(scope);
311
- return [...new Set(allFiles.filter((f) => (f.parents ?? [])[0] === rootDirectoryId).map((f) => f.name))];
312
- } else {
313
- const fileId = await this.getFileId(path, scope);
314
- const files = allFiles.filter((f) => (f.parents ?? [])[0] === fileId);
319
+ const parentDirectoryId = this.isRootPath(path)
320
+ ? await this.getQueryRootParentId(scope)
321
+ : await this.getFileId(path, scope);
315
322
 
316
- return [...new Set(files.map((f) => f.name))];
317
- }
323
+ const files = await this.findFilesByParent(parentDirectoryId, scope);
324
+ return [...new Set(files.map((f) => f.name))];
318
325
  }
319
326
 
320
327
  async createDirectory(path: string, scope: NativeStorageScope): Promise<void> {
328
+ if (this.isRootPath(path)) {
329
+ throw new CloudStorageError(`Directory ${path} already exists`, NativeCloudStorageErrorCode.FILE_ALREADY_EXISTS);
330
+ }
331
+
321
332
  try {
322
333
  await this.getFileId(path, scope);
323
334
  throw new CloudStorageError(`File ${path} already exists`, NativeCloudStorageErrorCode.FILE_ALREADY_EXISTS);
324
335
  } catch (error: unknown) {
325
336
  if (error instanceof CloudStorageError && error.code === NativeCloudStorageErrorCode.FILE_NOT_FOUND) {
326
- /* do nothing, simply create the file */
337
+ /* do nothing, simply create the directory */
327
338
  } else if (error instanceof CloudStorageError && error.code === NativeCloudStorageErrorCode.PATH_IS_DIRECTORY) {
328
339
  throw new CloudStorageError(
329
340
  `Directory ${path} already exists`,
@@ -334,22 +345,20 @@ export default class GoogleDrive implements NativeStorage {
334
345
  }
335
346
  }
336
347
 
337
- const files = await this.drive.listFiles(this.getRootDirectory(scope));
338
348
  const { directories, filename } = this.resolvePathToDirectories(path);
339
- const parentDirectoryId = this.findParentDirectoryId(files, directories);
349
+ const parentDirectoryId = await this.findParentDirectoryId(directories, scope);
350
+ if (parentDirectoryId === null) {
351
+ throw new CloudStorageError(`Directory not found`, NativeCloudStorageErrorCode.DIRECTORY_NOT_FOUND);
352
+ }
340
353
 
341
354
  await this.drive.createDirectory({
342
355
  name: filename,
343
- parents: parentDirectoryId
344
- ? [parentDirectoryId]
345
- : scope === 'app_data'
346
- ? [this.getRootDirectory(scope)]
347
- : undefined,
356
+ parents: [parentDirectoryId],
348
357
  });
349
358
  }
350
359
 
351
360
  async readFile(path: string, scope: NativeStorageScope): Promise<string> {
352
- const fileId = await this.getFileId(path, scope);
361
+ const fileId = await this.getFileId(path, scope, 'directory');
353
362
  const content = await this.drive.getFileText(fileId);
354
363
  return content;
355
364
  }
@@ -371,8 +380,7 @@ export default class GoogleDrive implements NativeStorage {
371
380
 
372
381
  if (!recursive) {
373
382
  // check if the directory is empty
374
- const files = await this.drive.listFiles(this.getRootDirectory(scope));
375
- const filesInDirectory = files.filter((f) => (f.parents ?? [])[0] === fileId);
383
+ const filesInDirectory = await this.findFilesByParent(fileId, scope);
376
384
  if (filesInDirectory.length > 0) {
377
385
  throw new CloudStorageError(
378
386
  `Directory ${path} is not empty`,
@@ -425,7 +433,7 @@ export default class GoogleDrive implements NativeStorage {
425
433
 
426
434
  if (overwrite) {
427
435
  try {
428
- fileId = await this.getFileId(remotePath, scope);
436
+ fileId = await this.getFileId(remotePath, scope, 'directory');
429
437
  } catch (error: unknown) {
430
438
  if (error instanceof CloudStorageError && error.code === NativeCloudStorageErrorCode.FILE_NOT_FOUND) {
431
439
  /* File doesn't exist -> we'll create it below */
@@ -435,7 +443,7 @@ export default class GoogleDrive implements NativeStorage {
435
443
  }
436
444
  } else {
437
445
  try {
438
- await this.getFileId(remotePath, scope);
446
+ await this.getFileId(remotePath, scope, 'directory');
439
447
  throw new CloudStorageError(
440
448
  `File ${remotePath} already exists`,
441
449
  NativeCloudStorageErrorCode.FILE_ALREADY_EXISTS
@@ -459,18 +467,16 @@ export default class GoogleDrive implements NativeStorage {
459
467
  });
460
468
  } else {
461
469
  // Need to create a new file first
462
- const files = await this.drive.listFiles(this.getRootDirectory(scope));
463
470
  const { directories, filename } = this.resolvePathToDirectories(remotePath);
464
- const parentDirectoryId = this.findParentDirectoryId(files, directories);
471
+ const parentDirectoryId = await this.findParentDirectoryId(directories, scope);
472
+ if (parentDirectoryId === null) {
473
+ throw new CloudStorageError(`Directory not found`, NativeCloudStorageErrorCode.DIRECTORY_NOT_FOUND);
474
+ }
465
475
 
466
476
  await this.drive.createFile(
467
477
  {
468
478
  name: filename,
469
- parents: parentDirectoryId
470
- ? [parentDirectoryId]
471
- : scope === 'app_data'
472
- ? [this.getRootDirectory(scope)]
473
- : undefined,
479
+ parents: [parentDirectoryId],
474
480
  },
475
481
  {
476
482
  mimeType,
package/src/types/main.ts CHANGED
@@ -8,6 +8,8 @@ export enum CloudStorageScope {
8
8
  AppData = 'app_data',
9
9
  }
10
10
 
11
+ export type ICloudDocumentsMode = 'icloud' | 'legacy_sandbox';
12
+
11
13
  export interface CloudStorageFileStat {
12
14
  size: number;
13
15
  birthtimeMs: number;
@@ -29,6 +31,13 @@ export interface CloudStorageProviderOptions {
29
31
  * The directory scope to use for iCloud operations. Defaults to 'app_data'.
30
32
  */
31
33
  scope?: CloudStorageScope;
34
+ /**
35
+ * The directory mode to use for CloudStorageScope.Documents.
36
+ * `icloud` uses the user-facing iCloud Documents directory, while
37
+ * `legacy_sandbox` uses the local app sandbox Documents directory.
38
+ * Defaults to `icloud`.
39
+ */
40
+ documentsMode?: ICloudDocumentsMode;
32
41
  };
33
42
 
34
43
  [CloudStorageProvider.GoogleDrive]: {
@@ -1,12 +1,12 @@
1
- export type NativeStorageScope = 'documents' | 'app_data';
1
+ import type {
2
+ CloudStorageFileStat,
3
+ Spec as NativeCloudStorageCloudKitSpec,
4
+ } from '../specs/NativeCloudStorageCloudKitIOS';
5
+ import type { Spec as NativeCloudStorageLocalFileSystemSpec } from '../specs/NativeCloudStorageLocalFileSystem';
2
6
 
3
- export interface NativeStorageFileStat {
4
- size: number;
5
- birthtimeMs: number;
6
- mtimeMs: number;
7
- isDirectory: boolean;
8
- isFile: boolean;
9
- }
7
+ export type NativeStorageScope = 'documents' | 'documents_legacy' | 'app_data';
8
+
9
+ export type NativeStorageFileStat = CloudStorageFileStat;
10
10
 
11
11
  export enum NativeCloudStorageErrorCode {
12
12
  INVALID_SCOPE = 'ERR_INVALID_SCOPE',
@@ -29,50 +29,6 @@ export enum NativeCloudStorageErrorCode {
29
29
  NETWORK_ERROR = 'ERR_NETWORK_ERROR',
30
30
  }
31
31
 
32
- export interface NativeLocalFileSystem {
33
- getConstants: () => {
34
- temporaryDirectory: string;
35
- };
36
- createFile: (path: string, data: string) => Promise<string>;
37
- readFile: (path: string) => Promise<string>;
38
- downloadFile: (remoteUri: string, localPath: string, options?: { headers?: Record<string, string> }) => Promise<void>;
39
- uploadFile: (
40
- localPath: string,
41
- remoteUri: string,
42
- options?: {
43
- headers?: Record<string, string>;
44
- method?: 'PUT' | 'POST' | 'PATCH';
45
- } & (
46
- | {
47
- uploadType?: 'binary';
48
- }
49
- | {
50
- uploadType?: 'multipart';
51
- fieldName?: string;
52
- parameters?: Record<string, string>;
53
- }
54
- )
55
- ) => Promise<void>;
56
- }
32
+ export type NativeLocalFileSystem = NativeCloudStorageLocalFileSystemSpec;
57
33
 
58
- export interface NativeStorage {
59
- fileExists: (path: string, scope: NativeStorageScope) => Promise<boolean>;
60
- appendToFile: (path: string, data: string, scope: NativeStorageScope) => Promise<void>;
61
- createFile: (path: string, data: string, scope: NativeStorageScope, overwrite: boolean) => Promise<void>;
62
- createDirectory: (path: string, scope: NativeStorageScope) => Promise<void>;
63
- listFiles: (path: string, scope: NativeStorageScope) => Promise<string[]>;
64
- readFile: (path: string, scope: NativeStorageScope) => Promise<string>;
65
- deleteFile: (path: string, scope: NativeStorageScope) => Promise<void>;
66
- deleteDirectory: (path: string, recursively: boolean, scope: NativeStorageScope) => Promise<void>;
67
- statFile: (path: string, scope: NativeStorageScope) => Promise<NativeStorageFileStat>;
68
- downloadFile: (remotePath: string, localPath: string, scope: NativeStorageScope) => Promise<void>;
69
- uploadFile: (
70
- remotePath: string,
71
- localPath: string,
72
- mimeType: string,
73
- scope: NativeStorageScope,
74
- overwrite: boolean
75
- ) => Promise<void>;
76
- isCloudAvailable: () => Promise<boolean>;
77
- triggerSync: (path: string, scope: NativeStorageScope) => Promise<void>;
78
- }
34
+ export type NativeStorage = Omit<NativeCloudStorageCloudKitSpec, 'onCloudAvailabilityChanged'>;
@@ -10,6 +10,7 @@ export const LINKING_ERROR =
10
10
  export const DEFAULT_PROVIDER_OPTIONS: DeepRequired<CloudStorageProviderOptions> = {
11
11
  [CloudStorageProvider.ICloud]: {
12
12
  scope: CloudStorageScope.AppData,
13
+ documentsMode: 'icloud',
13
14
  },
14
15
  [CloudStorageProvider.GoogleDrive]: {
15
16
  scope: CloudStorageScope.AppData,
@@ -1,10 +1,10 @@
1
- import { NativeModules } from 'react-native';
1
+ import NativeCloudStorageLocalFileSystem from '../specs/NativeCloudStorageLocalFileSystem';
2
2
  import { NativeLocalFileSystem as TNativeLocalFileSystem } from '../types/native';
3
3
  import { createProxiedNativeModule } from '../utils/native';
4
4
  import { LINKING_ERROR } from './constants';
5
5
 
6
6
  const NativeLocalFileSystem = createProxiedNativeModule<TNativeLocalFileSystem>(
7
- NativeModules.CloudStorageLocalFileSystem
7
+ NativeCloudStorageLocalFileSystem as unknown as TNativeLocalFileSystem | null
8
8
  );
9
9
 
10
10
  export const localFileSystem =
@@ -6,7 +6,7 @@ import CloudStorageError from './cloud-storage-error';
6
6
  * @param nativeModule The native module to proxy.
7
7
  * @returns The proxied native module.
8
8
  */
9
- export const createProxiedNativeModule = <T extends object>(nativeModule: T | undefined): T | null => {
9
+ export const createProxiedNativeModule = <T extends object>(nativeModule: T | null | undefined): T | null => {
10
10
  if (!nativeModule) return null;
11
11
 
12
12
  return new Proxy(nativeModule, {
@@ -1,24 +0,0 @@
1
- #import <React/RCTBridgeModule.h>
2
-
3
- @interface RCT_EXTERN_MODULE(CloudStorageCloudKit, NSObject)
4
-
5
- RCT_EXTERN_METHOD(fileExists:(NSString *)path withScope:(NSString *)scope withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject)
6
- RCT_EXTERN_METHOD(appendToFile:(NSString *)path withData:(NSString *)data withScope:(NSString *)scope withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject)
7
- RCT_EXTERN_METHOD(createFile:(NSString *)path withData:(NSString *)data withScope:(NSString *)scope withOverwrite:(BOOL)overwrite withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject)
8
- RCT_EXTERN_METHOD(createDirectory:(NSString *)path withScope:(NSString *)scope withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject)
9
- RCT_EXTERN_METHOD(listFiles:(NSString *)path withScope:(NSString *)scope withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject)
10
- RCT_EXTERN_METHOD(readFile:(NSString *)path withScope:(NSString *)scope withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject)
11
- RCT_EXTERN_METHOD(triggerSync:(NSString *)path withScope:(NSString *)scope withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject)
12
- RCT_EXTERN_METHOD(deleteFile:(NSString *)path withScope:(NSString *)scope withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject)
13
- RCT_EXTERN_METHOD(deleteDirectory:(NSString *)path withRecursive:(BOOL)recursive withScope:(NSString *)scope withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject)
14
- RCT_EXTERN_METHOD(statFile:(NSString *)path withScope:(NSString *)scope withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject)
15
- RCT_EXTERN_METHOD(downloadFile:(NSString *)remotePath withLocalPath:(NSString *)localPath withScope:(NSString *)scope withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject)
16
- RCT_EXTERN_METHOD(uploadFile:(NSString *)remotePath withLocalPath:(NSString *)localPath withMimeType:(NSString *)mimeType withScope:(NSString *)scope withOverwrite:(BOOL)overwrite withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject)
17
- RCT_EXTERN_METHOD(isCloudAvailable:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject)
18
-
19
- + (BOOL)requiresMainQueueSetup
20
- {
21
- return NO;
22
- }
23
-
24
- @end
@@ -1,16 +0,0 @@
1
- #import <React/RCTBridgeModule.h>
2
- #import <React/RCTEventEmitter.h>
3
-
4
- @interface RCT_EXTERN_MODULE(CloudStorageEventEmitter, RCTEventEmitter)
5
-
6
- RCT_EXTERN_METHOD(supportedEvents)
7
- RCT_EXTERN_METHOD(startObserving)
8
- RCT_EXTERN_METHOD(stopObserving)
9
- RCT_EXTERN_METHOD(iCloudIdentityChanged:(NSNotification *)notification)
10
-
11
- + (BOOL)requiresMainQueueSetup
12
- {
13
- return NO;
14
- }
15
-
16
- @end
@@ -1,30 +0,0 @@
1
- import Foundation
2
-
3
- @objc(CloudStorageEventEmitter)
4
- class CloudStorageEventEmitter: RCTEventEmitter {
5
- public static var shared: CloudStorageEventEmitter!
6
-
7
- override init() {
8
- super.init()
9
- CloudStorageEventEmitter.shared = self
10
- }
11
-
12
- override func supportedEvents() -> [String]! {
13
- ["RNCloudStorage.cloud.availability-changed"]
14
- }
15
-
16
- override func startObserving() {
17
- NotificationCenter.default.addObserver(self, selector: #selector(iCloudIdentityChanged(_:)), name: NSNotification.Name.NSUbiquityIdentityDidChange, object: nil)
18
- // call this immediately after init to trigger an initial value being sent to the JS side
19
- iCloudIdentityChanged()
20
- }
21
-
22
- override func stopObserving() {
23
- NotificationCenter.default.removeObserver(self, name: NSNotification.Name.NSUbiquityIdentityDidChange, object: nil)
24
- }
25
-
26
- @objc func iCloudIdentityChanged(_: Notification? = nil) {
27
- let isAvailable = CloudKitUtils.isCloudKitAvailable()
28
- CloudStorageEventEmitter.shared.sendEvent(withName: "RNCloudStorage.cloud.availability-changed", body: ["available": isAvailable])
29
- }
30
- }
@@ -1,15 +0,0 @@
1
- #import <React/RCTBridgeModule.h>
2
-
3
- @interface RCT_EXTERN_MODULE(CloudStorageLocalFileSystem, NSObject)
4
-
5
- RCT_EXTERN_METHOD(readFile:(NSString *)path withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject)
6
- RCT_EXTERN_METHOD(createFile:(NSString *)path withData:(NSString *)data withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject)
7
- RCT_EXTERN_METHOD(downloadFile:(NSString *)remoteUri withLocalPath:(NSString *)localPath withOptions:(NSDictionary *)options withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject)
8
- RCT_EXTERN_METHOD(uploadFile:(NSString *)localPath withRemoteUri:(NSString *)remoteUri withOptions:(NSDictionary *)options withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject)
9
-
10
- + (BOOL)requiresMainQueueSetup
11
- {
12
- return NO;
13
- }
14
-
15
- @end