Commit aeb1241f authored by Eduardo Sanz García's avatar Eduardo Sanz García
Browse files

feat (AuthService): callbacks are invoke on change

parent bb91cf52
<a name="1.0.0-beta.6"></a>
# [1.0.0-beta.6](https://gitlab.ebi.ac.uk/tools-glue/ng-ebi-authorization/compare/1.0.0-beta.5...1.0.0-beta.6) (2019-01-24)
### Features
* only calls login and logout callbacks when change of state
* only emit user if the token has changed (similar to distinctUntilChanged)
<a name="1.0.0-beta.5"></a>
# [1.0.0-beta.5](https://gitlab.ebi.ac.uk/tools-glue/ng-ebi-authorization/compare/1.0.0-beta.4...1.0.0-beta.5) (2019-01-23)
### Features
......
......@@ -565,15 +565,40 @@ describe('AuthService', () => {
}));
it('must be able call login events', () => {
let hasExecuted = false;
service.addLogInEventListener(() => hasExecuted = true);
let counter = 0;
service.addLogInEventListener(() => ++counter);
expect(counter).toBe(0);
expect(hasExecuted).toBe(false);
window.dispatchEvent(new MessageEvent('message', {
data: VALID_TOKEN_1
}));
expect(user).toEqual({
uid: 'usr-1',
name: 'Ed Munden Gras',
nickname: '1',
email: 'test@ebi.ac.uk',
token: VALID_TOKEN_1
}, 'user must have correct details');
expect(counter).toBe(1, 'callbacks should execute for first time');
expect(user).not.toBeNull('user must be authenticated at this point');
window.dispatchEvent(new MessageEvent('message', {
data: VALID_TOKEN_2
}));
expect(user).toEqual({
uid: 'usr-2',
name: 'Alice Wonderland',
nickname: '2',
email: 'test@ebi.ac.uk',
token: VALID_TOKEN_2
}, 'user must have correct details');
expect(counter).toBe(1, 'callbacks should have not been executed');
service.logOut();
expect(user).toBeNull('user must not be authenticated at this point');
window.dispatchEvent(new MessageEvent('message', {
data: VALID_TOKEN_1
}));
expect(user).toEqual({
uid: 'usr-1',
name: 'Ed Munden Gras',
......@@ -581,14 +606,13 @@ describe('AuthService', () => {
email: 'test@ebi.ac.uk',
token: VALID_TOKEN_1
}, 'user must have correct details');
expect(hasExecuted).toBe(true);
expect(counter).toBe(2, 'callbacks should be executed again');
});
it('must be able call logout events (via logout method)', () => {
let hasExecuted = false;
expect(hasExecuted).toBe(false);
service.addLogOutEventListener(() => hasExecuted = true);
let counter = 0;
service.addLogOutEventListener(() => ++counter);
expect(counter).toBe(0);
window.dispatchEvent(new MessageEvent('message', {
data: VALID_TOKEN_1
......@@ -602,10 +626,36 @@ describe('AuthService', () => {
email: 'test@ebi.ac.uk',
token: VALID_TOKEN_1
}, 'user must have correct details');
expect(counter).toBe(0);
service.logOut();
expect(counter).toBe(1);
service.logOut();
expect(counter).toBe(1);
window.dispatchEvent(new MessageEvent('message', {
data: VALID_TOKEN_1
}));
expect(user).not.toBeNull('user must be authenticated at this point');
expect(counter).toBe(1);
service.logOut();
expect(counter).toBe(2);
service.logOut();
expect(counter).toBe(2);
});
it('must totally remove the token from localStorage on logOut', () => {
window.dispatchEvent(new MessageEvent('message', {
data: VALID_TOKEN_1
}));
expect(user).not.toBeNull('user must be authenticated at this point');
expect(localStorage.hasOwnProperty(tokenName)).toBe(true);
expect(hasExecuted).toBe(false);
service.logOut();
expect(hasExecuted).toBe(true);
expect(localStorage.hasOwnProperty(tokenName)).toBe(false);
});
......
......@@ -46,12 +46,12 @@ export interface User {
export class AuthService implements OnDestroy {
private _user = new BehaviorSubject < User | null > (null);
private _currentState: string | null = null; // stores the string token or null otherwise (logout)
private _loginCallbacks: Function[] = [];
private _logoutCallbacks: Function[] = [];
private _unlistenLoginMessage: Function;
private _unlistenChangesFromOtherWindows: Function;
private _unlistenEvents: Function[];
private _timeoutID: number | null = null;
......@@ -88,15 +88,17 @@ export class AuthService implements OnDestroy {
}
const renderer = this._rendererFactory.createRenderer(null, null);
this._unlistenLoginMessage = this._listenLoginMessage(renderer);
this._unlistenChangesFromOtherWindows = this._listenChangesFromOtherWindows(renderer);
this._unlistenEvents = [
this._listenLoginMessage(renderer),
this._listenChangesFromOtherWindows(renderer)
];
this._currentState = null;
this._updateUser(); // TODO: experiment with setTimeOut
}
public ngOnDestroy() {
this._unlistenLoginMessage();
this._unlistenChangesFromOtherWindows();
this._unlistenEvents.forEach(fn => fn());
}
public user(): Observable < User | null > {
......@@ -356,7 +358,7 @@ export class AuthService implements OnDestroy {
tap(token => {
this._storageRemover();
this._storageUpdater(token);
this._updateUser(false);
this._updateUser();
// Triggers updating other windows
this._commKeyUpdater();
......@@ -481,21 +483,25 @@ export class AuthService implements OnDestroy {
return event.origin === this._appURL;
}
private _updateUser(invokeLoginCallbacks = true) {
private _updateUser() {
if (this._timeoutID) {
window.clearTimeout(this._timeoutID);
}
if (this._tokenService.isTokenValid()) {
const token = this._tokenService.getToken() as string;
this._user.next({
uid: < string > this._getClaim('sub'),
name: < string > this._getClaim('name'),
nickname: < string > this._getClaim('nickname'),
email: < string > this._getClaim('email'),
token: < string > this._tokenService.getToken()
uid: this._getClaim('sub'),
name: this._getClaim('name'),
nickname: this._getClaim('nickname'),
email: this._getClaim('email'),
token
});
if (invokeLoginCallbacks) {
if (this._currentState === null) {
// this._loginCallbacks is an empty list when first called from the constructor.
// Latter it could be filled as clients of the library call `this.addLogInEventListener(myfunction)`
this._loginCallbacks.forEach(callback => callback());
}
......@@ -504,15 +510,24 @@ export class AuthService implements OnDestroy {
// Coercing dates to numbers with the unary operator '+'
const delay = +expireDate - +new Date();
this._timeoutID = window.setTimeout(() => this.logOut(), delay);
if (this._currentState !== token) {
this._currentState = token;
}
} else {
this._storageRemover(); // Cleanup possible left behind token
this._user.next(null);
this._logoutCallbacks.forEach(callback => callback());
if (this._currentState !== null) {
// this._logoutCallbacks is an empty list when first called from the constructor.
// Latter it could be filled as clients of the library call `this.addLogOutEventListener(myfunction)`
this._logoutCallbacks.forEach(callback => callback());
this._user.next(null);
}
this._currentState = null;
}
}
private _getClaim(claim: string): string | null {
return this._tokenService.getClaim < string, null > (claim, null);
private _getClaim(claim: string): string {
return this._tokenService.getClaim < string, string > (claim, '');
}
private _deprecationWarning(oldMethod: string, newMethod: string) {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment