diff --git a/frontend/src/app/models/queue/source-sync-message.ts b/frontend/src/app/models/queue/source-sync-message.ts index d459855f..2fcbbe4e 100644 --- a/frontend/src/app/models/queue/source-sync-message.ts +++ b/frontend/src/app/models/queue/source-sync-message.ts @@ -4,6 +4,6 @@ export class SourceSyncMessage { source: Source current_user: string couchdb_endpoint_base: string - + fasten_api_endpoint_base: string response?: any } diff --git a/frontend/src/app/workers/queue.service.ts b/frontend/src/app/workers/queue.service.ts index fffbcd2d..a16d77e6 100644 --- a/frontend/src/app/workers/queue.service.ts +++ b/frontend/src/app/workers/queue.service.ts @@ -21,6 +21,7 @@ export class QueueService { sourceSync.source = source sourceSync.current_user = this.fastenDbService.current_user sourceSync.couchdb_endpoint_base = environment.couchdb_endpoint_base + sourceSync.fasten_api_endpoint_base = environment.fasten_api_endpoint_base const input$: Observable = of(JSON.stringify(sourceSync)); return fromWorker(() => new Worker(new URL('./source-sync.worker', import.meta.url), {type: 'module'}), input$) // .subscribe(message => { diff --git a/frontend/src/app/workers/source-sync.worker.ts b/frontend/src/app/workers/source-sync.worker.ts index 48480a0f..4a24fe12 100644 --- a/frontend/src/app/workers/source-sync.worker.ts +++ b/frontend/src/app/workers/source-sync.worker.ts @@ -7,6 +7,8 @@ import {SourceSyncMessage} from '../models/queue/source-sync-message'; import {NewPouchdbRepositoryWebWorker, PouchdbRepository} from '../../lib/database/pouchdb_repository'; import {NewClient} from '../../lib/conduit/factory'; import {Source} from '../../lib/models/database/source'; +import {ClientConfig} from '../../lib/models/client/client-config'; +import {client} from 'fhirclient'; export class SourceSyncWorker implements DoWork { public work(input$: Observable): Observable { @@ -19,7 +21,10 @@ export class SourceSyncWorker implements DoWork { const sourceSyncMessage = JSON.parse(msg) as SourceSyncMessage const db = NewPouchdbRepositoryWebWorker(sourceSyncMessage.current_user, sourceSyncMessage.couchdb_endpoint_base) - const client = NewClient(sourceSyncMessage.source.source_type, new Source(sourceSyncMessage.source)) + + let clientConfig = new ClientConfig() + clientConfig.fasten_api_endpoint_base = sourceSyncMessage.fasten_api_endpoint_base + const client = NewClient(sourceSyncMessage.source.source_type, new Source(sourceSyncMessage.source), clientConfig) //TODO: validate the FHIR version from the datasource matches the client // if the source token has been refreshed, we need to store it in the DB. // await db.UpsertSource() diff --git a/frontend/src/lib/conduit/factory.ts b/frontend/src/lib/conduit/factory.ts index 7799c69e..cd3a5dbb 100644 --- a/frontend/src/lib/conduit/factory.ts +++ b/frontend/src/lib/conduit/factory.ts @@ -10,28 +10,29 @@ import {CignaClient} from './fhir/cigna_client'; import {EpicClient} from './fhir/epic_client'; import {HealthITClient} from './fhir/healthit_client'; import {LogicaClient} from './fhir/logica_client'; +import {ClientConfig} from '../models/client/client-config'; -export function NewClient(sourceType: SourceType, source: Source): IClient { +export function NewClient(sourceType: SourceType, source: Source, clientConfig: ClientConfig): IClient { switch(sourceType) { case SourceType.Aetna: - return new AetnaClient(source) + return new AetnaClient(source, clientConfig) case SourceType.Athena: - return new AthenaClient(source) + return new AthenaClient(source, clientConfig) case SourceType.BlueButtonMedicare: - return new BlueButtonClient(source) + return new BlueButtonClient(source, clientConfig) case SourceType.CareEvolution: - return new CareEvolutionClient(source) + return new CareEvolutionClient(source, clientConfig) case SourceType.Cerner: - return new CernerClient(source) + return new CernerClient(source, clientConfig) case SourceType.Cigna: - return new CignaClient(source) + return new CignaClient(source, clientConfig) case SourceType.Epic: - return new EpicClient(source) + return new EpicClient(source, clientConfig) case SourceType.HealthIT: - return new HealthITClient(source) + return new HealthITClient(source, clientConfig) case SourceType.Logica: - return new LogicaClient(source) + return new LogicaClient(source, clientConfig ) // case SourceType.Manual: // return new ManualClient(source) default: diff --git a/frontend/src/lib/conduit/fhir/aetna_client.ts b/frontend/src/lib/conduit/fhir/aetna_client.ts index fff74203..0d3ba5d1 100644 --- a/frontend/src/lib/conduit/fhir/aetna_client.ts +++ b/frontend/src/lib/conduit/fhir/aetna_client.ts @@ -3,10 +3,11 @@ import {FHIR401Client} from './base/fhir401_r4_client'; import {Source} from '../../models/database/source'; import {IDatabaseRepository} from '../../database/interface'; import {UpsertSummary} from '../../models/fasten/upsert-summary'; +import {ClientConfig} from '../../models/client/client-config'; export class AetnaClient extends FHIR401Client implements IClient { - constructor(source: Source) { - super(source); + constructor(source: Source, clientConfig: ClientConfig) { + super(source, clientConfig); } /** diff --git a/frontend/src/lib/conduit/fhir/athena_client.ts b/frontend/src/lib/conduit/fhir/athena_client.ts index 9c471a64..85a658b1 100644 --- a/frontend/src/lib/conduit/fhir/athena_client.ts +++ b/frontend/src/lib/conduit/fhir/athena_client.ts @@ -3,10 +3,11 @@ import {FHIR401Client} from './base/fhir401_r4_client'; import {Source} from '../../models/database/source'; import {IDatabaseRepository} from '../../database/interface'; import {UpsertSummary} from '../../models/fasten/upsert-summary'; +import {ClientConfig} from '../../models/client/client-config'; export class AthenaClient extends FHIR401Client implements IClient { - constructor(source: Source) { - super(source); + constructor(source: Source, clientConfig: ClientConfig) { + super(source, clientConfig); } /** diff --git a/frontend/src/lib/conduit/fhir/base/base_client.ts b/frontend/src/lib/conduit/fhir/base/base_client.ts index 221c7be7..2b563ee2 100644 --- a/frontend/src/lib/conduit/fhir/base/base_client.ts +++ b/frontend/src/lib/conduit/fhir/base/base_client.ts @@ -1,6 +1,8 @@ import {Source} from '../../../models/database/source'; import * as Oauth from '@panva/oauth4webapi'; import {IResourceRaw} from '../../interface'; +import {GetEndpointAbsolutePath} from '../../../utils/endpoint_absolute_path'; +import {ClientConfig} from '../../../models/client/client-config'; class SourceUpdateStatus { @@ -11,13 +13,16 @@ class SourceUpdateStatus { // BaseClient is an abstract/partial class, its intended to be used by FHIR clients, and generically handle OAuth requests. export abstract class BaseClient { + private clientConfig: ClientConfig private oauthClient: Oauth.Client private oauthAuthorizationServer: Oauth.AuthorizationServer public source: Source public headers: Headers - protected constructor(source: Source) { + + protected constructor(source: Source, clientConfig: ClientConfig) { this.source = source + this.clientConfig = clientConfig this.headers = new Headers() //init Oauth client based on source configuration @@ -65,7 +70,7 @@ export abstract class BaseClient { //this endpoint requires a CORS relay //get the path to the Fasten server, and append `cors/` and then append the request url let resourceParts = new URL(resourceUrl) - resourceUrl = this.getCORSProxyPath() + `${resourceParts.hostname}${resourceParts.pathname}${resourceParts.search}` + resourceUrl = GetEndpointAbsolutePath(globalThis.location, this.clientConfig.fasten_api_endpoint_base) + `/${resourceParts.hostname}${resourceParts.pathname}${resourceParts.search}` } @@ -144,11 +149,4 @@ export abstract class BaseClient { return sourceUpdateStatus }) } - - private getCORSProxyPath(): string { - //TODO: this path should be passed in as a variable - const basePath = globalThis.location.pathname.split('/web').slice(0, 1)[0]; - - return `${globalThis.location.origin}${basePath || '/'}cors/` - } } diff --git a/frontend/src/lib/conduit/fhir/base/fhir401_r4_client.ts b/frontend/src/lib/conduit/fhir/base/fhir401_r4_client.ts index fa0e59a4..e9afecd8 100644 --- a/frontend/src/lib/conduit/fhir/base/fhir401_r4_client.ts +++ b/frontend/src/lib/conduit/fhir/base/fhir401_r4_client.ts @@ -4,14 +4,15 @@ import {Source} from '../../../models/database/source'; import {IDatabaseRepository} from '../../../database/interface'; import {ResourceFhir} from '../../../models/database/resource_fhir'; import {UpsertSummary} from '../../../models/fasten/upsert-summary'; +import {ClientConfig} from '../../../models/client/client-config'; export class FHIR401Client extends BaseClient implements IClient { //clients extending this class must validate fhirVersion matches using conformance/metadata url. fhirVersion = "4.0.1" - constructor(source: Source) { - super(source); + constructor(source: Source, clientConfig: ClientConfig) { + super(source, clientConfig); } /** diff --git a/frontend/src/lib/conduit/fhir/bluebutton_client.ts b/frontend/src/lib/conduit/fhir/bluebutton_client.ts index c4dc3074..16f67ce5 100644 --- a/frontend/src/lib/conduit/fhir/bluebutton_client.ts +++ b/frontend/src/lib/conduit/fhir/bluebutton_client.ts @@ -3,10 +3,11 @@ import {FHIR401Client} from './base/fhir401_r4_client'; import {Source} from '../../models/database/source'; import {IDatabaseRepository} from '../../database/interface'; import {UpsertSummary} from '../../models/fasten/upsert-summary'; +import {ClientConfig} from '../../models/client/client-config'; export class BlueButtonClient extends FHIR401Client implements IClient { - constructor(source: Source) { - super(source); + constructor(source: Source, clientConfig: ClientConfig) { + super(source, clientConfig); } /** diff --git a/frontend/src/lib/conduit/fhir/careevolution_client.ts b/frontend/src/lib/conduit/fhir/careevolution_client.ts index 3be4d358..3adbf9df 100644 --- a/frontend/src/lib/conduit/fhir/careevolution_client.ts +++ b/frontend/src/lib/conduit/fhir/careevolution_client.ts @@ -1,10 +1,11 @@ import {IClient} from '../interface'; import {FHIR401Client} from './base/fhir401_r4_client'; import {Source} from '../../models/database/source'; +import {ClientConfig} from '../../models/client/client-config'; export class CareEvolutionClient extends FHIR401Client implements IClient { - constructor(source: Source) { - super(source); + constructor(source: Source, clientConfig: ClientConfig) { + super(source, clientConfig); //CareEvolution API requires the following Accept header for every request this.headers.set("Accept","application/json+fhir") } diff --git a/frontend/src/lib/conduit/fhir/cerner_client.ts b/frontend/src/lib/conduit/fhir/cerner_client.ts index 3d5147bf..dbe883e4 100644 --- a/frontend/src/lib/conduit/fhir/cerner_client.ts +++ b/frontend/src/lib/conduit/fhir/cerner_client.ts @@ -3,10 +3,11 @@ import {FHIR401Client} from './base/fhir401_r4_client'; import {Source} from '../../models/database/source'; import {IDatabaseRepository} from '../../database/interface'; import {UpsertSummary} from '../../models/fasten/upsert-summary'; +import {ClientConfig} from '../../models/client/client-config'; export class CernerClient extends FHIR401Client implements IClient { - constructor(source: Source) { - super(source); + constructor(source: Source, clientConfig: ClientConfig) { + super(source, clientConfig); //Cerner API requires the following Accept header for every request this.headers.set("Accept","application/json+fhir") } diff --git a/frontend/src/lib/conduit/fhir/cigna_client.ts b/frontend/src/lib/conduit/fhir/cigna_client.ts index 7e2428bb..e5d02b6b 100644 --- a/frontend/src/lib/conduit/fhir/cigna_client.ts +++ b/frontend/src/lib/conduit/fhir/cigna_client.ts @@ -1,10 +1,11 @@ import {IClient} from '../interface'; import {FHIR401Client} from './base/fhir401_r4_client'; import {Source} from '../../models/database/source'; +import {ClientConfig} from '../../models/client/client-config'; export class CignaClient extends FHIR401Client implements IClient { - constructor(source: Source) { - super(source); + constructor(source: Source, clientConfig: ClientConfig) { + super(source, clientConfig); } } diff --git a/frontend/src/lib/conduit/fhir/epic_client.ts b/frontend/src/lib/conduit/fhir/epic_client.ts index f7348b88..611b5cfc 100644 --- a/frontend/src/lib/conduit/fhir/epic_client.ts +++ b/frontend/src/lib/conduit/fhir/epic_client.ts @@ -3,10 +3,11 @@ import {FHIR401Client} from './base/fhir401_r4_client'; import {Source} from '../../models/database/source'; import {IDatabaseRepository} from '../../database/interface'; import {UpsertSummary} from '../../models/fasten/upsert-summary'; +import {ClientConfig} from '../../models/client/client-config'; export class EpicClient extends FHIR401Client implements IClient { - constructor(source: Source) { - super(source); + constructor(source: Source, clientConfig: ClientConfig) { + super(source, clientConfig); //Epic API requires the following Accept header for every request this.headers.set("Accept","application/json+fhir") } diff --git a/frontend/src/lib/conduit/fhir/healthit_client.ts b/frontend/src/lib/conduit/fhir/healthit_client.ts index 24c47547..2b6a194c 100644 --- a/frontend/src/lib/conduit/fhir/healthit_client.ts +++ b/frontend/src/lib/conduit/fhir/healthit_client.ts @@ -3,10 +3,11 @@ import {FHIR401Client} from './base/fhir401_r4_client'; import {Source} from '../../models/database/source'; import {IDatabaseRepository} from '../../database/interface'; import {UpsertSummary} from '../../models/fasten/upsert-summary'; +import {ClientConfig} from '../../models/client/client-config'; export class HealthITClient extends FHIR401Client implements IClient { - constructor(source: Source) { - super(source); + constructor(source: Source, clientConfig: ClientConfig) { + super(source, clientConfig); //HealthIT API requires the following Accept header for every request this.headers.set("Accept","application/json+fhir") } diff --git a/frontend/src/lib/conduit/fhir/logica_client.ts b/frontend/src/lib/conduit/fhir/logica_client.ts index 1ab3ac6b..f0b290d1 100644 --- a/frontend/src/lib/conduit/fhir/logica_client.ts +++ b/frontend/src/lib/conduit/fhir/logica_client.ts @@ -2,9 +2,10 @@ import {IClient} from '../interface'; import {FHIR401Client} from './base/fhir401_r4_client'; import {Source} from '../../models/database/source'; import {IDatabaseRepository} from '../../database/interface'; +import {ClientConfig} from '../../models/client/client-config'; export class LogicaClient extends FHIR401Client implements IClient { - constructor(source: Source) { - super(source); + constructor(source: Source, clientConfig: ClientConfig) { + super(source, clientConfig); } } diff --git a/frontend/src/lib/database/interface.ts b/frontend/src/lib/database/interface.ts index 8f21716d..6c40c22f 100644 --- a/frontend/src/lib/database/interface.ts +++ b/frontend/src/lib/database/interface.ts @@ -46,6 +46,4 @@ export interface IDatabaseRepository { GetResource(resource_id: string): Promise GetResources(): Promise GetResourcesForSource(source_id: string, source_resource_type?: string): Promise - - GetEndpointAbsolutePath(currentUrl: {pathname: string, protocol: string, host: string}, relativePath: string): string } diff --git a/frontend/src/lib/database/pouchdb_repository.ts b/frontend/src/lib/database/pouchdb_repository.ts index 28a9bee0..d32f7bdd 100644 --- a/frontend/src/lib/database/pouchdb_repository.ts +++ b/frontend/src/lib/database/pouchdb_repository.ts @@ -4,6 +4,7 @@ import {DocType} from './constants'; import {ResourceFhir} from '../models/database/resource_fhir'; import {ResourceTypeCounts, SourceSummary} from '../models/fasten/source-summary'; import {Base64} from '../utils/base64'; +import {GetEndpointAbsolutePath} from '../utils/endpoint_absolute_path'; // PouchDB & plugins import * as PouchDB from 'pouchdb/dist/pouchdb'; @@ -18,6 +19,7 @@ import {PouchdbUpsert} from './plugins/upsert'; import {UpsertSummary} from '../models/fasten/upsert-summary'; import {PouchdbCryptConfig, PouchdbCrypto, PouchdbCryptoOptions} from './plugins/crypto'; +import {utils} from 'protractor'; // !!!!!!!!!!!!!!!!WARNING!!!!!!!!!!!!!!!!!!!!! // most pouchdb plugins seem to fail when used in a webworker. @@ -80,7 +82,7 @@ export class PouchdbRepository implements IDatabaseRepository { this.remotePouchEndpoint = couchDbEndpointBase } else { //relative, we need to retrive the absolutePath from base - this.remotePouchEndpoint = this.GetEndpointAbsolutePath(globalThis.location, couchDbEndpointBase) + this.remotePouchEndpoint = GetEndpointAbsolutePath(globalThis.location, couchDbEndpointBase) } //setup PouchDB Plugins @@ -446,25 +448,4 @@ export class PouchdbRepository implements IDatabaseRepository { } return h } - - /////////////////////////////////////////////////////////////////////////////////////// - // Helper methods - /////////////////////////////////////////////////////////////////////////////////////// - - //Fasten may be served behind a reverse proxy with a subpath, so lets try to find that component if it exists. - // if no subpath is found, just use the current url information to generate a path - public GetEndpointAbsolutePath(currentUrl: {pathname: string, protocol: string, host: string}, relativePath: string): string { - //no `/web` path to strip out, lets just use the relative path - let absolutePath = relativePath - - if(currentUrl.pathname.includes('/web')){ - // probably running locally, and *may* include a subpath - let subPath = currentUrl.pathname.split('/web').slice(0, 1)[0] - if(subPath != "/"){ - //subpath, so we need to update the absolutePath with the subpath before adding the relative path to the end - absolutePath = subPath + relativePath - } - } - return `${currentUrl.protocol}//${currentUrl.host}${absolutePath}` - } } diff --git a/frontend/src/lib/models/client/client-config.ts b/frontend/src/lib/models/client/client-config.ts new file mode 100644 index 00000000..53ad0d72 --- /dev/null +++ b/frontend/src/lib/models/client/client-config.ts @@ -0,0 +1,6 @@ +import {LighthouseSourceMetadata} from '../lighthouse/lighthouse-source-metadata'; +import {SourceType} from '../database/source_types'; + +export class ClientConfig { + fasten_api_endpoint_base: string +} diff --git a/frontend/src/lib/utils/endpoint_absolute_path.ts b/frontend/src/lib/utils/endpoint_absolute_path.ts new file mode 100644 index 00000000..c9bc6cd5 --- /dev/null +++ b/frontend/src/lib/utils/endpoint_absolute_path.ts @@ -0,0 +1,20 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// Helper methods +/////////////////////////////////////////////////////////////////////////////////////// + +//Fasten may be served behind a reverse proxy with a subpath, so lets try to find that component if it exists. +// if no subpath is found, just use the current url information to generate a path +export function GetEndpointAbsolutePath(currentUrl: {pathname: string, protocol: string, host: string}, relativePath: string): string { + //no `/web` path to strip out, lets just use the relative path + let absolutePath = relativePath + + if(currentUrl.pathname.includes('/web')){ + // probably running locally, and *may* include a subpath + let subPath = currentUrl.pathname.split('/web').slice(0, 1)[0] + if(subPath != "/"){ + //subpath, so we need to update the absolutePath with the subpath before adding the relative path to the end + absolutePath = subPath + relativePath + } + } + return `${currentUrl.protocol}//${currentUrl.host}${absolutePath}` +}