Ionic: promise goes to error even though it's resolved - angularjs

I'm building my first ionic app and I have a problem with one of my promises.
The app talks with a REST api to login and register, the login part works but for the register part every time I fill in the form and submit I get the error message, even if the user is created (I can see him in the database of the api) and I can't figure out why, even though the promise is resolved, I still get the error message.
the controller.js code:
wannaPlay.controller('RegisterCtrl', function($scope, AuthService, $ionicPopup, $state) {
$scope.player = {
firstName: '',
lastName: '',
password: '',
email: ''
};
$scope.signup = function() {
AuthService.register($scope.player).then(function(msg) {
$state.go('outside.login');
var alertPopup = $ionicPopup.alert({
title: 'Enregistrement effectué !',
template: msg
});
}, function(errMsg) {
var alertPopup = $ionicPopup.alert({
title: 'Erreur lors de l\'enregistrement !',
template: errMsg
});
});
};
})
the services.js code:
var register = function(player) {
return $q(function(resolve, reject) {
$http.post(API_ENDPOINT.url + '/playersignup', player).then(function(result) {
if (result.data.succes) {
resolve(result.data.msg);
} else {
reject(result.data.msg);
}
});
});
};

Is the REST api really returning a boolean called 'succes', not 'success'?
Maybe it's just that typo. Else you should consider putting logmessages or breakpoints to see what you recieve from the REST backend like this:
console.log(result);
if (result.data.succes) {
console.log("success");
resolve(result.data.msg);
} else {
console.log("error");
reject(result.data.msg);
}

Related

satellizer then not called after authentication

I'm very new to angular, so my knowledge is based on tutorials and even then I don't succeed.
I need to authenticate using a google account. That works, I get a token where my api calls could be authorized with. But after login the pop up window should dismiss and I should be redirected to the homepage. This doesn't work.
this is my controller
angular.module('MyApp').controller('loginController', ['$scope', '$auth', '$location','loginService', loginController]);
function loginController($scope, $auth, $location, loginService) {
$scope.authenticate = function(provider) {
$auth.authenticate(provider).then(function(data) {
loginService.saveToken(data.data.token);
console.log('You have successfully signed in with ' + provider + '!');
$location.path('http://localhost/#/home');
});
};
};
in app.js I have my configuration. this is not my work but a friend who is an intern as wel as me, he is responsible for a mobile application, where he uses the same function to get his token, and it works.
authProvider.google({
clientId: CLIENT_ID,
redirectUri: 'http://localhost:3000/api/users/signIn'
});
$authProvider.storage = 'localStorage'; // or 'sessionStorage'
$authProvider.loginRedirect = 'http://localhost/#/home';
This is the controller in node where the url is redirected to (google developer console)
router.get('/signIn', function(req, res) {
//console.log(req);
var code = req.query.code;
oauth2Client.getToken(code, function(err, tokens) {
if (!err) {
https.get("https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=" + tokens.access_token, function(response) {
// Continuously update stream with data
var body = '';
response.setEncoding('utf8');
response.on('data', function(d) {
body += d;
});
// Data fetched
response.on('end', function() {
var parsed = JSON.parse(body);
// Check if client_id is from the right app
if (parsed.issued_to == '343234242055-vd082vo0o8r8lmfvp1a973736fd98dht.apps.googleusercontent.com') {
User.getGoogleId(parsed.user_id, function(err, user) {
if (err) {
res.status(500).send({
message: 'not authorized app'
});
}
// No user returned, create one
if (!user) {
// Request user info
oauth2Client.setCredentials(tokens);
plus.people.get({
userId: 'me',
auth: oauth2Client
}, function(err, plusUser) {
if (err) res.status(500).send({
message: 'not authorized app'
});
else {
// Create new user
User.create(plusUser.name.givenName, plusUser.name.familyName, (plusUser.name.givenName + "." + plusUser.name.familyName + "#cozmos.be").toLowerCase(), parsed.user_id, function(err, newUser) {
if (err) res.status(500).send({
message: 'not authorized app'
});
else {
res.statusCode = 200;
return res.send({
response: 'Success',
id: user._id,
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
token: tokens.access_token
});
}
});
}
});
} else {
// Return user
res.statusCode = 200;
return res.send({
response: 'Success',
id: user._id,
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
token: tokens.access_token
});
}
});
}
// if not right app, return unauthorized response
else {
res.status(500).send({
message: 'not authorized app'
});
}
});
});
}
});
});
So I login, I get asked to give permission to the application to use my account info, I get a json response where I can see my name, email and token, and that's it
Even within the company where I work, no one could find an answer. So I came with a solution myself. I don't use satellizer anymore.
.when('/access_token=:access_token', {
template: '',
controller: function($window, $http, $location, $rootScope) {
var hash = $location.path().substr(1);
var splitted = hash.split('&');
var params = {};
for (var i = 0; i < splitted.length; i++) {
var param = splitted[i].split('=');
var key = param[0];
var value = param[1];
params[key] = value;
$rootScope.accesstoken = params;
}
console.log(params.access_token);
var json = {
Token: params.access_token
};
$window.localStorage['token'] = params.access_token;
$http.post('http://localhost:3000/api/users/signIn', json).success(function(data, status) {
console.log(data);
}).error(function(err) {
console.log(err);
});
$location.path("/home");
}
/*controller: 'createNewsFeed',
templateUrl: 'homepage.html'*/
}).
So redirect the page by itself. Because the authentication works on the backend side, I can get a access token, which is the only thing I really need for future use of my rest api. I defined a route where, after receiving the json with the token, my browser is manually redirected to with $window.location. So when that page is loaded (not visible for the user, it goes too fast to notice) I analyse the token, save the token, analyse authentication, when that is successful I manually redirect to the homepage.

