LP1840773 SCKO Angular
authorBill Erickson <berickxx@gmail.com>
Thu, 23 Jun 2022 14:46:31 +0000 (10:46 -0400)
committerBill Erickson <berickxx@gmail.com>
Thu, 23 Jun 2022 14:46:31 +0000 (10:46 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/eg2/src/app/scko/scko.component.html
Open-ILS/src/eg2/src/app/scko/scko.component.ts
Open-ILS/src/eg2/src/app/scko/scko.service.ts
Open-ILS/src/eg2/src/app/share/dialog/confirm.component.html
Open-ILS/src/eg2/src/app/share/dialog/confirm.component.ts

index 45df3e9..ebec821 100644 (file)
   <div class="col-lg-3"><eg-scko-summary></eg-scko-summary></div>
 </div>
 
+<eg-confirm-dialog #logoutDialog 
+  i18n-dialogTitle i18n-dialogBody
+  i18n-confirmButtonText i18n-cancelButtonText
+  dialogTitle="Logout Notice"
+  dialogBody="Your login session will timeout due to inactivity"
+  confirmButtonText="Continue Session"
+  cancelButtonText="Logout">
+</eg-confirm-dialog>
+
 <!-- global toast alerts -->
 <eg-toast></eg-toast>
 
index 31a5bf8..4e21ebb 100644 (file)
@@ -1,8 +1,10 @@
-import {Component, OnInit, ViewEncapsulation} from '@angular/core';
+import {Component, OnInit, AfterViewInit, ViewChild, ViewEncapsulation} from '@angular/core';
 import {Router, ActivatedRoute, NavigationEnd} from '@angular/router';
 import {AuthService} from '@eg/core/auth.service';
+import {NetService} from '@eg/core/net.service';
 import {SckoService} from './scko.service';
 import {ServerStoreService} from '@eg/core/server-store.service';
+import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
 
 @Component({
   templateUrl: 'scko.component.html',
@@ -10,16 +12,31 @@ import {ServerStoreService} from '@eg/core/server-store.service';
   encapsulation: ViewEncapsulation.None
 })
 
-export class SckoComponent implements OnInit {
+export class SckoComponent implements OnInit, AfterViewInit {
+
+    @ViewChild('logoutDialog') logoutDialog: ConfirmDialogComponent;
 
     constructor(
         private router: Router,
         private route: ActivatedRoute,
+        private net: NetService,
+        private auth: AuthService,
         public  scko: SckoService
     ) {}
 
     ngOnInit() {
+        this.net.authExpired$.subscribe(how => {
+            console.debug("SCKO auth expired with info", how);
+            this.scko.resetPatron();
+            this.auth.logout();
+            this.router.navigate(['/scko']);
+        });
+
         this.scko.load();
     }
+
+    ngAfterViewInit() {
+        this.scko.logoutDialog = this.logoutDialog;
+    }
 }
 
index e1b6c3a..03b156f 100644 (file)
@@ -7,6 +7,7 @@ import {EventService, EgEvent} from '@eg/core/event.service';
 import {IdlService, IdlObject} from '@eg/core/idl.service';
 import {StoreService} from '@eg/core/store.service';
 import {PatronService, PatronSummary, PatronStats} from '@eg/staff/share/patron/patron.service';
+import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
 
 @Injectable({providedIn: 'root'})
 export class SckoService {
@@ -15,10 +16,18 @@ export class SckoService {
     patronSummary: PatronSummary;
     barcodeRegex: RegExp;
     patronPasswordRequired = false;
+    patronIdleTimeout: number;
+    patronTimeoutId: number;
+    logoutWarningTimeout = 20;
+    logoutWarningTimerId: number;
 
     sessionCheckouts: any[] = [];
 
+    // We get this from the main scko component.
+    logoutDialog: ConfirmDialogComponent;
+
     constructor(
+        private router: Router,
         private route: ActivatedRoute,
         private org: OrgService,
         private net: NetService,
@@ -60,6 +69,12 @@ export class SckoService {
             this.patronPasswordRequired =
                 sets['circ.selfcheck.patron_password_required'];
 
+            this.patronIdleTimeout =
+                Number(sets['circ.selfcheck.patron_login_timeout'] || 160);
+
+            // Compensate for the warning dialog
+            this.patronIdleTimeout -= this.logoutWarningTimeout;
+
             // Load a patron by barcode via URL params.
             // Useful for development.
             const username = this.route.snapshot.queryParamMap.get('patron');
@@ -119,7 +134,50 @@ export class SckoService {
         })
         .then(patron => this.patronSummary = new PatronSummary(patron))
         .then(_ => this.patrons.getVitalStats(this.patronSummary.patron))
-        .then(stats => this.patronSummary.stats = stats);
+        .then(stats => this.patronSummary.stats = stats)
+        .then(_ => this.startPatronTimer());
+    }
+
+    resetPatronTimeout() {
+        if (this.patronTimeoutId) {
+            clearTimeout(this.patronTimeoutId);
+        }
+        this.startPatronTimer();
+    }
+
+    startPatronTimer() {
+        console.debug('Starting patron timeout counter', this.patronIdleTimeout);
+
+        this.patronTimeoutId = setTimeout(
+            () => this.showPatronLogoutWarning(),
+            this.patronIdleTimeout * 1000
+        );
+    }
+
+    showPatronLogoutWarning() {
+        console.debug('Session timing out.  Show warning dialog');
+
+        this.logoutDialog.open().subscribe(remain => {
+            if (remain) {
+                clearTimeout(this.logoutWarningTimerId);
+                this.logoutWarningTimerId = null;
+                this.resetPatronTimeout();
+            } else {
+                this.resetPatron();
+                this.router.navigate(['/scko']);
+            }
+        });
+
+        // Force the session to end if no action is taken on the
+        // logout warning dialog.
+        this.logoutWarningTimerId = setTimeout(
+            () => {
+                console.debug('Clearing patron on warning dialog timeout');
+                this.resetPatron();
+                this.router.navigate(['/scko']);
+            },
+            this.logoutWarningTimeout * 1000
+        );
     }
 
     sessionTotalCheckouts(): number {
index 05cf562..c824401 100644 (file)
     </ng-container>
   </div>
   <div class="modal-footer">
-    <button type="button" class="btn btn-success" 
-      (click)="close(true)" i18n>Confirm</button>
-    <button type="button" class="btn btn-warning" 
-      (click)="close(false)" i18n>Cancel</button>
+    <button type="button" class="btn btn-success" (click)="close(true)">
+      <ng-container *ngIf="confirmButtonText">{{confirmButtonText}}</ng-container>
+      <ng-container *ngIf="!confirmButtonText" i18n>Confirm</ng-container>
+    </button>
+    <button type="button" class="btn btn-warning" (click)="close(false)">
+      <ng-container *ngIf="cancelButtonText">{{cancelButtonText}}</ng-container>
+      <ng-container *ngIf="!cancelButtonText" i18n>Cancel</ng-container>
+    </button>
   </div>
 </ng-template>
index f195f32..3b6be8b 100644 (file)
@@ -13,6 +13,9 @@ export class ConfirmDialogComponent extends DialogComponent {
     // What question are we asking?
     @Input() public dialogBody: string;
     @Input() public dialogBodyTemplate: TemplateRef<any>;
+
+    @Input() confirmButtonText: string;
+    @Input() cancelButtonText: string;
 }