'Firebase Cloud Function - RangeError: Maximum call stack size exceeded

I am trying to write an onCall Firebase Cloud Function that calls an external Zoho Desk API to return a list of support tickets.

But whenever I call my Firebase Cloud Function it returns this error: Unhandled error RangeError: Maximum call stack size exceeded.

Most of the other answers I have found are in relation to Firebase document snapshots, and they say it is caused by a infinite loop. But I'm not sure how to apply that knowledge to my external api call.

Here is the Cloud Function in question:

export const getZohoDeskTickets = functions
  .region('us-central1')
  .https.onCall(async (data, context) => {
    // Check if it passed App Check
    if (context.app == undefined) {
      throw new functions.https.HttpsError(
        'failed-precondition',
        'The function must be called from an App Check verified app.'
      );
    }

    // Check the authentication
    if (!context.auth) {
      // Throwing an HttpsError so that the client gets the error details.
      throw new functions.https.HttpsError(
        'unauthenticated',
        'The function must be called while authenticated.'
      );
    }

    // Do the operation
    const zohoAccessToken = await getSecretVersion(
      'zoho-self-client-api-access-token'
    ).catch((error) => {
      throw new functions.https.HttpsError('unknown', error.message, error);
    });
    return axios
      .get('https://desk.zoho.com/api/v1/tickets', {
        headers: {
          orgId: '123456789',
          Authorization: `Zoho-oauthtoken ${zohoAccessToken}`,
        },
      })
      .catch(async (error) => {
        functions.logger.error(error.response.data);
        throw new functions.https.HttpsError(
          'unknown',
          error.response.data.message,
          error.response.data
        );
      });
  });

UPDATE:

I found this helpful thread which showed that sometimes the infinite loop is not coming from the Cloud Function but the caller, and also that the call stack can be helpful for debugging.

In the interest of that, here is my call stack as shown in the Firebase Emulator logs. It doesn't make sense to me because it's just the same line repeated again and again:

Unhandled error RangeError: Maximum call stack size exceeded

at TCP.get [as reading] (_tls_wrap.js:617:7)
at Function.entries (<anonymous>)
at encode (/Users/macbook/Dev/my-app/functions/node_modules/firebase-functions/lib/common/providers/https.js:157:37)
at encode (/Users/macbook/Dev/my-app/functions/node_modules/firebase-functions/lib/common/providers/https.js:158:22)
at encode (/Users/macbook/Dev/my-app/functions/node_modules/firebase-functions/lib/common/providers/https.js:158:22)
at encode (/Users/macbook/Dev/my-app/functions/node_modules/firebase-functions/lib/common/providers/https.js:158:22)
at encode (/Users/macbook/Dev/my-app/functions/node_modules/firebase-functions/lib/common/providers/https.js:158:22)
at encode (/Users/macbook/Dev/my-app/functions/node_modules/firebase-functions/lib/common/providers/https.js:158:22)
at encode (/Users/macbook/Dev/my-app/functions/node_modules/firebase-functions/lib/common/providers/https.js:158:22)
at encode (/Users/macbook/Dev/my-app/functions/node_modules/firebase-functions/lib/common/providers/https.js:158:22)

And here is the Chrome browser console call stack:

postJSON    @   index.esm2017.js?6f1f:499
call    @   index.esm2017.js?6f1f:553
await in call (async)       
eval    @   index.esm2017.js?6f1f:485
eval    @   SupportPage.vue?c3cc:37
eval    @   index.js??clonedRule…tup=true&lang=ts:15
__awaiter   @   index.js??clonedRule…tup=true&lang=ts:11
handleClick @   SupportPage.vue?c3cc:36
callWithErrorHandling   @   runtime-core.esm-bundler.js?f781:155
callWithAsyncErrorHandling  @   runtime-core.esm-bundler.js?f781:164
emit$1  @   runtime-core.esm-bundler.js?f781:718
eval    @   runtime-core.esm-bundler.js?f781:7232
onClick @   QBtn.js?9c40:148
callWithErrorHandling   @   runtime-core.esm-bundler.js?f781:155
callWithAsyncErrorHandling  @   runtime-core.esm-bundler.js?f781:164
invoker @   runtime-dom.esm-bundler.js?9cec:366

And here is the calling function inside my Vue app:

<template>
  <v-btn design="alpha" @click="handleClick">loadData</v-btn>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import VBtn from 'src/components/VBtn.vue';
import { httpsCallable, FunctionsError } from 'firebase/functions';
import { functions } from 'src/config/firebase';

const data = ref();

const getZohoDeskTickets = httpsCallable(functions, 'getZohoDeskTickets');

const isFunctionsError = (error: unknown): error is FunctionsError => {
  return (error as FunctionsError).details !== undefined;
};

const handleClick = async () => {
  const ticket = await getZohoDeskTickets().catch((error) => {
    if (isFunctionsError(error)) {
      console.log(error.code);
      console.log(error.message);
      console.log(error.details);
    } else {
      console.log(error);
    }
  });
  data.value = ticket;
  console.log(ticket);
  return ticket;
};
</script>

But even with that I still cannot figure this out.

What is causing the infinite loop?

Or maybe it is something else causing this error?



Solution 1:[1]

Finally got it!

The solution came from this answer.

In short; I needed to add a .then() onto the returned axios chain like so:

export const getZohoDeskTickets = functions
  .region('us-central1')
  .https.onCall(async (data, context) => {
    // Check if it passed App Check
    if (context.app == undefined) {
      throw new functions.https.HttpsError(
        'failed-precondition',
        'The function must be called from an App Check verified app.'
      );
    }

    // Check the authentication
    if (!context.auth) {
      // Throwing an HttpsError so that the client gets the error details.
      throw new functions.https.HttpsError(
        'unauthenticated',
        'The function must be called while authenticated.'
      );
    }

    // Do the operation
    const zohoAccessToken = await getSecretVersion(
      'zoho-self-client-api-access-token'
    ).catch((error) => {
      throw new functions.https.HttpsError('unknown', error.message, error);
    });
    return axios
      .get('https://desk.zoho.com/api/v1/tickets', {
        headers: {
          orgId: '774638961',
          Authorization: `Zoho-oauthtoken ${zohoAccessToken}`,
        },
      })
      .then((response) => {
        functions.logger.info(response.data);
        return response.data;
      })
      .catch((error) => {
        functions.logger.error(error.response.data);
        throw new functions.https.HttpsError(
          'unknown',
          error.response.data.message,
          error.response.data
        );
      });
  });

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 TinyTiger