Apparent race condition getting firebase.User from controller in Firebase 3.x - angularjs

I'm using Firebase 3.x and the auth state change event to assign the current user in my service -
.service('FireService', function ($q, $http,$state) {
var accessToken;
var user;
firebase.auth().onAuthStateChanged(function (usr) {
if (usr) {
// User signed in!
user = usr;
} else {
user = null;
$state.go('login');
}
});
return {
currentUser: function () {
return user;
}
}
But I need the current user to a reference to my child objects from the database in the controller
var getLog = function () {
var logRange = currentDate.format('MMMYYYY').toUpperCase();
var userId = FireService.currentUser().uid;
var ref = LogService.logs(userid,logRange);
// download the data into a local object
ref.once('value').then(
function (result) {
logObj = result;
})
};
When I set breakpoints, I see that the currentUser() is always empty because the state change hasn't fired yet. I've also tried to assign my userid directly from firebase.User but this is also not yet initialized by the time my controllers are ready. How do I get the currently logged in user so that it's available before I start routing around in my app?

I would recommend using the observer directly within the getLog method, so it waits until the current user is set:
var getLog = function () {
var unsubscribe = firebase.auth().onAuthStateChanged(function(user) {
if (user) {
var logRange = currentDate.format('MMMYYYY').toUpperCase();
var userId = user.uid;
var ref = LogService.logs(userid,logRange);
// download the data into a local object
ref.once('value').then(
function (result) {
logObj = result;
});
}
unsubscribe(); // So this observer is only triggered once.
});
};
As the ref.once('value') resolves asynchronously anyway, it will just add a bit of latency to the method but won't change its behavior.
Using this "one off" observer pattern (an observer that calls its unsubscribe function the first time it is called) allows you to make sure Firebase Auth is fully initialized before executing the code. If Firebase Auth is already initialized by the time you create the observer, then its resolution will be triggered immediately. Remember that observers are always called asynchronously.
If you end up using this pattern often, you could add a method on FireService:
...
ready: function() {
return $q(function(resolve, reject) {
var unsubscribe = firebase.auth().onAuthStateChanged(function(user) {
unsubscribe();
resolve(user);
});
});
}
...
And use it within your getLog method:
var getLog = function () {
FireService.ready().then(function(user) {
if (!user) { return; }
var logRange = currentDate.format('MMMYYYY').toUpperCase();
var userId = user.uid;
var ref = LogService.logs(userid,logRange);
// download the data into a local object
ref.once('value').then(
function (result) {
logObj = result;
});
});
};

Related

How to use cookies outside controller?

I've setup cookies inside a Login Controller. Whenever I'm trying to get the values of cookies outside of controller it's throwing an error. please check this out what I'm missing.
Controller After i get success response I'm setting cookies
app.controller('AngularLoginController', ['$scope','$http','$cookies','$rootScope', function($scope, $http,
$cookies,$rootScope) {
$scope.loginForm = function() {
$http.post("login.php", {
'email' :$scope.inputData.email,
'password':$scope.inputData.password
}).success(function(data) {
console.log(data);
if ( data != 'wrong') {
var loggedIn = $cookies.get('loggedIn');
// Putting cookies
$cookies['myCookieArray']= {'loggedIn':true,'username':data};
getmycookiesback = $cookies['myCookieArray'];
window.location.href = '#/userlist';
$rootScope.display = true;
$rootScope.username = getmycookiesback.username;
}
else {
$scope.errorMsg = "Invalid Email and Password";
}
})
}
}]);
After I login it calls AngularLoginController and set Cookies values after success Response.
Outside Controller if I'm comparing value of cookies it's throwing error getmycookiesback is not defined
var onlyLoggedIn = function ($location,$q,$cookies,$rootScope) {
var deferred = $q.defer();
var url = $location.absUrl();
if (getmycookiesback.loggedIn === "undefined") { // Error on This line
deferred.reject();
window.location.href = '#/login';
}
else{
deferred.resolve();
$rootScope.display = true;
$rootScope.username = getmycookiesback.username;
window.location.href = url;
alert(getmycookiesback.username);
//$cookies.remove('loggedIn');
return true;
}
return deferred.promise;
};
Why it's undefined if i have defined it in Controller.
Is there another way i can get value of cookies outside controller ?
Of course it is undefined; it appears to be a local variable in that function scope.
You may want to take a look at Angular Services for things you want to access globally - you may want to implement a function on a service to do this, or store that information locally within the service and provide a function to access it - take a look here.

Can't retrieve anonymous authenticated user's info on first login

Logic: users select a few items on the homepage, then click a 'confirm' button which starts a Firebase $signInAnonymously() auth flow. Their selection is stored under their users/{uid} branch in the database tree and they are redirected to a checkout page that retrieves their selection and asks for more information to proceed.
Issue: when the user lands on the checkout page for the first time their auth state cannot be retrieved (so their selection doesn't appear). However when they refresh the page, everything works as expected for all subsequent attempts (their user info is stored and now retrievable)
Code:
User auth and selection setter/getter factory userService
var auth = $firebaseAuth();
var usersRef = $firebaseRef.users; // custom ref in app config
// Authenticate anonymously to create user session
function startSession() {
return auth.$signInAnonymously()
.then(function(user) {
return user;
})
.catch(function(error) {
console.log(error);
});
}
// Check authentication state before everything loads
function checkAuthState() {
return $q(function(resolve) {
var unsubscribe = auth.$onAuthStateChanged(function(user) {
if (user) {
unsubscribe();
resolve(user);
}
else {
console.log('User unidentified');
}
});
});
}
// Save user's menu selection into selection node of firebase
function saveSelection(items, user) {
var selectionRef = usersRef.child(user.uid).child('selection');
for (var i = 0, item; !!(item = items[i]); i++) {
var id = item.id;
if (item.selected) {
selectionRef.child(id).update(item);
}
else if (typeof(selectionRef.child(id)) !== 'undefined') {
selectionRef.child(id).remove();
}
}
}
// Get user's selection from firebase
function getSelection(user) {
var selectionRef = usersRef.child(user.uid).child('selection');
return $q(function(resolve) {
var selection = $firebaseArray(selectionRef);
resolve(selection);
});
}
Menu controller:
var menu = this;
menu.saveMenu = saveMenu;
// Save menu selection and create anonymous user on firebase
function saveMenu() {
var items = menu.items;
return userService.startSession()
.then(function(user) {
return userService.saveSelection(items, user);
});
}
Checkout controller:
// Get selected menu items from user data
function getCheckoutItems() {
return userService.checkAuthState()
.then(function(user) {
return userService.getSelection(user);
})
.then(function(selection) {
checkout.items = selection;
return checkout.items;
})
.catch(function(error) {
console.log(error);
});
}
I've looked through dozens of posts on SO before asking this. Here are a couple of the similar ones I've found:
Apparent race condition getting firebase.User from controller in Firebase 3.x
Handle asynchronous authentication in Firebase on page reload to get list that needs user's uid
I've also looked through the reference on GitHub to set it up:
https://github.com/firebase/angularfire/blob/master/docs/reference.md#firebaseauth
NB: I'm using the new version of Firebase with Angular 1.5.8:
"firebase": "^3.4.1",
"angularfire": "^2.0.2"
UPDATE
Got it. I had to add a resolve to my /checkout route to wait for authentication before loading elements on the page... Figured it out thanks to this answer by David East from the Firebase team.
resolve: {
// controller will not be loaded until $waitForSignIn resolves
"firebaseUser": function($firebaseAuthService) {
return $firebaseAuthService.$waitForSignIn();
}
}

React/React Native: Execution order in a Component

I am not able to figure out the sequence of code execution in react native. The following code logs test, test 2 and then test 1. I want to change value of submittedURI.
constructor(props) {
super(props);
this.state = {
enterURI: ''
};
}
onUriTextChanged(event) {
this.setState({ enterURI: event.nativeEvent.text });
}
onSubmitPress() {
var submittedURI = this.state.enterURI;
console.log("test "+submittedURI);
var url = encodeURIComponent(submittedURI), data = null;
var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.urlmeta.org/?url=" + url);
xhr.send(data);
xhr.addEventListener("readystatechange", function () {
if (this.readyState === this.DONE) {
// console.log(this.responseText);
var responseJSON = JSON.parse(this.responseText);
submittedURI = responseJSON.meta.image;
console.log("test 1 "+submittedURI);
}
});
this.props.onURISubmit(submittedURI);
console.log("test 2 "+submittedURI);
}
The function in the xhr.addEventListner("readystatechange", function() { ... }) is a callback and get's called after the request is DONE. Therefore console.log("test 2 "+submittedURI); is called before the request is completed.
See the docs here (https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/onreadystatechange)
If you want to change submittedURI you are going to have to call this.props.onURISubmit(submittedURI); function within the readystatechange callback. Make sure the callback has the proper context to access this.props.

AngularJS: Access connected user info from controller

I'm struggling with AngularJS and access to connected user info here. My goal is to save the info of the connected user in a Service, so that is available throughout the app. So this is how I do it:
The login function is exposed through a Login service, and its code is the following:
(function(){
var $loginService = angular.module("RockMyTask.LoginService", [
"satellizer",
"RockMyTask.ApiService"
]);
/**
* Login service
*
* function login(identifier, password, after)
* Logs the given user. Global data about the user are registered in the root scope.
* #param identifier The user's identifier (either email or password)
* #param password The user's password
* #param success (optional) A callback to execute when the user is successfully logged in. This callback is passed
* the response object.
* #param failure (optional) A callback to execute when the log in fails. This callback is passed the response
* object.
*
* function restore()
* If the user was already logged in, restore the user data in the rootScope.
*
* function connectedUser()
* Return the data of the currently connected user. If the user is not connected an empty object is returned.
* If the user data hasn't be fetched yet, then an empty object is returned and this object will be filled with
* the user data as soon as it is received.
*/
$loginService.factory("Login", ["$auth", "$rootScope", "User",
function($auth, $rootScope, User) {
var obj = {};
var connectedUser = {};
obj.logout = function() {
$auth.logout();
//Authorization.clear();
connectedUser = null;
};
obj.login = function(identifier, password, success, failure) {
var credentials = {
user_identifier: identifier,
password: password
};
$auth.login(credentials).then(function(auth_response) {
// fetch user data (with both spectator and rockstar flags set to true)
User.me(true, true).then(function(response) {
connectedUser = response.data;
//TODO add check for DEBUG_MODE
console.log("Received connected user data and stored it in connectedUser var");
}, function() {
connectedUser = {};
});
// execute provided success callback
if (_.isFunction(success)) {
success(auth_response);
}
}, function(response) {
connectedUser = null;
// execute provided failure callback
if (_.isFunction(failure)) {
failure(response);
}
});
};
obj.restore = function() {
if ($auth.isAuthenticated()) {
User.me(true, true).then(function(response) {
connectedUser = response.data;
}, function() {
connectedUser = null;
});
} else {
connectedUser = null;
}
};
obj.getConnectedUser = function(){
if($auth.isAuthenticated && !connectedUser){
User.me(true, true).then(function(response) {
connectedUser = response.data;
}, function() {
connectedUser = null;
});
}
return connectedUser;
};
obj.updateUserInfo = function(user){
connectedUser = user;
};
obj.isConnectedUserRockstar = function(){
return connectedUser.rocker != null;
};
obj.isConnectedUserSpectator = function(){
return connectedUser.spectator != null;
};
return obj;
}]);
})();
As you may observe in the code, upon successful login, I launch an HTTP request (User.me(true, ture)) which then stores the returned data (representing info about connected user) in an ad hoc variable of the service, connectedUser.
To access the connected user info from any point of the application, I've implemented a function in the Login service called getConnectedUser, whose code is reported here:
obj.getConnectedUser = function(){
if($auth.isAuthenticated && !connectedUser){
User.me(true, true).then(function(response) {
connectedUser = response.data;
}, function() {
connectedUser = null;
});
}
return connectedUser;
};
This function checks if the user is indeed authenticated and if the variable connectedUser has been already correctly populated and if not it triggers again the HTTP function to retrieve the data (otherwise it simply returns the connectedUser var).
Now, my problem is the following. The page I redirect the user to, after successful login, uses a controller, to which I've passed the Login service. The controller tries to retrieve the connected user info but the result is a null pointer. What am I doing wrong??
The code in the controller (just in case even if very simple):
$scope.user = Login.getConnectedUser();
EDIT 1
Looking at the console in the browser I see that the HTTP request has success and the returned data is:
{"id":3,"email":"iCartwright#hotmail.com","name":"Michele","surname":"Imperiali","locale":"it_IT","picture":"https:\/\/storage.rockmytask.it\/users\/profile_image\/3.jpeg","birthday":"1982-06-23","gender":"M","country":"IT","province":"Lombardia","city":"Milano","zip":"20100","street":"190 Grover Expressway","mobile_phone":"(437)924-8282","home_phone":"239-250-3166x783","username":"spectator","validated":true,"timezone":"Europe\/Rome","created_at":"2016-04-01T12:50:53+0000","rocker":null,"spectator":{"description":"I need help with my garden !"}}
As previously stated, when I try to access on the variables of $scope.user in the controller I get a null pointer exception.
I think the issue has to do with the
, function() {
connectedUser = null;
}
Is there a reason you are setting connectedUser to null? If the reason is because you want to set it to null if you get an error from the backend or a bad return then doing :
obj.getConnectedUser = function(){
if($auth.isAuthenticated && !connectedUser){
User.me(true, true).then(function(response) {
connectedUser = response.data;
}, function(error) {
connectedUser = null;
});
}
return connectedUser;
};
This then would only set connectUser to null if you get a bad response, As of right now it looks like the connectedUser is always getting set to null and explaining your null pointer.

MobileFirst: Adapter Authentication calls Submit success repeatedly in Angular App

Environment:
MFPF v7.0
Eclipse: Luna SR.2 (4.4.2)
Windows 7
I face an strange issue. I am using adapter based authentication in one of my Angular based project.
The app authenticates well, but it repeatedly calls the submitSuccess.
I guess, it has something with the way Angular works, either I should use Challenge Handler as a Service or Controller. Because the way MobileFirst detects & handle instances of a/any handler objects. And that cause reference mis-match to dispose off or execute the relevant functions at appropriate time.
Currently I use it as a service.
Below is the challenge handler that I use.
define(['angular'], function(angular){
var loginChallengeHandler = angular.module('webApp.loginChallengeHandler', [])
.service('loginChallengeHandler', function(){
var _this = this;
_this.AuthRealmChallengeHandler = WL.Client.createChallengeHandler("AdapterAuthRealm");
_this.AuthRealmChallengeHandler.isCustomResponse = function(response) {
console.error("AuthRealmChallengeHandler.isCustomResponse:: " , response);
if (!response || !response.responseJSON || response.responseText === null) {
return false;
}
if (typeof(response.responseJSON.authRequired) !== 'undefined' || response.responseJSON.authRequired == true){
return true;
} else {
return false;
}
};
_this.AuthRealmChallengeHandler.handleChallenge = function(response){
console.error("AuthRealmChallengeHandler.handleChallenge:: " , response);
var authRequired = response.responseJSON.authRequired;
if (authRequired == true){
console.error("------Auth Required----- ");
_authenticationFailed(response);
} else if (authRequired == false){
console.error("------Auth PASSED ----- ");
//Now tell WL Authentication that user has been verified successfully so that it finishes the authentication process
_this.AuthRealmChallengeHandler.submitSuccess();
console.error("------ submitSuccess ----- ");
}
};
_this.AuthRealmChallengeHandler.userLogin = function(dataObjRef) {
var loginStatePromise = $q.defer();
_this.AuthRealmChallengeHandler.submitAdapterAuthentication(options,{
onFailure: function (error) {
loginStatePromise.resolve({ state:false , val: "" });
console.log("submitAdapterAuthentication Failed called ", error);
},
onSuccess: function(response) {
console.log("-> submitAdapterAuthentication onSuccess called " , response);
loginStatePromise.resolve({ state: _state , val: _msg });
},
timeout: 30000
});
return loginStatePromise.promise;
};
_this.AuthRealmChallengeHandler.logout = function (){
var userLogoutPromise = $q.defer();
WL.Client.logout("AdapterAuthRealm",{
onSuccess: function(){
console.log("onSuccess");
userLogoutPromise.resolve(true);
},
onFailure: function(){
console.log("onFailure");
userLogoutPromise.resolve(false);
},
timeout: 30000
});
return userLogoutPromise.promise;
};
var _authenticationFailed = function(response){
console.error("_authenticationFailed:: " , response);
//register failure request
_this.AuthRealmChallengeHandler.submitFailure();
};
});
return loginChallengeHandler;
});
I have also tried to bind the handler object with window object, so that it can access the handler's instance methods correctly.
Like:
window.AuthRealmChallengeHandler = WL.Client.createChallengeHandler("AdapterAuthRealm");
window.AuthRealmChallengeHandler.isCustomResponse = function(response) {
.
.
But still same issue.
I solved this issue and here is my solution for anyone facing similar issue in future.
Solution Description (few words)
As per my understanding, the IBM MobileFirst is expecting only one challenge-handler instance (the object that is create via createChallengeHandler function) to exists in the app. So most probably it assumes that the instance would be hooked into the window object.
Now based on this knowledge, we can see that above code is not working even we made instance through service ( i.e. singleton per angular app). Why ? Because, now the handler object becomes accessible via another reference, and this caused issues in resolving the handler references within the WL APIs.
So I just changed a bit of code (hooked it into window) so that WL APIs could reach the correct handler instance and clean-up the requests poll before marking the call successful and dispose off all the cached requests.
One more thing I would suggest.
Create only one handler instance in your client code
Create it as a service or factory - both are singletons in angularjs
Avoid using controllers, because there can be many controller instances within the angular app and it would lead to multiple handler references
And importantly trust IBM MobileFirst :)
Working Challenge Handler as Service
define(['angular'], function(angular){
'use strict';
var loginChallengeHandler = angular.module('webApp.loginChallengeHandler', [])
.service('loginChallengeHandler', function(){
//NOTE:- Below Must be bind with Window Object, otherwise will NOT work as per challenge handlers default behavior
window.AuthRealmChallengeHandler = WL.Client.createChallengeHandler("AdapterAuthRealm");
AuthRealmChallengeHandler.isCustomResponse = function(response) {
if (response && response.responseJSON && typeof (response.responseJSON.authStatus) === "string"){
return true;
} else {
return false;
}
};
AuthRealmChallengeHandler.handleChallenge = function(response){
var authStatus = response.responseJSON.authStatus;
if (authStatus === "failed"){
console.error("------Auth Required----- ");
_authenticationFailed(response);
} else if (authStatus === "passed"){
console.error("------Auth PASSED ----- ");
//do something here like change page etc.
//Now tell WL Authentication that user has been verified successfully so that it finishes the authentication process
AuthRealmChallengeHandler.submitSuccess();
}
};
AuthRealmChallengeHandler.userLogin = function(dataObjRef) {
var loginStatePromise = $q.defer();
AuthRealmChallengeHandler.submitAdapterAuthentication(options,{
onFailure: function (error) {
loginStatePromise.resolve({ state:false , val: "" });
},
onSuccess: function(response) {
loginStatePromise.resolve({ state: _state , val: _msg });
},
timeout: 30000
});
return loginStatePromise.promise;
};
AuthRealmChallengeHandler.logout = function (){
var userLogoutPromise = $q.defer();
WL.Client.logout("AdapterAuthRealm",{
onSuccess: function(){
//$state.go("home.login");
userLogoutPromise.resolve(true);
},
onFailure: function(){
userLogoutPromise.resolve(false);
},
timeout: 30000
});
return userLogoutPromise.promise;
};
var _authenticationFailed = function(response){
//register failure request
AuthRealmChallengeHandler.submitFailure();
};
});//end of service
return loginChallengeHandler;
});
Adapter
function onAuthRequired(headers, errorMessage){
errorMessage = errorMessage ? errorMessage : null;
return {
authStatus: "failed",
errorMessage: errorMessage
};
}
function Login(request){
if(request){
/* IF user credentials are Verified Correctly
* and user is authenticated then create User Identity
* and return success message if it is required by client app.
*/
userIdentity = {
userId: "abc",
displayName: "ABc",
attributes: {}
};
WL.Server.setActiveUser("AdapterAuthRealm", userIdentity);
WL.Logger.error("Auth Successful:");
return {
authStatus: "passed",
submitResponse: "send a Success message in case is required on client-side"
};
}else{
return onAuthRequired(null, "send an error message if required on client side");
}
}
I faced the same issue with adapter based authentication but I was using pure javascript, so no angular. From that I can tell you it's a MobileFirst issue and nothing related to angular.
This might sound contradictory to the documentations but don't call the submitSuccess function, just call your code on successful authentication. It will work fine and authenticate properly.
Also, make sure that you only have the security test set on the specific functions that you use after auth and not on the auth function itself.
Your code seems fine to me but I'm not that good in angular.

Resources