LP#626157 Ang2 experiments
authorBill Erickson <berickxx@gmail.com>
Tue, 21 Nov 2017 16:38:49 +0000 (11:38 -0500)
committerBill Erickson <berickxx@gmail.com>
Mon, 5 Mar 2018 17:24:06 +0000 (17:24 +0000)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
19 files changed:
Open-ILS/webby-src/src/app/base-resolver.service.ts [new file with mode: 0644]
Open-ILS/webby-src/src/app/base-routing.module.ts
Open-ILS/webby-src/src/app/base.module.ts
Open-ILS/webby-src/src/app/core/README
Open-ILS/webby-src/src/app/core/auth.service.ts [new file with mode: 0644]
Open-ILS/webby-src/src/app/core/eg-auth.service.ts [deleted file]
Open-ILS/webby-src/src/app/core/eg-event.service.ts [deleted file]
Open-ILS/webby-src/src/app/core/eg-idl.service.ts [deleted file]
Open-ILS/webby-src/src/app/core/eg-net.service.ts [deleted file]
Open-ILS/webby-src/src/app/core/event.service.ts [new file with mode: 0644]
Open-ILS/webby-src/src/app/core/idl.service.ts [new file with mode: 0644]
Open-ILS/webby-src/src/app/core/net.service.ts [new file with mode: 0644]
Open-ILS/webby-src/src/app/core/store.service.ts [new file with mode: 0644]
Open-ILS/webby-src/src/app/staff/circ/patron/bcsearch/bcsearch.component.ts
Open-ILS/webby-src/src/app/staff/circ/patron/bcsearch/bcsearch.module.ts
Open-ILS/webby-src/src/app/staff/login.component.html
Open-ILS/webby-src/src/app/staff/login.component.ts
Open-ILS/webby-src/src/app/staff/staff-resolver.service.ts
Open-ILS/webby-src/src/app/staff/staff-routing.module.ts

diff --git a/Open-ILS/webby-src/src/app/base-resolver.service.ts b/Open-ILS/webby-src/src/app/base-resolver.service.ts
new file mode 100644 (file)
index 0000000..e1e8b73
--- /dev/null
@@ -0,0 +1,23 @@
+import { Injectable }             from '@angular/core';
+import { Observable }             from 'rxjs/Observable';
+import { Router, Resolve, RouterStateSnapshot,
+         ActivatedRouteSnapshot } from '@angular/router';
+import { EgIdlService }  from '@eg/core/idl.service';
+@Injectable()
+export class EgBaseResolver implements Resolve<any> {
+
+    constructor(private router: Router, private egIdl: EgIdlService) {}
+
+    resolve(
+        route: ActivatedRouteSnapshot, 
+        state: RouterStateSnapshot): Observable<any> {
+
+        console.debug('EgBaseResolver:resolve()');
+
+        this.egIdl.parseIdl();
+
+        return Observable.empty(); // Nothing to report.
+    }
+}
index 08b9c48..7280e37 100644 (file)
@@ -1,5 +1,6 @@
 import { NgModule }             from '@angular/core';
 import { RouterModule, Routes } from '@angular/router';
+import { EgBaseResolver }       from './base-resolver.service';
 import { WelcomeComponent }     from './welcome.component';
 
 /**
@@ -7,17 +8,20 @@ import { WelcomeComponent }     from './welcome.component';
  * When lazy loading, no module references should be directly imported.
  * The refs are encoded in the loadChildren attribute of each route.
  */
