UNPKG

@react-native-firebase/storage

Version:

React Native Firebase - React Native Firebase provides native integration with Cloud Storage, providing support to upload and download files directly from your device and from your Firebase Cloud Storage bucket.

335 lines (300 loc) 10.5 kB
/* * Copyright (c) 2016-present Invertase Limited & Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this library except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ import { isFunction, isNull, isObject } from '@react-native-firebase/app/dist/module/common'; import type { EmitterSubscription } from 'react-native'; import { TaskEvent } from './StorageStatics'; import type { FullMetadata, StorageError, StorageObserver, StorageReference, Subscribe, Task, TaskSnapshot, Unsubscribe, } from './types/storage'; import type { StorageReferenceInternal, StorageInternal } from './types/internal'; let TASK_ID = 0; type TaskEventType = (typeof TaskEvent)[keyof typeof TaskEvent]; function createEmptyMetadata(ref: StorageReference): FullMetadata { return { bucket: ref.bucket, fullPath: ref.fullPath, generation: '', metageneration: '', name: ref.name, size: 0, timeCreated: '', updated: '', downloadTokens: undefined, ref, }; } function wrapErrorEventListener( listenerFn: ((error: StorageError) => void) | null | undefined, unsubscribe: (() => void) | null | undefined, ): (snapshot: TaskSnapshot) => void { return (snapshot: TaskSnapshot) => { if (unsubscribe) { setTimeout(() => unsubscribe(), 0); } // 1 frame = 16ms, pushing to next frame if (isFunction(listenerFn)) { const errorEvent = snapshot as TaskSnapshot & { error?: StorageError; }; if (errorEvent.error) { listenerFn(errorEvent.error); } } }; } function wrapSnapshotEventListener( task: StorageTask, listenerFn: ((snapshot: TaskSnapshot) => unknown) | null | undefined, unsubscribe: (() => void) | null | undefined, ): ((snapshot: TaskSnapshot) => void) | null { if (!isFunction(listenerFn)) { return null; } return (snapshot: TaskSnapshot) => { if (unsubscribe) { setTimeout(() => unsubscribe(), 0); } // 1 frame = 16ms, pushing to next frame if (isFunction(listenerFn)) { const taskSnapshot = Object.assign({}, snapshot); taskSnapshot.task = task as Task; taskSnapshot.ref = task._ref; if (!taskSnapshot.metadata) { taskSnapshot.metadata = createEmptyMetadata(task._ref); } else { if (!taskSnapshot.metadata.generation) taskSnapshot.metadata.generation = ''; if (!taskSnapshot.metadata.bucket) taskSnapshot.metadata.bucket = task._ref.bucket; if (!taskSnapshot.metadata.metageneration) taskSnapshot.metadata.metageneration = ''; if (!taskSnapshot.metadata.fullPath) taskSnapshot.metadata.fullPath = task._ref.fullPath; if (!taskSnapshot.metadata.name) taskSnapshot.metadata.name = task._ref.name; if (!taskSnapshot.metadata.ref) taskSnapshot.metadata.ref = task._ref; } Object.freeze(taskSnapshot); task._snapshot = taskSnapshot; listenerFn(taskSnapshot); } }; } function addTaskEventListener( task: StorageTask, eventName: string, listener: (snapshot: TaskSnapshot) => void, ): EmitterSubscription { let _eventName = eventName; if (_eventName !== TaskEvent.STATE_CHANGED) { _eventName = `${task._type}_${eventName}`; } return task._storage.emitter.addListener( task._storage.eventNameForApp(task._id, _eventName), listener, ); } function subscribeToEvents( task: StorageTask, nextOrObserver?: ((snapshot: TaskSnapshot) => unknown) | StorageObserver<TaskSnapshot> | null, error?: ((error: StorageError) => unknown) | null, complete?: (() => unknown) | null, ): () => void { let _error: ((snapshot: TaskSnapshot) => void) | undefined; let _errorSubscription: EmitterSubscription | undefined; let _next: ((snapshot: TaskSnapshot) => void) | null | undefined; let _nextSubscription: EmitterSubscription | undefined; let _complete: ((snapshot: TaskSnapshot) => void) | null | undefined; let _completeSubscription: EmitterSubscription | undefined; const unsubscribe = () => { if (_nextSubscription) { _nextSubscription.remove(); } if (_errorSubscription) { _errorSubscription.remove(); } if (_completeSubscription) { _completeSubscription.remove(); } }; if (isFunction(nextOrObserver)) { _error = wrapErrorEventListener(error, unsubscribe); _next = wrapSnapshotEventListener(task, nextOrObserver, null); _complete = wrapSnapshotEventListener(task, complete, unsubscribe); } else if (isObject(nextOrObserver)) { const observer = nextOrObserver as StorageObserver<TaskSnapshot>; _error = wrapErrorEventListener(observer.error, unsubscribe); _next = wrapSnapshotEventListener(task, observer.next, null); _complete = wrapSnapshotEventListener(task, observer.complete, unsubscribe); } else if (isNull(nextOrObserver)) { _error = wrapErrorEventListener(error, unsubscribe); _complete = wrapSnapshotEventListener(task, complete, unsubscribe); } else { throw new Error( "firebase.storage.StorageTask.on(*, _) 'nextOrObserver' must be a Function, an Object or Null.", ); } if (_next) { _nextSubscription = addTaskEventListener(task, TaskEvent.STATE_CHANGED, _next); } if (_error) { _errorSubscription = addTaskEventListener(task, 'failure', _error); } if (_complete) { _completeSubscription = addTaskEventListener(task, 'success', _complete); } return unsubscribe; } export default class StorageTask { _type: string; _id: number; _promise: Promise<TaskSnapshot> | null; _ref: StorageReference; _beginTask: (task: StorageTask) => Promise<TaskSnapshot>; _storage: StorageInternal; _snapshot: TaskSnapshot; constructor( type: string, storageRef: StorageReference, beginTaskFn: (task: StorageTask) => Promise<TaskSnapshot>, ) { this._type = type; this._id = TASK_ID++; this._promise = null; this._ref = storageRef; this._beginTask = beginTaskFn; this._storage = (storageRef as StorageReferenceInternal)._storage; this._snapshot = { bytesTransferred: 0, totalBytes: 0, state: 'running', metadata: createEmptyMetadata(storageRef), task: this as Task, ref: storageRef, }; } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask#then */ get then(): ( onFulfilled?: ((snapshot: TaskSnapshot) => TaskSnapshot) | null, onRejected?: ((error: StorageError) => any) | null, ) => Promise<TaskSnapshot> { if (!this._promise) { this._promise = this._beginTask(this); } const promise = this._promise; return ( onFulfilled?: ((snapshot: TaskSnapshot) => any) | null, onRejected?: ((error: StorageError) => any) | null, ) => { return new Promise((resolve, reject) => { promise .then((response: any) => { this._snapshot = { ...response, ref: this._ref, task: this as Task, state: response?.state ?? this._snapshot.state, metadata: response?.metadata ? response.metadata : createEmptyMetadata(this._ref), } as TaskSnapshot; if (onFulfilled) { resolve(onFulfilled(this._snapshot!)); } else { resolve(response); } }) .catch((error: StorageError) => { if (onRejected) { resolve(onRejected(error)); } else { reject(error); } }); }); }; } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask#catch */ get catch(): (onRejected: (error: StorageError) => any) => Promise<any> { if (!this._promise) { this._promise = this._beginTask(this); } return this._promise!.catch.bind(this._promise); } get snapshot(): TaskSnapshot { return this._snapshot; } // // NOT on Web SDK // /** // * @url https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask#finally // */ // get finally() { // if (!this._promise) this._promise = this._beginTask(this); // return this._promise.finally.bind(this._promise); // } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask#on */ on(event: TaskEventType): Subscribe<TaskSnapshot>; on( event: TaskEventType, nextOrObserver?: StorageObserver<TaskSnapshot> | null | ((snapshot: TaskSnapshot) => unknown), error?: ((error: StorageError) => unknown) | null, complete?: (() => unknown) | null, ): Unsubscribe; on( event: TaskEventType, nextOrObserver?: StorageObserver<TaskSnapshot> | null | ((snapshot: TaskSnapshot) => unknown), error?: ((error: StorageError) => unknown) | null, complete?: (() => unknown) | null, ): Unsubscribe | Subscribe<TaskSnapshot> { if (event !== TaskEvent.STATE_CHANGED) { throw new Error( `firebase.storage.StorageTask.on event argument must be a string with a value of '${TaskEvent.STATE_CHANGED}'`, ); } if (!this._promise) { this._promise = this._beginTask(this); } // if only event provided return the subscriber function if (!nextOrObserver && !error && !complete) { return ((...args: Parameters<Subscribe<TaskSnapshot>>) => subscribeToEvents(this, ...args)) as Subscribe<TaskSnapshot>; } return subscribeToEvents(this, nextOrObserver, error, complete); } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask#pause */ pause(): boolean { return this._storage.native.setTaskStatus(this._id, 0); } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask#pause */ resume(): boolean { return this._storage.native.setTaskStatus(this._id, 1); } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask#cancel */ cancel(): boolean { return this._storage.native.setTaskStatus(this._id, 2); } }