Firebase's set() not working inside onAuthStateChanged() - javascript

I'm currently developing my first "big" web app with ReactJS using Firebase as my DB. Everything was going well until I came across with this issue.
I'm trying to save the user info when they log in into the app with Facebook or Twitter, I'm using the methods provided by Firebase like this:
authenticate(option){
let provider;
switch (option) {
case 'facebook':
provider = new firebase.auth.FacebookAuthProvider();
break;
case 'twitter':
provider = new firebase.auth.TwitterAuthProvider();
break;
default:
provider = 'no-provider'
break;
}
if(provider !== 'no-provider'){
firebase.auth().signInWithPopup(provider).then((result) => {
console.log("Login with " + provider + " ok!");
console.log(result);
}).catch((error) => {
console.log("Error: there was an error trying to login.");
});
}
}
But when I try to save the user into the DB once they've logged in, nothing happens.
This is what I have on App.js
componentDidMount() {
base.auth().onAuthStateChanged((loggedUser) => {
if (loggedUser) {
this.setState({
userName: loggedUser.displayName,
userPhoto: loggedUser.photoURL,
userUID: loggedUser.uid
});
base.database().ref('users/' + loggedUser.uid).on('value',
(user) => {
if (user.val() !== null) {
if (user.val().votedAlbums !== undefined) {
this.setState({
votedAlbums: user.val().votedAlbums
});
}
} else {
console.log(loggedUser.uid);
let userInfo = {
lastActivityTime: new Date()
}
base.database().ref('users/' + loggedUser.uid).set(userInfo).then((ref) =>{
console.log(ref); // this logs 'undefined'
}).catch((error) => {
console.log(error);
});
}
},
(err) => {
this.props.history.push('/error');
}
);
}
});
}
I tried saving the user on another function and it works, both update() and set(). But when I call set() inside onAuthStateChanged() it doesn't.
My DB rules are:
{
"rules": {
".read": true,
".write": "auth != null"
}
}
Am I missing something?
Thanks

As #bennygenel suggested, I changed the value of lastActivityTime to a simple string like this:
componentDidMount() {
base.auth().onAuthStateChanged((loggedUser) => {
if (loggedUser) {
this.setState({
userName: loggedUser.displayName,
userPhoto: loggedUser.photoURL,
userUID: loggedUser.uid
});
base.database().ref('users/' + loggedUser.uid).once('value',
(user) => {
if (user.val() !== null) {
if (user.val().votedAlbums !== undefined) {
this.setState({
votedAlbums: user.val().votedAlbums
});
}
} else {
console.log(loggedUser.uid);
let userInfo = {
lastActivityTime: "whatever" // replaced new Date() with "whatever"
}
base.database().ref('users/' + loggedUser.uid).set(userInfo).then((ref) =>{
console.log(ref);
}).catch((error) => {
console.log(error);
});
}
},
(err) => {
this.props.history.push('/error');
}
);
}
});
}
And now when the user logs in its added to the DB. I also changed the on() method with once() because it was adding the user even if I deleted directly from the Firebase console.
Now to add the date to lastActivityItem I only have to add the toString() method to the generated date like this:
let d = new Date()
let userInfo = {
lastActivityTime: d.toString()
}
If someone knows why this happened, please share.
Thank you #bennygenel !!!

Related

How can I fix double running function in firebase and react project?

I am trying to check existing of data on firebase real-time database and then add new data.
When I add exist data, it works well.
When I add new data, it works two time and don't add new data.
registerStaff = async (model) => {
if (!firebase.apps.length) {
return false;
}
if (model) {
return new Promise((resolve, reject) => {
this.db.ref("tbl_phone_number").on("value", async (snapshot) => {
if (snapshot.exists()) {
const samePhone = await _.filter(snapshot.val(), (o) => {
`find same phone number`;
return o.phone.toString() === model.phone.toString();
});
console.log("checking...", snapshot.val());
if (samePhone.length > 0) {
`checking samephone Number`;
console.log("exist...");
`if exist, return error`;
resolve({
type: "phone",
message: "The phone number is already used.",
});
} else {
`If there is no, add new phone number`;
const newPostKey = this.db
.ref()
.child("tbl_phone_number")
.push().key;
this.db
.ref(`tbl_phone_number/${newPostKey}`)
.set({ phone: model.phone, type: model.type })
.then(() => {
console.log("making...==>");
resolve({
type: "success",
message: "Successfully registered.",
});
})
.catch((err) => {
resolve({
type: "phone",
message: "Sorry. Something went wrong",
});
});
}
}
});
});
}
};
//console log result checking... checking... exist... checking... exist... making...
I found solution to solve this problem in using function "on" and "once".
'on' function of firebase is used to keep data from firebase and add new data automatically when I add new data.
'once' function of firebase is used to change data only once.
Therefore in above question, if I use 'once' function instead of 'on', it will work well.
Good luck.

