In a simple SPA with Vue and Firebase, there are two routes: Login and Chat.
Upon login, the user is redirected to the Chat route where Firebase database bindings are done manually using vuefire's $bindAsArray(), inside the created() life-cycle hook. This is because the bindings require the uid assigned by the Firebase authentication to be available.
This works fine, until the user refreshes the page. If auth().currentUser is used to get the uid, it returns null. If the auth().onAuthStateChanged() watcher is used, Vue attempts to render the component before the Firebase database bindings are done. What am I missing?
I come across this scenario, as workaround I use component wrapper that has UID as property, if UID is null show a waiting message/animation else show your original component.
My real scenario is a little more complex to post it here (firebase, routing, vuex) but basically that wrapper component should look similar to this
<template>
<component :is="CurrentComponent" />
</template>
<script>
import App from './App';
import WaitingAnimation from './WaitingAnimation';
export default {
data() {
return {
Uid: null,
}
},
computed: {
CurrentComponent() {
return this.Uid == null ? WaitingAnimation : App;
}
}
beforeMount() {
//While Firebase is initializing `Firebase.auth().currentUser` will be null
let currentUser = Firebase.auth().currentUser;
//Check currentUser in case you previously initialize Firebase somewhere else
if (currentUser) {
//if currentUser is ready we just finish here
this.Uid = currentUser.uid;
} else {
// if currentUser isn't ready we need to listen for changes
// onAuthStateChanged takes a functions as callback and also return a function
// to stop listening for changes
let authListenerUnsuscribe = Firebase.auth().onAuthStateChanged(user => {
//onAuthStateChanged can pass null when logout
if (user) {
this.Uid = user.uid;
authListenerUnsuscribe(); //Stop listening for changes
}
});
}
}
}
</script>
Related
Currently I'm building an app that on user signup navigates the user to an email verification page. This page then watches the firebase user object inside of a Pinia store waiting for the emailVerified user property to update before directing them to a new page.
When I update the user object manually using vue devtools I can observe my console.log. When I receive the email verification email and use the link provided by firebase my watcher does not react to the user update. I can refresh the pinia store using my vue devtools and I see emailVerified inside my firebase user object has been updated to true but my watcher was never hit.
Any ideas on why I am losing reactivity when going through the email flow?
testStore.js
export const useTestStore = defineStore('test', () => {
const auth = getAuth()
const {user} = useAuth(auth)
return {
user: user,
}
})
emailVerification.js
<script setup>
const { user } = storeToRefs(testStore)
watch(user, () => {
console.log('Direct user to new page')
}, { deep:true })
</script>
For some reason when replacing my watcher with setInterval it seems to works... although this is not the ideal solution
setInterval(function () {
if(user){
if(user.value?.emailVerified) {
console.log('Direct user to new page');
}
}
}, 5000);
Not sure if anyone has any experience with getstream and react native.
I followed there tutorial to implement getstreams SDK into my existing app and its working great but I'm stuck on tokens. I've successfully set up firebase so when a new user signs up I can see there UID and information over on both firebase auth and getstream but I'm hung up on my frontend getting the user to sign in on the chat with there token. I set up firebase with there extension but still having issues. Works great with dev.tokens but just can't get past this part. Is there any examples out there or better documentation for this? Thank you!
Only documentation I can find.. not specific to react native
https://getstream.io/chat/docs/react/tokens_and_authentication/
This is currently how I initialize my user.. the user token is hard coded in my chat config file.
// useChatClient.js
import { useEffect, useState } from 'react';
import { StreamChat } from 'stream-chat';
import { chatApiKey, chatUserId, chatUserName, chatUserToken } from './chatConfig';
const user = {
id: chatUserId,
name: chatUserName,
};
const chatClient = StreamChat.getInstance(chatApiKey);
export const useChatClient = () => {
const [clientIsReady, setClientIsReady] = useState(false);
useEffect(() => {
const setupClient = async () => {
try {
chatClient.connectUser(user, chatUserToken);
setClientIsReady(true);
// connectUser is an async function. So you can choose to await for it or not depending on your use case (e.g. to show custom loading indicator)
// But in case you need the chat to load from offline storage first then you should render chat components
// immediately after calling `connectUser()`.
// BUT ITS NECESSARY TO CALL connectUser FIRST IN ANY CASE.
} catch (error) {
if (error instanceof Error) {
console.error(`An error occurred while connecting the user: ${error.message}`);
}
}
};
// If the chat client has a value in the field `userID`, a user is already connected
// and we can skip trying to connect the user again.
if (!chatClient.userID) {
setupClient();
}
}, []);
return {
clientIsReady,
};
};
The next step is to request the token from the Firebase cloud function (ext-auth-chat-getStreamUserToken), and then initialise the current user with that token.
There is a guide and video showing how to do this using the Stream Chat Flutter SDK:
https://getstream.io/chat/docs/sdk/flutter/guides/token_generation_with_firebase/
https://youtu.be/Dt_taxX98sg
in a VueJS / QuasarJS application Im using firebase-js-sdk [1] together with firebaseui-web [2] to handle authentication.
After successful auth with any of the configured providers (e.g. password, google, apple, etc) I want to check which provider the user used. But immediately after successful authentication the user.providerData[] array that should contain the information is empty.
BUT if I reload my app the user.providerData[] array is suddenly populated correctly.
Iยดm checking for user data with something like this
import { getAuth } from "firebase/auth";
const auth = getAuth();
const user = auth.currentUser;
if (user) {
console.log(user.providerData)
}
After that the user object is fully populated (incl auth tokens, etc) but the user.providerData[] array is empty. Only after a page reload (CTRL-R) does the array get populated.
I searched both projects issues pages and documentation and didnt find anything that could explain this.
Im thankful for ANY idea where to look next!
EDIT
As suggested by #aside Im using onAuthStateChanged to check for updates of the user state.
onAuthStateChanged(
fbAuth,
(user) => {
if (user) {
console.log("onAuthStateChanged: user found");
console.log("onAuthStateChanged: user.providerData", user.providerData);
console.log("onAuthStateChanged: user", user);
} else {
console.log("onAuthStateChanged: no user found");
}
},
function (error) {
console.log("onAuthStateChanged:", error);
}
);
But even if I wait minutes after authentication is completed, still the user.providerData array is only populated after a page reload.
Here is a full demo: https://codesandbox.io/s/github/perelin/firebase-auth-providerdata-test
Thanks in advance :)
Im using
"firebase": "9.6.1",
"firebaseui": "6.0.0",
[1] https://github.com/firebase/firebase-js-sdk
[2] https://github.com/firebase/firebaseui-web
Your app should call getAuth().currentUser.reload() to refresh the local user data after login.
This could be done either in beforeRouteEnter() nav guard of the LoggedIn view:
// LoggedIn.vue
import { getAuth, signOut } from "firebase/auth";
export default {
async beforeRouteEnter(to, from, next) {
await getAuth().currentUser?.reload() ๐
next()
},
}
demo 1
Or in the onAuthStateChanged callback:
// main.js
onAuthStateChanged(
fbAuth,
async (user) => {
await user?.reload() ๐
},
)
demo 2
Your code is only running once instead of running every time the auth state is updated.
If you want to listen to any changes to the auth state, use a callback along with onAuthStateChanged as described here.
https://firebase.google.com/docs/auth/web/manage-users#get_the_currently_signed-in_user
import { getAuth, onAuthStateChanged } from "firebase/auth";
const auth = getAuth();
onAuthStateChanged(auth, (user) => {
if (user) {
// Check used provider here
const providerData = user.providerData;
// ...
} else {
// User is signed out
// ...
}
});
The reason checking/requesting the user object right after authentication does not work is that it might take firebase a second to update the providerData array. signInWithX might therefore return before the property is updated.
I'm a bit confused over the firebase function onAuthStateChanged().
componentDidMount() {
fbAuth.onAuthStateChanged(function(user) {
if (user) { //THIS TRIGGERS BOTH AT LOGIN AND REGISTRATION
console.log("LOGGED IN");
} else {
//TRIGGERS WHEN LOGGING OUT; NOT WHEN FAILING TO LOGIN!
console.log("LOGGED OUT");
}
});
}
I thought that the if(user) block triggered when the user has logged in, but the console.log is also triggered when a new account is created. How do I make a conditional that only triggers at login (and not by creating a new account?)
onAuthStateChanged(nextOrObserver, error, completed) returns function()
returns a listener function
Therefore you need to listen it when the component is mounted and unlisten when the component is unmounted
You need to make a separate Component if you want to listen specifically for Login
Login.js // Or whatever your login component is
componentDidMount() {
// Bind the variable to the instance of the class.
this.authFirebaseListener = firebase.auth().onAuthStateChanged((user) => {
this.setState({
loading: false, // For the loader maybe
user, // User Details
isAuth: true
});
});
}
componentWillUnmount() {
this.authFirebaseListener && this.authFirebaseListener() // Unlisten it by calling it as a function
}
i'm stuck on this issue working on a ionic2 project with "firestore" service from firebase.
I have an osservable to get some data from firestore in a template using the async pipe.
Rule on this EndPoint give read and write access only to logged user.
When i sign-out i put a redirect to login page.
..and now come the issue..
when i land in the login page, after a few second, jump out the IonicErrorHandler notifying that i have insufficient permission.
so;
how i can tell to firestore osservable;
"hey dude, stop it, i call u later if someone log-in again"
(ill try an unsubscribe() befour the signOut but not work, and also
it does not from persistence)
Recapping:
when i logOut
this.afAuth.auth.signOut();
the error:
core.es5.js:1020 ERROR Error: Missing or insufficient permissions.
at new FirestoreError (error.js:164)
at JsonProtoSerializer.fromRpcStatus (serializer.js:126)
at JsonProtoSerializer.fromWatchChange (serializer.js:517)
at PersistentListenStream.onMessage (persistent_stream.js:334)
at persistent_stream.js:270
at persistent_stream.js:247
at async_queue.js:81
at t.invoke (polyfills.js:3)
at Object.onInvoke (core.es5.js:3890)
at t.invoke (polyfills.js:3)
(to be precise, i recive it 3 times. Exactly the number or of documents in the collection)
Service where i call the firestore endpoint:
export interface Attivita {
id: string;
committente: string;
durata: number;
nome: string;
progetto: string;
userId: string;
}
#Injectable()
export class FirebaseService {
attivitaCollectionRef: AngularFirestoreCollection<Attivita>;
attivita$: Observable<Attivita[]>;
constructor(private afs: AngularFirestore,
public afAuth: AngularFireAuth ) {
}
setOsservableAttivita(uId){
this.attivitaCollectionRef = this.afs.collection('attivita', ref => {
return ref.where("userId", "==", uId)
});
this.attivita$ = this.attivitaCollectionRef.snapshotChanges().map(actions => {
return actions.map(action => {
console.log(action)
const data = action.payload.doc.data() as Attivita;
const id = action.payload.doc.id;
return { id, ...data };
});
});
}
}
tks in advance to all help me to understand it
:)
I'd recommend watching the authState from Firebase and only taking from snapshotChanges while you're authenticated. The switchMap operator allows you to switch between observables based on conditions such as whether or not the user is authenticated. Here is an example of a possible solution.
// Assuming rxjs 5.5.0 with lettable operators
import { map } from 'rxjs/operators/map';
import { switchMap } from 'rxjs/operators/switchMap';
import { empty } from 'rxjs/observable/empty';
import { create } from 'rxjs/observable/create';
const actions$ = this.attivitaCollectionRef.snapshotChanges()
.map(actions => {
return actions.map(action => {
console.log(action)
const data = action.payload.doc.data() as Attivita;
const id = action.payload.doc.id;
return { id, ...data };
});
});
}),
this.attivita$ = create(
// Listen for changes in the authState
subscriber => this.afAuth.onAuthStateChanged(subscriber))
)
.pipe(
// Determine if the user is logged in
map(user => !!user),
// switchMap will unsubscribe from the previous observable
// so when isLoggedIn switches to false actions$ will be unsubscribed from
switchMap(isLoggedIn => isLoggedIn ? actions$ : empty()),
);
calling this after logout solved it for me:
afStore.firestore.disableNetwork();
Yes,
Normally this is solved by doing an unsubscribe of your subscription in the ngOnDestroy() of the component you are navigating away from.
That's the way I do it.
So for you, that would be:
ngOnDestroy() {
this.attivita$.unsubscribe();
}
However, it is very difficult to pinpoint which you should unsubscribe as the error does not give any indication on that.
I have added a question to the devs of angularFire on your issue:
https://github.com/angular/angularfire2/issues/1459.
It would be nice to have helped so that the exception points you in the right direction, for example, the path which was not unsubscribed or the last path segment.
Also, there are alternative methods for doing this listed in this post:
http://brianflove.com/2016/12/11/anguar-2-unsubscribe-observables/
Hope that helps.