Situation: I am developing a device application that runs from a single page load and a main module. The application communicates through two API calls. The application requires a current authentication state acquired from an API call to "/api/login" sent via POST. The API call used to save and populate the data of the view renderings is communicated by an API call sent to "/api" via POST. Both calls are made to the same protocol://domain:port.
Problem: In IE9 only, Chrome and FF work as expected, the data returned is not valid for the second API call to "/api". In IE9, the developer tool network panel shows that the call is being made to "/api" with all of the parameters that are suppose to be made. However the response shows a failure response as if I was still making a call to the login api with invalid parameters.
How is this possible even though I am configuring the $http.post to expire immediately on both calls and cache is set to false on both calls? How can I fix it or work around it? Below is the code that I am using.
EDIT: Forgot to mention that I am viewing this from on a Windows 7 ThinkPad.
Code from index.js:
var LocalPortal = angular.module('LocalPortal', ['ngResource'])
.value('lpApi', {
_apiList : [],
_apiUri : '/api',
_functionStack : {},
_host : document.location.hostname,
_port : 8080,
_protocol : document.location.protocol,
_sessionTimeout : 900,
_addToCallStack : function(param) {
this._functionStack = $.extend(this._functionStack, param);
},
_doApiCall : function(dataSegment, command, params) {
var segment = {};
segment[dataSegment] = {"command" : command};
if (params) {
segment[dataSegment]["params"] = params;
}
this._addToCallStack(segment);
$.subscribe('api.received', function(e,data) {
$.publish(command, data[dataSegment]);
});
},
_restoreAuthState : function() {
var _authState = (sessionStorage.getItem('lpApi.authState')) ? JSON.parse(sessionStorage.getItem('lpApi.authState')) : {};
lpApi.authState = _authState;
},
_setAuthTime : function(timeVal) {
lpApi.authState.authTime = timeVal;
lpApi._storeAuthState();
},
_storeAuthState : function() {
sessionStorage.setItem('lpApi.authState', JSON.stringify(lpApi.authState));
},
_url : function() {
return this._protocol + '//'
+ this._host +
(this._port ? ':' + this._port : '')
+ this._apiUri;
},
apiList : function() {
this._doApiCall("apiList", "api.list");
return this;
},
getPayload : function() {
var payload = this._functionStack;
this._functionStack = {};
return payload;
},
getUrl : function(uriExt) {
return this._url() + uriExt;
},
init : function(){
var loc = document.location;
this._apiList = sessionStorage.getItem('apiList');
this._host = loc.hostname;
this._port = loc.port;
this._protocol = loc.protocol;
//store a reference to this object for later callbacks to access
window.lpApi = this;
//restore authentication state if any
lpApi._restoreAuthState();
},
logOff : function() {
lpApi._setAuthTime(0);
sessionStorage.removeItem('lpApi.authState');
}
})
.config(function($routeProvider) {
$routeProvider.
when('/login', {templateUrl: 'partials/login.html', controller: LoginCtrl}).
when('/home', {templateUrl: 'partials/gateway.html', controller: GatewayCtrl}).
when('/gateway', {templateUrl: 'partials/gateway.html', controller: GatewayCtrl}).
when('/assets', {templateUrl: 'partials/assets.html', controller: AssetsCtrl}).
when('/connectivity', {templateUrl: 'partials/connectivity.html', controller: ConnectivityCtrl}).
when('/events', {templateUrl: 'partials/events.html', controller: EventsCtrl}).
otherwise({redirectTo:'/home', templateUrl: 'partials/gateway.html', controller: GatewayCtrl});
for (var that in this) {
if (that.substr(-4) == 'Ctrl') {
if (this[that].prototype.menu) {
for (var index in this[that].prototype.menu.items) {
$routeProvider.
when(this[that].prototype.menu.items[index].href, {
templateUrl: this[that].prototype.menu.items[index].template,
controller: that
});
}
}
if (this[that].prototype.ctrlActions) {
for (var index in this[that].prototype.ctrlActions.items) {
$routeProvider.
when(this[that].prototype.ctrlActions.items[index].href, {
templateUrl: this[that].prototype.ctrlActions.items[index].template,
controller: that
});
}
}
if (this[that].prototype.mainActions) {
for (var index in this[that].prototype.mainActions.items) {
$routeProvider.
when(this[that].prototype.mainActions.items[index].href, {
templateUrl: this[that].prototype.mainActions.items[index].template,
controller: that
});
}
}
if (this[that].prototype.secondaryActions) {
for (var index in this[that].prototype.secondaryActions.items) {
$routeProvider.
when(this[that].prototype.secondaryActions.items[index].href, {
templateUrl: this[that].prototype.secondaryActions.items[index].template,
controller: that
});
}
}
}
}
})
.directive('appTopMenu', function() {
return {
restrict: 'E',
templateUrl: 'partials/top-menu.html'
}
})
.directive('appMenu', function() {
return {
restrict: 'E',
templateUrl: 'partials/menu.html',
link: function(scope,element,attr) {
for (var that in this) {
if (that.substr(-4) == 'Ctrl') {
if (this[that].prototype.menu) {
for (var index in this[that].prototype.menu.items) {
scope.menu.items[scope.menu.items.length] = this[that].prototype.menu.items[index];
}
}
}
}
}
}
})
.run(function(){
});
var IndexCtrl = function($scope, lpApi, $location) {
//Properties
$scope._localPortalContainer = 'container';
$scope._loggedIn = true;
//Functions
$scope.assertAuthentication = function() {
if(!$scope.isAuthenticated()){
lpApi.logOff();
$scope.setRouteReload('/login');
}
};
$scope.getLocalPortalContainer = function() {
return $scope._localPortalContainer;
};
$scope.getLoggedInStatus = function() {
return $scope._loggedIn;
};
$scope.isAuthenticated = function() {
if (sessionStorage) {
if (sessionStorage.getItem('lpApi.authState')) {
lpApi.init();
return ((new Date().getTime() - lpApi.authState.authTime) < 900000);
}
else {
lpApi.init();
return false;
}
}
return false;
};
$scope.setErrorMsg = function(msg) {
$scope.errorMsg = msg;
};
$scope.setLocalPortalContainer = function($container){
$scope._localPortalContainer = $container;
};
$scope.setLoggedInStatus = function(status) {
$scope._loggedIn = status;
};
$scope.setRoute = function($route) {
$location.path($route);
};
$scope.setRouteReload = function($route) {
$location.path($route);
document.location.reload();
};
if (!$scope.isAuthenticated()) {
$scope.setLocalPortalContainer('container-small');
$scope.setLoggedInStatus(false);
$scope.setRoute('/login');
}
};
IndexCtrl.$inject = ['$scope', 'lpApi', '$location'];
Code for login.js:
var LoginCtrl = function($scope, $http, lpApi) {
// Functions
$scope.authenticate = function(username, password, $http){
var data = {auth: {username: username, password: password}};
$http.post(lpApi.getUrl('')+'/login', data, {cache: false, headers: {Expires: -1}})
.error($scope.postError)
.success($scope.setAuthState);
};
$scope.login = function($scope, lpApi) {
if($scope.isAuthenticated() && lpApi.logOff) {
lpApi.logOff();
$scope.setRouteReload('/login');
}
$scope.setLocalPortalContainer('container-small');
$scope.setLoggedInStatus(false);
};
$scope.postError = function(data, status, headers, config){
// output error message
$scope.setErrorMsg(data.errorMessage[0])
};
$scope.setAuthState = function(data, status, headers, config){
lpApi.authState.sessionId = data.sessionId || null;
lpApi.authState.roles = data.roles || null;
lpApi.authState.requirePasswordChange = data.requirePasswordChange || null;
lpApi.authState.success = data.success;
if (data.success) {
lpApi._setAuthTime(new Date().getTime());
$scope.setLocalPortalContainer('container');
$scope.setLoggedInStatus(true);
$scope.setRoute('/home');
}
else {
// output error message
$scope.setErrorMsg(data.errorMessage[0])
}
};
$scope.submit = function(){
$scope.authenticate(this.input_username, this.input_password, $http);
};
$scope.login($scope, lpApi);
};
LoginCtrl.$inject = ['$scope', '$http', 'lpApi'];
Code for gateway.js:
var GatewayCtrl = function($scope, lpApi, $http) {
lpApi.cloudlinkInfo();
lpApi.loggingQuery({limit:"5",page:"0"});
lpApi.modemInfo();
lpApi.osInfo();
lpApi.systemNodeinfo();
lpApi.systemProductinfo();
lpApi.tunnelInfo();
// Properties
$scope.loadData = function(data, status, headers, config) {
console.log('succes');
console.log(JSON.stringify(data)); // {"success":false,"errorMessage":["Invalid authentication."]} (See config url is not "/api/login")
console.log(status);
console.log(headers);
console.log(JSON.stringify(config)); // {"method":"POST","url":"protocol://domain:port/api","data":{"cloudlinkInfo": {"command":"cloudlink.info"},"loggingQuery" "command":"logging.query","params":{"limit":"5","page":"0"}},"modemInfo": "command":"modem.info"},"osInfo":{"command":"os.info"},"systemNodeinfo": "command":"system.nodeinfo"},"systemProductinfo" : {"command":"system.productinfo"},"tunnelInfo":{"command":"tunnel.info"}}}
};
$scope.postError = function(data, status, headers, config) {
console.log('error');
console.log(JSON.stringify(data));
console.log(status);
console.log(headers);
console.log(JSON.stringify(config));
};
$scope.assertAuthentication(); // <== asserts we have a current authState.
var payload = lpApi.getPayload(), apiUrl = lpApi.getUrl(''); // <== payload is an object of api calls and their params, apiUrl is protocol://domain:port/api
$http.post(apiUrl, payload).error($scope.postError).success($scope.loadData);
};
GatewayCtrl.$inject = ['$scope', 'lpApi', '$http'];
The "Invalid Authentication" response was actually a valid response. It turns out the cookie that the device's web service was setting was incomplete according to the expectations of IE9 and IE9 was disregarding it all together. Chrome and FF are so much more forgiving.