Ozgur Sahin
Ozgur Sahin

Reputation: 1453

Proper way to return stream from Firebase callable function (Cloud function)?

What's the proper way to return stream from Firebase callable function? I want it to behave like a proxy and return the response stream directly. Currently, I wait for response and append them like shown below. I don't think it's the right way to do it as it won't return stream directly.

const axios = require('axios');
const functions = require("firebase-functions");
const {PassThrough} = require("stream");
exports.create = functions.https.onCall(async(data, context) => {

    const options = {
        method: 'POST',
        url: '...someurl',
       ,
        responseType: 'stream'
      };

    const response =  await axios(options)

    const chunks = response.data.pipe(new PassThrough({encoding:'base64'}));

    // then we use an async generator to read the chunks
    let str = '';
    for await (let chunk of chunks) {
        str += chunk;
    }
    return str;
}

Upvotes: 3

Views: 1636

Answers (2)

Ozgur Sahin
Ozgur Sahin

Reputation: 1453

If you don't want to upload data to a bucket and directly return it as base64, I found this way is better instead of concanetating chunks yourself. In this way we use promise and then to make sure function waits for the end of the promise.

const axios = require('axios');
const functions = require("firebase-functions");
const {PassThrough} = require("stream");
exports.create = functions.https.onCall(async(data, context) => {

let promise = new Promise((resolve, reject) => {
    inputData.pipe(concat({encoding: 'buffer'}, buf => {

        const options = {
          method: 'POST',
          url: '....',
          data: buf,
          responseType: "arraybuffer"
        };

        axios.request(options).then(function (response) {
    
            let base64String = response.data.toString('base64')
            resolve(base64String)
    
        }).catch(function (error) {
          reject(error)
   
        });
    
    }))
    })

    return promise.then(
        function(value) { 
          console.log('succeed') 
          return value;
      },
        function(error) { 
          console.log(error)
      }
      );
}

Upvotes: 0

Joe Moore
Joe Moore

Reputation: 2023

If you have access to cloud storage buckets you can upload the data to a bucket, generate a download URL and redirect your HTTP request to the download link

//using admin sdk and express
import * as admin from "firebase-admin";
const bucket = admin.storage().bucket('<bucket name>');
await bucket.file('<filename>').save(data).catch();

bucket.file('<filename>').getSignedURL({
   action: 'read',
   expires: Date.now() + 600000, //expires in 10 minutes
}).then(url => {
   response.redirect(url[0]); //redirect to download URL
}).catch(error => {
   console.log(error);
})

You will have to change your storage permissions settings to authenticate the download URL creation and access. You can do this in the rules section of the storage project category on firebase

service firebase.storage {
  match /b/{bucket}/o {
    match /<bucket name>/{allPaths=**} {
      allow read: if true;
      allow write: if request.auth != null; //all other locations are secured by auth
    }
    match /{allPaths=**} {
      allow read, write: if request.auth != null; 
    }
  }
}

Ensure you replace <bucket name> with the name of your bucket. This rule allows anyone to read from the databucket but not write to it unless authorized. All other locations are secure. If you are storing confidential information do not use this permissions workaround and secure the download URL creation by correct auth.

Upvotes: 2

Related Questions