0

I am using the firebase admin sdk with bubble as a nodejs project

here is the code i am trying. THIS doesn't work. I have included updated code at the bottom but will leave this for now hoping others can learn from my errors.

function(properties, context) {
    console.log('trying')
    var list = []
    var util = require("util")

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

    var serviceAccount = <COPIED/PASTED FROM FIREBASE>

    // Fetch the service account key JSON file contenT


    if (admin != null) {
        admin.initializeApp({
            credential: admin.credential.cert(serviceAccount),
            databaseURL: "https://login-database-brc.firebaseio.com"

        })
        list.push('logged into app')
        var db = admin.database();
        var ref = db.ref('BRC/4jV39aEK6jOecLdKONJMKatsEYn1/data')


        ref.once('value').then((snapshot) => {
            snapshot.val().forEach(element => {
                list.push('check')
                list.push(element.Name)

            })


        });
    } else {
        list.push('nothing happened')
    }



    // As an admin, the app has access to read and write all data, regardless of Security Rules



    return {
        data: list
    }

}

here are the rules

{
    "rules":{
      "BRC": {
        "$uid":{ 
            ".read": "$uid === auth.uid",
            ".write": "$uid === auth.uid"
        }
    }
}
}

I only end up seeing 'logged in' in the output list. I would expect to see the name property from each object in the data bucket.

I am able to make this work fine client side using the standard sdk, and using password authentication. I can’t seem to get beyond initializing the apps Firebase instance.

i do have /BRC with rules that require authentication, but with the server SDK i didnt think this mattered

I want to access all ojects held within BRC/4jV39aEK6jOecLdKONJMKatsEYn1/data

[UPDATED CODE TO PERFORM OPERATIONS SYNCRONOUS]

function(properties, context) {
var serviceAccount = {'jsonObjectFromFirebase'};
var list =[];

var admin = require("firebase-admin");


// Initialize the app with a service account, granting admin privileges
if (!admin.apps.length){
    initializeApp(serviceAccount)
};

// As an admin, the app has access to read and write all data, regardless of Security Rules
    
admin.database().ref('test').once('value')
    .then((snapshot) => {
    snapshot.val().forEach(element => {
        let a = element.name;
        list.push(a);
     })
    
    return_data(list)
    
});
    
function return_data (list){
    return { data: list};
};
    
function initializeApp (serviceAccount){
    admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: "https://thunkableproject-f51d9-default-rtdb.firebaseio.com"
});
}


}

the issue is that now return_data is never called. so I'm not sure what's happening here.

[update 2, trying again to create a synchronous call


var list =[]

var admin = require("firebase-admin");


// Initialize the app with a service account, granting admin privileges
if (!admin.apps.length){
    initializeApp(serviceAccount)
}

// As an admin, the app has access to read and write all data, regardless of Security Rules
    
admin.database().ref('BRC/4jV39aEK6jOecLdKONJMKatsEYn1/data').once("value", function(snapshot){
    let element = snapshot.val()
    let list = []
    for (var key in element) {
       list.push(element[key].Name) 
    }
    
    return_data(list)
  })

    
function return_data (list){
    return { data: list}
}
    
function initializeApp (serviceAccount){
    admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: "https://login-database-brc.firebaseio.com"
});
}

I have also tried this to no avail

    
var promise = admin.database().ref('BRC/4jV39aEK6jOecLdKONJMKatsEYn1/data').once("value");
    
promise.then(snapshot => {
    let element = snapshot.val()
    let list = []
    for (var key in element) {
       list.push(element[key].name) 
    };})
.catch(error => {
    alert(error)
});
    
return { data: list}

Update 9:23 EST 12/29/20

I think I now am calling this function in an asynchronous way that should wait for a callback. the issue now is that the code in the .then() never executes.


function getData() {
   var promise = admin.database().ref('BRC/4jV39aEK6jOecLdKONJMKatsEYn1/data').once("value");

   promise.then(snapshot => {
      let element = snapshot.val()
      let list = []
      for (var key in element) {
         list.push(element[key].Name)
      };
      return list;
   })
      .catch(error => {
         return error
      });

   return promise;
}




getData().then((list) => {
   return { "data": list }
})
Jared Gibb
  • 47
  • 5

2 Answers2

0

The problem is that your return { data: list } runs before the list.push(element.Name) happens. This is because data is loaded from Firebase asynchronously, and runs out of the order you may expect. This behavior is the same on both client and server-side SDKs, and in most cloud based APIs you'll find these days.

