diff --git a/app/src/util_android.cc b/app/src/util_android.cc index 34d2f39a7f..71ba7d2ce1 100644 --- a/app/src/util_android.cc +++ b/app/src/util_android.cc @@ -118,6 +118,15 @@ METHOD_LOOKUP_DECLARATION(uribuilder, URI_BUILDER_METHODS) METHOD_LOOKUP_DEFINITION(uribuilder, "android/net/Uri$Builder", URI_BUILDER_METHODS) +// Methods of the java.net.URL class. +// clang-format off +#define URL_METHODS(X) \ + X(Constructor, "", "(Ljava/lang/String;)V"), \ + X(ConstructorWithURL, "", "(Ljava/net/URL;Ljava/lang/String;)V") +// clang-format on +METHOD_LOOKUP_DECLARATION(url, URL_METHODS) +METHOD_LOOKUP_DEFINITION(url, "java/net/URL", URL_METHODS) + // clang-format off #define FILE_OUTPUT_STREAM_METHODS(X) \ X(ConstructorFile, "", "(Ljava/io/File;)V"), \ @@ -151,13 +160,6 @@ METHOD_LOOKUP_DECLARATION(url_class_loader, URL_CLASS_LOADER_METHODS) METHOD_LOOKUP_DEFINITION(url_class_loader, "java/net/URLClassLoader", URL_CLASS_LOADER_METHODS) -// clang-format off -#define URL_METHODS(X) \ - X(Constructor, "", "(Ljava/net/URL;Ljava/lang/String;)V") -// clang-format on -METHOD_LOOKUP_DECLARATION(url, URL_METHODS) -METHOD_LOOKUP_DEFINITION(url, "java/net/URL", URL_METHODS) - // clang-format off #define JAVA_URI_METHODS(X) X(ToUrl, "toURL", "()Ljava/net/URL;") // clang-format on @@ -395,6 +397,7 @@ static void ReleaseClasses(JNIEnv* env) { uri::ReleaseClass(env); object::ReleaseClass(env); uribuilder::ReleaseClass(env); + url::ReleaseClass(env); if (g_jniresultcallback_loaded) { jniresultcallback::ReleaseClass(env); g_jniresultcallback_loaded = false; @@ -402,7 +405,6 @@ static void ReleaseClasses(JNIEnv* env) { JavaThreadContext::Terminate(env); #if defined(FIREBASE_ANDROID_FOR_DESKTOP) java_uri::ReleaseClass(env); - url::ReleaseClass(env); url_class_loader::ReleaseClass(env); #endif // defined(FIREBASE_ANDROID_FOR_DESKTOP) } @@ -493,7 +495,8 @@ bool Initialize(JNIEnv* env, jobject activity_object) { throwable::CacheMethodIds(env, activity_object) && uri::CacheMethodIds(env, activity_object) && object::CacheMethodIds(env, activity_object) && - uribuilder::CacheMethodIds(env, activity_object))) { + uribuilder::CacheMethodIds(env, activity_object) && + url::CacheMethodIds(env, activity_object))) { ReleaseClasses(env); TerminateActivityClasses(env); return false; @@ -508,7 +511,6 @@ bool Initialize(JNIEnv* env, jobject activity_object) { #if defined(FIREBASE_ANDROID_FOR_DESKTOP) // Cache JVM class-loader for desktop. if (!(java_uri::CacheMethodIds(env, activity_object) && - url::CacheMethodIds(env, activity_object) && url_class_loader::CacheMethodIds(env, activity_object))) { return false; } @@ -1182,6 +1184,17 @@ jobject ParseUriString(JNIEnv* env, const char* uri_string) { return uri; } +// Convert a char array into a jobject of type java.net.URL. +// The caller must call env->DeleteLocalRef() on the returned jobject. +jobject CharsToURL(JNIEnv* env, const char* url_string) { + jobject url_jstring = env->NewStringUTF(url_string); + jobject url = env->NewObject( + url::GetClass(), url::GetMethodId(url::kConstructor), url_jstring); + CheckAndClearJniExceptions(env); + env->DeleteLocalRef(url_jstring); + return url; +} + // Convert a jbyteArray to a vector, releasing the reference to the // jbyteArray. std::vector JniByteArrayToVector(JNIEnv* env, jobject array) { @@ -1623,9 +1636,9 @@ jclass FindClassInFiles( env->NewObjectArray(embedded_files.size(), url::GetClass(), nullptr); for (int i = 0; i < embedded_files.size(); ++i) { jstring embedded_file_string = env->NewStringUTF(embedded_files[i].name); - jobject jar_url = - env->NewObject(url::GetClass(), url::GetMethodId(url::kConstructor), - cache_url, embedded_file_string); + jobject jar_url = env->NewObject(url::GetClass(), + url::GetMethodId(url::kConstructorWithURL), + cache_url, embedded_file_string); env->SetObjectArrayElement(url_path_array, i, jar_url); env->DeleteLocalRef(jar_url); env->DeleteLocalRef(embedded_file_string); diff --git a/app/src/util_android.h b/app/src/util_android.h index 8df7d8f9b5..bcf15e81a9 100644 --- a/app/src/util_android.h +++ b/app/src/util_android.h @@ -942,6 +942,10 @@ jobject CharsToJniUri(JNIEnv* env, const char* uri); // The caller must call env->DeleteLocalRef() on the returned jobject. jobject ParseUriString(JNIEnv* env, const char* uri_string); +// Convert a char array into a jobject of type java.net.URL. +// The caller must call env->DeleteLocalRef() on the returned jobject. +jobject CharsToURL(JNIEnv* env, const char* url_string); + // Convert a jbyteArray to a vector, releasing the reference to the jbyteArray. std::vector JniByteArrayToVector(JNIEnv* env, jobject array); diff --git a/functions/integration_test/src/integration_test.cc b/functions/integration_test/src/integration_test.cc index 4b58e2c4bf..3759cfdcec 100644 --- a/functions/integration_test/src/integration_test.cc +++ b/functions/integration_test/src/integration_test.cc @@ -65,12 +65,27 @@ class FirebaseFunctionsTest : public FirebaseTest { // Sign in an anonymous user. void SignIn(); + firebase::Future TestFunctionHelper( + const char* function_name, + firebase::functions::HttpsCallableReference& ref, + const firebase::Variant* const function_data, + const firebase::Variant& expected_result, + firebase::functions::Error expected_error = + firebase::functions::kErrorNone); + firebase::Future TestFunction( const char* function_name, const firebase::Variant* const function_data, const firebase::Variant& expected_result, firebase::functions::Error expected_error = firebase::functions::kErrorNone); + firebase::Future + TestFunctionFromURL(const char* function_url, + const firebase::Variant* const function_data, + const firebase::Variant& expected_result, + firebase::functions::Error expected_error = + firebase::functions::kErrorNone); + bool initialized_; firebase::auth::Auth* auth_; firebase::functions::Functions* functions_; @@ -181,15 +196,11 @@ void FirebaseFunctionsTest::SignIn() { // A helper function for calling a Firebase Function and waiting on the result. firebase::Future -FirebaseFunctionsTest::TestFunction( - const char* function_name, const firebase::Variant* const function_data, +FirebaseFunctionsTest::TestFunctionHelper( + const char* function_name, firebase::functions::HttpsCallableReference& ref, + const firebase::Variant* const function_data, const firebase::Variant& expected_result, firebase::functions::Error expected_error) { - // Create a callable that we can run our test with. - LogDebug("Calling %s", function_name); - firebase::functions::HttpsCallableReference ref; - ref = functions_->GetHttpsCallable(function_name); - firebase::Future future; if (function_data == nullptr) { future = ref.Call(); @@ -207,6 +218,34 @@ FirebaseFunctionsTest::TestFunction( return future; } +firebase::Future +FirebaseFunctionsTest::TestFunction( + const char* function_name, const firebase::Variant* const function_data, + const firebase::Variant& expected_result, + firebase::functions::Error expected_error) { + // Create a callable that we can run our test with. + LogDebug("Calling %s", function_name); + firebase::functions::HttpsCallableReference ref; + ref = functions_->GetHttpsCallable(function_name); + + return TestFunctionHelper(function_name, ref, function_data, expected_result, + expected_error); +} + +firebase::Future +FirebaseFunctionsTest::TestFunctionFromURL( + const char* function_url, const firebase::Variant* const function_data, + const firebase::Variant& expected_result, + firebase::functions::Error expected_error) { + // Create a callable that we can run our test with. + LogDebug("Calling by URL %s", function_url); + firebase::functions::HttpsCallableReference ref; + ref = functions_->GetHttpsCallableFromURL(function_url); + + return TestFunctionHelper(function_url, ref, function_data, expected_result, + expected_error); +} + TEST_F(FirebaseFunctionsTest, TestInitializeAndTerminate) { // Already tested via SetUp() and TearDown(). } @@ -323,4 +362,22 @@ TEST_F(FirebaseFunctionsTest, TestErrorHandling) { firebase::functions::kErrorOutOfRange); } +TEST_F(FirebaseFunctionsTest, TestFunctionFromURL) { + SignIn(); + + // addNumbers(4, 2) = 6 + firebase::Variant data(firebase::Variant::EmptyMap()); + data.map()["firstNumber"] = 4; + data.map()["secondNumber"] = 2; + std::string proj = app_->options().project_id(); + std::string url = + "https://us-central1-" + proj + ".cloudfunctions.net/addNumbers"; + firebase::Variant result = + TestFunctionFromURL(url.c_str(), &data, firebase::Variant::Null()) + .result() + ->data(); + EXPECT_TRUE(result.is_map()); + EXPECT_EQ(result.map()["operationResult"], 6); +} + } // namespace firebase_testapp_automated diff --git a/functions/src/android/functions_android.cc b/functions/src/android/functions_android.cc index e6d5aff7fc..7bdc6fe244 100644 --- a/functions/src/android/functions_android.cc +++ b/functions/src/android/functions_android.cc @@ -35,7 +35,11 @@ const char kApiIdentifier[] = "Functions"; X(GetHttpsCallable, "getHttpsCallable", \ "(Ljava/lang/String;)" \ "Lcom/google/firebase/functions/HttpsCallableReference;", \ - util::kMethodTypeInstance), \ + util::kMethodTypeInstance), \ + X(GetHttpsCallableFromURL, "getHttpsCallableFromUrl", \ + "(Ljava/net/URL;)" \ + "Lcom/google/firebase/functions/HttpsCallableReference;", \ + util::kMethodTypeInstance), \ X(UseFunctionsEmulator, "useFunctionsEmulator", \ "(Ljava/lang/String;)V", \ util::kMethodTypeInstance) @@ -137,6 +141,7 @@ void FunctionsInternal::Terminate(App* app) { JNIEnv* env = app->GetJNIEnv(); firebase_functions::ReleaseClass(env); functions_exception::ReleaseClass(env); + functions_exception_code::ReleaseClass(env); // Call Terminate on all other Functions internal classes. HttpsCallableReferenceInternal::Terminate(app); @@ -199,6 +204,29 @@ HttpsCallableReferenceInternal* FunctionsInternal::GetHttpsCallable( return internal; } +HttpsCallableReferenceInternal* FunctionsInternal::GetHttpsCallableFromURL( + const char* url) const { + FIREBASE_ASSERT_RETURN(nullptr, url != nullptr); + JNIEnv* env = app_->GetJNIEnv(); + jobject url_object = util::CharsToURL(env, url); + jobject callable_reference_obj = + env->CallObjectMethod(obj_, + firebase_functions::GetMethodId( + firebase_functions::kGetHttpsCallableFromURL), + url_object); + env->DeleteLocalRef(url_object); + if (util::LogException( + env, kLogLevelError, + "Functions::GetHttpsCallableFromURL() (url = %s) failed", url)) { + return nullptr; + } + HttpsCallableReferenceInternal* internal = new HttpsCallableReferenceInternal( + const_cast(this), callable_reference_obj); + env->DeleteLocalRef(callable_reference_obj); + util::CheckAndClearJniExceptions(env); + return internal; +} + void FunctionsInternal::UseFunctionsEmulator(const char* origin) { FIREBASE_ASSERT(origin != nullptr); JNIEnv* env = app_->GetJNIEnv(); diff --git a/functions/src/android/functions_android.h b/functions/src/android/functions_android.h index 5bfc7140f8..416a404798 100644 --- a/functions/src/android/functions_android.h +++ b/functions/src/android/functions_android.h @@ -52,6 +52,10 @@ class FunctionsInternal { HttpsCallableReferenceInternal* GetHttpsCallable(const char* name) const; + // Get a FunctionsReference for the specified URL. + HttpsCallableReferenceInternal* GetHttpsCallableFromURL( + const char* url) const; + void UseFunctionsEmulator(const char* origin); // Convert an error code obtained from a Java FunctionsException into a C++ diff --git a/functions/src/common/functions.cc b/functions/src/common/functions.cc index 58816c346a..fdc96e3fdd 100644 --- a/functions/src/common/functions.cc +++ b/functions/src/common/functions.cc @@ -146,6 +146,12 @@ HttpsCallableReference Functions::GetHttpsCallable(const char* name) const { return HttpsCallableReference(internal_->GetHttpsCallable(name)); } +HttpsCallableReference Functions::GetHttpsCallableFromURL( + const char* url) const { + if (!internal_) return HttpsCallableReference(); + return HttpsCallableReference(internal_->GetHttpsCallableFromURL(url)); +} + void Functions::UseFunctionsEmulator(const char* origin) { if (!internal_) return; internal_->UseFunctionsEmulator(origin); diff --git a/functions/src/desktop/callable_reference_desktop.cc b/functions/src/desktop/callable_reference_desktop.cc index b551128830..f9aa3571aa 100644 --- a/functions/src/desktop/callable_reference_desktop.cc +++ b/functions/src/desktop/callable_reference_desktop.cc @@ -14,6 +14,8 @@ #include "functions/src/desktop/callable_reference_desktop.h" +#include + #include "app/rest/request.h" #include "app/rest/util.h" #include "app/src/function_registry.h" @@ -32,8 +34,8 @@ enum CallableReferenceFn { }; HttpsCallableReferenceInternal::HttpsCallableReferenceInternal( - FunctionsInternal* functions, const char* name) - : functions_(functions), name_(name) { + FunctionsInternal* functions, const char* url) + : functions_(functions), url_(url) { functions_->future_manager().AllocFutureApi(this, kCallableReferenceFnCount); rest::InitTransportCurl(); transport_.set_is_async(true); @@ -46,7 +48,7 @@ HttpsCallableReferenceInternal::~HttpsCallableReferenceInternal() { HttpsCallableReferenceInternal::HttpsCallableReferenceInternal( const HttpsCallableReferenceInternal& other) - : functions_(other.functions_), name_(other.name_) { + : functions_(other.functions_), url_(other.url_) { functions_->future_manager().AllocFutureApi(this, kCallableReferenceFnCount); rest::InitTransportCurl(); transport_.set_is_async(true); @@ -55,14 +57,14 @@ HttpsCallableReferenceInternal::HttpsCallableReferenceInternal( HttpsCallableReferenceInternal& HttpsCallableReferenceInternal::operator=( const HttpsCallableReferenceInternal& other) { functions_ = other.functions_; - name_ = other.name_; + url_ = other.url_; return *this; } #if defined(FIREBASE_USE_MOVE_OPERATORS) || defined(DOXYGEN) HttpsCallableReferenceInternal::HttpsCallableReferenceInternal( HttpsCallableReferenceInternal&& other) - : functions_(other.functions_), name_(std::move(other.name_)) { + : functions_(other.functions_), url_(std::move(other.url_)) { other.functions_ = nullptr; functions_->future_manager().MoveFutureApi(&other, this); rest::InitTransportCurl(); @@ -73,7 +75,7 @@ HttpsCallableReferenceInternal& HttpsCallableReferenceInternal::operator=( HttpsCallableReferenceInternal&& other) { functions_ = other.functions_; other.functions_ = nullptr; - name_ = std::move(other.name_); + url_ = std::move(other.url_); functions_->future_manager().MoveFutureApi(&other, this); return *this; } @@ -300,8 +302,7 @@ void HttpsCallableReferenceInternal::ResolveFuture( Future HttpsCallableReferenceInternal::Call( const Variant& data) { // Set up the request. - std::string url = functions_->GetUrl(name_); - request_.set_url(url.data()); + request_.set_url(url_.data()); request_.set_method(rest::util::kPost); request_.add_header(rest::util::kContentType, rest::util::kApplicationJson); @@ -318,8 +319,8 @@ Future HttpsCallableReferenceInternal::Call( std::string json = util::VariantToJson(body); request_.set_post_fields(json.data()); - firebase::LogDebug("Calling Cloud Function with name: %s\nurl: %s\ndata: %s", - name_.c_str(), url.c_str(), json.c_str()); + firebase::LogDebug("Calling Cloud Function with url: %s\ndata: %s", + url_.c_str(), json.c_str()); // Set up the future to resolve when the request is complete. ReferenceCountedFutureImpl* future_impl = future(); diff --git a/functions/src/desktop/callable_reference_desktop.h b/functions/src/desktop/callable_reference_desktop.h index 6dfb30f4e5..03e89bf393 100644 --- a/functions/src/desktop/callable_reference_desktop.h +++ b/functions/src/desktop/callable_reference_desktop.h @@ -15,6 +15,8 @@ #ifndef FIREBASE_FUNCTIONS_SRC_DESKTOP_CALLABLE_REFERENCE_DESKTOP_H_ #define FIREBASE_FUNCTIONS_SRC_DESKTOP_CALLABLE_REFERENCE_DESKTOP_H_ +#include + #include "app/rest/transport_curl.h" #include "app/rest/transport_interface.h" #include "app/src/include/firebase/future.h" @@ -52,8 +54,7 @@ class HttpsCallableRequest : public rest::Request { class HttpsCallableReferenceInternal { public: - HttpsCallableReferenceInternal(FunctionsInternal* functions, - const char* name); + HttpsCallableReferenceInternal(FunctionsInternal* functions, const char* url); ~HttpsCallableReferenceInternal(); // Copy constructor. It's totally okay (and efficient) to copy @@ -108,8 +109,8 @@ class HttpsCallableReferenceInternal { // Keep track of the Functions object for managing Futures. FunctionsInternal* functions_; - // The name of the endpoint this reference points to. - std::string name_; + // The URL of the endpoint this reference points to. + std::string url_; rest::TransportCurl transport_; // For now, we only allow one request per reference at a time in C++. diff --git a/functions/src/desktop/functions_desktop.cc b/functions/src/desktop/functions_desktop.cc index 9bc0c9a079..9390daa338 100644 --- a/functions/src/desktop/functions_desktop.cc +++ b/functions/src/desktop/functions_desktop.cc @@ -35,7 +35,13 @@ const char* FunctionsInternal::region() const { return region_.c_str(); } HttpsCallableReferenceInternal* FunctionsInternal::GetHttpsCallable( const char* name) const { return new HttpsCallableReferenceInternal( - const_cast(this), name); + const_cast(this), GetUrl(name).c_str()); +} + +HttpsCallableReferenceInternal* FunctionsInternal::GetHttpsCallableFromURL( + const char* url) const { + return new HttpsCallableReferenceInternal( + const_cast(this), url); } std::string FunctionsInternal::GetUrl(const std::string& name) const { diff --git a/functions/src/desktop/functions_desktop.h b/functions/src/desktop/functions_desktop.h index 07b14040cb..a6224a99a5 100644 --- a/functions/src/desktop/functions_desktop.h +++ b/functions/src/desktop/functions_desktop.h @@ -39,6 +39,10 @@ class FunctionsInternal { // Get a FunctionsReference for the specified path. HttpsCallableReferenceInternal* GetHttpsCallable(const char* name) const; + // Get a FunctionsReference for the specified URL. + HttpsCallableReferenceInternal* GetHttpsCallableFromURL( + const char* url) const; + void UseFunctionsEmulator(const char* origin); // Returns the URL for the endpoint with the given name. diff --git a/functions/src/include/firebase/functions.h b/functions/src/include/firebase/functions.h index 39335188b5..500a05cc7a 100644 --- a/functions/src/include/firebase/functions.h +++ b/functions/src/include/firebase/functions.h @@ -90,6 +90,9 @@ class Functions { /// @brief Get a FunctionsReference for the specified path. HttpsCallableReference GetHttpsCallable(const char* name) const; + /// @brief Get a FunctionsReference for the specified URL. + HttpsCallableReference GetHttpsCallableFromURL(const char* url) const; + /// @brief Sets an origin for a Cloud Functions emulator to use. void UseFunctionsEmulator(const char* origin); diff --git a/functions/src/ios/functions_ios.h b/functions/src/ios/functions_ios.h index 09a358d766..c03b4ce1dc 100644 --- a/functions/src/ios/functions_ios.h +++ b/functions/src/ios/functions_ios.h @@ -55,6 +55,9 @@ class FunctionsInternal { // Get a FunctionsReference for the specified path. HttpsCallableReferenceInternal* GetHttpsCallable(const char* name) const; + // Get a FunctionsReference for the specified URL. + HttpsCallableReferenceInternal* GetHttpsCallableFromURL(const char* url) const; + void UseFunctionsEmulator(const char* origin); FutureManager& future_manager() { return future_manager_; } diff --git a/functions/src/ios/functions_ios.mm b/functions/src/ios/functions_ios.mm index f0ca0e25c7..763d657023 100644 --- a/functions/src/ios/functions_ios.mm +++ b/functions/src/ios/functions_ios.mm @@ -48,6 +48,14 @@ MakeUnique([impl_.get()->get() HTTPSCallableWithName:@(name)])); } +HttpsCallableReferenceInternal* FunctionsInternal::GetHttpsCallableFromURL(const char* url) const { + // HttpsCallableReferenceInternal handles deleting the wrapper pointer. + NSURL *nsurl = [NSURL URLWithString:@(url)]; + return new HttpsCallableReferenceInternal( + const_cast(this), + MakeUnique([impl_.get()->get() HTTPSCallableWithURL:nsurl])); +} + void FunctionsInternal::UseFunctionsEmulator(const char* origin) { std::string origin_str(origin); // origin is in the format localhost:5005 diff --git a/release_build_files/readme.md b/release_build_files/readme.md index c00f8eea66..b78e3af194 100644 --- a/release_build_files/readme.md +++ b/release_build_files/readme.md @@ -603,6 +603,8 @@ code. - General (Android): Fixed a bug that required Android apps to include `com.google.android.gms:play-services-base` as an explicit dependency when only using AdMob, Analytics, Remote Config, or Messaging. + - Functions: Add a new method `GetHttpsCallableFromURL`, to create callables + with URLs other than cloudfunctions.net. ### 9.0.0 - Changes