How to avoid using loginPopup or loginRedirect and silently get the authenticated

I want to avoid using the loginPopup or loginRedirect and get authenticated directly. I am using the below code. I am try to achieve SSO by hosting my chatbot on sharepoint website. As i have already login to my sharepoint website I need to avoid login again.
Current functionality, When i click on the chatbot icon i am getting a pop-up window on the current site. i.e. sharepoint and i am login again, I need to avoid this.
For complete code click here
function onSignInClick() {
alert("Inside onSignInClick function");
let requestObj = {
scopes: ["user.read", 'openid', 'profile']
};
debugger;
clientApplication.loginPopup(requestObj)
.then(onSignin)
.catch(function (error) { console.log(error) });
}
function onSignin(idToken) {
let user = clientApplication.getAccount();
document.getElementById("userName").innerHTML = "Currently logged in as " + user.name;
let requestObj1 = {
scopes: ["user.read", 'openid', 'profile']
};
}
var clientApplication;
(function () {
var msalConfig = {
auth: {
clientId: '<client id>',
authority: 'https://login.microsoftonline.com/<directory id>'
},
cache: {
cacheLocation: 'localStorage',
storeAuthStateInCookie: false
}
};
if (!clientApplication) {
clientApplication = new Msal.UserAgentApplication(msalConfig);
//alert("Inside if clientApplication: " + JSON.parse(clientApplication));
}
}());
(async function main() {
// Add your BOT ID below
var BOT_ID = "<Bot-Id>";
var theURL = "https://powerva.microsoft.com/api/botmanagement/v1/directline/directlinetoken?botId=" + BOT_ID;
//alert("before userId async function: " + JSON.parse(clientApplication));
var userId = clientApplication.account?.accountIdentifier != null
? ("You-customized-prefix" + clientApplication.account.accountIdentifier).substr(0, 64)
: (Math.random().toString() + Date.now().toString()).substr(0, 64)
;
//debugger;
alert("after userId async function: " + JSON.parse(userId));
const { token } = await fetchJSON(theURL);
const directLine = window.WebChat.createDirectLine({ token });
const store = WebChat.createStore({}, ({ dispatch }) => next => action => {
const { type } = action;
if (action.type === 'DIRECT_LINE/CONNECT_FULFILLED') {
dispatch({
type: 'WEB_CHAT/SEND_EVENT',
payload: {
name: 'startConversation',
type: 'event',
value: { text: "hello" }
}
});
return next(action);
}
if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY') {
const activity = action.payload.activity;
let resourceUri;
if (activity.from && activity.from.role === 'bot' && (resourceUri = getOAuthCardResourceUri(activity))) {
exchangeTokenAsync(resourceUri)
.then(function (token) {
if (token) {
directLine.postActivity({
type: 'invoke',
name: 'signin/tokenExchange',
value: {
id: activity.attachments[0].content.tokenExchangeResource.id,
connectionName: activity.attachments[0].content.connectionName,
token
},
"from": {
id: userId,
name: clientApplication.account.name,
role: "user"
}
}).subscribe(
id => {
if (id === 'retry') {
// bot was not able to handle the invoke, so display the oauthCard
return next(action);
}
// else: tokenexchange successful and we do not display the oauthCard
},
error => {
// an error occurred to display the oauthCard
return next(action);
}
);
return;
}
else
return next(action);
});
}
else
return next(action);
}
else
return next(action);
});
window.WebChat.renderWebChat(
{
directLine: directLine,
store,
userID: userId,
styleOptions
},
document.getElementById('webchat')
);
})()
.catch(err => console.error("An error occurred: " + err));
By your scenario to eliminate loginPopup or loginRedirect in autentication,I understood that you don't require user involvement to login. The possible solution for your scenario is to use Client credential flow.
Client Credential Flow does not require user login and can silently Sign-in with application permissions.
Please refer this code sample which can help.

