Logging in no longer causes page refresh.

This patch updates the LastLocation service to store data from the
UI-Router state, rather than the url, which allows us to navigate
to a state directly using UI-Router.

It also updates the SearchParamProvider to grab its data in the config
phase, before the UI-Router is initialized. This permits us to
forcefully remove any query parameters passed to our application in
the URL - as happens during the OAuth exchange - before the router
is aware of them, and thus circumvents an infinite digest loop.

The end result is that StoryBoard no longer has to reload itself
after a user is successfully authorized.

Note: The URL class used is not supported by PhantomJS. As such,
PhantomJS has been removed from our karma.conf.

Change-Id: Iae113544ad35708f63c5636189f0c88990eab203
This commit is contained in:
Michael Krotscheck 2016-01-21 06:37:37 -08:00
parent 4b9dbd1d99
commit a3abee9058
5 changed files with 73 additions and 63 deletions

View File

@ -29,7 +29,6 @@ module.exports = function (config) {
'karma-coverage',
'karma-jasmine',
'karma-html-reporter',
'karma-phantomjs-launcher',
'karma-chrome-launcher',
'karma-firefox-launcher'
],
@ -52,7 +51,7 @@ module.exports = function (config) {
colors: false,
browsers: [ 'PhantomJS', 'Firefox' ],
browsers: [ 'Firefox' ],
preprocessors: {
'./dist/js/storyboard.js': ['coverage']

View File

@ -22,8 +22,7 @@
*/
angular.module('sb.auth').controller('AuthAuthorizeController',
function ($stateParams, $state, $log, OpenId, $window, LastLocation,
localStorageService) {
function ($stateParams, $state, $log, OpenId) {
'use strict';
// First, check for the edge case where the API returns an error code
@ -36,9 +35,6 @@ angular.module('sb.auth').controller('AuthAuthorizeController',
return;
}
// Store the last path...
localStorageService.set('lastPath', LastLocation.get());
// We're not an error, let's fire the authorization.
OpenId.authorize();
});

View File

@ -36,21 +36,6 @@ angular.module('sb.auth').controller('AuthTokenController',
return;
}
// Validate any previously stored redirect path
function buildNextPath() {
// First, do we have a stored last location?
var location = LastLocation.get();
// Sanity check on the location, we don't want to bounce straight
// back into auth.
if (location.indexOf('/auth') > -1) {
location = '/';
}
return location;
}
// Looks like there's no error, so let's see if we can resolve a token.
// TODO: Finish implementing.
OpenId.token($searchParams)
@ -58,9 +43,7 @@ angular.module('sb.auth').controller('AuthTokenController',
function (token) {
Session.updateSession(token)
.then(function () {
var nextPath = buildNextPath();
$window.location.href =
UrlUtil.buildApplicationUrl(nextPath);
LastLocation.go('sb.page.about', {});
});
},
function (error) {

View File

@ -18,18 +18,46 @@
* Utility injector, injects the query parameters from the NON-hashbang URL as
* $searchParams.
*/
angular.module('sb.util').factory('$searchParams',
function ($window, UrlUtil) {
angular.module('sb.util').provider('$searchParams',
function ($windowProvider) {
'use strict';
var params = {};
var search = $window.location.search;
if (!!search) {
var pageParams = {};
this.extractSearchParameters = function () {
var window = $windowProvider.$get();
var search = window.location.search;
if (search.charAt(0) === '?') {
search = search.substr(1);
}
var queryComponents = search.split('&');
for (var i = 0; i < queryComponents.length; i++) {
var parts = queryComponents[i].split('=');
var key = decodeURIComponent(parts[0]) || null;
var value = decodeURIComponent(parts[1]) || null;
return UrlUtil.deserializeParameters(search);
if (!!key && !!value) {
pageParams[key] = value;
}
}
};
this.$get = function () {
return angular.copy(pageParams);
};
})
.config(function ($searchParamsProvider, $windowProvider) {
'use strict';
// Make sure we save the search parameters so they can be used later.
$searchParamsProvider.extractSearchParameters();
// Overwrite the URL's current state.
var window = $windowProvider.$get();
var url = new URL(window.location.toString());
url.search = '';
if (window.location.toString() !== url.toString()) {
window.history.replaceState({},
window.document.title,
url.toString());
}
return params;
});

View File

@ -17,49 +17,53 @@
/**
* A service that keeps track of the last page we visited.
*/
angular.module('sb.util')
.factory('LastLocation',
function ($rootScope, localStorageService, $location) {
angular.module('sb.util').factory('LastLocation',
function ($rootScope, localStorageService, $state) {
'use strict';
/**
* The last detected length of the history
* onStateChange handler. Stores the next destination state, and its
* parameters, so we can keep revisit the history after bouncing out
* for authentication.
*
* @param event The state change event.
* @param toState The destination state.
* @param toParams The parameters for that destination state.
*/
// When the location changes, store the new one. Since the $location
// object changes too quickly, we instead extract the hash manually.
function onLocationChange() {
var path = $location.path();
if (!!path && path.indexOf('/auth') === -1) {
localStorageService.set('lastLocation', path);
function onStateChange(event, toState, toParams) {
if (toState.name.indexOf('sb.auth') === -1) {
var data = {
'name': toState.name,
'params': toParams
};
localStorageService.set('lastLocation',
angular.toJson(data));
}
}
}
// Add the listener to the application, remove it when the scope is
// destroyed.
$rootScope.$on('$destroy',
$rootScope.$on('$stateChangeStart', onStateChange)
);
// The published API.
return {
/**
* Get the recorded history path at the provided index.
* Navigate to the last recorded state.
*
* @param defaultStateName A fallback state.
* @param defaultStateParams Default state parameters.
*/
get: function () {
return localStorageService.get('lastLocation');
},
/**
* Initialize this service.
*/
initialize: function () {
// Register (and disconnect) our listener.
$rootScope.$on('$destroy',
$rootScope.$on('$locationChangeStart', onLocationChange)
);
go: function (defaultStateName, defaultStateParams) {
var last = localStorageService.get('lastLocation');
if (!last) {
$state.go(defaultStateName, defaultStateParams);
} else {
last = angular.fromJson(last);
$state.go(last.name, last.params);
}
}
};
})
.run(function (LastLocation) {
'use strict';
// Initialize this service.
LastLocation.initialize();
});