pass in the cors proxy endpoint

This commit is contained in:
Jason Kulatunga 2022-10-29 13:07:19 -07:00
parent 6dc835d080
commit 8d7ddddd78
19 changed files with 85 additions and 65 deletions

View File

@ -4,6 +4,6 @@ export class SourceSyncMessage {
source: Source
current_user: string
couchdb_endpoint_base: string
fasten_api_endpoint_base: string
response?: any
}

View File

@ -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<string> = of(JSON.stringify(sourceSync));
return fromWorker<string, string>(() => new Worker(new URL('./source-sync.worker', import.meta.url), {type: 'module'}), input$)
// .subscribe(message => {

View File

@ -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<string, string> {
public work(input$: Observable<string>): Observable<string> {
@ -19,7 +21,10 @@ export class SourceSyncWorker implements DoWork<string, string> {
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()

View File

@ -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:

View File

@ -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);
}
/**

View File

@ -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);
}
/**

View File

@ -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/`
}
}

View File

@ -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);
}
/**

View File

@ -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);
}
/**

View File

@ -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")
}

View File

@ -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")
}

View File

@ -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);
}
}

View File

@ -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")
}

View File

@ -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")
}

View File

@ -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);
}
}

View File

@ -46,6 +46,4 @@ export interface IDatabaseRepository {
GetResource(resource_id: string): Promise<ResourceFhir>
GetResources(): Promise<IDatabasePaginatedResponse>
GetResourcesForSource(source_id: string, source_resource_type?: string): Promise<IDatabasePaginatedResponse>
GetEndpointAbsolutePath(currentUrl: {pathname: string, protocol: string, host: string}, relativePath: string): string
}

View File

@ -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}`
}
}

View File

@ -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
}

View File

@ -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}`
}