diff --git a/packages/storage/lib/StorageDownloadTask.js b/packages/storage/lib/StorageDownloadTask.ts similarity index 83% rename from packages/storage/lib/StorageDownloadTask.js rename to packages/storage/lib/StorageDownloadTask.ts index 0d6809e524..24cbb7cd24 100644 --- a/packages/storage/lib/StorageDownloadTask.js +++ b/packages/storage/lib/StorageDownloadTask.ts @@ -16,11 +16,12 @@ */ import StorageTask from './StorageTask'; +import type { Reference, TaskSnapshot } from './types/storage'; const DOWNLOAD_TASK = 'download'; export default class StorageDownloadTask extends StorageTask { - constructor(storageRef, beginTaskFn) { + constructor(storageRef: Reference, beginTaskFn: (task: StorageTask) => Promise) { super(DOWNLOAD_TASK, storageRef, beginTaskFn); } } diff --git a/packages/storage/lib/StorageListResult.js b/packages/storage/lib/StorageListResult.ts similarity index 51% rename from packages/storage/lib/StorageListResult.js rename to packages/storage/lib/StorageListResult.ts index c0646e7657..d252346287 100644 --- a/packages/storage/lib/StorageListResult.js +++ b/packages/storage/lib/StorageListResult.ts @@ -15,28 +15,47 @@ * */ +import type { Reference } from './types/storage'; +import type { ListResultInternal, StorageInternal } from './types/internal'; + // To avoid React Native require cycle warnings -let StorageReference = null; -export function provideStorageReferenceClass(storageReference) { +let StorageReference: (new (storage: StorageInternal, path: string) => Reference) | null = null; + +export function provideStorageReferenceClass( + storageReference: new (storage: StorageInternal, path: string) => Reference, +): void { StorageReference = storageReference; } export default class StorageListResult { - constructor(storage, nativeData) { + private _nextPageToken: string | null; + private _items: Reference[]; + private _prefixes: Reference[]; + + constructor(storage: StorageInternal, nativeData: ListResultInternal) { this._nextPageToken = nativeData.nextPageToken || null; - this._items = nativeData.items.map(path => new StorageReference(storage, path)); - this._prefixes = nativeData.prefixes.map(path => new StorageReference(storage, path)); + + if (!StorageReference) { + throw new Error( + 'StorageReference class has not been provided. This is likely a module initialization issue.', + ); + } + + // TypeScript doesn't narrow the type after the null check, so we assign to a const + const StorageReferenceClass = StorageReference; + this._items = nativeData.items.map(path => new StorageReferenceClass(storage, path)); + this._prefixes = nativeData.prefixes.map(path => new StorageReferenceClass(storage, path)); } - get items() { + get items(): Reference[] { return this._items; } - get nextPageToken() { + get nextPageToken(): string | null { return this._nextPageToken; } - get prefixes() { + get prefixes(): Reference[] { return this._prefixes; } } diff --git a/packages/storage/lib/StorageReference.js b/packages/storage/lib/StorageReference.ts similarity index 78% rename from packages/storage/lib/StorageReference.js rename to packages/storage/lib/StorageReference.ts index 40a61f9705..dca3bb5c7b 100644 --- a/packages/storage/lib/StorageReference.js +++ b/packages/storage/lib/StorageReference.ts @@ -35,9 +35,20 @@ import StorageListResult, { provideStorageReferenceClass } from './StorageListRe import { StringFormat } from './StorageStatics'; import StorageUploadTask from './StorageUploadTask'; import { validateMetadata } from './utils'; - -export default class StorageReference extends ReferenceBase { - constructor(storage, path) { +import type { + Reference, + SettableMetadata, + ListOptions, + FullMetadata, + Task, + Storage, +} from './types/storage'; +import type { ListResultInternal, StorageInternal } from './types/internal'; + +export default class StorageReference extends ReferenceBase implements Reference { + _storage: StorageInternal; + + constructor(storage: StorageInternal, path: string) { super(path); this._storage = storage; } @@ -45,28 +56,28 @@ export default class StorageReference extends ReferenceBase { /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#bucket */ - get bucket() { - return this._storage._customUrlOrRegion.replace('gs://', ''); + get bucket(): string { + return this._storage._customUrlOrRegion!.replace('gs://', ''); } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#fullPath */ - get fullPath() { + get fullPath(): string { return this.path; } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#name */ - get name() { + get name(): string { return pathLastComponent(this.path); } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#parent */ - get parent() { + get parent(): Reference | null { const parentPath = pathParent(this.path); if (parentPath === null) { return parentPath; @@ -77,21 +88,21 @@ export default class StorageReference extends ReferenceBase { /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#root */ - get root() { + get root(): Reference { return new StorageReference(this._storage, '/'); } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#storage */ - get storage() { + get storage(): Storage { return this._storage; } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#child */ - child(path) { + child(path: string): Reference { const childPath = pathChild(this.path, path); return new StorageReference(this._storage, childPath); } @@ -99,53 +110,54 @@ export default class StorageReference extends ReferenceBase { /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#delete */ - delete() { + delete(): Promise { return this._storage.native.delete(this.toString()); } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#getDownloadURL */ - getDownloadURL() { + getDownloadURL(): Promise { return this._storage.native.getDownloadURL(this.toString()); } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#getMetadata */ - getMetadata() { + getMetadata(): Promise { return this._storage.native.getMetadata(this.toString()); } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#list */ - list(options) { + list(options?: ListOptions): Promise { if (!isUndefined(options) && !isObject(options)) { throw new Error( "firebase.storage.StorageReference.list(*) 'options' expected an object value.", ); } - const listOptions = { + const listOptions: { maxResults: number; pageToken?: string } = { maxResults: 1000, }; if (options) { if (hasOwnProperty(options, 'maxResults')) { - if (!isNumber(options.maxResults) || !isInteger(options.maxResults)) { + const maxResults = options.maxResults; + if (!isNumber(maxResults) || !isInteger(maxResults)) { throw new Error( "firebase.storage.StorageReference.list(*) 'options.maxResults' expected a number value.", ); } - if (options.maxResults < 1 || options.maxResults > 1000) { + if (maxResults < 1 || maxResults > 1000) { throw new Error( "firebase.storage.StorageReference.list(*) 'options.maxResults' expected a number value between 1-1000.", ); } - listOptions.maxResults = options.maxResults; + listOptions.maxResults = maxResults; } if (options.pageToken) { @@ -161,29 +173,34 @@ export default class StorageReference extends ReferenceBase { return this._storage.native .list(this.toString(), listOptions) - .then(data => new StorageListResult(this._storage, data)); + .then((data: ListResultInternal) => new StorageListResult(this._storage, data)); } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#listAll */ - listAll() { + listAll(): Promise { return this._storage.native .listAll(this.toString()) - .then(data => new StorageListResult(this._storage, data)); + .then((data: ListResultInternal) => new StorageListResult(this._storage, data)); } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#put */ - put(data, metadata) { + put(data: Blob | Uint8Array | ArrayBuffer, metadata?: SettableMetadata): Task { if (!isUndefined(metadata)) { validateMetadata(metadata, false); } return new StorageUploadTask(this, task => Base64.fromData(data).then(({ string, format }) => { - const { _string, _format, _metadata } = this._updateString(string, format, metadata, false); + const { _string, _format, _metadata } = this._updateString( + string as string, + format, + metadata, + false, + ); return this._storage.native.putString( this.toString(), _string, @@ -198,7 +215,11 @@ export default class StorageReference extends ReferenceBase { /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#putString */ - putString(string, format = StringFormat.RAW, metadata) { + putString( + string: string, + format: (typeof StringFormat)[keyof typeof StringFormat] = StringFormat.RAW, + metadata?: SettableMetadata, + ): Task { const { _string, _format, _metadata } = this._updateString(string, format, metadata, false); return new StorageUploadTask(this, task => @@ -209,7 +230,7 @@ export default class StorageReference extends ReferenceBase { /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#fullPath */ - toString() { + toString(): string { if (this.path.length <= 1) { return `${this._storage._customUrlOrRegion}`; } @@ -220,7 +241,7 @@ export default class StorageReference extends ReferenceBase { /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#updateMetadata */ - updateMetadata(metadata) { + updateMetadata(metadata: SettableMetadata): Promise { validateMetadata(metadata); return this._storage.native.updateMetadata(this.toString(), metadata); } @@ -232,7 +253,7 @@ export default class StorageReference extends ReferenceBase { /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference */ - writeToFile(filePath) { + writeToFile(filePath: string): Task { if (!isString(filePath)) { throw new Error( "firebase.storage.StorageReference.writeToFile(*) 'filePath' expects a string value.", @@ -247,7 +268,7 @@ export default class StorageReference extends ReferenceBase { /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference */ - putFile(filePath, metadata) { + putFile(filePath: string, metadata?: SettableMetadata): Task { if (!isUndefined(metadata)) { validateMetadata(metadata, false); } @@ -263,7 +284,12 @@ export default class StorageReference extends ReferenceBase { ); } - _updateString(string, format, metadata, update = false) { + _updateString( + string: string, + format: (typeof StringFormat)[keyof typeof StringFormat], + metadata: SettableMetadata | undefined, + update = false, + ): { _string: string; _format: string; _metadata: SettableMetadata | undefined } { if (!isString(string)) { throw new Error( "firebase.storage.StorageReference.putString(*, _, _) 'string' expects a string value.", @@ -301,7 +327,7 @@ export default class StorageReference extends ReferenceBase { if (isUndefined(metadata)) { _metadata = {}; } - _metadata.contentType = mediaType; + _metadata!.contentType = mediaType; _string = base64String; _format = StringFormat.BASE64; } diff --git a/packages/storage/lib/StorageStatics.js b/packages/storage/lib/StorageStatics.ts similarity index 96% rename from packages/storage/lib/StorageStatics.js rename to packages/storage/lib/StorageStatics.ts index bd94f2d1e8..26bdb82bdd 100644 --- a/packages/storage/lib/StorageStatics.js +++ b/packages/storage/lib/StorageStatics.ts @@ -20,14 +20,16 @@ export const StringFormat = { BASE64: 'base64', BASE64URL: 'base64url', DATA_URL: 'data_url', -}; +} as const; + export const TaskEvent = { STATE_CHANGED: 'state_changed', -}; +} as const; + export const TaskState = { RUNNING: 'running', PAUSED: 'paused', SUCCESS: 'success', CANCELLED: 'cancelled', ERROR: 'error', -}; +} as const; diff --git a/packages/storage/lib/StorageTask.js b/packages/storage/lib/StorageTask.ts similarity index 50% rename from packages/storage/lib/StorageTask.js rename to packages/storage/lib/StorageTask.ts index 134b662df0..4afd32c81e 100644 --- a/packages/storage/lib/StorageTask.js +++ b/packages/storage/lib/StorageTask.ts @@ -16,58 +16,75 @@ */ import { isFunction, isNull, isObject } from '@react-native-firebase/app/dist/module/common'; +import type { EmitterSubscription } from 'react-native'; import { TaskEvent } from './StorageStatics'; +import type { TaskSnapshot, Reference, Task, TaskSnapshotObserver } from './types/storage'; +import type { ReferenceInternal, StorageInternal } from './types/internal'; let TASK_ID = 0; -function wrapErrorEventListener(listenerFn, unsubscribe) { - return event => { +function wrapErrorEventListener( + listenerFn: ((error: Error) => 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)) { - listenerFn(event.error); + const errorEvent = snapshot as TaskSnapshot & { error?: Error }; + if (errorEvent.error) { + listenerFn(errorEvent.error); + } } }; } -function wrapSnapshotEventListener(task, listenerFn, unsubscribe) { +function wrapSnapshotEventListener( + task: StorageTask, + listenerFn: ((snapshot: TaskSnapshot) => void) | null | undefined, + unsubscribe: (() => void) | null | undefined, +): ((snapshot: TaskSnapshot) => void) | null { if (!isFunction(listenerFn)) { return null; } - return event => { + return (snapshot: TaskSnapshot) => { if (unsubscribe) { setTimeout(() => unsubscribe(), 0); } // 1 frame = 16ms, pushing to next frame if (isFunction(listenerFn)) { - const snapshot = Object.assign({}, event); - snapshot.task = task; - snapshot.ref = task._ref; + const taskSnapshot = Object.assign({}, snapshot); + taskSnapshot.task = task as unknown as Task; + taskSnapshot.ref = task._ref; - if (snapshot.metadata) { - if (!snapshot.metadata.generation) { - snapshot.metadata.generation = ''; + if (taskSnapshot.metadata) { + if (!taskSnapshot.metadata.generation) { + taskSnapshot.metadata.generation = ''; } - if (!snapshot.metadata.bucket) { - snapshot.metadata.bucket = task._ref.bucket; + if (!taskSnapshot.metadata.bucket) { + taskSnapshot.metadata.bucket = task._ref.bucket; } - if (!snapshot.metadata.metageneration) { - snapshot.metadata.metageneration = ''; + if (!taskSnapshot.metadata.metageneration) { + taskSnapshot.metadata.metageneration = ''; } // // TODO(salakar): these are always here, cannot repro without, remove in 6.1.0 if no issues: - // if (!snapshot.metadata.name) snapshot.metadata.name = task._ref.name; - // if (!snapshot.metadata.fullPath) snapshot.metadata.fullPath = task._ref.fullPath; + // if (!taskSnapshot.metadata.name) taskSnapshot.metadata.name = task._ref.name; + // if (!taskSnapshot.metadata.fullPath) taskSnapshot.metadata.fullPath = task._ref.fullPath; } - Object.freeze(snapshot); - task._snapshot = snapshot; + Object.freeze(taskSnapshot); + task._snapshot = taskSnapshot; - listenerFn(snapshot); + listenerFn(taskSnapshot); } }; } -function addTaskEventListener(task, eventName, listener) { +function addTaskEventListener( + task: StorageTask, + eventName: string, + listener: (snapshot: TaskSnapshot) => void, +): EmitterSubscription { let _eventName = eventName; if (_eventName !== TaskEvent.STATE_CHANGED) { _eventName = `${task._type}_${eventName}`; @@ -79,15 +96,20 @@ function addTaskEventListener(task, eventName, listener) { ); } -function subscribeToEvents(task, nextOrObserver, error, complete) { - let _error; - let _errorSubscription; +function subscribeToEvents( + task: StorageTask, + nextOrObserver?: ((snapshot: TaskSnapshot) => void) | TaskSnapshotObserver | null, + error?: ((error: Error) => void) | null, + complete?: (() => void) | null, +): () => void { + let _error: ((snapshot: TaskSnapshot) => void) | undefined; + let _errorSubscription: EmitterSubscription | undefined; - let _next; - let _nextSubscription; + let _next: ((snapshot: TaskSnapshot) => void) | null | undefined; + let _nextSubscription: EmitterSubscription | undefined; - let _complete; - let _completeSubscription; + let _complete: ((snapshot: TaskSnapshot) => void) | null | undefined; + let _completeSubscription: EmitterSubscription | undefined; const unsubscribe = () => { if (_nextSubscription) { @@ -103,12 +125,13 @@ function subscribeToEvents(task, nextOrObserver, error, complete) { if (isFunction(nextOrObserver)) { _error = wrapErrorEventListener(error, unsubscribe); - _next = wrapSnapshotEventListener(task, nextOrObserver); + _next = wrapSnapshotEventListener(task, nextOrObserver, null); _complete = wrapSnapshotEventListener(task, complete, unsubscribe); } else if (isObject(nextOrObserver)) { - _error = wrapErrorEventListener(nextOrObserver.error, unsubscribe); - _next = wrapSnapshotEventListener(task, nextOrObserver.next); - _complete = wrapSnapshotEventListener(task, nextOrObserver.complete, unsubscribe); + const observer = nextOrObserver as TaskSnapshotObserver; + _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); @@ -134,47 +157,76 @@ function subscribeToEvents(task, nextOrObserver, error, complete) { } export default class StorageTask { - constructor(type, storageRef, beginTaskFn) { + _type: string; + _id: number; + _promise: Promise | null; + _ref: Reference; + _beginTask: (task: StorageTask) => Promise; + _storage: StorageInternal; + _snapshot: TaskSnapshot | null; + + constructor( + type: string, + storageRef: Reference, + beginTaskFn: (task: StorageTask) => Promise, + ) { this._type = type; this._id = TASK_ID++; this._promise = null; this._ref = storageRef; this._beginTask = beginTaskFn; - this._storage = storageRef._storage; + this._storage = (storageRef as ReferenceInternal)._storage; this._snapshot = null; } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask#then */ - get then() { + get then(): ( + onFulfilled?: ((snapshot: TaskSnapshot) => TaskSnapshot) | null, + onRejected?: ((error: Error) => any) | null, + ) => Promise { if (!this._promise) { this._promise = this._beginTask(this); } - return new Promise((resolve, reject) => { - const boundPromise = this._promise.then.bind(this._promise); - - boundPromise(response => { - this._snapshot = { ...response, ref: this._ref, task: this }; - resolve(response); - }).catch(error => { - reject(error); + const promise = this._promise; + return ( + onFulfilled?: ((snapshot: TaskSnapshot) => any) | null, + onRejected?: ((error: Error) => any) | null, + ) => { + return new Promise((resolve, reject) => { + promise + .then((response: any) => { + this._snapshot = { ...response, ref: this._ref, task: this } as TaskSnapshot; + if (onFulfilled) { + resolve(onFulfilled(this._snapshot!)); + } else { + resolve(response); + } + }) + .catch((error: Error) => { + if (onRejected) { + resolve(onRejected(error)); + } else { + reject(error); + } + }); }); - }).then.bind(this._promise); + }; } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask#catch */ - get catch() { + get catch(): (onRejected: (error: Error) => any) => Promise { if (!this._promise) { this._promise = this._beginTask(this); } - return this._promise.catch.bind(this._promise); + return this._promise!.catch.bind(this._promise); } - get snapshot() { + get snapshot(): TaskSnapshot | null { return this._snapshot; } @@ -190,7 +242,12 @@ export default class StorageTask { /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask#on */ - on(event, nextOrObserver, error, complete) { + on( + event: 'state_changed', + nextOrObserver?: TaskSnapshotObserver | null | ((snapshot: TaskSnapshot) => void), + error?: ((error: Error) => void) | null, + complete?: (() => void) | null, + ): () => void { 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}'`, @@ -203,7 +260,7 @@ export default class StorageTask { // if only event provided return the subscriber function if (!nextOrObserver && !error && !complete) { - return subscribeToEvents.bind(null, this); + return ((...args: any[]) => subscribeToEvents(this, ...args)) as () => void; } return subscribeToEvents(this, nextOrObserver, error, complete); @@ -212,21 +269,21 @@ export default class StorageTask { /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask#pause */ - pause() { + pause(): Promise { return this._storage.native.setTaskStatus(this._id, 0); } /** - * @url https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask#resume + * @url https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask#pause */ - resume() { + resume(): Promise { return this._storage.native.setTaskStatus(this._id, 1); } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask#cancel */ - cancel() { + cancel(): Promise { return this._storage.native.setTaskStatus(this._id, 2); } } diff --git a/packages/storage/lib/StorageUploadTask.js b/packages/storage/lib/StorageUploadTask.ts similarity index 83% rename from packages/storage/lib/StorageUploadTask.js rename to packages/storage/lib/StorageUploadTask.ts index b9fed92741..4209e57344 100644 --- a/packages/storage/lib/StorageUploadTask.js +++ b/packages/storage/lib/StorageUploadTask.ts @@ -16,11 +16,12 @@ */ import StorageTask from './StorageTask'; +import type { Reference, TaskSnapshot } from './types/storage'; const UPLOAD_TASK = 'upload'; export default class StorageUploadTask extends StorageTask { - constructor(storageRef, beginTaskFn) { + constructor(storageRef: Reference, beginTaskFn: (task: StorageTask) => Promise) { super(UPLOAD_TASK, storageRef, beginTaskFn); } } diff --git a/packages/storage/lib/index.d.ts b/packages/storage/lib/index.d.ts deleted file mode 100644 index 09287f6e26..0000000000 --- a/packages/storage/lib/index.d.ts +++ /dev/null @@ -1,1186 +0,0 @@ -/* - * 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 { ReactNativeFirebase } from '@react-native-firebase/app'; - -/** - * Firebase Cloud Storage package for React Native. - * - * #### Example 1 - * - * Access the firebase export from the `storage` package: - * - * ```js - * import { firebase } from '@react-native-firebase/storage'; - * - * // firebase.storage().X - * ``` - * - * #### Example 2 - * - * Using the default export from the `storage` package: - * - * ```js - * import storage from '@react-native-firebase/storage'; - * - * // storage().X - * ``` - * - * #### Example 3 - * - * Using the default export from the `app` package: - * - * ```js - * import firebase from '@react-native-firebase/app'; - * import '@react-native-firebase/storage'; - * - * // firebase.storage().X - * ``` - * - * @firebase storage - */ -export namespace FirebaseStorageTypes { - import FirebaseModule = ReactNativeFirebase.FirebaseModule; - import NativeFirebaseError = ReactNativeFirebase.NativeFirebaseError; - - /** - * Possible string formats used for uploading via `StorageReference.putString()` - * - * ```js - * firebase.storage.StringFormat; - * ``` - */ - export interface StringFormat { - /** - * Raw string format. - * - * #### Usage - * - * ```js - * firebase.storage.StringFormat.RAW; - * ``` - * - * #### Example String Format - * - * ```js - * const sampleString = ''; - * ``` - */ - RAW: 'raw'; - - /** - * Base64 string format. - * - * Learn more about Base64 [on the Mozilla Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding) - * - * #### Usage - * - * ```js - * firebase.storage.StringFormat.BASE64; - * ``` - * - * #### Example String Format - * - * ```js - * const sampleString = 'PEZvbyBCYXI+'; - * ``` - * - */ - BASE64: 'base64'; - - /** - * Base64Url string format. - * - * #### Usage - * - * ```js - * firebase.storage.StringFormat.BASE64URL; - * ``` - * - * #### Example String Format - * - * ```js - * const sampleString = 'PEZvbyBCYXI-'; - * ``` - * - */ - BASE64URL: 'base64url'; - - /** - * Data URL string format. - * - * #### Usage - * - * ```js - * firebase.storage.StringFormat.DATA_URL; - * ``` - * - * #### Example String Format - * - * ```js - * const sampleString = 'data:text/plain;base64,PEZvbyBCYXI+'; - * ``` - */ - DATA_URL: 'data_url'; - } - - /** - * An event to subscribe to that is triggered on a Upload or Download task. - * - * Event subscription is created via `StorageTask.on()`. - * - * ```js - * firebase.storage.TaskEvent; - * ``` - */ - export interface TaskEvent { - /** - * An event that indicates that the tasks state has changed. - * - * ```js - * firebase.storage.TaskEvent.STATE_CHANGED; - * ``` - */ - STATE_CHANGED: 'state_changed'; - } - - /** - * A collection of properties that indicates the current tasks state. - * - * An event subscription is created via `StorageTask.on()`. - * - * ```js - * firebase.storage.TaskEvent; - * ``` - */ - export interface TaskState { - /** - * Task has been cancelled by the user. - */ - CANCELLED: 'cancelled'; - - /** - * An Error occurred, see TaskSnapshot.error for details. - */ - ERROR: 'error'; - - /** - * Task has been paused. Resume the task via `StorageTask.resume()`. - */ - PAUSED: 'paused'; - - /** - * Task is running. Pause the task via `StorageTask.pause()` - */ - RUNNING: 'running'; - - /** - * Task has completed successfully. - */ - SUCCESS: 'success'; - } - - /** - * Cloud Storage statics. - * - * #### Example - * - * ```js - * firebase.storage; - * ``` - */ - export interface Statics { - /** - * Possible string formats used for uploading via `StorageReference.putString()` - * - * #### Example - * - * ```js - * firebase.storage.StringFormat; - * ``` - */ - StringFormat: StringFormat; - - /** - * A collection of properties that indicates the current tasks state. - * - * #### Example - * - * ```js - * firebase.storage.TaskState; - * ``` - */ - TaskState: TaskState; - - /** - * An event to subscribe to that is triggered on a Upload or Download task. - * - * #### Example - * - * ```js - * firebase.storage.TaskEvent; - * ``` - */ - TaskEvent: TaskEvent; - SDK_VERSION: string; - } - - /** - * An interface representing all the metadata properties that can be set. - * - * This is used in updateMetadata, put, putString & putFile. - */ - export interface SettableMetadata { - /** - * The 'Cache-Control' HTTP header that will be set on the storage object when it's requested. - * - * #### Example 1 - * - * To turn off caching, you can set the following cacheControl value. - * - * ```js - * { - * cacheControl: 'no-store', - * } - * ``` - * - * #### Example 2 - * - * To aggressively cache an object, e.g. static assets, you can set the following cacheControl value. - * - * ```js - * { - * cacheControl: 'public, max-age=31536000', - * } - * ``` - * - * [Learn more about this header on Mozilla.](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control) - */ - cacheControl?: string | null; - - /** - * The 'Content-Disposition' HTTP header that will be set on the storage object when it's requested. - * - * [Learn more about this header on Mozilla.](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) - */ - contentDisposition?: string | null; - - /** - * The 'Content-Encoding' HTTP header that will be used on the storage object when it's requested. - * - * [Learn more about this header on Mozilla.](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding) - */ - contentEncoding?: string | null; - - /** - * The 'Content-Language' HTTP header that will be set on the storage object when it's requested. - * - * [Learn more about this header on Mozilla.](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Language) - */ - contentLanguage?: string | null; - - /** - * The 'Content-Type' HTTP header that will be set on the object when it's requested. - * - * This is used to indicate the media type (or MIME type) of the object. When uploading a file - * Firebase Cloud Storage for React Native will attempt to automatically detect this if `contentType` - * is not already set, if it fails to detect a media type it will default to `application/octet-stream`. - * - * For `DATA_URL` string formats uploaded via `putString` this will also be automatically extracted if available. - * - * #### Example - * - * Setting the content type as JSON, e.g. for when uploading a JSON string via `putString`. - * - * ```js - * { - * contentType: 'application/json', - * } - * ``` - * - * [Learn more about this header on Mozilla.](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) - */ - contentType?: string | null; - - /** - * You may specify the md5hash of the file in metadata on upload only. It may not be updated via updateMetadata - */ - md5hash?: string | null; - - /** - * Additional user-defined custom metadata for this storage object. - * - * All values must be strings. Set to null to delete all. Any keys ommitted during update will be removed. - * - * #### Example - * - * Adding a user controlled NSFW meta data field. - * - * ```js - * { - * customMetadata: { - * 'nsfw': 'true' - * }, - * } - */ - customMetadata?: { - [key: string]: string; - } | null; - } - - /** - * The full readable metadata returned by `TaskSnapshot.metadata` or `StorageReference.getMetadata()`. - */ - export interface FullMetadata extends SettableMetadata { - /** - * A Base64-encoded MD5 hash of the storage object being uploaded. - */ - md5Hash: string | null; - - /** - * The bucket this storage object is contained in. - * - * #### Example Value - * - * ``` - * gs://my-project-storage-bucket - * ``` - */ - bucket: string; - - /** - * The full path to this storage object in its bucket. - * - * #### Example Value - * - * ``` - * invertase/logo.png - * ``` - */ - fullPath: string; - - /** - * Storage object generation values enable users to uniquely identify data resources, e.g. object versioning. - * - * Read more on generation on the [Google Cloud Storage documentation](https://cloud.google.com/storage/docs/generations-preconditions). - */ - generation: string; - - /** - * Storage object metageneration values enable users to uniquely identify data resources, e.g. object versioning. - * - * Read more on metageneration on the [Google Cloud Storage documentation](https://cloud.google.com/storage/docs/generations-preconditions). - */ - metageneration: string; - - /** - * The short name of storage object in its bucket, e.g. it's file name. - * - * #### Example Value - * - * ``` - * logo.png - * ``` - */ - name: string; - - /** - * The size of this storage object in bytes. - */ - size: number; - - /** - * A date string representing when this storage object was created. - * - * #### Example Value - * - * ``` - * 2019-05-02T00:34:56.264Z - * ``` - */ - timeCreated: string; - - /** - * A date string representing when this storage object was last updated. - * - * #### Example Value - * - * ``` - * 2019-05-02T00:35:56.264Z - * ``` - */ - updated: string; - } - - /** - * Represents a reference to a Google Cloud Storage object in React Native Firebase. - * - * A reference can be used to upload and download storage objects, get/set storage object metadata, retrieve storage object download urls and delete storage objects. - * - * #### Example 1 - * - * Get a reference to a specific storage path. - * - * ```js - * const ref = firebase.storage().ref('invertase/logo.png'); - * ``` - * - * #### Example 2 - * - * Get a reference to a specific storage path on another bucket in the same firebase project. - * - * ```js - * const ref = firebase.storage().refFromURL('gs://other-bucket/invertase/logo.png'); - * ``` - */ - export interface Reference { - /** - * The name of the bucket containing this reference's object. - */ - bucket: string; - /** - * A reference pointing to the parent location of this reference, or null if this reference is the root. - */ - parent: Reference | null; - /** - * The full path of this object. - */ - fullPath: string; - /** - * The short name of this object, which is the last component of the full path. For example, - * if fullPath is 'full/path/image.png', name is 'image.png'. - */ - name: string; - /** - * A reference to the root of this reference's bucket. - */ - root: Reference; - /** - * The storage service associated with this reference. - */ - storage: Module; - - /** - * Returns a gs:// URL for this object in the form `gs://///`. - * - * #### Example - * - * ```js - * const ref = firebase.storage().ref('invertase/logo.png'); - * console.log('Full path: ', ref.toString()); // gs://invertase.io/invertase/logo.png - * ``` - */ - toString(): string; - - /** - * Returns a reference to a relative path from this reference. - * - * #### Example - * - * ```js - * const parent = firebase.storage().ref('invertase'); - * const ref = parent.child('logo.png'); - * ``` - * - * @param path The relative path from this reference. Leading, trailing, and consecutive slashes are removed. - */ - child(path: string): Reference; - - /** - * Deletes the object at this reference's location. - * - * #### Example - * - * ```js - * const ref = firebase.storage().ref('invertase/logo.png'); - * await ref.delete(); - * ``` - */ - delete(): Promise; - - /** - * Fetches a long lived download URL for this object. - * - * #### Example - * - * ```js - * const ref = firebase.storage().ref('invertase/logo.png'); - * const url = await ref.getDownloadURL(); - * ``` - */ - getDownloadURL(): Promise; - - /** - * Fetches metadata for the object at this location, if one exists. - * - * #### Example - * - * ```js - * const ref = firebase.storage().ref('invertase/logo.png'); - * const metadata = await ref.getMetadata(); - * console.log('Cache control: ', metadata.cacheControl); - * ``` - */ - getMetadata(): Promise; - - /** - * List items (files) and prefixes (folders) under this storage reference. - * - * List API is only available for Firebase Rules Version 2. - * - * GCS is a key-blob store. Firebase Storage imposes the semantic of '/' delimited folder structure. - * Refer to GCS's List API if you want to learn more. - * - * To adhere to Firebase Rules's Semantics, Firebase Storage does not support objects whose paths - * end with "/" or contain two consecutive "/"s. Firebase Storage List API will filter these unsupported objects. - * list() may fail if there are too many unsupported objects in the bucket. - * - * #### Example - * - * ```js - * const ref = firebase.storage().ref('/'); - * const results = await ref.list({ - * maxResults: 30, - * }); - * ``` - * - * @param options An optional ListOptions interface. - */ - list(options?: ListOptions): Promise; - - /** - * List all items (files) and prefixes (folders) under this storage reference. - * - * This is a helper method for calling list() repeatedly until there are no more results. The default pagination size is 1000. - * - * Note: The results may not be consistent if objects are changed while this operation is running. - * - * Warning: `listAll` may potentially consume too many resources if there are too many results. - * - * #### Example - * - * ```js - * const ref = firebase.storage().ref('/'); - * const results = await ref.listAll(); - * ``` - */ - listAll(): Promise; - - /** - * Puts a file from local disk onto the storage bucket. - * - * #### Example - * - * ```js - * const ref = firebase.storage().ref('invertase/new-logo.png'); - * const path = `${firebase.utils.FilePath.DOCUMENT_DIRECTORY}/new-logo.png`; - * const task = ref.putFile(path, { - * cacheControl: 'no-store', // disable caching - * }); - * ``` - * - * @param localFilePath The local file path to upload to the bucket at the reference location. - * @param metadata Any additional `SettableMetadata` for this task. - */ - putFile(localFilePath: string, metadata?: SettableMetadata): Task; - - /** - * Downloads a file to the specified local file path on the device. - * - * #### Example - * - * Get a Download Storage task to download a file: - * - * ```js - * const downloadTo = `${firebase.utils.FilePath.DOCUMENT_DIRECTORY}/foobar.json`; - * - * const task = firebase.storage().ref('/foo/bar.json').writeToFile(downloadTo); - * ``` - * @param localFilePath - */ - writeToFile(localFilePath: string): Task; - - /** - * Puts data onto the storage bucket. - * - * #### Example - * - * ```js - * const ref = firebase.storage().ref('invertase/new-logo.png'); - * const task = ref.put(BLOB, { - * cacheControl: 'no-store', // disable caching - * }); - * ``` - * - * @param data The data to upload to the storage bucket at the reference location. - * @param metadata - */ - put(data: Blob | Uint8Array | ArrayBuffer, metadata?: SettableMetadata): Task; - - /** - * Puts a string on the storage bucket. Depending on the string type, set a {@link storage.StringFormat} type. - * - * #### Example - * - * ```js - * const ref = firebase.storage().ref('invertase/new-logo.png'); - * const task = ref.putString('PEZvbyBCYXI+', firebase.storage.StringFormat.BASE64, { - * cacheControl: 'no-store', // disable caching - * }); - * ``` - * - * @param data The string data, must match the format provided. - * @param format The format type of the string, e.g. a Base64 format string. - * @param metadata Any additional `SettableMetadata` for this task. - */ - putString( - data: string, - format?: 'raw' | 'base64' | 'base64url' | 'data_url', - metadata?: SettableMetadata, - ): Task; - - /** - * Updates the metadata for this reference object on the storage bucket. - * - * #### Example - * - * ```js - * const ref = firebase.storage().ref('invertase/nsfw-logo.png'); - * const updatedMetadata = await ref.updateMetadata({ - * customMetadata: { - * 'nsfw': 'true', - * } - * }); - * ``` - * - * @param metadata A `SettableMetadata` instance to update. - */ - updateMetadata(metadata: SettableMetadata): Promise; - } - - /** - * The snapshot observer returned from a {@link storage.Task#on} listener. - * - * #### Example - * - * ```js - * const ref = firebase.storage().ref(...); - * const task = ref.put(...) - * - * task.on('state_changed', { - * next(taskSnapshot) { - * console.log(taskSnapshot.state); - * }, - * error(error) { - * console.error(error.message); - * }, - * complete() { - * console.log('Task complete'); - * }, - * }) - * ``` - */ - export interface TaskSnapshotObserver { - /** - * Called when the task state changes. - * - * @param taskSnapshot A `TaskSnapshot` for the event. - */ - next: (taskSnapshot: TaskSnapshot) => void; - - /** - * Called when the task errors. - * - * @param error A JavaScript error. - */ - error: (error: NativeFirebaseError) => void; - - /** - * Called when the task has completed successfully. - */ - complete: () => void; - } - - /** - * Storage Task used for Uploading or Downloading files. - * - * #### Example 1 - * - * Get a Upload Storage task to upload a string: - * - * ```js - * const string = '{ "foo": 1 }'; - * const task = firebase - * .storage() - * .ref('/foo/bar.json') - * .putString(string); - * ``` - * - * #### Example 2 - * - * Get a Download Storage task to download a file: - * - * ```js - * const downloadTo = `${firebase.utils.FilePath.DOCUMENT_DIRECTORY}/bar.json`; - * - * const task = firebase - * .storage() - * .ref('/foo/bar.json') - * .writeToFile(downloadTo); - * ``` - */ - export interface Task { - /** - * Initial state of Task.snapshot is `null`. Once uploading begins, it updates to a `TaskSnapshot` object. - */ - snapshot: null | TaskSnapshot; - - /** - * Pause the current Download or Upload task. - * - * #### Example - * - * Pause a running task inside a state changed listener: - * - * ```js - * task.on('state_changed', taskSnapshot => { - * if (taskSnapshot.state === firebase.storage.TaskState.RUNNING) { - * console.log('Pausing my task!'); - * task.pause(); - * } - * }); - * ``` - * - */ - pause(): Promise; - - /** - * Resume the current Download or Upload task. - * - * #### Example - * - * Resume a previously paused task inside a state changed listener: - * - * ```js - * task.on('state_changed', taskSnapshot => { - * // ... pause me ... - * if (taskSnapshot.state === firebase.storage.TaskState.PAUSED) { - * console.log('Resuming my task!'); - * task.resume(); - * } - * }); - * ``` - * - */ - resume(): Promise; - - /** - * Cancel the current Download or Upload task. - * - * - * #### Example - * - * Cancel a task inside a state changed listener: - * - * ```js - * task.on('state_changed', taskSnapshot => { - * console.log('Cancelling my task!'); - * task.cancel(); - * }); - * ``` - * - */ - cancel(): Promise; - - /** - * Task event handler called when state has changed on the task. - * - * #### Example - * - * ```js - * const task = firebase - * .storage() - * .ref('/foo/bar.json') - * .writeToFile(downloadTo); - * - * task.on('state_changed', (taskSnapshot) => { - * console.log(taskSnapshot.state); - * }); - * - * task.then(() => {] - * console.log('Task complete'); - * }) - * .catch((error) => { - * console.error(error.message); - * }); - * ``` - * - * @param event The event name to handle, always `state_changed`. - * @param nextOrObserver The optional event observer function. - * @param error An optional JavaScript error handler. - * @param complete An optional complete handler function. - */ - on( - event: 'state_changed', - nextOrObserver?: TaskSnapshotObserver | null | ((a: TaskSnapshot) => any), - error?: ((a: NativeFirebaseError) => any) | null, - complete?: (() => void) | null, - ): () => void; - - // /** - // * @ignore May not exist in RN JS Environment yet so we'll hide from docs. - // */ - // finally(onFinally?: (() => void) | undefined | null): Promise; - - then( - onFulfilled?: ((a: TaskSnapshot) => any) | null, - onRejected?: ((a: NativeFirebaseError) => any) | null, - ): Promise; - - catch(onRejected: (a: NativeFirebaseError) => any): Promise; - } - - /** - * A TaskSnapshot provides information about a storage tasks state. - * - * #### Example 1 - * - * ```js - * firebase - * .storage() - * .ref('/foo/bar.json') - * .putString(JSON.stringify({ foo: 'bar' })) - * .then((taskSnapshot) => { - * if (taskSnapshot.state === firebase.storage.TaskState.SUCCESS) { - * console.log('Total bytes uploaded: ', taskSnapshot.totalBytes); - * } - * }); - * ``` - * - * #### Example 2 - * - * ```js - * const task = firebase - * .storage() - * .ref('/foo/bar.json') - * .putString(JSON.stringify({ foo: 'bar' })); - * - * task.on('state_changed', taskSnapshot => { - * if (taskSnapshot.state === firebase.storage.TaskState.PAUSED) { - * console.log('Resuming my task!'); - * task.resume(); - * } - * }); - * ``` - */ - export interface TaskSnapshot { - /** - * The number of bytes currently transferred. - */ - bytesTransferred: number; - - /** - * The metadata of the tasks via a {@link storage.FullMetadata} interface. - */ - metadata: FullMetadata; - - /** - * The {@link storage.Reference} of the task. - */ - ref: Reference; - - /** - * The current state of the task snapshot. - */ - state: 'cancelled' | 'error' | 'paused' | 'running' | 'success'; - - /** - * The parent {@link storage.Task} of this snapshot. - */ - task: Task; - - /** - * The total amount of bytes for this task. - */ - totalBytes: number; - - /** - * If the {@link storage.TaskSnapshot#state} is `error`, returns a JavaScript error of the - * current task snapshot. - */ - error?: NativeFirebaseError; - } - - /** - * Result returned from a non-resumable upload. - */ - export interface TaskResult { - /** - * The metadata of the tasks via a {@link storage.FullMetadata} interface. - */ - metadata: FullMetadata; - - /** - * The {@link storage.Reference} of the task. - */ - ref: Reference; - } - - /** - * The options `list()` accepts. - */ - export interface ListOptions { - /** - * If set, limits the total number of `prefixes` and `items` to return. The default and maximum maxResults is 1000. - */ - maxResults?: number; - - /** - * The `nextPageToken` from a previous call to `list()`. If provided, listing is resumed from the previous position. - */ - pageToken?: string; - } - - /** - * Result returned by `list()`. - */ - export interface ListResult { - /** - * Objects in this directory. You can call `getMetadata()` and `getDownloadUrl()` on them. - */ - items: Reference[]; - - /** - * If set, there might be more results for this list. Use this token to resume the list. - */ - nextPageToken: string | null; - - /** - * References to prefixes (sub-folders). You can call `list()` on them to get its contents. - * - * Folders are implicit based on '/' in the object paths. For example, if a bucket has two objects '/a/b/1' and '/a/b/2', list('/a') will return '/a/b' as a prefix. - */ - prefixes: Reference[]; - } - - /** - * Storage Emulator options. Web only. - */ - export interface EmulatorMockTokenOptions { - /** - * the mock auth token to use for unit testing Security Rules. - */ - mockUserToken?: string; - } - - /** - * The Cloud Storage service is available for the default app, a given app or a specific storage bucket. - * - * #### Example 1 - * - * Get the storage instance for the **default app**: - * - * ```js - * const storageForDefaultApp = firebase.storage(); - * ``` - * - * #### Example 2 - * - * Get the storage instance for a **secondary app**: - * - * ```js - * const otherApp = firebase.app('otherApp'); - * const storageForOtherApp = firebase.storage(otherApp); - * ``` - * - * #### Example 3 - * - * Get the storage instance for a **specific storage bucket**: - * - * ```js - * const defaultApp = firebase.app(); - * const storageForBucket = defaultApp.storage('gs://another-bucket-url'); - * - * const otherApp = firebase.app('otherApp'); - * const storageForOtherAppBucket = otherApp.storage('gs://another-bucket-url'); - * ``` - * - */ - export class Module extends FirebaseModule { - /** - * The current `FirebaseApp` instance for this Firebase service. - */ - app: ReactNativeFirebase.FirebaseApp; - - /** - * Returns the current maximum time in milliseconds to retry an upload if a failure occurs. - * - * #### Example - * - * ```js - * const uploadRetryTime = firebase.storage().maxUploadRetryTime; - * ``` - */ - maxUploadRetryTime: number; - - /** - * Sets the maximum time in milliseconds to retry an upload if a failure occurs. - * - * #### Example - * - * ```js - * await firebase.storage().setMaxUploadRetryTime(25000); - * ``` - * - * @param time The new maximum upload retry time in milliseconds. - */ - setMaxUploadRetryTime(time: number): Promise; - - /** - * Returns the current maximum time in milliseconds to retry a download if a failure occurs. - * - * #### Example - * - * ```js - * const downloadRetryTime = firebase.storage().maxUploadRetryTime; - * ``` - */ - maxDownloadRetryTime: number; - - /** - * Sets the maximum time in milliseconds to retry a download if a failure occurs. - * - * #### Example - * - * ```js - * await firebase.storage().setMaxDownloadRetryTime(25000); - * ``` - * - * @param time The new maximum download retry time in milliseconds. - */ - setMaxDownloadRetryTime(time: number): Promise; - - /** - * Returns the current maximum time in milliseconds to retry operations other than upload and download if a failure occurs. - * - * #### Example - * - * ```js - * const maxOperationRetryTime = firebase.storage().maxOperationRetryTime; - * ``` - */ - maxOperationRetryTime: number; - - /** - * Sets the maximum time in milliseconds to retry operations other than upload and download if a failure occurs. - * - * #### Example - * - * ```js - * await firebase.storage().setMaxOperationRetryTime(5000); - * ``` - * - * @param time The new maximum operation retry time in milliseconds. - */ - setMaxOperationRetryTime(time: number): Promise; - - /** - * Returns a new {@link storage.Reference} instance. - * - * #### Example - * - * ```js - * const ref = firebase.storage().ref('cats.gif'); - * ``` - * - * @param path An optional string pointing to a location on the storage bucket. If no path - * is provided, the returned reference will be the bucket root path. - */ - ref(path?: string): Reference; - - /** - * Returns a new {@link storage.Reference} instance from a storage bucket URL. - * - * #### Example - * - * ```js - * const gsUrl = 'gs://react-native-firebase-testing/cats.gif'; - * const httpUrl = 'https://firebasestorage.googleapis.com/v0/b/react-native-firebase-testing.appspot.com/o/cats.gif'; - * - * const refFromGsUrl = firebase.storage().refFromURL(gsUrl); - * // or - * const refFromHttpUrl = firebase.storage().refFromURL(httpUrl); - * ``` - * - * @param url A storage bucket URL pointing to a single file or location. Must be either a `gs://` url or an `http` url, - * e.g. `gs://assets/logo.png` or `https://firebasestorage.googleapis.com/v0/b/react-native-firebase-testing.appspot.com/o/cats.gif`. - */ - refFromURL(url: string): Reference; - - /** - * Modify this Storage instance to communicate with the Firebase Storage emulator. - * This must be called synchronously immediately following the first call to firebase.storage(). - * Do not use with production credentials as emulator traffic is not encrypted. - * - * Note: on android, hosts 'localhost' and '127.0.0.1' are automatically remapped to '10.0.2.2' (the - * "host" computer IP address for android emulators) to make the standard development experience easy. - * If you want to use the emulator on a real android device, you will need to specify the actual host - * computer IP address. - * - * @param host: emulator host (eg, 'localhost') - * @param port: emulator port (eg, 9199) - */ - useEmulator(host: string, port: number): void; - } -} - -type StorageNamespace = ReactNativeFirebase.FirebaseModuleWithStaticsAndApp< - FirebaseStorageTypes.Module, - FirebaseStorageTypes.Statics -> & { - firebase: ReactNativeFirebase.Module; - app(name?: string): ReactNativeFirebase.FirebaseApp; -}; - -declare const defaultExport: StorageNamespace; - -export const firebase: ReactNativeFirebase.Module & { - storage: typeof defaultExport; - app(name?: string): ReactNativeFirebase.FirebaseApp & { storage(): FirebaseStorageTypes.Module }; -}; - -export default defaultExport; - -/** - * Attach namespace to `firebase.` and `FirebaseApp.`. - */ -declare module '@react-native-firebase/app' { - namespace ReactNativeFirebase { - import FirebaseModuleWithStaticsAndApp = ReactNativeFirebase.FirebaseModuleWithStaticsAndApp; - interface Module { - storage: FirebaseModuleWithStaticsAndApp< - FirebaseStorageTypes.Module, - FirebaseStorageTypes.Statics - >; - } - interface FirebaseApp { - storage(bucket?: string): FirebaseStorageTypes.Module; - } - } -} - -export * from './modular'; diff --git a/packages/storage/lib/index.ts b/packages/storage/lib/index.ts new file mode 100644 index 0000000000..fb6a132255 --- /dev/null +++ b/packages/storage/lib/index.ts @@ -0,0 +1,39 @@ +/* + * 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. + * + */ + +// Export types from types/storage +export type { + Storage, + FirebaseStorageTypes, + Reference, + FullMetadata, + SettableMetadata, + ListResult, + ListOptions, + TaskSnapshot, + TaskSnapshotObserver, + TaskResult, + Task, + EmulatorMockTokenOptions, +} from './types/storage'; + +// Export modular API functions +export * from './modular'; + +// Export namespaced API +export * from './namespaced'; +export { default } from './namespaced'; diff --git a/packages/storage/lib/modular/index.js b/packages/storage/lib/modular.ts similarity index 69% rename from packages/storage/lib/modular/index.js rename to packages/storage/lib/modular.ts index 9e47536201..9b99459f5c 100644 --- a/packages/storage/lib/modular/index.js +++ b/packages/storage/lib/modular.ts @@ -14,22 +14,21 @@ * limitations under the License. * */ -/** - * @typedef {import('..').FirebaseStorageTypes} FirebaseStorageTypes - * @typedef {import('..').FirebaseStorageTypes.Module} Storage - * @typedef {import('..').FirebaseStorageTypes.Reference} Reference - * @typedef {import('..').FirebaseStorageTypes.FullMetadata} FullMetadata - * @typedef {import('..').FirebaseStorageTypes.ListResult} ListResult - * @typedef {import('..').FirebaseStorageTypes.TaskResult} TaskResult - * @typedef {import('..').FirebaseStorageTypes.Task} Task - * @typedef {import('..').FirebaseStorageTypes.ListOptions} ListOptions - * @typedef {import('..').FirebaseStorageTypes.SettableMetadata} SettableMetadata - * @typedef {import('..').FirebaseStorageTypes.EmulatorMockTokenOptions} EmulatorMockTokenOptions - * @typedef {import('@firebase/app').FirebaseApp} FirebaseApp - */ import { getApp } from '@react-native-firebase/app'; import { MODULAR_DEPRECATION_ARG } from '@react-native-firebase/app/dist/module/common'; +import type { FirebaseApp } from '@react-native-firebase/app'; +import type { + Storage, + Reference, + FullMetadata, + ListResult, + ListOptions, + TaskResult, + Task, + SettableMetadata, + EmulatorMockTokenOptions, +} from './types/storage'; /** * Returns a Storage instance for the given app. @@ -37,7 +36,7 @@ import { MODULAR_DEPRECATION_ARG } from '@react-native-firebase/app/dist/module/ * @param bucketUrl - Storage bucket URL. Optional. * @returns {Storage} */ -export function getStorage(app, bucketUrl) { +export function getStorage(app?: FirebaseApp, bucketUrl?: string): Storage { if (app) { if (bucketUrl != null) { return getApp(app.name).storage(bucketUrl); @@ -61,7 +60,13 @@ export function getStorage(app, bucketUrl) { * @param options - `EmulatorMockTokenOptions` instance. Optional. Web only. * @returns {void} */ -export function connectStorageEmulator(storage, host, port, options) { +export function connectStorageEmulator( + storage: Storage, + host: string, + port: number, + options?: EmulatorMockTokenOptions, +): void { + // @ts-ignore - MODULAR_DEPRECATION_ARG is filtered out internally return storage.useEmulator.call(storage, host, port, options, MODULAR_DEPRECATION_ARG); } @@ -72,7 +77,8 @@ export function connectStorageEmulator(storage, host, port, options) { * is provided, the returned reference will be the bucket root path. Optional. * @returns {Reference} */ -export function ref(storage, path) { +export function ref(storage: Storage, path?: string): Reference { + // @ts-ignore - MODULAR_DEPRECATION_ARG is filtered out internally return storage.ref.call(storage, path, MODULAR_DEPRECATION_ARG); } @@ -81,7 +87,8 @@ export function ref(storage, path) { * @param storageRef - Storage `Reference` instance. * @returns {Promise} */ -export function deleteObject(storageRef) { +export function deleteObject(storageRef: Reference): Promise { + // @ts-ignore - MODULAR_DEPRECATION_ARG is filtered out internally return storageRef.delete.call(storageRef, MODULAR_DEPRECATION_ARG); } @@ -90,8 +97,7 @@ export function deleteObject(storageRef) { * @param storageRef - Storage `Reference` instance. * @returns {Promise} */ -// eslint-disable-next-line -export function getBlob(storageRef, maxDownloadSizeBytes) { +export function getBlob(_storageRef: Reference, _maxDownloadSizeBytes?: number): Promise { throw new Error('`getBlob()` is not implemented'); } @@ -101,8 +107,10 @@ export function getBlob(storageRef, maxDownloadSizeBytes) { * @param maxDownloadSizeBytes - The maximum allowed size in bytes to retrieve. Web only. * @returns {Promise} */ -// eslint-disable-next-line -export function getBytes(storageRef, maxDownloadSizeBytes) { +export function getBytes( + _storageRef: Reference, + _maxDownloadSizeBytes?: number, +): Promise { throw new Error('`getBytes()` is not implemented'); } @@ -111,7 +119,8 @@ export function getBytes(storageRef, maxDownloadSizeBytes) { * @param storageRef - Storage `Reference` instance. * @returns {Promise} */ -export function getDownloadURL(storageRef) { +export function getDownloadURL(storageRef: Reference): Promise { + // @ts-ignore - MODULAR_DEPRECATION_ARG is filtered out internally return storageRef.getDownloadURL.call(storageRef, MODULAR_DEPRECATION_ARG); } @@ -120,7 +129,8 @@ export function getDownloadURL(storageRef) { * @param storageRef - Storage `Reference` instance. * @returns {Promise} */ -export function getMetadata(storageRef) { +export function getMetadata(storageRef: Reference): Promise { + // @ts-ignore - MODULAR_DEPRECATION_ARG is filtered out internally return storageRef.getMetadata.call(storageRef, MODULAR_DEPRECATION_ARG); } @@ -130,8 +140,10 @@ export function getMetadata(storageRef) { * @param maxDownloadSizeBytes - The maximum allowed size in bytes to retrieve. Web only. * @returns {NodeJS.ReadableStream;} */ -// eslint-disable-next-line -export function getStream(storageRef, maxDownloadSizeBytes) { +export function getStream( + _storageRef: Reference, + _maxDownloadSizeBytes?: number, +): NodeJS.ReadableStream { throw new Error('`getStream()` is not implemented'); } @@ -141,7 +153,8 @@ export function getStream(storageRef, maxDownloadSizeBytes) { * @param options - Storage `ListOptions` instance. The options list() accepts. * @returns {Promise} */ -export function list(storageRef, options) { +export function list(storageRef: Reference, options?: ListOptions): Promise { + // @ts-ignore - MODULAR_DEPRECATION_ARG is filtered out internally return storageRef.list.call(storageRef, options, MODULAR_DEPRECATION_ARG); } @@ -150,7 +163,8 @@ export function list(storageRef, options) { * @param storageRef - Storage `Reference` instance. * @returns {Promise} */ -export function listAll(storageRef) { +export function listAll(storageRef: Reference): Promise { + // @ts-ignore - MODULAR_DEPRECATION_ARG is filtered out internally return storageRef.listAll.call(storageRef, MODULAR_DEPRECATION_ARG); } @@ -160,7 +174,11 @@ export function listAll(storageRef) { * @param metadata - A Storage `SettableMetadata` instance to update. * @returns {Promise} */ -export function updateMetadata(storageRef, metadata) { +export function updateMetadata( + storageRef: Reference, + metadata: SettableMetadata, +): Promise { + // @ts-ignore - MODULAR_DEPRECATION_ARG is filtered out internally return storageRef.updateMetadata.call(storageRef, metadata, MODULAR_DEPRECATION_ARG); } @@ -171,8 +189,11 @@ export function updateMetadata(storageRef, metadata) { * @param metadata - A Storage `SettableMetadata` instance to update. Optional. * @returns {Promise} */ -// eslint-disable-next-line -export async function uploadBytes(storageRef, data, metadata) { +export async function uploadBytes( + _storageRef: Reference, + _data: Blob | Uint8Array | ArrayBuffer, + _metadata?: SettableMetadata, +): Promise { throw new Error('`uploadBytes()` is not implemented'); } @@ -183,7 +204,12 @@ export async function uploadBytes(storageRef, data, metadata) { * @param metadata - A Storage `SettableMetadata` instance to update. Optional. * @returns {Task} */ -export function uploadBytesResumable(storageRef, data, metadata) { +export function uploadBytesResumable( + storageRef: Reference, + data: Blob | Uint8Array | ArrayBuffer, + metadata?: SettableMetadata, +): Task { + // @ts-ignore - MODULAR_DEPRECATION_ARG is filtered out internally return storageRef.put.call(storageRef, data, metadata, MODULAR_DEPRECATION_ARG); } @@ -195,7 +221,13 @@ export function uploadBytesResumable(storageRef, data, metadata) { * @param metadata - A Storage `SettableMetadata` instance to update. Optional. * @returns {Task} */ -export function uploadString(storageRef, data, format, metadata) { +export function uploadString( + storageRef: Reference, + data: string, + format?: string, + metadata?: SettableMetadata, +): Task { + // @ts-ignore - MODULAR_DEPRECATION_ARG is filtered out internally return storageRef.putString.call(storageRef, data, format, metadata, MODULAR_DEPRECATION_ARG); } @@ -207,7 +239,8 @@ export function uploadString(storageRef, data, format, metadata) { * @param url - A storage bucket URL pointing to a single file or location. Must be either a `gs://` url or an `http` url. Not available on web. * @returns {Reference} */ -export function refFromURL(storage, url) { +export function refFromURL(storage: Storage, url: string): Reference { + // @ts-ignore - MODULAR_DEPRECATION_ARG is filtered out internally return storage.refFromURL.call(storage, url, MODULAR_DEPRECATION_ARG); } @@ -217,7 +250,8 @@ export function refFromURL(storage, url) { * @param time - The new maximum operation retry time in milliseconds. * @returns {Promise} */ -export function setMaxOperationRetryTime(storage, time) { +export function setMaxOperationRetryTime(storage: Storage, time: number): Promise { + // @ts-ignore - MODULAR_DEPRECATION_ARG is filtered out internally return storage.setMaxOperationRetryTime.call(storage, time, MODULAR_DEPRECATION_ARG); } @@ -227,7 +261,8 @@ export function setMaxOperationRetryTime(storage, time) { * @param time - The new maximum operation retry time in milliseconds. * @returns {Promise} */ -export function setMaxUploadRetryTime(storage, time) { +export function setMaxUploadRetryTime(storage: Storage, time: number): Promise { + // @ts-ignore - MODULAR_DEPRECATION_ARG is filtered out internally return storage.setMaxUploadRetryTime.call(storage, time, MODULAR_DEPRECATION_ARG); } @@ -238,7 +273,12 @@ export function setMaxUploadRetryTime(storage, time) { * @param metadata Any additional `SettableMetadata` for this task. * @returns {Task} */ -export function putFile(storageRef, filePath, metadata) { +export function putFile( + storageRef: Reference, + filePath: string, + metadata?: SettableMetadata, +): Task { + // @ts-ignore - MODULAR_DEPRECATION_ARG is filtered out internally return storageRef.putFile.call(storageRef, filePath, metadata, MODULAR_DEPRECATION_ARG); } @@ -248,7 +288,8 @@ export function putFile(storageRef, filePath, metadata) { * @param localFilePath The local file path to upload to on the device. * @returns {Task} */ -export function writeToFile(storageRef, filePath) { +export function writeToFile(storageRef: Reference, filePath: string): Task { + // @ts-ignore - MODULAR_DEPRECATION_ARG is filtered out internally return storageRef.writeToFile.call(storageRef, filePath, MODULAR_DEPRECATION_ARG); } @@ -257,7 +298,8 @@ export function writeToFile(storageRef, filePath) { * @param storageRef - Storage Reference instance. * @returns {String} */ -export function toString(storageRef) { +export function toString(storageRef: Reference): string { + // @ts-ignore - MODULAR_DEPRECATION_ARG is filtered out internally return storageRef.toString.call(storageRef, MODULAR_DEPRECATION_ARG); } @@ -267,7 +309,8 @@ export function toString(storageRef) { * @param path - The relative path from this reference. Leading, trailing, and consecutive slashes are removed. * @returns {String} */ -export function child(storageRef, path) { +export function child(storageRef: Reference, path: string): Reference { + // @ts-ignore - MODULAR_DEPRECATION_ARG is filtered out internally return storageRef.child.call(storageRef, path, MODULAR_DEPRECATION_ARG); } @@ -277,8 +320,9 @@ export function child(storageRef, path) { * @param time - The new maximum download retry time in milliseconds. * @returns {Promise} */ -export function setMaxDownloadRetryTime(storage, time) { +export function setMaxDownloadRetryTime(storage: Storage, time: number): Promise { + // @ts-ignore - MODULAR_DEPRECATION_ARG is filtered out internally return storage.setMaxDownloadRetryTime.call(storage, time, MODULAR_DEPRECATION_ARG); } -export { StringFormat, TaskEvent, TaskState } from '../StorageStatics'; +export { StringFormat, TaskEvent, TaskState } from './StorageStatics'; diff --git a/packages/storage/lib/modular/index.d.ts b/packages/storage/lib/modular/index.d.ts deleted file mode 100644 index 0d3f41cb27..0000000000 --- a/packages/storage/lib/modular/index.d.ts +++ /dev/null @@ -1,276 +0,0 @@ -/* - * 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 { ReactNativeFirebase } from '@react-native-firebase/app'; -import { FirebaseStorageTypes } from '../index'; - -import Storage = FirebaseStorageTypes.Module; -import Reference = FirebaseStorageTypes.Reference; -import FullMetadata = FirebaseStorageTypes.FullMetadata; -import ListResult = FirebaseStorageTypes.ListResult; -import TaskResult = FirebaseStorageTypes.TaskResult; -import Task = FirebaseStorageTypes.Task; -import ListOptions = FirebaseStorageTypes.ListOptions; -import SettableMetadata = FirebaseStorageTypes.SettableMetadata; -import EmulatorMockTokenOptions = FirebaseStorageTypes.EmulatorMockTokenOptions; -import FirebaseApp = ReactNativeFirebase.FirebaseApp; - -export const StringFormat: FirebaseStorageTypes.StringFormat; -export const TaskEvent: FirebaseStorageTypes.TaskEvent; -export const TaskState: FirebaseStorageTypes.TaskState; - -/** - * Union of literal string values in StringFormat "enum" object - */ -export type StringFormat = (typeof StringFormat)[keyof typeof StringFormat]; - -/** - * Returns the existing default {@link Storage} instance that is associated with the - * default {@link FirebaseApp}. The default storage bucket is used. If no instance exists, initializes a new - * instance with default settings. - * @param app - Firebase app to get FirebaseStorage instance for. - * @param bucketUrl - The gs:// url to your Firebase Storage Bucket. If not passed, uses the app's default Storage Bucket. - * @returns The {@link Storage} instance of the provided app. - */ -export declare function getStorage(app?: FirebaseApp, bucketUrl?: string): Storage; - -/** - * Returns the existing default {@link Storage} instance that is associated with the - * provided {@link FirebaseApp}. The default storage bucket is used. If no instance exists, initializes a new - * instance with default settings. - * - * @param app - The {@link FirebaseApp} instance that the returned {@link Storage} - * instance is associated with. - * @returns The {@link Firestore} instance of the provided app. - */ -export declare function getStorage(app?: FirebaseApp): Storage; - -/** - * Returns the existing default {@link Storage} instance that is associated with the - * provided {@link FirebaseApp}. If no instance exists, initializes a new - * instance with default settings. - * - * @param app - The {@link FirebaseApp} instance that the returned {@link Storage} - * instance is associated with. If `null` the default app is used. - * @param bucketUrl - The gs:// url to the Firebase Storage Bucket. If `null` the default bucket is used. - * @returns The {@link Firestore} instance of the provided app. - */ -export declare function getStorage(app?: FirebaseApp, bucketUrl?: string): Storage; - -export function getStorage(app?: FirebaseApp, bucketUrl?: string): Storage; - -/** - * Connects a {@link Storage} instance to the Firebase Storage emulator. - * @param storage - A reference to the `Storage` instance. - * @param host - Emulator host, e.g., 'localhost'. - * @param port - Emulator port, e.g., 9199. - * @param options - Optional. {@link EmulatorMockTokenOptions} instance. - */ -export function connectStorageEmulator( - storage: Storage, - host: string, - port: number, - options?: EmulatorMockTokenOptions, -): void; - -/** - * Creates a {@link Reference} from a given path or URL. - * @param storage - The {@link Storage} instance. - * @param path - Optional. A string pointing to a location within the storage bucket. - * @returns A new {@link Reference}. - */ -export function ref(storage: Storage, path?: string): Reference; - -/** - * Deletes the object at the given reference's location. - * @param storageRef - The {@link Reference} to the object to delete. - * @returns A promise that resolves when the delete is complete. - */ -export function deleteObject(storageRef: Reference): Promise; - -/** - * Retrieves the blob at the given reference's location. Throws an error if the object is not found. - * @param storageRef - The {@link Reference} to the object. - * @param maxDownloadSizeBytes - Optional. Maximum size in bytes to retrieve. - * @returns A promise resolving to the Blob. - */ -export function getBlob(storageRef: Reference, maxDownloadSizeBytes?: number): Promise; - -/** - * Retrieves bytes (up to the specified max size) from an object at the given reference's location. - * Throws an error if the object is not found or if the size exceeds the maximum allowed. - * @param storageRef - The {@link Reference} to the object. - * @param maxDownloadSizeBytes - Optional. Maximum size in bytes to retrieve. - * @returns A promise resolving to an ArrayBuffer. - */ -export function getBytes( - storageRef: Reference, - maxDownloadSizeBytes?: number, -): Promise; - -/** - * Retrieves a long-lived download URL for the object at the given reference's location. - * @param storageRef - The {@link Reference} to the object. - * @returns A promise resolving to the URL string. - */ -export function getDownloadURL(storageRef: Reference): Promise; - -/** - * Retrieves metadata for the object at the given reference's location. - * @param storageRef - The {@link Reference} to the object. - * @returns A promise resolving to the object's {@link FullMetadata}. - */ -export function getMetadata(storageRef: Reference): Promise; - -/** - * Retrieves a readable stream for the object at the given reference's location. This API is only available in Node.js. - * @param storageRef - The {@link Reference} to the object. - * @param maxDownloadSizeBytes - Optional. Maximum size in bytes to retrieve. - * @returns A NodeJS ReadableStream. - */ -export function getStream( - storageRef: Reference, - maxDownloadSizeBytes?: number, -): NodeJS.ReadableStream; - -/** - * Lists items and prefixes under the given reference. - * @param storageRef - The {@link Reference} under which to list items. - * @param options - Optional. Configuration for listing. - * @returns A promise resolving to a {@link ListResult}. - */ -export function list(storageRef: Reference, options?: ListOptions): Promise; - -/** - * Lists all items and prefixes under the given reference. - * @param storageRef - The {@link Reference} under which to list items. - * @returns A promise resolving to a {@link ListResult}. - */ -export function listAll(storageRef: Reference): Promise; - -/** - * Updates metadata for the object at the given reference. - * @param storageRef - The {@link Reference} to the object. - * @param metadata - The metadata to update. - * @returns A promise resolving to the updated {@link FullMetadata}. - */ -export function updateMetadata( - storageRef: Reference, - metadata: SettableMetadata, -): Promise; - -/** - * Uploads data to the object's location at the given reference. The upload is not resumable. - * @param storageRef - The {@link Reference} where the data should be uploaded. - * @param data - The data to upload. - * @param metadata - Optional. Metadata to associate with the uploaded object. - * @returns A promise resolving to a {@link TaskResult}. - */ -export function uploadBytes( - storageRef: Reference, - data: Blob | Uint8Array | ArrayBuffer, - metadata?: SettableMetadata, -): Promise; - -/** - * Initiates a resumable upload session for the data to the object's location at the given reference. - * @param storageRef - The {@link Reference} where the data should be uploaded. - * @param data - The data to upload. - * @param metadata - Optional. Metadata to associate with the uploaded object. - * @returns A {@link Task} associated with the upload process. - */ -export function uploadBytesResumable( - storageRef: Reference, - data: Blob | Uint8Array | ArrayBuffer, - metadata?: SettableMetadata, -): Task; - -/** - * Uploads a string to the object's location at the given reference. The string format must be specified. - * @param storageRef - The {@link Reference} where the string should be uploaded. - * @param data - The string data to upload. - * @param format - Optional. The format of the string ('raw', 'base64', 'base64url', 'data_url'). - * @param metadata - Optional. Metadata to associate with the uploaded object. - * @returns A {@link Task} associated with the upload process. - */ -export function uploadString( - storageRef: Reference, - data: string, - format?: StringFormat, - metadata?: SettableMetadata, -): Task; - -/** - * Creates a {@link Reference} from a storage bucket URL. - * @param storage - The {@link Storage} instance. - * @param url - A URL pointing to a file or location in a storage bucket. - * @returns A {@link Reference} pointing to the specified URL. - */ -export function refFromURL(storage: Storage, url: string): Reference; - -/** - * Sets the maximum time in milliseconds to retry operations other than upload and download if a failure occurs. - * @param storage - The {@link Storage} instance. - * @param time - The new maximum operation retry time in milliseconds. - */ -export function setMaxOperationRetryTime(storage: Storage, time: number): Promise; - -/** - * Sets the maximum time in milliseconds to retry upload operations if a failure occurs. - * @param storage - The {@link Storage} instance. - * @param time - The new maximum upload retry time in milliseconds. - */ -export function setMaxUploadRetryTime(storage: Storage, time: number): Promise; - -/** - * Puts a file from a local disk onto the storage bucket at the given reference. - * @param storageRef - The {@link Reference} where the file should be uploaded. - * @param filePath - The local file path of the file to upload. - * @param metadata - Optional. Metadata to associate with the uploaded file. - * @returns A {@link Task} associated with the upload process. - */ -export function putFile(storageRef: Reference, filePath: string, metadata?: SettableMetadata): Task; - -/** - * Downloads a file to the specified local file path on the device. - * @param storageRef - The {@link Reference} from which the file should be downloaded. - * @param filePath - The local file path where the file should be written. - * @returns A {@link Task} associated with the download process. - */ -export function writeToFile(storageRef: Reference, filePath: string): Task; - -/** - * Returns a gs:// URL for the object at the given reference. - * @param storageRef - The {@link Reference} to the object. - * @returns The URL as a string. - */ -export function toString(storageRef: Reference): string; - -/** - * Returns a reference to a relative path from the given reference. - * @param storageRef - The {@link Reference} as the base. - * @param path - The relative path from the base reference. - * @returns A new {@link Reference}. - */ -export function child(storageRef: Reference, path: string): Reference; - -/** - * Sets the maximum time in milliseconds to retry download operations if a failure occurs. - * @param storage - The {@link Storage} instance. - * @param time - The new maximum download retry time in milliseconds. - */ -export function setMaxDownloadRetryTime(storage: Storage, time: number): Promise; diff --git a/packages/storage/lib/index.js b/packages/storage/lib/namespaced.ts similarity index 76% rename from packages/storage/lib/index.js rename to packages/storage/lib/namespaced.ts index a43c61bed9..5135f3382f 100644 --- a/packages/storage/lib/index.js +++ b/packages/storage/lib/namespaced.ts @@ -27,15 +27,18 @@ import { createModuleNamespace, FirebaseModule, getFirebaseRoot, + type ModuleConfig, } from '@react-native-firebase/app/dist/module/internal'; +import type { ReactNativeFirebase } from '@react-native-firebase/app'; import StorageReference from './StorageReference'; import { StringFormat, TaskEvent, TaskState } from './StorageStatics'; import { getGsUrlParts, getHttpUrlParts, handleStorageEvent } from './utils'; -import version from './version'; +import { version } from './version'; import fallBackModule from './web/RNFBStorageModule'; +import type { Storage, StorageStatics, Reference, EmulatorMockTokenOptions } from './types/storage'; +import type { StorageInternal } from './types/internal'; -// import { STATICS } from '@react-native-firebase/storage'; -const statics = { +const statics: StorageStatics = { StringFormat, TaskEvent, TaskState, @@ -46,7 +49,17 @@ const nativeEvents = ['storage_event']; const nativeModuleName = 'RNFBStorageModule'; class FirebaseStorageModule extends FirebaseModule { - constructor(app, config, bucketUrl) { + emulatorHost: string | undefined; + emulatorPort: number; + _maxUploadRetryTime: number; + _maxDownloadRetryTime: number; + _maxOperationRetryTime: number; + + constructor( + app: ReactNativeFirebase.FirebaseAppBase, + config: ModuleConfig, + bucketUrl?: string | null, + ) { super(app, config, bucketUrl); if (bucketUrl === undefined) { this._customUrlOrRegion = `gs://${app.options.storageBucket}`; @@ -57,7 +70,7 @@ class FirebaseStorageModule extends FirebaseModule { } this.emitter.addListener( - this.eventNameForApp(nativeEvents[0]), + this.eventNameForApp(nativeEvents[0]!), handleStorageEvent.bind(null, this), ); @@ -72,46 +85,46 @@ class FirebaseStorageModule extends FirebaseModule { /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setmaxuploadretrytime */ - get maxUploadRetryTime() { + get maxUploadRetryTime(): number { return this._maxUploadRetryTime; } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setmaxdownloadretrytime */ - get maxDownloadRetryTime() { + get maxDownloadRetryTime(): number { return this._maxDownloadRetryTime; } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#maxoperationretrytime */ - get maxOperationRetryTime() { + get maxOperationRetryTime(): number { return this._maxOperationRetryTime; } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#ref */ - ref(path = '/') { + ref(path: string = '/'): Reference { if (!isString(path)) { throw new Error("firebase.storage().ref(*) 'path' must be a string value."); } - return createDeprecationProxy(new StorageReference(this, path)); + return createDeprecationProxy(new StorageReference(this, path)) as unknown as Reference; } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#refFromURL */ - refFromURL(url) { + refFromURL(url: string): Reference { if (!isString(url) || (!url.startsWith('gs://') && !url.startsWith('http'))) { throw new Error( "firebase.storage().refFromURL(*) 'url' must be a string value and begin with 'gs://' or 'https://'.", ); } - let path; - let bucket; + let path: string; + let bucket: string; if (url.startsWith('http')) { const parts = getHttpUrlParts(url); @@ -126,13 +139,13 @@ class FirebaseStorageModule extends FirebaseModule { } const storageInstance = this.app.storage(bucket); - return new StorageReference(storageInstance, path); + return new StorageReference(storageInstance as StorageInternal, path); } /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setMaxOperationRetryTime */ - setMaxOperationRetryTime(time) { + setMaxOperationRetryTime(time: number): Promise { if (!isNumber(time)) { throw new Error( "firebase.storage().setMaxOperationRetryTime(*) 'time' must be a number value.", @@ -146,7 +159,7 @@ class FirebaseStorageModule extends FirebaseModule { /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setMaxUploadRetryTime */ - setMaxUploadRetryTime(time) { + setMaxUploadRetryTime(time: number): Promise { if (!isNumber(time)) { throw new Error("firebase.storage().setMaxUploadRetryTime(*) 'time' must be a number value."); } @@ -158,7 +171,7 @@ class FirebaseStorageModule extends FirebaseModule { /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setMaxDownloadRetryTime */ - setMaxDownloadRetryTime(time) { + setMaxDownloadRetryTime(time: number): Promise { if (!isNumber(time)) { throw new Error( "firebase.storage().setMaxDownloadRetryTime(*) 'time' must be a number value.", @@ -169,7 +182,7 @@ class FirebaseStorageModule extends FirebaseModule { return this.native.setMaxDownloadRetryTime(time); } - useEmulator(host, port) { + useEmulator(host: string, port: number, _options?: EmulatorMockTokenOptions): void { if (!host || !isString(host) || !port || !isNumber(port)) { throw new Error('firebase.storage().useEmulator() takes a non-empty host and port'); } @@ -191,16 +204,15 @@ class FirebaseStorageModule extends FirebaseModule { this.emulatorHost = host; this.emulatorPort = port; this.native.useEmulator(_host, port, this._customUrlOrRegion); - return [_host, port]; // undocumented return, just used to unit test android host remapping + // @ts-ignore undocumented return, just used to unit test android host remapping + return [_host, port]; } } // import { SDK_VERSION } from '@react-native-firebase/storage'; export const SDK_VERSION = version; -// import storage from '@react-native-firebase/storage'; -// storage().X(...); -export default createModuleNamespace({ +const storageNamespace = createModuleNamespace({ statics, version, namespace, @@ -212,11 +224,28 @@ export default createModuleNamespace({ ModuleClass: FirebaseStorageModule, }); +type StorageNamespace = ReactNativeFirebase.FirebaseModuleWithStaticsAndApp< + Storage, + StorageStatics +> & { + storage: ReactNativeFirebase.FirebaseModuleWithStaticsAndApp; + firebase: ReactNativeFirebase.Module; + app(name?: string): ReactNativeFirebase.FirebaseApp; +}; + +// import storage from '@react-native-firebase/storage'; +// storage().X(...); +export default storageNamespace as unknown as StorageNamespace; + // import storage, { firebase } from '@react-native-firebase/storage'; // storage().X(...); // firebase.storage().X(...); -export const firebase = getFirebaseRoot(); - -export * from './modular'; +export const firebase = + getFirebaseRoot() as unknown as ReactNativeFirebase.FirebaseNamespacedExport< + 'storage', + Storage, + StorageStatics, + true + >; setReactNativeModule(nativeModuleName, fallBackModule); diff --git a/packages/storage/lib/types/internal.ts b/packages/storage/lib/types/internal.ts new file mode 100644 index 0000000000..c47f87531c --- /dev/null +++ b/packages/storage/lib/types/internal.ts @@ -0,0 +1,50 @@ +/* + * 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 type { ModuleConfig } from '@react-native-firebase/app/dist/module/types/internal'; +import type { Storage, Reference } from './storage'; +import type EventEmitter from 'react-native/Libraries/vendor/emitter/EventEmitter'; + +/** + * Internal Storage type with access to private properties. + * Used internally by StorageReference and other internal classes. + */ +export type StorageInternal = Storage & { + native: any; + _customUrlOrRegion: string | null; + emitter: EventEmitter; + eventNameForApp: (...args: Array) => string; + _config: ModuleConfig; +}; + +/** + * Internal Reference type with access to private properties. + * Used internally by StorageTask and other internal classes. + */ +export type ReferenceInternal = Reference & { + _storage: StorageInternal; +}; + +/** + * Internal ListResult type with access to private properties. + * Used internally by StorageListResult and other internal classes. + */ +export type ListResultInternal = { + nextPageToken?: string | null; + items: string[]; + prefixes: string[]; +}; diff --git a/packages/storage/lib/types/storage.ts b/packages/storage/lib/types/storage.ts new file mode 100644 index 0000000000..3eed14eae1 --- /dev/null +++ b/packages/storage/lib/types/storage.ts @@ -0,0 +1,375 @@ +/* + * 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 type { ReactNativeFirebase } from '@react-native-firebase/app'; + +// ============ Options & Result Types ============ + +/** + * Metadata that can be set when uploading or updating a file. + */ +export interface SettableMetadata { + cacheControl?: string | null; + contentDisposition?: string | null; + contentEncoding?: string | null; + contentLanguage?: string | null; + contentType?: string | null; + customMetadata?: { [key: string]: string } | null; + md5hash?: string | null; +} + +/** + * Full metadata for a file, including read-only properties. + */ +export interface FullMetadata extends SettableMetadata { + bucket: string; + generation: string; + metageneration: string; + fullPath: string; + name: string; + size: number; + timeCreated: string; + updated: string; + md5Hash: string | null; + metadata?: { [key: string]: string }; +} + +/** + * Options for listing files and folders. + */ +export interface ListOptions { + maxResults?: number; + pageToken?: string; +} + +/** + * Result of listing files and folders. + */ +export interface ListResult { + items: Reference[]; + prefixes: Reference[]; + nextPageToken: string | null; +} + +/** + * Snapshot of a storage task (upload or download). + */ +export interface TaskSnapshot { + bytesTransferred: number; + totalBytes: number; + state: string; + metadata: FullMetadata | null; + task: Task; + ref: Reference; + /** + * If the state is `error`, returns a JavaScript error of the current task snapshot. + */ + error?: Error; +} + +/** + * Result of a completed task. + */ +export interface TaskResult { + bytesTransferred: number; + totalBytes: number; + state: string; + metadata: FullMetadata | null; +} + +/** + * Storage reference to a file or folder location. + */ +export interface Reference { + bucket: string; + fullPath: string; + name: string; + parent: Reference | null; + root: Reference; + storage: Storage; + + child(path: string): Reference; + delete(): Promise; + getDownloadURL(): Promise; + getMetadata(): Promise; + list(options?: ListOptions): Promise; + listAll(): Promise; + put(data: Blob | Uint8Array | ArrayBuffer, metadata?: SettableMetadata): Task; + putString( + string: string, + format?: 'raw' | 'base64' | 'base64url' | 'data_url', + metadata?: SettableMetadata, + ): Task; + toString(): string; + updateMetadata(metadata: SettableMetadata): Promise; + writeToFile(filePath: string): Task; + putFile(filePath: string, metadata?: SettableMetadata): Task; +} + +/** + * Observer object for task state changes. + */ +export interface TaskSnapshotObserver { + /** + * Called when the task state changes. + */ + next?: (snapshot: TaskSnapshot) => void; + + /** + * Called when the task errors. + */ + error?: (error: Error) => void; + + /** + * Called when the task has completed successfully. + */ + complete?: () => void; +} + +/** + * Storage task for uploads or downloads. + */ +export interface Task { + /** + * Initial state of Task.snapshot is `null`. Once uploading begins, it updates to a `TaskSnapshot` object. + */ + snapshot: TaskSnapshot | null; + + /** + * Pause the current Download or Upload task. + */ + pause(): Promise; + + /** + * Resume the current Download or Upload task. + */ + resume(): Promise; + + /** + * Cancel the current Download or Upload task. + */ + cancel(): Promise; + + /** + * Subscribe to task state changes. + * + * @param event The event name to handle, always `state_changed`. + * @param nextOrObserver The optional event observer function or object. + * @param error An optional JavaScript error handler. + * @param complete An optional complete handler function. + */ + on( + event: 'state_changed', + nextOrObserver?: TaskSnapshotObserver | null | ((snapshot: TaskSnapshot) => void), + error?: ((error: Error) => void) | null, + complete?: (() => void) | null, + ): () => void; + + /** + * Attaches callbacks for the resolution and/or rejection of the Task. + * + * @param onFulfilled Optional callback for when the task completes successfully. + * @param onRejected Optional callback for when the task fails. + */ + then( + onFulfilled?: ((snapshot: TaskSnapshot) => any) | null, + onRejected?: ((error: Error) => any) | null, + ): Promise; + + /** + * Attaches a callback for only the rejection of the Task. + * + * @param onRejected Callback for when the task fails. + */ + catch(onRejected: (error: Error) => any): Promise; +} + +/** + * Options for connecting to the storage emulator (web only). + */ +export interface EmulatorMockTokenOptions { + mockUserToken?: string | null; +} + +// ============ Module Interface ============ + +/** + * Storage module instance - returned from firebase.storage() or firebase.app().storage() + */ +export interface Storage extends ReactNativeFirebase.FirebaseModule { + /** The FirebaseApp this module is associated with */ + app: ReactNativeFirebase.FirebaseApp; + + /** + * The maximum time in milliseconds to retry an upload if a failure occurs. + */ + readonly maxUploadRetryTime: number; + + /** + * The maximum time in milliseconds to retry a download if a failure occurs. + */ + readonly maxDownloadRetryTime: number; + + /** + * The maximum time in milliseconds to retry operations if a failure occurs. + */ + readonly maxOperationRetryTime: number; + + /** + * Returns a reference to the object at the specified path. + * + * @param path - An optional string pointing to a location on the storage bucket. If no path + * is provided, the returned reference will be the bucket root path. + */ + ref(path?: string): Reference; + + /** + * Returns a reference to the object at the specified URL. + * + * @param url - A storage bucket URL pointing to a single file or location. Must be either a `gs://` url or an `http` url. + */ + refFromURL(url: string): Reference; + + /** + * Sets the maximum time in milliseconds to retry operations if a failure occurs. + * + * @param time - The new maximum operation retry time in milliseconds. + */ + setMaxOperationRetryTime(time: number): Promise; + + /** + * Sets the maximum time in milliseconds to retry an upload if a failure occurs. + * + * @param time - The new maximum upload retry time in milliseconds. + */ + setMaxUploadRetryTime(time: number): Promise; + + /** + * Sets the maximum time in milliseconds to retry a download if a failure occurs. + * + * @param time - The new maximum download retry time in milliseconds. + */ + setMaxDownloadRetryTime(time: number): Promise; + + /** + * Changes this instance to point to a Cloud Storage emulator running locally. + * + * @param host - The host of the emulator, e.g. "localhost" or "10.0.2.2" for Android. + * @param port - The port of the emulator, e.g. 9199. + * @param options - Optional settings for the emulator connection (web only). + * @returns {void} - Undocumented return, just used to unit test android host remapping. + */ + useEmulator(host: string, port: number, options?: EmulatorMockTokenOptions): void; +} + +// ============ Statics Interface ============ + +/** + * String formats for uploading data. + */ +export interface StringFormat { + RAW: 'raw'; + BASE64: 'base64'; + BASE64URL: 'base64url'; + DATA_URL: 'data_url'; +} + +/** + * Task events. + */ +export interface TaskEvent { + STATE_CHANGED: 'state_changed'; +} + +/** + * Task states. + */ +export interface TaskState { + RUNNING: 'running'; + PAUSED: 'paused'; + SUCCESS: 'success'; + CANCELLED: 'cancelled'; + ERROR: 'error'; +} + +/** + * Static properties available on firebase.storage + */ +export interface StorageStatics { + StringFormat: StringFormat; + TaskEvent: TaskEvent; + TaskState: TaskState; +} + +/** + * FirebaseApp type with storage() method. + * @deprecated Import FirebaseApp from '@react-native-firebase/app' instead. + * The storage() method is added via module augmentation. + */ +export type FirebaseApp = ReactNativeFirebase.FirebaseApp; + +// ============ Module Augmentation ============ + +/* eslint-disable @typescript-eslint/no-namespace */ +declare module '@react-native-firebase/app' { + namespace ReactNativeFirebase { + interface Module { + storage: FirebaseModuleWithStaticsAndApp; + } + interface FirebaseApp { + storage(bucketUrl?: string): Storage; + } + } +} +/* eslint-enable @typescript-eslint/no-namespace */ + +// ============ Backwards Compatibility Namespace - to be removed with namespaced exports ============ + +// Helper types to reference outer scope types within the namespace +// These are needed because TypeScript can't directly alias types with the same name +type _Storage = Storage; +type _StorageStatics = StorageStatics; +type _Reference = Reference; +type _FullMetadata = FullMetadata; +type _SettableMetadata = SettableMetadata; +type _ListResult = ListResult; +type _ListOptions = ListOptions; +type _TaskSnapshot = TaskSnapshot; +type _TaskResult = TaskResult; +type _Task = Task; +type _EmulatorMockTokenOptions = EmulatorMockTokenOptions; + +/** + * @deprecated Use the exported types directly instead. + * FirebaseStorageTypes namespace is kept for backwards compatibility. + */ +/* eslint-disable @typescript-eslint/no-namespace */ +export namespace FirebaseStorageTypes { + // Short name aliases referencing top-level types + export type Module = _Storage; + export type Statics = _StorageStatics; + export type Reference = _Reference; + export type FullMetadata = _FullMetadata; + export type SettableMetadata = _SettableMetadata; + export type ListResult = _ListResult; + export type ListOptions = _ListOptions; + export type TaskSnapshot = _TaskSnapshot; + export type TaskResult = _TaskResult; + export type Task = _Task; + export type EmulatorMockTokenOptions = _EmulatorMockTokenOptions; +} +/* eslint-enable @typescript-eslint/no-namespace */ diff --git a/packages/storage/lib/utils.js b/packages/storage/lib/utils.ts similarity index 66% rename from packages/storage/lib/utils.js rename to packages/storage/lib/utils.ts index 07fb0c27d0..b84de083e5 100644 --- a/packages/storage/lib/utils.js +++ b/packages/storage/lib/utils.ts @@ -17,6 +17,9 @@ import { isNull, isObject, isString } from '@react-native-firebase/app/dist/module/common'; import { NativeFirebaseError } from '@react-native-firebase/app/dist/module/internal'; +import type { SettableMetadata } from './types/storage'; +import type { StorageInternal } from './types/internal'; +import type { NativeErrorUserInfo } from '@react-native-firebase/app/dist/module/types/internal'; const SETTABLE_FIELDS = [ 'cacheControl', @@ -26,20 +29,35 @@ const SETTABLE_FIELDS = [ 'contentType', 'customMetadata', 'md5hash', -]; - -export async function handleStorageEvent(storageInstance, event) { +] as const; + +export async function handleStorageEvent( + storageInstance: StorageInternal, + event: { + taskId: string; + eventName: string; + body?: { error?: NativeErrorUserInfo }; + }, +): Promise { const { taskId, eventName } = event; const body = event.body || {}; if (body.error) { - body.error = await NativeFirebaseError.fromEvent(body.error, storageInstance._config.namespace); + // Convert NativeErrorUserInfo to NativeFirebaseError instance + const nativeError = NativeFirebaseError.fromEvent( + body.error, + storageInstance._config.namespace, + ); + // Assign NativeFirebaseError (Error instance) to body.error for consumers + // Type assertion needed because body.error is typed as NativeErrorUserInfo in input, + // but consumers expect Error instance + (body as { error?: Error }).error = nativeError; } storageInstance.emitter.emit(storageInstance.eventNameForApp(taskId, eventName), body); } -export function getHttpUrlParts(url) { +export function getHttpUrlParts(url: string): { bucket: string; path: string } | null { const decoded = decodeURIComponent(url); const parts = decoded.match(/\/b\/(.*)\/o\/([a-zA-Z0-9./\-_]+)(.*)/); @@ -47,10 +65,10 @@ export function getHttpUrlParts(url) { return null; } - return { bucket: `gs://${parts[1]}`, path: parts[2] }; + return { bucket: `gs://${parts[1]}`, path: parts[2]! }; } -export function getGsUrlParts(url) { +export function getGsUrlParts(url: string): { bucket: string; path: string } { const bucket = url.substring(0, url.indexOf('/', 5)) || url; const path = (url.indexOf('/', 5) > -1 ? url.substring(url.indexOf('/', 5) + 1, url.length) : '/') || '/'; @@ -58,7 +76,7 @@ export function getGsUrlParts(url) { return { bucket, path }; } -export function validateMetadata(metadata, update = true) { +export function validateMetadata(metadata: SettableMetadata, update = true): SettableMetadata { if (!isObject(metadata)) { throw new Error('firebase.storage.SettableMetadata must be an object value if provided.'); } @@ -66,9 +84,11 @@ export function validateMetadata(metadata, update = true) { const metadataEntries = Object.entries(metadata); for (let i = 0; i < metadataEntries.length; i++) { - const [key, value] = metadataEntries[i]; + const entry = metadataEntries[i]; + if (!entry) continue; + const [key, value] = entry; // validate keys - if (!SETTABLE_FIELDS.includes(key)) { + if (!SETTABLE_FIELDS.includes(key as (typeof SETTABLE_FIELDS)[number])) { throw new Error( `firebase.storage.SettableMetadata unknown property '${key}' provided for metadata.`, ); diff --git a/packages/storage/lib/web/RNFBStorageModule.android.js b/packages/storage/lib/web/RNFBStorageModule.android.js deleted file mode 100644 index af77c859b1..0000000000 --- a/packages/storage/lib/web/RNFBStorageModule.android.js +++ /dev/null @@ -1,2 +0,0 @@ -// No-op for android. -export default {}; diff --git a/packages/storage/lib/web/RNFBStorageModule.android.ts b/packages/storage/lib/web/RNFBStorageModule.android.ts new file mode 100644 index 0000000000..eeef32701e --- /dev/null +++ b/packages/storage/lib/web/RNFBStorageModule.android.ts @@ -0,0 +1,3 @@ +// No-op for android. +const RNFBStorageModule = {}; +export default RNFBStorageModule; diff --git a/packages/storage/lib/web/RNFBStorageModule.ios.js b/packages/storage/lib/web/RNFBStorageModule.ios.js deleted file mode 100644 index a3429ada0e..0000000000 --- a/packages/storage/lib/web/RNFBStorageModule.ios.js +++ /dev/null @@ -1,2 +0,0 @@ -// No-op for ios. -export default {}; diff --git a/packages/storage/lib/web/RNFBStorageModule.ios.ts b/packages/storage/lib/web/RNFBStorageModule.ios.ts new file mode 100644 index 0000000000..47cf3864ae --- /dev/null +++ b/packages/storage/lib/web/RNFBStorageModule.ios.ts @@ -0,0 +1,3 @@ +// Re-export the main module +const RNFBStorageModule = {}; +export default RNFBStorageModule; diff --git a/packages/storage/lib/web/RNFBStorageModule.js b/packages/storage/lib/web/RNFBStorageModule.ts similarity index 58% rename from packages/storage/lib/web/RNFBStorageModule.js rename to packages/storage/lib/web/RNFBStorageModule.ts index b58cc8491c..d67a5d6fea 100644 --- a/packages/storage/lib/web/RNFBStorageModule.js +++ b/packages/storage/lib/web/RNFBStorageModule.ts @@ -12,6 +12,15 @@ import { uploadBytesResumable, ref as firebaseStorageRef, } from '@react-native-firebase/app/dist/module/internal/web/firebaseStorage'; +import type { + StorageReference, + UploadTask, + UploadTaskSnapshot as FirebaseUploadTaskSnapshot, + FullMetadata, + ListResult as FirebaseListResult, +} from '@react-native-firebase/app/dist/module/internal/web/firebaseStorage'; +import type { FirebaseApp } from '@react-native-firebase/app/dist/module/internal/web/firebaseApp'; + import { guard, getWebError, @@ -19,17 +28,62 @@ import { } from '@react-native-firebase/app/dist/module/internal/web/utils'; import { Base64 } from '@react-native-firebase/app/dist/module/common'; -function rejectWithCodeAndMessage(code, message) { - return Promise.reject( - getWebError({ - code, - message, - }), - ); +type Storage = ReturnType; +interface EmulatorConfig { + host: string; + port: number; +} + +interface Metadata { + bucket?: string; + generation?: string; + metageneration?: string; + fullPath?: string; + name?: string; + size?: number; + timeCreated?: string; + updated?: string; + md5Hash?: string; + cacheControl?: string; + contentLanguage?: string; + contentDisposition?: string; + contentEncoding?: string; + contentType?: string; + customMetadata?: Record; +} + +interface SettableMetadata { + cacheControl?: string; + contentDisposition?: string; + contentEncoding?: string; + contentType?: string; + contentLanguage?: string; + customMetadata?: Record; + md5Hash?: string; +} + +interface UploadTaskSnapshot { + totalBytes: number; + bytesTransferred: number; + state: string; + metadata: Metadata; } -function metadataToObject(metadata) { - const out = { +interface ListOptions { + maxResults?: number; + pageToken?: string; +} + +function rejectWithCodeAndMessage(code: string, message: string): Promise { + const error = new Error(message); + (error as Error & { code?: string }).code = code; + return Promise.reject(getWebError(error)); +} + +function metadataToObject( + metadata: Metadata | FullMetadata, +): Metadata & { metadata?: Record } { + const out: Metadata & { metadata?: Record } = { bucket: metadata.bucket, generation: metadata.generation, metageneration: metadata.metageneration, @@ -70,7 +124,10 @@ function metadataToObject(metadata) { return out; } -function uploadTaskErrorToObject(error, snapshot) { +function uploadTaskErrorToObject( + error: Error, + snapshot: FirebaseUploadTaskSnapshot | null, +): UploadTaskSnapshot & { error: ReturnType } { return { ...uploadTaskSnapshotToObject(snapshot), state: 'error', @@ -78,28 +135,30 @@ function uploadTaskErrorToObject(error, snapshot) { }; } -function uploadTaskSnapshotToObject(snapshot) { +function uploadTaskSnapshotToObject( + snapshot: FirebaseUploadTaskSnapshot | null, +): UploadTaskSnapshot { return { totalBytes: snapshot ? snapshot.totalBytes : 0, bytesTransferred: snapshot ? snapshot.bytesTransferred : 0, state: snapshot ? taskStateToString(snapshot.state) : 'unknown', - metadata: snapshot ? metadataToObject(snapshot.metadata) : {}, + metadata: snapshot && snapshot.metadata ? metadataToObject(snapshot.metadata as Metadata) : {}, }; } -function taskStateToString(state) { - const override = { +function taskStateToString(state: string): string { + const override: Record = { canceled: 'cancelled', }; if (state in override) { - return override[state]; + return override[state]!; } return state; } -function makeSettableMetadata(metadata) { +function makeSettableMetadata(metadata: SettableMetadata): SettableMetadata { return { cacheControl: metadata.cacheControl, contentDisposition: metadata.contentDisposition, @@ -110,74 +169,79 @@ function makeSettableMetadata(metadata) { }; } -function listResultToObject(result) { +function listResultToObject(result: FirebaseListResult) { return { - nextPageToken: result.nextPageToken, + nextPageToken: result.nextPageToken ?? undefined, items: result.items.map(ref => ref.fullPath), prefixes: result.prefixes.map(ref => ref.fullPath), }; } -const emulatorForApp = {}; -const appInstances = {}; -const storageInstances = {}; -const tasks = {}; +const emulatorForApp: Record = {}; +const appInstances: Record = {}; +const storageInstances: Record = {}; +const tasks: Record = {}; -function getBucketFromUrl(url) { +function getBucketFromUrl(url: string): string { // Check if the URL starts with "gs://" if (url.startsWith('gs://')) { // Return the bucket name by extracting everything up to the first slash after "gs://" // and removing the "gs://" prefix - return url.substring(5).split('/')[0]; + return url.substring(5).split('/')[0]!; } else { // Assume the URL is a path format, strip the leading slash if it exists and extract the bucket name const strippedUrl = url.startsWith('/') ? url.substring(1) : url; // Extract the bucket from the path, assuming it ends at the first slash after the domain - return strippedUrl.split('/')[0]; + return strippedUrl.split('/')[0]!; } } -function getCachedAppInstance(appName) { +function getCachedAppInstance(appName: string): FirebaseApp { return (appInstances[appName] ??= getApp(appName)); } -function emulatorKey(appName, url) { +function emulatorKey(appName: string, url: string): string { const bucket = getBucketFromUrl(url); return `${appName}|${bucket}`; } // Returns a cached Storage instance. -function getCachedStorageInstance(appName, url) { - let instance; - const bucket = url ? getBucketFromUrl(url) : getCachedAppInstance(appName).options.storageBucket; - - if (!url) { - instance = getCachedStorageInstance( - appName, - getCachedAppInstance(appName).options.storageBucket, - ); +function getCachedStorageInstance(appName: string, url?: string | null): Storage { + let instance: Storage; + const app = getCachedAppInstance(appName); + const bucket = url ? getBucketFromUrl(url) : (app.options.storageBucket ?? undefined); + + if (!url || !bucket) { + const defaultBucket = app.options.storageBucket; + if (!defaultBucket) { + throw new Error(`No storage bucket configured for app ${appName}`); + } + instance = getCachedStorageInstance(appName, defaultBucket); } else { - instance = storageInstances[`${appName}|${bucket}`] ??= getStorage( - getCachedAppInstance(appName), - bucket, - ); + instance = storageInstances[`${appName}|${bucket}`] ??= getStorage(app, bucket); } - const key = emulatorKey(appName, bucket); - if (emulatorForApp[key]) { - connectStorageEmulator(instance, emulatorForApp[key].host, emulatorForApp[key].port); + if (bucket) { + const key = emulatorKey(appName, bucket); + if (emulatorForApp[key]) { + connectStorageEmulator(instance, emulatorForApp[key].host, emulatorForApp[key].port); + } } return instance; } // Returns a Storage Reference. -function getReferenceFromUrl(appName, url) { +function getReferenceFromUrl(appName: string, url: string): StorageReference { const path = url.substring(url.indexOf('/') + 1); const instance = getCachedStorageInstance(appName, path); return firebaseStorageRef(instance, url); } -const CONSTANTS = {}; +const CONSTANTS: { + maxDownloadRetryTime?: number; + maxOperationRetryTime?: number; + maxUploadRetryTime?: number; +} = {}; const defaultAppInstance = getApps()[0]; if (defaultAppInstance) { @@ -191,11 +255,11 @@ export default { /** * Delete an object at the path. - * @param {string} appName - The app name. - * @param {string} url - The path to the object. + * @param appName - The app name. + * @param url - The path to the object. * @return {Promise} */ - delete(appName, url) { + delete(appName: string, url: string): Promise { return guard(async () => { const ref = getReferenceFromUrl(appName, url); await deleteObject(ref); @@ -204,11 +268,11 @@ export default { /** * Get the download URL for an object. - * @param {string} appName - The app name. - * @param {string} url - The path to the object. + * @param appName - The app name. + * @param url - The path to the object. * @return {Promise} The download URL. */ - getDownloadURL(appName, url) { + getDownloadURL(appName: string, url: string): Promise { return guard(async () => { const ref = getReferenceFromUrl(appName, url); const downloadURL = await getDownloadURL(ref); @@ -218,11 +282,14 @@ export default { /** * Get the metadata for an object. - * @param {string} appName - The app name. - * @param {string} url - The path to the object. + * @param appName - The app name. + * @param url - The path to the object. * @return {Promise} The metadata. */ - getMetadata(appName, url) { + getMetadata( + appName: string, + url: string, + ): Promise }> { return guard(async () => { const ref = getReferenceFromUrl(appName, url); const metadata = await getMetadata(ref); @@ -232,12 +299,20 @@ export default { /** * List objects at the path. - * @param {string} appName - The app name. - * @param {string} url - The path to the object. - * @param {Object} listOptions - The list options. + * @param appName - The app name. + * @param url - The path to the object. + * @param listOptions - The list options. * @return {Promise} The list result. */ - list(appName, url, listOptions) { + list( + appName: string, + url: string, + listOptions?: ListOptions, + ): Promise<{ + nextPageToken?: string; + items: string[]; + prefixes: string[]; + }> { return guard(async () => { const ref = getReferenceFromUrl(appName, url); const listResult = await list(ref, listOptions); @@ -247,11 +322,18 @@ export default { /** * List all objects at the path. - * @param {string} appName - The app name. - * @param {string} url - The path to the object. + * @param appName - The app name. + * @param url - The path to the object. * @return {Promise} The list result. */ - listAll(appName, url) { + listAll( + appName: string, + url: string, + ): Promise<{ + nextPageToken?: string; + items: string[]; + prefixes: string[]; + }> { return guard(async () => { const ref = getReferenceFromUrl(appName, url); const listResult = await listAll(ref); @@ -261,11 +343,15 @@ export default { /** * Update the metadata for an object. - * @param {string} appName - The app name. - * @param {string} url - The path to the object. - * @param {Object} metadata - The metadata (SettableMetadata). + * @param appName - The app name. + * @param url - The path to the object. + * @param metadata - The metadata (SettableMetadata). */ - updateMetadata(appName, url, metadata) { + updateMetadata( + appName: string, + url: string, + metadata: SettableMetadata, + ): Promise }> { return guard(async () => { const ref = getReferenceFromUrl(appName, url); const updated = await updateMetadata(ref, makeSettableMetadata(metadata)); @@ -273,7 +359,7 @@ export default { }); }, - setMaxDownloadRetryTime() { + setMaxDownloadRetryTime(): void { if (__DEV__) { // eslint-disable-next-line no-console console.warn( @@ -285,11 +371,11 @@ export default { /** * Set the maximum operation retry time. - * @param {string} appName - The app name. - * @param {number} milliseconds - The maximum operation retry time. + * @param appName - The app name. + * @param milliseconds - The maximum operation retry time. * @return {Promise} */ - setMaxOperationRetryTime(appName, milliseconds) { + setMaxOperationRetryTime(appName: string, milliseconds: number): Promise { return guard(async () => { const storage = getCachedStorageInstance(appName); storage.maxOperationRetryTime = milliseconds; @@ -298,11 +384,11 @@ export default { /** * Set the maximum upload retry time. - * @param {string} appName - The app name. - * @param {number} milliseconds - The maximum upload retry time. + * @param appName - The app name. + * @param milliseconds - The maximum upload retry time. * @return {Promise} */ - setMaxUploadRetryTime(appName, milliseconds) { + setMaxUploadRetryTime(appName: string, milliseconds: number): Promise { return guard(async () => { const storage = getCachedStorageInstance(appName); storage.maxUploadRetryTime = milliseconds; @@ -311,12 +397,12 @@ export default { /** * Use the Firebase Storage emulator. - * @param {string} appName - The app name. - * @param {string} host - The emulator host. - * @param {number} port - The emulator port. + * @param appName - The app name. + * @param host - The emulator host. + * @param port - The emulator port. * @return {Promise} */ - useEmulator(appName, host, port, url) { + useEmulator(appName: string, host: string, port: number, url: string): Promise { return guard(async () => { const instance = getCachedStorageInstance(appName, url); connectStorageEmulator(instance, host, port); @@ -325,7 +411,7 @@ export default { }); }, - writeToFile() { + writeToFile(): Promise { return rejectWithCodeAndMessage( 'unsupported', 'This operation is not supported in this environment.', @@ -334,18 +420,25 @@ export default { /** * Put a string to the path. - * @param {string} appName - The app name. - * @param {string} url - The path to the object. - * @param {string} string - The string to put. - * @param {string} format - The format of the string. - * @param {Object} metadata - The metadata (SettableMetadata). - * @param {string} taskId - The task ID. + * @param appName - The app name. + * @param url - The path to the object. + * @param string - The string to put. + * @param format - The format of the string. + * @param metadata - The metadata (SettableMetadata). + * @param taskId - The task ID. * @return {Promise} The upload snapshot. */ - putString(appName, url, string, format, metadata = {}, taskId) { + putString( + appName: string, + url: string, + string: string, + format: string, + metadata: SettableMetadata = {}, + taskId: string, + ): Promise { return guard(async () => { const ref = getReferenceFromUrl(appName, url); - let decodedString = null; + let decodedString: string | null = null; // This is always either base64 or base64url switch (format) { @@ -357,7 +450,7 @@ export default { break; } - const arrayBuffer = new Uint8Array([...decodedString].map(c => c.charCodeAt(0))); + const arrayBuffer = new Uint8Array([...decodedString!].map(c => c.charCodeAt(0))); const task = uploadBytesResumable(ref, arrayBuffer, { ...makeSettableMetadata(metadata), @@ -367,10 +460,10 @@ export default { // Store the task in the tasks map. tasks[taskId] = task; - const snapshot = await new Promise((resolve, reject) => { + const snapshot = await new Promise((resolve, reject) => { task.on( 'state_changed', - snapshot => { + (snapshot: FirebaseUploadTaskSnapshot) => { const event = { body: uploadTaskSnapshotToObject(snapshot), appName, @@ -379,7 +472,7 @@ export default { }; emitEvent('storage_event', event); }, - error => { + (error: Error) => { const errorSnapshot = uploadTaskErrorToObject(error, task.snapshot); const event = { body: { @@ -402,7 +495,7 @@ export default { delete tasks[taskId]; const event = { body: { - ...uploadTaskSnapshotToObject(snapshot), + ...uploadTaskSnapshotToObject(task.snapshot), state: 'success', }, appName, @@ -423,7 +516,7 @@ export default { }); }, - putFile() { + putFile(): Promise { return rejectWithCodeAndMessage( 'unsupported', 'This operation is not supported in this environment.', @@ -432,12 +525,12 @@ export default { /** * Set the status of a task. - * @param {string} appName - The app name. - * @param {string} taskId - The task ID. - * @param {number} status - The status. + * @param appName - The app name. + * @param taskId - The task ID. + * @param status - The status. * @return {Promise} Whether the status was set. */ - setTaskStatus(appName, taskId, status) { + setTaskStatus(appName: string, taskId: string, status: number): Promise { // TODO this function implementation cannot // be tested right now since we're unable // to create a big enough upload to be able to @@ -465,7 +558,7 @@ export default { } emitEvent('storage_event', { - data: buildUploadSnapshotMap(task.snapshot), + data: uploadTaskSnapshotToObject(task.snapshot), appName, taskId, }); diff --git a/packages/storage/package.json b/packages/storage/package.json index b131f5d4b2..13d135c852 100644 --- a/packages/storage/package.json +++ b/packages/storage/package.json @@ -3,12 +3,13 @@ "version": "23.8.5", "author": "Invertase (http://invertase.io)", "description": "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.", - "main": "lib/index.js", - "types": "lib/index.d.ts", + "main": "./dist/module/index.js", + "types": "./dist/typescript/lib/index.d.ts", "scripts": { - "build": "genversion --semi lib/version.js", + "build": "genversion --esm --semi lib/version.ts", "build:clean": "rimraf android/build && rimraf ios/build", - "prepare": "yarn run build" + "compile": "bob build", + "prepare": "yarn run build && yarn compile" }, "repository": { "type": "git", @@ -34,5 +35,38 @@ "publishConfig": { "access": "public", "provenance": true - } + }, + "devDependencies": { + "react-native-builder-bob": "^0.40.13" + }, + "exports": { + ".": { + "source": "./lib/index.ts", + "types": "./dist/typescript/lib/index.d.ts", + "default": "./dist/module/index.js" + }, + "./package.json": "./package.json" + }, + "react-native-builder-bob": { + "source": "lib", + "output": "dist", + "targets": [ + [ + "module", + { + "esm": true + } + ], + [ + "typescript", + { + "tsc": "../../node_modules/.bin/tsc" + } + ] + ] + }, + "eslintIgnore": [ + "node_modules/", + "dist/" + ] } diff --git a/packages/storage/tsconfig.json b/packages/storage/tsconfig.json new file mode 100644 index 0000000000..1c070c526d --- /dev/null +++ b/packages/storage/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../tsconfig.packages.base.json", + "compilerOptions": { + "baseUrl": ".", + "rootDir": ".", + "paths": { + "@react-native-firebase/app/dist/module/common/*": ["../app/dist/typescript/lib/common/*"], + "@react-native-firebase/app/dist/module/common": ["../app/dist/typescript/lib/common"], + "@react-native-firebase/app/dist/module/internal/web/*": [ + "../app/dist/typescript/lib/internal/web/*" + ], + "@react-native-firebase/app/dist/module/internal/*": [ + "../app/dist/typescript/lib/internal/*" + ], + "@react-native-firebase/app/dist/module/internal": ["../app/dist/typescript/lib/internal"], + "@react-native-firebase/app": ["../app/dist/typescript/lib"], + "@react-native-firebase/app/dist/module/types/internal": ["../app/dist/typescript/lib/types/internal"], + } + }, + "include": ["lib/**/*"], + "exclude": ["node_modules", "dist", "__tests__", "**/*.test.ts"] +} diff --git a/packages/storage/type-test.ts b/packages/storage/type-test.ts index ff05532e3c..9f06ec6f34 100644 --- a/packages/storage/type-test.ts +++ b/packages/storage/type-test.ts @@ -1,6 +1,9 @@ import storage, { firebase, - FirebaseStorageTypes, + // Types + type Storage, + type FirebaseStorageTypes, + // Modular API getStorage, connectStorageEmulator, ref, @@ -285,3 +288,29 @@ setMaxDownloadRetryTime(modularStorage1, 25000).then(() => { console.log(StringFormat.RAW); console.log(TaskEvent.STATE_CHANGED); console.log(TaskState.SUCCESS); + +// Test type usage +const storageInstance2: Storage = firebase.storage(); +console.log(storageInstance2.app.name); + +// Test backwards compatibility types +const legacyType: FirebaseStorageTypes.Module = storageInstance2; +console.log(legacyType.app.name); +const legacyReference: FirebaseStorageTypes.Reference = storageInstance2.ref('test'); +console.log(legacyReference.fullPath); +const legacyMetadata: FirebaseStorageTypes.FullMetadata = {} as FirebaseStorageTypes.FullMetadata; +console.log(legacyMetadata); +const legacySettableMetadata: FirebaseStorageTypes.SettableMetadata = + {} as FirebaseStorageTypes.SettableMetadata; +console.log(legacySettableMetadata); +const legacyListResult: FirebaseStorageTypes.ListResult = {} as FirebaseStorageTypes.ListResult; +console.log(legacyListResult); +const legacyTaskSnapshot: FirebaseStorageTypes.TaskSnapshot = + {} as FirebaseStorageTypes.TaskSnapshot; +console.log(legacyTaskSnapshot); +const legacyTask: FirebaseStorageTypes.Task = {} as FirebaseStorageTypes.Task; +console.log(legacyTask); + +// Test SDK_VERSION +const sdkVersion: string = storage.SDK_VERSION; +console.log(sdkVersion); diff --git a/tests/local-tests/index.js b/tests/local-tests/index.js index d7c4615c41..1f15f9fdb9 100644 --- a/tests/local-tests/index.js +++ b/tests/local-tests/index.js @@ -28,6 +28,7 @@ import { FirestoreOnSnapshotInSyncTest } from './firestore/onSnapshotInSync'; import { VertexAITestComponent } from './vertexai/vertexai'; import { AuthMFADemonstrator } from './auth/auth-mfa-demonstrator'; import { HttpsCallableTestComponent } from './functions/https-callable'; +import { StorageTestComponent } from './storage/storage'; const testComponents = { // List your imported components here... @@ -38,6 +39,7 @@ const testComponents = { 'VertexAI Generation Example': VertexAITestComponent, 'Auth MFA Demonstrator': AuthMFADemonstrator, 'HttpsCallable Test': HttpsCallableTestComponent, + 'Storage Test': StorageTestComponent, }; export function TestComponents() { diff --git a/tests/local-tests/storage/storage.tsx b/tests/local-tests/storage/storage.tsx new file mode 100644 index 0000000000..f77f882619 --- /dev/null +++ b/tests/local-tests/storage/storage.tsx @@ -0,0 +1,346 @@ +import React, { useState } from 'react'; +import { Button, Text, View, ScrollView, StyleSheet } from 'react-native'; + +import { getApp, utils } from '@react-native-firebase/app'; + +import { + getStorage, + connectStorageEmulator, + ref, + uploadString, + writeToFile, + putFile, + TaskState, +} from '@react-native-firebase/storage'; + +const storage = getStorage(); +connectStorageEmulator(storage, 'localhost', 9199); + +const secondStorageBucket = 'gs://react-native-firebase-testing'; +const secondStorage = getStorage(getApp(), secondStorageBucket); +connectStorageEmulator(secondStorage, 'localhost', 9199); + +export function StorageTestComponent(): React.JSX.Element { + const [responseData, setResponseData] = useState(''); + + const clearAndSetResponse = (data: string): void => { + setResponseData(''); + setTimeout(() => setResponseData(data), 100); + }; + + const handleUploadString = async (): Promise => { + try { + const secondStorage = getStorage(getApp(), secondStorageBucket); + clearAndSetResponse('Uploading string...'); + const storageReference = ref(secondStorage, 'only-second-bucket/ok.txt'); + const snapshot = await uploadString(storageReference, 'Hello World'); + clearAndSetResponse( + JSON.stringify( + { + success: true, + state: snapshot.state, + metadata: snapshot.metadata, + bytesTransferred: snapshot.bytesTransferred, + totalBytes: snapshot.totalBytes, + }, + null, + 2, + ), + ); + } catch (error: any) { + clearAndSetResponse( + JSON.stringify( + { + success: false, + error: error.message, + code: error.code, + }, + null, + 2, + ), + ); + } + }; + + const handleUploadStringUnauthorized = async (): Promise => { + try { + clearAndSetResponse('Testing unauthorized upload...'); + const storageReference = ref(secondStorage, 'react-native-firebase-testing/should-fail.txt'); + await uploadString(storageReference, 'Hello World'); + clearAndSetResponse( + JSON.stringify( + { + success: false, + error: 'Expected error but upload succeeded', + }, + null, + 2, + ), + ); + } catch (error: any) { + clearAndSetResponse( + JSON.stringify( + { + success: true, + error: error.message, + code: error.code, + expectedError: error.code === 'storage/unauthorized', + }, + null, + 2, + ), + ); + } + }; + + const handleDownloadFile = async (): Promise => { + try { + clearAndSetResponse('Downloading file...'); + const storageRef = ref(storage, 'ok.jpeg'); + const path = `${utils.FilePath.DOCUMENT_DIRECTORY}/onDownload.jpeg`; + + await new Promise((resolve, reject) => { + const task = writeToFile(storageRef, path); + const unsubscribe = task.on('state_changed', { + next: snapshot => { + if (snapshot.state === TaskState.SUCCESS) { + clearAndSetResponse( + JSON.stringify( + { + success: true, + state: snapshot.state, + bytesTransferred: snapshot.bytesTransferred, + totalBytes: snapshot.totalBytes, + localPath: path, + }, + null, + 2, + ), + ); + unsubscribe(); + resolve(); + } + }, + error: (error: any) => { + unsubscribe(); + clearAndSetResponse( + JSON.stringify( + { + success: false, + error: error.message, + code: error.code, + }, + null, + 2, + ), + ); + reject(error); + }, + }); + }); + } catch (error: any) { + clearAndSetResponse( + JSON.stringify( + { + success: false, + error: error.message, + code: error.code, + }, + null, + 2, + ), + ); + } + }; + + const handleUploadFile = async (): Promise => { + try { + clearAndSetResponse('Uploading file...'); + const path = `${utils.FilePath.DOCUMENT_DIRECTORY}/onDownload.gif`; + const storageRef = ref(storage, 'uploadOk.jpeg'); + + await new Promise((resolve, reject) => { + const task = putFile(storageRef, path); + const unsubscribe = task.on('state_changed', { + next: snapshot => { + if (snapshot.state === TaskState.SUCCESS) { + clearAndSetResponse( + JSON.stringify( + { + success: true, + state: snapshot.state, + bytesTransferred: snapshot.bytesTransferred, + totalBytes: snapshot.totalBytes, + metadata: snapshot.metadata, + }, + null, + 2, + ), + ); + unsubscribe(); + resolve(); + } + }, + error: (error: any) => { + unsubscribe(); + clearAndSetResponse( + JSON.stringify( + { + success: false, + error: error.message, + code: error.code, + }, + null, + 2, + ), + ); + reject(error); + }, + }); + }); + } catch (error: any) { + clearAndSetResponse( + JSON.stringify( + { + success: false, + error: error.message, + code: error.code, + }, + null, + 2, + ), + ); + } + }; + + const handleUploadStringWithObserver = async (): Promise => { + try { + clearAndSetResponse('Uploading string with observer...'); + const storageRef = ref(storage, 'playground/putStringBlob.json'); + + await new Promise((resolve, reject) => { + const uploadTaskSnapshot = uploadString( + storageRef, + 'Just a string to put in a file for upload', + ); + + const unsubscribe = uploadTaskSnapshot.on('state_changed', { + next: snapshot => { + if (snapshot.state === TaskState.SUCCESS) { + clearAndSetResponse( + JSON.stringify( + { + success: true, + state: snapshot.state, + bytesTransferred: snapshot.bytesTransferred, + totalBytes: snapshot.totalBytes, + metadata: snapshot.metadata, + snapshotAvailable: !!snapshot, + }, + null, + 2, + ), + ); + unsubscribe(); + resolve(); + } + }, + error: (error: any) => { + unsubscribe(); + clearAndSetResponse( + JSON.stringify( + { + success: false, + error: error.message, + code: error.code, + }, + null, + 2, + ), + ); + reject(error); + }, + }); + }); + } catch (error: any) { + clearAndSetResponse( + JSON.stringify( + { + success: false, + error: error.message, + code: error.code, + }, + null, + 2, + ), + ); + } + }; + + return ( + + Storage Test + Ensure Emulator is running!! + + +