There is sample code for uploading files directly to Cloud Functions here: https://cloud.google.com/functions/docs/writing/http#multipart_data_and_file_uploads, but be aware there is a file size limit when using this approach (10MB). If you have files larger than that, I recommend you use the Cloud Storage approach and use something like the Firebase Realtime Database to manage the state on the client.
So, you generate the signed upload URL using a Cloud Function, then use the RTDB to track progress for the client. Return the URL and a reference to the location in the DB where you're going to track progress. The client would watch this location for updates. The client then uploads the file to Cloud Storage and the second function is triggered. After post processing it updates the RTDB which pushes the update down to the client. From the client's perspective this is all synchronous, but it's actually a series of async operations with state coalescing in the database.
Here's the sample code for inline updates taken from the docs if you're OK with file sizes < 10MB:
/**
* Parses a 'multipart/form-data' upload request
*
* @param {Object} req Cloud Function request context.
* @param {Object} res Cloud Function response context.
*/
const path = require('path');
const os = require('os');
const fs = require('fs');
// Node.js doesn't have a built-in multipart/form-data parsing library.
// Instead, we can use the 'busboy' library from NPM to parse these requests.
const Busboy = require('busboy');
exports.uploadFile = (req, res) => {
if (req.method === 'POST') {
const busboy = new Busboy({ headers: req.headers });
const tmpdir = os.tmpdir();
// This object will accumulate all the fields, keyed by their name
const fields = {};
// This object will accumulate all the uploaded files, keyed by their name.
const uploads = {};
// This code will process each non-file field in the form.
busboy.on('field', (fieldname, val) => {
// TODO(developer): Process submitted field values here
console.log(`Processed field ${fieldname}: ${val}.`);
fields[fieldname] = val;
});
let fileWrites = [];
// This code will process each file uploaded.
busboy.on('file', (fieldname, file, filename) => {
// Note: os.tmpdir() points to an in-memory file system on GCF
// Thus, any files in it must fit in the instance's memory.
console.log(`Processed file ${filename}`);
const filepath = path.join(tmpdir, filename);
uploads[fieldname] = filepath;
const writeStream = fs.createWriteStream(filepath);
file.pipe(writeStream);
// File was processed by Busboy; wait for it to be written to disk.
const promise = new Promise((resolve, reject) => {
file.on('end', () => {
writeStream.end();
});
writeStream.on('finish', resolve);
writeStream.on('error', reject);
});
fileWrites.push(promise);
});
// Triggered once all uploaded files are processed by Busboy.
// We still need to wait for the disk writes (saves) to complete.
busboy.on('finish', () => {
Promise.all(fileWrites)
.then(() => {
// TODO(developer): Process saved files here
for (const name in uploads) {
const file = uploads[name];
fs.unlinkSync(file);
}
res.send();
});
});
busboy.end(req.rawBody);
} else {
// Return a "method not allowed" error
res.status(405).end();
}
};