Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Firebase Function: Unhandled error RangeError: Maximum call stack size exceeded

I have the following callable function that gets some data from my database, then creates a PDF with that data using html-pdf, uploads that PDF to storage and finally returns the name of the file in storage. It works well in https form, but I want to convert it to a callable function, and for some reason I can't figure out, it crashes with this error: RangeError: maximum call stack size exceeded.

I suspect it has something to do with the fact that html-pdf doesn't work with promises and instead uses an error/data callback. But I've tried to convert it into a promise without success.

export const createPdf = functions.https.onCall((data, context) => {
    const refId = data.refId;
    const companyId = data.companyId;
    const userId = context.auth.uid;

    return admin.database().ref('/references').child(companyId).child(refId).once('value', (snapshot) => {
        const filePath = '/references/' + refId + '/pdfs/' + refId + '.pdf';
        const localeId = snapshot.child('locale').val();

        return admin.database().ref('/tags').child(localeId).once('value', (tagsSnapshot) => {
            const jsLocaleId = localeId.replace(/_/, "-");
            const projectDate = moment().locale(jsLocaleId)
                .year(snapshot.child('year').val())
                .month(snapshot.child('month').val() - 1)
                .date(15)
                .format('MMMM YYYY');

            const tags = tagsSnapshot.val();
            const projectCategories = ...
            const pictures = snapshot.child('pictures').val();

            const pdfData = {
                projectName: snapshot.child('projectName').val(),
                surface: snapshot.child('surface').val(),
                companyName: snapshot.child('companyName').val(),
                date: projectDate,
                newBuilding: snapshot.child('newBuilding').val(),
                customerName: snapshot.child('customerName').val(),
                categories: projectCategories,
                address: snapshot.child('address').val().replace(/\n/g, '<br>'),
                satellite: snapshot.child('satellite').val(),
                pictures: !isNullOrUndefined(pictures) ? pictures.map((item) => {
                    return {url: item}
                }) : []
            };
            console.log("data", pdfData);
            const options = {...};

            const localTemplate = path.join(os.tmpdir(), 'share.html');
            const localPDFFile = path.join(os.tmpdir(), 'share.pdf');
            const languageCode = localeId.split("_")[0];

            return admin.storage().bucket().file('/templates/share-' + languageCode + '.html').download({destination: localTemplate}).then(() => {
                const source = fs.readFileSync(localTemplate, 'utf8');
                const html = handlebars.compile(source)(pdfData);
                pdf.create(html, options).toFile(localPDFFile, function (err, result) {
                    if (err) {
                        console.log(err);
                        throw new functions.https.HttpsError('internal', err.message);
                    }

                    return admin.storage().bucket().upload(localPDFFile, {
                        destination: filePath,
                        resumable: false,
                        metadata: {contentType: 'application/pdf'}
                    }).then((files) => {
                        console.log("files", files);
                        return files[0].getMetadata().then((metadata) => {
                            const name = metadata[0]["name"];
                            return {
                                name: name
                            };
                        });
                    }).catch(error => {
                        console.error(error);
                        throw new functions.https.HttpsError('internal', "Could not upload PDF because " + error.message);
                    });
                });
            }).catch((error) => {
                console.error("Could not download template");
                throw new functions.https.HttpsError('internal', error.message);
            });
        });
    });
});
like image 704
Sebastien Avatar asked Aug 17 '18 16:08

Sebastien


1 Answers

Callable functions shouldn't return just any promise. They should return a promise that resolves with the response to send to the client. Yours is returning a promise that resolves when the database operation is complete. Cloud Functions is probably attempting to serialize the value contained in the promise (a DataSnapshot object). This might contain circular references which are causing problems during serialization.

It looks like you're assuming that returning a promise nested three promises deep is going to send the response to the client, but that's not the way promises work. You could get away with this in an HTTP function since you can call response.send() deeply nested, but that's not going to work here. You're going to have to un-nest all your promises and run them in serial. (What you're doing now is considered bad style for promises.)

like image 122
Doug Stevenson Avatar answered Nov 17 '22 02:11

Doug Stevenson