Skip to content

Functions useEmulator not persistent between regions #3537

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

Closed
epodol opened this issue Jun 28, 2021 · 7 comments
Closed

Functions useEmulator not persistent between regions #3537

epodol opened this issue Jun 28, 2021 · 7 comments
Assignees

Comments

@epodol
Copy link

epodol commented Jun 28, 2021

[REQUIRED] Environment info

firebase-tools: 9.14.0

Platform: Windows with WSL 2 Ubuntu

[REQUIRED] Test case

React and Reactfire are being used in this project. The repo is available at https://github.com/epodol/bsclibrary.

        return isDev
          ? functions('us-west2').useEmulator('localhost', 5001)
          : functions('us-west2');
import { useAuth, useFunctions} from 'reactfire';
// ...
const functions = useFunctions();
// ...
exports.addNewUser = functions
  .region('us-west2')
  .https.onCall(async (data, context) => {

[REQUIRED] Steps to reproduce

  1. Set up a firebase project (as normal)
  2. Create a callable function in another region (us-west2 is used in this example)
  3. Call functions() with a custom region as depicted here: https://firebase.google.com/docs/functions/locations#client-side_location_selection_for_callable_functions
  4. When tagging a useEmulator() call onto the functions() with a region, it will result in both the region and the emulator call being ignored.

[REQUIRED] Expected behavior

The emulator should still work when a custom region is defined.

[REQUIRED] Actual behavior

functions('us-west2').useEmulator('localhost', 5001)

image

functions().useEmulator('localhost', 5001)

image
image

image

May be related to #3031, #3364, #3418

@i14h
Copy link
Member

i14h commented Jun 28, 2021

thanks @epodol for reporting this. @kmcnellis can you please take a look at this?

@epodol
Copy link
Author

epodol commented Jun 29, 2021

I have looked into this some more and created a basic html/js only example. Here are a few and some conclusions I came to.

Examples

Example 1:
    firebase.initializeApp(firebaseConfig);

    const functions = firebase.functions();
    functions.useEmulator('localhost', 5001)
    functions.httpsCallable('test')()

Example 1 calls: http://localhost:5001/$PROJECT/us-central1/test

Example 2:
    firebase.initializeApp(firebaseConfig);

    const functions = firebase.app().functions('us-west2');
    functions.useEmulator('localhost', 5001)
    functions.httpsCallable('test')()

Example 2 calls http://localhost:5001/$PROJECT/us-west2/test (Ideal)

Example 3:
    firebase.initializeApp(firebaseConfig);

    const functions = firebase.app().functions('us-west2');
    firebase.app().functions().useEmulator('localhost', 5001)
    firebase.app().functions().httpsCallable('test')()

Example 3 calls http://localhost:5001/$PROJECT/us-central1/test (Back to example 1)

Example 4:
    firebase.initializeApp(firebaseConfig);

    const functions = firebase.app().functions('us-west2');
    firebase.app().functions('us-west2').useEmulator('localhost', 5001)
    firebase.app().functions('us-west2').httpsCallable('test')()

Example 4 calls http://localhost:5001/$PROJECT/us-west2/test (Ideal)

Example 5:
    firebase.initializeApp(firebaseConfig);

    const functions = firebase.app().functions('us-west2');
    functions.useEmulator('localhost', 5001)
    firebase.app().functions().httpsCallable('test')()

Example 5 calls https://us-central1-$PROJECT.cloudfunctions.net/test (Calling Production)

Example 6:
    firebase.initializeApp(firebaseConfig);

    const functions = firebase.app().functions('us-west2');
    functions.useEmulator('localhost', 5001)
    firebase.app().functions('us-west2').httpsCallable('test')()

Example 6 calls http://localhost:5001/$PROJECT/us-west2/test (Ideal)

Conclusions

Calling useEmulator(...) in Example 5 does not do anything and will always call production, all the other examples change behavior based on the useEmulator(...) call. The only difference between Examples 5 and 6 is that the us-west2 region is defined in Example 6 right before calling the callable function.

I'm not sure if this is actually a bug with Firebase anymore, but I think there could be some improvement with how Firebase handles custom regions. Ideally, the region could be defined as an option/argument in the same httpsCallable() call so functions with different regions could be called without having to initialize functions() again.

Currently, the best solution for me is to use Example 6 since I am using reactfire. For example:

import { useAuth, useFirebaseApp } from 'reactfire';
// ...
  const functions = useFirebaseApp().functions('us-west2');
// ...
          await functions
            .httpsCallable('addNewUser')({
// ...

If this is all intended behavior, then you can close this issue/change the title. Thank you!

@kmcnellis
Copy link
Member

I think @samtstern is the most familiar with this code.

@kmcnellis
Copy link
Member

But I think basically the issue is in how the useEmulator function interacts with the firebase.app().functions().

The example#5 above, calling production:

    firebase.initializeApp(firebaseConfig);
    const functions = firebase.app().functions('us-west2');
    functions.useEmulator('localhost', 5001)
    firebase.app().functions().httpsCallable('test')()

The line const functions = firebase.app().functions('us-west2'); creates an instance of the functions class, which is then modified by the call to useEmulator (defined here ) to use the emulator origin. However, it does not change any SDK-wide variables.

Additionally, there is a layer of caching of the functions instance using the ComponentContainer, that will return that same 'us-west2' instance of functions each time firebase.app().functions('us-west2'); is called. This is why example#6 works.

So when you then call firebase.app().functions().httpsCallable('test')(), the firebase.app().functions() call both 1) created a new instance of the functions class (since it has a different name than 'us-west2', and 2) does not set the region or emulator origin on it, causing it to call us-central1 production.

@kmcnellis kmcnellis removed their assignment Jun 29, 2021
@kmcnellis
Copy link
Member

I think it's probably working as designed, but the design seems quite confusing/unintuitive to me.

@epodol
Copy link
Author

epodol commented Jun 29, 2021

Thank you @kmcnellis! That is the conclusion I came to as well. I appreciate the help!

@epodol epodol changed the title Functions Emulator calling Production when region set Functions useEmulator not persistent between regions Jun 29, 2021
@samtstern
Copy link
Contributor

@kmcnellis has nailed it. This is working as designed, and the design is confusing 🤦

For all of our SDKs we decided that useEmulator would be a per instance setting, not a per-FirebaseApp or per-SDK setting. However each product does "instance caching" a bit differently. For Realtime Database it's per-URL caching. For Functions it's per-region.

Closing as this is not a bug, although I take it as feedback and I'll think about ways to improve this without breaking people!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants