react-native-cloud-storage 1.2.3 → 1.3.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 (43) hide show
  1. package/README.md +66 -10
  2. package/ios/CloudStorage.m +1 -0
  3. package/ios/CloudStorage.swift +46 -0
  4. package/lib/commonjs/RNCloudStorage.js +10 -0
  5. package/lib/commonjs/RNCloudStorage.js.map +1 -1
  6. package/lib/commonjs/createRNCloudStorage.js.map +1 -1
  7. package/lib/commonjs/google-drive/client.js +172 -0
  8. package/lib/commonjs/google-drive/client.js.map +1 -0
  9. package/lib/commonjs/google-drive/index.js +72 -54
  10. package/lib/commonjs/google-drive/index.js.map +1 -1
  11. package/lib/commonjs/google-drive/types.js +15 -0
  12. package/lib/commonjs/google-drive/types.js.map +1 -1
  13. package/lib/commonjs/types/native.js +2 -0
  14. package/lib/commonjs/types/native.js.map +1 -1
  15. package/lib/module/RNCloudStorage.js +14 -4
  16. package/lib/module/RNCloudStorage.js.map +1 -1
  17. package/lib/module/createRNCloudStorage.js +2 -2
  18. package/lib/module/createRNCloudStorage.js.map +1 -1
  19. package/lib/module/google-drive/client.js +164 -0
  20. package/lib/module/google-drive/client.js.map +1 -0
  21. package/lib/module/google-drive/index.js +66 -49
  22. package/lib/module/google-drive/index.js.map +1 -1
  23. package/lib/module/google-drive/types.js +9 -1
  24. package/lib/module/google-drive/types.js.map +1 -1
  25. package/lib/module/types/native.js +2 -0
  26. package/lib/module/types/native.js.map +1 -1
  27. package/lib/typescript/RNCloudStorage.d.ts +12 -2
  28. package/lib/typescript/RNCloudStorage.d.ts.map +1 -1
  29. package/lib/typescript/google-drive/client.d.ts +33 -0
  30. package/lib/typescript/google-drive/client.d.ts.map +1 -0
  31. package/lib/typescript/google-drive/index.d.ts +7 -4
  32. package/lib/typescript/google-drive/index.d.ts.map +1 -1
  33. package/lib/typescript/google-drive/types.d.ts +10 -3
  34. package/lib/typescript/google-drive/types.d.ts.map +1 -1
  35. package/lib/typescript/types/native.d.ts +3 -0
  36. package/lib/typescript/types/native.d.ts.map +1 -1
  37. package/package.json +1 -4
  38. package/src/RNCloudStorage.ts +16 -5
  39. package/src/createRNCloudStorage.ts +2 -2
  40. package/src/google-drive/client.ts +180 -0
  41. package/src/google-drive/index.ts +97 -73
  42. package/src/google-drive/types.ts +12 -4
  43. package/src/types/native.ts +3 -0
@@ -1,4 +1,3 @@
1
- import { GDrive, HttpError, MimeTypes } from 'react-native-google-drive-api-wrapper-js';
2
1
  import type NativeRNCloudStorage from '../types/native';
