From 053dc3b278b9f1038a8745fbe9f773f81c26c594 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Thu, 8 Jan 2026 11:08:25 +0000 Subject: [PATCH 01/36] feat: tsconfig for storage --- packages/storage/tsconfig.json | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 packages/storage/tsconfig.json diff --git a/packages/storage/tsconfig.json b/packages/storage/tsconfig.json new file mode 100644 index 0000000000..58755dcb00 --- /dev/null +++ b/packages/storage/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../../tsconfig.packages.base.json", + "compilerOptions": { + "baseUrl": ".", + "rootDir": ".", + "paths": { + "@react-native-firebase/app/lib/common/*": ["../app/dist/typescript/commonjs/lib/common/*"], + "@react-native-firebase/app/lib/common": ["../app/dist/typescript/commonjs/lib/common"], + "@react-native-firebase/app/lib/internal/web/*": [ + "../app/dist/typescript/commonjs/lib/internal/web/*" + ], + "@react-native-firebase/app/lib/internal/*": [ + "../app/dist/typescript/commonjs/lib/internal/*" + ], + "@react-native-firebase/app/lib/internal": ["../app/dist/typescript/commonjs/lib/internal"], + "@react-native-firebase/app": ["../app/dist/typescript/commonjs/lib"] + } + }, + "include": ["lib/**/*"], + "exclude": ["node_modules", "dist", "__tests__", "**/*.test.ts"] +} From 7ebedb9aa45bbd87f8dec97593c928a97708ec95 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Thu, 8 Jan 2026 11:10:32 +0000 Subject: [PATCH 02/36] package.json --- packages/storage/package.json | 60 ++++++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/packages/storage/package.json b/packages/storage/package.json index f65d98c4e1..910cc75d47 100644 --- a/packages/storage/package.json +++ b/packages/storage/package.json @@ -3,12 +3,16 @@ "version": "23.7.0", "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/commonjs/index.js", + "module": "./dist/module/index.js", + "types": "./dist/typescript/commonjs/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" + "build:plugin": "rimraf plugin/build && tsc --build plugin", + "lint:plugin": "eslint plugin/src/*", + "compile": "bob build", + "prepare": "yarn run build && yarn run build:plugin && yarn compile" }, "repository": { "type": "git", @@ -34,5 +38,53 @@ "publishConfig": { "access": "public", "provenance": true + }, + "exports": { + ".": { + "source": "./lib/index.ts", + "import": { + "types": "./dist/typescript/module/lib/index.d.ts", + "default": "./dist/module/index.js" + }, + "require": { + "types": "./dist/typescript/commonjs/lib/index.d.ts", + "default": "./dist/commonjs/index.js" + } + }, + "./package.json": "./package.json" + }, + "files": [ + "lib", + "plugin/src", + "tsconfig.json", + "plugin/tsconfig.json", + "dist", + "!**/__tests__", + "!**/__fixtures__", + "!**/__mocks__" + ], + "react-native-builder-bob": { + "source": "lib", + "output": "dist", + "targets": [ + [ + "module", + { + "esm": true + } + ], + [ + "commonjs", + { + "esm": true + } + ], + [ + "typescript", + { + "tsc": "../../node_modules/.bin/tsc" + } + ] + ] } } From 22367cfee8f95957e5c7c5aaf161895ea90ec890 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Thu, 8 Jan 2026 11:19:21 +0000 Subject: [PATCH 03/36] deleted old declaration files --- packages/storage/lib/index.d.ts | 1186 ----------------------- packages/storage/lib/modular/index.d.ts | 276 ------ 2 files changed, 1462 deletions(-) delete mode 100644 packages/storage/lib/index.d.ts delete mode 100644 packages/storage/lib/modular/index.d.ts 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/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; From 6c24d19616c1b6cd468a6f434c6b6ea3b8e90534 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Thu, 8 Jan 2026 15:42:25 +0000 Subject: [PATCH 04/36] chore: storage types --- packages/storage/lib/types/storage.ts | 305 ++++++++++++++++++++++++++ 1 file changed, 305 insertions(+) create mode 100644 packages/storage/lib/types/storage.ts diff --git a/packages/storage/lib/types/storage.ts b/packages/storage/lib/types/storage.ts new file mode 100644 index 0000000000..3d2f464483 --- /dev/null +++ b/packages/storage/lib/types/storage.ts @@ -0,0 +1,305 @@ +/* + * 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; + 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; +} + +/** + * 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?: string, metadata?: SettableMetadata): Task; + toString(): string; + updateMetadata(metadata: SettableMetadata): Promise; + writeToFile(filePath: string): Task; + putFile(filePath: string, metadata?: SettableMetadata): Task; +} + +/** + * Storage task for uploads or downloads. + */ +export interface Task extends Promise { + snapshot: TaskSnapshot | null; + on( + event: string, + nextOrObserver?: ((snapshot: TaskSnapshot) => void) | null, + error?: ((error: Error) => void) | null, + complete?: ((snapshot: TaskSnapshot) => void) | null, + ): () => void; + pause(): Promise; + resume(): Promise; + cancel(): Promise; + catch(onRejected: (error: Error) => void | PromiseLike): 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). + */ + 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 */ From c6ba689cfb56de2c0dcd8d8490cfa20ea06a42b5 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Thu, 8 Jan 2026 15:48:55 +0000 Subject: [PATCH 05/36] fix: task type --- packages/storage/lib/types/storage.ts | 51 ++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/packages/storage/lib/types/storage.ts b/packages/storage/lib/types/storage.ts index 3d2f464483..8010048c86 100644 --- a/packages/storage/lib/types/storage.ts +++ b/packages/storage/lib/types/storage.ts @@ -115,18 +115,59 @@ export interface Reference { /** * Storage task for uploads or downloads. */ -export interface Task extends Promise { +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: string, nextOrObserver?: ((snapshot: TaskSnapshot) => void) | null, error?: ((error: Error) => void) | null, complete?: ((snapshot: TaskSnapshot) => void) | null, ): () => void; - pause(): Promise; - resume(): Promise; - cancel(): Promise; - catch(onRejected: (error: Error) => void | PromiseLike): Promise; + + /** + * 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; } /** From d4f35a5986bec0969af59cf1506e8f6503edd050 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Thu, 8 Jan 2026 16:23:09 +0000 Subject: [PATCH 06/36] chore: modular, namespaced, types and index --- packages/storage/lib/index.ts | 38 +++ packages/storage/lib/modular.ts | 328 ++++++++++++++++++++++++++ packages/storage/lib/namespaced.ts | 254 ++++++++++++++++++++ packages/storage/lib/types/storage.ts | 1 + 4 files changed, 621 insertions(+) create mode 100644 packages/storage/lib/index.ts create mode 100644 packages/storage/lib/modular.ts create mode 100644 packages/storage/lib/namespaced.ts diff --git a/packages/storage/lib/index.ts b/packages/storage/lib/index.ts new file mode 100644 index 0000000000..fd32598fba --- /dev/null +++ b/packages/storage/lib/index.ts @@ -0,0 +1,38 @@ +/* + * 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, + 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.ts b/packages/storage/lib/modular.ts new file mode 100644 index 0000000000..cf1099fbfb --- /dev/null +++ b/packages/storage/lib/modular.ts @@ -0,0 +1,328 @@ +/* + * 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 { getApp } from '@react-native-firebase/app'; +import { MODULAR_DEPRECATION_ARG } from '@react-native-firebase/app/lib/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. + * @param app - FirebaseApp. Optional. + * @param bucketUrl - Storage bucket URL. Optional. + * @returns {Storage} + */ +export function getStorage(app?: FirebaseApp, bucketUrl?: string): Storage { + if (app) { + if (bucketUrl != null) { + return getApp(app.name).storage(bucketUrl); + } + + return getApp(app.name).storage(); + } + + if (bucketUrl != null) { + return getApp().storage(bucketUrl); + } + + return getApp().storage(); +} + +/** + * Modify this Storage instance to communicate with the Firebase Storage emulator. + * @param storage - Storage instance. + * @param host - emulator host (e.g. - 'localhost') + * @param port - emulator port (e.g. - 9199) + * @param options - `EmulatorMockTokenOptions` instance. Optional. Web only. + * @returns {void} + */ +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); +} + +/** + * Modify this Storage instance to communicate with the Firebase Storage emulator. + * @param storage - Storage instance. + * @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. Optional. + * @returns {Reference} + */ +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); +} + +/** + * Deletes the object at this reference's location. + * @param storageRef - Storage `Reference` instance. + * @returns {Promise} + */ +export function deleteObject(storageRef: Reference): Promise { + // @ts-ignore - MODULAR_DEPRECATION_ARG is filtered out internally + return storageRef.delete.call(storageRef, MODULAR_DEPRECATION_ARG); +} + +/** + * Downloads the data at the object's location. Returns an error if the object is not found. + * @param storageRef - Storage `Reference` instance. + * @returns {Promise} + */ +export function getBlob(_storageRef: Reference, _maxDownloadSizeBytes?: number): Promise { + throw new Error('`getBlob()` is not implemented'); +} + +/** + * Downloads the data at the object's location. Returns an error if the object is not found. + * @param storageRef - Storage `Reference` instance. + * @param maxDownloadSizeBytes - The maximum allowed size in bytes to retrieve. Web only. + * @returns {Promise} + */ +export function getBytes( + _storageRef: Reference, + _maxDownloadSizeBytes?: number, +): Promise { + throw new Error('`getBytes()` is not implemented'); +} + +/** + * Deletes the object at this reference's location. + * @param storageRef - Storage `Reference` instance. + * @returns {Promise} + */ +export function getDownloadURL(storageRef: Reference): Promise { + // @ts-ignore - MODULAR_DEPRECATION_ARG is filtered out internally + return storageRef.getDownloadURL.call(storageRef, MODULAR_DEPRECATION_ARG); +} + +/** + * Fetches metadata for the object at this location, if one exists. + * @param storageRef - Storage `Reference` instance. + * @returns {Promise} + */ +export function getMetadata(storageRef: Reference): Promise { + // @ts-ignore - MODULAR_DEPRECATION_ARG is filtered out internally + return storageRef.getMetadata.call(storageRef, MODULAR_DEPRECATION_ARG); +} + +/** + * Downloads the data at the object's location. This API is only available in Nodejs. + * @param storageRef - Storage `Reference` instance. + * @param maxDownloadSizeBytes - The maximum allowed size in bytes to retrieve. Web only. + * @returns {NodeJS.ReadableStream;} + */ +export function getStream( + _storageRef: Reference, + _maxDownloadSizeBytes?: number, +): NodeJS.ReadableStream { + throw new Error('`getStream()` is not implemented'); +} + +/** + * List items (files) and prefixes (folders) under this storage reference + * @param storageRef - Storage `Reference` instance. + * @param options - Storage `ListOptions` instance. The options list() accepts. + * @returns {Promise} + */ +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); +} + +/** + * List all items (files) and prefixes (folders) under this storage reference. + * @param storageRef - Storage `Reference` instance. + * @returns {Promise} + */ +export function listAll(storageRef: Reference): Promise { + // @ts-ignore - MODULAR_DEPRECATION_ARG is filtered out internally + return storageRef.listAll.call(storageRef, MODULAR_DEPRECATION_ARG); +} + +/** + * Updates the metadata for this object. + * @param storageRef - Storage `Reference` instance. + * @param metadata - A Storage `SettableMetadata` instance to update. + * @returns {Promise} + */ +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); +} + +/** + * Uploads data to this object's location. The upload is not resumable. + * @param storageRef - Storage `Reference` instance. + * @param data - The data (Blob | Uint8Array | ArrayBuffer) to upload to the storage bucket at the reference location. + * @param metadata - A Storage `SettableMetadata` instance to update. Optional. + * @returns {Promise} + */ +export async function uploadBytes( + _storageRef: Reference, + _data: Blob | Uint8Array | ArrayBuffer, + _metadata?: SettableMetadata, +): Promise { + throw new Error('`uploadBytes()` is not implemented'); +} + +/** + * Uploads data to this object's location. The upload is not resumable. + * @param storageRef - Storage `Reference` instance. + * @param data - The data (Blob | Uint8Array | ArrayBuffer) to upload to the storage bucket at the reference location. + * @param metadata - A Storage `SettableMetadata` instance to update. Optional. + * @returns {Task} + */ +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); +} + +/** + * Uploads data to this object's location. The upload is not resumable. + * @param storageRef - Storage `Reference` instance. + * @param value - The string to upload. + * @param format - The format of the string to upload ('raw' | 'base64' | 'base64url' | 'data_url'). Optional. + * @param metadata - A Storage `SettableMetadata` instance to update. Optional. + * @returns {Task} + */ +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); +} + +// Methods not on the Firebase JS SDK below + +/** + * Returns a new Storage `Reference` instance from a storage bucket URL. + * @param storage - Storage instance. + * @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: Storage, url: string): Reference { + // @ts-ignore - MODULAR_DEPRECATION_ARG is filtered out internally + return storage.refFromURL.call(storage, url, MODULAR_DEPRECATION_ARG); +} + +/** + * Sets the maximum time in milliseconds to retry a download if a failure occurs.. android & iOS only. + * @param storage - Storage instance. + * @param time - The new maximum operation retry time in milliseconds. + * @returns {Promise} + */ +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); +} + +/** + * Sets the maximum time in milliseconds to retry an upload if a failure occurs. android & iOS only. + * @param storage - Storage instance. + * @param time - The new maximum operation retry time in milliseconds. + * @returns {Promise} + */ +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); +} + +/** + * Puts a file from local disk onto the storage bucket. + * @param storageRef - Storage Reference instance. + * @param localFilePath The local file path to upload to the bucket at the reference location. + * @param metadata Any additional `SettableMetadata` for this task. + * @returns {Task} + */ +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); +} + +/** + * Downloads a file to the specified local file path on the device. + * @param storageRef - Storage Reference instance. + * @param localFilePath The local file path to upload to on the device. + * @returns {Task} + */ +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); +} + +/** + * Returns a gs:// URL for this object in the form `gs://///`. + * @param storageRef - Storage Reference instance. + * @returns {String} + */ +export function toString(storageRef: Reference): string { + // @ts-ignore - MODULAR_DEPRECATION_ARG is filtered out internally + return storageRef.toString.call(storageRef, MODULAR_DEPRECATION_ARG); +} + +/** + * Returns a reference to a relative path from this reference. + * @param storageRef - Storage Reference instance. + * @param path - The relative path from this reference. Leading, trailing, and consecutive slashes are removed. + * @returns {String} + */ +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); +} + +/** + * Sets the maximum time in milliseconds to retry a download if a failure occurs. + * @param storage - Storage instance. + * @param time - The new maximum download retry time in milliseconds. + * @returns {Promise} + */ +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'; diff --git a/packages/storage/lib/namespaced.ts b/packages/storage/lib/namespaced.ts new file mode 100644 index 0000000000..90a1c57d7c --- /dev/null +++ b/packages/storage/lib/namespaced.ts @@ -0,0 +1,254 @@ +/* + * 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 { + isAndroid, + isNumber, + isString, + createDeprecationProxy, +} from '@react-native-firebase/app/lib/common'; + +import { setReactNativeModule } from '@react-native-firebase/app/lib/internal/nativeModule'; +import { + createModuleNamespace, + FirebaseModule, + getFirebaseRoot, +} from '@react-native-firebase/app/lib/internal'; +import type { ModuleConfig } from '@react-native-firebase/app/lib/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'; +// @ts-ignore - Web fallback module +import fallBackModule from './web/RNFBStorageModule'; +import type { Storage, StorageStatics, Reference, EmulatorMockTokenOptions } from './types/storage'; + +const statics: StorageStatics = { + StringFormat, + TaskEvent, + TaskState, +}; + +const namespace = 'storage'; +const nativeEvents = ['storage_event']; +const nativeModuleName = 'RNFBStorageModule'; + +class FirebaseStorageModule extends FirebaseModule { + declare _customUrlOrRegion: string; + declare emulatorHost: string | undefined; + declare emulatorPort: number; + declare _maxUploadRetryTime: number; + declare _maxDownloadRetryTime: number; + declare _maxOperationRetryTime: number; + + constructor( + app: ReactNativeFirebase.FirebaseAppBase, + config: ModuleConfig, + bucketUrl?: string | null, + ) { + super(app, config, bucketUrl); + if (bucketUrl === undefined) { + this._customUrlOrRegion = `gs://${app.options.storageBucket}`; + } else if (!isString(bucketUrl) || !bucketUrl.startsWith('gs://')) { + throw new Error( + "firebase.app().storage(*) bucket url must be a string and begin with 'gs://'", + ); + } + + this.emitter.addListener( + this.eventNameForApp(nativeEvents[0]!), + handleStorageEvent.bind(null, this), + ); + + // Emulator instance vars needed to send through on iOS, iOS does not persist emulator state between calls + this.emulatorHost = undefined; + this.emulatorPort = 0; + this._maxUploadRetryTime = this.native.maxUploadRetryTime || 0; + this._maxDownloadRetryTime = this.native.maxDownloadRetryTime || 0; + this._maxOperationRetryTime = this.native.maxOperationRetryTime || 0; + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setmaxuploadretrytime + */ + get maxUploadRetryTime(): number { + return this._maxUploadRetryTime; + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setmaxdownloadretrytime + */ + get maxDownloadRetryTime(): number { + return this._maxDownloadRetryTime; + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#maxoperationretrytime + */ + get maxOperationRetryTime(): number { + return this._maxOperationRetryTime; + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#ref + */ + 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)) as unknown as Reference; + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#refFromURL + */ + 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: string; + let bucket: string; + + if (url.startsWith('http')) { + const parts = getHttpUrlParts(url); + if (!parts) { + throw new Error( + "firebase.storage().refFromURL(*) unable to parse 'url', ensure it's a valid storage url'.", + ); + } + ({ bucket, path } = parts); + } else { + ({ bucket, path } = getGsUrlParts(url)); + } + + const storageInstance = this.app.storage(bucket); + return new StorageReference(storageInstance, path); + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setMaxOperationRetryTime + */ + setMaxOperationRetryTime(time: number): Promise { + if (!isNumber(time)) { + throw new Error( + "firebase.storage().setMaxOperationRetryTime(*) 'time' must be a number value.", + ); + } + + this._maxOperationRetryTime = time; + return this.native.setMaxOperationRetryTime(time); + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setMaxUploadRetryTime + */ + setMaxUploadRetryTime(time: number): Promise { + if (!isNumber(time)) { + throw new Error("firebase.storage().setMaxUploadRetryTime(*) 'time' must be a number value."); + } + + this._maxUploadRetryTime = time; + return this.native.setMaxUploadRetryTime(time); + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setMaxDownloadRetryTime + */ + setMaxDownloadRetryTime(time: number): Promise { + if (!isNumber(time)) { + throw new Error( + "firebase.storage().setMaxDownloadRetryTime(*) 'time' must be a number value.", + ); + } + + this._maxDownloadRetryTime = time; + return this.native.setMaxDownloadRetryTime(time); + } + + 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'); + } + + let _host = host; + + const androidBypassEmulatorUrlRemap = + typeof this.firebaseJson.android_bypass_emulator_url_remap === 'boolean' && + this.firebaseJson.android_bypass_emulator_url_remap; + if (!androidBypassEmulatorUrlRemap && isAndroid && _host) { + if (_host === 'localhost' || _host === '127.0.0.1') { + _host = '10.0.2.2'; + // eslint-disable-next-line no-console + console.log( + 'Mapping storage host to "10.0.2.2" for android emulators. Use real IP on real devices. You can bypass this behaviour with "android_bypass_emulator_url_remap" flag.', + ); + } + } + this.emulatorHost = host; + this.emulatorPort = port; + this.native.useEmulator(_host, port, this._customUrlOrRegion); + // @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; + +const storageNamespace = createModuleNamespace({ + statics, + version, + namespace, + nativeEvents, + nativeModuleName, + hasMultiAppSupport: true, + hasCustomUrlOrRegionSupport: true, + disablePrependCustomUrlOrRegion: true, + ModuleClass: FirebaseStorageModule as any, +}); + +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() as unknown as ReactNativeFirebase.FirebaseNamespacedExport< + 'storage', + Storage, + StorageStatics, + true + >; + +export * from './modular'; + +setReactNativeModule(nativeModuleName, fallBackModule); diff --git a/packages/storage/lib/types/storage.ts b/packages/storage/lib/types/storage.ts index 8010048c86..c55925e294 100644 --- a/packages/storage/lib/types/storage.ts +++ b/packages/storage/lib/types/storage.ts @@ -243,6 +243,7 @@ export interface Storage extends ReactNativeFirebase.FirebaseModule { * @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; } From 30e16425d249e7ef4c95c65b57958992a0da3173 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Thu, 8 Jan 2026 16:33:07 +0000 Subject: [PATCH 07/36] convert the rest of the src files to TS --- packages/storage/lib/StorageDownloadTask.ts | 27 ++ packages/storage/lib/StorageListResult.ts | 56 ++++ packages/storage/lib/StorageReference.ts | 329 ++++++++++++++++++++ packages/storage/lib/StorageStatics.ts | 35 +++ packages/storage/lib/StorageTask.ts | 298 ++++++++++++++++++ packages/storage/lib/StorageUploadTask.ts | 27 ++ packages/storage/lib/modular/index.js | 284 ----------------- packages/storage/lib/utils.ts | 109 +++++++ 8 files changed, 881 insertions(+), 284 deletions(-) create mode 100644 packages/storage/lib/StorageDownloadTask.ts create mode 100644 packages/storage/lib/StorageListResult.ts create mode 100644 packages/storage/lib/StorageReference.ts create mode 100644 packages/storage/lib/StorageStatics.ts create mode 100644 packages/storage/lib/StorageTask.ts create mode 100644 packages/storage/lib/StorageUploadTask.ts delete mode 100644 packages/storage/lib/modular/index.js create mode 100644 packages/storage/lib/utils.ts diff --git a/packages/storage/lib/StorageDownloadTask.ts b/packages/storage/lib/StorageDownloadTask.ts new file mode 100644 index 0000000000..14a17e34f8 --- /dev/null +++ b/packages/storage/lib/StorageDownloadTask.ts @@ -0,0 +1,27 @@ +/* + * 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 StorageTask from './StorageTask'; +import type { Reference } from './types/storage'; + +const DOWNLOAD_TASK = 'download'; + +export default class StorageDownloadTask extends StorageTask { + constructor(storageRef: Reference, beginTaskFn: (task: StorageTask) => Promise) { + super(DOWNLOAD_TASK, storageRef, beginTaskFn); + } +} diff --git a/packages/storage/lib/StorageListResult.ts b/packages/storage/lib/StorageListResult.ts new file mode 100644 index 0000000000..9b1272b0b2 --- /dev/null +++ b/packages/storage/lib/StorageListResult.ts @@ -0,0 +1,56 @@ +/* + * 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 { Reference } from './types/storage'; + +// To avoid React Native require cycle warnings +let StorageReference: (new (storage: any, path: string) => Reference) | null = null; + +export function provideStorageReferenceClass( + storageReference: new (storage: any, path: string) => Reference, +): void { + StorageReference = storageReference; +} + +export default class StorageListResult { + private _nextPageToken: string | null; + private _items: Reference[]; + private _prefixes: Reference[]; + + constructor( + storage: any, + nativeData: { nextPageToken?: string | null; items: string[]; prefixes: string[] }, + ) { + this._nextPageToken = nativeData.nextPageToken || null; + // @ts-ignore - StorageReference is set by provideStorageReferenceClass before use + this._items = nativeData.items.map(path => new StorageReference(storage, path)); + // @ts-ignore - StorageReference is set by provideStorageReferenceClass before use + this._prefixes = nativeData.prefixes.map(path => new StorageReference(storage, path)); + } + + get items(): Reference[] { + return this._items; + } + + get nextPageToken(): string | null { + return this._nextPageToken; + } + + get prefixes(): Reference[] { + return this._prefixes; + } +} diff --git a/packages/storage/lib/StorageReference.ts b/packages/storage/lib/StorageReference.ts new file mode 100644 index 0000000000..2e0b5ba8f3 --- /dev/null +++ b/packages/storage/lib/StorageReference.ts @@ -0,0 +1,329 @@ +/* + * 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 { + Base64, + getDataUrlParts, + hasOwnProperty, + isInteger, + isNumber, + isObject, + isString, + isUndefined, + pathChild, + pathLastComponent, + pathParent, + ReferenceBase, + toFilePath, +} from '@react-native-firebase/app/lib/common'; +import StorageDownloadTask from './StorageDownloadTask'; +import StorageListResult, { provideStorageReferenceClass } from './StorageListResult'; +import { StringFormat } from './StorageStatics'; +import StorageUploadTask from './StorageUploadTask'; +import { validateMetadata } from './utils'; +import type { Reference, SettableMetadata, ListOptions, FullMetadata, Task } from './types/storage'; + +export default class StorageReference extends ReferenceBase implements Reference { + declare _storage: any; // Will be properly typed when Storage module is converted + + constructor(storage: any, path: string) { + super(path); + this._storage = storage; + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#bucket + */ + get bucket(): string { + return this._storage._customUrlOrRegion.replace('gs://', ''); + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#fullPath + */ + get fullPath(): string { + return this.path; + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#name + */ + get name(): string { + return pathLastComponent(this.path); + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#parent + */ + get parent(): Reference | null { + const parentPath = pathParent(this.path); + if (parentPath === null) { + return parentPath; + } + return new StorageReference(this._storage, parentPath); + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#root + */ + get root(): Reference { + return new StorageReference(this._storage, '/'); + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#storage + */ + get storage(): any { + return this._storage; + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#child + */ + child(path: string): Reference { + const childPath = pathChild(this.path, path); + return new StorageReference(this._storage, childPath); + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#delete + */ + delete(): Promise { + return this._storage.native.delete(this.toString()); + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#getDownloadURL + */ + getDownloadURL(): Promise { + return this._storage.native.getDownloadURL(this.toString()); + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#getMetadata + */ + getMetadata(): Promise { + return this._storage.native.getMetadata(this.toString()); + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#list + */ + list(options?: ListOptions): Promise { + if (!isUndefined(options) && !isObject(options)) { + throw new Error( + "firebase.storage.StorageReference.list(*) 'options' expected an object value.", + ); + } + + const listOptions: { maxResults: number; pageToken?: string } = { + maxResults: 1000, + }; + + if (options) { + if (hasOwnProperty(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 (maxResults < 1 || maxResults > 1000) { + throw new Error( + "firebase.storage.StorageReference.list(*) 'options.maxResults' expected a number value between 1-1000.", + ); + } + + listOptions.maxResults = maxResults; + } + + if (options.pageToken) { + if (!isString(options.pageToken)) { + throw new Error( + "firebase.storage.StorageReference.list(*) 'options.pageToken' expected a string value.", + ); + } + + listOptions.pageToken = options.pageToken; + } + } + + return this._storage.native + .list(this.toString(), listOptions) + .then((data: any) => new StorageListResult(this._storage, data)); + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#listAll + */ + listAll(): Promise { + return this._storage.native + .listAll(this.toString()) + .then((data: any) => new StorageListResult(this._storage, data)); + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#put + */ + 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 as string, + format, + metadata, + false, + ); + return this._storage.native.putString( + this.toString(), + _string, + _format, + _metadata, + task._id, + ); + }), + ) as unknown as Task; + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#putString + */ + putString(string: string, format: string = StringFormat.RAW, metadata?: SettableMetadata): Task { + const { _string, _format, _metadata } = this._updateString(string, format, metadata, false); + + return new StorageUploadTask(this, task => + this._storage.native.putString(this.toString(), _string, _format, _metadata, task._id), + ) as unknown as Task; + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#fullPath + */ + toString(): string { + if (this.path.length <= 1) { + return `${this._storage._customUrlOrRegion}`; + } + + return `${this._storage._customUrlOrRegion}/${this.path}`; + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#updateMetadata + */ + updateMetadata(metadata: SettableMetadata): Promise { + validateMetadata(metadata); + return this._storage.native.updateMetadata(this.toString(), metadata); + } + + /* ---------------------------------------- + * EXTRA APIS (DO NOT ON EXIST WEB SDK) + * ---------------------------------------- */ + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference + */ + writeToFile(filePath: string): Task { + if (!isString(filePath)) { + throw new Error( + "firebase.storage.StorageReference.writeToFile(*) 'filePath' expects a string value.", + ); + } + + return new StorageDownloadTask(this, task => + this._storage.native.writeToFile(this.toString(), toFilePath(filePath), task._id), + ) as unknown as Task; + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference + */ + putFile(filePath: string, metadata?: SettableMetadata): Task { + if (!isUndefined(metadata)) { + validateMetadata(metadata, false); + } + + if (!isString(filePath)) { + throw new Error( + "firebase.storage.StorageReference.putFile(*, _) 'filePath' expects a string value.", + ); + } + + return new StorageUploadTask(this, task => + this._storage.native.putFile(this.toString(), toFilePath(filePath), metadata, task._id), + ) as unknown as Task; + } + + _updateString( + string: string, + format: string, + 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.", + ); + } + + if (!Object.values(StringFormat).includes(format as any)) { + throw new Error( + `firebase.storage.StorageReference.putString(_, *, _) 'format' provided is invalid, must be one of ${Object.values( + StringFormat, + ).join(',')}.`, + ); + } + + if (!isUndefined(metadata)) { + validateMetadata(metadata, update); + } + + let _string = string; + let _format = format; + let _metadata = metadata; + + if (format === StringFormat.RAW) { + _string = Base64.btoa(_string); + _format = StringFormat.BASE64; + } else if (format === StringFormat.DATA_URL) { + const { mediaType, base64String } = getDataUrlParts(_string); + if (isUndefined(base64String)) { + throw new Error( + 'firebase.storage.StorageReference.putString(*, _, _) invalid data_url string provided.', + ); + } + + if (isUndefined(metadata) || isUndefined(metadata.contentType)) { + if (isUndefined(metadata)) { + _metadata = {}; + } else { + _metadata = { ...metadata }; + } + _metadata.contentType = mediaType; + _string = base64String; + _format = StringFormat.BASE64; + } + } + return { _string, _metadata, _format }; + } +} + +provideStorageReferenceClass(StorageReference); diff --git a/packages/storage/lib/StorageStatics.ts b/packages/storage/lib/StorageStatics.ts new file mode 100644 index 0000000000..26bdb82bdd --- /dev/null +++ b/packages/storage/lib/StorageStatics.ts @@ -0,0 +1,35 @@ +/* + * 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 const StringFormat = { + RAW: 'raw', + 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.ts b/packages/storage/lib/StorageTask.ts new file mode 100644 index 0000000000..67a7c4a9fe --- /dev/null +++ b/packages/storage/lib/StorageTask.ts @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2016-present Invertase Limited & Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this library except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { isFunction, isNull, isObject } from '@react-native-firebase/app/lib/common'; +import type { EmitterSubscription } from 'react-native'; +import { TaskEvent } from './StorageStatics'; +import type { TaskSnapshot, Reference } from './types/storage'; + +let TASK_ID = 0; + +function wrapErrorEventListener( + listenerFn: ((error: Error) => void) | null | undefined, + unsubscribe: (() => void) | null | undefined, +): (event: { error: Error }) => void { + return event => { + if (unsubscribe) { + setTimeout(() => unsubscribe(), 0); + } // 1 frame = 16ms, pushing to next frame + if (isFunction(listenerFn)) { + listenerFn(event.error); + } + }; +} + +function wrapSnapshotEventListener( + task: StorageTask, + listenerFn: ((snapshot: TaskSnapshot) => void) | null | undefined, + unsubscribe: (() => void) | null | undefined, +): ((event: any) => void) | null { + if (!isFunction(listenerFn)) { + return null; + } + return event => { + if (unsubscribe) { + setTimeout(() => unsubscribe(), 0); + } // 1 frame = 16ms, pushing to next frame + if (isFunction(listenerFn)) { + const snapshot = Object.assign({}, event) as any; + snapshot.task = task; + snapshot.ref = task._ref; + + if (snapshot.metadata) { + if (!snapshot.metadata.generation) { + snapshot.metadata.generation = ''; + } + if (!snapshot.metadata.bucket) { + snapshot.metadata.bucket = task._ref.bucket; + } + if (!snapshot.metadata.metageneration) { + snapshot.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; + } + + Object.freeze(snapshot); + task._snapshot = snapshot; + + listenerFn(snapshot); + } + }; +} + +function addTaskEventListener( + task: StorageTask, + eventName: string, + listener: (event: any) => void, +): EmitterSubscription { + let _eventName = eventName; + if (_eventName !== TaskEvent.STATE_CHANGED) { + _eventName = `${task._type}_${eventName}`; + } + + return task._storage.emitter.addListener( + task._storage.eventNameForApp(task._id, _eventName), + listener, + ); +} + +function subscribeToEvents( + task: StorageTask, + nextOrObserver?: + | ((snapshot: TaskSnapshot) => void) + | { + next?: (snapshot: TaskSnapshot) => void; + error?: (error: Error) => void; + complete?: () => void; + } + | null, + error?: ((error: Error) => void) | null, + complete?: (() => void) | null, +): () => void { + let _error: ((event: { error: Error }) => void) | undefined; + let _errorSubscription: EmitterSubscription | undefined; + + let _next: ((event: any) => void) | null | undefined; + let _nextSubscription: EmitterSubscription | undefined; + + let _complete: ((event: any) => void) | null | undefined; + let _completeSubscription: EmitterSubscription | undefined; + + const unsubscribe = () => { + if (_nextSubscription) { + _nextSubscription.remove(); + } + if (_errorSubscription) { + _errorSubscription.remove(); + } + if (_completeSubscription) { + _completeSubscription.remove(); + } + }; + + if (isFunction(nextOrObserver)) { + _error = wrapErrorEventListener(error, unsubscribe); + _next = wrapSnapshotEventListener(task, nextOrObserver, unsubscribe); + _complete = wrapSnapshotEventListener(task, complete, unsubscribe); + } else if (isObject(nextOrObserver)) { + _error = wrapErrorEventListener(nextOrObserver.error, unsubscribe); + _next = wrapSnapshotEventListener(task, nextOrObserver.next, unsubscribe); + _complete = wrapSnapshotEventListener(task, nextOrObserver.complete, unsubscribe); + } else if (isNull(nextOrObserver)) { + _error = wrapErrorEventListener(error, unsubscribe); + _complete = wrapSnapshotEventListener(task, complete, unsubscribe); + } else { + throw new Error( + "firebase.storage.StorageTask.on(*, _) 'nextOrObserver' must be a Function, an Object or Null.", + ); + } + + if (_next) { + _nextSubscription = addTaskEventListener(task, TaskEvent.STATE_CHANGED, _next); + } + + if (_error) { + _errorSubscription = addTaskEventListener(task, 'failure', _error); + } + + if (_complete) { + _completeSubscription = addTaskEventListener(task, 'success', _complete); + } + + return unsubscribe; +} + +export default class StorageTask { + declare _type: string; + declare _id: number; + declare _promise: Promise | null; + declare _ref: Reference; + declare _beginTask: (task: StorageTask) => Promise; + declare _storage: any; // Will be properly typed when Storage module is converted + declare _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 as any)._storage; + this._snapshot = null; + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask#then + */ + get then(): ( + onFulfilled?: ((snapshot: TaskSnapshot) => any) | null, + onRejected?: ((error: Error) => any) | null, + ) => Promise { + if (!this._promise) { + this._promise = this._beginTask(this); + } + + 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); + } + }); + }); + }) as any; + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask#catch + */ + get catch(): (onRejected: (error: Error) => any) => Promise { + if (!this._promise) { + this._promise = this._beginTask(this); + } + return this._promise!.catch.bind(this._promise); + } + + get snapshot(): TaskSnapshot | null { + return this._snapshot; + } + + // // NOT on Web SDK + // /** + // * @url https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask#finally + // */ + // get finally() { + // if (!this._promise) this._promise = this._beginTask(this); + // return this._promise.finally.bind(this._promise); + // } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask#on + */ + on( + event: string, + nextOrObserver?: + | ((snapshot: TaskSnapshot) => void) + | { + next?: (snapshot: TaskSnapshot) => void; + error?: (error: Error) => void; + complete?: () => void; + } + | null, + 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}'`, + ); + } + + if (!this._promise) { + this._promise = this._beginTask(this); + } + + // if only event provided return the subscriber function + if (!nextOrObserver && !error && !complete) { + return ((...args: any[]) => subscribeToEvents(this, ...args)) as () => void; + } + + return subscribeToEvents(this, nextOrObserver, error, complete); + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask#pause + */ + pause(): Promise { + return this._storage.native.setTaskStatus(this._id, 0); + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask#pause + */ + resume(): Promise { + return this._storage.native.setTaskStatus(this._id, 1); + } + + /** + * @url https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask#cancel + */ + cancel(): Promise { + return this._storage.native.setTaskStatus(this._id, 2); + } +} diff --git a/packages/storage/lib/StorageUploadTask.ts b/packages/storage/lib/StorageUploadTask.ts new file mode 100644 index 0000000000..afdc50d06d --- /dev/null +++ b/packages/storage/lib/StorageUploadTask.ts @@ -0,0 +1,27 @@ +/* + * 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 StorageTask from './StorageTask'; +import type { Reference } from './types/storage'; + +const UPLOAD_TASK = 'upload'; + +export default class StorageUploadTask extends StorageTask { + constructor(storageRef: Reference, beginTaskFn: (task: StorageTask) => Promise) { + super(UPLOAD_TASK, storageRef, beginTaskFn); + } +} diff --git a/packages/storage/lib/modular/index.js b/packages/storage/lib/modular/index.js deleted file mode 100644 index 99c5d0716d..0000000000 --- a/packages/storage/lib/modular/index.js +++ /dev/null @@ -1,284 +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. - * - */ -/** - * @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/lib/common'; - -/** - * Returns a Storage instance for the given app. - * @param app - FirebaseApp. Optional. - * @param bucketUrl - Storage bucket URL. Optional. - * @returns {Storage} - */ -export function getStorage(app, bucketUrl) { - if (app) { - if (bucketUrl != null) { - return getApp(app.name).storage(bucketUrl); - } - - return getApp(app.name).storage(); - } - - if (bucketUrl != null) { - return getApp().storage(bucketUrl); - } - - return getApp().storage(); -} - -/** - * Modify this Storage instance to communicate with the Firebase Storage emulator. - * @param storage - Storage instance. - * @param host - emulator host (e.g. - 'localhost') - * @param port - emulator port (e.g. - 9199) - * @param options - `EmulatorMockTokenOptions` instance. Optional. Web only. - * @returns {void} - */ -export function connectStorageEmulator(storage, host, port, options) { - return storage.useEmulator.call(storage, host, port, options, MODULAR_DEPRECATION_ARG); -} - -/** - * Modify this Storage instance to communicate with the Firebase Storage emulator. - * @param storage - Storage instance. - * @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. Optional. - * @returns {Reference} - */ -export function ref(storage, path) { - return storage.ref.call(storage, path, MODULAR_DEPRECATION_ARG); -} - -/** - * Deletes the object at this reference's location. - * @param storageRef - Storage `Reference` instance. - * @returns {Promise} - */ -export function deleteObject(storageRef) { - return storageRef.delete.call(storageRef, MODULAR_DEPRECATION_ARG); -} - -/** - * Downloads the data at the object's location. Returns an error if the object is not found. - * @param storageRef - Storage `Reference` instance. - * @returns {Promise} - */ -// eslint-disable-next-line -export function getBlob(storageRef, maxDownloadSizeBytes) { - throw new Error('`getBlob()` is not implemented'); -} - -/** - * Downloads the data at the object's location. Returns an error if the object is not found. - * @param storageRef - Storage `Reference` instance. - * @param maxDownloadSizeBytes - The maximum allowed size in bytes to retrieve. Web only. - * @returns {Promise} - */ -// eslint-disable-next-line -export function getBytes(storageRef, maxDownloadSizeBytes) { - throw new Error('`getBytes()` is not implemented'); -} - -/** - * Deletes the object at this reference's location. - * @param storageRef - Storage `Reference` instance. - * @returns {Promise} - */ -export function getDownloadURL(storageRef) { - return storageRef.getDownloadURL.call(storageRef, MODULAR_DEPRECATION_ARG); -} - -/** - * Fetches metadata for the object at this location, if one exists. - * @param storageRef - Storage `Reference` instance. - * @returns {Promise} - */ -export function getMetadata(storageRef) { - return storageRef.getMetadata.call(storageRef, MODULAR_DEPRECATION_ARG); -} - -/** - * Downloads the data at the object's location. This API is only available in Nodejs. - * @param storageRef - Storage `Reference` instance. - * @param maxDownloadSizeBytes - The maximum allowed size in bytes to retrieve. Web only. - * @returns {NodeJS.ReadableStream;} - */ -// eslint-disable-next-line -export function getStream(storageRef, maxDownloadSizeBytes) { - throw new Error('`getStream()` is not implemented'); -} - -/** - * List items (files) and prefixes (folders) under this storage reference - * @param storageRef - Storage `Reference` instance. - * @param options - Storage `ListOptions` instance. The options list() accepts. - * @returns {Promise} - */ -export function list(storageRef, options) { - return storageRef.list.call(storageRef, options, MODULAR_DEPRECATION_ARG); -} - -/** - * List all items (files) and prefixes (folders) under this storage reference. - * @param storageRef - Storage `Reference` instance. - * @returns {Promise} - */ -export function listAll(storageRef) { - return storageRef.listAll.call(storageRef, MODULAR_DEPRECATION_ARG); -} - -/** - * Updates the metadata for this object. - * @param storageRef - Storage `Reference` instance. - * @param metadata - A Storage `SettableMetadata` instance to update. - * @returns {Promise} - */ -export function updateMetadata(storageRef, metadata) { - return storageRef.updateMetadata.call(storageRef, metadata, MODULAR_DEPRECATION_ARG); -} - -/** - * Uploads data to this object's location. The upload is not resumable. - * @param storageRef - Storage `Reference` instance. - * @param data - The data (Blob | Uint8Array | ArrayBuffer) to upload to the storage bucket at the reference location. - * @param metadata - A Storage `SettableMetadata` instance to update. Optional. - * @returns {Promise} - */ -// eslint-disable-next-line -export async function uploadBytes(storageRef, data, metadata) { - throw new Error('`uploadBytes()` is not implemented'); -} - -/** - * Uploads data to this object's location. The upload is not resumable. - * @param storageRef - Storage `Reference` instance. - * @param data - The data (Blob | Uint8Array | ArrayBuffer) to upload to the storage bucket at the reference location. - * @param metadata - A Storage `SettableMetadata` instance to update. Optional. - * @returns {Task} - */ -export function uploadBytesResumable(storageRef, data, metadata) { - return storageRef.put.call(storageRef, data, metadata, MODULAR_DEPRECATION_ARG); -} - -/** - * Uploads data to this object's location. The upload is not resumable. - * @param storageRef - Storage `Reference` instance. - * @param value - The string to upload. - * @param format - The format of the string to upload ('raw' | 'base64' | 'base64url' | 'data_url'). Optional. - * @param metadata - A Storage `SettableMetadata` instance to update. Optional. - * @returns {Task} - */ -export function uploadString(storageRef, data, format, metadata) { - return storageRef.putString.call(storageRef, data, format, metadata, MODULAR_DEPRECATION_ARG); -} - -// Methods not on the Firebase JS SDK below - -/** - * Returns a new Storage `Reference` instance from a storage bucket URL. - * @param storage - Storage instance. - * @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) { - return storage.refFromURL.call(storage, url, MODULAR_DEPRECATION_ARG); -} - -/** - * Sets the maximum time in milliseconds to retry a download if a failure occurs.. android & iOS only. - * @param storage - Storage instance. - * @param time - The new maximum operation retry time in milliseconds. - * @returns {Promise} - */ -export function setMaxOperationRetryTime(storage, time) { - return storage.setMaxOperationRetryTime.call(storage, time, MODULAR_DEPRECATION_ARG); -} - -/** - * Sets the maximum time in milliseconds to retry an upload if a failure occurs. android & iOS only. - * @param storage - Storage instance. - * @param time - The new maximum operation retry time in milliseconds. - * @returns {Promise} - */ -export function setMaxUploadRetryTime(storage, time) { - return storage.setMaxUploadRetryTime.call(storage, time, MODULAR_DEPRECATION_ARG); -} - -/** - * Puts a file from local disk onto the storage bucket. - * @param storageRef - Storage Reference instance. - * @param localFilePath The local file path to upload to the bucket at the reference location. - * @param metadata Any additional `SettableMetadata` for this task. - * @returns {Task} - */ -export function putFile(storageRef, filePath, metadata) { - return storageRef.putFile.call(storageRef, filePath, metadata, MODULAR_DEPRECATION_ARG); -} - -/** - * Downloads a file to the specified local file path on the device. - * @param storageRef - Storage Reference instance. - * @param localFilePath The local file path to upload to on the device. - * @returns {Task} - */ -export function writeToFile(storageRef, filePath) { - return storageRef.writeToFile.call(storageRef, filePath, MODULAR_DEPRECATION_ARG); -} - -/** - * Returns a gs:// URL for this object in the form `gs://///`. - * @param storageRef - Storage Reference instance. - * @returns {String} - */ -export function toString(storageRef) { - return storageRef.toString.call(storageRef, MODULAR_DEPRECATION_ARG); -} - -/** - * Returns a reference to a relative path from this reference. - * @param storageRef - Storage Reference instance. - * @param path - The relative path from this reference. Leading, trailing, and consecutive slashes are removed. - * @returns {String} - */ -export function child(storageRef, path) { - return storageRef.child.call(storageRef, path, MODULAR_DEPRECATION_ARG); -} - -/** - * Sets the maximum time in milliseconds to retry a download if a failure occurs. - * @param storage - Storage instance. - * @param time - The new maximum download retry time in milliseconds. - * @returns {Promise} - */ -export function setMaxDownloadRetryTime(storage, time) { - return storage.setMaxDownloadRetryTime.call(storage, time, MODULAR_DEPRECATION_ARG); -} - -export { StringFormat, TaskEvent, TaskState } from '../StorageStatics'; diff --git a/packages/storage/lib/utils.ts b/packages/storage/lib/utils.ts new file mode 100644 index 0000000000..b9ad5b2568 --- /dev/null +++ b/packages/storage/lib/utils.ts @@ -0,0 +1,109 @@ +/* + * 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 { isNull, isObject, isString } from '@react-native-firebase/app/lib/common'; +import { NativeFirebaseError } from '@react-native-firebase/app/lib/internal'; +import type { SettableMetadata } from './types/storage'; + +const SETTABLE_FIELDS = [ + 'cacheControl', + 'contentDisposition', + 'contentEncoding', + 'contentLanguage', + 'contentType', + 'customMetadata', + 'md5hash', +] as const; + +export async function handleStorageEvent( + storageInstance: any, + event: { + taskId: string; + eventName: string; + body?: { error?: any }; + }, +): Promise { + const { taskId, eventName } = event; + const body = event.body || {}; + + if (body.error) { + body.error = await NativeFirebaseError.fromEvent(body.error, storageInstance._config.namespace); + } + + storageInstance.emitter.emit(storageInstance.eventNameForApp(taskId, eventName), body); +} + +export function getHttpUrlParts(url: string): { bucket: string; path: string } | null { + const decoded = decodeURIComponent(url); + const parts = decoded.match(/\/b\/(.*)\/o\/([a-zA-Z0-9./\-_]+)(.*)/); + + if (!parts || parts.length < 3) { + return null; + } + + return { bucket: `gs://${parts[1]}`, path: parts[2]! }; +} + +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) : '/') || '/'; + + return { bucket, path }; +} + +export function validateMetadata(metadata: any, update = true): SettableMetadata { + if (!isObject(metadata)) { + throw new Error('firebase.storage.SettableMetadata must be an object value if provided.'); + } + + const metadataEntries = Object.entries(metadata); + + for (let i = 0; i < metadataEntries.length; i++) { + const entry = metadataEntries[i]; + if (!entry) continue; + const [key, value] = entry; + // validate keys + if (!SETTABLE_FIELDS.includes(key as (typeof SETTABLE_FIELDS)[number])) { + throw new Error( + `firebase.storage.SettableMetadata unknown property '${key}' provided for metadata.`, + ); + } + + // md5 is only allowed on put, not on update + if (key === 'md5hash' && update === true) { + throw new Error( + `firebase.storage.SettableMetadata md5hash may only be set on upload, not on updateMetadata`, + ); + } + + // validate values + if (key !== 'customMetadata') { + if (!isString(value) && !isNull(value)) { + throw new Error( + `firebase.storage.SettableMetadata invalid property '${key}' should be a string or null value.`, + ); + } + } else if (!isObject(value) && !isNull(value)) { + throw new Error( + 'firebase.storage.SettableMetadata.customMetadata must be an object of keys and string values or null value.', + ); + } + } + + return metadata as SettableMetadata; +} From 65b4e231abe5b5af13a71bd8d527dae3e90b59e3 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Thu, 8 Jan 2026 16:45:31 +0000 Subject: [PATCH 08/36] update web implementation --- packages/storage/lib/namespaced.ts | 1 - .../lib/web/RNFBStorageModule.android.js | 2 - .../lib/web/RNFBStorageModule.android.ts | 3 + .../storage/lib/web/RNFBStorageModule.ios.js | 2 - .../storage/lib/web/RNFBStorageModule.ios.ts | 3 + ...BStorageModule.js => RNFBStorageModule.ts} | 229 ++++++++++++------ 6 files changed, 158 insertions(+), 82 deletions(-) delete mode 100644 packages/storage/lib/web/RNFBStorageModule.android.js create mode 100644 packages/storage/lib/web/RNFBStorageModule.android.ts delete mode 100644 packages/storage/lib/web/RNFBStorageModule.ios.js create mode 100644 packages/storage/lib/web/RNFBStorageModule.ios.ts rename packages/storage/lib/web/{RNFBStorageModule.js => RNFBStorageModule.ts} (65%) diff --git a/packages/storage/lib/namespaced.ts b/packages/storage/lib/namespaced.ts index 90a1c57d7c..4674133c58 100644 --- a/packages/storage/lib/namespaced.ts +++ b/packages/storage/lib/namespaced.ts @@ -34,7 +34,6 @@ import StorageReference from './StorageReference'; import { StringFormat, TaskEvent, TaskState } from './StorageStatics'; import { getGsUrlParts, getHttpUrlParts, handleStorageEvent } from './utils'; import { version } from './version'; -// @ts-ignore - Web fallback module import fallBackModule from './web/RNFBStorageModule'; import type { Storage, StorageStatics, Reference, EmulatorMockTokenOptions } from './types/storage'; 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 65% rename from packages/storage/lib/web/RNFBStorageModule.js rename to packages/storage/lib/web/RNFBStorageModule.ts index bcc575995f..7cbf36bde1 100644 --- a/packages/storage/lib/web/RNFBStorageModule.js +++ b/packages/storage/lib/web/RNFBStorageModule.ts @@ -15,17 +15,59 @@ import { import { guard, getWebError, emitEvent } from '@react-native-firebase/app/lib/internal/web/utils'; import { Base64 } from '@react-native-firebase/app/lib/common'; -function rejectWithCodeAndMessage(code, message) { - return Promise.reject( - getWebError({ - code, - message, - }), - ); +interface EmulatorConfig { + host: string; + port: number; } -function metadataToObject(metadata) { - const out = { +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; +} + +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): Metadata & { metadata?: Record } { + const out: Metadata & { metadata?: Record } = { bucket: metadata.bucket, generation: metadata.generation, metageneration: metadata.metageneration, @@ -66,7 +108,7 @@ function metadataToObject(metadata) { return out; } -function uploadTaskErrorToObject(error, snapshot) { +function uploadTaskErrorToObject(error: any, snapshot: any): UploadTaskSnapshot & { error: any } { return { ...uploadTaskSnapshotToObject(snapshot), state: 'error', @@ -74,7 +116,7 @@ function uploadTaskErrorToObject(error, snapshot) { }; } -function uploadTaskSnapshotToObject(snapshot) { +function uploadTaskSnapshotToObject(snapshot: any): UploadTaskSnapshot { return { totalBytes: snapshot ? snapshot.totalBytes : 0, bytesTransferred: snapshot ? snapshot.bytesTransferred : 0, @@ -83,19 +125,19 @@ function uploadTaskSnapshotToObject(snapshot) { }; } -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, @@ -106,7 +148,7 @@ function makeSettableMetadata(metadata) { }; } -function listResultToObject(result) { +function listResultToObject(result: { nextPageToken?: string; items: any[]; prefixes: any[] }) { return { nextPageToken: result.nextPageToken, items: result.items.map(ref => ref.fullPath), @@ -114,37 +156,37 @@ function listResultToObject(result) { }; } -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): any { 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; +function getCachedStorageInstance(appName: string, url?: string | null): any { + let instance: any; const bucket = url ? getBucketFromUrl(url) : getCachedAppInstance(appName).options.storageBucket; if (!url) { @@ -167,13 +209,17 @@ function getCachedStorageInstance(appName, url) { } // Returns a Storage Reference. -function getReferenceFromUrl(appName, url) { +function getReferenceFromUrl(appName: string, url: string): any { 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) { @@ -187,11 +233,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); @@ -200,11 +246,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); @@ -214,11 +260,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); @@ -228,12 +277,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); @@ -243,11 +300,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); @@ -257,11 +321,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)); @@ -269,7 +337,7 @@ export default { }); }, - setMaxDownloadRetryTime() { + setMaxDownloadRetryTime(): void { if (__DEV__) { // eslint-disable-next-line no-console console.warn( @@ -281,11 +349,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; @@ -294,11 +362,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; @@ -307,12 +375,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); @@ -321,7 +389,7 @@ export default { }); }, - writeToFile() { + writeToFile(): Promise { return rejectWithCodeAndMessage( 'unsupported', 'This operation is not supported in this environment.', @@ -330,18 +398,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) { @@ -353,7 +428,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), @@ -363,7 +438,7 @@ 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 => { @@ -419,7 +494,7 @@ export default { }); }, - putFile() { + putFile(): Promise { return rejectWithCodeAndMessage( 'unsupported', 'This operation is not supported in this environment.', @@ -428,12 +503,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 @@ -461,7 +536,7 @@ export default { } emitEvent('storage_event', { - data: buildUploadSnapshotMap(task.snapshot), + data: uploadTaskSnapshotToObject(task.snapshot), appName, taskId, }); From db33fe1db8702ef8464a1e9173dd53b0001ec83c Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Thu, 8 Jan 2026 16:46:51 +0000 Subject: [PATCH 09/36] type-test --- packages/storage/type-test.ts | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/storage/type-test.ts b/packages/storage/type-test.ts index ff05532e3c..1684843962 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,31 @@ 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 legacyStatics: FirebaseStorageTypes.Statics = storage; +console.log(legacyStatics.SDK_VERSION); +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); From e225e38e988e9017b46a14665c78fb751a766afc Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Thu, 8 Jan 2026 16:48:01 +0000 Subject: [PATCH 10/36] remove type declarations from tsconfig root --- tsconfig.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index 0658ab77a9..bcbefe4fe0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,16 +1,12 @@ { "include": [ "packages/ai/lib/types/polyfills.d.ts", - "packages/app-check/lib/index.d.ts", - "packages/app-check/lib/modular/index.d.ts", "packages/app-distribution/lib/index.d.ts", "packages/app-distribution/lib/modular/index.d.ts", "packages/app/lib/internal/global.d.ts", "packages/app/lib/internal/web/memidb/index.d.ts", "packages/auth/lib/index.d.ts", "packages/auth/lib/modular/index.d.ts", - "packages/crashlytics/lib/index.d.ts", - "packages/crashlytics/lib/modular/index.d.ts", "packages/database/lib/index.d.ts", "packages/database/lib/modular/index.d.ts", "packages/database/lib/modular/query.d.ts", @@ -35,8 +31,6 @@ "packages/perf/lib/modular/index.d.ts", "packages/remote-config/lib/index.d.ts", "packages/remote-config/lib/modular/index.d.ts", - "packages/storage/lib/index.d.ts", - "packages/storage/lib/modular/index.d.ts", "packages/functions/type-test.ts", "packages/app/type-test.ts", "packages/app-check/type-test.ts", From aa82bda14fa290647fc5435b017ec14830bbcc02 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Thu, 8 Jan 2026 16:49:58 +0000 Subject: [PATCH 11/36] remove js files --- packages/storage/lib/StorageListResult.js | 42 --- packages/storage/lib/StorageReference.js | 313 ---------------------- packages/storage/lib/StorageStatics.js | 33 --- packages/storage/lib/StorageTask.js | 232 ---------------- packages/storage/lib/StorageUploadTask.js | 26 -- packages/storage/lib/index.js | 222 --------------- packages/storage/lib/utils.js | 99 ------- 7 files changed, 967 deletions(-) delete mode 100644 packages/storage/lib/StorageListResult.js delete mode 100644 packages/storage/lib/StorageReference.js delete mode 100644 packages/storage/lib/StorageStatics.js delete mode 100644 packages/storage/lib/StorageTask.js delete mode 100644 packages/storage/lib/StorageUploadTask.js delete mode 100644 packages/storage/lib/index.js delete mode 100644 packages/storage/lib/utils.js diff --git a/packages/storage/lib/StorageListResult.js b/packages/storage/lib/StorageListResult.js deleted file mode 100644 index c0646e7657..0000000000 --- a/packages/storage/lib/StorageListResult.js +++ /dev/null @@ -1,42 +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. - * - */ - -// To avoid React Native require cycle warnings -let StorageReference = null; -export function provideStorageReferenceClass(storageReference) { - StorageReference = storageReference; -} - -export default class StorageListResult { - constructor(storage, nativeData) { - 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)); - } - - get items() { - return this._items; - } - - get nextPageToken() { - return this._nextPageToken; - } - - get prefixes() { - return this._prefixes; - } -} diff --git a/packages/storage/lib/StorageReference.js b/packages/storage/lib/StorageReference.js deleted file mode 100644 index ffd440aec2..0000000000 --- a/packages/storage/lib/StorageReference.js +++ /dev/null @@ -1,313 +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 { - Base64, - getDataUrlParts, - hasOwnProperty, - isInteger, - isNumber, - isObject, - isString, - isUndefined, - pathChild, - pathLastComponent, - pathParent, - ReferenceBase, - toFilePath, -} from '@react-native-firebase/app/lib/common'; -import StorageDownloadTask from './StorageDownloadTask'; -import StorageListResult, { provideStorageReferenceClass } from './StorageListResult'; -import { StringFormat } from './StorageStatics'; -import StorageUploadTask from './StorageUploadTask'; -import { validateMetadata } from './utils'; - -export default class StorageReference extends ReferenceBase { - constructor(storage, path) { - super(path); - this._storage = storage; - } - - /** - * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#bucket - */ - get bucket() { - return this._storage._customUrlOrRegion.replace('gs://', ''); - } - - /** - * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#fullPath - */ - get fullPath() { - return this.path; - } - - /** - * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#name - */ - get name() { - return pathLastComponent(this.path); - } - - /** - * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#parent - */ - get parent() { - const parentPath = pathParent(this.path); - if (parentPath === null) { - return parentPath; - } - return new StorageReference(this._storage, parentPath); - } - - /** - * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#root - */ - get root() { - return new StorageReference(this._storage, '/'); - } - - /** - * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#storage - */ - get storage() { - return this._storage; - } - - /** - * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#child - */ - child(path) { - const childPath = pathChild(this.path, path); - return new StorageReference(this._storage, childPath); - } - - /** - * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#delete - */ - delete() { - return this._storage.native.delete(this.toString()); - } - - /** - * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#getDownloadURL - */ - getDownloadURL() { - return this._storage.native.getDownloadURL(this.toString()); - } - - /** - * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#getMetadata - */ - getMetadata() { - return this._storage.native.getMetadata(this.toString()); - } - - /** - * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#list - */ - list(options) { - if (!isUndefined(options) && !isObject(options)) { - throw new Error( - "firebase.storage.StorageReference.list(*) 'options' expected an object value.", - ); - } - - const listOptions = { - maxResults: 1000, - }; - - if (options) { - if (hasOwnProperty(options, 'maxResults')) { - if (!isNumber(options.maxResults) || !isInteger(options.maxResults)) { - throw new Error( - "firebase.storage.StorageReference.list(*) 'options.maxResults' expected a number value.", - ); - } - - if (options.maxResults < 1 || options.maxResults > 1000) { - throw new Error( - "firebase.storage.StorageReference.list(*) 'options.maxResults' expected a number value between 1-1000.", - ); - } - - listOptions.maxResults = options.maxResults; - } - - if (options.pageToken) { - if (!isString(options.pageToken)) { - throw new Error( - "firebase.storage.StorageReference.list(*) 'options.pageToken' expected a string value.", - ); - } - - listOptions.pageToken = options.pageToken; - } - } - - return this._storage.native - .list(this.toString(), listOptions) - .then(data => new StorageListResult(this._storage, data)); - } - - /** - * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#listAll - */ - listAll() { - return this._storage.native - .listAll(this.toString()) - .then(data => new StorageListResult(this._storage, data)); - } - - /** - * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#put - */ - put(data, metadata) { - 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); - return this._storage.native.putString( - this.toString(), - _string, - _format, - _metadata, - task._id, - ); - }), - ); - } - - /** - * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#putString - */ - putString(string, format = StringFormat.RAW, metadata) { - const { _string, _format, _metadata } = this._updateString(string, format, metadata, false); - - return new StorageUploadTask(this, task => - this._storage.native.putString(this.toString(), _string, _format, _metadata, task._id), - ); - } - - /** - * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#fullPath - */ - toString() { - if (this.path.length <= 1) { - return `${this._storage._customUrlOrRegion}`; - } - - return `${this._storage._customUrlOrRegion}/${this.path}`; - } - - /** - * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#updateMetadata - */ - updateMetadata(metadata) { - validateMetadata(metadata); - return this._storage.native.updateMetadata(this.toString(), metadata); - } - - /* ---------------------------------------- - * EXTRA APIS (DO NOT ON EXIST WEB SDK) - * ---------------------------------------- */ - - /** - * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference - */ - writeToFile(filePath) { - if (!isString(filePath)) { - throw new Error( - "firebase.storage.StorageReference.writeToFile(*) 'filePath' expects a string value.", - ); - } - - return new StorageDownloadTask(this, task => - this._storage.native.writeToFile(this.toString(), toFilePath(filePath), task._id), - ); - } - - /** - * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference - */ - putFile(filePath, metadata) { - if (!isUndefined(metadata)) { - validateMetadata(metadata, false); - } - - if (!isString(filePath)) { - throw new Error( - "firebase.storage.StorageReference.putFile(*, _) 'filePath' expects a string value.", - ); - } - - return new StorageUploadTask(this, task => - this._storage.native.putFile(this.toString(), toFilePath(filePath), metadata, task._id), - ); - } - - _updateString(string, format, metadata, update = false) { - if (!isString(string)) { - throw new Error( - "firebase.storage.StorageReference.putString(*, _, _) 'string' expects a string value.", - ); - } - - if (!Object.values(StringFormat).includes(format)) { - throw new Error( - `firebase.storage.StorageReference.putString(_, *, _) 'format' provided is invalid, must be one of ${Object.values( - StringFormat, - ).join(',')}.`, - ); - } - - if (!isUndefined(metadata)) { - validateMetadata(metadata, update); - } - - let _string = string; - let _format = format; - let _metadata = metadata; - - if (format === StringFormat.RAW) { - _string = Base64.btoa(_string); - _format = StringFormat.BASE64; - } else if (format === StringFormat.DATA_URL) { - const { mediaType, base64String } = getDataUrlParts(_string); - if (isUndefined(base64String)) { - throw new Error( - 'firebase.storage.StorageReference.putString(*, _, _) invalid data_url string provided.', - ); - } - - if (isUndefined(metadata) || isUndefined(metadata.contentType)) { - if (isUndefined(metadata)) { - _metadata = {}; - } - _metadata.contentType = mediaType; - _string = base64String; - _format = StringFormat.BASE64; - } - } - return { _string, _metadata, _format }; - } -} - -provideStorageReferenceClass(StorageReference); diff --git a/packages/storage/lib/StorageStatics.js b/packages/storage/lib/StorageStatics.js deleted file mode 100644 index bd94f2d1e8..0000000000 --- a/packages/storage/lib/StorageStatics.js +++ /dev/null @@ -1,33 +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. - * - */ - -export const StringFormat = { - RAW: 'raw', - BASE64: 'base64', - BASE64URL: 'base64url', - DATA_URL: 'data_url', -}; -export const TaskEvent = { - STATE_CHANGED: 'state_changed', -}; -export const TaskState = { - RUNNING: 'running', - PAUSED: 'paused', - SUCCESS: 'success', - CANCELLED: 'cancelled', - ERROR: 'error', -}; diff --git a/packages/storage/lib/StorageTask.js b/packages/storage/lib/StorageTask.js deleted file mode 100644 index c744a06fd5..0000000000 --- a/packages/storage/lib/StorageTask.js +++ /dev/null @@ -1,232 +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 { isFunction, isNull, isObject } from '@react-native-firebase/app/lib/common'; -import { TaskEvent } from './StorageStatics'; - -let TASK_ID = 0; - -function wrapErrorEventListener(listenerFn, unsubscribe) { - return event => { - if (unsubscribe) { - setTimeout(() => unsubscribe(), 0); - } // 1 frame = 16ms, pushing to next frame - if (isFunction(listenerFn)) { - listenerFn(event.error); - } - }; -} - -function wrapSnapshotEventListener(task, listenerFn, unsubscribe) { - if (!isFunction(listenerFn)) { - return null; - } - return event => { - 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; - - if (snapshot.metadata) { - if (!snapshot.metadata.generation) { - snapshot.metadata.generation = ''; - } - if (!snapshot.metadata.bucket) { - snapshot.metadata.bucket = task._ref.bucket; - } - if (!snapshot.metadata.metageneration) { - snapshot.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; - } - - Object.freeze(snapshot); - task._snapshot = snapshot; - - listenerFn(snapshot); - } - }; -} - -function addTaskEventListener(task, eventName, listener) { - let _eventName = eventName; - if (_eventName !== TaskEvent.STATE_CHANGED) { - _eventName = `${task._type}_${eventName}`; - } - - return task._storage.emitter.addListener( - task._storage.eventNameForApp(task._id, _eventName), - listener, - ); -} - -function subscribeToEvents(task, nextOrObserver, error, complete) { - let _error; - let _errorSubscription; - - let _next; - let _nextSubscription; - - let _complete; - let _completeSubscription; - - const unsubscribe = () => { - if (_nextSubscription) { - _nextSubscription.remove(); - } - if (_errorSubscription) { - _errorSubscription.remove(); - } - if (_completeSubscription) { - _completeSubscription.remove(); - } - }; - - if (isFunction(nextOrObserver)) { - _error = wrapErrorEventListener(error, unsubscribe); - _next = wrapSnapshotEventListener(task, nextOrObserver); - _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); - } else if (isNull(nextOrObserver)) { - _error = wrapErrorEventListener(error, unsubscribe); - _complete = wrapSnapshotEventListener(task, complete, unsubscribe); - } else { - throw new Error( - "firebase.storage.StorageTask.on(*, _) 'nextOrObserver' must be a Function, an Object or Null.", - ); - } - - if (_next) { - _nextSubscription = addTaskEventListener(task, TaskEvent.STATE_CHANGED, _next); - } - - if (_error) { - _errorSubscription = addTaskEventListener(task, 'failure', _error); - } - - if (_complete) { - _completeSubscription = addTaskEventListener(task, 'success', _complete); - } - - return unsubscribe; -} - -export default class StorageTask { - constructor(type, storageRef, beginTaskFn) { - this._type = type; - this._id = TASK_ID++; - this._promise = null; - this._ref = storageRef; - this._beginTask = beginTaskFn; - this._storage = storageRef._storage; - this._snapshot = null; - } - - /** - * @url https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask#then - */ - get then() { - 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); - }); - }).then.bind(this._promise); - } - - /** - * @url https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask#catch - */ - get catch() { - if (!this._promise) { - this._promise = this._beginTask(this); - } - return this._promise.catch.bind(this._promise); - } - - get snapshot() { - return this._snapshot; - } - - // // NOT on Web SDK - // /** - // * @url https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask#finally - // */ - // get finally() { - // if (!this._promise) this._promise = this._beginTask(this); - // return this._promise.finally.bind(this._promise); - // } - - /** - * @url https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask#on - */ - on(event, nextOrObserver, error, complete) { - if (event !== TaskEvent.STATE_CHANGED) { - throw new Error( - `firebase.storage.StorageTask.on event argument must be a string with a value of '${TaskEvent.STATE_CHANGED}'`, - ); - } - - if (!this._promise) { - this._promise = this._beginTask(this); - } - - // if only event provided return the subscriber function - if (!nextOrObserver && !error && !complete) { - return subscribeToEvents.bind(null, this); - } - - return subscribeToEvents(this, nextOrObserver, error, complete); - } - - /** - * @url https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask#pause - */ - pause() { - return this._storage.native.setTaskStatus(this._id, 0); - } - - /** - * @url https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask#resume - */ - resume() { - return this._storage.native.setTaskStatus(this._id, 1); - } - - /** - * @url https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask#cancel - */ - cancel() { - return this._storage.native.setTaskStatus(this._id, 2); - } -} diff --git a/packages/storage/lib/StorageUploadTask.js b/packages/storage/lib/StorageUploadTask.js deleted file mode 100644 index b9fed92741..0000000000 --- a/packages/storage/lib/StorageUploadTask.js +++ /dev/null @@ -1,26 +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 StorageTask from './StorageTask'; - -const UPLOAD_TASK = 'upload'; - -export default class StorageUploadTask extends StorageTask { - constructor(storageRef, beginTaskFn) { - super(UPLOAD_TASK, storageRef, beginTaskFn); - } -} diff --git a/packages/storage/lib/index.js b/packages/storage/lib/index.js deleted file mode 100644 index 37e18b44e9..0000000000 --- a/packages/storage/lib/index.js +++ /dev/null @@ -1,222 +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 { - isAndroid, - isNumber, - isString, - createDeprecationProxy, -} from '@react-native-firebase/app/lib/common'; - -import { setReactNativeModule } from '@react-native-firebase/app/lib/internal/nativeModule'; -import { - createModuleNamespace, - FirebaseModule, - getFirebaseRoot, -} from '@react-native-firebase/app/lib/internal'; -import StorageReference from './StorageReference'; -import { StringFormat, TaskEvent, TaskState } from './StorageStatics'; -import { getGsUrlParts, getHttpUrlParts, handleStorageEvent } from './utils'; -import version from './version'; -import fallBackModule from './web/RNFBStorageModule'; - -// import { STATICS } from '@react-native-firebase/storage'; -const statics = { - StringFormat, - TaskEvent, - TaskState, -}; - -const namespace = 'storage'; -const nativeEvents = ['storage_event']; -const nativeModuleName = 'RNFBStorageModule'; - -class FirebaseStorageModule extends FirebaseModule { - constructor(app, config, bucketUrl) { - super(app, config, bucketUrl); - if (bucketUrl === undefined) { - this._customUrlOrRegion = `gs://${app.options.storageBucket}`; - } else if (!isString(bucketUrl) || !bucketUrl.startsWith('gs://')) { - throw new Error( - "firebase.app().storage(*) bucket url must be a string and begin with 'gs://'", - ); - } - - this.emitter.addListener( - this.eventNameForApp(nativeEvents[0]), - handleStorageEvent.bind(null, this), - ); - - // Emulator instance vars needed to send through on iOS, iOS does not persist emulator state between calls - this.emulatorHost = undefined; - this.emulatorPort = 0; - this._maxUploadRetryTime = this.native.maxUploadRetryTime || 0; - this._maxDownloadRetryTime = this.native.maxDownloadRetryTime || 0; - this._maxOperationRetryTime = this.native.maxOperationRetryTime || 0; - } - - /** - * @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setmaxuploadretrytime - */ - get maxUploadRetryTime() { - return this._maxUploadRetryTime; - } - - /** - * @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setmaxdownloadretrytime - */ - get maxDownloadRetryTime() { - return this._maxDownloadRetryTime; - } - - /** - * @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#maxoperationretrytime - */ - get maxOperationRetryTime() { - return this._maxOperationRetryTime; - } - - /** - * @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#ref - */ - ref(path = '/') { - if (!isString(path)) { - throw new Error("firebase.storage().ref(*) 'path' must be a string value."); - } - return createDeprecationProxy(new StorageReference(this, path)); - } - - /** - * @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#refFromURL - */ - refFromURL(url) { - 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; - - if (url.startsWith('http')) { - const parts = getHttpUrlParts(url); - if (!parts) { - throw new Error( - "firebase.storage().refFromURL(*) unable to parse 'url', ensure it's a valid storage url'.", - ); - } - ({ bucket, path } = parts); - } else { - ({ bucket, path } = getGsUrlParts(url)); - } - - const storageInstance = this.app.storage(bucket); - return new StorageReference(storageInstance, path); - } - - /** - * @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setMaxOperationRetryTime - */ - setMaxOperationRetryTime(time) { - if (!isNumber(time)) { - throw new Error( - "firebase.storage().setMaxOperationRetryTime(*) 'time' must be a number value.", - ); - } - - this._maxOperationRetryTime = time; - return this.native.setMaxOperationRetryTime(time); - } - - /** - * @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setMaxUploadRetryTime - */ - setMaxUploadRetryTime(time) { - if (!isNumber(time)) { - throw new Error("firebase.storage().setMaxUploadRetryTime(*) 'time' must be a number value."); - } - - this._maxUploadRetryTime = time; - return this.native.setMaxUploadRetryTime(time); - } - - /** - * @url https://firebase.google.com/docs/reference/js/firebase.storage.Storage#setMaxDownloadRetryTime - */ - setMaxDownloadRetryTime(time) { - if (!isNumber(time)) { - throw new Error( - "firebase.storage().setMaxDownloadRetryTime(*) 'time' must be a number value.", - ); - } - - this._maxDownloadRetryTime = time; - return this.native.setMaxDownloadRetryTime(time); - } - - useEmulator(host, port) { - if (!host || !isString(host) || !port || !isNumber(port)) { - throw new Error('firebase.storage().useEmulator() takes a non-empty host and port'); - } - - let _host = host; - - const androidBypassEmulatorUrlRemap = - typeof this.firebaseJson.android_bypass_emulator_url_remap === 'boolean' && - this.firebaseJson.android_bypass_emulator_url_remap; - if (!androidBypassEmulatorUrlRemap && isAndroid && _host) { - if (_host === 'localhost' || _host === '127.0.0.1') { - _host = '10.0.2.2'; - // eslint-disable-next-line no-console - console.log( - 'Mapping storage host to "10.0.2.2" for android emulators. Use real IP on real devices. You can bypass this behaviour with "android_bypass_emulator_url_remap" flag.', - ); - } - } - 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 - } -} - -// 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({ - statics, - version, - namespace, - nativeEvents, - nativeModuleName, - hasMultiAppSupport: true, - hasCustomUrlOrRegionSupport: true, - disablePrependCustomUrlOrRegion: true, - ModuleClass: FirebaseStorageModule, -}); - -// import storage, { firebase } from '@react-native-firebase/storage'; -// storage().X(...); -// firebase.storage().X(...); -export const firebase = getFirebaseRoot(); - -export * from './modular'; - -setReactNativeModule(nativeModuleName, fallBackModule); diff --git a/packages/storage/lib/utils.js b/packages/storage/lib/utils.js deleted file mode 100644 index dfefbbef11..0000000000 --- a/packages/storage/lib/utils.js +++ /dev/null @@ -1,99 +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 { isNull, isObject, isString } from '@react-native-firebase/app/lib/common'; -import { NativeFirebaseError } from '@react-native-firebase/app/lib/internal'; - -const SETTABLE_FIELDS = [ - 'cacheControl', - 'contentDisposition', - 'contentEncoding', - 'contentLanguage', - 'contentType', - 'customMetadata', - 'md5hash', -]; - -export async function handleStorageEvent(storageInstance, event) { - const { taskId, eventName } = event; - const body = event.body || {}; - - if (body.error) { - body.error = await NativeFirebaseError.fromEvent(body.error, storageInstance._config.namespace); - } - - storageInstance.emitter.emit(storageInstance.eventNameForApp(taskId, eventName), body); -} - -export function getHttpUrlParts(url) { - const decoded = decodeURIComponent(url); - const parts = decoded.match(/\/b\/(.*)\/o\/([a-zA-Z0-9./\-_]+)(.*)/); - - if (!parts || parts.length < 3) { - return null; - } - - return { bucket: `gs://${parts[1]}`, path: parts[2] }; -} - -export function getGsUrlParts(url) { - const bucket = url.substring(0, url.indexOf('/', 5)) || url; - const path = - (url.indexOf('/', 5) > -1 ? url.substring(url.indexOf('/', 5) + 1, url.length) : '/') || '/'; - - return { bucket, path }; -} - -export function validateMetadata(metadata, update = true) { - if (!isObject(metadata)) { - throw new Error('firebase.storage.SettableMetadata must be an object value if provided.'); - } - - const metadataEntries = Object.entries(metadata); - - for (let i = 0; i < metadataEntries.length; i++) { - const [key, value] = metadataEntries[i]; - // validate keys - if (!SETTABLE_FIELDS.includes(key)) { - throw new Error( - `firebase.storage.SettableMetadata unknown property '${key}' provided for metadata.`, - ); - } - - // md5 is only allowed on put, not on update - if (key === 'md5hash' && update === true) { - throw new Error( - `firebase.storage.SettableMetadata md5hash may only be set on upload, not on updateMetadata`, - ); - } - - // validate values - if (key !== 'customMetadata') { - if (!isString(value) && !isNull(value)) { - throw new Error( - `firebase.storage.SettableMetadata invalid property '${key}' should be a string or null value.`, - ); - } - } else if (!isObject(value) && !isNull(value)) { - throw new Error( - 'firebase.storage.SettableMetadata.customMetadata must be an object of keys and string values or null value.', - ); - } - } - - return metadata; -} From 699726229a3f73bf43009b04465efff98cc343f0 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Thu, 8 Jan 2026 16:50:44 +0000 Subject: [PATCH 12/36] remove another js file --- packages/storage/lib/StorageDownloadTask.js | 26 --------------------- 1 file changed, 26 deletions(-) delete mode 100644 packages/storage/lib/StorageDownloadTask.js diff --git a/packages/storage/lib/StorageDownloadTask.js b/packages/storage/lib/StorageDownloadTask.js deleted file mode 100644 index 0d6809e524..0000000000 --- a/packages/storage/lib/StorageDownloadTask.js +++ /dev/null @@ -1,26 +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 StorageTask from './StorageTask'; - -const DOWNLOAD_TASK = 'download'; - -export default class StorageDownloadTask extends StorageTask { - constructor(storageRef, beginTaskFn) { - super(DOWNLOAD_TASK, storageRef, beginTaskFn); - } -} From 1eaae1a69c8f2c0f3ac55cdff7e54a5377654fb0 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Thu, 8 Jan 2026 17:49:29 +0000 Subject: [PATCH 13/36] fix: dependencies --- packages/storage/package.json | 9 ++++----- yarn.lock | 2 ++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/storage/package.json b/packages/storage/package.json index 910cc75d47..98585fe52c 100644 --- a/packages/storage/package.json +++ b/packages/storage/package.json @@ -9,10 +9,8 @@ "scripts": { "build": "genversion --esm --semi lib/version.ts", "build:clean": "rimraf android/build && rimraf ios/build", - "build:plugin": "rimraf plugin/build && tsc --build plugin", - "lint:plugin": "eslint plugin/src/*", "compile": "bob build", - "prepare": "yarn run build && yarn run build:plugin && yarn compile" + "prepare": "yarn run build && yarn compile" }, "repository": { "type": "git", @@ -39,6 +37,9 @@ "access": "public", "provenance": true }, + "devDependencies": { + "react-native-builder-bob": "^0.40.13" + }, "exports": { ".": { "source": "./lib/index.ts", @@ -55,9 +56,7 @@ }, "files": [ "lib", - "plugin/src", "tsconfig.json", - "plugin/tsconfig.json", "dist", "!**/__tests__", "!**/__fixtures__", diff --git a/yarn.lock b/yarn.lock index 9ab486323b..63cbe8b423 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5251,6 +5251,8 @@ __metadata: "@react-native-firebase/storage@npm:23.7.0, @react-native-firebase/storage@workspace:packages/storage": version: 0.0.0-use.local resolution: "@react-native-firebase/storage@workspace:packages/storage" + dependencies: + react-native-builder-bob: "npm:^0.40.13" peerDependencies: "@react-native-firebase/app": 23.7.0 languageName: unknown From ba4cdd6359f4959fa6306e004d22c8da32b9c4c0 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Thu, 8 Jan 2026 17:49:50 +0000 Subject: [PATCH 14/36] fix: types and create storage private type --- packages/storage/lib/StorageDownloadTask.ts | 4 ++-- packages/storage/lib/StorageListResult.ts | 4 ++-- packages/storage/lib/StorageReference.ts | 6 ++++-- packages/storage/lib/StorageTask.ts | 14 +++++++------- packages/storage/lib/StorageUploadTask.ts | 4 ++-- packages/storage/lib/namespaced.ts | 15 ++++++++------- packages/storage/lib/types/internal.ts | 10 ++++++++++ 7 files changed, 35 insertions(+), 22 deletions(-) create mode 100644 packages/storage/lib/types/internal.ts diff --git a/packages/storage/lib/StorageDownloadTask.ts b/packages/storage/lib/StorageDownloadTask.ts index 14a17e34f8..24cbb7cd24 100644 --- a/packages/storage/lib/StorageDownloadTask.ts +++ b/packages/storage/lib/StorageDownloadTask.ts @@ -16,12 +16,12 @@ */ import StorageTask from './StorageTask'; -import type { Reference } from './types/storage'; +import type { Reference, TaskSnapshot } from './types/storage'; const DOWNLOAD_TASK = 'download'; export default class StorageDownloadTask extends StorageTask { - constructor(storageRef: Reference, beginTaskFn: (task: StorageTask) => Promise) { + constructor(storageRef: Reference, beginTaskFn: (task: StorageTask) => Promise) { super(DOWNLOAD_TASK, storageRef, beginTaskFn); } } diff --git a/packages/storage/lib/StorageListResult.ts b/packages/storage/lib/StorageListResult.ts index 9b1272b0b2..0b6d11f573 100644 --- a/packages/storage/lib/StorageListResult.ts +++ b/packages/storage/lib/StorageListResult.ts @@ -15,7 +15,7 @@ * */ -import type { Reference } from './types/storage'; +import type { Reference, Storage } from './types/storage'; // To avoid React Native require cycle warnings let StorageReference: (new (storage: any, path: string) => Reference) | null = null; @@ -32,7 +32,7 @@ export default class StorageListResult { private _prefixes: Reference[]; constructor( - storage: any, + storage: Storage, nativeData: { nextPageToken?: string | null; items: string[]; prefixes: string[] }, ) { this._nextPageToken = nativeData.nextPageToken || null; diff --git a/packages/storage/lib/StorageReference.ts b/packages/storage/lib/StorageReference.ts index 2e0b5ba8f3..269a031dc2 100644 --- a/packages/storage/lib/StorageReference.ts +++ b/packages/storage/lib/StorageReference.ts @@ -36,11 +36,12 @@ import { StringFormat } from './StorageStatics'; import StorageUploadTask from './StorageUploadTask'; import { validateMetadata } from './utils'; import type { Reference, SettableMetadata, ListOptions, FullMetadata, Task } from './types/storage'; +import type { StoragePrivate } from './types/internal'; export default class StorageReference extends ReferenceBase implements Reference { - declare _storage: any; // Will be properly typed when Storage module is converted + _storage: StoragePrivate; - constructor(storage: any, path: string) { + constructor(storage: StoragePrivate, path: string) { super(path); this._storage = storage; } @@ -49,6 +50,7 @@ export default class StorageReference extends ReferenceBase implements Reference * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#bucket */ get bucket(): string { + // @ts-ignore return this._storage._customUrlOrRegion.replace('gs://', ''); } diff --git a/packages/storage/lib/StorageTask.ts b/packages/storage/lib/StorageTask.ts index 67a7c4a9fe..4b76f5c540 100644 --- a/packages/storage/lib/StorageTask.ts +++ b/packages/storage/lib/StorageTask.ts @@ -159,13 +159,13 @@ function subscribeToEvents( } export default class StorageTask { - declare _type: string; - declare _id: number; - declare _promise: Promise | null; - declare _ref: Reference; - declare _beginTask: (task: StorageTask) => Promise; - declare _storage: any; // Will be properly typed when Storage module is converted - declare _snapshot: TaskSnapshot | null; + _type: string; + _id: number; + _promise: Promise | null; + _ref: Reference; + _beginTask: (task: StorageTask) => Promise; + _storage: any; // Will be properly typed when Storage module is converted + _snapshot: TaskSnapshot | null; constructor( type: string, diff --git a/packages/storage/lib/StorageUploadTask.ts b/packages/storage/lib/StorageUploadTask.ts index afdc50d06d..4209e57344 100644 --- a/packages/storage/lib/StorageUploadTask.ts +++ b/packages/storage/lib/StorageUploadTask.ts @@ -16,12 +16,12 @@ */ import StorageTask from './StorageTask'; -import type { Reference } from './types/storage'; +import type { Reference, TaskSnapshot } from './types/storage'; const UPLOAD_TASK = 'upload'; export default class StorageUploadTask extends StorageTask { - constructor(storageRef: Reference, beginTaskFn: (task: StorageTask) => Promise) { + constructor(storageRef: Reference, beginTaskFn: (task: StorageTask) => Promise) { super(UPLOAD_TASK, storageRef, beginTaskFn); } } diff --git a/packages/storage/lib/namespaced.ts b/packages/storage/lib/namespaced.ts index 4674133c58..37ea0b3d56 100644 --- a/packages/storage/lib/namespaced.ts +++ b/packages/storage/lib/namespaced.ts @@ -36,6 +36,7 @@ import { getGsUrlParts, getHttpUrlParts, handleStorageEvent } from './utils'; import { version } from './version'; import fallBackModule from './web/RNFBStorageModule'; import type { Storage, StorageStatics, Reference, EmulatorMockTokenOptions } from './types/storage'; +import type { StoragePrivate } from './types/internal'; const statics: StorageStatics = { StringFormat, @@ -48,12 +49,12 @@ const nativeEvents = ['storage_event']; const nativeModuleName = 'RNFBStorageModule'; class FirebaseStorageModule extends FirebaseModule { - declare _customUrlOrRegion: string; - declare emulatorHost: string | undefined; - declare emulatorPort: number; - declare _maxUploadRetryTime: number; - declare _maxDownloadRetryTime: number; - declare _maxOperationRetryTime: number; + _customUrlOrRegion: string = ''; + emulatorHost: string | undefined; + emulatorPort: number; + _maxUploadRetryTime: number; + _maxDownloadRetryTime: number; + _maxOperationRetryTime: number; constructor( app: ReactNativeFirebase.FirebaseAppBase, @@ -139,7 +140,7 @@ class FirebaseStorageModule extends FirebaseModule { } const storageInstance = this.app.storage(bucket); - return new StorageReference(storageInstance, path); + return new StorageReference(storageInstance as StoragePrivate, path); } /** diff --git a/packages/storage/lib/types/internal.ts b/packages/storage/lib/types/internal.ts new file mode 100644 index 0000000000..5d8d661eb0 --- /dev/null +++ b/packages/storage/lib/types/internal.ts @@ -0,0 +1,10 @@ +import type { Storage } from './storage'; + +/** + * Internal Storage type with access to private properties. + * Used internally by StorageReference and other internal classes. + */ +export type StoragePrivate = Storage & { + native: any; + _customUrlOrRegion: string; +}; From 3abfb9e7fdb76d0a8ffc54de7928fe5c0bbf01a0 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 9 Jan 2026 15:05:24 +0000 Subject: [PATCH 15/36] chore: remove statics that are added at runtime via app --- packages/storage/type-test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/storage/type-test.ts b/packages/storage/type-test.ts index 1684843962..9f06ec6f34 100644 --- a/packages/storage/type-test.ts +++ b/packages/storage/type-test.ts @@ -296,8 +296,6 @@ console.log(storageInstance2.app.name); // Test backwards compatibility types const legacyType: FirebaseStorageTypes.Module = storageInstance2; console.log(legacyType.app.name); -const legacyStatics: FirebaseStorageTypes.Statics = storage; -console.log(legacyStatics.SDK_VERSION); const legacyReference: FirebaseStorageTypes.Reference = storageInstance2.ref('test'); console.log(legacyReference.fullPath); const legacyMetadata: FirebaseStorageTypes.FullMetadata = {} as FirebaseStorageTypes.FullMetadata; From 3219fbcbce14deb041b3d69e02a341af928f03a2 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 9 Jan 2026 15:36:22 +0000 Subject: [PATCH 16/36] fix: update type --- packages/storage/lib/namespaced.ts | 2 +- packages/storage/lib/types/internal.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/storage/lib/namespaced.ts b/packages/storage/lib/namespaced.ts index 37ea0b3d56..f951e10aed 100644 --- a/packages/storage/lib/namespaced.ts +++ b/packages/storage/lib/namespaced.ts @@ -49,7 +49,7 @@ const nativeEvents = ['storage_event']; const nativeModuleName = 'RNFBStorageModule'; class FirebaseStorageModule extends FirebaseModule { - _customUrlOrRegion: string = ''; + _customUrlOrRegion: string | null = null; emulatorHost: string | undefined; emulatorPort: number; _maxUploadRetryTime: number; diff --git a/packages/storage/lib/types/internal.ts b/packages/storage/lib/types/internal.ts index 5d8d661eb0..6dc85db0fb 100644 --- a/packages/storage/lib/types/internal.ts +++ b/packages/storage/lib/types/internal.ts @@ -6,5 +6,5 @@ import type { Storage } from './storage'; */ export type StoragePrivate = Storage & { native: any; - _customUrlOrRegion: string; + _customUrlOrRegion: string | null; }; From 074f261e441764dcab6dbfb57028a4632314e459 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 9 Jan 2026 16:11:27 +0000 Subject: [PATCH 17/36] fix: web types --- packages/storage/lib/web/RNFBStorageModule.ts | 80 ++++++++++++------- 1 file changed, 49 insertions(+), 31 deletions(-) diff --git a/packages/storage/lib/web/RNFBStorageModule.ts b/packages/storage/lib/web/RNFBStorageModule.ts index 7cbf36bde1..dfc97550cc 100644 --- a/packages/storage/lib/web/RNFBStorageModule.ts +++ b/packages/storage/lib/web/RNFBStorageModule.ts @@ -12,6 +12,16 @@ import { uploadBytesResumable, ref as firebaseStorageRef, } from '@react-native-firebase/app/lib/internal/web/firebaseStorage'; +import type { FirebaseApp } from 'firebase/app'; +import type { + StorageReference, + UploadTask, + UploadTaskSnapshot as FirebaseUploadTaskSnapshot, + FullMetadata, + ListResult as FirebaseListResult, +} from 'firebase/storage'; + +type Storage = ReturnType; import { guard, getWebError, emitEvent } from '@react-native-firebase/app/lib/internal/web/utils'; import { Base64 } from '@react-native-firebase/app/lib/common'; @@ -66,7 +76,9 @@ function rejectWithCodeAndMessage(code: string, message: string): Promise return Promise.reject(getWebError(error)); } -function metadataToObject(metadata: Metadata): Metadata & { metadata?: Record } { +function metadataToObject( + metadata: Metadata | FullMetadata, +): Metadata & { metadata?: Record } { const out: Metadata & { metadata?: Record } = { bucket: metadata.bucket, generation: metadata.generation, @@ -108,7 +120,10 @@ function metadataToObject(metadata: Metadata): Metadata & { metadata?: Record } { return { ...uploadTaskSnapshotToObject(snapshot), state: 'error', @@ -116,12 +131,14 @@ function uploadTaskErrorToObject(error: any, snapshot: any): UploadTaskSnapshot }; } -function uploadTaskSnapshotToObject(snapshot: any): UploadTaskSnapshot { +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) : {}, }; } @@ -148,18 +165,18 @@ function makeSettableMetadata(metadata: SettableMetadata): SettableMetadata { }; } -function listResultToObject(result: { nextPageToken?: string; items: any[]; prefixes: any[] }) { +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: Record = {}; -const appInstances: Record = {}; -const storageInstances: Record = {}; -const tasks: Record = {}; +const appInstances: Record = {}; +const storageInstances: Record = {}; +const tasks: Record = {}; function getBucketFromUrl(url: string): string { // Check if the URL starts with "gs://" @@ -175,7 +192,7 @@ function getBucketFromUrl(url: string): string { } } -function getCachedAppInstance(appName: string): any { +function getCachedAppInstance(appName: string): FirebaseApp { return (appInstances[appName] ??= getApp(appName)); } @@ -185,31 +202,32 @@ function emulatorKey(appName: string, url: string): string { } // Returns a cached Storage instance. -function getCachedStorageInstance(appName: string, url?: string | null): any { - let instance: any; - 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: string, url: string): any { +function getReferenceFromUrl(appName: string, url: string): StorageReference { const path = url.substring(url.indexOf('/') + 1); const instance = getCachedStorageInstance(appName, path); return firebaseStorageRef(instance, url); @@ -438,10 +456,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, @@ -450,7 +468,7 @@ export default { }; emitEvent('storage_event', event); }, - error => { + (error: Error) => { const errorSnapshot = uploadTaskErrorToObject(error, task.snapshot); const event = { body: { @@ -473,7 +491,7 @@ export default { delete tasks[taskId]; const event = { body: { - ...uploadTaskSnapshotToObject(snapshot), + ...uploadTaskSnapshotToObject(task.snapshot), state: 'success', }, appName, From e09276a335850cd3d1e3545c8c035fbac780c123 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 9 Jan 2026 16:23:57 +0000 Subject: [PATCH 18/36] fix: remove any and ignore --- packages/storage/lib/StorageListResult.ts | 19 +++++++++++++------ packages/storage/lib/namespaced.ts | 2 +- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/storage/lib/StorageListResult.ts b/packages/storage/lib/StorageListResult.ts index 0b6d11f573..6fe8e5e96f 100644 --- a/packages/storage/lib/StorageListResult.ts +++ b/packages/storage/lib/StorageListResult.ts @@ -18,10 +18,10 @@ import type { Reference, Storage } from './types/storage'; // To avoid React Native require cycle warnings -let StorageReference: (new (storage: any, path: string) => Reference) | null = null; +let StorageReference: (new (storage: Storage, path: string) => Reference) | null = null; export function provideStorageReferenceClass( - storageReference: new (storage: any, path: string) => Reference, + storageReference: new (storage: Storage, path: string) => Reference, ): void { StorageReference = storageReference; } @@ -36,10 +36,17 @@ export default class StorageListResult { nativeData: { nextPageToken?: string | null; items: string[]; prefixes: string[] }, ) { this._nextPageToken = nativeData.nextPageToken || null; - // @ts-ignore - StorageReference is set by provideStorageReferenceClass before use - this._items = nativeData.items.map(path => new StorageReference(storage, path)); - // @ts-ignore - StorageReference is set by provideStorageReferenceClass before use - 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(): Reference[] { diff --git a/packages/storage/lib/namespaced.ts b/packages/storage/lib/namespaced.ts index f951e10aed..c2f75bd85c 100644 --- a/packages/storage/lib/namespaced.ts +++ b/packages/storage/lib/namespaced.ts @@ -222,7 +222,7 @@ const storageNamespace = createModuleNamespace({ hasMultiAppSupport: true, hasCustomUrlOrRegionSupport: true, disablePrependCustomUrlOrRegion: true, - ModuleClass: FirebaseStorageModule as any, + ModuleClass: FirebaseStorageModule, }); type StorageNamespace = ReactNativeFirebase.FirebaseModuleWithStaticsAndApp< From e2b1a53783b5473fba50dd24f058e2ce93721121 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 9 Jan 2026 16:34:51 +0000 Subject: [PATCH 19/36] fix: storage reference and list types --- packages/storage/lib/StorageListResult.ts | 9 +++++---- packages/storage/lib/StorageReference.ts | 13 ++++++++----- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/storage/lib/StorageListResult.ts b/packages/storage/lib/StorageListResult.ts index 6fe8e5e96f..87bd444ba5 100644 --- a/packages/storage/lib/StorageListResult.ts +++ b/packages/storage/lib/StorageListResult.ts @@ -15,13 +15,14 @@ * */ -import type { Reference, Storage } from './types/storage'; +import type { Reference } from './types/storage'; +import type { StoragePrivate } from './types/internal'; // To avoid React Native require cycle warnings -let StorageReference: (new (storage: Storage, path: string) => Reference) | null = null; +let StorageReference: (new (storage: StoragePrivate, path: string) => Reference) | null = null; export function provideStorageReferenceClass( - storageReference: new (storage: Storage, path: string) => Reference, + storageReference: new (storage: StoragePrivate, path: string) => Reference, ): void { StorageReference = storageReference; } @@ -32,7 +33,7 @@ export default class StorageListResult { private _prefixes: Reference[]; constructor( - storage: Storage, + storage: StoragePrivate, nativeData: { nextPageToken?: string | null; items: string[]; prefixes: string[] }, ) { this._nextPageToken = nativeData.nextPageToken || null; diff --git a/packages/storage/lib/StorageReference.ts b/packages/storage/lib/StorageReference.ts index 269a031dc2..432ef7a15c 100644 --- a/packages/storage/lib/StorageReference.ts +++ b/packages/storage/lib/StorageReference.ts @@ -50,8 +50,7 @@ export default class StorageReference extends ReferenceBase implements Reference * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#bucket */ get bucket(): string { - // @ts-ignore - return this._storage._customUrlOrRegion.replace('gs://', ''); + return this._storage._customUrlOrRegion!.replace('gs://', ''); } /** @@ -209,7 +208,11 @@ export default class StorageReference extends ReferenceBase implements Reference /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#putString */ - putString(string: string, format: string = StringFormat.RAW, metadata?: SettableMetadata): Task { + 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 => @@ -276,7 +279,7 @@ export default class StorageReference extends ReferenceBase implements Reference _updateString( string: string, - format: string, + format: (typeof StringFormat)[keyof typeof StringFormat], metadata: SettableMetadata | undefined, update = false, ): { _string: string; _format: string; _metadata: SettableMetadata | undefined } { @@ -286,7 +289,7 @@ export default class StorageReference extends ReferenceBase implements Reference ); } - if (!Object.values(StringFormat).includes(format as any)) { + if (!Object.values(StringFormat).includes(format)) { throw new Error( `firebase.storage.StorageReference.putString(_, *, _) 'format' provided is invalid, must be one of ${Object.values( StringFormat, From a84f1f4d67e28d0adc38b5382579340e83effda9 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 9 Jan 2026 16:37:03 +0000 Subject: [PATCH 20/36] fix: metadata --- packages/storage/lib/StorageReference.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/storage/lib/StorageReference.ts b/packages/storage/lib/StorageReference.ts index 432ef7a15c..3f7b4fdb66 100644 --- a/packages/storage/lib/StorageReference.ts +++ b/packages/storage/lib/StorageReference.ts @@ -319,10 +319,8 @@ export default class StorageReference extends ReferenceBase implements Reference if (isUndefined(metadata) || isUndefined(metadata.contentType)) { if (isUndefined(metadata)) { _metadata = {}; - } else { - _metadata = { ...metadata }; } - _metadata.contentType = mediaType; + _metadata!.contentType = mediaType; _string = base64String; _format = StringFormat.BASE64; } From 4ac377800856a505e7fdb94274e52c3c9e7b7170 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Mon, 12 Jan 2026 09:12:39 +0000 Subject: [PATCH 21/36] refactor: improve types to match existing and update StorageTask --- packages/storage/lib/StorageTask.ts | 99 ++++++++++++-------------- packages/storage/lib/index.ts | 1 + packages/storage/lib/types/internal.ts | 13 +++- packages/storage/lib/types/storage.ts | 38 ++++++++-- 4 files changed, 91 insertions(+), 60 deletions(-) diff --git a/packages/storage/lib/StorageTask.ts b/packages/storage/lib/StorageTask.ts index 4b76f5c540..cefbf5ea63 100644 --- a/packages/storage/lib/StorageTask.ts +++ b/packages/storage/lib/StorageTask.ts @@ -18,20 +18,24 @@ import { isFunction, isNull, isObject } from '@react-native-firebase/app/lib/common'; import type { EmitterSubscription } from 'react-native'; import { TaskEvent } from './StorageStatics'; -import type { TaskSnapshot, Reference } from './types/storage'; +import type { TaskSnapshot, Reference, Task, TaskSnapshotObserver } from './types/storage'; +import type { ReferencePrivate, StoragePrivate } from './types/internal'; let TASK_ID = 0; function wrapErrorEventListener( listenerFn: ((error: Error) => void) | null | undefined, unsubscribe: (() => void) | null | undefined, -): (event: { error: Error }) => void { - return event => { +): (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); + } } }; } @@ -40,38 +44,38 @@ function wrapSnapshotEventListener( task: StorageTask, listenerFn: ((snapshot: TaskSnapshot) => void) | null | undefined, unsubscribe: (() => void) | null | undefined, -): ((event: any) => void) | null { +): ((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) as any; - 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); } }; } @@ -79,7 +83,7 @@ function wrapSnapshotEventListener( function addTaskEventListener( task: StorageTask, eventName: string, - listener: (event: any) => void, + listener: (snapshot: TaskSnapshot) => void, ): EmitterSubscription { let _eventName = eventName; if (_eventName !== TaskEvent.STATE_CHANGED) { @@ -94,24 +98,17 @@ function addTaskEventListener( function subscribeToEvents( task: StorageTask, - nextOrObserver?: - | ((snapshot: TaskSnapshot) => void) - | { - next?: (snapshot: TaskSnapshot) => void; - error?: (error: Error) => void; - complete?: () => void; - } - | null, + nextOrObserver?: ((snapshot: TaskSnapshot) => void) | TaskSnapshotObserver | null, error?: ((error: Error) => void) | null, complete?: (() => void) | null, ): () => void { - let _error: ((event: { error: Error }) => void) | undefined; + let _error: ((snapshot: TaskSnapshot) => void) | undefined; let _errorSubscription: EmitterSubscription | undefined; - let _next: ((event: any) => void) | null | undefined; + let _next: ((snapshot: TaskSnapshot) => void) | null | undefined; let _nextSubscription: EmitterSubscription | undefined; - let _complete: ((event: any) => void) | null | undefined; + let _complete: ((snapshot: TaskSnapshot) => void) | null | undefined; let _completeSubscription: EmitterSubscription | undefined; const unsubscribe = () => { @@ -131,9 +128,10 @@ function subscribeToEvents( _next = wrapSnapshotEventListener(task, nextOrObserver, unsubscribe); _complete = wrapSnapshotEventListener(task, complete, unsubscribe); } else if (isObject(nextOrObserver)) { - _error = wrapErrorEventListener(nextOrObserver.error, unsubscribe); - _next = wrapSnapshotEventListener(task, nextOrObserver.next, unsubscribe); - _complete = wrapSnapshotEventListener(task, nextOrObserver.complete, unsubscribe); + const observer = nextOrObserver as TaskSnapshotObserver; + _error = wrapErrorEventListener(observer.error, unsubscribe); + _next = wrapSnapshotEventListener(task, observer.next, unsubscribe); + _complete = wrapSnapshotEventListener(task, observer.complete, unsubscribe); } else if (isNull(nextOrObserver)) { _error = wrapErrorEventListener(error, unsubscribe); _complete = wrapSnapshotEventListener(task, complete, unsubscribe); @@ -161,23 +159,23 @@ function subscribeToEvents( export default class StorageTask { _type: string; _id: number; - _promise: Promise | null; + _promise: Promise | null; _ref: Reference; - _beginTask: (task: StorageTask) => Promise; - _storage: any; // Will be properly typed when Storage module is converted + _beginTask: (task: StorageTask) => Promise; + _storage: StoragePrivate; _snapshot: TaskSnapshot | null; constructor( type: string, storageRef: Reference, - beginTaskFn: (task: StorageTask) => Promise, + beginTaskFn: (task: StorageTask) => Promise, ) { this._type = type; this._id = TASK_ID++; this._promise = null; this._ref = storageRef; this._beginTask = beginTaskFn; - this._storage = (storageRef as any)._storage; + this._storage = (storageRef as ReferencePrivate)._storage; this._snapshot = null; } @@ -185,15 +183,15 @@ export default class StorageTask { * @url https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask#then */ get then(): ( - onFulfilled?: ((snapshot: TaskSnapshot) => any) | null, + onFulfilled?: ((snapshot: TaskSnapshot) => TaskSnapshot) | null, onRejected?: ((error: Error) => any) | null, - ) => Promise { + ) => Promise { if (!this._promise) { this._promise = this._beginTask(this); } const promise = this._promise; - return (( + return ( onFulfilled?: ((snapshot: TaskSnapshot) => any) | null, onRejected?: ((error: Error) => any) | null, ) => { @@ -215,7 +213,7 @@ export default class StorageTask { } }); }); - }) as any; + }; } /** @@ -245,15 +243,8 @@ export default class StorageTask { * @url https://firebase.google.com/docs/reference/js/firebase.storage.UploadTask#on */ on( - event: string, - nextOrObserver?: - | ((snapshot: TaskSnapshot) => void) - | { - next?: (snapshot: TaskSnapshot) => void; - error?: (error: Error) => void; - complete?: () => void; - } - | null, + event: 'state_changed', + nextOrObserver?: TaskSnapshotObserver | null | ((snapshot: TaskSnapshot) => void), error?: ((error: Error) => void) | null, complete?: (() => void) | null, ): () => void { diff --git a/packages/storage/lib/index.ts b/packages/storage/lib/index.ts index fd32598fba..fb6a132255 100644 --- a/packages/storage/lib/index.ts +++ b/packages/storage/lib/index.ts @@ -25,6 +25,7 @@ export type { ListResult, ListOptions, TaskSnapshot, + TaskSnapshotObserver, TaskResult, Task, EmulatorMockTokenOptions, diff --git a/packages/storage/lib/types/internal.ts b/packages/storage/lib/types/internal.ts index 6dc85db0fb..9cf01f2306 100644 --- a/packages/storage/lib/types/internal.ts +++ b/packages/storage/lib/types/internal.ts @@ -1,4 +1,5 @@ -import type { Storage } from './storage'; +import type { Storage, Reference } from './storage'; +import type EventEmitter from 'react-native/Libraries/vendor/emitter/EventEmitter'; /** * Internal Storage type with access to private properties. @@ -7,4 +8,14 @@ import type { Storage } from './storage'; export type StoragePrivate = Storage & { native: any; _customUrlOrRegion: string | null; + emitter: EventEmitter; + eventNameForApp: (...args: Array) => string; +}; + +/** + * Internal Reference type with access to private properties. + * Used internally by StorageTask and other internal classes. + */ +export type ReferencePrivate = Reference & { + _storage: StoragePrivate; }; diff --git a/packages/storage/lib/types/storage.ts b/packages/storage/lib/types/storage.ts index c55925e294..3eed14eae1 100644 --- a/packages/storage/lib/types/storage.ts +++ b/packages/storage/lib/types/storage.ts @@ -44,7 +44,7 @@ export interface FullMetadata extends SettableMetadata { size: number; timeCreated: string; updated: string; - md5Hash: string; + md5Hash: string | null; metadata?: { [key: string]: string }; } @@ -75,6 +75,10 @@ export interface TaskSnapshot { metadata: FullMetadata | null; task: Task; ref: Reference; + /** + * If the state is `error`, returns a JavaScript error of the current task snapshot. + */ + error?: Error; } /** @@ -105,13 +109,37 @@ export interface Reference { list(options?: ListOptions): Promise; listAll(): Promise; put(data: Blob | Uint8Array | ArrayBuffer, metadata?: SettableMetadata): Task; - putString(string: string, format?: string, 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. */ @@ -145,10 +173,10 @@ export interface Task { * @param complete An optional complete handler function. */ on( - event: string, - nextOrObserver?: ((snapshot: TaskSnapshot) => void) | null, + event: 'state_changed', + nextOrObserver?: TaskSnapshotObserver | null | ((snapshot: TaskSnapshot) => void), error?: ((error: Error) => void) | null, - complete?: ((snapshot: TaskSnapshot) => void) | null, + complete?: (() => void) | null, ): () => void; /** From 0c3bfcd909aa72201adef8b5b85bc0a5bfe5a41b Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Mon, 2 Feb 2026 14:46:27 +0000 Subject: [PATCH 22/36] yarn.lock --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index b4458efcf8..544070fcb7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21043,7 +21043,7 @@ __metadata: languageName: node linkType: hard -"react-native-builder-bob@npm:^0.40.17": +"react-native-builder-bob@npm:^0.40.13, react-native-builder-bob@npm:^0.40.17": version: 0.40.17 resolution: "react-native-builder-bob@npm:0.40.17" dependencies: From 79f0e8e4e9c5d9b72b37992d8d1212afa2149fe1 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Mon, 2 Feb 2026 15:10:30 +0000 Subject: [PATCH 23/36] fix: tsconfig paths --- packages/storage/tsconfig.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/storage/tsconfig.json b/packages/storage/tsconfig.json index 58755dcb00..53dca73192 100644 --- a/packages/storage/tsconfig.json +++ b/packages/storage/tsconfig.json @@ -4,16 +4,16 @@ "baseUrl": ".", "rootDir": ".", "paths": { - "@react-native-firebase/app/lib/common/*": ["../app/dist/typescript/commonjs/lib/common/*"], - "@react-native-firebase/app/lib/common": ["../app/dist/typescript/commonjs/lib/common"], - "@react-native-firebase/app/lib/internal/web/*": [ - "../app/dist/typescript/commonjs/lib/internal/web/*" + "@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/lib/internal/*": [ - "../app/dist/typescript/commonjs/lib/internal/*" + "@react-native-firebase/app/dist/module/internal/*": [ + "../app/dist/typescript/lib/internal/*" ], - "@react-native-firebase/app/lib/internal": ["../app/dist/typescript/commonjs/lib/internal"], - "@react-native-firebase/app": ["../app/dist/typescript/commonjs/lib"] + "@react-native-firebase/app/dist/module/internal": ["../app/dist/typescript/lib/internal"], + "@react-native-firebase/app": ["../app/dist/typescript/lib"] } }, "include": ["lib/**/*"], From 8f34cb509d5d7823390fbd312039b5d7124df61d Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Mon, 2 Feb 2026 15:19:03 +0000 Subject: [PATCH 24/36] fix(web): use types from firebase-js-sdk --- packages/storage/lib/web/RNFBStorageModule.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/storage/lib/web/RNFBStorageModule.ts b/packages/storage/lib/web/RNFBStorageModule.ts index 3c7d610567..d67a5d6fea 100644 --- a/packages/storage/lib/web/RNFBStorageModule.ts +++ b/packages/storage/lib/web/RNFBStorageModule.ts @@ -12,14 +12,14 @@ import { uploadBytesResumable, ref as firebaseStorageRef, } from '@react-native-firebase/app/dist/module/internal/web/firebaseStorage'; -import type { FirebaseApp } from 'firebase/app'; import type { StorageReference, UploadTask, UploadTaskSnapshot as FirebaseUploadTaskSnapshot, FullMetadata, ListResult as FirebaseListResult, -} from 'firebase/storage'; +} 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, From f3711eab35b120e7295e7d65ec3f8b6b47c7c18d Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Mon, 2 Feb 2026 15:28:25 +0000 Subject: [PATCH 25/36] fix: update package.json to match others after publish issues --- packages/storage/package.json | 35 +++++++++-------------------------- 1 file changed, 9 insertions(+), 26 deletions(-) diff --git a/packages/storage/package.json b/packages/storage/package.json index dfbd11ed25..13d135c852 100644 --- a/packages/storage/package.json +++ b/packages/storage/package.json @@ -3,9 +3,8 @@ "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": "./dist/commonjs/index.js", - "module": "./dist/module/index.js", - "types": "./dist/typescript/commonjs/lib/index.d.ts", + "main": "./dist/module/index.js", + "types": "./dist/typescript/lib/index.d.ts", "scripts": { "build": "genversion --esm --semi lib/version.ts", "build:clean": "rimraf android/build && rimraf ios/build", @@ -43,25 +42,11 @@ "exports": { ".": { "source": "./lib/index.ts", - "import": { - "types": "./dist/typescript/module/lib/index.d.ts", - "default": "./dist/module/index.js" - }, - "require": { - "types": "./dist/typescript/commonjs/lib/index.d.ts", - "default": "./dist/commonjs/index.js" - } + "types": "./dist/typescript/lib/index.d.ts", + "default": "./dist/module/index.js" }, "./package.json": "./package.json" }, - "files": [ - "lib", - "tsconfig.json", - "dist", - "!**/__tests__", - "!**/__fixtures__", - "!**/__mocks__" - ], "react-native-builder-bob": { "source": "lib", "output": "dist", @@ -72,12 +57,6 @@ "esm": true } ], - [ - "commonjs", - { - "esm": true - } - ], [ "typescript", { @@ -85,5 +64,9 @@ } ] ] - } + }, + "eslintIgnore": [ + "node_modules/", + "dist/" + ] } From 2b72c0edbbe637824512fd9dc229d85059d173ce Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Mon, 2 Feb 2026 15:37:57 +0000 Subject: [PATCH 26/36] chore: license header --- packages/storage/lib/types/internal.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/storage/lib/types/internal.ts b/packages/storage/lib/types/internal.ts index 9cf01f2306..8bc66ec1cf 100644 --- a/packages/storage/lib/types/internal.ts +++ b/packages/storage/lib/types/internal.ts @@ -1,3 +1,20 @@ +/* + * 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 { Storage, Reference } from './storage'; import type EventEmitter from 'react-native/Libraries/vendor/emitter/EventEmitter'; From 563524dbdbbc0c115e7e6aad4bcf09c67380f63d Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Mon, 2 Feb 2026 16:00:50 +0000 Subject: [PATCH 27/36] fix: internal types and remove any type --- packages/storage/lib/StorageListResult.ts | 11 ++++------- packages/storage/lib/StorageReference.ts | 21 ++++++++++++++------- packages/storage/lib/StorageTask.ts | 6 +++--- packages/storage/lib/namespaced.ts | 4 ++-- packages/storage/lib/types/internal.ts | 16 +++++++++++++--- 5 files changed, 36 insertions(+), 22 deletions(-) diff --git a/packages/storage/lib/StorageListResult.ts b/packages/storage/lib/StorageListResult.ts index 87bd444ba5..d252346287 100644 --- a/packages/storage/lib/StorageListResult.ts +++ b/packages/storage/lib/StorageListResult.ts @@ -16,13 +16,13 @@ */ import type { Reference } from './types/storage'; -import type { StoragePrivate } from './types/internal'; +import type { ListResultInternal, StorageInternal } from './types/internal'; // To avoid React Native require cycle warnings -let StorageReference: (new (storage: StoragePrivate, path: string) => Reference) | null = null; +let StorageReference: (new (storage: StorageInternal, path: string) => Reference) | null = null; export function provideStorageReferenceClass( - storageReference: new (storage: StoragePrivate, path: string) => Reference, + storageReference: new (storage: StorageInternal, path: string) => Reference, ): void { StorageReference = storageReference; } @@ -32,10 +32,7 @@ export default class StorageListResult { private _items: Reference[]; private _prefixes: Reference[]; - constructor( - storage: StoragePrivate, - nativeData: { nextPageToken?: string | null; items: string[]; prefixes: string[] }, - ) { + constructor(storage: StorageInternal, nativeData: ListResultInternal) { this._nextPageToken = nativeData.nextPageToken || null; if (!StorageReference) { diff --git a/packages/storage/lib/StorageReference.ts b/packages/storage/lib/StorageReference.ts index 26f303a276..000b645bbe 100644 --- a/packages/storage/lib/StorageReference.ts +++ b/packages/storage/lib/StorageReference.ts @@ -35,13 +35,20 @@ import StorageListResult, { provideStorageReferenceClass } from './StorageListRe import { StringFormat } from './StorageStatics'; import StorageUploadTask from './StorageUploadTask'; import { validateMetadata } from './utils'; -import type { Reference, SettableMetadata, ListOptions, FullMetadata, Task } from './types/storage'; -import type { StoragePrivate } from './types/internal'; +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: StoragePrivate; + _storage: StorageInternal; - constructor(storage: StoragePrivate, path: string) { + constructor(storage: StorageInternal, path: string) { super(path); this._storage = storage; } @@ -88,7 +95,7 @@ export default class StorageReference extends ReferenceBase implements Reference /** * @url https://firebase.google.com/docs/reference/js/firebase.storage.Reference#storage */ - get storage(): any { + get storage(): Storage { return this._storage; } @@ -166,7 +173,7 @@ export default class StorageReference extends ReferenceBase implements Reference return this._storage.native .list(this.toString(), listOptions) - .then((data: any) => new StorageListResult(this._storage, data)); + .then((data: ListResultInternal) => new StorageListResult(this._storage, data)); } /** @@ -175,7 +182,7 @@ export default class StorageReference extends ReferenceBase implements Reference listAll(): Promise { return this._storage.native .listAll(this.toString()) - .then((data: any) => new StorageListResult(this._storage, data)); + .then((data: ListResultInternal) => new StorageListResult(this._storage, data)); } /** diff --git a/packages/storage/lib/StorageTask.ts b/packages/storage/lib/StorageTask.ts index 87a6e3da4d..41a43b7a4a 100644 --- a/packages/storage/lib/StorageTask.ts +++ b/packages/storage/lib/StorageTask.ts @@ -19,7 +19,7 @@ import { isFunction, isNull, isObject } from '@react-native-firebase/app/dist/mo import type { EmitterSubscription } from 'react-native'; import { TaskEvent } from './StorageStatics'; import type { TaskSnapshot, Reference, Task, TaskSnapshotObserver } from './types/storage'; -import type { ReferencePrivate, StoragePrivate } from './types/internal'; +import type { ReferenceInternal, StorageInternal } from './types/internal'; let TASK_ID = 0; @@ -162,7 +162,7 @@ export default class StorageTask { _promise: Promise | null; _ref: Reference; _beginTask: (task: StorageTask) => Promise; - _storage: StoragePrivate; + _storage: StorageInternal; _snapshot: TaskSnapshot | null; constructor( @@ -175,7 +175,7 @@ export default class StorageTask { this._promise = null; this._ref = storageRef; this._beginTask = beginTaskFn; - this._storage = (storageRef as ReferencePrivate)._storage; + this._storage = (storageRef as ReferenceInternal)._storage; this._snapshot = null; } diff --git a/packages/storage/lib/namespaced.ts b/packages/storage/lib/namespaced.ts index 6163b0d04c..4508975c78 100644 --- a/packages/storage/lib/namespaced.ts +++ b/packages/storage/lib/namespaced.ts @@ -36,7 +36,7 @@ import { getGsUrlParts, getHttpUrlParts, handleStorageEvent } from './utils'; import { version } from './version'; import fallBackModule from './web/RNFBStorageModule'; import type { Storage, StorageStatics, Reference, EmulatorMockTokenOptions } from './types/storage'; -import type { StoragePrivate } from './types/internal'; +import type { StorageInternal } from './types/internal'; const statics: StorageStatics = { StringFormat, @@ -140,7 +140,7 @@ class FirebaseStorageModule extends FirebaseModule { } const storageInstance = this.app.storage(bucket); - return new StorageReference(storageInstance as StoragePrivate, path); + return new StorageReference(storageInstance as StorageInternal, path); } /** diff --git a/packages/storage/lib/types/internal.ts b/packages/storage/lib/types/internal.ts index 8bc66ec1cf..817b3399a0 100644 --- a/packages/storage/lib/types/internal.ts +++ b/packages/storage/lib/types/internal.ts @@ -22,7 +22,7 @@ import type EventEmitter from 'react-native/Libraries/vendor/emitter/EventEmitte * Internal Storage type with access to private properties. * Used internally by StorageReference and other internal classes. */ -export type StoragePrivate = Storage & { +export type StorageInternal = Storage & { native: any; _customUrlOrRegion: string | null; emitter: EventEmitter; @@ -33,6 +33,16 @@ export type StoragePrivate = Storage & { * Internal Reference type with access to private properties. * Used internally by StorageTask and other internal classes. */ -export type ReferencePrivate = Reference & { - _storage: StoragePrivate; +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[]; }; From c35284930262623a8249e23497707c9e67d84b15 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Mon, 2 Feb 2026 16:02:50 +0000 Subject: [PATCH 28/36] fix: remove unknown types --- packages/storage/lib/StorageReference.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/storage/lib/StorageReference.ts b/packages/storage/lib/StorageReference.ts index 000b645bbe..dca3bb5c7b 100644 --- a/packages/storage/lib/StorageReference.ts +++ b/packages/storage/lib/StorageReference.ts @@ -209,7 +209,7 @@ export default class StorageReference extends ReferenceBase implements Reference task._id, ); }), - ) as unknown as Task; + ); } /** @@ -224,7 +224,7 @@ export default class StorageReference extends ReferenceBase implements Reference return new StorageUploadTask(this, task => this._storage.native.putString(this.toString(), _string, _format, _metadata, task._id), - ) as unknown as Task; + ); } /** @@ -262,7 +262,7 @@ export default class StorageReference extends ReferenceBase implements Reference return new StorageDownloadTask(this, task => this._storage.native.writeToFile(this.toString(), toFilePath(filePath), task._id), - ) as unknown as Task; + ); } /** @@ -281,7 +281,7 @@ export default class StorageReference extends ReferenceBase implements Reference return new StorageUploadTask(this, task => this._storage.native.putFile(this.toString(), toFilePath(filePath), metadata, task._id), - ) as unknown as Task; + ); } _updateString( From 37daabefbfc23538df00de0fe19bf6c911a214f4 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Mon, 2 Feb 2026 16:14:41 +0000 Subject: [PATCH 29/36] fix: types in utils --- packages/storage/lib/types/internal.ts | 2 ++ packages/storage/lib/utils.ts | 16 +++++++++++++--- packages/storage/tsconfig.json | 3 ++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/storage/lib/types/internal.ts b/packages/storage/lib/types/internal.ts index 817b3399a0..c47f87531c 100644 --- a/packages/storage/lib/types/internal.ts +++ b/packages/storage/lib/types/internal.ts @@ -15,6 +15,7 @@ * */ +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'; @@ -27,6 +28,7 @@ export type StorageInternal = Storage & { _customUrlOrRegion: string | null; emitter: EventEmitter; eventNameForApp: (...args: Array) => string; + _config: ModuleConfig; }; /** diff --git a/packages/storage/lib/utils.ts b/packages/storage/lib/utils.ts index 9d52bc5a62..b80d4706d4 100644 --- a/packages/storage/lib/utils.ts +++ b/packages/storage/lib/utils.ts @@ -18,6 +18,8 @@ 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', @@ -30,18 +32,26 @@ const SETTABLE_FIELDS = [ ] as const; export async function handleStorageEvent( - storageInstance: any, + storageInstance: StorageInternal, event: { taskId: string; eventName: string; - body?: { error?: any }; + 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); diff --git a/packages/storage/tsconfig.json b/packages/storage/tsconfig.json index 53dca73192..1c070c526d 100644 --- a/packages/storage/tsconfig.json +++ b/packages/storage/tsconfig.json @@ -13,7 +13,8 @@ "../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": ["../app/dist/typescript/lib"], + "@react-native-firebase/app/dist/module/types/internal": ["../app/dist/typescript/lib/types/internal"], } }, "include": ["lib/**/*"], From 0ffafc1df4b9781f2e6468c5b2414336372d2d80 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Mon, 2 Feb 2026 16:15:40 +0000 Subject: [PATCH 30/36] fix: update any to settablemetadata --- packages/storage/lib/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/storage/lib/utils.ts b/packages/storage/lib/utils.ts index b80d4706d4..b84de083e5 100644 --- a/packages/storage/lib/utils.ts +++ b/packages/storage/lib/utils.ts @@ -76,7 +76,7 @@ export function getGsUrlParts(url: string): { bucket: string; path: string } { return { bucket, path }; } -export function validateMetadata(metadata: any, update = true): SettableMetadata { +export function validateMetadata(metadata: SettableMetadata, update = true): SettableMetadata { if (!isObject(metadata)) { throw new Error('firebase.storage.SettableMetadata must be an object value if provided.'); } @@ -115,5 +115,5 @@ export function validateMetadata(metadata: any, update = true): SettableMetadata } } - return metadata as SettableMetadata; + return metadata; } From 0298e44a10fc25bf177e34d71f73560ec939097e Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Mon, 2 Feb 2026 16:41:48 +0000 Subject: [PATCH 31/36] test: update jest paths --- tsconfig-jest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/tsconfig-jest.json b/tsconfig-jest.json index 46819e73f5..adbf0f2c81 100644 --- a/tsconfig-jest.json +++ b/tsconfig-jest.json @@ -8,6 +8,7 @@ "@react-native-firebase/app/dist/module/common/*": ["packages/app/lib/common/*"], "@react-native-firebase/app/dist/module/internal": ["packages/app/lib/internal"], "@react-native-firebase/app/dist/module/internal/*": ["packages/app/lib/internal/*"], + "@react-native-firebase/app/dist/module/types/internal": ["packages/app/lib/types/internal"], "@react-native-firebase/auth": ["packages/auth/lib"], "@react-native-firebase/app-check": ["packages/app-check/lib"], "@react-native-firebase/ai": ["packages/ai/lib"] From fd9273229340ebaf43eea6969a0af76dcf4cf1b2 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Mon, 2 Feb 2026 16:56:31 +0000 Subject: [PATCH 32/36] chore: temp fix to ios workflow --- .github/workflows/tests_e2e_ios.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests_e2e_ios.yml b/.github/workflows/tests_e2e_ios.yml index 71e5088ad7..4fe59c8e8f 100644 --- a/.github/workflows/tests_e2e_ios.yml +++ b/.github/workflows/tests_e2e_ios.yml @@ -128,7 +128,7 @@ jobs: - uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: '26.0.1' # temporarily pinning version until simulators are defined 'latest-stable' + xcode-version: '26.2.0' # temporarily pinning version until simulators are defined 'latest-stable' - uses: actions/checkout@v4 with: From 77bc17b03de25186867400863b846a0340fb86af Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Mon, 2 Feb 2026 17:05:10 +0000 Subject: [PATCH 33/36] chore: remove modular export --- packages/storage/lib/namespaced.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/storage/lib/namespaced.ts b/packages/storage/lib/namespaced.ts index 4508975c78..512cb4344d 100644 --- a/packages/storage/lib/namespaced.ts +++ b/packages/storage/lib/namespaced.ts @@ -249,6 +249,4 @@ export const firebase = true >; -export * from './modular'; - setReactNativeModule(nativeModuleName, fallBackModule); From 813ebd8567ecdc2f6d8099dc970ffc80aaa7d30e Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Tue, 3 Feb 2026 15:45:10 +0000 Subject: [PATCH 34/36] fix: do not initialise property as null --- packages/storage/lib/namespaced.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/storage/lib/namespaced.ts b/packages/storage/lib/namespaced.ts index 512cb4344d..5135f3382f 100644 --- a/packages/storage/lib/namespaced.ts +++ b/packages/storage/lib/namespaced.ts @@ -49,7 +49,6 @@ const nativeEvents = ['storage_event']; const nativeModuleName = 'RNFBStorageModule'; class FirebaseStorageModule extends FirebaseModule { - _customUrlOrRegion: string | null = null; emulatorHost: string | undefined; emulatorPort: number; _maxUploadRetryTime: number; From ee5637fcc655f369f2ab5fb33892dbfaeb1e27f1 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Tue, 3 Feb 2026 16:31:35 +0000 Subject: [PATCH 35/36] fix: storage task observers --- packages/storage/lib/StorageTask.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/storage/lib/StorageTask.ts b/packages/storage/lib/StorageTask.ts index 41a43b7a4a..4afd32c81e 100644 --- a/packages/storage/lib/StorageTask.ts +++ b/packages/storage/lib/StorageTask.ts @@ -125,12 +125,12 @@ function subscribeToEvents( if (isFunction(nextOrObserver)) { _error = wrapErrorEventListener(error, unsubscribe); - _next = wrapSnapshotEventListener(task, nextOrObserver, unsubscribe); + _next = wrapSnapshotEventListener(task, nextOrObserver, null); _complete = wrapSnapshotEventListener(task, complete, unsubscribe); } else if (isObject(nextOrObserver)) { const observer = nextOrObserver as TaskSnapshotObserver; _error = wrapErrorEventListener(observer.error, unsubscribe); - _next = wrapSnapshotEventListener(task, observer.next, unsubscribe); + _next = wrapSnapshotEventListener(task, observer.next, null); _complete = wrapSnapshotEventListener(task, observer.complete, unsubscribe); } else if (isNull(nextOrObserver)) { _error = wrapErrorEventListener(error, unsubscribe); From 0caa7afc0b7d56f6893fa2dbae522766c0e3835c Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Tue, 3 Feb 2026 16:31:53 +0000 Subject: [PATCH 36/36] chore: example app for storage --- tests/local-tests/index.js | 2 + tests/local-tests/storage/storage.tsx | 346 ++++++++++++++++++++++++++ 2 files changed, 348 insertions(+) create mode 100644 tests/local-tests/storage/storage.tsx 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!! + + +