react-native-firebase see if user has an internet connection - firebase

I'm trying to upload something to my firestore database, but if the user doesn't have an internet connection it just tries to upload it foreever withour giving me an error.
Is there a way to cancel it when I don't have a connection?

I see two options:
use react-native-netinfo to detect if there's a connection before uploading, something like
NetInfo.fetch().then(({isConnected}) => {
if (isConnected) {
doSomethingWithFirebase();
}
});
Add a timeout to make it fail, like so:
// make it a promise, if it isn't already
const firebaseResult = new Promise(resolve => {
doSomethingWithFirebase()
.then(() => resolve(true))
.catch(() => resolve(false))
})
// resolve after 30s
const timeout = new Promise(resolve => setTimeout(() => resolve(false), 30 * 1000));
const didUpload = await Promise.race([firebasePromise, timeOut]);
I'd personally go with #2, because you can show the user an error (something like "failed to upload. does your connection work?") but it depends what it's for, like if it's analytics data that you don't want them to know about #1 is good for that.
Edit: as OP pointed out, with #2 the action would take place when the connection came back online again, without notice, which may not be desired behavior. You could indicate that there's an open connection somehow, like with an "uploading" icon, and clear it when it finally resolves (with failure or success).

Related

How do I listen to email verification event with firebase authentication and react native?

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.
});
}

Firestore email attachments

Basically what I'm looking for is the ability to send emails with attachments (multiple attachments) to my email. My current approach is to get files and convert them to the dataUri then save email skeleton to firestore. Then I have a cloud function that fires onSave and sends the email to my email using Gmail transporter and after I delete the record from firestore so there is no persistent saving to a database. This approach works but its very limited because I can't send files which sum exceeding 1 MB and that's sucks.
Can anyone point me the best way to get similar behaviour but be able to send larger files? I would like to send up to 5 files with 3mb per file if its possible.
P.S.
I've enabled blaze plan
Im not sure whats wrong here.. Im unable to send an email.
Yeah, right I forgot about HTTPS functions. But I've one problem Im unable to send an email and Im not sure what's wrong.
exports.sendEmaill = functions.https.onRequest((req, res) => {
if (req.method !== 'POST') {
return res.status(403).send('Forbidden!');
}
console.log(req.body);
return cors(req, res, () => {
const mailOptions = parseBody(req.body);
console.log(mailOptions);
// return res.status(200).send(mailOptions)
return mailTransport.sendMail(mailOptions)
.then((info) => res.status(200).send(info))
.catch((error) => res.status(403).send(error));
});
});
With this I can fire the function
// return res.status(200).send(mailOptions)
But as soon as I want to use mailTransport I got
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

Firebase Auth successful, but Database Ref set and update does not happen in electron app version 1.4.2

I'm able to authorize the Firebase app from my existing Electron app using firebase.auth().signInWithCustomToken. The promise for this method resolves and I'm able to obtain the current authorized user with firebase.auth().currentUser.uid.
At this point I must technically be able to write to /users/<currentUser>. However calling the userRef.set() and userRef.update() methods does not update the database reference and fails silently (both the callback and the promise from these methods do not resolve and there is no error thrown).
What is strange is that the exact same code works in a different, newly created Electron app. My code looks like below:
const writeToFirebase = (customToken) => {
syncApp.auth().signInWithCustomToken(customToken).then(user => {
const userId = firebase.auth().currentUser.uid; // this is successfull
const userRef = firebase.database().ref("/users/" + userId);
userRef.set({data: data}, () => { //callback does not trigger });
userRef.update({data: data})
.then(() => {//promise does not resolve})
.catch(err) => {// promise is not rejected either! }
});
}
Any pointers on how to go about debugging this would be helpful.
I discovered the problem. It's unlikely anybody else would have the same issue, but if you do, take a look at the userAgent value in your browserWindow.loadURL in Electron.
Mine was set to an Android mobile device & Firebase was not setting/updating due to this reason. I presume the Firebase server reacts differently when it sees a mobile userAgent and I was using the Firebase JS SDK and not the Android SDK which caused the issue.

Receiving updates from the server across all devices

I'm using Angular 2 to send requets to my Laravel PHP API and show them in my client.
So far, I used the following code to check for new updates from the server and update my data (making an API call in interval of 3 seconds):
ngOnInit()
{
this.dataRefresh = setInterval(() => {
this.dataInit();
}, 3000);
}
dataInit()
{
this.http.get('APICALL')
.map((res:Response) => res.json())
.subscribe(
data => {
this.data = data;
},
err => {
console.error(err);
});
}
Now I'm thinking further. Let's say 50 computers are connected in the same time to my app. Each of them making an API call every 3 seconds, which means the server will have to handle about 16 requests every second.
My question is:
Can I handle this scenario using Angular 2 / Laravel?
If no, what's the current way to do this?
Thanks in advance.
You can use a real time database like firebase.
Instead of your clients asking for updates to your API every second, your API will let know the clients when there is any update.
You can find firebase tutorials here & here.
There are also some packages available like angular2-firebase and angularfire2 to help your Angular2 app connect to your firebase db easily.

Rebus MSMQ appears to be losing messages

I have followed the pub/sub demo with msmq and am loosing messages when the publisher is started before the subscribers. The msmq has already been created.
My Publisher code in one console app
_activator = new BuiltinHandlerActivator();
Configure.With(_activator)
.Transport(t => t.UseMsmq("PaymentsToTake"))
.Subscriptions(s => s.StoreInMemory())
.Start();
/* In the timer code */
MyDateMessage m = new MyDateMessage()
{
NowTime = DateTime.Now,
Counter = _index
};
_activator.Bus.Publish(m).Wait();
_index++;
My Subscriber Code in another console app
_activator = new BuiltinHandlerActivator();
_activator.Register(() => new PrintDateTime());
Configure.With(_activator)
.Transport(t => t.UseMsmq("PaymentsToTake-Receiver1"))
.Routing(r => r.TypeBased().Map<MyDateMessage>("PaymentsToTake"))
.Start();
_activator.Bus.Subscribe<MyDateMessage>().Wait();
Results
When I run the subscriber, I get the message Sending MyDateMessage -> and then when I run the consumer, the first message that comes up is "53 The time is" hence messages 0-52 were lost!
I suspect this is because you are using the in-mem subscription storage, meaning that the publisher does NOT remember who subscribed when it was previously running.
For most (if not all) real-world scenarios, you should choose some kind of persistent storage for subscriptions. It could be a database like SQL Server, or it could even be a local JSON file.
You just need to change the line
.Subscriptions(s => s.StoreInMemory())
into something like e.g.
.Subscriptions(s => s.UseJsonFile(#"subscriptions.json"))
Could you try and see if that fixes your problem? :)

Resources