3
2
  import {
4
3
  CloudStorageErrorCode,
@@ -6,23 +5,26 @@ import {
6
5
  type NativeRNCloudCloudStorageScope,
7
6
  } from '../types/native';
8
7
  import CloudStorageError from '../utils/CloudStorageError';
9
- import type { GoogleDriveDetailedFile, GoogleDriveFile, GoogleDriveListOperationResponse } from './types';
8
+ import { MimeTypes, type GoogleDriveFile, type GoogleDriveFileSpace } from './types';
10
9
  import { DeviceEventEmitter } from 'react-native';
10
+ import GoogleDriveApiClient, { GoogleDriveHttpError } from './client';
11
11
 
12
- export default class GoogleDriveApiClient implements NativeRNCloudStorage {
13
- private static drive: GDrive = new GDrive();
12
+ /**
13
+ * A proxy class that wraps the Google Drive API client implementation to match the native iOS interface.
14
+ */
15
+ export default class GoogleDrive implements NativeRNCloudStorage {
16
+ private static drive: GoogleDriveApiClient = new GoogleDriveApiClient();
14
17
  public static throwOnFilesWithSameName = false;
15
18
  public filesWithSameNameSubscribers: (({ path, fileIds }: { path: string; fileIds: string[] }) => void)[];
16
19
 
17
20
  constructor() {
18
21
  this.filesWithSameNameSubscribers = [];
19
- GoogleDriveApiClient.drive.fetchTimeout = 3000;
20
22
  return new Proxy(this, {
21
23
  // before calling any function, check if the access token is set
22
- get(target: GoogleDriveApiClient, prop: keyof GoogleDriveApiClient) {
24
+ get(target: GoogleDrive, prop: keyof GoogleDrive) {
23
25
  const allowedFunctions = ['isCloudAvailable', 'subscribeToFilesWithSameName'];
24
26
  if (typeof target[prop] === 'function' && !allowedFunctions.includes(prop.toString())) {
25
- if (!GoogleDriveApiClient.drive.accessToken) {
27
+ if (!GoogleDrive.drive.accessToken.length) {
26
28
  throw new CloudStorageError(
27
29
  `Google Drive access token is not set, cannot call function ${prop.toString()}`,
28
30
  CloudStorageErrorCode.GOOGLE_DRIVE_ACCESS_TOKEN_MISSING
@@ -36,8 +38,8 @@ export default class GoogleDriveApiClient implements NativeRNCloudStorage {
36
38
  }
37
39
 
38
40
  // when setting accessToken, set it on the GDrive instance
39
- public static set accessToken(accessToken: string | undefined) {
40
- GoogleDriveApiClient.drive.accessToken = accessToken;
41
+ public static set accessToken(accessToken: string | null) {
42
+ GoogleDrive.drive.accessToken = accessToken ?? '';
41
43
 
42
44
  // emit an event for the useIsCloudAvailable hook
43
45
  DeviceEventEmitter.emit('RNCloudStorage.cloud.availability-changed', {
@@ -45,8 +47,8 @@ export default class GoogleDriveApiClient implements NativeRNCloudStorage {
45
47
  });
46
48
  }
47
49
 
48
- public static get accessToken(): string | undefined {
49
- return GoogleDriveApiClient.drive.accessToken;
50
+ public static get accessToken(): string | null {
51
+ return GoogleDrive.drive.accessToken.length ? GoogleDrive.drive.accessToken : null;
50
52
  }
51
53
 
52
54
  public subscribeToFilesWithSameName(subscriber: ({ path, fileIds }: { path: string; fileIds: string[] }) => void): {
@@ -61,9 +63,9 @@ export default class GoogleDriveApiClient implements NativeRNCloudStorage {
61
63
  };
62
64
  }
63
65
 
64
- public isCloudAvailable: () => Promise<boolean> = async () => !!GoogleDriveApiClient.accessToken?.length;
66
+ public isCloudAvailable: () => Promise<boolean> = async () => !!GoogleDrive.drive.accessToken.length;
65
67
 
66
- private getRootDirectory(scope: NativeRNCloudCloudStorageScope): 'drive' | 'appDataFolder' {
68
+ private getRootDirectory(scope: NativeRNCloudCloudStorageScope): GoogleDriveFileSpace {
67
69
  switch (scope) {
68
70
  case 'documents':
69
71
  return 'drive';
@@ -135,7 +137,7 @@ export default class GoogleDriveApiClient implements NativeRNCloudStorage {
135
137
  * @returns A promise that resolves to the ID of the root directory or null if it could not be found.
136
138
  */
137
139
  private async getRootDirectoryId(scope: NativeRNCloudCloudStorageScope): Promise<string | null> {
138
- const files = await this.listInternalFiles(scope);
140
+ const files = await GoogleDrive.drive.listFiles(this.getRootDirectory(scope));
139
141
  for (const file of files) {
140
142
  if (!files.find((f) => f.id === file.parents![0])) return file.parents![0] ?? null;
141
143
  }
@@ -143,15 +145,6 @@ export default class GoogleDriveApiClient implements NativeRNCloudStorage {
143
145
  return null;
144
146
  }
145
147
 
146
- private async listInternalFiles(scope: NativeRNCloudCloudStorageScope): Promise<GoogleDriveFile[]> {
147
- const files: GoogleDriveListOperationResponse = await GoogleDriveApiClient.drive.files.list({
148
- spaces: [this.getRootDirectory(scope)],
149
- fields: 'files(id,kind,mimeType,name,parents,spaces)',
150
- });
151
-
152
- return files.files;
153
- }
154
-
155
148
  private checkIfMultipleFilesWithSameName(
156
149
  path: string,
157
150
  files: GoogleDriveFile[],
@@ -167,7 +160,7 @@ export default class GoogleDriveApiClient implements NativeRNCloudStorage {
167
160
 
168
161
  if (possibleFiles.length <= 1) return;
169
162
 
170
- if (GoogleDriveApiClient.throwOnFilesWithSameName) {
163
+ if (GoogleDrive.throwOnFilesWithSameName) {
171
164
  throw new CloudStorageError(
172
165
  `Multiple files with the same name found at path ${path}: ${possibleFiles.map((f) => f.id).join(', ')}`,
173
166
  CloudStorageErrorCode.MULTIPLE_FILES_SAME_NAME
@@ -180,10 +173,10 @@ export default class GoogleDriveApiClient implements NativeRNCloudStorage {
180
173
  private async getFileId(
181
174
  path: string,
182
175
  scope: NativeRNCloudCloudStorageScope,
183
- throwIfDirectory = true
176
+ throwIf: 'directory' | 'file' | false = false
184
177
  ): Promise<string> {
185
178
  try {
186
- const files = await this.listInternalFiles(scope);
179
+ const files = await GoogleDrive.drive.listFiles(this.getRootDirectory(scope));
187
180
 
188
181
  if (path === '' || path === '/') {
189
182
  const rootDirectoryId = await this.getRootDirectoryId(scope);
@@ -208,12 +201,14 @@ export default class GoogleDriveApiClient implements NativeRNCloudStorage {
208
201
  file = files.find((f) => f.name === filename && f.parents![0] === parentDirectoryId);
209
202
  }
210
203
  if (!file) throw new CloudStorageError(`File not found`, CloudStorageErrorCode.FILE_NOT_FOUND);
211
- if (file.mimeType === MimeTypes.FOLDER && throwIfDirectory) {
204
+ if (file.mimeType === MimeTypes.FOLDER && throwIf === 'directory') {
212
205
  throw new CloudStorageError(`Path ${path} is a directory`, CloudStorageErrorCode.PATH_IS_DIRECTORY);
206
+ } else if (file.mimeType !== MimeTypes.FOLDER && throwIf === 'file') {
207
+ throw new CloudStorageError(`Path ${path} is a file`, CloudStorageErrorCode.FILE_NOT_FOUND);
213
208
  }
214
209
  return file.id;
215
210
  } catch (e: unknown) {
216
- if (e instanceof HttpError && e.json?.error?.status === 'UNAUTHENTICATED') {
211
+ if (e instanceof GoogleDriveHttpError && e.json?.error?.status === 'UNAUTHENTICATED') {
217
212
  throw new CloudStorageError(
218
213
  `Could not authenticate with Google Drive`,
219
214
  CloudStorageErrorCode.AUTHENTICATION_FAILED,
@@ -228,7 +223,7 @@ export default class GoogleDriveApiClient implements NativeRNCloudStorage {
228
223
 
229
224
  async fileExists(path: string, scope: NativeRNCloudCloudStorageScope): Promise<boolean> {
230
225
  try {
231
- await this.getFileId(path, scope, false);
226
+ await this.getFileId(path, scope);
232
227
  return true;
233
228
  } catch (e: any) {
234
229
  if (e instanceof CloudStorageError && e.code === CloudStorageErrorCode.FILE_NOT_FOUND) return false;
@@ -241,7 +236,7 @@ export default class GoogleDriveApiClient implements NativeRNCloudStorage {
241
236
  let prevContent = '';
242
237
  try {
243
238
  fileId = await this.getFileId(path, scope);
244
- prevContent = await GoogleDriveApiClient.drive.files.getText(fileId);
239
+ prevContent = await GoogleDrive.drive.getFileText(fileId);
245
240
  } catch (e: any) {
246
241
  if (e instanceof CloudStorageError && e.code === CloudStorageErrorCode.FILE_NOT_FOUND) {
247
242
  /* do nothing, simply create the file */
@@ -250,24 +245,30 @@ export default class GoogleDriveApiClient implements NativeRNCloudStorage {
250
245
  }
251
246
  }
252
247
 
253
- const uploader = GoogleDriveApiClient.drive.files
254
- .newMultipartUploader()
255
- .setData(prevContent + data, MimeTypes.TEXT);
256
- if (fileId) uploader.setIdOfFileToUpdate(fileId);
257
- else {
258
- const files = await this.listInternalFiles(scope);
248
+ if (fileId) {
249
+ await GoogleDrive.drive.updateFile(fileId, {
250
+ body: prevContent + data,
251
+ mimeType: MimeTypes.TEXT,
252
+ });
253
+ } else {
254
+ const files = await GoogleDrive.drive.listFiles(this.getRootDirectory(scope));
259
255
  const { directories, filename } = this.resolvePathToDirectories(path);
260
256
  const parentDirectoryId = this.findParentDirectoryId(files, directories);
261
- uploader.setRequestBody({
262
- name: filename,
263
- parents: parentDirectoryId
264
- ? [parentDirectoryId]
265
- : scope === 'app_data'
266
- ? [this.getRootDirectory(scope)]
267
- : undefined,
268
- });
257
+ await GoogleDrive.drive.createFile(
258
+ {
259
+ name: filename,
260
+ parents: parentDirectoryId
261
+ ? [parentDirectoryId]
262
+ : scope === 'app_data'
263
+ ? [this.getRootDirectory(scope)]
264
+ : undefined,
265
+ },
266
+ {
267
+ body: data,
268
+ mimeType: MimeTypes.TEXT,
269
+ }
270
+ );
269
271
  }
270
- await uploader.execute();
271
272
  }
272
273
 
273
274
  async createFile(
@@ -300,28 +301,36 @@ export default class GoogleDriveApiClient implements NativeRNCloudStorage {
300
301
  }
301
302
  }
302
303
 
303
- const uploader = GoogleDriveApiClient.drive.files.newMultipartUploader().setData(data, MimeTypes.TEXT);
304
- if (fileId) uploader.setIdOfFileToUpdate(fileId);
305
- else {
306
- const files = await this.listInternalFiles(scope);
304
+ if (fileId) {
305
+ await GoogleDrive.drive.updateFile(fileId, {
306
+ body: data,
307
+ mimeType: MimeTypes.TEXT,
308
+ });
309
+ } else {
310
+ const files = await GoogleDrive.drive.listFiles(this.getRootDirectory(scope));
307
311
  const { directories, filename } = this.resolvePathToDirectories(path);
308
312
  const parentDirectoryId = this.findParentDirectoryId(files, directories);
309
- uploader.setRequestBody({
310
- name: filename,
311
- parents: parentDirectoryId
312
- ? [parentDirectoryId]
313
- : scope === 'app_data'
314
- ? [this.getRootDirectory(scope)]
315
- : undefined,
316
- });
313
+ await GoogleDrive.drive.createFile(
314
+ {
315
+ name: filename,
316
+ parents: parentDirectoryId
317
+ ? [parentDirectoryId]
318
+ : scope === 'app_data'
319
+ ? [this.getRootDirectory(scope)]
320
+ : undefined,
321
+ },
322
+ {
323
+ body: data,
324
+ mimeType: MimeTypes.TEXT,
325
+ }
326
+ );
317
327
  }
318
- await uploader.execute();
319
328
  }
320
329
 
321
330
  async listFiles(path: string, scope: NativeRNCloudCloudStorageScope): Promise<string[]> {
322
- const allFiles = await this.listInternalFiles(scope);
331
+ const allFiles = await GoogleDrive.drive.listFiles(this.getRootDirectory(scope));
323
332
  if (path !== '') {
324
- const fileId = await this.getFileId(path, scope, false);
333
+ const fileId = await this.getFileId(path, scope);
325
334
  const files = allFiles.filter((f) => (f.parents ?? [])[0] === fileId);
326
335
 
327
336
  return Array.from(new Set(files.map((f) => f.name)));
@@ -345,25 +354,23 @@ export default class GoogleDriveApiClient implements NativeRNCloudStorage {
345
354
  }
346
355
  }
347
356
 
348
- const uploader = GoogleDriveApiClient.drive.files.newMetadataOnlyUploader();
349
- const files = await this.listInternalFiles(scope);
357
+ const files = await GoogleDrive.drive.listFiles(this.getRootDirectory(scope));
350
358
  const { directories, filename } = this.resolvePathToDirectories(path);
351
359
  const parentDirectoryId = this.findParentDirectoryId(files, directories);
352
- uploader.setRequestBody({
360
+
361
+ await GoogleDrive.drive.createDirectory({
353
362
  name: filename,
354
- mimeType: MimeTypes.FOLDER,
355
363
  parents: parentDirectoryId
356
364
  ? [parentDirectoryId]
357
365
  : scope === 'app_data'
358
366
  ? [this.getRootDirectory(scope)]
359
367
  : undefined,
360
368
  });
361
- await uploader.execute();
362
369
  }
363
370
 
364
371
  async readFile(path: string, scope: NativeRNCloudCloudStorageScope): Promise<string> {
365
372
  const fileId = await this.getFileId(path, scope);
366
- const content = await GoogleDriveApiClient.drive.files.getText(fileId);
373
+ const content = await GoogleDrive.drive.getFileText(fileId);
367
374
  return content;
368
375
  }
369
376
 
@@ -373,17 +380,34 @@ export default class GoogleDriveApiClient implements NativeRNCloudStorage {
373
380
  }
374
381
 
375
382
  async deleteFile(path: string, scope: NativeRNCloudCloudStorageScope): Promise<void> {
376
- const fileId = await this.getFileId(path, scope);
377
- await GoogleDriveApiClient.drive.files.delete(fileId);
383
+ // if trying to pass a directory, throw an error
384
+ const fileId = await this.getFileId(path, scope, 'directory');
385
+ await GoogleDrive.drive.deleteFile(fileId);
386
+ }
387
+
388
+ async deleteDirectory(path: string, recursive: boolean, scope: NativeRNCloudCloudStorageScope): Promise<void> {
389
+ // if trying to pass a file, throw an error
390
+ const fileId = await this.getFileId(path, scope, 'file');
391
+
392
+ if (!recursive) {
393
+ // check if the directory is empty
394
+ const files = await GoogleDrive.drive.listFiles(this.getRootDirectory(scope));
395
+ const filesInDirectory = files.filter((f) => (f.parents ?? [])[0] === fileId);
396
+ if (filesInDirectory.length > 0) {
397
+ throw new CloudStorageError(
398
+ `Directory ${path} is not empty`,
399
+ CloudStorageErrorCode.DELETE_ERROR,
400
+ filesInDirectory
401
+ );
402
+ }
403
+ }
404
+
405
+ await GoogleDrive.drive.deleteFile(fileId);
378
406
  }
379
407
 
380
408
  async statFile(path: string, scope: NativeRNCloudCloudStorageScope): Promise<NativeRNCloudCloudStorageFileStat> {
381
409
  const fileId = await this.getFileId(path, scope, false);
382
- const file: GoogleDriveDetailedFile = await (
383
- await GoogleDriveApiClient.drive.files.get(fileId!, {
384
- fields: 'id,kind,mimeType,name,parents,spaces,size,createdTime,modifiedTime',
385
- })
386
- ).json();
410
+ const file = await GoogleDrive.drive.getFile(fileId!);
387
411
 
388
412
  return {
389
413
  size: file.size ?? 0,
@@ -1,13 +1,21 @@
1
+ export type GoogleDriveFileSpace = 'appDataFolder' | 'drive';
2
+
3
+ export enum MimeTypes {
4
+ BINARY = 'application/octet-stream',
5
+ CSV = 'text/csv',
6
+ FOLDER = 'application/vnd.google-apps.folder',
7
+ JSON = 'application/json',
8
+ PDF = 'application/pdf',
9
+ TEXT = 'text/plain',
10
+ }
11
+
1
12
  export interface GoogleDriveFile {
2
13
  id: string;
3
14
  kind: 'drive#file';
4
15
  mimeType: string;
5
16
  name: string;
6
17
  parents: string[];
7
- spaces: ('appDataFolder' | 'drive')[];
8
- }
9
-
10
- export interface GoogleDriveDetailedFile extends GoogleDriveFile {
18
+ spaces: GoogleDriveFileSpace[];
11
19
  createdTime: string;
12
20
  modifiedTime: string;
13
21
  size?: number;
@@ -10,8 +10,10 @@ export interface NativeRNCloudCloudStorageFileStat {
10
10
 
11
11
  export enum CloudStorageErrorCode {
12
12
  FILE_NOT_FOUND = 'ERR_FILE_NOT_FOUND',
13
+ PATH_IS_FILE = 'ERR_PATH_IS_FILE',
13
14
  PATH_IS_DIRECTORY = 'ERR_PATH_IS_DIRECTORY',
14
15
  DIRECTORY_NOT_FOUND = 'ERR_DIRECTORY_NOT_FOUND',
16
+ DIRECTORY_NOT_EMPTY = 'ERR_DIRECTORY_NOT_EMPTY',
15
17
  FILE_ALREADY_EXISTS = 'ERR_FILE_EXISTS',
16
18
  MULTIPLE_FILES_SAME_NAME = 'ERR_MULTIPLE_FILES_SAME_NAME',
17
19
  AUTHENTICATION_FAILED = 'ERR_AUTHENTICATION_FAILED',
@@ -33,6 +35,7 @@ export default interface NativeRNCloudStorage {
33
35
  readFile: (path: string, scope: NativeRNCloudCloudStorageScope) => Promise<string>;
34
36
  downloadFile: (path: string, scope: NativeRNCloudCloudStorageScope) => Promise<void>;
35
37
  deleteFile: (path: string, scope: NativeRNCloudCloudStorageScope) => Promise<void>;
38
+ deleteDirectory: (path: string, recursively: boolean, scope: NativeRNCloudCloudStorageScope) => Promise<void>;
36
39
  statFile: (path: string, scope: NativeRNCloudCloudStorageScope) => Promise<NativeRNCloudCloudStorageFileStat>;
37
40
  isCloudAvailable: () => Promise<boolean>;
38
41
  }