Update V-if in vuejs reactively

I was wondering if it's possible to re-render this v-if statement inside my component.
I am enabling/disabling a user account through firebase admin functions. This currently works, however whenever I disable a user I have to refresh the page in order to show updates, I can manually refresh, but wondered if there is a way to do this with reactivity? I've tried to update the array manually (UsersAuth contains all the users from Firebase, with the disabled: true|false boolean).
html
<span v-if="usersAuth[index].disabled === true"> <button type="button" v-on:click="enableUser(user.id, index)" class="btn btn-success">Enable</button></span>
<span v-if="usersAuth[index].disabled === false"><button type="button" v-on:click="disableUser(user.id)" class="btn btn-primary">Disable</button></span>
VueJS Methods
data () {
return {
users: [],
user: null,
usersAuth: null,
selecteduser: null
}
},
created () {
// call all users from the firebase store.
const addMessage = firebase.functions().httpsCallable('listAllUsers')
addMessage()
.then(result => {
this.usersAuth = result.data.users
})
firebase.auth().onAuthStateChanged((user) => {
this.user = user
})
this.users = []
firebase
.firestore()
.collection('roles')
.get()
.then(snap => {
snap.forEach(doc => {
const user = doc.data()
console.log(doc.data())
user.id = doc.id
this.users.push(user)
})
})
// get the users' enabled status
},
disableUser (uid) {
const addMessage = firebase.functions().httpsCallable('disableUser')
const data = { uid: uid }
addMessage(data)
.then((result) => {
if (result === true) {
console.log(this.userAuth)
}
})
.catch(function (error) {
console.log(error)
})
},
enableUser (uid, index) {
const addMessage = firebase.functions().httpsCallable('enableUser')
const data = { uid: uid }
addMessage(data)
.then((result) => {
this.usersAuth[index].disabled = true
})
.catch(function (error) {
console.log(error)
})
},
listAllUsers () {
const addMessage = firebase.functions().httpsCallable('listAllUsers')
addMessage()
.then((result) => {
console.log(result)
})
.catch(function (error) {
console.log(error)
})
}
Firebase function (if you require this)
exports.disableUser = functions.https.onCall(async (data, context) => {
if (!context.auth.token.superadmin) return
try {
listUsers = admin.auth().updateUser(data.uid, {
disabled: true
})
.then(function() {
console.log("Successfully disabled user " + data.uid);
})
return true
} catch (error) {
console.log(error)
}
});
exports.enableUser = functions.https.onCall(async (data, context) => {
if (!context.auth.token.superadmin) return
try {
listUsers = admin.auth().updateUser(data.uid, {
disabled: false
})
.then(function() {
console.log("Successfully disabled user " + data.uid);
})
return true
} catch (error) {
console.log(error)
}
});
exports.listAllUsers = functions.https.onCall((data, context) => {
if (!context.auth.token.superadmin) return
try {
return admin.auth().listUsers()
} catch (error) {
console.log(error)
}
});
In your enableUser method, this.usersAuth[index].disabled = true should be this.usersAuth[index].disabled = false, so that you're enabling the user rather than disabling them.
You can read The Vue Instance and Reactivity in Depth for more information about how reacitivty works with Vue.
When a Vue instance is created, it adds all the properties found in
its data object to Vue’s reactivity system. When the values of those
properties change, the view will “react”, updating to match the new
values.
On a side note, if disabled is either true or false, you can simplify your code to:
<span v-if="usersAuth[index].disabled">
and <span v-else>

Got "Possible Unhandled Promise Rejection" in my redux action with firebase

