Refresh expired credentials
Show error toast with a link to sign-in child window. Close child window on successful login and refresh credentials without reloading page. Listen to window focus (using Page Visibility API) to refresh credentials immediately after window/tab focus. Feature: Issue 4097 Change-Id: If5c798b6ac3b4cb97f2ac537c9f5b4b58cf58f9a
This commit is contained in:
committed by
Andrew Bonventre
parent
28bb25d510
commit
a48ddd7aae
@@ -14,16 +14,17 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
var HIDE_ALERT_TIMEOUT_MS = 5000;
|
||||
var CHECK_SIGN_IN_INTERVAL_MS = 60000;
|
||||
var SIGN_IN_WIDTH_PX = 690;
|
||||
var SIGN_IN_HEIGHT_PX = 500;
|
||||
|
||||
Polymer({
|
||||
is: 'gr-error-manager',
|
||||
|
||||
properties: {
|
||||
_alertElement: Element,
|
||||
_hideAlertHandle: Number,
|
||||
_hideAlertTimeout: {
|
||||
type: Number,
|
||||
value: 5000,
|
||||
},
|
||||
},
|
||||
|
||||
attached: function() {
|
||||
@@ -67,7 +68,7 @@
|
||||
|
||||
this._clearHideAlertHandle();
|
||||
this._hideAlertHandle =
|
||||
this.async(this._hideAlert.bind(this), this._hideAlertTimeout);
|
||||
this.async(this._hideAlert, HIDE_ALERT_TIMEOUT_MS);
|
||||
var el = this._createToastAlert();
|
||||
el.show(text);
|
||||
this._alertElement = el;
|
||||
@@ -88,12 +89,17 @@
|
||||
},
|
||||
|
||||
_showAuthErrorAlert: function() {
|
||||
// TODO(viktard): close alert if it's not for auth error.
|
||||
if (this._alertElement) { return; }
|
||||
|
||||
var el = this._createToastAlert();
|
||||
el.addEventListener('action', this._refreshPage.bind(this));
|
||||
el.show('Auth error', 'Refresh page');
|
||||
this._alertElement = el;
|
||||
this._alertElement = this._createToastAlert();
|
||||
this._alertElement.show('Auth error', 'Refresh credentials.');
|
||||
this.listen(this._alertElement, 'action', '_createLoginPopup');
|
||||
|
||||
if (typeof document.hidden !== undefined) {
|
||||
this.listen(document, 'visibilitychange', '_handleVisibilityChange');
|
||||
}
|
||||
this._requestCheckLoggedIn();
|
||||
},
|
||||
|
||||
_createToastAlert: function() {
|
||||
@@ -102,8 +108,44 @@
|
||||
return el;
|
||||
},
|
||||
|
||||
_refreshPage: function() {
|
||||
window.location.reload();
|
||||
_handleVisibilityChange: function() {
|
||||
if (!document.hidden) {
|
||||
this.flushDebouncer('checkLoggedIn');
|
||||
}
|
||||
},
|
||||
|
||||
_requestCheckLoggedIn: function() {
|
||||
this.debounce(
|
||||
'checkLoggedIn', this._checkSignedIn, CHECK_SIGN_IN_INTERVAL_MS);
|
||||
},
|
||||
|
||||
_checkSignedIn: function() {
|
||||
this.$.restAPI.refreshCredentials().then(function(isLoggedIn) {
|
||||
if (isLoggedIn) {
|
||||
this._handleCredentialRefresh();
|
||||
} else {
|
||||
this._requestCheckLoggedIn();
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
_createLoginPopup: function(e) {
|
||||
var left = window.screenLeft + (window.outerWidth - SIGN_IN_WIDTH_PX) / 2;
|
||||
var top = window.screenTop + (window.outerHeight - SIGN_IN_HEIGHT_PX) / 2;
|
||||
var options = [
|
||||
'width=' + SIGN_IN_WIDTH_PX,
|
||||
'height=' + SIGN_IN_HEIGHT_PX,
|
||||
'left=' + left,
|
||||
'top=' + top,
|
||||
];
|
||||
window.open('/login/%3FcloseAfterLogin', '_blank', options.join(','));
|
||||
},
|
||||
|
||||
_handleCredentialRefresh: function() {
|
||||
this.unlisten(document, 'visibilitychange', '_handleVisibilityChange');
|
||||
this.unlisten(this._alertElement, 'action', '_createLoginPopup');
|
||||
this._hideAlert();
|
||||
this._showAlert('Credentials refreshed.');
|
||||
},
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -33,27 +33,32 @@ limitations under the License.
|
||||
<script>
|
||||
suite('gr-error-manager tests', function() {
|
||||
var element;
|
||||
var sandbox;
|
||||
|
||||
setup(function() {
|
||||
sandbox = sinon.sandbox.create();
|
||||
stub('gr-rest-api-interface', {
|
||||
getLoggedIn: function() { return Promise.resolve(true); },
|
||||
});
|
||||
element = fixture('basic');
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
test('show auth error', function(done) {
|
||||
var showAuthErrorStub = sinon.stub(element, '_showAuthErrorAlert');
|
||||
var showAuthErrorStub = sandbox.stub(element, '_showAuthErrorAlert');
|
||||
element.fire('server-error', {response: {status: 403}});
|
||||
flush(function() {
|
||||
assert.isTrue(showAuthErrorStub.calledOnce);
|
||||
showAuthErrorStub.restore();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('show normal server error', function(done) {
|
||||
var showAlertStub = sinon.stub(element, '_showAlert');
|
||||
var textSpy = sinon.spy(function() { return Promise.resolve('ZOMG'); });
|
||||
var showAlertStub = sandbox.stub(element, '_showAlert');
|
||||
var textSpy = sandbox.spy(function() { return Promise.resolve('ZOMG'); });
|
||||
element.fire('server-error', {response: {status: 500, text: textSpy}});
|
||||
|
||||
assert.isTrue(textSpy.called);
|
||||
@@ -61,14 +66,13 @@ limitations under the License.
|
||||
assert.isTrue(showAlertStub.calledOnce);
|
||||
assert.isTrue(showAlertStub.lastCall.calledWithExactly(
|
||||
'Server error: ZOMG'));
|
||||
showAlertStub.restore();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('show network error', function(done) {
|
||||
var consoleErrorStub = sinon.stub(console, 'error');
|
||||
var showAlertStub = sinon.stub(element, '_showAlert');
|
||||
var consoleErrorStub = sandbox.stub(console, 'error');
|
||||
var showAlertStub = sandbox.stub(element, '_showAlert');
|
||||
element.fire('network-error', {error: new Error('ZOMG')});
|
||||
flush(function() {
|
||||
assert.isTrue(showAlertStub.calledOnce);
|
||||
@@ -76,10 +80,45 @@ limitations under the License.
|
||||
'Server unavailable'));
|
||||
assert.isTrue(consoleErrorStub.calledOnce);
|
||||
assert.isTrue(consoleErrorStub.lastCall.calledWithExactly('ZOMG'));
|
||||
showAlertStub.restore();
|
||||
consoleErrorStub.restore();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('show auth refresh toast', function(done) {
|
||||
var refreshStub = sandbox.stub(element.$.restAPI, 'refreshCredentials',
|
||||
function() { return Promise.resolve(true); });
|
||||
var toastSpy = sandbox.spy(element, '_createToastAlert');
|
||||
var windowOpen = sandbox.stub(window, 'open');
|
||||
element.fire('server-error', {response: {status: 403}});
|
||||
flush(function() {
|
||||
assert.isTrue(toastSpy.called);
|
||||
var toast = toastSpy.lastCall.returnValue;
|
||||
assert.isOk(toast);
|
||||
assert.include(
|
||||
Polymer.dom(toast.root).textContent, 'Auth error');
|
||||
assert.include(
|
||||
Polymer.dom(toast.root).textContent, 'Refresh credentials.');
|
||||
|
||||
assert.isFalse(windowOpen.called);
|
||||
toast.fire('action');
|
||||
assert.isTrue(windowOpen.called);
|
||||
|
||||
var hideToastSpy = sandbox.spy(toast, 'hide');
|
||||
|
||||
assert.isFalse(refreshStub.called);
|
||||
element.flushDebouncer('checkLoggedIn');
|
||||
flush(function() {
|
||||
assert.isTrue(refreshStub.called);
|
||||
assert.isTrue(hideToastSpy.called);
|
||||
|
||||
assert.notStrictEqual(toastSpy.lastCall.returnValue, toast);
|
||||
toast = toastSpy.lastCall.returnValue;
|
||||
assert.isOk(toast);
|
||||
assert.include(
|
||||
Polymer.dom(toast.root).textContent, 'Credentials refreshed');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -40,6 +40,10 @@
|
||||
|
||||
// Routes.
|
||||
page('/', loadUser, function(data) {
|
||||
if (data.querystring.match(/^closeAfterLogin/)) {
|
||||
// Close child window on redirect after login.
|
||||
window.close();
|
||||
}
|
||||
// For backward compatibility with GWT links.
|
||||
if (data.hash) {
|
||||
page.redirect(data.hash);
|
||||
|
||||
Reference in New Issue
Block a user