Realtime DB Transactions

59 views
Skip to first unread message

Daniel Ansorregui

unread,
Jan 20, 2021, 5:59:30 AM1/20/21
to Firebase Google Group

Hi,

I am designing an application for fun.
With flutter + firebase realtime DB + cloud functions with triggers.

And one of the key "challenges" in the design is that I wanted it to be serverless.
Everything should run on firebase alone, not requiring anything else.
And I need it to be secure, since I am planning to run user code on a sandbox.


My question is on the Cloud Functions triggers.
I have a function to change usernames.
Right now it works using "query+write+delete":

export const changeUsername = functions.https.onCall((data, context) => {
  if(context.auth === null)
    return {'msg': 'Unauthorized user'}
  const uid = context.auth!.uid;
  // Check name
  const newname = validateUserName(data.username)
  if(newname === null)
    return {'msg': 'Invalid username'}

  // Check name is valid
  return dbRoot.child('users/'+uid+'/info/name').once('value')
  .then(function(setName: any) {
    const oldname = setName.val();
    if (newname === oldname) {
      //console.log('Username is the same '+oldname)
      return {'msg': 'Username not changed'}
    }
    //Check the new name is not taken
    return dbRoot.child('usernames/'+newname.toLowerCase()).once('value')
    .then(function(taken: any) {
      if (taken.val() !== null && taken.val() !== uid) {
        //console.log('Username taken by uid '+taken.val())
        return {'msg': 'Username already taken'}
      }
      //Change it
      dbRoot.child('users/'+uid+'/info/name').set(newname)
      if (newname.toLowerCase() !== oldname.toLowerCase()) {
        dbRoot.child('usernames/'+newname.toLowerCase()).set(uid)
        dbRoot.child('usernames/'+oldname.toLowerCase()).remove()
      }
      //console.log('Username changed  '+oldname+' => '+ newname)
      return {'msg': 'Username changed to #'+newname}
    });
  });
});



The problem with this approach is that collisions are possible.
2 users might collide and change to the same name at the same time.
So I implemented a transaction one instead.
But I can't make it work, it times out with a recursion issue.

changeUsernameTransaction
Unhandled error RangeError: Maximum call stack size exceeded

This is the function:
export const changeUsernameTransaction = functions.https.onCall((data, context) => {
  if(context.auth === null)
    return {'msg': 'Unauthorized user'}
  const uid = context.auth!.uid;
  // Check name
  const newname = validateUserName(data.username)
  if(newname === null)
    return {'msg': 'Invalid username'}
  // Check name is valid
  return dbRoot.child('users/'+uid+'/info/name').once('value')
  .then(function(setName: any) {
    const oldname = setName.val();
    if (newname === oldname) {
      //console.log('Username is the same '+oldname)
      return {'msg': 'Username not changed'}
    }
    return dbRoot.child('usernames/'+newname.toLowerCase()).transaction(
      function(currentData: any) {
        if (currentData === null) {
          // We can try to change it
          dbRoot.child('users/'+uid+'/info/name').set(newname)
          if (newname.toLowerCase() !== oldname.toLowerCase()) {
            dbRoot.child('usernames/'+oldname.toLowerCase()).remove()
          }
          return uid;
        } else {
          return; // Abort the transaction.
        }
      }, function(error: any, committed: any, snapshot: any) {
        if (error) {
          console.log('Transaction failed abnormally!', error);
          return {'msg': 'ERROR'};
        } else if (!committed) {
          return {'msg': 'Username already taken'};
        } else {
          return {'msg': 'Username changed to #'+newname}
        }
      });
  });
});

I welcome:
- Any architecture change that will allow me to do this in these contention scenarios.
- How to fix the code
- How to not even need to care about these issues (by letting the DB handle it automatically)

Thanks!



Christian Giovanni Gurdian Ruiz

unread,
Jan 20, 2021, 12:38:56 PM1/20/21
to Firebase Google Group

Hello, try this, if I am not mistaken to be searching for the line you want to modify is in firebase, don't do that, that's an error, you must bring the whole line in the response and send the whole line with the modifications where firebase only will verify the line and update it I had that big problem when I started programming, this is how I solved it I also recommend using Threads, because what I saw you only have in a single class I hope it has been useful, please comment how it went  

Rachel Myers

unread,
Jan 20, 2021, 3:22:50 PM1/20/21
to fireba...@googlegroups.com
If you key a document by the username, keys have to be unique, and the first user that writes the document would win. Make sure you're to `create` the document, not `set` it, so that future attempts to use that username fail instead of overwriting the existing user. That's the only way that I can think of to "let the db handle it automatically". 

--
You received this message because you are subscribed to the Google Groups "Firebase Google Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to firebase-tal...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/firebase-talk/7ebb0ef1-5faf-4f7e-81c9-b3c24a266b67n%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages