I have an app with different 'procedures' (think posts or pages), which one can like. Currently the process works: Tap like => run method "likeProcedure" => run dispatch action "likeProcedure" => update UI. It usually happens almost immediately, but sometimes there's a lag that gives this a "non-native" feel. Is there some sort of way that I could return feedback immediately, while stile holding single origin of truth on the firebase database?
Thank you!
Page Code:
<v-icon
v-if="!userProfile.likedProcedures || !userProfile.likedProcedures[procedure.id]"
color="grey lighten-1"
#click="likeProcedure({ id: procedure.id })"
>
mdi-star-outline
</v-icon>
and
computed: {
...mapState(["userProfile"]),
procedures() {
return this.$store.getters.getFilteredProcedures();
},
},
Vuex code:
async likeProcedure({ dispatch }, postId) {
const userId = fb.auth.currentUser.uid;
// update user object
await fb.usersCollection.doc(userId).update({
[`likedProcedures.${postId.id}`]: true,
});
dispatch("fetchUserProfile", { uid: userId });
},
Side note: I'm trying to remove the dispatch("fetchUserProfile") command, but this doesn't work, because then I'm calling dispatch without using it. And I cannot remove dispatch because then the object calling it is empty. And I cannot remove the object, because then the argument ('postId') isn't working. So if anyone knows how to deal with that, that would be extremely helpful.
Thank you :)
So this is the best solution I've come up yet. It kind of destroys the idea of a single source of truth, but at least it provides an immediate UI update:
async likeProcedure({ dispatch, state }, postId) {
console.log("likeProcedure");
const userId = fb.auth.currentUser.uid;
// line below provides immediate update to state and hence to the UI
state.userProfile.likedProcedures[postId.id] = true;
// line below updates Firebase database
await fb.usersCollection.doc(userId).update({
[`likedProcedures.${postId.id}`]: state.userProfile.likedProcedures[
postId.id
],
});
// line below then fetches the updated profile from Firebase and updates
// the profile in state. Kind of useless, but ensures that client and
// Firebase are in-sync
dispatch("fetchUserProfile", { uid: userId });
},
async fetchUserProfile({ commit }, user) {
// fetch user profile
const userProfile = await fb.usersCollection.doc(user.uid).get();
// set user profile in state
commit("setUserProfile", userProfile.data());
// change route to dashboard
if (router.currentRoute.path === "/login") {
router.push("/");
}
},
I want to send cloud firestore data to algolia to enable full-text search. Firebase cloud function log is showing an error about application id. I am not able to understand this error and how to fix this.
name: 'RetryError',
message: 'Unreachable hosts - your application id may be incorrect. If the error persists, contact support#algolia.com.'
This is my index.js file
exports.addFirestoreDataToAlgolia =
functions.https.onRequest((req, res) => {
var arr = [];
admin.firestore().collection("tags").get()
.then((docs) => {
docs.forEach((doc) => {
let user = doc.data();
user.objectID = doc.id;
arr.push(user);
})
const client = algoliasearch(ALGOLIA_ID, ALGOLIA_ADMIN_KEY);
const index = client.initIndex(ALGOLIA_INDEX_NAME);
return index.saveObjects(arr, (err, content) => {
if (err) {
res.status(500);
}
else {
res.status(200).send(content);
}
})
})
.catch( err => {
console.log(err);
})
})
Outbound requests (outside of Google services) can only be made from functions on a paid plan (https://firebase.google.com/pricing).
Reason for the wrong appID error is that the Algolia is trying to resolve a dns using your appID, which fails. See https://github.com/algolia/algoliasearch-client-javascript/issues/587#issuecomment-407397688
You have to move off of the free Spark plan in order to call out to Algolia from your function..
I also got this error with NextJS, it was working fine with react but then when I moved to NextJs I got the error.
Turns out it was my .env variables that were not being passed correctly to the client/browser. Renaming the variables from REACT_APP_<variable name> to NEXT_PUBLIC_<variable name> to make them available to the browser as per the NextJs documentation fixed the issue.
NEXT_PUBLIC_ALGOLIA_APP_ID=xxxxxx
NEXT_PUBLIC_ALGOLIA_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
NEXT_PUBLIC_ALGOLIA_ADMIN_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxx
im using this tutorial:
https://firebase.google.com/docs/auth/admin/create-custom-tokens#using_a_service_account_id
to create a node.js function (deployed to google cloud functions) to authenticate my users. the function is super simple:
const admin = require('firebase-admin');
admin.initializeApp({
serviceAccountId: 'authenticator#igibo-b0b27.iam.gserviceaccount.com'
});
exports.authenticate = (req, res) => {
let pass;
let uid;
if (req.query) {
if (req.query.v == 3) {
pass = req.query.p;
uid = req.query.u;
}
admin.auth().createCustomToken(uid)
.then(function(customToken) {
res.status(200).send(customToken);
return customToken;
})
.catch(function(error) {
console.error("Error creating custom token:" + JSON.stringify(error));
res.status(400).send(error);
});
} else {
console.error("EMPTY to authentication");
res.end();
}
};
but im getting this annoying error:
{"code":"auth/insufficient-permission","message":"Permission iam.serviceAccounts.signBlob is required to perform this operation on service account projects/-/serviceAccounts/authenticator#igibo-b0b27.iam.gserviceaccount.com.; Please refer to https://firebase.google.com/docs/auth/admin/create-custom-tokens for more details on how to use and troubleshoot this feature."}
in the very same tutorial it says i must go to IAM and adjust some roles for the service account WHICH I DID but still getting this error.
this is a absolutelly simple task and shouldn't being such a hassle...
what i am forgetting? the id is correct! the role is correct! the code is correct!
what is wrong?
Firebase mentions about this error on its docs:
https://firebase.google.com/docs/auth/admin/create-custom-tokens#failed_to_determine_service_account
You must initialize your app correctly through a JSON config file.
A simple fix would be:
Go to
https://console.cloud.google.com/iam-admin/iam?project=PROJECT_NAME
Edit your default service account.
Add the role Service Account
Token Creator
In a few minutes your project will be able to create signed tokens.
I'm getting a Firebase error "Error: getToken aborted due to token change" while running Firestore transaction using the JavaScript library. The error doesn't get thrown every time and I couldn't find the pattern. I suppose I've implemented some race conditions somewhere.
The user flow in my app goes like this:
Register a new account & submit an additional string in the same form
Log user in after registration using the same credentials
After log in, take that additional string and save it to Firestore (in a transaction).
Transaction fails due to Error: getToken aborted due to token change.
The flow of promises:
firebase.auth().createUserWithEmailAndPassword(email, password)
.catch(signupError => {
// no problems here
})
.then(() => {
return firebase.auth().signInWithEmailAndPassword(email, password)
})
.catch(loginError => {
// no problem here
})
.then((user) => {
// Database write call which fails (see lower code block)
return this.claimInput.writeClaimedPlace(user.user.uid, claimedPlace);
})
.catch(error => {
// "getToken aborted" ERROR GETS CAUGHT HERE, transaction fails
})
}
The database transaction call
firestore.runTransaction(function(transaction) {
return transaction.get(usersBusinessRef).then(function(usersBusinesDoc) {
let claimedPlaces = [];
if (usersBusinesDoc.exists && usersBusinesDoc.data().claimedPlaces) {
claimedPlaces = usersBusinesDoc.data().claimedPlaces;
}
claimedPlaces.push(claimedPlace);
return transaction.set(usersBusinessRef, { claimedPlaces }, { merge: true });
});
});
I couldn't find the error anywhere on google.
I'm thinking the error is caused by the token change that happens at log in. On the other hand I'm reading that Firebase accepts old tokens for a few more moments. Any thoughts?
I got a similar error[error code] while debugging my client which was connecting to firebase via a React App.
The solution was
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read;
allow write: if false;
}
}
}
Putting the above inside the rules part of the firestore settings which apparently means you need to allow reads for external apis but writes are blocked and it was previously blocking both reads and writes.
This could be one of the issues if you are trying to read from your client/server
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read;
allow write;
}
}
}
DISCLAIMER: * I am not an Expert at firebase. I am not sure if this would compromise your DB to be written by external apis since you are opening your firestore firewall by doing this
P.S. there is a firebase-admin module which I think helps in doing writes by handling authentication in a separate fashion. I think that module is more suited for writes and the normal firebase.firestore(app) is for reads.
Due to my probable misuse of anonymous authentication (see How to prevent Firebase anonymous user token from expiring) I have a lot of anonymous users in my app that I don't actually want.
I can't see any way to bulk delete these users. Do I have to do it manually one-by-one? Is there anyway to use the API to access user accounts and manipulate them for users other than the current user?
This code sample uses the Firebase Admin SDK for Node.js, and will delete any user that has no providerData, which means the user is anonymous:
function deleteAnonymousUsers(nextPageToken) {
adminApp
.auth()
.listUsers(20, nextPageToken)
.then(function(listUsersResult) {
listUsersResult.users.forEach(function(userRecord) {
if (userRecord.providerData.length === 0) { //this user is anonymous
console.log(userRecord); // do your delete here
adminApp.auth().deleteUser(userRecord.uid)
.then(function() {
console.log("Successfully deleted user");
})
.catch(function(error) {
console.log("Error deleting user:", error);
});
}
});
if (listUsersResult.pageToken) {
// List next batch of users.
deleteAnonymousUsers(listUsersResult.pageToken);
}
})
.catch(function(error) {
console.log('Error listing users:', error);
});
}
There is no way in the Firebase Console to bulk-delete users.
There is no API to bulk-delete users.
But there is administrative API that allows you to delete user accounts. See https://firebase.google.com/docs/auth/admin/manage-users#delete_a_user
I just wanted to add a method I just used to (sort-of) bulk-delete. Mostly because I felt clever after doing it and I am not that clever.
I downloaded a mouse-automation application that lets you record your mouse clicks then replay it automatically. I just deleted almost 1000 users while playing the piano lol.
I used Macro Recorder and it worked like a charm. Just recorded a few iterations in the console of me deleting users, set it to repeat 500 times and walked away.
I know this isn't a very technical answer, but it saved me hours of monotonous mouse clicking so hopefully someone else looking for a way to bulk-delete will benefit from it as well. I hated the fact that there was no bulk-delete and really needed a way out of it. It only took about 20 manual deletes to realize there were apps that could do what I was doing.
If you do not need to do it on a large scale and you want to delete some anonymous users from Firebase Console UI, but you are lazy to click on 250 users one-by-one, run the following code in your console (screen where table with users is shown):
rows = Array.from(document.querySelectorAll('td.auth-user-identifier-cell')).map(td => td.parentNode).filter((tr) => tr.innerText.includes('anonymous'))
var nextTick = null
function openContextMenu(tr) {
console.log('openning menu')
tr.querySelector('.edit-account-button').click()
nextTick = deleteUser
}
function deleteUser() {
console.log('deleting user')
document.querySelector('.cdk-overlay-connected-position-bounding-box button:last-of-type').click()
nextTick = confirmDelete
}
function confirmDelete() {
console.log('confirming action')
document.querySelector('.cdk-global-overlay-wrapper .confirm-button').click()
nextTick = getUser
}
function getUser() {
console.log('getting user')
openContextMenu(rows.shift())
}
nextTick = getUser
step = 500
setInterval(() => {
nextTick()
}, step)
It basically selects all rows which contain anonymous user and simulate you clicking the three dots, then clicking on delete account and as a last step it confirms action in the modal which appears.
Before running the script, select 250 rows per page in the table's footer. When all anonymous users are removed, you must manually go to next page and re run the script (or code in another "tick" which paginates for you).
It takes 1.5 second to delete one user (you can modify this with step variable, but I do not recommend go lower than 500ms - mind the UI animations).
It runs also in a tab in background so you can watch some YT in meantime :)
Update 2021:
I had around 10,000 anonymous users, and #regretoverflow's solution lead to exceeding the delete user quota. However, slightly tweaking the code to utilize the admin's deleteUsers([userId1, userId2, ...]) API works like a charm.
function deleteAnonymousUsers(nextPageToken: string | undefined) {
firebaseAdmin
.auth()
.listUsers(1000, nextPageToken)
.then(function (listUsersResult) {
const anonymousUsers: string[] = [];
listUsersResult.users.forEach(function (userRecord) {
if (userRecord.providerData.length === 0) {
anonymousUsers.push(userRecord.uid);
}
});
firebaseAdmin
.auth()
.deleteUsers(anonymousUsers)
.then(function () {
if (listUsersResult.pageToken) {
// List next batch of users.
deleteAnonymousUsers(listUsersResult.pageToken);
}
})
})
}
deleteAnonymousUsers(undefined);
There is a firebase-functions-helper package, that can help to delete firebase users in bulk.
// Get all users
firebaseHelper.firebase
.getAllUsers(100)
.then(users => {
users.map(user => {
firebaseHelper.firebase
.deleteUsers([user.uid]);
})
})
The code above will get 100 users, and delete all of them. If you don't pass the number, the default value is 1000. You can read the instruction on Github repository.
I faced the same problem today then I found Firebase Admin SDK. I am using Node.js which is very easy to install, so you can try the following code. It is not a complete answer I know but one can build its own script/application to delete stored uids. Yet, there is no way to retrieve a list, so you have to build one somehow every time you create an anonymous account.
First, download your 'serviceAccountKey.json' which can be done through the Firebase Console (Project Settings). In my case I renamed the download file to a more friendly name and saved to documents folder.
console.firebase.google.com/project/yourprojectname/settings/serviceaccounts/adminsdk
Useful links:
Firebase Admin SDK Setup
Firebase Admin User Management
Firebase Admin Database API
Then, play around using Windows cmd.exe or any other shell. The 'npm install -g' installs firebase-admin globally in your machine.
$ npm install firebase-admin -g
$ node
> var admin = require("firebase-admin");
> admin.initializeApp({
credential: admin.credential.cert("./documents/yourprojectname-firebase-admin.json"),
databaseURL: "https://yourprojectname.firebaseio.com"
});
> var db = admin.database();
// Of course use an existent UID of your choice
> admin.auth().getUser('2w8XEVe7qZaFn2ywc5MnlPdHN90s').then((user) => console.log
(user))
> admin.auth().deleteUser('2w8XEVe7qZaFn2ywc5MnlPdHN90s').then(function() {
console.log("Successfully deleted user");
}).catch(function(error) {
console.log("Error deleting user:", error);
});
// To get access to some key/values in your Database:
> var ref = db.ref("users/1234");
> ref.once("value", function(snapshot) {
console.log(snapshot.val());
});
I was writing myself a firebase functions function with Firebase auth.
It works like a charm for me and i can clean with one API call.
// Delete all Anon User
exports.deleteUser = functions.https.onRequest(async (req, res) => {
const admin = require("firebase-admin");
//initialize auth
admin.initializeApp();
//create auth instance
const auth = admin.auth();
//Get the list of all Users
const allUsers = await auth.listUsers();
//Identify the Anon User give other user null
const allUsersUID = allUsers.users.map((user) => (user.providerData.length === 0) ? user.uid : null);
//remove the null
const filteredallUsersUID = allUsersUID.filter(e => e !== null)
//delete and answer the API call
return auth.deleteUsers(filteredallUsersUID).then(() => res.send("All Anon-User deleted"));
});
With this you can just simply call your API URL
https://[Your_API_URL]/deleteUser
Just require basic knowledge of Firebase Functions.
I assume this could be also added to a cron job.
I had the same problem. because Firebase doesn't provide any API to delete bulk users but this is how I have deleted all anonymous users.
Download all the users as json via firebase tool
firebase auth:export users --format=json
https://firebase.google.com/docs/cli/auth#file_format
You can write a firebase cloud function to trigger or write a action method to trigger
import the json file in to your file,
const Users = require('./users.json'); // ES5 <br>
import Users from './users.json'); // ES6 <br>
normally anonymous user doesn't have email so it is easy to delete the record which doesn't have email id
Users.users.map(user => {
setTimeout(() => {
admin.auth().deleteUser(user.localId).then(() =>{
console.log("Successfully deleted user");
})
.catch((error) => {
console.log("Error deleting user:", error);
});
}, 20000);
});
Don't try to reduce the timeout second. It will throw below error
Error deleting user: { Error: www.googleapis.com network timeout. Please try again.
The Firebase Admin SDK can also delete multiple users at once.
Here is Node.js sample.
admin.auth().deleteUsers([uid1, uid2, uid3])
.then(deleteUsersResult => {
console.log('Successfully deleted ' + deleteUsersResult.successCount + ' users');
console.log('Failed to delete ' + deleteUsersResult.failureCount + ' users');
deleteUsersResult.errors.forEach(err => {
console.log(err.error.toJSON());
});
})
.catch(error => {
console.log('Error deleting users:', error);
});
Notice: there is a limitation as list all users.
The maximum number of users allowed to be deleted is 1000 per batch.