$scope var not updating on Parse update

I am building an app using ionic and parse. I am updating a boolean in parse based on a click. Everything works on parse end, I see the user object updated in the console after the function runs, however the scope variable is not updating until user logs out, comes back to the page, and then usually has to even refresh again just to see the $scope.isInstagramLinked updated to its true value.
Controller
var app = angular.module('myApp.controllers.account', []);
app.controller('AccountCtrl', function ($scope, $state, $cordovaOauth, AuthService) {
$scope.isInstagramLinked = AuthService.user.attributes.is_instagram_linked;
$scope.linkInstagram = function() {
$cordovaOauth.instagram('######', [], {})
.then(function(result) {
console.log("Response Object -> " + JSON.stringify(result));
console.log(result.access_token);
// save the access token & get user info
AuthService.setInstagramAccessToken(result.access_token).then(function() {
console.log('Token saved!');
});
}, function(error) {
console.log("Error -> " + error);
});
}
$scope.unlinkInstagram = function() {
AuthService.removeInstagramInfo().then(function() {
console.log('Insta unlinked');
console.log(AuthService.user.attributes);
});
}
});
Service
var app = angular.module('myApp.services.authentication', []);
app.service('AuthService', function ($q, $http, $ionicPopup) {
var self = {
user: Parse.User.current(),
'setInstagramAccessToken': function(token) {
var d = $q.defer();
var user = self.user;
user.set("instagram_access_token", token);
user.save(null, {
success: function(user) {
self.user = Parse.User.current();
d.resolve(self.user);
},
error: function(user, error) {
$ionicPopup.alert({
title: "Save Error",
subTitle: error.message
});
d.reject(error);
}
});
self.setInstagramUserInfo(token);
return d.promise;
},
'setInstagramUserInfo': function(token) {
var d = $q.defer();
var endpoint = 'https://api.instagram.com/v1/users/self?access_token=' + token + '&callback=JSON_CALLBACK';
$http.jsonp(endpoint).then(function(response) {
console.log(response.data.data.username);
console.log(response.data.data.id);
var user = self.user;
user.set('is_instagram_linked', true);
user.set('instagram_username', response.data.data.username);
user.set('instagram_user_id', response.data.data.id);
user.save(null, {
success: function(user) {
self.user = Parse.User.current();
d.resolve(self.user);
},
error: function(user, error) {
$ionicPopup.alert({
title: "Save Error",
subTitle: error.message
});
d.reject(error);
}
});
});
},
'removeInstagramInfo': function() {
var d = $q.defer();
var user = self.user;
user.set('is_instagram_linked', false);
user.set('instagram_access_token', null);
user.set('instagram_username', null);
user.set('instagram_user_id', null);
user.save(null, {
success: function(user) {
self.user = Parse.User.current();
d.resolve(self.user);
},
error: function(user, error) {
$ionicPopup.alert({
title: "Save Error",
subTitle: error.message
});
d.reject(error);
}
});
return d.promise;
}
};
return self;
});
I tried something like this at the end of the function but get an error saying Error: [$rootScope:inprog] $digest already in progress
$scope.$apply(function () {
$scope.isInstagramLinked = false;
});
I'm guessing that you're assuming that the following line
$scope.isInstagramLinked = AuthService.user.attributes.is_instagram_linked;
is going to make '$scope.isInstagramLinked' update anytime 'AuthService.user.attributes.is_instagram_linked' updates. That's not the case, though. Because 'AuthService.user.attributes.is_instagram_linked' references a primitive (boolean) value, it just assigns it - it doesn't maintain any kind of reference to it - that only happens with objects.
You need to manually set $scope.isInstangramLinked = true in the $cordovaOauth.instagram() success/"then" handler.
tl;dr:
$scope.isLinked = false;
someFunction().then(function(){
$scope.isLinked = true; // this is what you're missing
})
.error(function(err){...})
If you don't want to set it manually, you can also use $scope.$watch to watch 'AuthService.user.attributes.is_instagram_linked' for changes, and then update '$scope.isInstagramLinked' when it does.

