Skip to content

Add custom domain support to callable functions #2100

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Nov 11, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions firebase-functions/firebase-functions.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ android {
testOptions.unitTests.includeAndroidResources = true
}

configurations {
all {
exclude module: 'commons-logging'
}
}

dependencies {
implementation project(':firebase-common')
implementation project(':firebase-components')
Expand All @@ -63,6 +69,7 @@ dependencies {
implementation 'com.google.firebase:firebase-iid-interop:17.0.0'

implementation 'com.squareup.okhttp3:okhttp:3.12.1'
implementation 'commons-validator:commons-validator:1.7'

annotationProcessor 'com.google.auto.value:auto-value:1.6.2'

Expand Down
4 changes: 2 additions & 2 deletions firebase-functions/ktx/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package com.google.firebase.functions.ktx {

public final class FunctionsKt {
ctor public FunctionsKt();
method @NonNull public static com.google.firebase.functions.FirebaseFunctions functions(@NonNull com.google.firebase.ktx.Firebase, @NonNull String region);
method @NonNull public static com.google.firebase.functions.FirebaseFunctions functions(@NonNull com.google.firebase.ktx.Firebase, @NonNull String regionOrCustomDomain);
method @NonNull public static com.google.firebase.functions.FirebaseFunctions functions(@NonNull com.google.firebase.ktx.Firebase, @NonNull com.google.firebase.FirebaseApp app);
method @NonNull public static com.google.firebase.functions.FirebaseFunctions functions(@NonNull com.google.firebase.ktx.Firebase, @NonNull com.google.firebase.FirebaseApp app, @NonNull String region);
method @NonNull public static com.google.firebase.functions.FirebaseFunctions functions(@NonNull com.google.firebase.ktx.Firebase, @NonNull com.google.firebase.FirebaseApp app, @NonNull String regionOrCustomDomain);
method @NonNull public static com.google.firebase.functions.FirebaseFunctions getFunctions(@NonNull com.google.firebase.ktx.Firebase);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,16 @@ import com.google.firebase.platforminfo.LibraryVersionComponent
val Firebase.functions: FirebaseFunctions
get() = FirebaseFunctions.getInstance()

/** Returns the [FirebaseFunctions] instance of a given [region]. */
fun Firebase.functions(region: String): FirebaseFunctions = FirebaseFunctions.getInstance(region)
/** Returns the [FirebaseFunctions] instance of a given [regionOrCustomDomain]. */
fun Firebase.functions(regionOrCustomDomain: String): FirebaseFunctions =
FirebaseFunctions.getInstance(regionOrCustomDomain)

/** Returns the [FirebaseFunctions] instance of a given [FirebaseApp]. */
fun Firebase.functions(app: FirebaseApp): FirebaseFunctions = FirebaseFunctions.getInstance(app)

/** Returns the [FirebaseFunctions] instance of a given [FirebaseApp] and [region]. */
fun Firebase.functions(app: FirebaseApp, region: String): FirebaseFunctions =
FirebaseFunctions.getInstance(app, region)
/** Returns the [FirebaseFunctions] instance of a given [FirebaseApp] and [regionOrCustomDomain]. */
fun Firebase.functions(app: FirebaseApp, regionOrCustomDomain: String): FirebaseFunctions =
FirebaseFunctions.getInstance(app, regionOrCustomDomain)

internal const val LIBRARY_NAME: String = "fire-fun-ktx"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ public void testGetUrl() {
functions = FirebaseFunctions.getInstance(app);
url = functions.getURL("my-endpoint");
assertEquals("https://us-central1-my-project.cloudfunctions.net/my-endpoint", url.toString());

functions = FirebaseFunctions.getInstance(app, "https://mydomain.com");
url = functions.getURL("my-endpoint");
assertEquals("https://mydomain.com/my-endpoint", url.toString());

functions = FirebaseFunctions.getInstance(app, "https://mydomain.com/foo");
url = functions.getURL("my-endpoint");
assertEquals("https://mydomain.com/foo/my-endpoint", url.toString());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.apache.commons.validator.routines.UrlValidator;
import org.json.JSONException;
import org.json.JSONObject;

Expand Down Expand Up @@ -73,26 +74,40 @@ public class FirebaseFunctions {
private final String projectId;

// The region to use for all function references.
private final String region;
@Nullable private final String region;

// A custom domain for the http trigger, such as "https://mydomain.com"
@Nullable private final String customDomain;

// The format to use for constructing urls from region, projectId, and name.
private String urlFormat = "https://%1$s-%2$s.cloudfunctions.net/%3$s";

// Allowed custom domain protocols.
private String[] customDomainSchemes = {"http", "https"};

// Emulator settings
@Nullable private EmulatedServiceSettings emulatorSettings;

FirebaseFunctions(
FirebaseApp app,
Context context,
String projectId,
String region,
String regionOrCustomDomain,
ContextProvider contextProvider) {
this.app = app;
this.client = new OkHttpClient();
this.serializer = new Serializer();
this.contextProvider = Preconditions.checkNotNull(contextProvider);
this.projectId = Preconditions.checkNotNull(projectId);
this.region = Preconditions.checkNotNull(region);

UrlValidator validator = new UrlValidator(customDomainSchemes);
if (validator.isValid(regionOrCustomDomain)) {
this.region = null;
this.customDomain = regionOrCustomDomain;
} else {
this.region = regionOrCustomDomain;
this.customDomain = null;
}

maybeInstallProviders(context);
}
Expand Down Expand Up @@ -135,20 +150,22 @@ public void onProviderInstallFailed(int i, android.content.Intent intent) {
}

/**
* Creates a Cloud Functions client with the given app and region.
* Creates a Cloud Functions client with the given app and region or custom domain.
*
* @param app The app for the Firebase project.
* @param region The region for the HTTPS trigger, such as "us-central1".
* @param regionOrCustomDomain The region or custom domain for the HTTPS trigger, such as
* "us-central1" or "https://mydomain.com".
*/
@NonNull
public static FirebaseFunctions getInstance(@NonNull FirebaseApp app, @NonNull String region) {
public static FirebaseFunctions getInstance(
@NonNull FirebaseApp app, @NonNull String regionOrCustomDomain) {
Preconditions.checkNotNull(app, "You must call FirebaseApp.initializeApp first.");
Preconditions.checkNotNull(region);
Preconditions.checkNotNull(regionOrCustomDomain);

FunctionsMultiResourceComponent component = app.get(FunctionsMultiResourceComponent.class);
Preconditions.checkNotNull(component, "Functions component does not exist.");

return component.get(region);
return component.get(regionOrCustomDomain);
}

/**
Expand All @@ -162,13 +179,14 @@ public static FirebaseFunctions getInstance(@NonNull FirebaseApp app) {
}

/**
* Creates a Cloud Functions client with the default app and given region.
* Creates a Cloud Functions client with the default app and given region or custom domain.
*
* @param region The region for the HTTPS trigger, such as "us-central1".
* @param regionOrCustomDomain The regionOrCustomDomain for the HTTPS trigger, such as
* "us-central1" or "https://mydomain.com".
*/
@NonNull
public static FirebaseFunctions getInstance(@NonNull String region) {
return getInstance(FirebaseApp.getInstance(), region);
public static FirebaseFunctions getInstance(@NonNull String regionOrCustomDomain) {
return getInstance(FirebaseApp.getInstance(), regionOrCustomDomain);
}

/** Creates a Cloud Functions client with the default app. */
Expand All @@ -191,17 +209,21 @@ public HttpsCallableReference getHttpsCallable(@NonNull String name) {
*/
@VisibleForTesting
URL getURL(String function) {
EmulatedServiceSettings emulatorSettings = this.emulatorSettings;
if (emulatorSettings != null) {
urlFormat =
"http://"
+ emulatorSettings.getHost()
+ ":"
+ emulatorSettings.getPort()
+ "/%2$s/%1$s/%3$s";
String str;
if (customDomain != null) {
str = customDomain + "/" + function;
} else {
EmulatedServiceSettings emulatorSettings = this.emulatorSettings;
if (emulatorSettings != null) {
urlFormat =
"http://"
+ emulatorSettings.getHost()
+ ":"
+ emulatorSettings.getPort()
+ "/%2$s/%1$s/%3$s";
}
str = String.format(urlFormat, region, projectId, function);
}

String str = String.format(urlFormat, region, projectId, function);
try {
return new URL(str);
} catch (MalformedURLException mfe) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,15 @@ class FunctionsMultiResourceComponent {
this.app = app;
}

synchronized FirebaseFunctions get(String region) {
FirebaseFunctions functions = instances.get(region);
synchronized FirebaseFunctions get(String regionOrCustomDomain) {
FirebaseFunctions functions = instances.get(regionOrCustomDomain);
String projectId = app.getOptions().getProjectId();

if (functions == null) {
functions =
new FirebaseFunctions(app, applicationContext, projectId, region, contextProvider);
instances.put(region, functions);
new FirebaseFunctions(
app, applicationContext, projectId, regionOrCustomDomain, contextProvider);
instances.put(regionOrCustomDomain, functions);
}
return functions;
}
Expand Down