-
 const routes: Routes = [
-  {    path: '',       component: WelcomeComponent  },
-  {    path: 'staff', 
+  { path: '',
+    component: WelcomeComponent
+  }, {
+    path: 'staff', 
+    resolve : {startup : EgBaseResolver},
     loadChildren: './staff/staff.module#EgStaffModule'
   }
 ];
 
 @NgModule({
   imports: [ RouterModule.forRoot(routes) ],
-  exports: [ RouterModule ]
+  exports: [ RouterModule ],
+  providers: [ EgBaseResolver ]
 })
 
 export class EgBaseRoutingModule {}
index ecb4d28..1fbb2fa 100644 (file)
@@ -13,10 +13,10 @@ import { EgBaseRoutingModule } from './base-routing.module';
 import { WelcomeComponent }    from './welcome.component';
 
 // Import and 'provide' globally required services.
-import { EgEventService } from '@eg/core/eg-event.service';
-import { EgIdlService }   from '@eg/core/eg-idl.service';
-import { EgNetService }   from '@eg/core/eg-net.service';
-import { EgAuthService }  from '@eg/core/eg-auth.service';
+import { EgEventService } from '@eg/core/event.service';
+import { EgIdlService }   from '@eg/core/idl.service';
+import { EgNetService }   from '@eg/core/net.service';
+import { EgAuthService }  from '@eg/core/auth.service';
 
 @NgModule({
   declarations: [
index 217411d..68dff84 100644 (file)
@@ -1,5 +1,5 @@
 Core types (classes) and Angular services used by all modules.
 
-Class files are named $classname.ts
+NOTES:
 
-Service files are named $servicename.service.ts
+* Avoid path navigation in the core services as paths will vary by application.
diff --git a/Open-ILS/webby-src/src/app/core/auth.service.ts b/Open-ILS/webby-src/src/app/core/auth.service.ts
new file mode 100644 (file)
index 0000000..dfb101e
--- /dev/null
@@ -0,0 +1,122 @@
+/**
+ * 
+ */
+import { Injectable, EventEmitter } from '@angular/core';
+import { Observable } from 'rxjs/Rx';
+import { EgNetService } from './net.service';
+import { EgEventService, EgEvent } from './event.service';
+import { EgIdlService, EgIdlObject } from './idl.service';
+import { EgStoreService } from './store.service';
+
+// Models a login instance.
+class EgAuthUser {
+    user:        EgIdlObject;
+    workstation: String; // workstation name
+    token:       String;
+    authtime:    Number;
+
+    constructor(token: String, authtime: Number, workstation: String) {
+        this.token = token;
+        this.workstation = workstation;
+        this.authtime = authtime;
+    }
+}
+
+// Params required for calling the login() method.
+interface EgAuthLoginArgs {
+    username:    String,
+    password:    String,
+    workstation: String,
+    type:        String // staff, persist, etc.
+}
+
+@Injectable()
+export class EgAuthService {
+
+    private activeUser: EgAuthUser;
+
+    // opChangeUser refers to the user that has been superseded during
+    // an op-change event.  This use will become the activeUser once
+    // again, when the op-change cycle has completed.
+    private opChangeUser: EgAuthUser;
+
+    constructor(
+        private egEvt: EgEventService,
+        private egNet: EgNetService,
+        private egStore: EgStoreService
+    ) {}
+
+    // - Accessor functions alway refer to the active user.
+
+    user(): EgIdlObject { 
+        return this.activeUser.user 
+    };
+
+    // Workstation name.
+    workstation(): String { 
+        return this.activeUser.workstation 
+    };
+
+    token(): String { 
+        return this.activeUser.token 
+    };
+
+    authtime(): Number { 
+        return this.activeUser.authtime 
+    };
+
+    login(args: EgAuthLoginArgs, ops?: Object): Promise<any> {
+        if (!ops) ops = {};
+
+        return new Promise<any>((resolve, reject) => {
+            this.egNet.request('open-ils.auth', 'open-ils.auth.login', args)
+            .subscribe(res => {
+                let evt = this.egEvt.parse(res);
+                if (evt) {
+                    if (evt.textcode == 'SUCCESS') {
+                        this.handleLoginOk(args, evt, false);
+                        resolve();
+                    }
+                } else {
+                    // Should never get here.
+                    reject();
+                }
+            });
+        });
+    }
+
+    testAuthToken(): Promise<any> {
+        return Promise.resolve();
+    }
+
+    // Stash the login data
+    handleLoginOk(args: EgAuthLoginArgs, evt: EgEvent, isOpChange: Boolean): void {
+
+        if (isOpChange) {
+            this.egStore.setLoginSessionItem('eg.auth.token.oc', this.token());
+            this.egStore.setLoginSessionItem('eg.auth.time.oc', this.authtime());
+            this.opChangeUser = this.activeUser;
+        }
+
+        this.activeUser = new EgAuthUser(
+            evt.payload.authtoken,
+            evt.payload.authtime,
+            args.workstation
+        );
+
+        this.egStore.setLoginSessionItem('eg.auth.token', this.token());
+        this.egStore.setLoginSessionItem('eg.auth.time', this.authtime());
+    }
+
+    undoOpChange(): Promise<any> {
+        if (this.opChangeUser) {
+            this.activeUser = this.opChangeUser;
+            this.opChangeUser = null;
+            this.egStore.removeLoginSessionItem('eg.auth.token.oc');                
+            this.egStore.removeLoginSessionItem('eg.auth.time.oc');                 
+            this.egStore.setLoginSessionItem('eg.auth.token', this.token());
+            this.egStore.setLoginSessionItem('eg.auth.time', this.authtime());
+        }
+        return this.testAuthToken();
+    }
+}
diff --git a/Open-ILS/webby-src/src/app/core/eg-auth.service.ts b/Open-ILS/webby-src/src/app/core/eg-auth.service.ts
deleted file mode 100644 (file)
index 8501133..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-/**
- * 
- */
-import { Injectable, EventEmitter } from '@angular/core';
-import { Observable } from 'rxjs/Rx';
-import { EgNetService } from './eg-net.service';
-import { EgEventService, EgEvent } from './eg-event.service';
-import { EgIdlService, EgIdlObject } from './eg-idl.service';
-
-class EgAuthUser {
-    user : EgIdlObject;
-    workstation : String;
-    token : String;
-    authtime : Number;
-
-    constructor(token: String, authtime: Number, workstation: String) {
-        this.token = token;
-        this.workstation = workstation;
-        this.authtime = authtime;
-    }
-}
-
-interface EgAuthArgs {
-    username : String,
-    password : String,
-    workstation : String,
-    type : String
-}
-
-@Injectable()
-export class EgAuthService {
-
-    private activeUser : EgAuthUser;
-    private opChangeUser : EgAuthUser;
-
-    user(): EgIdlObject { 
-        return this.activeUser.user 
-    };
-
-    // Workstation name.
-    workstation(): String { 
-        return this.activeUser.workstation 
-    };
-
-    token(): String { 
-        return this.activeUser.token 
-    };
-
-    authtime(): Number { 
-        return this.activeUser.authtime 
-    };
-
-    constructor(
-        private egEvt: EgEventService,
-        private egNet: EgNetService
-    ) {}
-
-    login(args: EgAuthArgs, ops?: Object): Promise<any> {
-
-        return new Promise<any>((resolve, reject) => {
-            this.egNet.request('open-ils.auth', 'open-ils.auth.login', args)
-            .subscribe(res => {
-                let evt = this.egEvt.parse(res);
-                if (evt) {
-                    if (evt.textcode == 'SUCCESS') {
-                        this.handleLoginOk(args, evt);
-                        resolve();
-                    }
-                }
-            });
-        });
-    }
-
-    // Stash the login data
-    handleLoginOk(args: EgAuthArgs, evt: EgEvent): void {
-
-        this.activeUser = new EgAuthUser(
-            evt.payload.authtoken,
-            evt.payload.authtime,
-            args.workstation
-        )
-
-        // TODO: put stuff into login session cache;
-    }
-}
diff --git a/Open-ILS/webby-src/src/app/core/eg-event.service.ts b/Open-ILS/webby-src/src/app/core/eg-event.service.ts
deleted file mode 100644 (file)
index 3f6afc7..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-import { Injectable } from '@angular/core';
-
-export class EgEvent {
-    code        : Number;
-    textcode    : String;
-    payload     : any;
-    desc        : String;
-    debug       : String;
-    note        : String;
-    servertime  : String;
-    ilsperm     : String;
-    ilspermloc  : Number;
-    success     : Boolean = false;
-
-    toString(): String {
-        let s = `Event: ${this.code}:${this.textcode} -> ${this.desc}`;
-        if (this.ilsperm)
-            s += `  ${this.ilsperm}@${this.ilspermloc}`;
-        if (this.note)
-            s += `\n${this.note}`;
-        return s;
-    }
-}
-
-@Injectable()
-export class EgEventService {
-
-    /**
-     * Returns an EgEvent if 'thing' is an event, null otherwise.
-     */
-    parse(thing: any): EgEvent {
-
-        // All events have a textcode
-        if (thing && typeof thing == 'object' && 'textcode' in thing) {
-
-            let evt = new EgEvent();
-
-            ['textcode','payload','desc','note','servertime','ilsperm']
-                .forEach(field => { evt[field] = thing[field]; });
-
-            evt.debug = thing.stacktrace;
-            evt.code = new Number(thing.code);
-            evt.ilspermloc = new Number(thing.ilspermloc);
-            evt.success = thing.textcode == 'SUCCESS';
-
-            return evt;
-        }
-
-        return null;
-    }
-}
-
-
diff --git a/Open-ILS/webby-src/src/app/core/eg-idl.service.ts b/Open-ILS/webby-src/src/app/core/eg-idl.service.ts
deleted file mode 100644 (file)
index 3fffd12..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-import { Injectable } from '@angular/core';
-
-// Added globally by /IDL2js
-declare var _preload_fieldmapper_IDL: Object;
-
-/**
- * NOTE: To achieve full type strictness and avoid compile warnings,
- * we would likely have to pre-compile the IDL down to a .ts file with all 
- * of the IDL class and field definitions.
- */
-
-/**
- * Every IDL object class implements this interface.
- */
-export interface EgIdlObject {
-    a: any[];
-    classname: String;
-    _isfieldmapper: Boolean;
-}
-
-@Injectable()
-export class EgIdlService {
-
-    classes: Object;
-
-    parseIdl(): void {
-        let this_ = this;
-        this_.classes = _preload_fieldmapper_IDL;
-
-        /**
-         * Creates the class constructor and getter/setter
-         * methods for each IDL class.
-         */
-        
-        let mkclass = (cls, fields) => {
-            this_.classes[cls].classname = cls;
-
-            // This dance lets us encode each IDL object with the
-            // EgIdlObject interface.  Useful for adding type restrictions
-            // where desired for functions, etc.
-            let generator:any = ((): EgIdlObject => {
-
-                var x:any = function(seed) {
-                    this.a = seed || [];
-                    this.classname = cls;
-                    this._isfieldmapper = true;
-                };
-
-                fields.forEach(function(field, idx) {
-                    x.prototype[field.name] = function(n) {
-                        if (arguments.length==1) this.a[idx] = n;
-                        return this.a[idx];
-                    }
-                });
-
-                return x;
-            })
-
-            this_[cls] = generator();
-
-            // global class constructors required for JSON_v1.js
-            // TODO: Move away from requiring we add classes to window.
-            window[cls] = this_[cls]; 
-        }
-
-        for (var cls in this_.classes) 
-            mkclass(cls, this_.classes[cls].fields);
-    };
-}
-
diff --git a/Open-ILS/webby-src/src/app/core/eg-net.service.ts b/Open-ILS/webby-src/src/app/core/eg-net.service.ts
deleted file mode 100644 (file)
index 9232828..0000000
+++ /dev/null
@@ -1,140 +0,0 @@
-/**
- * 
- * constructor(private egNet : EgNetService) {
- *   ...
- *   egNet.request(service, method, param1 [, param2, ...])
- *     .subscribe(
- *       (res) => console.log('received one resopnse: ' + res),
- *       (err) => console.error('recived request error: ' + err),
- *       ()    => console.log('request complete')
- *     )
- *   );
- *   ...
- * }
- *
- * Each response is relayed via Observable onNext().  The interface is 
- * the same for streaming and atomic requests.
- */
-import { Injectable, EventEmitter } from '@angular/core';
-import { Observable, Observer } from 'rxjs/Rx';
-import { EgEventService, EgEvent } from './eg-event.service';
-
-// Global vars from opensrf.js
-// These are availavble at runtime, but are not exported.
-declare var OpenSRF, OSRF_TRANSPORT_TYPE_WS;
-
-export class EgNetRequest {
-    service    : String;
-    method     : String;
-    params     : any[];
-    observer   : Observer<any>;
-    superseded : Boolean = false;
-
-    // Last EgEvent encountered by this request.
-    // Most callers will not need to import EgEvent since the parsed
-    // event will be available here.
-    evt: EgEvent;
-
-    constructor(service: String, method: String, params: any[]) {
-        this.service = service;
-        this.method = method;
-        this.params = params;
-    }
-}
-
-@Injectable()
-export class EgNetService {
-
-    permFailed$: EventEmitter<EgNetRequest>;
-    authExpired$: EventEmitter<EgNetRequest>;
-
-    // If true, permission failures are emitted via permFailed$ 
-    // and the active request is marked as superseded.
-    permFailedHasHandler: Boolean = false;
-
-    constructor(
-        private egEvt: EgEventService
-    ) { 
-        this.permFailed$ = new EventEmitter<EgNetRequest>();
-        this.authExpired$ = new EventEmitter<EgNetRequest>();
-    }
-
-    // Variadic params version
-    request(service: String, method: String, ...params: any[]): Observable<any> {
-        return this.requestWithParamList(service, method, params);
-    }
-
-    // Array params version
-    requestWithParamList(service: String, 
-        method: String, params: any[]): Observable<any> {
-
-        var request = new EgNetRequest(service, method, params);
-
-        return Observable.create(
-            observer => {
-                request.observer = observer;
-                this.sendRequest(request);
-            }
-        );
-    }
-
-    private sendRequest(request: EgNetRequest): void {
-        OpenSRF.Session.transport = OSRF_TRANSPORT_TYPE_WS;
-        var this_ = this;
-
-        new OpenSRF.ClientSession(request.service).request({
-            async  : true,
-            method : request.method,
-            params : request.params,
-            oncomplete : function() {
-                // A superseded request will be complete()'ed by the 
-                // superseder at a later time.
-                if (!request.superseded)
-                    request.observer.complete();
-            },
-            onresponse : function(r) {
-                this_.dispatchResponse(request, r.recv().content());
-            },
-            onerror : function(errmsg) {
-                let msg = `${request.method} failed! See server logs. ${errmsg}`;
-                console.error(msg);
-                request.observer.error(msg);
-            },
-            onmethoderror : function(req, statCode, statMsg) { 
-                let msg = 
-                    `${request.method} failed! stat=${statCode} msg=${statMsg}`;
-                console.error(msg);
-                request.observer.error(msg);
-            }
-
-        }).send();
-    }
-
-    // Relay response object to the caller for typical/successful responses.  
-    // Applies special handling to response events that require global attention.
-    private dispatchResponse = function(request, response) {
-        request.evt = this.egEvt.parse(response);
-
-        if (request.evt) {
-            switch(request.evt.textcode) {
-
-                case 'NO_SESSION':
-                    console.debug(`EgNet emitting event: ${request.evt}`);
-                    request.observer.error(request.evt.toString());
-                    this.authExpired$.emit(request);
-                    return;
-
-                case 'PERM_FAILURE':
-                    if (this.permFailedHasHandler) {
-                        console.debug(`EgNet emitting event: ${request.evt}`);
-                        request.superseded = true;
-                        this.permFailed$.emit(request);
-                        return;
-                    }
-            } 
-        }
-
-        // Pass the response to the caller.
-        request.observer.next(response);
-    };
-}
diff --git a/Open-ILS/webby-src/src/app/core/event.service.ts b/Open-ILS/webby-src/src/app/core/event.service.ts
new file mode 100644 (file)
index 0000000..3f6afc7
--- /dev/null
@@ -0,0 +1,53 @@
+import { Injectable } from '@angular/core';
+
+export class EgEvent {
+    code        : Number;
+    textcode    : String;
+    payload     : any;
+    desc        : String;
+    debug       : String;
+    note        : String;
+    servertime  : String;
+    ilsperm     : String;
+    ilspermloc  : Number;
+    success     : Boolean = false;
+
+    toString(): String {
+        let s = `Event: ${this.code}:${this.textcode} -> ${this.desc}`;
+        if (this.ilsperm)
+            s += `  ${this.ilsperm}@${this.ilspermloc}`;
+        if (this.note)
+            s += `\n${this.note}`;
+        return s;
+    }
+}
+
+@Injectable()
+export class EgEventService {
+
+    /**
+     * Returns an EgEvent if 'thing' is an event, null otherwise.
+     */
+    parse(thing: any): EgEvent {
+
+        // All events have a textcode
+        if (thing && typeof thing == 'object' && 'textcode' in thing) {
+
+            let evt = new EgEvent();
+
+            ['textcode','payload','desc','note','servertime','ilsperm']
+                .forEach(field => { evt[field] = thing[field]; });
+
+            evt.debug = thing.stacktrace;
+            evt.code = new Number(thing.code);
+            evt.ilspermloc = new Number(thing.ilspermloc);
+            evt.success = thing.textcode == 'SUCCESS';
+
+            return evt;
+        }
+
+        return null;
+    }
+}
+
+
diff --git a/Open-ILS/webby-src/src/app/core/idl.service.ts b/Open-ILS/webby-src/src/app/core/idl.service.ts
new file mode 100644 (file)
index 0000000..3fffd12
--- /dev/null
@@ -0,0 +1,70 @@
+import { Injectable } from '@angular/core';
+
+// Added globally by /IDL2js
+declare var _preload_fieldmapper_IDL: Object;
+
+/**
+ * NOTE: To achieve full type strictness and avoid compile warnings,
+ * we would likely have to pre-compile the IDL down to a .ts file with all 
+ * of the IDL class and field definitions.
+ */
+
+/**
+ * Every IDL object class implements this interface.
+ */
+export interface EgIdlObject {
+    a: any[];
+    classname: String;
+    _isfieldmapper: Boolean;
+}
+
+@Injectable()
+export class EgIdlService {
+
+    classes: Object;
+
+    parseIdl(): void {
+        let this_ = this;
+        this_.classes = _preload_fieldmapper_IDL;
+
+        /**
+         * Creates the class constructor and getter/setter
+         * methods for each IDL class.
+         */
+        
+        let mkclass = (cls, fields) => {
+            this_.classes[cls].classname = cls;
+
+            // This dance lets us encode each IDL object with the
+            // EgIdlObject interface.  Useful for adding type restrictions
+            // where desired for functions, etc.
+            let generator:any = ((): EgIdlObject => {
+
+                var x:any = function(seed) {
+                    this.a = seed || [];
+                    this.classname = cls;
+                    this._isfieldmapper = true;
+                };
+
+                fields.forEach(function(field, idx) {
+                    x.prototype[field.name] = function(n) {
+                        if (arguments.length==1) this.a[idx] = n;
+                        return this.a[idx];
+                    }
+                });
+
+                return x;
+            })
+
+            this_[cls] = generator();
+
+            // global class constructors required for JSON_v1.js
+            // TODO: Move away from requiring we add classes to window.
+            window[cls] = this_[cls]; 
+        }
+
+        for (var cls in this_.classes) 
+            mkclass(cls, this_.classes[cls].fields);
+    };
+}
+
diff --git a/Open-ILS/webby-src/src/app/core/net.service.ts b/Open-ILS/webby-src/src/app/core/net.service.ts
new file mode 100644 (file)
index 0000000..c6082a7
--- /dev/null
@@ -0,0 +1,140 @@
+/**
+ * 
+ * constructor(private egNet : EgNetService) {
+ *   ...
+ *   egNet.request(service, method, param1 [, param2, ...])
+ *     .subscribe(
+ *       (res) => console.log('received one resopnse: ' + res),
+ *       (err) => console.error('recived request error: ' + err),
+ *       ()    => console.log('request complete')
+ *     )
+ *   );
+ *   ...
+ * }
+ *
+ * Each response is relayed via Observable onNext().  The interface is 
+ * the same for streaming and atomic requests.
+ */
+import { Injectable, EventEmitter } from '@angular/core';
+import { Observable, Observer } from 'rxjs/Rx';
+import { EgEventService, EgEvent } from './event.service';
+
+// Global vars from opensrf.js
+// These are availavble at runtime, but are not exported.
+declare var OpenSRF, OSRF_TRANSPORT_TYPE_WS;
+
+export class EgNetRequest {
+    service    : String;
+    method     : String;
+    params     : any[];
+    observer   : Observer<any>;
+    superseded : Boolean = false;
+
+    // Last EgEvent encountered by this request.
+    // Most callers will not need to import EgEvent since the parsed
+    // event will be available here.
+    evt: EgEvent;
+
+    constructor(service: String, method: String, params: any[]) {
+        this.service = service;
+        this.method = method;
+        this.params = params;
+    }
+}
+
+@Injectable()
+export class EgNetService {
+
+    permFailed$: EventEmitter<EgNetRequest>;
+    authExpired$: EventEmitter<EgNetRequest>;
+
+    // If true, permission failures are emitted via permFailed$ 
+    // and the active request is marked as superseded.
+    permFailedHasHandler: Boolean = false;
+
+    constructor(
+        private egEvt: EgEventService
+    ) { 
+        this.permFailed$ = new EventEmitter<EgNetRequest>();
+        this.authExpired$ = new EventEmitter<EgNetRequest>();
+    }
+
+    // Variadic params version
+    request(service: String, method: String, ...params: any[]): Observable<any> {
+        return this.requestWithParamList(service, method, params);
+    }
+
+    // Array params version
+    requestWithParamList(service: String, 
+        method: String, params: any[]): Observable<any> {
+
+        var request = new EgNetRequest(service, method, params);
+
+        return Observable.create(
+            observer => {
+                request.observer = observer;
+                this.sendRequest(request);
+            }
+        );
+    }
+
+    private sendRequest(request: EgNetRequest): void {
+        OpenSRF.Session.transport = OSRF_TRANSPORT_TYPE_WS;
+        var this_ = this;
+
+        new OpenSRF.ClientSession(request.service).request({
+            async  : true,
+            method : request.method,
+            params : request.params,
+            oncomplete : function() {
+                // A superseded request will be complete()'ed by the 
+                // superseder at a later time.
+                if (!request.superseded)
+                    request.observer.complete();
+            },
+            onresponse : function(r) {
+                this_.dispatchResponse(request, r.recv().content());
+            },
+            onerror : function(errmsg) {
+                let msg = `${request.method} failed! See server logs. ${errmsg}`;
+                console.error(msg);
+                request.observer.error(msg);
+            },
+            onmethoderror : function(req, statCode, statMsg) { 
+                let msg = 
+                    `${request.method} failed! stat=${statCode} msg=${statMsg}`;
+                console.error(msg);
+                request.observer.error(msg);
+            }
+
+        }).send();
+    }
+
+    // Relay response object to the caller for typical/successful responses.  
+    // Applies special handling to response events that require global attention.
+    private dispatchResponse = function(request, response) {
+        request.evt = this.egEvt.parse(response);
+
+        if (request.evt) {
+            switch(request.evt.textcode) {
+
+                case 'NO_SESSION':
+                    console.debug(`EgNet emitting event: ${request.evt}`);
+                    request.observer.error(request.evt.toString());
+                    this.authExpired$.emit(request);
+                    return;
+
+                case 'PERM_FAILURE':
+                    if (this.permFailedHasHandler) {
+                        console.debug(`EgNet emitting event: ${request.evt}`);
+                        request.superseded = true;
+                        this.permFailed$.emit(request);
+                        return;
+                    }
+            } 
+        }
+
+        // Pass the response to the caller.
+        request.observer.next(response);
+    };
+}
diff --git a/Open-ILS/webby-src/src/app/core/store.service.ts b/Open-ILS/webby-src/src/app/core/store.service.ts
new file mode 100644 (file)
index 0000000..caf1625
--- /dev/null
@@ -0,0 +1,94 @@
+/**
+ * Store and retrieve data from various sources.
+ */
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs/Rx';
+
+@Injectable()
+export class EgStoreService {
+
+    setItem(key: string, val: any, isJson?: Boolean): Promise<any> {
+
+        // TODO: route keys appropriately
+
+        this.setLocalItem(key, val, false);
+        return Promise.resolve();
+    }
+
+    setLocalItem(key: string, val: any, isJson?: Boolean): void {
+        if (!isJson) val = JSON.stringify(val);
+        window.localStorage.setItem(key, val);
+    }
+
+    setServerItem(key: string, val: any): Promise<any> {
+        // JSON-ify on the server?
+        return Promise.resolve();
+    }
+
+    setSessionItem(key: string, val: any, isJson?: Boolean): void {
+        if (!isJson) val = JSON.stringify(val);
+    }
+
+    setLoginSessionItem(key: string, val: any, isJson?:Boolean): void {
+        if (!isJson) val = JSON.stringify(val);
+    }
+
+    getItem(key: string): Promise<any> {
+        // TODO: route keys appropriately
+
+        return Promise.resolve(this.getLocalItem(key));
+    }
+
+    getLocalItem(key: string): any {
+        let valJson: string = window.localStorage.getItem(key);
+        if (valJson === null)  return null;
+        try {
+            return JSON.parse(valJson); 
+        } catch (E) {
+            console.error(`Deleting invalid JSON for localItem: ` 
+                + `${key} => ${valJson} : ${E}`);
+            this.removeLocalItem(key);
+        }
+        return null;
+    }
+
+    getServerItem(key: string): Promise<any> {
+        return Promise.resolve();
+    }
+
+    getSessionItem(key: string): any {
+    }
+
+    getLoginSessionItem(key: string): any {
+    }
+
+    removeItem(key: string): Promise<any> {
+        return Promise.resolve();
+    }
+
+    removeLocalItem(key: string): void {
+        let valJson: string = window.localStorage.getItem(key);
+        if (valJson === null)  return null;
+        try {
+            return JSON.parse(valJson); 
+        } catch (E) {
+            console.error(
+                `Deleting invalid JSON for localItem: ${key} => ${valJson}`);
+            this.removeLocalItem(key);
+            return null;
+        }
+    }
+
+    removeServerItem(key: string): Promise<any> {
+        return Promise.resolve();
+    }
+
+    removeSessionItem(key: string): void {
+    }
+
+    removeLoginSessionItem(key: string): void {
+    }
+
+
+}
+
index cb95c1f..18765e4 100644 (file)
@@ -1,8 +1,7 @@
 import { Component, OnInit } from '@angular/core';
 import { ActivatedRoute } from '@angular/router';
-import { EgNetService } from '@eg/core/eg-net.service';
-import { EgIdlService, EgIdlObject } from '@eg/core/eg-idl.service';
-import { EgAuthService } from '@eg/core/eg-auth.service';
+import { EgNetService } from '@eg/core/net.service';
+import { EgAuthService } from '@eg/core/auth.service';
 
 declare var js2JSON;
 
@@ -18,7 +17,6 @@ export class EgBcSearchComponent implements OnInit {
     constructor(
         private route: ActivatedRoute,
         private egNet: EgNetService,
-        private egIdl: EgIdlService,
         private egAuth: EgAuthService
     ) {}
 
@@ -49,27 +47,6 @@ export class EgBcSearchComponent implements OnInit {
             e => console.log('echo event: ' + e),
             () => console.log('done')
         );
-
-        this.egIdl.parseIdl();
-        
-        let org = new this.egIdl['aou']();
-        org.name('HELLO');
-        console.log(org.name() + ' : ' + js2JSON(org));
-
-        function foo(obj: EgIdlObject) {
-            console.log('org is an instance!');
-        }
-
-        foo(org);
-
-        this.egAuth.login({
-            username: 'admin', 
-            password: 'demo123', 
-            workstation : 'BR1-skiddoo',
-            type : 'staff'
-        }).then(res => {
-            console.log('login OK with auth ' + this.egAuth.token());
-        });
     }
 
     findUser(): void {
index af67ff1..0f38729 100644 (file)
@@ -1,7 +1,7 @@
 import { CommonModule }            from '@angular/common';
 import { NgModule }                from '@angular/core';
 import { FormsModule }             from '@angular/forms';
-import { EgNetService }            from '@eg/core/eg-net.service';
+import { EgNetService }            from '@eg/core/net.service';
 import { EgBcSearchComponent }     from './bcsearch.component';
 import { EgBcSearchRoutingModule } from './bcsearch-routing.module';
 
index 8bb9cd6..09726bf 100644 (file)
@@ -7,7 +7,8 @@
 
           <div class="form-group">
             <label for="username" i18n>Username</label>
-            <input type="text" 
+            <input 
+              type="text" 
                 class="form-control" 
                 id="username" 
                 name="username"
@@ -19,7 +20,8 @@
 
           <div class="form-group">
             <label for="password" i18n>Password</label>
-            <input type="password" 
+            <input 
+              type="password" 
               class="form-control"
               id="password" 
               name="password"
index fa96cfa..4c544d7 100644 (file)
@@ -1,5 +1,5 @@
 import { Component, OnInit, Renderer } from '@angular/core';
-import { EgAuthService } from '@eg/core/eg-auth.service';
+import { EgAuthService } from '@eg/core/auth.service';
 
 @Component({
   templateUrl : './login.component.html'
@@ -14,6 +14,8 @@ export class EgStaffLoginComponent implements OnInit {
       workstation :  'BR1-skiddoo'
     };
 
+    workstations = [];
+
     constructor(
       private egAuth: EgAuthService,
       private renderer: Renderer
@@ -22,10 +24,13 @@ export class EgStaffLoginComponent implements OnInit {
     ngOnInit() {
       // Focus username
       this.renderer.selectRootElement('#username').focus();
+
+      // load workstations...
     }
 
     handleSubmit() {
       this.egAuth.login(this.args).then(
+        // redirect to desire page.
         res => console.log('Authtoken: ' + this.egAuth.token())
       );
     }
index 47c8ba2..e7a7065 100644 (file)
@@ -16,13 +16,15 @@ export class EgStaffResolver implements Resolve<any> {
         route: ActivatedRouteSnapshot, 
         state: RouterStateSnapshot): Observable<any> {
 
-        // async placeholder for startup routines
+        console.debug('EgStaffResolver:resolve()');
+
+        // TODO: check auth session
+
+        // async placeholder for staff startup routines
         return Observable.create(
             observer => {
                 observer.next(123);
-                console.debug('completing EgStaffRouteResolver');
                 observer.complete();
-                console.debug('completed EgStaffRouteResolver');
             }
         );
     }
index 85452c7..55c2c68 100644 (file)
@@ -7,8 +7,6 @@ import { EgStaffResolver }       from './staff-resolver.service';
 const routes: Routes = [
   { path: '',
     component: EgStaffComponent,
-    // base resolver.  Called first and guaranteed to 
-    // complete before any child resolvers are started.
     resolve : {startup : EgStaffResolver},
     children : [
         {