$scope.alerts undefined when doing unit testing client side with angularjs and jasmine

I am writting unit tests for my controller. I am testing the userSave() function with jasmine and want to check the contents of the $scope.alerts variable which is an array. But $scope.alerts is empty when it gets out of the userSave() function, so I can't check for its content. I dont know why it's empty since $scope.alerts is global. Below is the code for my controller and the test case.
controller
'use strict';
angular.module('myApp')
.controller('RegisterCtrl', function ($scope, $http, $location) {
$scope.message = 'Register Route';
$scope.init = function () {
$scope.inviteCode = $location.hash();
};
$scope.init();
$scope.master = {};
// Array to hold alert messages
$scope.alerts = [];
$scope.closeAlert = function (index) {
$scope.alerts.splice(index, 1);
};
$scope.userSave = function (user) {
/*jshint unused: false */
$http.post('api/users/v1/', {
'invite_code': $scope.inviteCode,
'password': user.password,
'last_name': user.lastName,
'first_name': user.firstName
}).success(function (data, status, headers, config) {
$location.url($location.path());
$location.path('/');
}).error(function (data, status, headers, config) {
if (status === 404) {
$scope.alerts.push({
type: 'danger',
msg: 'Your invitation code has expired or is invalid, your registration will require a new one to be completed'
});
} else if (status === 204) {
$scope.alerts.push({
type: 'danger',
msg: 'This email already exists, please click login and forgot your password to recover your password'
});
} else {
$scope.alerts.push({
type: 'danger',
msg: 'There was an error registering'
});
}
});
};
});
Test case
'use strict';
describe('Controller: RegisterCtrl', function () {
// load the controller's module
beforeEach(module('myApp'));
var RegisterCtrl, scope, httpBackend,http;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope, $httpBackend,$http) {
scope = $rootScope.$new();
httpBackend = $httpBackend;
http = $http;
RegisterCtrl = $controller('RegisterCtrl', {
$scope: scope
});
}));
afterEach(function() {
httpBackend.verifyNoOutstandingExpectation();
httpBackend.verifyNoOutstandingRequest();
});
it('should return a 404 for expired inviteCode', function () {
var userClientPayload = {
lastName: 'pav',
firstName: 'nga',
password: 'test'
};
var userServerPayload = {
invite_code: 'expiredCode',
last_name: 'pav',
first_name: 'nga',
password: 'test'
};
httpBackend.whenPOST('api/users/v1/',userServerPayload).respond(404);
scope.inviteCode = 'expiredCode';
scope.userSave(userClientPayload);
//httpBackend.expectPOST('api/users/v1/',userServerPayload).respond(404);
console.log(scope.alerts);
expect(scope.alerts.type).toBe('danger');
expect(scope.alerts.msg).toBe('Your invitation code has expired or is invalid, your registration will require a new one to be completed');
httpBackend.flush();
});
});
Error
Controller: RegisterCtrl should return a 404 for expired inviteCode FAILED
Expected undefined to be 'danger'.
at /Users/z001hm0/Documents/api_portal/developer-portal/client/test/unit/controllers/register.controller.spec.js:59
Expected undefined to be 'Your invitation code has expired or is invalid, your registration will require a new one to be completed'.
at /Users/z001hm0/Documents/api_portal/developer-portal/client/test/unit/controllers/register.controller.spec.js:60
$scope.alerts is an array, so this line is failing b/c an array doesn't have a type property:
expect(scope.alerts.type).toBe('danger');
Replace the above line with something like this:
expect(scope.alerts.length).toBe(1);
expect(scope.alerts[0].type).toBe('danger');
EDIT
The next problem is that you are making the call to expect() before you do httpBackend.flush(). The call to flush() is what makes the simulated server response happen in your test, and that will trigger the code that populates $scope.alerts.
So if we flip-flopped those lines of code it would look like this:
httpBackend.flush();
expect(scope.alerts.length).toBe(1);
expect(scope.alerts[0].type).toBe('danger');

