Skip to content

Commit

Permalink
feat(stark-ui): implementation of a session timeout warning
Browse files Browse the repository at this point in the history
  • Loading branch information
Mallikki committed Nov 23, 2018
1 parent 79ed8a0 commit 578b8a2
Show file tree
Hide file tree
Showing 25 changed files with 612 additions and 40 deletions.
42 changes: 42 additions & 0 deletions docs/TIMEOUT_WARNING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Session Timeout Warning configuration

Stark provides a nice feature when it comes to session expiration handling: in case the user's session is about to end due
to inactivity (the user is idle for some time) and you want to warn him about this, a dialog will be displayed.

This is what the `SessionTimeoutWarningDialogComponent` does and also asks the user if its session should be kept alive.

This warning dialog is displayed by an NGRX Effect that triggers when a `StarkSessionActionTypes.SESSION_TIMEOUT_COUNTDOWN_START` action
is dispatched (by the Session service when the user becomes idle).

To use this feature, you'll have to modify the `app.module.ts` file of your application

## app.module.ts

You'll have to import the StarkSessionUiModule like follow:

```
import {StarkSessionUiModule} from "@nationalbankbelgium/stark-ui";
@NgModule({
imports: [
StarkSessionUiModule.forRoot(),
...
]
})
```

To indicate that you don't wish to display such warning dialog, just set `timeoutWarningDialogDisabled` value to true.
This option is false by default.

```
import {StarkSessionUiModule} from "@nationalbankbelgium/stark-ui";
@NgModule({
imports: [
StarkSessionUiModule.forRoot({
timeoutWarningDialogDisabled: true
}),
...
]
})
```
18 changes: 9 additions & 9 deletions packages/stark-build/config/webpack.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ module.exports = function(env) {
// "default-src 'self'", // FIXME: enable as soon as the issue is fixed in Angular (https://github.com/angular/angular-cli/issues/6872 )
"child-src 'self'",
"connect-src 'self' ws://" + METADATA.HOST + ":" + METADATA.PORT + " " + webpackCustomConfig["cspConnectSrc"], // ws://HOST:PORT" is due to Webpack
`font-src 'self' ${webpackCustomConfig["cspFontSrc"] || ''}`,
`font-src 'self' ${webpackCustomConfig["cspFontSrc"] || ""}`,
"form-action 'self' " + webpackCustomConfig["cspFormAction"],
"frame-src 'self'", // deprecated. Use child-src instead. Used here because child-src is not yet supported by Firefox. Remove as soon as it is fully supported
"frame-ancestors 'none'", // the app will not be allowed to be embedded in an iframe (roughly equivalent to X-Frame-Options: DENY)
Expand Down Expand Up @@ -279,14 +279,14 @@ module.exports = function(env) {
*/
...(MONITOR
? [
new WebpackMonitor({
capture: true, // -> default 'true'
target: helpers.root("reports/webpack-monitor/stats.json"), // default -> '../monitor/stats.json'
launch: true, // -> default 'false'
port: 3030, // default -> 8081
excludeSourceMaps: true // default 'true'
})
]
new WebpackMonitor({
capture: true, // -> default 'true'
target: helpers.root("reports/webpack-monitor/stats.json"), // default -> '../monitor/stats.json'
launch: true, // -> default 'false'
port: 3030, // default -> 8081
excludeSourceMaps: true // default 'true'
})
]
: [])
],

