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.
});
}
I have an ionic app where i use firebase phone authentication which uses recaptcha. It works fine on android but throws error on ios saying recaptcha can only be run in an http environment. I would like to know if there's a way to perform firebase phone auth without using recaptcha.
this.recaptchaVerifier = new firebase.auth.RecaptchaVerifier('recaptcha-container',{
'size': 'invisible'
});
let appVerifier = this.recaptchaVerifier;
this.appService.sendPhoneVerification(phoneNumber,appVerifier)
.then(confirmationResult => {
//do something
})
Ios throws error 'RECAPTCHA can only be run in HTTP/HTTPS environment'
Well this is how I solved my issue "'RECAPTCHA can only be run in HTTP/HTTPS environment'".
Install the Firebase Plugin :plugin link
Add the it to your app.module.ts.
Make a platform check: to check if its iOS.
if (this.plt.is('ios')) {
//ios code here
} else {
//android here
}
Now add the following code (iOS platform) to send a verification code sms to the user to verify the phone number. Inject the plugin into the constructor. Create a variable to assign the data from the promise. Phone number should be country code + number. example '+19999999999'
public signInUser(phoneNum) {
this.firebase.verifyPhoneNumber(phoneNum).then((vdata) => {
this.refConfirm = vdata;
//you can redirect the person to a verification page or show an alert to
input verification code.
});
}
Now create a token to verify and sign in user with credentials using firebase.
public verifyPhoneNumber(phoneNumber) {
let tokenPhone = firebase.auth.PhoneAuthProvider.credential(this.refConfirm,
phoneNumber);
firebase.auth().signInWithCredential(tokenPhone).then((verifiedData) => {
//whatever you want to do here or redirect the user to home page.
});
}
Generate your GoogleService.plist on Firebase & add to your project root directory
You have to add reversed client id instead of normal one.
This is how I solved it.
I have successfully implemented a basic notification feature using react-native-firebase library, everything is working as expected, information is properly received and ready to be used for a purpose I have yet to determine. My code currently look like this for the notification handling part:
componentDidMount() {
/**
* When app on foreground, rewrap received notification and re-send it as notification using channelId
* A workaround because channelId never set by default by FCM API so we need to rewrap to make sure it is
* shown on user's notification tray
*/
this.notificationListener = firebase.notifications().onNotification((notification) => {
//data object must have channelId props as a workaround for foreground notification on Android
console.log('Notif ', notification);
notification.android.setChannelId(notification.data.channelId);
firebase.notifications().displayNotification(notification);
});
//On Notification tapped, be it from foreground or background
this.notificationOpen = firebase.notifications().onNotificationOpened((notificationOpen) => {
//body and title lost if accessed from background, taking info from data object by default
const notification = notificationOpen.notification;
console.log('Open ', notification)
Alert.alert(notification.data.title, notification.data.body);
});
//When notification received when app is closed
this.initialNotification = firebase.notifications().getInitialNotification()
.then((notificationOpen) => {
//body and title lost if accessed this way, taking info from data object where info will persist
if (notificationOpen) {
const notification = notificationOpen.notification;
console.log('Initial ', notification)
Alert.alert(notification.data.title, notification.data.body);
}
});
}
componentWillUnmount() {
this.notificationListener();
this.initialNotification()
this.notificationOpen();
}
The above code let me use any information I sent from firebase console or a php server set up by my colleague from within the above scope (not sure how the server side implementation was done, but it gives me the exact same notification object on my end).
So that's good and all, but the problem is when I set badge on IOS from firebase console, the badge doesn't go away once I opened the notification.
I have been trying to figure out if there's any extra bit I have to add to the above block to programatically decrement the badge counter, but have no luck so far.
So if anyone here can show me how to manage these notification objects properly (especially explaining the nature and lifecycle of these objects -- i.e. which data on which property/method persists or is static within the scope of the notification object) on both Android and IOS, that would be greatly appreciated :)
Turns out a simple firebase.notifications().setBadge(0) on root componentDidMount() clears out the badge count whenever the app is opened.
May need to use firebase.notifications().removeAllDeliveredNotifications() or firebase.notifications().cancelAllNotifications() to remove them from notification tray too.
May be you have to set code for badge while creating a notification
this.notificationListener = firebase.notifications().onNotification((notification) => {
const localNotification = new firebase.notifications.Notification()
.setNotificationId(notification.notificationId)
.setTitle(notification.title)
.setSubtitle(notification.subtitle)
.setBody(notification.body)
.setData(notification.data)
.ios.setBadge(notification.ios.badge);
firebase.notifications()
.displayNotification(localNotification)
.catch(err => console.error(err));
}
Put this line in code .ios.setBadge(notification.ios.badge); while building a notification and try again
I'm trying to use Meteor and this Cordova plugin -https://github.com/don/cordova-plugin-ble-central - added to my project using meteor add cordova in order to connect to a Bluetooth LE device (TI Sensortag). All I want to do to begin with is, when a link is clicked, to connect to the device and show a message.
I have the following code in the events section of my template javascript.
Template.measure.events({'click [data-action=scan-connect-stream]':
function(event, template) {
event.preventDefault();
if (Meteor.isCordova) {
Meteor.startup(function () {
ble.connect('24:09:00:DE:00:42',
function(){
alert('Connect success');
return;
},
function(){
alert('Connect failed');
return;
});
});
}
}
});
My problem is that sometimes the code works and I get a 'Connect success' alert but more often than not it it fails to connect and shows the 'Connect failed' alert. Before I added the return statements in the success and fail callbacks it didn't work at all.
I'm debugging this on an android device (meteor run android-device --verbose) and can see via adb logcat that the BLE Connect event in the Cordova plugin is firing but then doesn't connect. I get the same issue debugging on two different phones and when using a BLE device that isn't a TI Sensortag so I'm guessing this is an problem with the way the plugin is interacting with Meteor (maybe Meteor isn't waiting long enough for a success callback?).
Has anyone used this plugin successfully with Meteor or can anyone provide any clue as to what I'm doing wrong? Should I try wrapping it in a Meteor package or is there any way I can give the plugin more time to respond before the success or fail callbacks fire? Any help would be much appreciated!
For anyone who's having similar issues this is what sorted it for me. I put the ble.connect call into the success callback of the ble.scan function. Not sure why but scanning for a few seconds first does the job.
Template.measure.events({
'click [data-action=scan-connect-stream]': function(event, template) {
event.preventDefault();
if (Meteor.isCordova) {
Meteor.startup(function () {
device_id = '24:09:00:DE:00:42';
ble.scan([], 5,
function(peripherals){
connectDevice(device_id);
},
function(){
alert('No devices found');
}
);
});
}
}
});
var connectDevice = function (device_id) {
ble.connect(device_id,
function(){
alert('Device ' + device_id + ' connnected');
},
function(){
alert('Couldn\'t connect to device ' + device_id);
});
}
If anyone can explain why the ble.connect won't work on its own that'd be great!
EDIT: Looking at the Android code it seems that the plugin is designed in such a way that ble.scan has to be called before calling ble.connect. The ble.scan causes a LinkedHashMap in the Android code to be populated with any discovered devices. Only once the device is listed in the LinkedHashMap can you then connect to it using ble.connect.
I'm currently building an android application using ionic/ngcordova. I'm at the point of implementing push notifications. I've implemented push notifications as a service which is injected at app.run(function(){..}) stage. The registration part works and I receive a callback containing the regid. Also, when the application is in the active state, the event is raised and the notification is received.
The problem I'm having is that when the application goes into the background, the notifications are not received at all. I would expect that a local notification would be raised when the app isn't running or something similar, but absolutely nothing happens, which is weird.
I've trawled the web for the last couple of days looking for a solution but I've been unable to find anything which kind of indicates to me that it should just work.
The following is my notificationService.js inside my app
app.factory('notificationService', ['$cordovaPush', function($cordovaPush){
var dataFactory = {};
//
// When the device is ready and this service has been plumbed in...
document.addEventListener("deviceready", function(){
console.log("initializing push notifications...");
_register();
}, false);
//
// Registers the device for push notifications...
var _register = function(){
var config = {};
if ( device.platform == 'android' || device.platform == 'Android' || device.platform == "amazon-fireos" ){
// TODO: centralise this value as it can change...
config = {
senderID: "448168747432",
ecb: "onNotificationGCM"
};
}else {
// iOS
config = {
"badge":"true",
"sound":"true",
"alert":"true"
};
// Can add the following property to the config object to raise a callback with the information if need be...
// "ecb": "onNotificationRegisterAPN"
}
$cordovaPush.register(config).then(function(result){
//
// Typically returns "ok" for android and devicetoken for iOS
console.log(result);
});
};
window.onNotificationGCM = function(result){
console.log(result);
/*
I get called when the app is in the foreground, but nothing happens when the app is in the background.
*/
};
dataFactory.register = _register;
return dataFactory;
}]);
If it helps, I'm using PushSharp via a .net application in order to deliver the notifications. Any help would be greatly appreciated.
UPDATE: I'm using the following frameworks/libs:
Ionic Framework 1.2.14-beta6
Cordova 4.2.0
PushPlugin
For anyone else who's been pulling their hair out for a couple of days like I have, the solution was really simple...I was missing two properties in my Pushsharp QueueNotification request. So using the example given on the PushSharp github repo here: https://github.com/Redth/PushSharp#sample-code
push.QueueNotification(new GcmNotification().ForDeviceRegistrationId("DEVICE-REGISTRATION-ID-HERE").WithJson("{\"alert\":\"Hello World!\",\"badge\":7,\"sound\":\"sound.caf\"}"));
Needs to be updated to add the missing properties:
push.QueueNotification(new GcmNotification().ForDeviceRegistrationId("DEVICE REGISTRATION ID HERE")
.WithJson(#"{""alert"":""This is the future"",""badge"":7,""sound"":""sound.caf"",""title"":""Status Bar title"",""message"":""Some text you want to display to the user""}"));
Otherwise if your app happens to be developed using Cordova and its not currently in the foreground, nothing, repeat nothing will happen.
Tip my hat to gdelavald with his comment on PushPlugin for pointing me in the right direction here:
https://github.com/phonegap-build/PushPlugin/issues/212