handle asynchronous behavior firebase in angularjs

I'm trying put the authentication of my firebase in a service. But I stumbled on some problems with program flow. The response from firebase is slower and the code needs to wait for it to complete.
I tried to create a promise, but it doesnt work properly.
Here is my code:
//controller.js
articleControllers.controller('AuthController',
['$scope', '$firebaseObject', 'AuthenticationService',
function($scope, $firebaseObject, AuthenticationService){
$scope.login = function() {
AuthenticationService.login($scope.loginForm)
.then(function(result) {
if(result.error) {
$scope.message= result.error.message;
}
else {
console.log("user");
}
});
};
}
]);
services.js
myApp.factory('AuthenticationService',
function($firebase, $firebaseAuth, $routeParams, FIREBASE_URL) {
var auth = new Firebase(FIREBASE_URL);
var myReturnObject = {
//user login
login: function(user) {
return auth.authWithPassword({
email: user.loginEmail,
password: user.loginPassword
},
function(error, authData) {
console.log("hit after .then");
return {
error: error,
authData: authData
}
});
}
};
return myReturnObject;
});
I already used a promise once in my code for a $http get request. But for firebase it doesnt seem to work. I get the error: Cannot read property 'then' of undefined in controller.js.
Anyone an idea how I can let angular wait for the service?
remember to inject $q.
myApp.factory('AuthenticationService',
function($q, $firebase, $firebaseAuth, $routeParams, FIREBASE_URL) {
var auth = new Firebase(FIREBASE_URL);
var myReturnObject = {
//user login
login: function(user) {
return $q(function(resolve, reject) {
auth.authWithPassword({
email: user.loginEmail,
password: user.loginPassword
}, function authCallback(error, authData) {
if (error) {
return reject(error);
}
resolve(authData);
});
});
}
};
return myReturnObject;
});

Mongoose "Create" method - Is there no callback? (Using passport and angular)

I am using mongoose to create a user object on register. This works fine and any errors are returned as expected.
However, I want to log the user on right after they register (so registering logs you on if there are no errors).
I have the following for the register.
register_controller:
$scope.submitRegister = function() {
AuthenticationService.register(this.details).success(function() {
$log.debug('/POST to /api/register worked');
});
}
services.js:
.service('AuthenticationService', function($http, $timeout, $q, $session, $flash) {
...
this.register = function(details) {
var register = $http.post('/api/register', details);
register.success(function() {
console.log("User added fine");
}).error(function() {
console.log("error!!!");
});
return register;
};
...
users.js:
app.post('/api/register', authentication.register);
passport's authenticate.js:
module.exports = {
...
register: function(req, res){
var User = require('./controllers/api/login_api');
User.create({name: req.body.name, email: req.body.email, password: req.body.password}, function(err){
if (err) {
console.log(err);
return;
}
console.log("User added");
return res.send(200);
});
},
...
The error is reported back fine, no troubles, but I would have thought it would report something else back (like the created object?) which I could use down the line so my register_controller can have in the success function(object) {... login(object);...}.
Is this a limitation in the .create method or am I missing something obvious?
Thank you.
Two things to change in your server code:
passport's authenticate.js:
module.exports = {
...
register: function(req, res){
var User = require('./controllers/api/login_api');
User.create({name: req.body.name, email: req.body.email, password: req.body.password}, function(err, user){
if (err) {
console.log(err);
return;
}
console.log("User added");
return res.send(200, user);
});
},
...
I've added user to your callback Mongoose model.create api - return the created object to the CB
And to change the catch in your client:
.service('AuthenticationService', function($http, $timeout, $q, $session, $flash) {
...
this.register = function(details) {
var register = $http.post('/api/register', details);
register.success(function(user) {
console.log(user);
}).error(function() {
console.log("error!!!");
});
return register;
};
...
Now you can do with the created user object whatever you need

Resources