/* @flow */ import { statics as StorageStatics } from './'; import { isObject, isFunction } from './../../utils'; import StorageReference from './reference'; export const UPLOAD_TASK = 'upload'; export const DOWNLOAD_TASK = 'download'; declare type UploadTaskSnapshotType = { bytesTransferred: number, downloadURL: string|null, metadata: Object, // TODO flow type def for https://firebase.google.com/docs/reference/js/firebase.storage.FullMetadata.html ref: StorageReference, state: ( typeof StorageStatics.TaskState.RUNNING | typeof StorageStatics.TaskState.PAUSED | typeof StorageStatics.TaskState.SUCCESS | typeof StorageStatics.TaskState.CANCELLED | typeof StorageStatics.TaskState.ERROR ), task: StorageTask, totalBytes: number, }; declare type FuncSnapshotType = null|(snapshot: UploadTaskSnapshotType) => any; declare type FuncErrorType = null|(error: Error) => any; declare type NextOrObserverType = null | { next?: FuncSnapshotType, error?: FuncErrorType, complete?:FuncSnapshotType } | FuncSnapshotType; /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask */ export default class StorageTask { type: typeof UPLOAD_TASK | typeof DOWNLOAD_TASK ref: StorageReference storage: StorageReference.storage path: StorageReference.path then: Promise<*> catch: () => Promise<*> constructor(type: typeof UPLOAD_TASK | typeof DOWNLOAD_TASK, promise: Promise<*>, storageRef: StorageReference) { this.type = type; this.ref = storageRef; this.storage = storageRef.storage; this.path = storageRef.path; // 'proxy' original promise this.then = promise.then(this._interceptSnapshotEvent); this.catch = promise.catch.bind(promise); } /** * Intercepts a native snapshot result object attaches ref / task instances * and calls the original function * @returns {Promise.} * @private */ _interceptSnapshotEvent(f: ?Function): null | () => * { if (!isFunction(f)) return null; return (snapshot) => { const _snapshot = Object.assign({}, snapshot); _snapshot.task = this; _snapshot.ref = this.ref; return f && f(_snapshot); }; } /** * Intercepts a error object form native and converts to a JS Error * @param f * @returns {*} * @private */ _interceptErrorEvent(f: ?Function): null | (Error) => * { if (!isFunction(f)) return null; return (error) => { const _error = new Error(error.message); // $FlowFixMe _error.code = error.code; return f && f(_error); }; } /** * * @param nextOrObserver * @param error * @param complete * @returns {function()} * @private */ _subscribe(nextOrObserver: NextOrObserverType, error: FuncErrorType, complete: FuncSnapshotType): Function { let _error; let _next; let _complete; if (typeof nextOrObserver === 'function') { _error = this._interceptErrorEvent(error); _next = this._interceptSnapshotEvent(nextOrObserver); _complete = this._interceptSnapshotEvent(complete); } else if (nextOrObserver) { _error = this._interceptErrorEvent(nextOrObserver.error); _next = this._interceptSnapshotEvent(nextOrObserver.next); _complete = this._interceptSnapshotEvent(nextOrObserver.complete); } if (_next) { this.storage._addListener( this.path, StorageStatics.TaskEvent.STATE_CHANGED, _next ); } if (_error) { this.storage._addListener( this.path, `${this.type}_failure`, _error ); } if (_complete) { this.storage._addListener( this.path, `${this.type}_success`, _complete ); } return () => { if (_next) this.storage._removeListener(this.path, StorageStatics.TaskEvent.STATE_CHANGED, _next); if (_error) this.storage._removeListener(this.path, `${this.type}_failure`, _error); if (_complete) this.storage._removeListener(this.path, `${this.type}_success`, _complete); }; } /** * * @param event * @param nextOrObserver * @param error * @param complete * @returns {function()} */ on(event: string = StorageStatics.TaskEvent.STATE_CHANGED, nextOrObserver: NextOrObserverType, error: FuncErrorType, complete: FuncSnapshotType): Function { if (!event) { throw new Error('StorageTask.on listener is missing required string argument \'event\'.'); } if (event !== StorageStatics.TaskEvent.STATE_CHANGED) { throw new Error(`StorageTask.on event argument must be a string with a value of '${StorageStatics.TaskEvent.STATE_CHANGED}'`); } // if only event provided return the subscriber function if (!nextOrObserver && !error && !complete) { return this._subscribe.bind(this); } return this._subscribe(nextOrObserver, error, complete); } pause() { throw new Error('.pause() is not currently supported by react-native-firebase'); } resume() { // todo throw new Error('.resume() is not currently supported by react-native-firebase'); } cancel() { // todo throw new Error('.cancel() is not currently supported by react-native-firebase'); } }