From 680fef26d13d6c47d435dab8b9ab09f320b297fe Mon Sep 17 00:00:00 2001 From: Austin Benoit Date: Wed, 15 Oct 2025 12:54:36 -0400 Subject: [PATCH 1/6] Feat: add UseEmulator to storage api --- release_build_files/readme.md | 6 ++++ storage/src/android/storage_android.cc | 21 ++++++++++- storage/src/android/storage_android.h | 4 +++ storage/src/common/storage.cc | 5 +++ storage/src/desktop/metadata_desktop.cc | 2 +- storage/src/desktop/storage_desktop.cc | 35 +++++++++++++++---- storage/src/desktop/storage_desktop.h | 24 +++++++++++-- storage/src/desktop/storage_path.cc | 23 ++++++++---- storage/src/desktop/storage_path.h | 16 +++++---- .../src/desktop/storage_reference_desktop.cc | 2 +- storage/src/include/firebase/storage.h | 11 ++++++ storage/src/ios/storage_ios.h | 4 +++ storage/src/ios/storage_ios.mm | 12 +++++++ 13 files changed, 141 insertions(+), 24 deletions(-) diff --git a/release_build_files/readme.md b/release_build_files/readme.md index 04fd0cbf69..90c53a631b 100644 --- a/release_build_files/readme.md +++ b/release_build_files/readme.md @@ -613,6 +613,12 @@ workflow use only during the development of your app, not for publicly shipping code. ## Release Notes +### 13.3.0 +- Changes + - Storage: Add support for Firebase Storage emulator via `UseEmulator`. + The `UseEmulator` method should be called before invoking any other + methods on a new instance of Storage. Default port is 9199. + ### 13.2.0 - Changes - General (Android): Update to Firebase Android BoM version 34.4.0. diff --git a/storage/src/android/storage_android.cc b/storage/src/android/storage_android.cc index c4d2c953cb..59b150ed99 100644 --- a/storage/src/android/storage_android.cc +++ b/storage/src/android/storage_android.cc @@ -62,7 +62,9 @@ namespace internal { "(Ljava/lang/String;)" \ "Lcom/google/firebase/storage/StorageReference;"), \ X(GetApp, "getApp", \ - "()Lcom/google/firebase/FirebaseApp;") + "()Lcom/google/firebase/FirebaseApp;"), \ + X(UseEmulator, "useEmulator", \ + "(Ljava/lang/String;I)V") // clang-format on METHOD_LOOKUP_DECLARATION(firebase_storage, FIREBASE_STORAGE_METHODS) @@ -471,6 +473,23 @@ void StorageInternal::set_max_operation_retry_time( millis); } +void StorageInternal::UseEmulator(const char* host, int port) { + JNIEnv* env = app_->GetJNIEnv(); + FIREBASE_ASSERT_MESSAGE_RETURN_VOID((host != nullptr && host[0] != '\0'), + "Emulator host cannot be null or empty.") + FIREBASE_ASSERT_MESSAGE_RETURN_VOID((port > 0), + "Emulator port must be a positive number.") + + jobject host_string = env->NewStringUTF(host); + jint port_num = static_cast(port); + + env->CallVoidMethod( + obj_, firebase_storage::GetMethodId(firebase_storage::kUseEmulator), + host_string, port_num); + env->DeleteLocalRef(host_string); + util::CheckAndClearJniExceptions(env); +} + } // namespace internal } // namespace storage } // namespace firebase diff --git a/storage/src/android/storage_android.h b/storage/src/android/storage_android.h index 4ed8774a47..b25fa42e03 100644 --- a/storage/src/android/storage_android.h +++ b/storage/src/android/storage_android.h @@ -92,6 +92,10 @@ class StorageInternal { // if a failure occurs. void set_max_operation_retry_time(double max_transfer_retry_seconds); + // Configures the Storage SDK to use an emulated backend instead of call the + // default remote backend + void UseEmulator(const char* host, int port); + // Convert an error code obtained from a Java StorageException into a C++ // Error enum. Error ErrorFromJavaErrorCode(jint java_error_code) const; diff --git a/storage/src/common/storage.cc b/storage/src/common/storage.cc index 7678bf6a31..de118c1e2c 100644 --- a/storage/src/common/storage.cc +++ b/storage/src/common/storage.cc @@ -226,5 +226,10 @@ void Storage::set_max_operation_retry_time(double max_transfer_retry_seconds) { return internal_->set_max_operation_retry_time(max_transfer_retry_seconds); } +void Storage::UseEmulator(const char* host, int port) { + if (internal_) + internal_->UseEmulator(host, port); +} + } // namespace storage } // namespace firebase diff --git a/storage/src/desktop/metadata_desktop.cc b/storage/src/desktop/metadata_desktop.cc index d0d3fc86c1..5f687fbc19 100644 --- a/storage/src/desktop/metadata_desktop.cc +++ b/storage/src/desktop/metadata_desktop.cc @@ -133,7 +133,7 @@ const char* MetadataInternal::download_url() const { std::string MetadataInternal::GetPathFromToken(const std::string& token) const { std::string http_url = - StoragePath("gs://" + bucket_ + "/" + path_).AsHttpUrl(); + StoragePath(storage_internal_, "gs://" + bucket_ + "/" + path_).AsHttpUrl(); if (!token.empty()) http_url += "&token=" + token; return http_url; } diff --git a/storage/src/desktop/storage_desktop.cc b/storage/src/desktop/storage_desktop.cc index 10ed0896b5..c87bc6906b 100644 --- a/storage/src/desktop/storage_desktop.cc +++ b/storage/src/desktop/storage_desktop.cc @@ -35,10 +35,10 @@ StorageInternal::StorageInternal(App* app, const char* url) { if (url) { url_ = url; - root_ = StoragePath(url_); + root_ = StoragePath(this, url_); } else { const char* bucket = app->options().storage_bucket(); - root_ = StoragePath(bucket ? std::string(kGsScheme) + bucket : ""); + root_ = StoragePath(this, bucket ? std::string(kGsScheme) + bucket : ""); } // LINT.IfChange @@ -76,20 +76,22 @@ StorageInternal::~StorageInternal() { } // Get a StorageReference to the root of the database. -StorageReferenceInternal* StorageInternal::GetReference() const { +StorageReferenceInternal* StorageInternal::GetReference() { + configured_ = true; return new StorageReferenceInternal(url_, const_cast(this)); } // Get a StorageReference for the specified path. -StorageReferenceInternal* StorageInternal::GetReference( - const char* path) const { +StorageReferenceInternal* StorageInternal::GetReference(const char* path) { + configured_ = true; return new StorageReferenceInternal(root_.GetChild(path), const_cast(this)); } // Get a StorageReference for the provided URL. StorageReferenceInternal* StorageInternal::GetReferenceFromUrl( - const char* url) const { + const char* url) { + configured_ = true; return new StorageReferenceInternal(url, const_cast(this)); } @@ -134,6 +136,27 @@ void StorageInternal::CleanupCompletedOperations() { } } +void StorageInternal::UseEmulator(const char* host, int port) { + if (host == nullptr || host[0] == '\0') { + throw std::invalid_argument("Emulator host cannot be null or empty."); + } + host_ = host; + + if (port <= 0) { + throw std::invalid_argument("Emulator port must be a positive number."); + } + port_ = port; + + if (configured_) { + throw std::logic_error( + "Cannot connect to emulator after Storage SDK initialization. " + "Call use_emulator(host, port) before creating a Storage " + "reference or trying to load data."); + } + + scheme_ = "http"; +} + } // namespace internal } // namespace storage } // namespace firebase diff --git a/storage/src/desktop/storage_desktop.h b/storage/src/desktop/storage_desktop.h index d20f544952..3e4bc66f82 100644 --- a/storage/src/desktop/storage_desktop.h +++ b/storage/src/desktop/storage_desktop.h @@ -43,13 +43,13 @@ class StorageInternal { std::string url() { return url_; } // Get a StorageReference to the root of the database. - StorageReferenceInternal* GetReference() const; + StorageReferenceInternal* GetReference(); // Get a StorageReference for the specified path. - StorageReferenceInternal* GetReference(const char* path) const; + StorageReferenceInternal* GetReference(const char* path); // Get a StorageReference for the provided URL. - StorageReferenceInternal* GetReferenceFromUrl(const char* url) const; + StorageReferenceInternal* GetReferenceFromUrl(const char* url); // Returns the maximum time (in seconds) to retry a download if a failure // occurs. @@ -99,6 +99,19 @@ class StorageInternal { // Remove an operation from the list of outstanding operations. void RemoveOperation(RestOperation* operation); + // Configures the Storage SDK to use an emulated backend instead of call the + // default remote backend + void UseEmulator(const char* host, int port); + + // Returns the Host for the storage backend + std::string get_host() { return host_; } + + // Returns the Port for the storage backend + int get_port() { return port_; } + + // Returns the url scheme currenly in use for the storage backend + std::string get_scheme() {return scheme_;} + private: // Clean up completed operations. void CleanupCompletedOperations(); @@ -119,6 +132,11 @@ class StorageInternal { std::string user_agent_; Mutex operations_mutex_; std::vector operations_; + std::string host_ = "firebasestorage.googleapis.com"; + std::string scheme_ = "https"; + int port_ = 443; + bool configured_; + }; } // namespace internal diff --git a/storage/src/desktop/storage_path.cc b/storage/src/desktop/storage_path.cc index 56d0666e84..c14f025f2c 100644 --- a/storage/src/desktop/storage_path.cc +++ b/storage/src/desktop/storage_path.cc @@ -17,9 +17,11 @@ #include #include +#include #include "app/rest/util.h" #include "app/src/include/firebase/internal/common.h" +#include "storage/src/desktop/storage_desktop.h" namespace firebase { namespace storage { @@ -38,8 +40,10 @@ const char kBucketStartString[] = "firebasestorage.googleapis.com/v0/b/"; const size_t kBucketStartStringLength = FIREBASE_STRLEN(kBucketStartString); const char kBucketEndString[] = "/o/"; const size_t kBucketEndStringLength = FIREBASE_STRLEN(kBucketEndString); +const char kBucketIdentifierString[] = "/v0/b/"; -StoragePath::StoragePath(const std::string& path) { +StoragePath::StoragePath(StorageInternal *storage, const std::string& path) { + storage_internal_ = storage; bucket_ = ""; path_ = Path(""); if (path.compare(0, kGsSchemeLength, kGsScheme) == 0) { @@ -56,8 +60,10 @@ StoragePath::StoragePath(const std::string& path) { // Constructs a storage path, based on raw strings for the bucket, path, and // object. -StoragePath::StoragePath(const std::string& bucket, const std::string& path, +StoragePath::StoragePath(StorageInternal *storage, + const std::string& bucket, const std::string& path, const std::string& object) { + storage_internal_ = storage; bucket_ = bucket; path_ = Path(path).GetChild(object); } @@ -97,15 +103,20 @@ void StoragePath::ConstructFromHttpUrl(const std::string& url, int path_start) { std::string StoragePath::AsHttpUrl() const { static const char* kUrlEnd = "?alt=media"; // Construct the URL. Final format is: - // https://[projectname].googleapis.com/v0/b/[bucket]/o/[path and/or object] + // http[s]://[host]:[port]/v0/b/[bucket]/o/[path and/or object] return AsHttpMetadataUrl() + kUrlEnd; } std::string StoragePath::AsHttpMetadataUrl() const { // Construct the URL. Final format is: - // https://[projectname].googleapis.com/v0/b/[bucket]/o/[path and/or object] - std::string result = kHttpsScheme; - result += kBucketStartString; + // [scheme]://[host]:[port]/v0/b/[bucket]/o/[path and/or object] + + std::string result = storage_internal_->get_scheme(); + result += "://"; + result += storage_internal_->get_host(); + result += ":"; + result += std::to_string(storage_internal_->get_port()); + result += kBucketIdentifierString; result += bucket_; result += kBucketEndString; result += rest::util::EncodeUrl(path_.str()); diff --git a/storage/src/desktop/storage_path.h b/storage/src/desktop/storage_path.h index 4f7bc6ac19..71bce62282 100644 --- a/storage/src/desktop/storage_path.h +++ b/storage/src/desktop/storage_path.h @@ -25,6 +25,8 @@ namespace internal { extern const char kGsScheme[]; +class StorageInternal; + // Class for managing paths for firebase storage. // Storage paths are made up of a bucket, a path, // and (optionally) an object, located at that path. @@ -35,11 +37,11 @@ class StoragePath { // Constructs a storage path, based on an input URL. The URL can either be // an HTTP[s] link, or a gs URI. - explicit StoragePath(const std::string& path); + explicit StoragePath(StorageInternal *storage, const std::string& path); // Constructs a storage path, based on raw strings for the bucket, path, and // object. - StoragePath(const std::string& bucket, const std::string& path, + StoragePath(StorageInternal *storage, const std::string& bucket, const std::string& path, const std::string& object = ""); // The bucket portion of this path. @@ -60,14 +62,14 @@ class StoragePath { // in a path where bucket is "bucket", local_path is "path/otherchild/" and // object is an empty string. StoragePath GetChild(const std::string& path) const { - return StoragePath(bucket_, path_.GetChild(path)); + return StoragePath(storage_internal_, bucket_, path_.GetChild(path)); } // Returns the location one folder up from the current location. If the // path is at already at the root level, this returns the path unchanged. // The Object in the result is always set to empty. StoragePath GetParent() const { - return StoragePath(bucket_, path_.GetParent()); + return StoragePath(storage_internal_, bucket_, path_.GetParent()); } // Returns the path as a HTTP URL to the asset. @@ -82,14 +84,16 @@ class StoragePath { private: static const char* const kSeparator; - StoragePath(const std::string& bucket, const Path& path) - : bucket_(bucket), path_(path) {} + StoragePath(StorageInternal *storage, const std::string& bucket, const Path& path) + : storage_internal_(storage), bucket_(bucket), path_(path) {} void ConstructFromGsUri(const std::string& uri, int path_start); void ConstructFromHttpUrl(const std::string& url, int path_start); std::string bucket_; Path path_; + StorageInternal* storage_internal_; + }; } // namespace internal diff --git a/storage/src/desktop/storage_reference_desktop.cc b/storage/src/desktop/storage_reference_desktop.cc index aa99863e3b..7636af68b7 100644 --- a/storage/src/desktop/storage_reference_desktop.cc +++ b/storage/src/desktop/storage_reference_desktop.cc @@ -48,7 +48,7 @@ namespace internal { StorageReferenceInternal::StorageReferenceInternal( const std::string& storageUri, StorageInternal* storage) - : storage_(storage), storageUri_(storageUri) { + : storage_(storage), storageUri_(storage, storageUri) { storage_->future_manager().AllocFutureApi(this, kStorageReferenceFnCount); } diff --git a/storage/src/include/firebase/storage.h b/storage/src/include/firebase/storage.h index 8d081e4c0e..94c7b8fc94 100644 --- a/storage/src/include/firebase/storage.h +++ b/storage/src/include/firebase/storage.h @@ -139,6 +139,17 @@ class Storage { /// download if a failure occurs. Defaults to 120 seconds (2 minutes). void set_max_operation_retry_time(double max_transfer_retry_seconds); + /// @brief Configures the Storage SDK to use an emulated backend instead of + /// the default remote backend. This method should be called before invoking + /// any other methods on a new instance of Storage + void UseEmulator(const std::string& host, int port) { + UseEmulator(host.c_str(), port); + } + /// @brief Configures the Storage SDK to use an emulated backend instead of + /// the default remote backend. This method should be called before invoking + /// any other methods on a new instance of Storage + void UseEmulator(const char* host, int port); + private: /// @cond FIREBASE_APP_INTERNAL friend class Metadata; diff --git a/storage/src/ios/storage_ios.h b/storage/src/ios/storage_ios.h index 9a0a09dcd0..8cd23fc506 100644 --- a/storage/src/ios/storage_ios.h +++ b/storage/src/ios/storage_ios.h @@ -92,6 +92,10 @@ class StorageInternal { // if a failure occurs. void set_max_operation_retry_time(double max_transfer_retry_seconds); + // Configures the Storage SDK to use an emulated backend instead of call the + // default remote backend + void UseEmulator(const char* _Nullable host, int port); + FutureManager& future_manager() { return future_manager_; } // Whether this object was successfully initialized by the constructor. diff --git a/storage/src/ios/storage_ios.mm b/storage/src/ios/storage_ios.mm index ccad7e3b2d..ef3fd970b7 100644 --- a/storage/src/ios/storage_ios.mm +++ b/storage/src/ios/storage_ios.mm @@ -22,6 +22,7 @@ #include "storage/src/ios/storage_reference_ios.h" #import "FirebaseStorage-Swift.h" +#include namespace firebase { namespace storage { @@ -97,6 +98,17 @@ impl().maxOperationRetryTime = max_transfer_retry_seconds; } +void StorageInternal::UseEmulator(const char* host, int port) { + + NSCAssert(host && host[0] != '\0', @"Emulator host cannot be null or empty."); + + NSCAssert(port > 0, @"Emulator port must be a positive number."); + + NSString *hostString = [NSString stringWithUTF8String:host]; + + [impl() useEmulatorWithHost:hostString port:port]; +} + // Whether this object was successfully initialized by the constructor. bool StorageInternal::initialized() const { return impl() != nil; } From e52e3ecf006454b82e4260b4c4ea5f993ac03109 Mon Sep 17 00:00:00 2001 From: Austin Benoit Date: Tue, 21 Oct 2025 17:12:50 -0400 Subject: [PATCH 2/6] Fix formating --- storage/src/android/storage_android.cc | 4 ++-- storage/src/common/storage.cc | 3 +-- storage/src/desktop/metadata_desktop.cc | 3 ++- storage/src/desktop/storage_desktop.h | 3 +-- storage/src/desktop/storage_path.cc | 9 ++++----- storage/src/desktop/storage_path.h | 12 ++++++------ storage/src/desktop/storage_reference_desktop.cc | 2 +- 7 files changed, 17 insertions(+), 19 deletions(-) diff --git a/storage/src/android/storage_android.cc b/storage/src/android/storage_android.cc index 59b150ed99..eecb6ed459 100644 --- a/storage/src/android/storage_android.cc +++ b/storage/src/android/storage_android.cc @@ -477,8 +477,8 @@ void StorageInternal::UseEmulator(const char* host, int port) { JNIEnv* env = app_->GetJNIEnv(); FIREBASE_ASSERT_MESSAGE_RETURN_VOID((host != nullptr && host[0] != '\0'), "Emulator host cannot be null or empty.") - FIREBASE_ASSERT_MESSAGE_RETURN_VOID((port > 0), - "Emulator port must be a positive number.") + FIREBASE_ASSERT_MESSAGE_RETURN_VOID( + (port > 0), "Emulator port must be a positive number.") jobject host_string = env->NewStringUTF(host); jint port_num = static_cast(port); diff --git a/storage/src/common/storage.cc b/storage/src/common/storage.cc index de118c1e2c..9e5392b524 100644 --- a/storage/src/common/storage.cc +++ b/storage/src/common/storage.cc @@ -227,8 +227,7 @@ void Storage::set_max_operation_retry_time(double max_transfer_retry_seconds) { } void Storage::UseEmulator(const char* host, int port) { - if (internal_) - internal_->UseEmulator(host, port); + if (internal_) internal_->UseEmulator(host, port); } } // namespace storage diff --git a/storage/src/desktop/metadata_desktop.cc b/storage/src/desktop/metadata_desktop.cc index 5f687fbc19..a931f248fd 100644 --- a/storage/src/desktop/metadata_desktop.cc +++ b/storage/src/desktop/metadata_desktop.cc @@ -133,7 +133,8 @@ const char* MetadataInternal::download_url() const { std::string MetadataInternal::GetPathFromToken(const std::string& token) const { std::string http_url = - StoragePath(storage_internal_, "gs://" + bucket_ + "/" + path_).AsHttpUrl(); + StoragePath(storage_internal_, "gs://" + bucket_ + "/" + path_) + .AsHttpUrl(); if (!token.empty()) http_url += "&token=" + token; return http_url; } diff --git a/storage/src/desktop/storage_desktop.h b/storage/src/desktop/storage_desktop.h index 3e4bc66f82..3e493507f5 100644 --- a/storage/src/desktop/storage_desktop.h +++ b/storage/src/desktop/storage_desktop.h @@ -110,7 +110,7 @@ class StorageInternal { int get_port() { return port_; } // Returns the url scheme currenly in use for the storage backend - std::string get_scheme() {return scheme_;} + std::string get_scheme() { return scheme_; } private: // Clean up completed operations. @@ -136,7 +136,6 @@ class StorageInternal { std::string scheme_ = "https"; int port_ = 443; bool configured_; - }; } // namespace internal diff --git a/storage/src/desktop/storage_path.cc b/storage/src/desktop/storage_path.cc index c14f025f2c..c07766d987 100644 --- a/storage/src/desktop/storage_path.cc +++ b/storage/src/desktop/storage_path.cc @@ -16,8 +16,8 @@ #include -#include #include +#include #include "app/rest/util.h" #include "app/src/include/firebase/internal/common.h" @@ -42,7 +42,7 @@ const char kBucketEndString[] = "/o/"; const size_t kBucketEndStringLength = FIREBASE_STRLEN(kBucketEndString); const char kBucketIdentifierString[] = "/v0/b/"; -StoragePath::StoragePath(StorageInternal *storage, const std::string& path) { +StoragePath::StoragePath(StorageInternal* storage, const std::string& path) { storage_internal_ = storage; bucket_ = ""; path_ = Path(""); @@ -60,9 +60,8 @@ StoragePath::StoragePath(StorageInternal *storage, const std::string& path) { // Constructs a storage path, based on raw strings for the bucket, path, and // object. -StoragePath::StoragePath(StorageInternal *storage, - const std::string& bucket, const std::string& path, - const std::string& object) { +StoragePath::StoragePath(StorageInternal* storage, const std::string& bucket, + const std::string& path, const std::string& object) { storage_internal_ = storage; bucket_ = bucket; path_ = Path(path).GetChild(object); diff --git a/storage/src/desktop/storage_path.h b/storage/src/desktop/storage_path.h index 71bce62282..bffc7c79bb 100644 --- a/storage/src/desktop/storage_path.h +++ b/storage/src/desktop/storage_path.h @@ -37,12 +37,12 @@ class StoragePath { // Constructs a storage path, based on an input URL. The URL can either be // an HTTP[s] link, or a gs URI. - explicit StoragePath(StorageInternal *storage, const std::string& path); + explicit StoragePath(StorageInternal* storage, const std::string& path); // Constructs a storage path, based on raw strings for the bucket, path, and // object. - StoragePath(StorageInternal *storage, const std::string& bucket, const std::string& path, - const std::string& object = ""); + StoragePath(StorageInternal* storage, const std::string& bucket, + const std::string& path, const std::string& object = ""); // The bucket portion of this path. // In the path: MyBucket/folder/object, it would return "MyBucket". @@ -84,8 +84,9 @@ class StoragePath { private: static const char* const kSeparator; - StoragePath(StorageInternal *storage, const std::string& bucket, const Path& path) - : storage_internal_(storage), bucket_(bucket), path_(path) {} + StoragePath(StorageInternal* storage, const std::string& bucket, + const Path& path) + : storage_internal_(storage), bucket_(bucket), path_(path) {} void ConstructFromGsUri(const std::string& uri, int path_start); void ConstructFromHttpUrl(const std::string& url, int path_start); @@ -93,7 +94,6 @@ class StoragePath { std::string bucket_; Path path_; StorageInternal* storage_internal_; - }; } // namespace internal diff --git a/storage/src/desktop/storage_reference_desktop.cc b/storage/src/desktop/storage_reference_desktop.cc index 7636af68b7..11c1be720a 100644 --- a/storage/src/desktop/storage_reference_desktop.cc +++ b/storage/src/desktop/storage_reference_desktop.cc @@ -48,7 +48,7 @@ namespace internal { StorageReferenceInternal::StorageReferenceInternal( const std::string& storageUri, StorageInternal* storage) - : storage_(storage), storageUri_(storage, storageUri) { + : storage_(storage), storageUri_(storage, storageUri) { storage_->future_manager().AllocFutureApi(this, kStorageReferenceFnCount); } From 60260761ef2e3c3992e67162d4b4ccd1c81438c7 Mon Sep 17 00:00:00 2001 From: Austin Benoit Date: Tue, 21 Oct 2025 17:14:47 -0400 Subject: [PATCH 3/6] Update storage/src/desktop/storage_desktop.h Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- storage/src/desktop/storage_desktop.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storage/src/desktop/storage_desktop.h b/storage/src/desktop/storage_desktop.h index 3e493507f5..d0020c96ea 100644 --- a/storage/src/desktop/storage_desktop.h +++ b/storage/src/desktop/storage_desktop.h @@ -135,7 +135,7 @@ class StorageInternal { std::string host_ = "firebasestorage.googleapis.com"; std::string scheme_ = "https"; int port_ = 443; - bool configured_; + bool configured_ = false; }; } // namespace internal From 93db81c4a16e88bafab0c0ed37503c38b25d32c8 Mon Sep 17 00:00:00 2001 From: Austin Benoit Date: Tue, 21 Oct 2025 17:15:07 -0400 Subject: [PATCH 4/6] Update release_build_files/readme.md Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- release_build_files/readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/release_build_files/readme.md b/release_build_files/readme.md index 90c53a631b..8f71593877 100644 --- a/release_build_files/readme.md +++ b/release_build_files/readme.md @@ -616,8 +616,8 @@ code. ### 13.3.0 - Changes - Storage: Add support for Firebase Storage emulator via `UseEmulator`. - The `UseEmulator` method should be called before invoking any other - methods on a new instance of Storage. Default port is 9199. + The `UseEmulator` method should be called before invoking any other + methods on a new instance of Storage. Default port is 9199. ### 13.2.0 - Changes From de56bde2f8ae99b1bef767491cee999980895968 Mon Sep 17 00:00:00 2001 From: Austin Benoit Date: Tue, 21 Oct 2025 21:41:22 -0400 Subject: [PATCH 5/6] Fix the storage util as storagePath now needs storageinternal ref To get the url to show up correctly the storage path now depends upon the storage internal to query for the host and port --- .../desktop/storage_desktop_utils_tests.cc | 48 +++++++++++-------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/storage/tests/desktop/storage_desktop_utils_tests.cc b/storage/tests/desktop/storage_desktop_utils_tests.cc index 7322cab8a6..79d4db83a1 100644 --- a/storage/tests/desktop/storage_desktop_utils_tests.cc +++ b/storage/tests/desktop/storage_desktop_utils_tests.cc @@ -48,23 +48,23 @@ TEST_F(StorageDesktopUtilsTests, testGSStoragePathConstructors) { StoragePath test_path; // Test basic case: - test_path = StoragePath("gs://Bucket/path/Object"); + test_path = StoragePath(nullptr, "gs://Bucket/path/Object"); EXPECT_STREQ(test_path.GetBucket().c_str(), "Bucket"); EXPECT_STREQ(test_path.GetPath().c_str(), "path/Object"); // Test a more complex path: - test_path = StoragePath("gs://Bucket/path/morepath/Object"); + test_path = StoragePath(nullptr, "gs://Bucket/path/morepath/Object"); EXPECT_STREQ(test_path.GetBucket().c_str(), "Bucket"); EXPECT_STREQ(test_path.GetPath().c_str(), "path/morepath/Object"); // Extra slashes: - test_path = StoragePath("gs://Bucket/path////Object"); + test_path = StoragePath(nullptr, "gs://Bucket/path////Object"); EXPECT_STREQ(test_path.GetBucket().c_str(), "Bucket"); EXPECT_STREQ(test_path.GetPath().c_str(), "path/Object"); // Path with no Object: - test_path = StoragePath("gs://Bucket/path////more////"); + test_path = StoragePath(nullptr, "gs://Bucket/path////more////"); EXPECT_STREQ(test_path.GetBucket().c_str(), "Bucket"); EXPECT_STREQ(test_path.GetPath().c_str(), "path/more"); } @@ -76,29 +76,30 @@ TEST_F(StorageDesktopUtilsTests, testHTTPStoragePathConstructors) { std::string intended_path_result = "path/to/Object/Object.data"; // Test basic case: - test_path = StoragePath( - "http://firebasestorage.googleapis.com/v0/b/Bucket/o/" - "path%2fto%2FObject%2fObject.data"); + test_path = StoragePath(nullptr, + "http://firebasestorage.googleapis.com/v0/b/Bucket/o/" + "path%2fto%2FObject%2fObject.data"); EXPECT_STREQ(test_path.GetBucket().c_str(), intended_bucket_result.c_str()); EXPECT_STREQ(test_path.GetPath().c_str(), intended_path_result.c_str()); // httpS (instead of http): - test_path = StoragePath( - "https://firebasestorage.googleapis.com/v0/b/Bucket/o/" - "path%2fto%2FObject%2fObject.data"); + test_path = + StoragePath(nullptr, + "https://firebasestorage.googleapis.com/v0/b/Bucket/o/" + "path%2fto%2FObject%2fObject.data"); EXPECT_STREQ(test_path.GetBucket().c_str(), intended_bucket_result.c_str()); EXPECT_STREQ(test_path.GetPath().c_str(), intended_path_result.c_str()); // Extra slashes: - test_path = StoragePath( - "http://firebasestorage.googleapis.com/v0/b/Bucket/o/" - "path%2f%2f%2f%2fto%2FObject%2f%2f%2f%2fObject.data"); + test_path = StoragePath(nullptr, + "http://firebasestorage.googleapis.com/v0/b/Bucket/o/" + "path%2f%2f%2f%2fto%2FObject%2f%2f%2f%2fObject.data"); EXPECT_STREQ(test_path.GetBucket().c_str(), intended_bucket_result.c_str()); EXPECT_STREQ(test_path.GetPath().c_str(), intended_path_result.c_str()); } TEST_F(StorageDesktopUtilsTests, testInvalidConstructors) { - StoragePath bad_path("argleblargle://Bucket/path1/path2/Object"); + StoragePath bad_path(nullptr, "argleblargle://Bucket/path1/path2/Object"); EXPECT_FALSE(bad_path.IsValid()); } @@ -107,12 +108,12 @@ TEST_F(StorageDesktopUtilsTests, testStoragePathParent) { StoragePath test_path; // Test parent, when there is an GetObject. - test_path = StoragePath("gs://Bucket/path/Object").GetParent(); + test_path = StoragePath(nullptr, "gs://Bucket/path/Object").GetParent(); EXPECT_STREQ(test_path.GetBucket().c_str(), "Bucket"); EXPECT_STREQ(test_path.GetPath().c_str(), "path"); // Test parent with no GetObject. - test_path = StoragePath("gs://Bucket/path/morepath/").GetParent(); + test_path = StoragePath(nullptr, "gs://Bucket/path/morepath/").GetParent(); EXPECT_STREQ(test_path.GetBucket().c_str(), "Bucket"); EXPECT_STREQ(test_path.GetPath().c_str(), "path"); } @@ -122,27 +123,32 @@ TEST_F(StorageDesktopUtilsTests, testStoragePathChild) { StoragePath test_path; // Test child when there is no object. - test_path = StoragePath("gs://Bucket/path/morepath/").GetChild("newobj"); + test_path = + StoragePath(nullptr, "gs://Bucket/path/morepath/").GetChild("newobj"); EXPECT_STREQ(test_path.GetBucket().c_str(), "Bucket"); EXPECT_STREQ(test_path.GetPath().c_str(), "path/morepath/newobj"); // Test child when there is an object. - test_path = StoragePath("gs://Bucket/path/object").GetChild("newpath/"); + test_path = + StoragePath(nullptr, "gs://Bucket/path/object").GetChild("newpath/"); EXPECT_STREQ(test_path.GetBucket().c_str(), "Bucket"); EXPECT_STREQ(test_path.GetPath().c_str(), "path/object/newpath"); } TEST_F(StorageDesktopUtilsTests, testUrlConverter) { - StoragePath test_path("gs://Bucket/path1/path2/Object"); + std::unique_ptr app(firebase::testing::CreateApp()); + StorageInternal* storage = new StorageInternal(app.get(), "gs://Bucket"); + + StoragePath test_path(storage, "gs://Bucket/path1/path2/Object"); EXPECT_STREQ(test_path.GetBucket().c_str(), "Bucket"); EXPECT_STREQ(test_path.GetPath().c_str(), "path1/path2/Object"); EXPECT_STREQ(test_path.AsHttpUrl().c_str(), - "https://firebasestorage.googleapis.com" + "https://firebasestorage.googleapis.com:443" "/v0/b/Bucket/o/path1%2Fpath2%2FObject?alt=media"); EXPECT_STREQ(test_path.AsHttpMetadataUrl().c_str(), - "https://firebasestorage.googleapis.com" + "https://firebasestorage.googleapis.com:443" "/v0/b/Bucket/o/path1%2Fpath2%2FObject"); } From ad03abcf6a8f071edca79ad4633e17e4121e3d09 Mon Sep 17 00:00:00 2001 From: Austin Benoit Date: Wed, 22 Oct 2025 10:09:33 -0400 Subject: [PATCH 6/6] Change the Release Notes and change the UseEmulator to give errors --- release_build_files/readme.md | 2 +- storage/src/desktop/storage_desktop.cc | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/release_build_files/readme.md b/release_build_files/readme.md index 8f71593877..d8452c5f24 100644 --- a/release_build_files/readme.md +++ b/release_build_files/readme.md @@ -613,7 +613,7 @@ workflow use only during the development of your app, not for publicly shipping code. ## Release Notes -### 13.3.0 +### Upcoming - Changes - Storage: Add support for Firebase Storage emulator via `UseEmulator`. The `UseEmulator` method should be called before invoking any other diff --git a/storage/src/desktop/storage_desktop.cc b/storage/src/desktop/storage_desktop.cc index c87bc6906b..4d311ea11c 100644 --- a/storage/src/desktop/storage_desktop.cc +++ b/storage/src/desktop/storage_desktop.cc @@ -23,6 +23,7 @@ #include "app/src/app_common.h" #include "app/src/function_registry.h" #include "app/src/include/firebase/app.h" +#include "app/src/log.h" #include "storage/src/desktop/rest_operation.h" #include "storage/src/desktop/storage_reference_desktop.h" @@ -138,23 +139,26 @@ void StorageInternal::CleanupCompletedOperations() { void StorageInternal::UseEmulator(const char* host, int port) { if (host == nullptr || host[0] == '\0') { - throw std::invalid_argument("Emulator host cannot be null or empty."); + LogError("Emulator host cannot be null or empty."); + return; } - host_ = host; if (port <= 0) { - throw std::invalid_argument("Emulator port must be a positive number."); + LogError("Emulator port must be a positive number."); + return; } - port_ = port; if (configured_) { - throw std::logic_error( + LogError( "Cannot connect to emulator after Storage SDK initialization. " "Call use_emulator(host, port) before creating a Storage " "reference or trying to load data."); + return; } scheme_ = "http"; + port_ = port; + host_ = host; } } // namespace internal