Expand Down
4 changes: 2 additions & 2 deletions packages/stark-ui/assets/styles/_media-queries.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ $mobile-only-query: "(max-width: 599px)";
$mobile-only-screen-query: "screen and (max-width: 599px)";
$tablet-only-query: "(min-width: 600px) and (max-width: 959px)";
$tablet-only-screen-query: "screen and (min-width: 600px) and (max-width: 959px)";
$handset-toc-query-screen:"(min-width: 0px) and (max-width: 839px)";
$tablet-toc-query-screen:"(min-width: 840px) and (max-width: 1279px)";
$handset-toc-query-screen: "(min-width: 0px) and (max-width: 839px)";
$tablet-toc-query-screen: "(min-width: 840px) and (max-width: 1279px)";
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
& .stark-minimap-dropdown-toggle-menu {
border-color: mat-color($grey-palette, 500);
box-shadow: 0 1px 2px mat-color($grey-palette, 300);
background-color: #FFF;
background-color: #fff;
& mat-checkbox {
&:hover {
background: $md-primary-alpha-05;
Expand Down Expand Up @@ -85,7 +85,7 @@
& .stark-minimap-dropdown-toggle-menu {
border-color: mat-color($grey-palette, 500);
box-shadow: 0 1px 2px mat-color($grey-palette, 300);
background-color: #FFF;
background-color: #fff;
& mat-checkbox {
&:hover {
background: $md-primary-alpha-05;
Expand Down
3 changes: 3 additions & 0 deletions packages/stark-ui/src/modules/session-ui.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
export * from "./session-ui/session-ui.module";
export * from "./session-ui/pages";
export * from "./session-ui/effects";
export * from "./session-ui/entities";
export * from "./session-ui/components";
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ export const translationsEn: object = {
SESSION_LOGOUT: {
TITLE: "Logged out",
LOGIN: "Log in again"
},
SESSION_TIMEOUT: {
SECONDS: " seconds.",
STAY_CONNECTED: "Stay connected",
TITLE: "Session about to expire",
WILL_EXPIRE_IN: "Your session will expire in ",
WISH_STAY_CONNECTED: "Do you wish to stay connected?"
}
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ export const translationsFr: object = {
SESSION_LOGOUT: {
TITLE: "Déconnecté",
LOGIN: "Connexion"
},
SESSION_TIMEOUT: {
SECONDS: " secondes.",
STAY_CONNECTED: "Rester connecté",
TITLE: "Session sur le point d'expirer",
WILL_EXPIRE_IN: "Votre session va expirer dans ",
WISH_STAY_CONNECTED: "Voulez-vous rester connecté?"
}
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ export const translationsNl: object = {
SESSION_LOGOUT: {
TITLE: "Afgemeld",
LOGIN: "Opnieuw aanmelden"
},
SESSION_TIMEOUT: {
SECONDS: " seconden vervallen.",
STAY_CONNECTED: "Blijf verbonden",
TITLE: "Sessie verlopen",
WILL_EXPIRE_IN: "Uw sessie zal binnen ",
WISH_STAY_CONNECTED: "Wilt u verbonden blijven?"
}
}
};
1 change: 1 addition & 0 deletions packages/stark-ui/src/modules/session-ui/components.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./components/session-timeout-warning-dialog.component";
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* ============================================================================== */
/* S t a r k S e s s i o n T i m e o u t W a r n i n g D i a l o g */
/* ============================================================================== */
/* stark-ui: src/modules/session-ui/components/_session-timeout-warning-dialog.component.scss */

.stark-timeout-warning-dialog {
margin: 16px auto;
box-sizing: border-box;
border-radius: 8px;
text-align: center;
}

/* END stark-ui: src/modules/session-ui/components/_session-timeout-warning-dialog.component.scss */
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<div class="stark-timeout-warning-dialog" role="alertdialog" aria-busy="true" aria-live="assertive">
<h1 mat-dialog-title translate>STARK.SESSION_TIMEOUT.TITLE</h1>
<div mat-dialog-content>
<p>
<span translate>STARK.SESSION_TIMEOUT.WILL_EXPIRE_IN</span><span> {{ countdown$|async }} </span>
<span translate>STARK.SESSION_TIMEOUT.SECONDS</span>
</p>
<p translate>STARK.SESSION_TIMEOUT.WISH_STAY_CONNECTED</p>
</div>
<div mat-dialog-actions>
<button mat-raised-button
color="primary"
(click)="keepSession()"
aria-label="Close Dialog">
<span translate>STARK.SESSION_TIMEOUT.STAY_CONNECTED</span>
</button>
</div>
</div>


Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*tslint:disable:completed-docs*/
import { async, ComponentFixture, fakeAsync, TestBed, tick } from "@angular/core/testing";
import { CommonModule } from "@angular/common";
import { STARK_LOGGING_SERVICE } from "@nationalbankbelgium/stark-core";
import { MockStarkLoggingService } from "@nationalbankbelgium/stark-core/testing";
import { MatDialog, MAT_DIALOG_DATA, MatDialogRef, MatDialogModule } from "@angular/material/dialog";
import { StarkSessionTimeoutWarningDialogComponent } from "./session-timeout-warning-dialog.component";

import Spy = jasmine.Spy;
import createSpyObj = jasmine.createSpyObj;
import { Observer } from "rxjs";

describe("SessionTimeoutWarningDialogComponent", () => {
let component: StarkSessionTimeoutWarningDialogComponent;
let fixture: ComponentFixture<StarkSessionTimeoutWarningDialogComponent>;

let mockDialogRef: MatDialogRef<any>;
const mockLogger: MockStarkLoggingService = new MockStarkLoggingService();

beforeEach(async(() => {
return TestBed.configureTestingModule({
declarations: [StarkSessionTimeoutWarningDialogComponent],
imports: [CommonModule, MatDialogModule],
providers: [
{ provide: STARK_LOGGING_SERVICE, useValue: mockLogger },
{ provide: MatDialog, useValue: MatDialog },
{ provide: MAT_DIALOG_DATA, useValue: 20 },
{ provide: MatDialogRef, useValue: createSpyObj("MatDialogRefSpy", ["close"]) }
]
}).compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(StarkSessionTimeoutWarningDialogComponent);
mockDialogRef = TestBed.get(MatDialogRef);
component = fixture.componentInstance;

(<Spy>mockLogger.debug).calls.reset();
});

describe("ngOnInit", () => {
it("should set the countdown and decrement it every second", fakeAsync(() => {
const mockObserver: Observer<any> = createSpyObj<Observer<any>>("observerSpy", ["next", "error", "complete"]);

component.ngOnInit();
component.countdown$.subscribe(mockObserver);

expect(mockLogger.debug).toHaveBeenCalledTimes(1);

tick(20000);

expect(mockObserver.next).toHaveBeenCalledTimes(21);
expect(mockObserver.error).not.toHaveBeenCalled();
expect(mockObserver.complete).toHaveBeenCalled();

expect(mockDialogRef.close).toHaveBeenCalledTimes(1);
expect(mockDialogRef.close).toHaveBeenCalledWith("countdown-finished");
}));
});

describe("keepSession", () => {
it("should close the windows when the button is clicked", fakeAsync(() => {
component.ngOnInit();
component.keepSession();

expect(mockDialogRef.close).toHaveBeenCalledTimes(1);
expect(mockDialogRef.close).toHaveBeenCalledWith("keep-logged");
}));
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Component, Inject, OnInit, ViewEncapsulation } from "@angular/core";
import { MAT_DIALOG_DATA, MatDialogRef } from "@angular/material/dialog";
import { Observable, interval } from "rxjs";
import { STARK_LOGGING_SERVICE, StarkLoggingService } from "@nationalbankbelgium/stark-core";
import { map, startWith, tap, take } from "rxjs/operators";

/**
* The name of the component
*/
const componentName: string = "stark-session-timeout-warning-dialog";

/**
* Component to display a session timeout warning dialog
*/
@Component({
selector: "session-timeout-warning-dialog",
templateUrl: "./session-timeout-warning-dialog.component.html",
encapsulation: ViewEncapsulation.None
})
export class StarkSessionTimeoutWarningDialogComponent implements OnInit {
/**
* countdown Time in seconds before the current session expires.
*/
public countdown$: Observable<number>;

public constructor(
@Inject(STARK_LOGGING_SERVICE) public logger: StarkLoggingService,
@Inject(MatDialogRef) private dialogRef: MatDialogRef<StarkSessionTimeoutWarningDialogComponent>,
@Inject(MAT_DIALOG_DATA) private coutdown: number
) {}

/**
* Component lifecycle hook
*/
public ngOnInit(): void {
this.logger.debug(componentName + ": controller initialized");
this.countdown$ = interval(1000).pipe(
take(this.coutdown),
startWith(-1),
map((value: number) => this.coutdown - value - 1), // -1 due to the delay of the dialog animation
tap((value: number) => {
if (value === 0) {
this.dialogRef.close("countdown-finished");
}
})
);
}

/**
* This methods is used to close the dialog and to send an answer indicating that the user should keep logged.
*/
public keepSession(): void {
this.dialogRef.close("keep-logged");
}
}
1 change: 1 addition & 0 deletions packages/stark-ui/src/modules/session-ui/effects.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./effects/session-timeout-warning.effects";

0 comments on commit 578b8a2

Please sign in to comment.