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

View File

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

View File

@ -36,21 +36,6 @@ angular.module('sb.auth').controller('AuthTokenController',
return; 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. // Looks like there's no error, so let's see if we can resolve a token.
// TODO: Finish implementing. // TODO: Finish implementing.
OpenId.token($searchParams) OpenId.token($searchParams)
@ -58,9 +43,7 @@ angular.module('sb.auth').controller('AuthTokenController',
function (token) { function (token) {
Session.updateSession(token) Session.updateSession(token)
.then(function () { .then(function () {
var nextPath = buildNextPath(); LastLocation.go('sb.page.about', {});
$window.location.href =
UrlUtil.buildApplicationUrl(nextPath);
}); });
}, },
function (error) { function (error) {

View File

@ -18,18 +18,46 @@
* Utility injector, injects the query parameters from the NON-hashbang URL as * Utility injector, injects the query parameters from the NON-hashbang URL as
* $searchParams. * $searchParams.
*/ */
angular.module('sb.util').factory('$searchParams', angular.module('sb.util').provider('$searchParams',
function ($window, UrlUtil) { function ($windowProvider) {
'use strict'; 'use strict';
var params = {}; var pageParams = {};
var search = $window.location.search;
if (!!search) { this.extractSearchParameters = function () {
var window = $windowProvider.$get();
var search = window.location.search;
if (search.charAt(0) === '?') { if (search.charAt(0) === '?') {
search = search.substr(1); 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. * A service that keeps track of the last page we visited.
*/ */
angular.module('sb.util') angular.module('sb.util').factory('LastLocation',
.factory('LastLocation', function ($rootScope, localStorageService, $state) {
function ($rootScope, localStorageService, $location) {
'use strict'; '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.
*/ */
function onStateChange(event, toState, toParams) {
// When the location changes, store the new one. Since the $location if (toState.name.indexOf('sb.auth') === -1) {
// object changes too quickly, we instead extract the hash manually. var data = {
function onLocationChange() { 'name': toState.name,
var path = $location.path(); 'params': toParams
if (!!path && path.indexOf('/auth') === -1) { };
localStorageService.set('lastLocation', path); 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. // The published API.
return { 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 () { go: function (defaultStateName, defaultStateParams) {
return localStorageService.get('lastLocation'); var last = localStorageService.get('lastLocation');
}, if (!last) {
$state.go(defaultStateName, defaultStateParams);
/** } else {
* Initialize this service. last = angular.fromJson(last);
*/ $state.go(last.name, last.params);
initialize: function () { }
// Register (and disconnect) our listener.
$rootScope.$on('$destroy',
$rootScope.$on('$locationChangeStart', onLocationChange)
);
} }
}; };
})
.run(function (LastLocation) {
'use strict';
// Initialize this service.
LastLocation.initialize();
}); });