diff --git a/frontend/src/app/components/medical-sources-connected/medical-sources-connected.component.html b/frontend/src/app/components/medical-sources-connected/medical-sources-connected.component.html
index 6b84bbc8..3451137c 100644
--- a/frontend/src/app/components/medical-sources-connected/medical-sources-connected.component.html
+++ b/frontend/src/app/components/medical-sources-connected/medical-sources-connected.component.html
@@ -5,7 +5,7 @@
diff --git a/frontend/src/app/components/medical-sources-connected/medical-sources-connected.component.ts b/frontend/src/app/components/medical-sources-connected/medical-sources-connected.component.ts
index ae07aab4..ab735bab 100644
--- a/frontend/src/app/components/medical-sources-connected/medical-sources-connected.component.ts
+++ b/frontend/src/app/components/medical-sources-connected/medical-sources-connected.component.ts
@@ -10,6 +10,7 @@ import {ToastNotification, ToastType} from '../../models/fasten/toast';
import {ToastService} from '../../services/toast.service';
import {ActivatedRoute, Router} from '@angular/router';
import {Location} from '@angular/common';
+import {EventBusService} from '../../services/event-bus.service';
@Component({
selector: 'app-medical-sources-connected',
@@ -33,6 +34,7 @@ export class MedicalSourcesConnectedComponent implements OnInit {
private activatedRoute: ActivatedRoute,
private router: Router,
private location: Location,
+ private eventBusService: EventBusService,
) { }
ngOnInit(): void {
@@ -64,6 +66,9 @@ export class MedicalSourcesConnectedComponent implements OnInit {
.then(console.log)
}
+ this.eventBusService.eventBusSourceSyncMessages.subscribe((event) => {
+ this.status[event.source_id] = "token"
+ })
}
@@ -171,6 +176,7 @@ export class MedicalSourcesConnectedComponent implements OnInit {
.subscribe((resp) => {
// const sourceSyncMessage = JSON.parse(msg) as SourceSyncMessage
delete this.status[sourceType]
+ delete this.status[resp.source.id]
// window.location.reload();
// this.connectedSourceList.
@@ -269,7 +275,7 @@ export class MedicalSourcesConnectedComponent implements OnInit {
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public openModal(contentModalRef, sourceListItem: SourceListItem) {
- if(this.status[sourceListItem.metadata.source_type] || !sourceListItem.source){
+ if(this.status[sourceListItem.metadata.source_type] || !sourceListItem.source || this.status[sourceListItem.source.id]){
//if this source is currently "loading" dont open the modal window
return
}
@@ -287,15 +293,17 @@ export class MedicalSourcesConnectedComponent implements OnInit {
public sourceSyncHandler(source: Source){
- this.status[source.source_type] = "authorize"
+ this.status[source.id] = "authorize"
this.modalService.dismissAll()
this.fastenApi.syncSource(source.id).subscribe(
(respData) => {
+ delete this.status[source.id]
delete this.status[source.source_type]
console.log("source sync response:", respData)
},
(err) => {
+ delete this.status[source.id]
delete this.status[source.source_type]
console.log(err)
}
diff --git a/frontend/src/app/models/events/event.ts b/frontend/src/app/models/events/event.ts
new file mode 100644
index 00000000..ebdf30dd
--- /dev/null
+++ b/frontend/src/app/models/events/event.ts
@@ -0,0 +1,3 @@
+export interface Event {
+ event_type: string;
+}
diff --git a/frontend/src/app/models/events/event_source_complete.ts b/frontend/src/app/models/events/event_source_complete.ts
new file mode 100644
index 00000000..52d4393e
--- /dev/null
+++ b/frontend/src/app/models/events/event_source_complete.ts
@@ -0,0 +1,4 @@
+export interface EventSourceComplete extends Event {
+ event_type: string;
+ source_id: string;
+}
diff --git a/frontend/src/app/models/events/event_source_sync.ts b/frontend/src/app/models/events/event_source_sync.ts
new file mode 100644
index 00000000..81a63794
--- /dev/null
+++ b/frontend/src/app/models/events/event_source_sync.ts
@@ -0,0 +1,6 @@
+export interface EventSourceSync extends Event {
+ event_type: string;
+ source_id: string;
+ resource_type: string;
+ resource_id: string;
+}
diff --git a/frontend/src/app/services/auth.service.ts b/frontend/src/app/services/auth.service.ts
index 1c365461..ec527a57 100644
--- a/frontend/src/app/services/auth.service.ts
+++ b/frontend/src/app/services/auth.service.ts
@@ -10,6 +10,7 @@ import * as jose from 'jose';
import {UserRegisteredClaims} from '../models/fasten/user-registered-claims';
import {uuidV4} from '../../lib/utils/uuid';
import {HTTP_CLIENT_TOKEN} from "../dependency-injection";
+import {BehaviorSubject, Observable, Subject} from 'rxjs';
@Injectable({
providedIn: 'root'
@@ -17,7 +18,7 @@ import {HTTP_CLIENT_TOKEN} from "../dependency-injection";
export class AuthService {
FASTEN_JWT_LOCALSTORAGE_KEY = 'token';
-
+ public IsAuthenticatedSubject = new BehaviorSubject(false)
constructor(@Inject(HTTP_CLIENT_TOKEN) private _httpClient: HttpClient) {
}
@@ -129,17 +130,21 @@ export class AuthService {
this.setAuthToken(resp.data)
}
-
//TODO: now that we've moved to remote-first database, we can refactor and simplify this function significantly.
public async IsAuthenticated(): Promise {
let authToken = this.GetAuthToken()
let hasAuthToken = !!authToken
if(!hasAuthToken){
+ this.publishAuthenticationState(false)
return false
}
- //todo: check if the authToken has expired
- return true
+ //check if the authToken has expired
+ let jwtClaims = jose.decodeJwt(authToken)
+ let valid = Date.now() < (jwtClaims.exp * 1000);
+ this.publishAuthenticationState(valid)
+ return valid
+
// //check if the authToken has expired.
// let databaseEndpointBase = GetEndpointAbsolutePath(globalThis.location, environment.couchdb_endpoint_base)
@@ -180,6 +185,7 @@ export class AuthService {
}
public async Logout(): Promise {
+ this.publishAuthenticationState(false)
return localStorage.removeItem(this.FASTEN_JWT_LOCALSTORAGE_KEY)
// // let remotePouchDb = new PouchDB(this.getRemoteUserDb(localStorage.getItem("current_user")), {skip_setup: true});
// if(this.pouchDb){
@@ -192,6 +198,13 @@ export class AuthService {
/////////////////////////////////////////////////////////////////////////////////////////////////
private setAuthToken(token: string) {
+ this.publishAuthenticationState(true)
localStorage.setItem(this.FASTEN_JWT_LOCALSTORAGE_KEY, token)
}
+
+ private publishAuthenticationState(authenticated){
+ if(this.IsAuthenticatedSubject.value != authenticated){
+ this.IsAuthenticatedSubject.next(authenticated)
+ }
+ }
}
diff --git a/frontend/src/app/services/event-bus.service.spec.ts b/frontend/src/app/services/event-bus.service.spec.ts
new file mode 100644
index 00000000..574fc894
--- /dev/null
+++ b/frontend/src/app/services/event-bus.service.spec.ts
@@ -0,0 +1,16 @@
+import { TestBed } from '@angular/core/testing';
+
+import { EventBusService } from './event-bus.service';
+
+describe('EventBusService', () => {
+ let service: EventBusService;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({});
+ service = TestBed.inject(EventBusService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/services/event-bus.service.ts b/frontend/src/app/services/event-bus.service.ts
new file mode 100644
index 00000000..14c0f01e
--- /dev/null
+++ b/frontend/src/app/services/event-bus.service.ts
@@ -0,0 +1,48 @@
+import {AfterViewInit, Injectable, OnInit} from '@angular/core';
+import {NavigationEnd, Router} from '@angular/router';
+import {BehaviorSubject, Observable, Subject, Subscription} from 'rxjs';
+import {AuthService} from './auth.service';
+import {FastenApiService} from './fasten-api.service';
+import {ToastService} from './toast.service';
+import {Event} from '../models/events/event';
+import {EventSourceComplete} from '../models/events/event_source_complete';
+import {EventSourceSync} from '../models/events/event_source_sync';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class EventBusService {
+ eventBusSubscription: Subscription | undefined;
+ eventBusSourceSyncMessages: Subject = new Subject();
+ eventBusSourceCompleteMessages: Subject = new Subject();
+
+ constructor(
+ public router: Router,
+ public authService: AuthService,
+ public fastenApiService: FastenApiService,
+ public toastService: ToastService
+ ) {
+
+ // Ideally we want consistently listen to events, but only when the user is authenticated.
+ //TODO: find a way to abort the event bus connection.
+ this.authService.IsAuthenticatedSubject.subscribe((isAuthenticated) => {
+ console.log("isAuthenticated changed:", isAuthenticated)
+ if(isAuthenticated){
+ this.eventBusSubscription = this.fastenApiService.listenEventBus().subscribe((event: Event | EventSourceSync | EventSourceComplete)=>{
+ console.log("eventbus event:", event)
+ //TODO: start toasts.
+ if(event.event_type == "source_sync"){
+ this.eventBusSourceSyncMessages.next(event as EventSourceSync)
+ } else if(event.event_type == "source_complete"){
+ this.eventBusSourceCompleteMessages.next(event as EventSourceComplete)
+ }
+ })
+ } else {
+ //no longer authenticated, unsubscribe from eventbus
+ if(this.eventBusSubscription){
+ this.eventBusSubscription.unsubscribe()
+ }
+ }
+ });
+ }
+}
diff --git a/frontend/src/app/services/fasten-api.service.ts b/frontend/src/app/services/fasten-api.service.ts
index 93184be2..5866b914 100644
--- a/frontend/src/app/services/fasten-api.service.ts
+++ b/frontend/src/app/services/fasten-api.service.ts
@@ -65,7 +65,7 @@ export class FastenApiService {
'Authorization': `Bearer ${this.authService.GetAuthToken()}`
},
onmessage(ev) {
- observer.next(ev.data);
+ observer.next(JSON.parse(ev.data));
},
onerror(event) {
observer.error(event)