Hossam Elharmil
Hossam Elharmil

Reputation: 151

Busboy file event not firing

So, I have this post request which used to work fine but once I upgraded to Node 10 I can't seem to figure out why the file event completely stopped firing...

router.post('/uploadImage', authenticate.FBAuth, (req, res) => {
    const busboy = new BusBoy({ headers: req.headers });
    let imageFileName;
    let imageToBeUploaded = {};

    busboy.on('file', function onFile(fieldname, file, filename, encoding, mimetype) {
        console.log('onFile started');
        
        if (mimetype !== 'image/png' && mimetype !== 'image/jpeg') {
            return res.status(400).json({ error: 'Unsupported file format' });
        }

        const imageExtension = path.extname(filename);
        imageFileName = `${Math.round(Math.random() * 100000000000)}.${imageExtension}`;
        console.log(imageFileName);

        const filepath = path.join(os.tmpdir(), imageFileName);
        console.log(filepath);

        imageToBeUploaded = { filepath, mimetype };
        console.log(imageToBeUploaded);

        file.pipe(fs.createWriteStream(filepath));
    });

    busboy.on('finish', function onFinish() {
        admin
            .storage()
            .bucket(config.storageBucket)
            .upload(imageToBeUploaded.filepath, {
                resumable: false,
                metadata: {
                    metadata: {
                        contentType: imageToBeUploaded.mimetype
                    }
                }
            })
            .then(() => {
                const imageUrl = `https://firebasestorage.googleapis.com/v0/b/${config.storageBucket}/o/${imageFileName}?alt=media`;
                return db.doc(`/users/${req.user.handle}`).update({ imageUrl });
            })
            .then(() => {
                return res.json({ message: 'Image uploaded successfully' });
            })
            .catch(error => {
                console.error(error);
                return res.status(500).json({ error: error.code });
            });
    });

    busboy.end(req.rawBody);
});

I tried to do console.log(imageToBeUploaded); inside onFinish and it is always an empty object. Besides, any console logs inside onFile never seem to print.

I also tried without authenticate.FBAuth middleware but that didn't make any difference.

This is the error from bucket().upload() basically complaining that the path is invalid which makes sense since imageToBeUploaded is an empty object and hence imageToBeUploaded.filepath is undefined

TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received undefined
    at validateString (internal/validators.js:121:11)
    at Object.basename (path.js:1156:5)
    at Bucket.upload (/Users/mac/Dev/meows/server/node_modules/firebase-admin/node_modules/@google-cloud/storage/build/src/bucket.js:2493:38)
    at /Users/mac/Dev/meows/server/node_modules/firebase-admin/node_modules/@google-cloud/promisify/build/src/index.js:69:28
    at new Promise (<anonymous>)
    at Bucket.wrapper (/Users/mac/Dev/meows/server/node_modules/firebase-admin/node_modules/@google-cloud/promisify/build/src/index.js:54:16)
    at Busboy.onFinish (/Users/mac/Dev/meows/server/routes/profileRouter.js:116:14)
    at Busboy.emit (events.js:315:20)
    at Busboy.emit (/Users/mac/Dev/meows/server/node_modules/busboy/lib/main.js:37:33)
    at /Users/mac/Dev/meows/server/node_modules/busboy/lib/types/multipart.js:304:17 {
  code: 'ERR_INVALID_ARG_TYPE'
}

I am using Postman for the test and choosing form-data for body and File for the input type. Made sure that Postman handled the Content-Type header setting it to multipart/form-data; boundary=<calculated when request is sent>

Also, this is the file containing authenticate.FBAuth just in case

const admin = require('firebase-admin');
const db = admin.firestore();

module.exports = {
    FBAuth: (req, res, next) => {
        let idToken;
        if (req.headers.authorization == null || !req.headers.authorization.startsWith('bearer ')) {
            console.log('Unrecognized token format');
            return res.status(400).json({ error: 'Unauthorized operation' });
        }
        else {
            idToken = req.headers.authorization.split('bearer ')[1];
        }
        return admin.auth().verifyIdToken(idToken)
            .then(decodedToken => {
                req.user = decodedToken;
                return db.collection('users')
                    .where('userId', '==', req.user.uid)
                    .limit(1)
                    .get()
                    .then(data => {
                        req.user.handle = data.docs[0].data().handle;
                        req.user.imageUrl = data.docs[0].data().imageUrl;
                        return next();
                    });
            })
            .catch(error => {
                console.log('Error verifying token ', error);
                return res.status(403).json(error);
            });
    }
}

And here is package.json

{
  "name": "functions",
  "description": "Cloud Functions for Firebase",
  "scripts": {
    "start": "node index.js"
  },
  "engines": {
    "node": "10"
  },
  "dependencies": {
    "body-parser": "^1.19.0",
    "busboy": "^0.3.1",
    "express": "^4.17.1",
    "firebase": "^7.15.0",
    "firebase-admin": "^8.6.0",
    "unirest": "^0.6.0"
  },
  "private": true
}

Upvotes: 4

Views: 1422

Answers (1)

Hossam Elharmil
Hossam Elharmil

Reputation: 151

I fixed it

The problem is that the code was migrated to docker from a cloud function. In the cloud function you use busboy.end(req.rawBody) since this is where the cloud function stores the request after consuming it. But after migration to docker the request is passed directly to the express route and req.rawBody is never populated. The solution is to just replace busboy.end(req.rawBody) with req.pipe(busboy)

Upvotes: 8

Related Questions