I got the following error and I'm looking for the solution.
Possible Unhandled Promise Rejection (id: 0)
TypeError: undefined is not an object (evaluating 'e.type')
I found a lot of questions about this problem, almost solutions are inserting throw error into catch statement.
So I tried it, but the error's not gone.
What is wrong with my code?
Technologies
react-native
redux
react-navigation
This is my code.
Action
:
export function requestSignIn(email, password) {
return function (dispatch) {
// change loading status
dispatch(startedRequest());
if (email && password) {
firebase.auth().signInWithEmailAndPassword(email, password)
.then(response => {
if (response) {
// save email and password in local secure storage.
SecureStorage.setItem('email', email);
SecureStorage.setItem('password', password);
// save uid into store
dispatch(requestSignInSuccess(response.user.uid));
dispatch(NavigationService.navigate('Home', { uid: response.user.uid }));
} else {
dispatch(requestSignInFailure('network error'));
}
})
.catch(error => {
// I tried inserting 'throw error' here.
// throw error;
switch (error.code) {
case 'auth/user-not-found':
dispatch(requestSignInFailure('user not found'));
break;
case 'auth/invalid-email':
dispatch(requestSignInFailure('invalid email'));
break;
default:
dispatch(requestSignInFailure('something went wrong'));
}
})
} else {
dispatch(requestSignInFailure('error message from else statement'))
}
}
}
export function requestSignInSuccess(uid) {
return {
type: REQUEST_SIGN_IN_SUCCESS,
payload: {
uid: uid
}
}
}
export function requestSignInFailure(errorMessage) {
return {
type: REQUEST_SIGN_IN_FAILURE,
payload: {
errorMessage: errorMessage
}
}
}
The component calls the above Action
:
class SignIn extends Component {
async handleSignIn() {
const { requestSignIn } = this.props;
const { email, password } = this.props.auth;
Keyboard.dismiss();
requestSignIn(email, password);
}
:
Addition
I removed the statement dispatch(NavigationService.navigate('Home', { uid: response.user.uid }));, the error won't appear.
The cause of the problem is the screen has changed while the function is running?
If it's true, how do I execute dispatch(NavigationService.navigate('Home', { uid: response.user.uid }));
after dispatch(requestSignInSuccess(response.user.uid)); finished?

"documentRef" is not a valid DocumentReference showing while updating a field

I'm trying to count the number of notification. My database structure is Users/userId/notification/doc. I want to keep track number of the notification. My code is
notificationCount: async (change,context) => {
const countRef=db.collection("Users").doc(context.params.userID);
let increment;
if (change.after.exists && !change.before.exists) {
increment = 1;
} else if (!change.after.exists && change.before.exists) {
increment = -1;
} else {
return null;
}
return db.runTransaction((transaction) => {
return transaction.get(countRef).then((sfDoc) => {
if (!sfDoc.exists) {
return transaction.set({
notifications: 0
}, { merge: true });
} else {
var newNotification = sfDoc.data().population + increment;
return transaction.set({
notifications: newNotification
});
}
});
}).then(() => {
console.log("Transaction successfully committed!");
return null;
}).catch((error) => {
console.log("Transaction failed: ", error);
});
}
But I'm getting error
at Object.validateDocumentReference (/srv/node_modules/#google-cloud/firestore/build/src/reference.js:1810:15)
at WriteBatch.set (/srv/node_modules/#google-cloud/firestore/build/src/write-batch.js:241:21)
at Transaction.set (/srv/node_modules/#google-cloud/firestore/build/src/transaction.js:182:26)
at transaction.get.then (/srv/counter.js:71:30)
at <anonymous>
at process._tickDomainCallback (internal/process/next_tick.js:229:7)
Your error is in two places:
return transaction.set({
notifications: 0
}, { merge: true });
And here:
return transaction.set({
notifications: newNotification
});
You have to call out a specific document to update when calling transaction.set(). As you can see from the linked API docs, the first argument to set() must be a DocumentReference type object, but you're passing a plain old JavaScript object. Perhaps you meant to use the document reference countRef that you used to read from the same transaction:
return transaction.set(countRef, {
notifications: newNotification
});

Categories