Firebase-tools: Locally run Firebase Functions aren't accessing Storage or Firestore

Created on 6 Apr 2018  ·  23Comments  ·  Source: firebase/firebase-tools

I have a firebase function that uses expressjs to run a REST endpoint. It receives files in a POST request, uploads them to Firebase Storage, and records some metadata in Firestore.

The function works correctly when I deploy the function to the cloud. However, when I serve it locally with firebase serve the function appears to work correctly but it doesn't access Storage or Firestore. I tried inserting some console.logs and it seems to just skip right over the code without generating an error or anything.

Here is my function:

app.post('/account/:uId/persona/:pId/photos', (req, res) => {
  if (!req.file) {
    return res.status(400).send('No file uploaded.');
  }

  const uid = req.params.uId;
  const pid = req.params.pId;
  const filename = crypto.createHash('md5').update(req.file.buffer).digest('hex');
  const extension = req.file.mimetype.split('/')[1];
  const accessType = req.fields.accessType ? 'private' : 'public';
  const filePath = `/users/${uid}/personas/${pid}/photo_assets`;
  const bucket = gcs.bucket('path/to/bucket');

  // Create a new blob in the bucket and upload the file data.
  const blob = bucket.file(`${filePath}/${filename}.${extension}`);
  const blobStream = blob.createWriteStream();

  blobStream.on('error', (err) => {
    next(err);
  });

  blobStream.on('finish', () => {
    // The public URL can be used to directly access the file via HTTP.
    const publicUrl = format(`https://storage.googleapis.com/${bucket.name}/${blob.name}`);
    const publicGs = format(`gs://${bucket.name}/${blob.name}`);
    const userReference = admin.firestore().collection('tripp-users').doc(uid);
    let userData = userReference.get()
      .then(doc => {
        if (!doc.exists) {
          console.log('No such user!', uid);
          return res.status(404).send("No such user exists");
        }
        if (doc.data().personas) {
          let updateDoc = doc.data();
          var elementPos = updateDoc.personas.map(function(p) {return p.personaId; }).indexOf(pid);
          console.log('elementpos',elementPos);
          var persona = updateDoc.personas[elementPos];
          if (persona) {
            // Add File to Persona Photo Asset List
            if (!persona.photo_assets) {
              updateDoc.personas[elementPos].photo_assets = {};
            }
            else {
              updateDoc.personas[elementPos].photo_assets[filename] = accessType;
            }
            userReference.set({'personas': updateDoc.personas}, {merge: true});
          } else {
            console.log('no such persona id', pid);
            return res.status(404).send("No such persona exists");
          }
        }
        return
      })
      .catch(err => {
          console.log('Error getting document', err);
      });
  });
  blobStream.end(req.file.buffer);

  return res.status(201).send('image asset successfully added');
});

Most helpful comment

Same here using the firebase functions:shell with:
[email protected]
[email protected]
[email protected]

Case 1 (no promise) :heavy_check_mark:

module.exports = functions.https.onRequest((req, res) => {
  console.log('OK')
  res.status(200).end()
})

'OK' is properly printed by console.log. Function ends with HTTP 200 just fine.

Case 2 (promise) :x:

module.exports = functions.https.onRequest((req, res) => {
  admin
    .firestore()
    .collection('orders')
    .get()
    .then(snap => {
      console.log('OK')
      res.status(200).end()
    })
    .catch(error => {
      console.log('Error!')
      res.status(500).end()
    })
})

Nothing gets printed by console.log, but this happens:

info: Execution took 1023 ms, finished with status: 'crash'
RESPONSE RECEIVED FROM FUNCTION: 500, [object Object]

When deployed and running on the real server it works though (HTTP 200 appears in the Log in the Firebase Console).

Case 3 (immediatelly resolved promise) :heavy_check_mark:

module.exports = functions.https.onRequest((req, res) => {
  new Promise(resolve => {
    console.log('OK')
    res.status(200).end()
  })
})

'OK' is properly printed by console.log. Function ends with HTTP 200 just fine.

Case 4 (promise with setTimeout) :x:

module.exports = functions.https.onRequest((req, res) => {
  new Promise(resolve => {
    setTimeout(() => {
      console.log('OK')
      res.status(200).end()
    }, 5000)
  })
})

Same as "Case 2".

All 23 comments

Can you update your firebase-tools to 3.18.1 and try again?

I just did and no change.

I suspect it's because you're trying to access GCS, which the out of the box credential may not work for. Try using your own project's service account credentials by following the instructions here: https://firebase.google.com/docs/functions/local-emulator#set_up_admin_credentials_optional

I actually followed those instructions earlier today and setup the credentials.

So it didn't work to use a service account key?

Unfortunately no, it did not make a difference.

