skip to Main Content

I’m having an issue with Callable Functions. Here’s my Firebase function:

const functions = require('firebase-functions');
const admin = require('firebase-admin');
const request = require('request-promise');

admin.initializeApp(functions.config().firebase);

exports.phoneAuthRequest = functions.https.onCall((data, context) => {
  // Message text passed from the client.
  const text = data.text;

  // Authentication / user information is automatically added to the request.
  const uid = context.auth.uid;
  const phone = context.auth.token.phone_number;

  const url = "https://api.authy.com/protected/json/phones/verification/start?api_key=<key here>&via=sms&country_code=1&phone_number=" + phone;
  const httpReq = {
    uri: url,
    method: 'POST',
    json: true,
    resolveWithFullResponse: true
  };

  return new Promise((resolve, reject) => {
    request(httpReq).then((response) => {
      // Twillio request returned with success
      console.log("response: " + JSON.stringify(response));
      return resolve(response);
    }).catch(function(error) {
      console.log("error: " + JSON.stringify(error));
      return reject(error);
    });  
  });
});

And here is my client example:

<html>
  <head>
    <script src="https://www.gstatic.com/firebasejs/5.4.2/firebase-app.js"></script>
    <script src="https://www.gstatic.com/firebasejs/5.4.2/firebase-auth.js"></script>
    <script src="https://www.gstatic.com/firebasejs/5.4.2/firebase-database.js"></script>
    <script src="https://www.gstatic.com/firebasejs/5.4.2/firebase-functions.js"></script>

    <script>
      var config = { <firebase configuration in here> };
      firebase.initializeApp(config);

      function myTest() {
        var display = document.getElementById('display');
        display.innerHTML = "Started";

        var auth = firebase.auth();
        auth.signInWithEmailAndPassword(<username>,<password>).then(function(authResult) {
          display.innerHTML = "Signed in";

          var phoneAuthRequest = firebase.functions().httpsCallable('phoneAuthRequest');
          phoneAuthRequest({'text': 'Test'}).then(function(result) {
            display.innerHTML = JSON.stringify(result);
          }).catch(function(error) {
            display.innerHTML = JSON.stringify(error);
          });
        }).catch(function(error) {
          display.innerHTML = "Login failure.  Your email or password could not be validated.  " + JSON.stringify(error);
        });
      }
    </script>
  </head>

  <body>
    <input type="submit" value="Test It" data-wait="Loading..." id="loginBtn" class="login-window-login-btn w-button" onclick="myTest();">
    <div id="display"></div>
  </body>
</html>

A test of this code combination shows that the Firebase function ‘phoneAuthRequest’ gets called, which in turn makes the request to Twillio, and the response from Twillio is returned correctly in response, but the Console Logs show this error:

Unhandled error RangeError: Maximum call stack size exceeded
    at Function.mapValues (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13395:23)
    at encode (/user_code/node_modules/firebase-functions/lib/providers/https.js:242:18)
    at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13400:38
    at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:4925:15
    at baseForOwn (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:3010:24)
    at Function.mapValues (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13399:7)
    at encode (/user_code/node_modules/firebase-functions/lib/providers/https.js:242:18)
    at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13400:38
    at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:4925:15
    at baseForOwn (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:3010:24)
    at Function.mapValues (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13399:7)
    at encode (/user_code/node_modules/firebase-functions/lib/providers/https.js:242:18)
    at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13400:38
    at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:4925:15
    at baseForOwn (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:3010:24)
    at Function.mapValues (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13399:7)
    at encode (/user_code/node_modules/firebase-functions/lib/providers/https.js:242:18)
    at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13400:38
    at /user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:4925:15
    at baseForOwn (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:3010:24)
    at Function.mapValues (/user_code/node_modules/firebase-functions/node_modules/lodash/lodash.js:13399:7)
    at encode (/user_code/node_modules/firebase-functions/lib/providers/https.js:242:18)

And of course, the response back at the client is:

{"code":"internal"}

As I read similar posts, the advice was to serialize the promises, which I’m not sure I totally understand how to do, or, to wrap the async call (request in this case) inside of a new promise, which is exactly what I have done here, and it still doesn’t work.

If you are going to answer this question, can you please be specific about the proposed solution by showing a code example?

Thanks in advance…

2

Answers


  1. Chosen as BEST ANSWER

    Ok, I resolved this issue with a ton of knocking my head against the wall. Here's the code:

    exports.phoneAuthRequest = functions.https.onCall((data, context) => {
      // Example of using context to obtain the user information about the calling user
      const uid = context.auth.uid;
      // Example of obtaining a paramter that was passed by the calling function
      const phone = data.phone;
      const e164 = "+1" + phone;
    
      httpReq = ...
    
      // Example of a return if you have not yet made a call to an asynchonous function.
      return ({'status': 'success'});
    
      // Here is an example of a nested set of asynchonous calls and how to return from them.
      return admin.auth().updateUser(uid, {'phoneNumber': e164}).then(function(userRecord) {
        // the Phone Number was updated
        return request(httpReq).then((response) => {
          var updates = {};
          updates['/users/' + uid + "/centralVerified"] = false;
          return database.ref().update(updates).then(function(rslt) {
            return Promise.resolve({'status': 'success');
          }).catch(function(error) {
            return Promise.reject({'status': 'failed'});
          });
        }).catch(function(error) {
          return Promise.reject({'status': 'failed'});
        });
      }).catch(function(error) {
        return Promise.reject({'status': 'failed'});
      });
    });
    

    Notice in the above that there are two distinct ways of returning from a callable function. If you haven't made any asynchonous calls, then you can simply return values, but if you have called an asynchonous function, you must return a promise. If you are looking for a Reject or Resolve in the calling routine, a return of values is assumed to be a Resolve.

    I hope this helps others... Best of luck!


  2. Try eliminating the client and call the Firebase function through curl.

    Call functions via HTTP requests

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search