The typical solution is to return a promise, instead of the actual value. Instead of explaining it here, I recommend reading these:


Edit:

In your edit, you have to return the promise. For example in function defined like this:

function getData() {
  var promise = admin.database().ref('BRC/4jV39aEK6jOecLdKONJMKatsEYn1/data').once("value");
    
  promise.then(snapshot => {
    let element = snapshot.val()
    let list = []
    for (var key in element) {
       list.push(element[key].name) 
    };
    return list;
  })
  .catch(error => {
    alert(error)
  });
    
  return promise;
}

Now you can use this function like this:

readData().then((list) => {
  console.log(data);
})

Note that (as said in the comments) this does not make the code synchronous, as that isn't possible. It merely returns the promise to the caller, which then "waits" with then for the data to be available.

If you're on a modern enough platform, you may be able to use await in that last snippet:

var list = await readData();
console.log(data);

But while this may look more familiar, it still does the exact same thing behind the scenes and is not synchronous.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • I have tried to address. see my code update above. perhaps you can point me further in the right direction. I am a baby at firebase and JS – Jared Gibb Dec 29 '20 at 16:15
  • You can't make an asynchronous operation synchronous. You can't return something that hasn't loaded yet. The last link in my answer provides multiple examples of how to handle this, and I don't see anything of that in your edit. – Frank van Puffelen Dec 29 '20 at 18:29
  • Thank you for taking time @Frank van Puffelen. I have tried to recreate the synchronous call. still not understanding how to return the promise and wait for it. I followed the example from the last link in your previous answer. and included the code above – Jared Gibb Dec 29 '20 at 19:28
  • Again: you can't make an asynchronous operation synchronous. You can't return something that hasn't loaded yet. But by using a promise, you can allow the caller to "wait" until the data is available. I included an example of that in my answer. But at this point, I'm just repeating what's in the answers I linked. So if you're still struggling with this, I recommend reading those links, and some of the *many* other tutorials out there. Dealing with async function calls is initially tricky, but you will have to embraced it if you want to make use of cloud APIs such as Firebase. – Frank van Puffelen Dec 29 '20 at 21:02
  • ok. So i believe that I am now using the code correctly. I tried to emulate what you showed above. I declare getData and return a promise. The weird part is that I am not getting any output. not even when I dont use the promise. I have explicitly defined a variable. I would at least expect null or an array of 0 to be returned. I dont get any output using this code in the latest update. Again, thank you for your time I appreciate how valuable it is! – Jared Gibb Dec 30 '20 at 02:34
  • If you log the data as I do in my answer, it should show up. But this won't work: `getData().then((list) => { return { "data": list } })`. Once more: you can't return something that is asynchronously loaded, and your `getData()` is now an asynchronous method. There is no way to make asynchronous code work synchronously, no matter how often you wrap it. – Frank van Puffelen Dec 30 '20 at 02:48
  • Now i just feel I'm out of my wheelhouse. I dont see where my code differs from your code other than switching console.log for return {} I am not sure how I would then use the value that is returned. I did watch the video sequence. I am reading through the blog post and next your thread once again. I will watch the videos again tomorrow and try to work through a tutorial. Again, thank you for your time. I'm hoping I can come to some solution. it was so much easier with the client SDK for some reason. – Jared Gibb Dec 30 '20 at 03:29
  • In the last snippet you have, what does `return { "data": list }` do? Who is consuming that return value? If you put a breakpoint on that code you'll see it never gets the return value. Any code that needs the value from an asynchronous call, needs to be inside the `then` callback. – Frank van Puffelen Dec 30 '20 at 04:21
0

I was able to take your feedback Frank van Puffelen along with the help of a friend.

function(properties, context) {
    var serviceAccount = <serviceAccountPath>


    var admin = require("firebase-admin");


    // Initialize the app with a service account, granting admin privileges
    if (!admin.apps.length) {
        initializeApp(serviceAccount)
    }

    function initializeApp(serviceAccount) {
        admin.initializeApp({
            credential: admin.credential.cert(serviceAccount),
            databaseURL: "https://login-database-brc.firebaseio.com"
        });
    }


    let list = context.async(async callback => {
        try {
            const snapshot = await admin.database().ref('BRC/4jV39aEK6jOecLdKONJMKatsEYn1/data').once("value");
            const element = snapshot.val()
            let list = []
            for (key in element) {
                list.push(element[key].Name)
            };
            callback(null, list);
        } catch (err) {
            callback(err);
        }
    })

    return {
        "data": list
    }


}
Jared Gibb
  • 47
  • 5