I'm having the same problem running a firebase function thats accessing firestore using firebase-admin (so no GCS involved).

  app.get('/account/:uId/persona/:pId/photos/private', (req, res) => {
    const uid = req.params.uId;
    const pid = req.params.pId;

    let promiseChain;

    // getUserDocument :: string -> object
    const getUserDocument = uid =>
      admin
        .firestore()
        .collection('tripp-users')
        .doc(uid)
        .get()
        .then(doc => {
          if (!doc || !doc.exists) {
            console.log('No such user!', uid);
            res.status(404).send('No such user exists!');
            promiseChain.cancel();
          }
          return doc.data();
        })
        .catch(err => {
          console.log('Error getting user', err);
          res.status(500).send(`Error getting user document for uid: ${uid}`);
          promiseChain.cancel();
        });

    // getPersona :: object -> object
    const getPersona = data => {
      const matchingPersonas = data.personas.filter(
        persona => persona.personaId === pid
      );
      if (!matchingPersonas.length) {
        console.log('No such persona!', pid);
        res.status(404).send('No such persona exists');
        promiseChain.cancel();
      }
      return matchingPersonas[0];
    };

    // getPhotoAssets :: object -> object
    const getPhotoAssets = persona => persona.photo_assets;

    // getPublicImages :: object -> [string]
    const getPublicImages = images =>
      Object.keys(images).filter(key => images[key] === 'private');

    // getImageUrl :: string -> [string, string]
    const getImageUrl = image => {
      const bucket = gcs.bucket('celestial-geode-191223.appspot.com');
      const file = `users/${uid}/personas/${pid}/photo_assets/${image}`;
      const imageId = image.split('.')[0];
      const publicUrl = format(`https://storage.googleapis.com/${file}`);
      return [imageId, publicUrl];
    };

    // getAllImageUrls :: [string] -> [string, string]
    const getAllImageUrls = images => {
      const imageUrls = images.map(image => getImageUrl(image));
      return imageUrls;
    };

    promiseChain = Promise.resolve(uid)
      .then(getUserDocument)
      .then(getPersona)
      .then(getPhotoAssets)
      .then(getPublicImages)
      .then(getAllImageUrls)
      .then(imageUrls => res.status(201).json(imageUrls))
      .catch(err => {
        console.log('Error getting images', err);
        return res.status(500).send('Error getting images.');
      });
  });

Sorry for the large codeblocks. When I host this cloud function locally and make a GET request to it, I get this following error response:

{
    "error": {
        "code": 500,
        "status": "INTERNAL",
        "message": "function crashed",
        "errors": [
            "socket hang up"
        ]
    }
}

The console logs the error as:

info: User function triggered, starting execution
info: Execution took 1193 ms, finished with status: 'crash'

I inserted a bunch of console.logs and the function crashes when executing getUserDocument(). This cloud function works correctly when deployed.

I wrote a stripped down function to show just where the crash is happening:

  app.get('/account/:uId/persona/:pId/test', (req, res) => {
    const uid = req.params.uId;
    const pid = req.params.pId;

    admin.firestore()
      .collection('tripp-users')
      .doc(uid)
      .get()
      .then(succ => res.status(204).send("success"))
      .catch(err => res.status(500).send("failure"))
  });

Its not hitting the catch at then end of my function.

I have the same issue after update my packages (firebase-tools, firebase-admin, firebase-functions) to the latest version:

Execution took 940 ms, finished with status: 'crash'

Same here using the firebase functions:shell with:
[email protected]
[email protected]
[email protected]

Case 1 (no promise) :heavy_check_mark:

module.exports = functions.https.onRequest((req, res) => {
  console.log('OK')
  res.status(200).end()
})

'OK' is properly printed by console.log. Function ends with HTTP 200 just fine.

Case 2 (promise) :x:

module.exports = functions.https.onRequest((req, res) => {
  admin
    .firestore()
    .collection('orders')
    .get()
    .then(snap => {
      console.log('OK')
      res.status(200).end()
    })
    .catch(error => {
      console.log('Error!')
      res.status(500).end()
    })
})

Nothing gets printed by console.log, but this happens:

info: Execution took 1023 ms, finished with status: 'crash'
RESPONSE RECEIVED FROM FUNCTION: 500, [object Object]

When deployed and running on the real server it works though (HTTP 200 appears in the Log in the Firebase Console).

Case 3 (immediatelly resolved promise) :heavy_check_mark:

module.exports = functions.https.onRequest((req, res) => {
  new Promise(resolve => {
    console.log('OK')
    res.status(200).end()
  })
})

'OK' is properly printed by console.log. Function ends with HTTP 200 just fine.

Case 4 (promise with setTimeout) :x:

