The following code is meant to check the role of the user.
The middleware runs everytime the site is reloaded are a new route is taken.
// Some nuxt middleware
import * as firebase from 'firebase/app'
import 'firebase/auth'
export default function ({ app, store, route, redirect }) {
app.router.beforeEach((to, from, next) => {
// For some reason, this does not load every time.
firebase.auth().onAuthStateChanged((userAuth) => {
if (userAuth) {
console.log(userAuth)
firebase
.auth()
.currentUser.getIdTokenResult()
.then(function ({ claims }) {
// some auth stuff
})
})
}
For some reason, if the site is reloaded this user auth function always returns null. This leads to that the rest of the functions fail due to the unknown user data / user roles.
firebase.auth().onAuthStateChanged((userAuth) => {...})
So my question is, why does the upper function return null when the site is reloaded?
ps. Everything works normal if a new route is taken, it only fails when site is reloaded.
beforeEach is a guard triggered when you navigate from a page to another page thanks to vue router, aka using <nuxt-link> or $router.push.
On the initial page load, there is no navigation because you're rendering the content generated by the server, not the client directly.
Definition of a middleware from Nuxt's documentation
Middleware lets you define custom functions that can be run before rendering either a page or a group of pages (layout).
Notice, before rendering. This means that a middleware will be run as your beforeEach and on initial render.
Hence, you can totally strip the router guard part and simply let the middleware as this
export default function ({ app, store, route, redirect }) {
firebase.auth().onAuthStateChanged((userAuth) => {
...
this.store
.collection(collectioName)
.onSnapshot((data) => {
});
We can listen to a document with the onSnapshot() method. Each time the contents change, another call updates the document snapshot.
I am looking for hook/rxjs operator that we can use in between, when the data is about to change/emitted and data received by stream.
It will be helpful to
show loading spinner when we get new data or
disable form or table
when we received new update
Something like
this.store
.collection(collectioName)
.onSnapshot()
.pipe(
aboutToInitiate(() => { // start spinner },
dataReceived(() => { // stop spinner, stream received }
).subscribe(() => {
// Or we can stop spinner here, but where exactly we hook the logic to start spinner
);
Note: When creating/updating ( triggered manually ) we can start
spinner and stop on success.
But when the stream return an update that is triggered by server or real time update,
when we are subscribed at that time we need to show spinner or indicator
that we are about to receive some data and data updated/received.
So in short a hook in between subscription that automatically start spinner when about to receive data and stop automatically when data receives.
We can use tap operator but that will fire after data received not when it's initiating the process of getting update.
The onSnapshot() is listening to data changes in realtime. That means you can have a spinner only on initialisation. Every change after that happens immediately your device receives it from the backend or changed from your device.
You could start a Spinner before you initialize the onSnapshot()
//START Spinner
const unsub= this.store
.collection(collectioName)
.onSnapshot((data) => {
//STOP Spinner
});
//Stop listener
unsub()
With the unsub you can stop listening to realtime changes.
One thing you should consider is if you have offline capabilities enabled. In that case you could listen to both changes: data written do device cache and data written to backend.
You can do that by enabling the metatadat changeds to your listener like here:
db.collection("cities").doc("SF")
.onSnapshot({
// Listen for document metadata changes
includeMetadataChanges: true
}, (doc) => {
// ...
});
You can detect then if the data is writen only to the device or to the server:
db.collection("cities").doc("SF")
.onSnapshot((doc) => {
var source = doc.metadata.hasPendingWrites ? "Local" : "Server";
console.log(source, " data: ", doc.data());
});
You can find more about it here.
You could us that metada to determin when the data is written localy (then start the spinner) and when it's written to the server (stop the spinner)
EDIT - this question is still unanswered. There was an idea to listen to onIdTokenChanged, however the token is refreshed once every hour, which is not practical solution for me. I posted follow up question here if people can give me a hand that would be grant, because I am sitting on this problem since one week.
I am writing a simple react native app, and I want to show my main page only after user has verified their email. As far as I understand, there is no listener which I can use to listen to event where the user has been verified their email. I am using firebase.auth().onAuthStateChanged((user) => {....}) but the listener onAuthStateChanged has been called after user is logged in or registered in, not after a user has verified their email.
Few places suggested to use firebase.auth().user.reload() so that it will reload the current user and it will pick up the verification status from the database. However, I dont think it is a solution because I dont know when should I reload the current user, i.e. I dont know when the verification link has been clicked. So possible solution to this problem would be:
Send a confirmation 6 digit code to the user, and wait for the user to type it in the app; after the user types it, if the code is the same, I refresh the user. However I dont know how to send custom verification emails with firebase. I checked the documentation, but it is not helpful for me. If someone can point me to example written in react native, or write a small working example with custom email which I can send to the user (again in react native) that would be grant! EDIT - this doesn't seem like possible solution, since Firebase doesn't let you customize the emails
Is it possible solution for me to override onAuthStateChanged listener? S.t. it will listen for changes if the user's email has been verified or not? If that's a good idea can someone point me to the current onAuthStateChanged implementation in react-native, so I can see it as an "inspiration" when overriding? Or if someone has done something similar before, can you show me an example?
I've read several suggestions to use a deep link and to intersept the event when the link has been clicked, but I am not sure how to do this, or even if this is a proper solution to the problem.
Some people suggested to use firebase.auth().user.reload() when the app has been closed and reopened again. This comes from the assumption that when a user has been sent the link, in order for them to click on the link, they need to close the app, and reopen it again. I think this is pretty strong assumption, considering the fact, that they might verify their email via laptop and never close the app, so I dont think this is a good solution either.
Apparently this seems like a well known problem, yet there are not many good solutions. I think best possible solution would be to send 6 digit verification code to the user and after that code has been confirmed, I would reload the current user, pick up the emailVerified field, it will be set to true and then I will show the main screen. However, can someone help me with how do I send custom email in react native and firebase?
If anyone has any other suggestions, please let me know!
You can simply do this by passing a continue url in the actionCodeSettings as below:
const res = await firebase.auth().createUserWithEmailAndPassword(
email,
password
);
await res.user.sendEmailVerification({
url: "https://yoursite.com/continue-url"
});
Is it possible solution for me to override onAuthStateChanged listener? S.t. it will listen for changes if the user's email has been verified or not?
The onAuthStateChanged is called when the user's authentication state changes, so when they go from not being signed in to being signed in or vice versa. The email verification flag being set is not a change in authentication state, so the callback is not called in that case.
You can listen for onIdTokenChanged instead, which fires every time the ID token changes. Since the ID token includes the flag whether the user's email is verified, a callback on onIdTokenChanged will also be called when that changes.
I used #1man solution, just i make sure to delete the interval and unsubscribe from the onAuthStateChanged event:
const onAuthStateChangedUnsubscribe =
firebase.auth().onAuthStateChanged(async (user) => {
if (user) {
// -> Alert Email Verification
await user.sendEmailVerification()
const onIdTokenChangedUnsubscribe = firebase.auth().onIdTokenChanged((user) => {
const unsubscribeSetInterval = setTimeout(() => {
firebase.auth().currentUser.reload();
firebase.auth().currentUser.getIdToken(/* forceRefresh */ true)
}, 10000);
if (user && user.emailVerified) {
clearInterval(unsubscribeSetInterval) //delete interval
onAuthStateChangedUnsubscribe() //unsubscribe onAuthStateChanged
// -> Go to your screnn
return onIdTokenChangedUnsubscribe() //unsubscribe onIdTokenChanged
}
})
}
})
So, on my project I made a combination of sendEmailVerification() and reload().
Try it:
await firebase
.auth()
.currentUser.sendEmailVerification(actionCodeSettings)
.then(() => {
//useState used on my loading (user can cancel this loading and exit this task
setTextContent('Waiting for verification. Check your email!\nYou can close this verification and came back later');
const unsubscribeOnUserChanged = firebase
.auth()
.onUserChanged(response => {
const unsubscribeSetInterval = setInterval(() => {//this works as a next in for-like
firebase.auth().currentUser.reload();
}, 30000);
if (response.emailVerified) {
clearInterval(unsubscribeSetInterval); //stop setInterval
setLoading(false); //close loading describes above
navigation.goBack(); //return to parent (in my case to profile)
return unsubscribeOnUserChanged(); //unsubscribe onUserChanged
}
});
})
.catch(error => {
setLoading(false);
setError(true);
errorHandle(error);
});
#3 is a common workflow - Firebase sends the link which, when clicked, opens your app. Your app reads the deep link and handles the payload (email verified). I don't know what language you're using, but you mentioned that you don't know how to do this and it's probably something you'll want to explore.
Your concern in #4 (someone opening the link on a laptop) is only an issue if you allow it to be one. I don't know what language you're using, but when you call the verify email function, you have to pass a url to Firebase which it will use in the email it sends. So your users will be taken wherever you send them. If you send them to a web app or something because you want them to open it on a laptop, then I think your best bet in app would be to have your website (or wherever you're sending them) also write something to a Firestore or RTDB document and have your app listening to that doc for updates.
If the link you pass to Firebase is a deep link to your app, it won't work on their laptop. And in this case, you go back to #3 - read the deep link in your app and handle it early. Also, it's incumbent on you to explain to users how this works, so I'd have my send link confirmation screen explain that they should click the link on the current device.
An alternative would be to have your send link function in-app start a background timer that polls the auth record every few seconds/minutes (whatever your use case), and cancel it when the record is updated or the link expires. I don't love this because email links are valid for 3 days - that's an awful long time to be polling every few seconds in app.
I wanted to do the same thing on the web. I tried the previous three answers and searched a lot but was not able to find the answer. I ended up combining #Frank van Puffelen and #Hermanyo H's solutions into one and it worked for me:
const onAuthStateChangedUnsubscribe = firebase.auth().onAuthStateChanged(async (user) => {
if (user) {
setEmailVerified("Sent");
await user.sendEmailVerification();
const onIdTokenChangedUnsubscribe = firebase.auth().onIdTokenChanged((user) => {
if (user && user.emailVerified) {
setEmailVerified("Verified");
return onIdTokenChangedUnsubscribe(); //unsubscribe
}
setTimeout(() => {
firebase.auth().currentUser.reload();
firebase.auth().currentUser.getIdToken(/* forceRefresh */ true);
}, 10000);
});
}
});
I wrote my own events instead of using onAuthStateChange.
//Write this where you wrote onAuthStateChange event
import auth from '#react-native-firebase/auth';
import {DeviceEventEmitter} from 'react-native';
useEffect(()=>{
let loginListener = DeviceEventEmitter.addListener('#verified_login', params=>{
setUserDetails(auth()._user);
});
return loginListener;
}, []);
Then you can emit this event when you want to allow the user to log in. There's a lot of room for customization here.
await auth().signInWithEmailAndPassword(email, password);
if(auth()._user.emailVerified)
DeviceEventEmitter.emit('#verified_login');
else{
auth()._user.sendEmailVerification()
.then(()=>{
console.log('A verification link has been sent to your email. Please verify to proceed.');
let emailVerificationEventListener = setInterval(async ()=>{
auth().currentUser.reload();
if (auth().currentUser.emailVerified) {
clearInterval(emailVerificationEventListener);
DeviceEventEmitter.emit('#verified_login');
}
}, 1000);
})
.catch(error=>{
console.log(error);
});
}
The api seems to have changed, this worked for me.
auth.idTokenResult.subscribe((result) => {
console.log('onIdTokenChanged');
console.log(result);
})
This issue can be fixed smoothly using firebase dynamic links
when a user requests to authenticate their emails we send a dynamic link with the request:
auth().currentUser.sendEmailVerification({
url: "https://oursite.com/verified-email",
});
when the user clicks on the link in the email he will be redirected to the dynamic link we included above
then we listen to the link and handle it on the client:
dynamicLinks().onLink((link) => {
if (link.url.includes("verified-email")) {
auth().currentUser.reload();
}};
Did you consider the documentation on the Firebase documentation pages?
https://firebase.google.com/docs/auth/web/email-link-auth
Sample code on that page:
import { getAuth, isSignInWithEmailLink, signInWithEmailLink } from "firebase/auth";
// Confirm the link is a sign-in with email link.
const auth = getAuth();
if (isSignInWithEmailLink(auth, window.location.href)) {
// Additional state parameters can also be passed via URL.
// This can be used to continue the user's intended action before triggering
// the sign-in operation.
// Get the email if available. This should be available if the user completes
// the flow on the same device where they started it.
let email = window.localStorage.getItem('emailForSignIn');
if (!email) {
// User opened the link on a different device. To prevent session fixation
// attacks, ask the user to provide the associated email again. For example:
email = window.prompt('Please provide your email for confirmation');
}
// The client SDK will parse the code from the link for you.
signInWithEmailLink(auth, email, window.location.href)
.then((result) => {
// Clear email from storage.
window.localStorage.removeItem('emailForSignIn');
// You can access the new user via result.user
// Additional user info profile not available via:
// result.additionalUserInfo.profile == null
// You can check if the user is new or existing:
// result.additionalUserInfo.isNewUser
})
.catch((error) => {
// Some error occurred, you can inspect the code: error.code
// Common errors could be invalid email and invalid or expired OTPs.
});
}
tl;dr: Within a Redux middleware function, is it okay to dispatch a new action after calling next to finish updating the store?
I'm building a HackerNews reader using Flutter and built-flutter-redux, based off of Brian Egan's TodoMVC example. It uses HN's Firebase-backed API to pull data:
https://github.com/HackerNews/API
My actions look like this right now:
ActionDispatcher<Null> fetchHackerNewsTopStories;
ActionDispatcher<List<int>> fetchHackerNewsTopStoriesSuccess;
ActionDispatcher<Null> fetchHackerNewsTopStoriesFailure;
ActionDispatcher<Null> fetchNextHackerNewsItem;
ActionDispatcher<HackerNewsItem> fetchHackerNewsItemSuccess;
ActionDispatcher<Null> fetchHackerNewsItemFailure;
There's a piece of middleware that listens for the fetchHackerNewsTopStories action and kicks off a call to the API:
MiddlewareHandler<AppState, AppStateBuilder, AppActions, Null>
createFetchHackerNewsTopStories(HackerNewsRepository service) {
return (MiddlewareApi<AppState, AppStateBuilder, AppActions> api,
ActionHandler next, Action<Null> action) {
service.fetchHackerNewsTopStories().then((ids) {
return api.actions.fetchHackerNewsTopStoriesSuccess(ids);
}).catchError(api.actions.fetchHackerNewsTopStoriesFailure);
next(action);
};
}
When it returns, I update my app's state with the list of IDs.
At some point I need to dispatch another action, fetchNextHackerNewsItem. There's another middleware function that will listen for that action and request the details for the the first story. When those details arrive, it'll request the next story, and so on until everything's updated.
What I'd like to know is whether I can do this:
// Invoked when REST call for the list of top story IDs completes.
MiddlewareHandler<AppState, AppStateBuilder, AppActions, List<int>>
createFetchHackerNewsTopStoriesSuccess() {
return (MiddlewareApi<AppState, AppStateBuilder, AppActions> api,
ActionHandler next, Action<List<int>> action) {
next(action);
api.actions.fetchNextHackerNewsItem(); // Is this cool?
};
}
// Initiates a request for a single story's details.
MiddlewareHandler<AppState, AppStateBuilder, AppActions, Null>
createFetchNextHackerNewsItem(HackerNewsRepository service) {
return (MiddlewareApi<AppState, AppStateBuilder, AppActions> api,
ActionHandler next, Action<Null> action) {
int nextId = api.state.topStoryIds[api.state.loadedUpToIndex];
service.fetchHackerNewsItem(nextId).then((item) {
return api.actions.fetchHackerNewsItemSuccess(item);
}).catchError(api.actions.fetchHackerNewsTopStoriesFailure);
next(action);
};
}
Because createFetchNextHackerNewsItem relies on the app's state (api.state.topStoryIds[api.state.loadedUpToIndex]), I'd like for it to run after the store is updated by the next(action) call.
Is it cool to dispatch new actions in Redux middleware after calling next, or is that some kind of anti-pattern? If it is an anti-pattern, what's the best way to implement this flow?
Yes, it's fine - a middleware can do literally anything it wants when an action is dispatched. That includes modifying / logging / delaying/ swapping / ignoring the original action, as well as dispatching additional actions.
As I understand when a request to an event emitter on the server arrives, that request is never closed and you only need to res.write() every time you would like to send a message. However is there a way to be notified when the client that performed this request has left? Is there a property on the request object?
suppose I have the following route
app.get('/event',function(req,res){
//set response headers
//how do I check if req object is still active to send a message and perform other actions?
})
The basic sequence of events should be similar in other frameworks, but this example is Grails 3.3.
First set up endpoints to subscribe, and to close the connection.
def index() {
// handler for GET /api/subscribe
rx.stream { Observer observer ->
// This is the Grails event bus. background tasks,
// services and other controllers can post these
// events, CLIENT_HANGUP, SEND_MSG, which are
// just string constants.
eventBus.subscribe(CLIENT_HANGUP) {String msg ->
// Code to handle when the grails event bus
// posts CLIENT_HANGUP
// Do any side effects here, like update your counter
// Close the SSE connection
observer.onCompleted()
return
}
eventBus.subscribe(SEND_MSG) {String msg ->
// Send a Server Sent Event
observer.onNext(rx.respond(msg))
}
}
}
def disconnecting()
{
// handler for GET /api/disconnect
// Post the CLIENT_HANGUP event to the Grails event bus
notify(CLIENT_HANGUP, 'disconnect')
}
Now in the client, you need to arrange to GET /api/disconnect whenever your use-case requires it. Assuming you want to notice when someone navigates away from your page, you could register a function on window.onbeforeunload. This example is using Vue.js and Axios.
window.onbeforeunload = function (e) {
e.preventDefault()
Vue.$http({
method: 'get',
url: 'http://localhost:8080/api/disconnect'
})
.then((response) => { console.log(response) })
.catch(({error}) => { console.log(error) })
}
In the case of Servlet stacks like Grails, I found that I needed to do this even if I had no housekeeping of my own to do when the browser went away. Without it, page reloads were causing IOExceptions on the back end.