module.exports = functions.https.onRequest((req, res) => {
  new Promise(resolve => {
    setTimeout(() => {
      console.log('OK')
      res.status(200).end()
    }, 5000)
  })
})

Same as "Case 2".

BTW, I'm using node v6.11.5 as recommended by the docs and the error when using firebase serve --only functions instead of firebase functions:shell is:

{
    "error": {
        "code": 500,
        "status": "INTERNAL",
        "message": "function crashed",
        "errors": [
            "socket hang up"
        ]
    }
}

Thank you for the detailed descriptions and repro instructions everyone, I will investigate.

I was able to fix it in 2 ways, one was by following the server guide here where you create a service account certificate in json form, download it (put it somewhere in your code) and do

var admin = require('firebase-admin');
var serviceAccount = require('path/to/serviceAccountKey.json');

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: 'https://<DATABASE_NAME>.firebaseio.com',
});

the second option is to keep the initialization as it used to be done pre 1.0

var admin = require('firebase-admin');
var functions = require('firebase-functions');

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

both of this options work, but we decided to go with the second one as we don't know what does funcitions.config does on the server vs what we setup manually and decided to with that for this case, it's good to know that it works though.

now I don't know if this is a bug or not as I have seen the firebase team releasing a lot of revisions since 1.0 hit last week, I can confirm our package.json is pointing to the latest versions released as I write this; so I am guessing they are still ironing out a few things from the change.

hope it works

@samiq Didn't work for me.

Also, using the serviceAccount.json approach gives me the following error after running firebase functions:shell:

error:  code=500, status=INTERNAL, message=Function worker killed by signal: SIGTERM, errors=[Function worker killed by signal: SIGTERM]
⚠  functions: Error from emulator. [object Object]

@gustavopch make sure you have the latest version of the tools, one of my team members were having issues and they got fixed after updating

@ssbothwell and @gustavopch can you try this branch?

npm i -g https://github.com/firebase/firebase-tools.git#ll-fixemuasyncerror

@samiq Your suggestion is actually fixing a different issue. This issue manifests in async functions in general (see case 4 in https://github.com/firebase/firebase-tools/issues/720#issuecomment-380261653), not anything that's specific to Firebase projects and credentials.

@laurenzlong ok yeah,i was referencing the issue described originally as that's how I landed here. @gustavopch case is indeed different. thanks for the help and good luck everyone!!

@laurenzlong It worked. Thanks a lot!

Sorry I'm just now catching up on this thread.

solomon@prefect functions $ npm i -g https://github.com/firebase/firebase-tools.git#ll-fixemuasyncerror
npm ERR! code 1
npm ERR! Command failed: /usr/bin/git checkout ll-fixemuasyncerror
npm ERR! error: pathspec 'll-fixemuasyncerror' did not match any file(s) known to git.
npm ERR!

@ssbothwell That's because the branch was already merged. Use this instead: npm i -g https://github.com/firebase/firebase-tools.git

Same here using the firebase functions:shell with:
[email protected]
[email protected]
[email protected]

Case 1 (no promise) ✔️

module.exports = functions.https.onRequest((req, res) => {
  console.log('OK')
  res.status(200).end()
})

'OK' is properly printed by console.log. Function ends with HTTP 200 just fine.

Case 2 (promise) ❌

module.exports = functions.https.onRequest((req, res) => {
  admin
    .firestore()
    .collection('orders')
    .get()
    .then(snap => {
      console.log('OK')
      res.status(200).end()
    })
    .catch(error => {
      console.log('Error!')
      res.status(500).end()
    })
})

Nothing gets printed by console.log, but this happens:

info: Execution took 1023 ms, finished with status: 'crash'
RESPONSE RECEIVED FROM FUNCTION: 500, [object Object]

When deployed and running on the real server it works though (HTTP 200 appears in the Log in the Firebase Console).

Case 3 (immediatelly resolved promise) ✔️

module.exports = functions.https.onRequest((req, res) => {
  new Promise(resolve => {
    console.log('OK')
    res.status(200).end()
  })
})

'OK' is properly printed by console.log. Function ends with HTTP 200 just fine.

Case 4 (promise with setTimeout) ❌

module.exports = functions.https.onRequest((req, res) => {
  new Promise(resolve => {
    setTimeout(() => {
      console.log('OK')
      res.status(200).end()
    }, 5000)
  })
})

Same as "Case 2".

Not sure if you managed to resolve this issue, but on the surface, it looks to me that the problem could be because you are not actually returning the Promise, so it might be that the function is no longer available by the time the Promise resolves.

+1 for returning Promises.

@ssbothwell Try doing the same thing with fs.writeFileSync() from the Node File System module. I had a similar issue and this resolved it!

Was this page helpful?
0 / 5 - 0 ratings