using endpoint id and brand id everywhere.

brand id is used for logo specification.
This commit is contained in:
Jason Kulatunga 2024-01-16 22:29:43 -08:00
parent dec1914b91
commit 06aed9c17a
No known key found for this signature in database
23 changed files with 1331 additions and 226 deletions

View File

@ -215,6 +215,33 @@
"maximumError": "10kb"
}
]
},
"offline_sandbox": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.offline_sandbox.ts"
}
],
"optimization": false,
"outputHashing": "all",
"sourceMap": true,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": false,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "30mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
}
]
}
}
},
@ -236,6 +263,9 @@
"desktop_sandbox": {
"browserTarget": "fastenhealth:build:desktop_sandbox"
},
"offline_sandbox": {
"browserTarget": "fastenhealth:build:offline_sandbox"
},
"desktop_prod": {
"browserTarget": "fastenhealth:build:desktop_prod"
}

View File

@ -35,10 +35,10 @@ const routes: Routes = [
{ path: 'explore/:source_id/resource/:resource_type/:resource_id', component: ResourceDetailComponent, canActivate: [ IsAuthenticatedAuthGuard] },
{ path: 'sources', component: MedicalSourcesComponent, canActivate: [ IsAuthenticatedAuthGuard] },
{ path: 'sources/callback/:source_type', component: MedicalSourcesComponent, canActivate: [ IsAuthenticatedAuthGuard] },
{ path: 'sources/callback/:state', component: MedicalSourcesComponent, canActivate: [ IsAuthenticatedAuthGuard] },
{ path: 'resource/create', component: ResourceCreatorComponent, canActivate: [ IsAuthenticatedAuthGuard] },
{ path: 'desktop/callback/:source_id', component: DesktopCallbackComponent, canActivate: [ IsAuthenticatedAuthGuard] },
{ path: 'desktop/callback/:state', component: DesktopCallbackComponent, canActivate: [ IsAuthenticatedAuthGuard] },
{ path: 'background-jobs', component: BackgroundJobsComponent, canActivate: [ IsAuthenticatedAuthGuard] },
{ path: 'patient-profile', component: PatientProfileComponent, canActivate: [ IsAuthenticatedAuthGuard] },

View File

@ -1,8 +1,8 @@
<div class="card h-100 d-flex align-items-center justify-content-center mt-3 mt-3 rounded-0 cursor-pointer" [ngClass]="{'card-disable': sourceInfo?.metadata?.hidden}">
<div class="card h-100 d-flex align-items-center justify-content-center mt-3 mt-3 rounded-0 cursor-pointer" [ngClass]="{'card-disable': sourceInfo?.brand?.hidden}">
<div (click)="onCardClick()" class="card-body" [class.border-left-danger]="status == 'failed'">
<div class="h-100 d-flex align-items-center">
<img imageFallback [src]="'assets/sources/'+(sourceInfo?.metadata.brand_logo ? sourceInfo?.metadata?.brand_logo : sourceInfo?.metadata?.source_type+'.png')" [alt]="sourceInfo?.metadata?.display" class="img-fluid">
<img imageFallback [src]="'assets/sources/'+((sourceInfo?.brand?.id || sourceInfo?.source?.brand_id )+'.png')" [alt]="sourceInfo?.brand?.name" class="img-fluid">
<div *ngIf="status == 'failed'" class="card-img-overlay">
<span class="badge badge-danger">failed</span>
</div>
@ -13,7 +13,7 @@
</div>
<div class="card-footer text-center p-1" style="width:100%">
<small class="tx-gray-700">
{{sourceInfo?.metadata?.display || getSourceDisplayName(sourceInfo)}}
{{getSourceDisplayName(sourceInfo)}}
</small>
</div>
</div>

View File

@ -25,14 +25,17 @@ export class MedicalSourcesCardComponent implements OnInit {
getSourceDisplayName(sourceItem: SourceListItem): string {
if(!sourceItem) return "Unknown"
if(sourceItem.metadata?.display) {
return sourceItem.metadata?.display
if(sourceItem.source?.display) {
return sourceItem.source?.display
}
if(sourceItem.source?.source_type == 'manual') {
if(sourceItem.source?.platform_type == 'manual') {
return 'Uploaded ' + moment(sourceItem.source?.created_at).format('MMM DD, YYYY')
} else if(sourceItem.source?.source_type == 'fasten'){
} else if(sourceItem.source?.platform_type == 'fasten'){
return 'Fasten Health'
}
if(sourceItem.brand?.name) {
return sourceItem.brand?.name
}
return "Unknown"
}

View File

@ -4,7 +4,7 @@
<app-medical-sources-card class="col-sm-3 mg-b-20 px-3"
*ngFor="let sourceData of connectedSourceList"
[sourceInfo]="sourceData"
[status]="status[sourceData.source?.id] || status[sourceData.metadata?.source_type]"
[status]="status[sourceData.source?.id] || status[sourceData.brand?.id]"
(onClick)="openModal(contentModalRef, $event)"
></app-medical-sources-card>
</div>
@ -12,13 +12,13 @@
<ng-template #contentModalRef let-modal>
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">{{modalSelectedSourceListItem?.metadata["display"]}}</h4>
<h4 class="modal-title" id="modal-basic-title">{{modalSelectedSourceListItem?.source?.display || modalSelectedSourceListItem?.brand?.name}}</h4>
<button type="button" class="btn close" aria-label="Close" (click)="modal.dismiss('Cross click')">
<span aria-hidden="true">×</span>
</button>
</div>
<div *ngIf="modalSelectedSourceListItem.source?.source_type != 'fasten'; else fastenSourceDescription" class="modal-body">
<div *ngIf="modalSelectedSourceListItem.source?.platform_type != 'fasten'; else fastenSourceDescription" class="modal-body">
<h6>Manage Source</h6>
<p>Existing connections can be "Synced", "Reconnected" or "Deleted"</p>
<ul>
@ -43,7 +43,7 @@
<a routerLink="/explore/{{modalSelectedSourceListItem?.source?.id}}" (click)="modal.close()" class="btn btn-indigo mr-auto">Explore</a>
<div *ngIf="modalSelectedSourceListItem.source?.source_type != 'fasten'" class="d-inline-block" ngbDropdown>
<div *ngIf="modalSelectedSourceListItem.source?.platform_type != 'fasten'" class="d-inline-block" ngbDropdown>
<button
type="button"
class="btn btn-outline-indigo"
@ -52,8 +52,8 @@
Actions
</button>
<div ngbDropdownMenu aria-labelledby="dropdownManual">
<button *ngIf="modalSelectedSourceListItem.source?.source_type != 'manual'" ngbDropdownItem (click)="sourceSyncHandler(modalSelectedSourceListItem.source)" type="button" class="btn btn-indigo">Sync</button>
<button *ngIf="modalSelectedSourceListItem.source?.source_type != 'manual'" ngbDropdownItem (click)="sourceReconnectHandler(modalSelectedSourceListItem)" type="button" class="btn btn-danger">Reconnect</button>
<button *ngIf="modalSelectedSourceListItem.source?.platform_type != 'manual'" ngbDropdownItem (click)="sourceSyncHandler(modalSelectedSourceListItem.source)" type="button" class="btn btn-indigo">Sync</button>
<button *ngIf="modalSelectedSourceListItem.source?.platform_type != 'manual'" ngbDropdownItem (click)="sourceReconnectHandler(modalSelectedSourceListItem)" type="button" class="btn btn-danger">Reconnect</button>
<button ngbDropdownItem (click)="sourceDeleteHandler()" type="button" class="btn btn-danger">Delete</button>
</div>
</div>

View File

@ -12,6 +12,7 @@ import {ActivatedRoute, Router} from '@angular/router';
import {Location} from '@angular/common';
import {EventBusService} from '../../services/event-bus.service';
import {SourceState} from '../../models/fasten/source-state';
import {PatientAccessBrand} from '../../models/patient-access-brands';
@Component({
selector: 'app-medical-sources-connected',
@ -45,29 +46,41 @@ export class MedicalSourcesConnectedComponent implements OnInit {
//handle connected sources sources
const connectedSources = results as Source[]
forkJoin(connectedSources.map((source) => this.lighthouseApi.getLighthouseSource(source.source_type))).subscribe((connectedMetadata) => {
for(const ndx in connectedSources){
this.connectedSourceList.push({source: connectedSources[ndx], metadata: connectedMetadata[ndx]})
if(connectedSources[ndx].latest_background_job?.job_status == "STATUS_LOCKED"){
this.status[connectedSources[ndx].source_type] = "token"
} else if (connectedSources[ndx].latest_background_job?.job_status === "STATUS_FAILED") {
this.status[connectedSources[ndx].source_type] = "failed"
}
forkJoin(connectedSources.map((source) => {
if(source.platform_type == 'fasten' || source.platform_type == 'manual') {
return this.lighthouseApi.getLighthouseCatalogBrand(source.platform_type)
} else {
return this.lighthouseApi.getLighthouseCatalogBrand(source.brand_id)
}
})
}))
.subscribe((connectedBrand) => {
for(const ndx in connectedSources){
console.log(connectedSources[ndx])
this.connectedSourceList.push({source: connectedSources[ndx], brand: connectedBrand[ndx]})
if(connectedSources[ndx].latest_background_job?.job_status == "STATUS_LOCKED"){
this.status[connectedSources[ndx].brand_id] = "token"
} else if (connectedSources[ndx].latest_background_job?.job_status === "STATUS_FAILED") {
this.status[connectedSources[ndx].brand_id] = "failed"
}
}
})
})
const callbackSourceType = this.activatedRoute.snapshot.paramMap.get('source_type')
if(callbackSourceType) {
console.log("handle callback redirect from source")
this.status[callbackSourceType] = "token"
const callbackState = this.activatedRoute.snapshot.paramMap.get('state')
if(callbackState) {
let sourceInfo = this.lighthouseApi.getSourceState(callbackState)
console.log("handle callback redirect from source", callbackState, sourceInfo)
this.status[sourceInfo.brand_id] = "token"
//the structure of "availableSourceList" vs "connectedSourceList" sources is slightly different,
//connectedSourceList contains a "source" field. The this.fastenApi.createSource() call in the callback function will set it.
this.lighthouseApi.getLighthouseSource(callbackSourceType)
.then((metadata) => {
this.connectedSourceList.push({metadata: metadata})
return this.callback(callbackSourceType)
this.lighthouseApi.getLighthouseCatalogBrand(sourceInfo.brand_id)
.then((brandInfo) => {
this.connectedSourceList.push({brand: brandInfo})
return this.callback(sourceInfo)
})
.then(console.log)
}
@ -80,12 +93,12 @@ export class MedicalSourcesConnectedComponent implements OnInit {
/**
* if the user is redirected to this page from the lighthouse, we'll need to process the "code" to retrieve the access token & refresh token.
* @param sourceType
* @param expectedSourceStateInfo
*/
public async callback(sourceType: string) {
public async callback(expectedSourceStateInfo: SourceState) {
//get the source metadata again
await this.lighthouseApi.getLighthouseSource(sourceType)
await this.lighthouseApi.getLighthouseSource(expectedSourceStateInfo.endpoint_id)
.then(async (sourceMetadata: LighthouseSourceMetadata) => {
//get required parameters from the URI and local storage
@ -93,7 +106,6 @@ export class MedicalSourcesConnectedComponent implements OnInit {
//in desktop mode, we're using fragment routing, and the callback params are in the fragment.
const fragmentParams = new URLSearchParams(callbackUrlParts.hash.split('?')?.[1] || '')
const callbackCode = callbackUrlParts.searchParams.get("code") || fragmentParams.get("code")
const callbackState = callbackUrlParts.searchParams.get("state") || fragmentParams.get("state")
const callbackError = callbackUrlParts.searchParams.get("error") || fragmentParams.get("error")
const callbackErrorDescription = callbackUrlParts.searchParams.get("error_description") || fragmentParams.get("error_description")
@ -103,8 +115,7 @@ export class MedicalSourcesConnectedComponent implements OnInit {
});
this.location.replaceState(urlTree.toString());
const expectedSourceStateInfo = JSON.parse(localStorage.getItem(callbackState)) as SourceState
localStorage.removeItem(callbackState)
localStorage.removeItem(expectedSourceStateInfo.state)
if(callbackError && !callbackCode){
//TOOD: print this message in the UI
@ -114,10 +125,10 @@ export class MedicalSourcesConnectedComponent implements OnInit {
}
console.log("callback code:", callbackCode)
this.status[sourceType] = "token"
this.status[expectedSourceStateInfo.brand_id] = "token"
let payload: any
payload = await this.lighthouseApi.swapOauthToken(sourceType, sourceMetadata,expectedSourceStateInfo, callbackCode)
payload = await this.lighthouseApi.swapOauthToken(sourceMetadata,expectedSourceStateInfo, callbackCode)
if(!payload.access_token || payload.error){
//if the access token is not set, then something is wrong,
@ -143,38 +154,26 @@ export class MedicalSourcesConnectedComponent implements OnInit {
}
//get the portal information
const portalInfo = await this.lighthouseApi.getLighthouseCatalogPortal(expectedSourceStateInfo.portal_id)
//Create FHIR Client
const dbSourceCredential = new Source({
id: expectedSourceStateInfo.reconnect_source_id,
source_type: sourceType,
authorization_endpoint: sourceMetadata.authorization_endpoint,
token_endpoint: sourceMetadata.token_endpoint,
introspection_endpoint: sourceMetadata.introspection_endpoint,
userinfo_endpoint: sourceMetadata.userinfo_endpoint,
registration_endpoint: sourceMetadata.registration_endpoint,
api_endpoint_base_url: sourceMetadata.api_endpoint_base_url,
display: portalInfo.name,
brand_id: expectedSourceStateInfo.brand_id,
portal_id: expectedSourceStateInfo.portal_id,
endpoint_id: expectedSourceStateInfo.endpoint_id,
platform_type: sourceMetadata.platform_type,
client_id: sourceMetadata.client_id,
redirect_uri: sourceMetadata.redirect_uri,
scopes_supported: sourceMetadata.scopes_supported,
issuer: sourceMetadata.issuer,
grant_types_supported: sourceMetadata.grant_types_supported,
response_types_supported: sourceMetadata.response_types_supported,
aud: sourceMetadata.aud,
code_challenge_methods_supported: sourceMetadata.code_challenge_methods_supported || [],
confidential: sourceMetadata.confidential,
cors_relay_required: sourceMetadata.cors_relay_required,
patient: payload.patient,
access_token: payload.access_token,
refresh_token: payload.refresh_token,
id_token: payload.id_token,
dynamic_client_registration_mode: sourceMetadata.dynamic_client_registration_mode,
// @ts-ignore - in some cases the getAccessTokenExpiration is a string, which cases failures to store Source in db.
expires_at: parseInt(this.getAccessTokenExpiration(payload)),
})
@ -182,20 +181,20 @@ export class MedicalSourcesConnectedComponent implements OnInit {
this.fastenApi.createSource(dbSourceCredential)
.subscribe((resp) => {
// const sourceSyncMessage = JSON.parse(msg) as SourceSyncMessage
delete this.status[sourceType]
delete this.status[dbSourceCredential.brand_id]
delete this.status[resp.source.id]
// window.location.reload();
// this.connectedSourceList.
//find the index of the "inprogress" source in the connected List, and then add this source to its source metadata.
let foundSource = this.connectedSourceList.findIndex((item) => item.metadata.source_type == sourceType)
let foundSource = this.connectedSourceList.findIndex((item) => item.brand.id == dbSourceCredential.brand_id)
this.connectedSourceList[foundSource].source = resp.source
console.log("source sync-all response:", resp.summary)
const toastNotification = new ToastNotification()
toastNotification.type = ToastType.Success
toastNotification.message = `Successfully connected ${sourceType}`
toastNotification.message = `Successfully connected external data source`
// const upsertSummary = sourceSyncMessage.response as UpsertSummary
// if(upsertSummary && upsertSummary.totalResources != upsertSummary.updatedResources.length){
@ -207,12 +206,12 @@ export class MedicalSourcesConnectedComponent implements OnInit {
this.toastService.show(toastNotification)
},
(err) => {
delete this.status[sourceType]
delete this.status[dbSourceCredential.brand_id]
// window.location.reload();
const toastNotification = new ToastNotification()
toastNotification.type = ToastType.Error
toastNotification.message = `An error occurred while accessing ${sourceType}: '${this.extractErrorFromResponse(err)}'`
toastNotification.message = `An error occurred while accessing external data source: '${this.extractErrorFromResponse(err)}'`
toastNotification.autohide = false
toastNotification.link = {
text: "View Details",
@ -223,12 +222,12 @@ export class MedicalSourcesConnectedComponent implements OnInit {
});
})
.catch((err) => {
delete this.status[sourceType]
delete this.status[expectedSourceStateInfo.brand_id]
// window.location.reload();
const toastNotification = new ToastNotification()
toastNotification.type = ToastType.Error
toastNotification.message = `An error occurred while accessing ${sourceType}: '${JSON.stringify(err)}'`
toastNotification.message = `An error occurred while accessing external data source: '${JSON.stringify(err)}'`
toastNotification.autohide = false
this.toastService.show(toastNotification)
console.error(err)
@ -296,7 +295,7 @@ export class MedicalSourcesConnectedComponent implements OnInit {
public openModal(contentModalRef, sourceListItem: SourceListItem) {
if(
(this.status[sourceListItem.metadata.source_type] && this.status[sourceListItem.metadata.source_type] != 'failed') //if this source type is currently "loading" dont open the modal window
(this.status[sourceListItem.brand.id] && this.status[sourceListItem.brand.id] != 'failed') //if this source type is currently "loading" dont open the modal window
|| !sourceListItem.source //if there's no connected source, dont open the modal window
|| (this.status[sourceListItem.source.id] && this.status[sourceListItem.source.id] != 'failed') //if this source type is currently "loading" dont open the modal window
){
@ -323,12 +322,12 @@ export class MedicalSourcesConnectedComponent implements OnInit {
this.fastenApi.syncSource(source.id).subscribe(
(respData) => {
delete this.status[source.id]
delete this.status[source.source_type]
delete this.status[source.brand_id]
console.log("source sync response:", respData)
},
(err) => {
delete this.status[source.id]
delete this.status[source.source_type]
delete this.status[source.brand_id]
console.log(err)
}
)
@ -336,7 +335,7 @@ export class MedicalSourcesConnectedComponent implements OnInit {
public sourceDeleteHandler(){
let source = this.modalSelectedSourceListItem.source
let sourceDisplayName = this.modalSelectedSourceListItem?.metadata?.display || this.modalSelectedSourceListItem?.source?.source_type || 'unknown'
let sourceDisplayName = this.modalSelectedSourceListItem?.source?.display || this.modalSelectedSourceListItem?.brand?.name || 'unknown'
this.status[source.id] = "authorize"
this.modalService.dismissAll()
@ -344,7 +343,7 @@ export class MedicalSourcesConnectedComponent implements OnInit {
this.fastenApi.deleteSource(source.id).subscribe(
(respData) => {
delete this.status[source.id]
delete this.status[source.source_type]
delete this.status[source.brand_id]
//delete this source from the connnected list
let foundIndex = this.connectedSourceList.findIndex((connectedSource) => {
@ -365,7 +364,7 @@ export class MedicalSourcesConnectedComponent implements OnInit {
},
(err) => {
delete this.status[source.id]
delete this.status[source.source_type]
delete this.status[source.brand_id]
const toastNotification = new ToastNotification()
toastNotification.type = ToastType.Error
@ -376,17 +375,18 @@ export class MedicalSourcesConnectedComponent implements OnInit {
}
//this is similar to the connectHandler in the MedicalSourcesComponent
//TODO: refactor this to use the connectHandler in the MedicalSourcesComponent
public sourceReconnectHandler(selectedSourceListItem: SourceListItem){
let sourceType = selectedSourceListItem.metadata.source_type
this.lighthouseApi.getLighthouseSource(sourceType)
let endpointId = selectedSourceListItem?.source?.endpoint_id
this.lighthouseApi.getLighthouseSource(endpointId)
.then(async (sourceMetadata: LighthouseSourceMetadata) => {
console.log(sourceMetadata);
let authorizationUrl = await this.lighthouseApi.generateSourceAuthorizeUrl(sourceType, sourceMetadata, selectedSourceListItem.source.id)
let authorizationUrl = await this.lighthouseApi.generateSourceAuthorizeUrl(sourceMetadata, selectedSourceListItem.source.id)
console.log('authorize url:', authorizationUrl.toString());
// redirect to lighthouse with uri's (or open a new window in desktop mode)
this.lighthouseApi.redirectWithOriginAndDestination(authorizationUrl.toString(), sourceType, sourceMetadata.redirect_uri).subscribe((codeData) => {
this.lighthouseApi.redirectWithOriginAndDestination(authorizationUrl.toString(), sourceMetadata).subscribe((desktopRedirectData) => {
//Note: this code will only run in Desktop mode (with popups)
//in non-desktop environments, the user is redirected in the same window, and this code is never executed.
@ -394,7 +394,7 @@ export class MedicalSourcesConnectedComponent implements OnInit {
this.modalService.dismissAll()
//redirect the browser back to this page with the code in the query string parameters
this.lighthouseApi.redirectWithDesktopCode(sourceType, codeData)
this.lighthouseApi.redirectWithDesktopCode(desktopRedirectData.state, desktopRedirectData.codeData)
})
});
}

View File

@ -1,12 +0,0 @@
export class MetadataSource {
aliases?: string[]
brand_logo?: string
category: string[]
display: string
hidden: boolean
identifiers?: {[name:string]: string}
patient_access_description?: string
patient_access_url?: string
platform_type: string
source_type: string
}

View File

@ -1,7 +1,10 @@
export class SourceState {
state: string
source_type: string //used to override the source_type for sources which have a single redirect url (eg. Epic)
endpoint_id: string
portal_id: string
brand_id: string
reconnect_source_id?: string //used to reconnect a source
code_verifier?: string

View File

@ -1,15 +1,22 @@
import {LighthouseSourceMetadata} from '../lighthouse/lighthouse-source-metadata';
import {BackgroundJob} from './background-job';
export class Source extends LighthouseSourceMetadata{
export class Source {
id?: string
created_at?: string
updated_at?: string
user_id?: number
source_type: string
display?: string
brand_id?: string
portal_id?: string
endpoint_id: string
platform_type: string
latest_background_job?: BackgroundJob
patient: string
client_id: string
access_token: string
refresh_token?: string
id_token?: string
@ -19,8 +26,7 @@ export class Source extends LighthouseSourceMetadata{
dynamic_client_jwks?: any[]
dynamic_client_id?: string
constructor(object: any) {
super()
constructor(object: Partial<Source>) {
return Object.assign(this, object)
}
}

View File

@ -1,26 +1,27 @@
import {MetadataSource} from '../fasten/metadata-source';
import {PatientAccessEndpoint} from '../patient-access-brands';
export class LighthouseSourceMetadata extends MetadataSource {
authorization_endpoint: string
token_endpoint: string
introspection_endpoint: string
userinfo_endpoint: string
registration_endpoint: string
export class LighthouseSourceMetadata extends PatientAccessEndpoint {
brand_id: string
portal_id: string
// endpoint_id = embedded PatientAccessEndpoint.id
scopes_supported: string[]
issuer: string
grant_types_supported: string[]
response_types_supported: string[]
response_modes_supported: string[]
aud: string
code_challenge_methods_supported: string[]
api_endpoint_base_url: string
client_id: string
redirect_uri: string
confidential: boolean
dynamic_client_registration_mode: string
cors_relay_required: boolean
issuer: string
aud: string
platform_type: string
client_id: string
redirect_uri: string
}

View File

@ -1,11 +1,27 @@
import {MetadataSource} from '../fasten/metadata-source';
import {PatientAccessBrand, PatientAccessPortal} from '../patient-access-brands';
export interface LighthouseEndpointListDisplayItem {
id: string;
platform_type: string;
}
export interface LighthousePortalListDisplayItem extends PatientAccessPortal {
endpoints?: LighthouseEndpointListDisplayItem[];
}
export interface LighthouseBrandListDisplayItem extends PatientAccessBrand {
portals: LighthousePortalListDisplayItem[];
hidden: boolean;
}
export class LighthouseSourceSearchResult {
_index: string;
_type: string;
_id: string;
_score: number;
_source: MetadataSource;
_source: LighthouseBrandListDisplayItem;
sort: string[];
}

View File

@ -0,0 +1,155 @@
// Code generated by tygo. DO NOT EDIT.
//////////
// source: patient_access_brand.go
/**
* TODO: generate via reflection
*/
export class PatientAccessBrand {
/**
* Fasten UUID for the brand - id should be a unique identifier for the brand. It is globally unique and should be a UUID
*/
id: string;
/**
* List of identifiers for the organization, e.g., external system, etc NPI, etc
* Identifiers SHOULD include a platform identifier, so we know where this entry came from, but not required
*/
identifiers?: any /* datatypes.Identifier */[];
/**
* RFC3339 Date and time the organization was last updated - Timestamp should be the last updated datetime for the data from this source, not the current date
*/
last_updated: string;
/**
* Primary name for the organization to display on a card, e.g., General Hospital
* Note this is not used within the app, only the Portal name should be used.
*/
name: string;
/**
* URL for the organizations primary website. note this is distinct from a patient portal, described under Patient Access Details below
*/
brand_website?: string;
/**
* URL for the organizations logo, which will be displayed on a card, Note this is a fallback logo, the primary logo will always be the Portal logo
*/
logo?: string;
/**
* List of alternate names for the organization, e.g., GH, General, GH Hospital
*/
aliases?: string[];
/**
* List of locations for the organization
* These should be the locations where the organization has a physical presence, e.g., a hospital or clinic"
*/
locations?: any /* datatypes.Address */[];
/**
* Patient Access Details
* These must be references to Patient Access Portal resource Ids
*/
portal_ids: string[];
/**
* list of brand ids that were merged together to creat this brand
*/
brand_ids?: string[];
}
//////////
// source: patient_access_endpoint.go
/**
* TODO: generate via reflection
*/
export class PatientAccessEndpoint {
/**
* Fasten UUID for the endpoint
*/
id: string;
/**
* List of identifiers for the endpoint, e.g., GH1234
*/
identifiers?: any /* datatypes.Identifier */[];
/**
* RFC3339 Date and time the endpoint was last updated
*/
last_updated: string;
/**
* Status of the endpoint, e.g., active - http://terminology.hl7.org/CodeSystem/endpoint-status
*/
status: string;
/**
* Connection type for the endpoint, e.g., hl7-fhir-rest - http://terminology.hl7.org/CodeSystem/endpoint-connection-type
*/
connection_type: string;
/**
* Platform type for the endpoint, e.g., epic, "cerner"
*/
platform_type: string;
/**
* URL for the endpoint, must have trailing slash
*/
url: string;
/**
* oauth endpoints
*/
authorization_endpoint?: string;
token_endpoint?: string;
introspection_endpoint?: string;
userinfo_endpoint?: string;
/**
* optional - required when Dynamic Client Registration mode is set
*/
registration_endpoint?: string;
/**
* Fasten custom configuration
*/
fhir_version?: string;
smart_configuration_url?: string;
fhir_capabilities_url?: string;
/**
* Software info
*/
software_name?: string;
software_version?: string;
software_release_date?: string;
}
//////////
// source: patient_access_portal.go
/**
* TODO: generate via reflection
*/
export class PatientAccessPortal {
/**
* Fasten UUID for the portal
*/
id: string;
/**
* List of identifiers for the organization, e.g., GH1234
*/
identifiers?: any /* datatypes.Identifier */[];
/**
* RFC3339 date & time of the last update to the patient portals information
*/
last_updated: string;
/**
* Name of the patient portal, e.g., MyChart
*/
name: string;
/**
* URL for the patient portals logo, which will be displayed on a card
*/
logo?: string;
/**
* URL for the patient portal, where patients can manage accounts with this provider.
*/
portal_website?: string;
/**
* Description of the patient portal, e.g., Manage your health information with General Hospital
*/
description?: string;
/**
* List of endpoint IDs for the patient portal. This is used to associate the patient portal with the endpoints that are used to access it.
*/
endpoint_ids: string[];
}

View File

@ -30,7 +30,7 @@
<tbody>
<tr *ngFor="let backgroundJob of backgroundJobs" [class.border-left-danger]="backgroundJob.job_status == 'STATUS_FAILED'">
<td>
<img style="max-height:30px" [src]="'assets/sources/'+(backgroundJob?.data?.source_type + '.png')" [alt]="backgroundJob?.data?.source_type" class="img-fluid">
<img style="max-height:30px" [src]="'assets/sources/'+(backgroundJob?.data?.brand_id + '.png')" [alt]="backgroundJob?.data?.brand_id" class="img-fluid">
</td>
<td>{{backgroundJob.job_type}}</td>
<td container="body" [ngbTooltip]="backgroundJob.created_at | amDateFormat:'YYYY-MM-DD HH:mm'">{{backgroundJob.created_at | amDateFormat:'LL'}}</td>

View File

@ -3,7 +3,6 @@ import {Source} from '../../models/fasten/source';
import {Router} from '@angular/router';
import {ResourceFhir} from '../../models/fasten/resource_fhir';
import {forkJoin} from 'rxjs';
import {MetadataSource} from '../../models/fasten/metadata-source';
import {FastenApiService} from '../../services/fasten-api.service';
import {LighthouseService} from '../../services/lighthouse.service';
import { GridStack, GridStackOptions, GridStackWidget } from 'gridstack';
@ -29,7 +28,6 @@ export class DashboardComponent implements OnInit {
recordsCount: number = 0
patientForSource: {[name: string]: ResourceFhir} = {}
metadataSource: { [name: string]: MetadataSource }
dashboardConfigs: DashboardConfig[] = []
selectedDashboardConfigNdx: number = 0
@ -85,7 +83,7 @@ export class DashboardComponent implements OnInit {
}
isActive(source: Source){
if(source.source_type == "manual" || source.source_type == 'fasten'){
if(source.platform_type == "manual" || source.platform_type == 'fasten'){
return '--'
}
let expiresDate = new Date(source.expires_at);

View File

@ -4,8 +4,14 @@ import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
import {Source} from '../../models/fasten/source';
import {forkJoin} from 'rxjs';
import {LighthouseService} from '../../services/lighthouse.service';
import {SourceListItem} from '../medical-sources/medical-sources.component';
import {Router} from '@angular/router';
import {LighthouseBrandListDisplayItem} from '../../models/lighthouse/lighthouse-source-search';
import {LighthouseSourceMetadata} from '../../models/lighthouse/lighthouse-source-metadata';
export class SourceListItem {
source?: Source
metadata: LighthouseSourceMetadata
}
@Component({
selector: 'app-explore',
@ -27,7 +33,7 @@ export class ExploreComponent implements OnInit {
//handle connected sources sources
const connectedSources = results as Source[]
forkJoin(connectedSources.map((source) => this.lighthouseApi.getLighthouseSource(source.source_type))).subscribe((connectedMetadata) => {
forkJoin(connectedSources.map((source) => this.lighthouseApi.getLighthouseSource(source.endpoint_id))).subscribe((connectedMetadata) => {
for(const ndx in connectedSources){
this.connectedSources.push({source: connectedSources[ndx], metadata: connectedMetadata[ndx]})
}

View File

@ -57,7 +57,7 @@
</div>
<input [ngModel]="searchTermUpdate | async" (keyup)="searchTermUpdate.next($event.target.value)" type="text" class="form-control" placeholder="Search Term">
<div class="input-group-append">
<span class="input-group-text">{{this.availableSourceList.length}} of {{this.resultLimits.totalItems}} results</span>
<span class="input-group-text">{{this.availableLighthouseBrandList.length}} of {{this.resultLimits.totalItems}} results</span>
</div>
</div><!-- input-group -->
@ -65,7 +65,7 @@
</div>
<div *ngIf="!loading || availableSourceList.length > 0 else isLoadingTemplate" class="row row-sm"
<div *ngIf="!loading || availableLighthouseBrandList.length > 0 else isLoadingTemplate" class="row row-sm"
infiniteScroll
[infiniteScrollDistance]="2"
[infiniteScrollThrottle]="50"
@ -73,9 +73,9 @@
>
<app-medical-sources-card class="col-sm-3 mg-b-20 px-3"
*ngFor="let sourceData of availableSourceList"
[sourceInfo]="sourceData"
[status]="status[sourceData.metadata.source_type]"
*ngFor="let lighthouseBrand of availableLighthouseBrandList"
[sourceInfo]="lighthouseBrand"
[status]="status[lighthouseBrand.brand.id]"
(onClick)="connectModalHandler(contentModalRef, $event)"
></app-medical-sources-card>
@ -92,12 +92,12 @@
<div class="modal-header">
<div class="media">
<img class="modal-header-media-image mg-sm-r-20 mg-b-20 mg-sm-b-0" [src]="'assets/sources/'+(modalSelectedSourceListItem?.metadata.brand_logo ? modalSelectedSourceListItem?.metadata?.brand_logo : modalSelectedSourceListItem?.metadata?.source_type+'.png')" [alt]="modalSelectedSourceListItem?.metadata?.display">
<img class="modal-header-media-image mg-sm-r-20 mg-b-20 mg-sm-b-0" [src]="'assets/sources/'+(modalSelectedBrandListItem?.id +'.png')" [alt]="modalSelectedBrandListItem?.name">
<div class="media-body">
<h6>{{modalSelectedSourceListItem?.metadata.display}}</h6>
<a *ngIf="modalSelectedSourceListItem?.metadata.patient_access_url"
[href]="modalSelectedSourceListItem.metadata?.patient_access_url"
class="mg-b-0" externalLink>{{modalSelectedSourceListItem?.metadata.patient_access_url | shortDomain}}</a>
<h6>{{modalSelectedBrandListItem?.name}}</h6>
<a *ngIf="modalSelectedBrandListItem?.brand_website"
[href]="modalSelectedBrandListItem?.brand_website"
class="mg-b-0" externalLink>{{modalSelectedBrandListItem?.brand_website | shortDomain}}</a>
</div><!-- media-body -->
</div><!-- media -->
<button type="button" class="btn close" aria-label="Close" (click)="modal.dismiss('Cross click')">
@ -114,40 +114,58 @@
If the data about this institution is missing or incorrect, you can <a class="link" href="https://docs.google.com/spreadsheets/d/1ZSgwfd7kwxSnimk4yofIFcR8ZMUls0zi9SZpRiOJBx0/edit?usp=sharing" externalLink>click here</a> to submit a correction.
</p>
<ng-container *ngIf="modalSelectedSourceListItem?.metadata?.category?.length > 0 || modalSelectedSourceListItem?.metadata?.patient_access_description">
<hr/>
<ng-container *ngIf="modalSelectedSourceListItem?.metadata?.patient_access_description">
<h6>About this Source</h6>
<p >{{modalSelectedSourceListItem?.metadata?.patient_access_description}}</p>
<div *ngFor="let portal of modalSelectedBrandListItem.portals" class="list-group">
<ng-container *ngFor="let endpoint of portal.endpoints">
<div (click)="connectHandler($event, modalSelectedBrandListItem.id, portal.id, endpoint.id)" class="list-group-item list-group-item-action flex-column align-items-start cursor-pointer">
<div class="d-flex w-100 justify-content-between align-items-center">
<h5 class="mb-1">
<span *ngIf="status[endpoint.id] == 'authorize'" class="spinner-border spinner-border-sm text-primary" role="status" aria-hidden="true"></span>
{{portal.name}}
</h5>
<span class="badge badge-primary badge-pill">{{endpoint.platform_type}}</span>
</div>
<p class="mb-1">{{portal.description}}</p>
<small>{{portal.portal_website | shortDomain}}</small>
</div>
</ng-container>
<ng-container *ngIf="modalSelectedSourceListItem?.metadata.aliases?.length > 0">
<h6>Aliases</h6>
<ul>
<li *ngFor="let alias of modalSelectedSourceListItem?.metadata?.aliases">{{alias}}</li>
</ul>
</ng-container>
<ng-container *ngIf="modalSelectedSourceListItem?.metadata.platform_type">
<h6>Platform Type</h6>
<p>{{modalSelectedSourceListItem?.metadata.platform_type}}</p>
</ng-container>
<ng-container *ngIf="modalSelectedSourceListItem?.metadata?.category?.length > 0">
<h6>Categories</h6>
<ul>
<li *ngFor="let cat of modalSelectedSourceListItem?.metadata?.category">{{cat | medicalSourcesCategoryLookup}}</li>
</ul>
</ng-container>
</ng-container>
</div>
<!-- <ng-container *ngIf="modalSelectedSourceListItem?.metadata?.category?.length > 0 || modalSelectedSourceListItem?.metadata?.patient_access_description">-->
<!-- <hr/>-->
<!-- <ng-container *ngIf="modalSelectedSourceListItem?.metadata?.patient_access_description">-->
<!-- <h6>About this Source</h6>-->
<!-- <p >{{modalSelectedSourceListItem?.metadata?.patient_access_description}}</p>-->
<!-- </ng-container>-->
<!-- <ng-container *ngIf="modalSelectedSourceListItem?.metadata.aliases?.length > 0">-->
<!-- <h6>Aliases</h6>-->
<!-- <ul>-->
<!-- <li *ngFor="let alias of modalSelectedSourceListItem?.metadata?.aliases">{{alias}}</li>-->
<!-- </ul>-->
<!-- </ng-container>-->
<!-- <ng-container *ngIf="modalSelectedSourceListItem?.metadata.platform_type">-->
<!-- <h6>Platform Type</h6>-->
<!-- <p>{{modalSelectedSourceListItem?.metadata.platform_type}}</p>-->
<!-- </ng-container>-->
<!-- <ng-container *ngIf="modalSelectedSourceListItem?.metadata?.category?.length > 0">-->
<!-- <h6>Categories</h6>-->
<!-- <ul>-->
<!-- <li *ngFor="let cat of modalSelectedSourceListItem?.metadata?.category">{{cat | medicalSourcesCategoryLookup}}</li>-->
<!-- </ul>-->
<!-- </ng-container>-->
<!-- </ng-container>-->
</div>
<div class="modal-footer">
<!-- <button (click)="sourceSyncHandler(modalSelectedSourceListItem.source)" type="button" class="btn btn-indigo">Sync</button>-->
<!-- <button (click)="connectHandler($event, modalSelectedSourceListItem.source['source_type'])" type="button" class="btn btn-outline-light">Reconnect</button>-->
<button type="button" (click)="connectHandler($event, modalSelectedSourceListItem)" class="btn btn-indigo">
<span *ngIf="status[modalSelectedSourceListItem?.metadata?.source_type] == 'authorize'" class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
Connect
</button>
<button (click)="modal.dismiss('Close click')" type="button" class="btn btn-outline-light">Close</button>
</div>
<!-- <div class="modal-footer">-->
<!--&lt;!&ndash; <button (click)="sourceSyncHandler(modalSelectedSourceListItem.source)" type="button" class="btn btn-indigo">Sync</button>&ndash;&gt;-->
<!-- &lt;!&ndash; <button (click)="connectHandler($event, modalSelectedSourceListItem.source['source_type'])" type="button" class="btn btn-outline-light">Reconnect</button>&ndash;&gt;-->
<!-- <button type="button" (click)="connectHandler($event, modalSelectedSourceListItem)" class="btn btn-indigo">-->
<!-- <span *ngIf="status[modalSelectedSourceListItem?.metadata?.source_type] == 'authorize'" class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>-->
<!-- Connect-->
<!-- </button>-->
<!-- <button (click)="modal.dismiss('Close click')" type="button" class="btn btn-outline-light">Close</button>-->
<!-- </div>-->
</ng-template>

View File

@ -3,7 +3,6 @@ import {LighthouseService} from '../../services/lighthouse.service';
import {FastenApiService} from '../../services/fasten-api.service';
import {LighthouseSourceMetadata} from '../../models/lighthouse/lighthouse-source-metadata';
import {Source} from '../../models/fasten/source';
import {MetadataSource} from '../../models/fasten/metadata-source';
import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
import {ActivatedRoute} from '@angular/router';
import {environment} from '../../../environments/environment';
@ -11,18 +10,19 @@ import {BehaviorSubject, forkJoin, Observable, of, Subject} from 'rxjs';
import {
LighthouseSourceSearch,
LighthouseSourceSearchAggregation,
LighthouseSourceSearchResult
LighthouseBrandListDisplayItem
} from '../../models/lighthouse/lighthouse-source-search';
import {debounceTime, distinctUntilChanged, pairwise, startWith} from 'rxjs/operators';
import {MedicalSourcesFilter, MedicalSourcesFilterService} from '../../services/medical-sources-filter.service';
import {FormControl, FormGroup} from '@angular/forms';
import * as _ from 'lodash';
import {PatientAccessBrand} from '../../models/patient-access-brands';
export const sourceConnectWindowTimeout = 24*5000 //wait 2 minutes (5 * 24 = 120)
export class SourceListItem {
source?: Source
metadata: MetadataSource
brand: LighthouseBrandListDisplayItem | PatientAccessBrand
}
@Component({
@ -38,7 +38,7 @@ export class MedicalSourcesComponent implements OnInit {
uploadedFile: File[] = []
availableSourceList: SourceListItem[] = []
availableLighthouseBrandList: SourceListItem[] = []
searchTermUpdate = new BehaviorSubject<string>("");
status: { [name: string]: undefined | "token" | "authorize" } = {}
@ -69,7 +69,7 @@ export class MedicalSourcesComponent implements OnInit {
filterForm = this.filterService.filterForm;
//modal
modalSelectedSourceListItem:SourceListItem = null;
modalSelectedBrandListItem: LighthouseBrandListDisplayItem | PatientAccessBrand = null;
modalCloseResult = '';
constructor(
@ -85,7 +85,7 @@ export class MedicalSourcesComponent implements OnInit {
console.log("medical-sources - filterChanges", filterInfo)
//this function should only trigger when there's a change to the filter values -- which requires a new query
this.availableSourceList = []
this.availableLighthouseBrandList = []
this.resultLimits.totalItems = 0
this.resultLimits.scrollComplete = false
@ -99,14 +99,18 @@ export class MedicalSourcesComponent implements OnInit {
ngOnInit(): void {
//TODO: handle Callbacks from the source connect window
const callbackSourceType = this.activatedRoute.snapshot.paramMap.get('source_type')
if(callbackSourceType){
// TODO: handle Callbacks from the source connect window
const callbackState = this.activatedRoute.snapshot.paramMap.get('state')
if(callbackState){
//get the source state information from localstorage
let sourceStateInfo = this.lighthouseApi.getSourceState(callbackState)
//move this source from available to connected (with a progress bar)
//remove item from available sources list, add to connected sources.
let inProgressAvailableIndex = this.availableSourceList.findIndex((item) => item.metadata.source_type == callbackSourceType)
let inProgressAvailableIndex = this.availableLighthouseBrandList.findIndex((item) => item.brand.id == sourceStateInfo.brand_id)
if(inProgressAvailableIndex > -1){
let sourcesInProgress = this.availableSourceList.splice(inProgressAvailableIndex, 1);
let sourcesInProgress = this.availableLighthouseBrandList.splice(inProgressAvailableIndex, 1);
}
}
//we're not in a callback redirect, lets load the sources
@ -154,8 +158,8 @@ export class MedicalSourcesComponent implements OnInit {
// this.searchResults = wrapper.hits.hits;
this.resultLimits.totalItems = wrapper.hits.total.value;
this.availableSourceList = this.availableSourceList.concat(wrapper.hits.hits.map((result) => {
return {metadata: result._source}
this.availableLighthouseBrandList = this.availableLighthouseBrandList.concat(wrapper.hits.hits.map((result) => {
return {brand: result._source}
}))
//check if scroll is complete.
@ -258,34 +262,37 @@ export class MedicalSourcesComponent implements OnInit {
console.log("TODO: connect Handler")
this.modalSelectedSourceListItem = sourceListItem
this.modalSelectedBrandListItem = sourceListItem.brand
this.modalService.open(contentModalRef, {ariaLabelledBy: 'modal-basic-title'}).result.then((result) => {
this.modalSelectedSourceListItem = null
this.modalSelectedBrandListItem = null
this.modalCloseResult = `Closed with: ${result}`;
}, (reason) => {
this.modalSelectedSourceListItem = null
this.modalSelectedBrandListItem = null
});
}
// /**
// * after pressing the connect button in the Modal, this function will generate an authorize url for this source, and redirec the user.
// * after pressing the connect button in the Modal, this function will generate an authorize url for this source, and redirect the user.
// * @param $event
// * @param sourceType
// */
public connectHandler($event, sourceListItem: SourceListItem): void {
public connectHandler($event, brandId: string, portalId: string, endpointId: string): void {
($event.currentTarget as HTMLButtonElement).disabled = true;
this.status[sourceListItem.metadata.source_type] = "authorize"
this.status[brandId] = "authorize"
this.status[endpointId] = "authorize"
let sourceType = sourceListItem.metadata.source_type
this.lighthouseApi.getLighthouseSource(sourceType)
this.lighthouseApi.getLighthouseSource(endpointId)
.then(async (sourceMetadata: LighthouseSourceMetadata) => {
sourceMetadata.brand_id = brandId
sourceMetadata.portal_id = portalId
console.log(sourceMetadata);
let authorizationUrl = await this.lighthouseApi.generateSourceAuthorizeUrl(sourceType, sourceMetadata)
let authorizationUrl = await this.lighthouseApi.generateSourceAuthorizeUrl(sourceMetadata)
console.log('authorize url:', authorizationUrl.toString());
// redirect to lighthouse with uri's (or open a new window in desktop mode)
this.lighthouseApi.redirectWithOriginAndDestination(authorizationUrl.toString(), sourceType, sourceMetadata.redirect_uri).subscribe((codeData) => {
this.lighthouseApi.redirectWithOriginAndDestination(authorizationUrl.toString(), sourceMetadata).subscribe((codeData) => {
//Note: this code will only run in Desktop mode (with popups)
//in non-desktop environments, the user is redirected in the same window, and this code is never executed.
@ -293,7 +300,7 @@ export class MedicalSourcesComponent implements OnInit {
this.modalService.dismissAll()
//redirect the browser back to this page with the code in the query string parameters
this.lighthouseApi.redirectWithDesktopCode(sourceType, codeData)
this.lighthouseApi.redirectWithDesktopCode(sourceMetadata.platform_type, codeData)
})
});
}

View File

@ -8,8 +8,8 @@
<div class="">
<div class="patient-image-wrap embed-responsive-item">
<img [src]="'assets/sources/'+selectedSource?.source_type+'.png'"
alt="{{selectedSource?.source_type}}"
<img [src]="'assets/sources/'+selectedSource?.brand_id+'.png'"
alt="{{selectedSource?.endpoint_id}}"
class="img-fluid">
</div>
</div>
@ -51,7 +51,7 @@
<div class="az-content-left">
<div class="az-content-breadcrumb">
<span>Sources</span>
<span>{{selectedSource?.source_type}}</span>
<span>{{selectedSource?.endpoint_id}}</span>
<span>Details</span>
</div>
@ -75,7 +75,7 @@
<ng-template #empty >
<div class="container">
<div class="az-error-wrapper">
<h1>{{selectedSource?.source_type}}</h1>
<h1>{{selectedSource?.endpoint_id}}</h1>
<h2>No resources found for this Healthcare provider</h2>
<h6>You may need to re-authenticate to this source, or wait for the resources to sync.</h6>
<a routerLink="/sources" class="btn btn-outline-indigo">Sources</a>

View File

@ -28,7 +28,7 @@ export class AuthService {
const state = uuidV4()
let sourceStateInfo = new SourceState()
sourceStateInfo.state = state
sourceStateInfo.source_type = idp_type
// sourceStateInfo.source_type = idp_type
sourceStateInfo.redirect_uri = `${window.location.href}/callback/hello`
// generate the authorization url

View File

@ -9,11 +9,9 @@ import {User} from '../models/fasten/user';
import {ResourceFhir} from '../models/fasten/resource_fhir';
import {SourceSummary} from '../models/fasten/source-summary';
import {Summary} from '../models/fasten/summary';
import {MetadataSource} from '../models/fasten/metadata-source';
import {AuthService} from './auth.service';
import {GetEndpointAbsolutePath} from '../../lib/utils/endpoint_absolute_path';
import {environment} from '../../environments/environment';
import {ResourceAssociation} from '../models/fasten/resource_association';
import {ValueSet} from 'fhir/r4';
import {AttachmentModel} from '../../lib/models/datatypes/attachment-model';
import {BinaryModel} from '../../lib/models/resources/binary-model';

View File

@ -7,7 +7,6 @@ import {ResponseWrapper} from '../models/response-wrapper';
import {LighthouseSourceMetadata} from '../models/lighthouse/lighthouse-source-metadata';
import * as Oauth from '@panva/oauth4webapi';
import {SourceState} from '../models/fasten/source-state';
import {MetadataSource} from '../models/fasten/metadata-source';
import {uuidV4} from '../../lib/utils/uuid';
import {LighthouseSourceSearch} from '../models/lighthouse/lighthouse-source-search';
import {HTTP_CLIENT_TOKEN} from "../dependency-injection";
@ -15,6 +14,7 @@ import {MedicalSourcesFilter} from './medical-sources-filter.service';
import {OpenExternalLink} from '../../lib/utils/external_link';
import {Router, UrlSerializer} from '@angular/router';
import {Location} from '@angular/common';
import {PatientAccessBrand, PatientAccessEndpoint, PatientAccessPortal} from '../models/patient-access-brands';
export const sourceConnectDesktopTimeout = 24*5000 //wait 2 minutes (5 * 24 = 120)
@ -30,6 +30,14 @@ export class LighthouseService {
private location: Location,
) {}
public storeSourceState(state: string, sourceStateInfo: SourceState) {
localStorage.setItem(state, JSON.stringify(sourceStateInfo))
}
public getSourceState(state: string): SourceState {
return JSON.parse(localStorage.getItem(state))
}
public searchLighthouseSources(filter: MedicalSourcesFilter): Observable<LighthouseSourceSearch> {
if((typeof filter.searchAfter === 'string' || filter.searchAfter instanceof String) && (filter.searchAfter as string).length > 0){
filter.searchAfter = (filter.searchAfter as string).split(',')
@ -46,23 +54,8 @@ export class LighthouseService {
);
}
public getLighthouseSourceMetadataMap(showHidden = false): Observable<{[name: string]: MetadataSource}> {
const endpointUrl = new URL(`${environment.lighthouse_api_endpoint_base}/list`);
if(showHidden){
endpointUrl.searchParams.set('show_hidden', 'true');
}
return this._httpClient.get<ResponseWrapper>(endpointUrl.toString())
.pipe(
map((response: ResponseWrapper) => {
console.log("Metadata RESPONSE", response)
return response.data as {[name: string]: MetadataSource}
})
);
}
async getLighthouseSource(sourceType: string): Promise<LighthouseSourceMetadata> {
return this._httpClient.get<any>(`${environment.lighthouse_api_endpoint_base}/connect/${sourceType}`)
async getLighthouseSource(endpointId: string): Promise<LighthouseSourceMetadata> {
return this._httpClient.get<any>(`${environment.lighthouse_api_endpoint_base}/connect/${endpointId}`)
.pipe(
map((response: ResponseWrapper) => {
return response.data as LighthouseSourceMetadata
@ -70,12 +63,65 @@ export class LighthouseService {
).toPromise();
}
async getLighthouseCatalogBrand(brandIdOrPlatformType: string): Promise<PatientAccessBrand> {
if(brandIdOrPlatformType === 'fasten'){
return of({
id: 'fasten',
last_updated: '',
portal_ids: [],
name: '',
platform_type: 'fasten'
}).toPromise()
} else if (brandIdOrPlatformType === 'manual'){
return of({
id: 'manual',
last_updated: '',
portal_ids: [],
name: '',
platform_type: 'manual'
}).toPromise()
}
async generateSourceAuthorizeUrl(sourceType: string, lighthouseSource: LighthouseSourceMetadata, reconnectSourceId?: string): Promise<URL> {
const catalogUrl = new URL(`${environment.lighthouse_api_endpoint_base}/catalog`);
catalogUrl.searchParams.set('brand_id', brandIdOrPlatformType);
return this._httpClient.get<any>(catalogUrl.toString())
.pipe(
map((response: ResponseWrapper) => {
return response.data as PatientAccessBrand
})
).toPromise();
}
async getLighthouseCatalogPortal(portalId: string): Promise<PatientAccessPortal> {
const catalogUrl = new URL(`${environment.lighthouse_api_endpoint_base}/catalog`);
catalogUrl.searchParams.set('portal_id', portalId);
return this._httpClient.get<any>(catalogUrl.toString())
.pipe(
map((response: ResponseWrapper) => {
return response.data as PatientAccessPortal
})
).toPromise();
}
async getLighthouseCatalogEndpoint(endpointId: string): Promise<PatientAccessEndpoint> {
const catalogUrl = new URL(`${environment.lighthouse_api_endpoint_base}/catalog`);
catalogUrl.searchParams.set('endpoint_id', endpointId);
return this._httpClient.get<any>(catalogUrl.toString())
.pipe(
map((response: ResponseWrapper) => {
return response.data as PatientAccessEndpoint
})
).toPromise();
}
async generateSourceAuthorizeUrl(lighthouseSource: LighthouseSourceMetadata, reconnectSourceId?: string): Promise<URL> {
const state = uuidV4()
let sourceStateInfo = new SourceState()
sourceStateInfo.state = state
sourceStateInfo.source_type = sourceType
sourceStateInfo.endpoint_id = lighthouseSource.id
sourceStateInfo.portal_id = lighthouseSource.portal_id
sourceStateInfo.brand_id = lighthouseSource.brand_id
if(reconnectSourceId){
//if the source already exists, and we want to re-connect it (because of an expiration), we need to pass the existing source id
sourceStateInfo.reconnect_source_id = reconnectSourceId
@ -112,7 +158,8 @@ export class LighthouseService {
authorizationUrl.searchParams.set('code_challenge_method', codeChallengeMethod);
}
localStorage.setItem(state, JSON.stringify(sourceStateInfo))
//store the source state info
this.storeSourceState(state, sourceStateInfo)
return authorizationUrl
}
@ -136,23 +183,36 @@ export class LighthouseService {
* dest_url - https://patient360la.anthem.com/.../connect/authorize?redirect_uri=https://lighthouse.fastenhealth.com/callback/anthem
* redirect_url - lighthouse.fastenhealth.com/sandbox/redirect/anthem?origin_url=...&dest_url=...
*/
redirectWithOriginAndDestination(destUrl: string, sourceType: string, callbackUri: string): Observable<any> {
redirectWithOriginAndDestination(destUrl: string, redirectOpts: {platform_type: string, redirect_uri: string, brand_id: string, portal_id: string, id: string}): Observable<{ codeData:any, state:string }> {
const originUrlParts = new URL(window.location.href)
//retrieve the state info from destUrl
const destUrlParts = new URL(destUrl)
const state = destUrlParts.searchParams.get("state")
if(!state){
throw new Error("No state found in destination url")
}
if(environment.environment_desktop){
//hash based routing
originUrlParts.hash = `desktop/callback/${sourceType}`
originUrlParts.hash = `desktop/callback/${state}`
} else {
//path based routing
originUrlParts.hash = "" //reset hash in-case its present.
originUrlParts.pathname = this.pathJoin([originUrlParts.pathname, `callback/${sourceType}`])
originUrlParts.pathname = this.pathJoin([originUrlParts.pathname, `callback/${state}`])
}
const redirectUrlParts = new URL(callbackUri.replace("/callback/", "/redirect/"));
let redirectUrl = this.pathJoin([environment.lighthouse_api_endpoint_base, `redirect/${state}`])
const redirectUrlParts = new URL(redirectUrl);
const redirectParams = new URLSearchParams()
redirectParams.set("origin_url", originUrlParts.toString())
redirectParams.set("dest_url", destUrl)
redirectParams.set("desktop_mode", environment.environment_desktop ? "true" : "false")
redirectParams.set("brand_id", redirectOpts.brand_id)
redirectParams.set("portal_id", redirectOpts.portal_id)
redirectParams.set("endpoint_id", redirectOpts.id)
redirectUrlParts.search = redirectParams.toString()
console.log(redirectUrlParts.toString());
@ -161,9 +221,9 @@ export class LighthouseService {
if(environment.environment_desktop && environment.popup_source_auth){
//@ts-ignore
OpenExternalLink(redirectUrlParts.toString(), environment.environment_desktop, sourceType)
OpenExternalLink(redirectUrlParts.toString(), environment.environment_desktop, state)
return this.waitForDesktopCodeOrTimeout(sourceType)
return this.waitForDesktopCodeOrTimeout(state)
//now wait for response from the opened window
} else {
@ -174,7 +234,7 @@ export class LighthouseService {
}
async swapOauthToken(sourceType: string, sourceMetadata: LighthouseSourceMetadata, expectedSourceStateInfo: SourceState, code: string): Promise<any>{
async swapOauthToken(sourceMetadata: LighthouseSourceMetadata, expectedSourceStateInfo: SourceState, code: string): Promise<any>{
// @ts-expect-error
const client: oauth.Client = {
client_id: sourceMetadata.client_id
@ -188,7 +248,8 @@ export class LighthouseService {
} else {
console.log("This is a confidential client, using lighthouse token endpoint.")
//if this is a confidential client, we need to "override" token endpoint, and use the Fasten Lighthouse to complete the swap
sourceMetadata.token_endpoint = sourceMetadata.redirect_uri.replace("/callback/", "/token/")
sourceMetadata.token_endpoint = this.pathJoin([environment.lighthouse_api_endpoint_base, `token/${expectedSourceStateInfo.endpoint_id}`])
//use a placeholder client_secret (the actual secret is stored in Lighthouse)
client.client_secret = "placeholder"
client.token_endpoint_auth_method = "client_secret_basic"
@ -242,7 +303,7 @@ export class LighthouseService {
return parts.join(separator);
}
private waitForDesktopCodeOrTimeout(sourceType: string): Observable<any> {
private waitForDesktopCodeOrTimeout(state: string): Observable<{ codeData:any, state:string }> {
console.log(`waiting for wails Event notification from window`)
if(typeof wails == "undefined"){
@ -257,17 +318,20 @@ export class LighthouseService {
//throw an error if we wait more than 2 minutes (this will close the window)
timeout(sourceConnectDesktopTimeout),
//make sure we're only listening to events from the "opened" window.
filter((eventPayload: any ) => eventPayload.sender == sourceType),
filter((eventPayload: any ) => eventPayload.sender == state),
//after filtering, we should only have one event to handle.
first(),
map((event) => {
console.log(`received wails event notification from ${sourceType} window & sending acknowledgment`, event)
console.log(`received wails event notification from ${state} window & sending acknowledgment`, event)
// @ts-ignore
return event.data
return {
state: state,
codeData: event.data
}
}),
catchError((err) => {
console.warn(`timed out waiting for notification from ${sourceType} (${sourceConnectDesktopTimeout/1000}s), closing window`)
wails.Application.GetWindowByName(sourceType).Window.Close()
console.warn(`timed out waiting for notification from ${state} (${sourceConnectDesktopTimeout/1000}s), closing window`)
wails.Application.GetWindowByName(state).Window.Close()
return throwError(err)
})
)
@ -275,7 +339,7 @@ export class LighthouseService {
//after waiting for the desktop code, we need to redirect to the callback page with the code in the query params
// (which is what would have happened if we were in a browser and we were redirected as usual)
redirectWithDesktopCode(sourceType: string, codeData: any){
redirectWithDesktopCode(state: string, codeData: any){
if(!codeData){
//if we redirected completely, no callback data will be present.
@ -286,7 +350,7 @@ export class LighthouseService {
//redirect to callback page with code
let urlTree = this.router.createUrlTree(
['/sources/callback/' + sourceType],
['/sources/callback/' + state],
{ queryParams: codeData, }
);

View File

@ -0,0 +1,13 @@
export const environment = {
production: true,
environment_cloud: false,
environment_desktop: true,
environment_name: "sandbox",
popup_source_auth: false,
lighthouse_api_endpoint_base: 'http://localhost:4000',
// lighthouse_api_endpoint_base: 'https://lighthouse.fastenhealth.com/sandboxbeta',
//used to specify the api server that we're going to use (can be relative or absolute). Must not have trailing slash
fasten_api_endpoint_base: '/api',
};

799
yarn.lock Normal file
View File

@ -0,0 +1,799 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@angular/animations@~14.2.12":
version "14.2.12"
resolved "https://registry.npmjs.org/@angular/animations/-/animations-14.2.12.tgz#9ff16bad809d3ff7526f8359ef214e63e8236f9f"
integrity sha512-gwdnFZkvVUr+enUNfhfCGRGGqNHn1+vTA81apLfHYhJxgjiLUtETc4KTOrQevtDm022pEd+LSrvr8r+7ag+jkw==
dependencies:
tslib "^2.3.0"
"@angular/cdk@^14.1.0":
version "14.2.7"
resolved "https://registry.npmjs.org/@angular/cdk/-/cdk-14.2.7.tgz#65eb6fbbeed6120fad4e3913aa66f8b74c853ac3"
integrity sha512-/tEsYaUbDSnfEmKVvAMramIptmhI67O+9STjOV0i+74XR2NospeK0fkbywIANu1n3w6AHGMotvRWJrjmbCElFg==
dependencies:
tslib "^2.3.0"
optionalDependencies:
parse5 "^5.0.0"
"@angular/common@~14.2.12":
version "14.2.12"
resolved "https://registry.npmjs.org/@angular/common/-/common-14.2.12.tgz#b608ff629a635f323b297000f53f976f71ae3c80"
integrity sha512-oZunh9wfInFWhNO1P8uoEs/o4u8kerKMhw8GruywKm1TV7gHDP2Fi5WHGjFqq3XYptgBTPCTSEfyLX6Cwq1PUw==
dependencies:
tslib "^2.3.0"
"@angular/compiler@~14.2.12":
version "14.2.12"
resolved "https://registry.npmjs.org/@angular/compiler/-/compiler-14.2.12.tgz#2b17667acfb2cda1521de102246b178845838812"
integrity sha512-u2MH9+NRwbbFDRNiPWPexed9CnCq9+pGHLuyACSP2uR6Ik68cE6cayeZbIeoEV5vWpda/XsLmJgPJysw7dAZLQ==
dependencies:
tslib "^2.3.0"
"@angular/core@~14.2.12":
version "14.2.12"
resolved "https://registry.npmjs.org/@angular/core/-/core-14.2.12.tgz#118467ec4e8ea082931a84e8cefe722a0e110dc9"
integrity sha512-sGQxU5u4uawwvJa6jOTmGoisJiQ5HIN/RoBw99CmoqZIVyUSg9IRJJC1KVdH8gbpWBNLkElZv21lwJTL/msWyg==
dependencies:
tslib "^2.3.0"
"@angular/elements@^14.2.12":
version "14.3.0"
resolved "https://registry.npmjs.org/@angular/elements/-/elements-14.3.0.tgz#f4ce07241d5b05c8c843465cf564f3519ed8d5d5"
integrity sha512-fIg8IOD2R36v3SZ8yQEwTC8T71Hk0lbJFJXaOUZDZ6MfwdT8mMkFCujPRXOF0+p/ZnOiq2EhBwuPdjmKTf7XHA==
dependencies:
tslib "^2.3.0"
"@angular/forms@~14.2.12":
version "14.2.12"
resolved "https://registry.npmjs.org/@angular/forms/-/forms-14.2.12.tgz#2174e4e3b87390b0f1ebde0b3fc6c4d2ae793a1a"
integrity sha512-7abYlGIT2JnAtutQUlH3fQS6QEpbfftgvsVcZJCyvX0rXL3u2w2vUQkDHJH4YJJp3AHFVCH4/l7R4VcaPnrwvA==
dependencies:
tslib "^2.3.0"
"@angular/platform-browser-dynamic@~14.2.12":
version "14.2.12"
resolved "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-14.2.12.tgz#d4dac4a488c804ea07213a98450efa124a15d70a"
integrity sha512-oZhNJeaBmgw8+KBSYpKz2RYqEDyETC+HJXH8dwIFcP6BqqwL2NE70FdSR7EnOa5c41MEtTmMCGhrJSFR60x5/w==
dependencies:
tslib "^2.3.0"
"@angular/platform-browser@~14.2.12":
version "14.2.12"
resolved "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-14.2.12.tgz#24fe6bc87d056fc8b91924091da712fef7c66457"
integrity sha512-vOarWym8ucl1gjYWCzdwyBha+MTvL381mvTTUu8aUx6nVhHFjv4bvpjlZnZgojecqUPyxOwmPLLHvCZPJVHZYg==
dependencies:
tslib "^2.3.0"
"@angular/router@~14.2.12":
version "14.2.12"
resolved "https://registry.npmjs.org/@angular/router/-/router-14.2.12.tgz#f5b2c9ced3337e4a7af1905b861b114ac80a8482"
integrity sha512-r5tVus5RJDNc4U2v0jMtjPiAS1xDsVsJ70lS313DgZmBDHIVZP1cWIehdxwgNlGwQQtAA36eG7toBwqUU3gb/A==
dependencies:
tslib "^2.3.0"
"@ant-design/colors@^5.0.0":
version "5.1.1"
resolved "https://registry.npmjs.org/@ant-design/colors/-/colors-5.1.1.tgz#800b2186b1e27e66432e67d03ed96af3e21d8940"
integrity sha512-Txy4KpHrp3q4XZdfgOBqLl+lkQIc3tEvHXOimRN1giX1AEC7mGtyrO9p8iRGJ3FLuVMGa2gNEzQyghVymLttKQ==
dependencies:
"@ctrl/tinycolor" "^3.3.1"
"@ant-design/icons-angular@^14.1.0":
version "14.1.0"
resolved "https://registry.npmjs.org/@ant-design/icons-angular/-/icons-angular-14.1.0.tgz#7612507aae28872ce3ccd139857452426cc30be3"
integrity sha512-SvWi8p4L+cCfTGtezeUq1s8kahZEO1UVf7ZR615Fix3c2l9OnAJi+niDkq2fZHjpiarFrt8HoZ2EhMAMN7jfbg==
dependencies:
"@ant-design/colors" "^5.0.0"
tslib "^2.0.0"
"@babel/runtime@^7.21.0":
version "7.23.5"
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.5.tgz#11edb98f8aeec529b82b211028177679144242db"
integrity sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w==
dependencies:
regenerator-runtime "^0.14.0"
"@ctrl/tinycolor@^3.3.1":
version "3.6.1"
resolved "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz#b6c75a56a1947cc916ea058772d666a2c8932f31"
integrity sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==
"@lhncbc/ucum-lhc@^4.1.3", "@lhncbc/ucum-lhc@^4.1.6":
version "4.2.0"
resolved "https://registry.npmjs.org/@lhncbc/ucum-lhc/-/ucum-lhc-4.2.0.tgz#522cc16fe37739d7da16c27c8b9ad039ee39c2d7"
integrity sha512-OEiWX7IHFHLTFs7+w5EvGtI5dhXhhL0341LqZ9WEBWErtoY0/9xl/vn+wwT9vnBHnjQ7Ux0o7iEUXvN8uVn4xg==
dependencies:
coffeescript "^2.7.0"
csv-parse "^4.4.6"
csv-stringify "^1.0.4"
escape-html "^1.0.3"
is-integer "^1.0.6"
jsonfile "^2.2.3"
stream "0.0.2"
stream-transform "^0.1.1"
string-to-stream "^1.1.0"
xmldoc "^0.4.0"
"@microsoft/fetch-event-source@^2.0.1":
version "2.0.1"
resolved "https://registry.npmjs.org/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz#9ceecc94b49fbaa15666e38ae8587f64acce007d"
integrity sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA==
"@types/jquery@^3.5.14":
version "3.5.29"
resolved "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.29.tgz#3c06a1f519cd5fc3a7a108971436c00685b5dcea"
integrity sha512-oXQQC9X9MOPRrMhPHHOsXqeQDnWeCDT3PelUIg/Oy8FAbzSZtFHRjc7IpbfFVmpLtJ+UOoywpRsuO5Jxjybyeg==
dependencies:
"@types/sizzle" "*"
"@types/sizzle@*":
version "2.3.8"
resolved "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.8.tgz#518609aefb797da19bf222feb199e8f653ff7627"
integrity sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==
antlr4@~4.9.3:
version "4.9.3"
resolved "https://registry.npmjs.org/antlr4/-/antlr4-4.9.3.tgz#268b844ff8ce97d022399a05d4b37aa6ab4047b2"
integrity sha512-qNy2odgsa0skmNMCuxzXhM4M8J1YDaPv3TI+vCdnOAanu0N982wBrSqziDKRDctEZLZy9VffqIZXc0UGjjSP/g==
argparse@^1.0.7:
version "1.0.10"
resolved "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
dependencies:
sprintf-js "~1.0.2"
array-buffer-byte-length@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz#fabe8bc193fea865f317fe7807085ee0dee5aead"
integrity sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==
dependencies:
call-bind "^1.0.2"
is-array-buffer "^3.0.1"
autocomplete-lhc@^18.6.3:
version "18.6.3"
resolved "https://registry.npmjs.org/autocomplete-lhc/-/autocomplete-lhc-18.6.3.tgz#73217169e58581dfa3f4cd800d8225fcd79aefc2"
integrity sha512-1bkfn4RY4bk67O1PzN8Jr6H2APbrYN4RDofqputmmZ/Qe6cw6Dg1TkoqqP/2Y0lyApd/ctWcPpkbsVEnIDp9vQ==
available-typed-arrays@^1.0.5:
version "1.0.5"
resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7"
integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==
call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.4, call-bind@^1.0.5:
version "1.0.5"
resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz#6fa2b7845ce0ea49bf4d8b9ef64727a2c2e2e513"
integrity sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==
dependencies:
function-bind "^1.1.2"
get-intrinsic "^1.2.1"
set-function-length "^1.1.1"
coffeescript@^2.7.0:
version "2.7.0"
resolved "https://registry.npmjs.org/coffeescript/-/coffeescript-2.7.0.tgz#a43ec03be6885d6d1454850ea70b9409c391279c"
integrity sha512-hzWp6TUE2d/jCcN67LrW1eh5b/rSDKQK6oD6VMLlggYVUUFexgTH9z3dNYihzX4RMhze5FTUsUmOXViJKFQR/A==
commander@^2.18.0:
version "2.20.3"
resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
core-util-is@~1.0.0:
version "1.0.3"
resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
csv-parse@^4.4.6:
version "4.16.3"
resolved "https://registry.npmjs.org/csv-parse/-/csv-parse-4.16.3.tgz#7ca624d517212ebc520a36873c3478fa66efbaf7"
integrity sha512-cO1I/zmz4w2dcKHVvpCr7JVRu8/FymG5OEpmvsZYlccYolPBLoVGKUHgNoc4ZGkFeFlWGEDmMyBM+TTqRdW/wg==
csv-stringify@^1.0.4:
version "1.1.2"
resolved "https://registry.npmjs.org/csv-stringify/-/csv-stringify-1.1.2.tgz#77a41526581bce3380f12b00d7c5bbac70c82b58"
integrity sha512-3NmNhhd+AkYs5YtM1GEh01VR6PKj6qch2ayfQaltx5xpcAdThjnbbI5eT8CzRVpXfGKAxnmrSYLsNl/4f3eWiw==
dependencies:
lodash.get "~4.4.2"
date-fns@^1.30.1:
version "1.30.1"
resolved "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c"
integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==
date-fns@^2.16.1:
version "2.30.0"
resolved "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0"
integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==
dependencies:
"@babel/runtime" "^7.21.0"
deep-equal@^2.0.5:
version "2.2.3"
resolved "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz#af89dafb23a396c7da3e862abc0be27cf51d56e1"
integrity sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==
dependencies:
array-buffer-byte-length "^1.0.0"
call-bind "^1.0.5"
es-get-iterator "^1.1.3"
get-intrinsic "^1.2.2"
is-arguments "^1.1.1"
is-array-buffer "^3.0.2"
is-date-object "^1.0.5"
is-regex "^1.1.4"
is-shared-array-buffer "^1.0.2"
isarray "^2.0.5"
object-is "^1.1.5"
object-keys "^1.1.1"
object.assign "^4.1.4"
regexp.prototype.flags "^1.5.1"
side-channel "^1.0.4"
which-boxed-primitive "^1.0.2"
which-collection "^1.0.1"
which-typed-array "^1.1.13"
define-data-property@^1.0.1, define-data-property@^1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz#c35f7cd0ab09883480d12ac5cb213715587800b3"
integrity sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==
dependencies:
get-intrinsic "^1.2.1"
gopd "^1.0.1"
has-property-descriptors "^1.0.0"
define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1:
version "1.2.1"
resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c"
integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==
dependencies:
define-data-property "^1.0.1"
has-property-descriptors "^1.0.0"
object-keys "^1.1.1"
emitter-component@^1.1.1:
version "1.1.2"
resolved "https://registry.npmjs.org/emitter-component/-/emitter-component-1.1.2.tgz#d65af5833dc7c682fd0ade35f902d16bc4bad772"
integrity sha512-QdXO3nXOzZB4pAjM0n6ZE+R9/+kPpECA/XSELIcc54NeYVnBqIk+4DFiBgK+8QbV3mdvTG6nedl7dTYgO+5wDw==
es-get-iterator@^1.1.3:
version "1.1.3"
resolved "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz#3ef87523c5d464d41084b2c3c9c214f1199763d6"
integrity sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==
dependencies:
call-bind "^1.0.2"
get-intrinsic "^1.1.3"
has-symbols "^1.0.3"
is-arguments "^1.1.1"
is-map "^2.0.2"
is-set "^2.0.2"
is-string "^1.0.7"
isarray "^2.0.5"
stop-iteration-iterator "^1.0.0"
escape-html@^1.0.3:
version "1.0.3"
resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
esprima@^4.0.0:
version "4.0.1"
resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
fast-copy@^2.1.7:
version "2.1.7"
resolved "https://registry.npmjs.org/fast-copy/-/fast-copy-2.1.7.tgz#affc9475cb4b555fb488572b2a44231d0c9fa39e"
integrity sha512-ozrGwyuCTAy7YgFCua8rmqmytECYk/JYAMXcswOcm0qvGoE3tPb7ivBeIHTOK2DiapBhDZgacIhzhQIKU5TCfA==
fhirpath@3.2.0:
version "3.2.0"
resolved "https://registry.npmjs.org/fhirpath/-/fhirpath-3.2.0.tgz#1c70cfdf6f22edf3a6d79b157a10b19bca6039e4"
integrity sha512-T6j53O5MVd2DkJ+PXHN9cceNdhOFLZ1f5mKOBhuv1NKZ++yKW+6qyUM+YPGvU3ugEM2dERj6Hz4xhPlWaaQH0A==
dependencies:
"@lhncbc/ucum-lhc" "^4.1.3"
antlr4 "~4.9.3"
commander "^2.18.0"
date-fns "^1.30.1"
js-yaml "^3.13.1"
for-each@^0.3.3:
version "0.3.3"
resolved "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==
dependencies:
is-callable "^1.1.3"
function-bind@^1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
functions-have-names@^1.2.3:
version "1.2.3"
resolved "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2:
version "1.2.2"
resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz#281b7622971123e1ef4b3c90fd7539306da93f3b"
integrity sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==
dependencies:
function-bind "^1.1.2"
has-proto "^1.0.1"
has-symbols "^1.0.3"
hasown "^2.0.0"
gopd@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==
dependencies:
get-intrinsic "^1.1.3"
graceful-fs@^4.1.6:
version "4.2.11"
resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
has-bigints@^1.0.1:
version "1.0.2"
resolved "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa"
integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==
has-property-descriptors@^1.0.0:
version "1.0.1"
resolved "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz#52ba30b6c5ec87fd89fa574bc1c39125c6f65340"
integrity sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==
dependencies:
get-intrinsic "^1.2.2"
has-proto@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0"
integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==
has-symbols@^1.0.2, has-symbols@^1.0.3:
version "1.0.3"
resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
has-tostringtag@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25"
integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==
dependencies:
has-symbols "^1.0.2"
hasown@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c"
integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==
dependencies:
function-bind "^1.1.2"
inherits@^2.0.1, inherits@~2.0.3:
version "2.0.4"
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
internal-slot@^1.0.4:
version "1.0.6"
resolved "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz#37e756098c4911c5e912b8edbf71ed3aa116f930"
integrity sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==
dependencies:
get-intrinsic "^1.2.2"
hasown "^2.0.0"
side-channel "^1.0.4"
is-arguments@^1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b"
integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==
dependencies:
call-bind "^1.0.2"
has-tostringtag "^1.0.0"
is-array-buffer@^3.0.1, is-array-buffer@^3.0.2:
version "3.0.2"
resolved "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe"
integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==
dependencies:
call-bind "^1.0.2"
get-intrinsic "^1.2.0"
is-typed-array "^1.1.10"
is-bigint@^1.0.1:
version "1.0.4"
resolved "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3"
integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==
dependencies:
has-bigints "^1.0.1"
is-boolean-object@^1.1.0:
version "1.1.2"
resolved "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719"
integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==
dependencies:
call-bind "^1.0.2"
has-tostringtag "^1.0.0"
is-callable@^1.1.3:
version "1.2.7"
resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055"
integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==
is-date-object@^1.0.5:
version "1.0.5"
resolved "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f"
integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==
dependencies:
has-tostringtag "^1.0.0"
is-finite@^1.0.0:
version "1.1.0"
resolved "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz#904135c77fb42c0641d6aa1bcdbc4daa8da082f3"
integrity sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==
is-integer@^1.0.6:
version "1.0.7"
resolved "https://registry.npmjs.org/is-integer/-/is-integer-1.0.7.tgz#6bde81aacddf78b659b6629d629cadc51a886d5c"
integrity sha512-RPQc/s9yBHSvpi+hs9dYiJ2cuFeU6x3TyyIp8O2H6SKEltIvJOzRj9ToyvcStDvPR/pS4rxgr1oBFajQjZ2Szg==
dependencies:
is-finite "^1.0.0"
is-map@^2.0.1, is-map@^2.0.2:
version "2.0.2"
resolved "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127"
integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==
is-number-object@^1.0.4:
version "1.0.7"
resolved "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc"
integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==
dependencies:
has-tostringtag "^1.0.0"
is-regex@^1.1.4:
version "1.1.4"
resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958"
integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==
dependencies:
call-bind "^1.0.2"
has-tostringtag "^1.0.0"
is-set@^2.0.1, is-set@^2.0.2:
version "2.0.2"
resolved "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec"
integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==
is-shared-array-buffer@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79"
integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==
dependencies:
call-bind "^1.0.2"
is-string@^1.0.5, is-string@^1.0.7:
version "1.0.7"
resolved "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd"
integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==
dependencies:
has-tostringtag "^1.0.0"
is-symbol@^1.0.3:
version "1.0.4"
resolved "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c"
integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==
dependencies:
has-symbols "^1.0.2"
is-typed-array@^1.1.10:
version "1.1.12"
resolved "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a"
integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==
dependencies:
which-typed-array "^1.1.11"
is-weakmap@^2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2"
integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==
is-weakset@^2.0.1:
version "2.0.2"
resolved "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz#4569d67a747a1ce5a994dfd4ef6dcea76e7c0a1d"
integrity sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==
dependencies:
call-bind "^1.0.2"
get-intrinsic "^1.1.1"
isarray@^2.0.5:
version "2.0.5"
resolved "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==
jquery@^3.6.1:
version "3.7.1"
resolved "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz#083ef98927c9a6a74d05a6af02806566d16274de"
integrity sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==
js-yaml@^3.13.1:
version "3.14.1"
resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537"
integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==
dependencies:
argparse "^1.0.7"
esprima "^4.0.0"
jsonfile@^2.2.3:
version "2.4.0"
resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8"
integrity sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==
optionalDependencies:
graceful-fs "^4.1.6"
lforms@^34.3.1:
version "34.3.1"
resolved "https://registry.npmjs.org/lforms/-/lforms-34.3.1.tgz#54f4e2bd9e1f5d99d1c7cad71849d1ee653fe8d4"
integrity sha512-nAstl+sHd+Fxdz+4plrxhbMz7u/tf3OGQY0uc3zI00fd/NFsEXyBSE74aOPgUKOPJw5PCkpQvUkiQO1pucZYjg==
dependencies:
"@angular/animations" "~14.2.12"
"@angular/common" "~14.2.12"
"@angular/compiler" "~14.2.12"
"@angular/core" "~14.2.12"
"@angular/elements" "^14.2.12"
"@angular/forms" "~14.2.12"
"@angular/platform-browser" "~14.2.12"
"@angular/platform-browser-dynamic" "~14.2.12"
"@angular/router" "~14.2.12"
"@lhncbc/ucum-lhc" "^4.1.6"
"@types/jquery" "^3.5.14"
autocomplete-lhc "^18.6.3"
deep-equal "^2.0.5"
fast-copy "^2.1.7"
fhirpath "3.2.0"
jquery "^3.6.1"
moment "^2.29.4"
ng-zorro-antd "^14.1.1"
rxjs "~6.6.7"
tslib "^2.4.1"
zone.js "~0.11.8"
lodash.get@~4.4.2:
version "4.4.2"
resolved "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==
moment@^2.29.4:
version "2.29.4"
resolved "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108"
integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==
ng-zorro-antd@^14.1.1:
version "14.3.0"
resolved "https://registry.npmjs.org/ng-zorro-antd/-/ng-zorro-antd-14.3.0.tgz#0cc5a406b62f4dcbcda2eea4d8d654c27c323dae"
integrity sha512-mGVok0DggrvVYTYWpbJJVekBs6j19kAkxB7PdZp0bvYRedpOVWKSEDX1Cigy7txnGw5UsSuzRSn3h6oZcBUmTA==
dependencies:
"@angular/cdk" "^14.1.0"
"@ant-design/icons-angular" "^14.1.0"
date-fns "^2.16.1"
tslib "^2.3.0"
object-inspect@^1.9.0:
version "1.13.1"
resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2"
integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==
object-is@^1.1.5:
version "1.1.5"
resolved "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac"
integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==
dependencies:
call-bind "^1.0.2"
define-properties "^1.1.3"
object-keys@^1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
object.assign@^4.1.4:
version "4.1.5"
resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0"
integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==
dependencies:
call-bind "^1.0.5"
define-properties "^1.2.1"
has-symbols "^1.0.3"
object-keys "^1.1.1"
parse5@^5.0.0:
version "5.1.1"
resolved "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178"
integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==
process-nextick-args@~2.0.0:
version "2.0.1"
resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
readable-stream@^2.1.0:
version "2.3.8"
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b"
integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==
dependencies:
core-util-is "~1.0.0"
inherits "~2.0.3"
isarray "~1.0.0"
process-nextick-args "~2.0.0"
safe-buffer "~5.1.1"
string_decoder "~1.1.1"
util-deprecate "~1.0.1"
regenerator-runtime@^0.14.0:
version "0.14.0"
resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45"
integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==
regexp.prototype.flags@^1.5.1:
version "1.5.1"
resolved "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz#90ce989138db209f81492edd734183ce99f9677e"
integrity sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==
dependencies:
call-bind "^1.0.2"
define-properties "^1.2.0"
set-function-name "^2.0.0"
rxjs@~6.6.7:
version "6.6.7"
resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9"
integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==
dependencies:
tslib "^1.9.0"
safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
sax@~1.1.1:
version "1.1.6"
resolved "https://registry.npmjs.org/sax/-/sax-1.1.6.tgz#5d616be8a5e607d54e114afae55b7eaf2fcc3240"
integrity sha512-8zci48uUQyfqynGDSkUMD7FCJB96hwLnlZOXlgs1l3TX+LW27t3psSWKUxC0fxVgA86i8tL4NwGcY1h/6t3ESg==
set-function-length@^1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz#4bc39fafb0307224a33e106a7d35ca1218d659ed"
integrity sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==
dependencies:
define-data-property "^1.1.1"
get-intrinsic "^1.2.1"
gopd "^1.0.1"
has-property-descriptors "^1.0.0"
set-function-name@^2.0.0:
version "2.0.1"
resolved "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a"
integrity sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==
dependencies:
define-data-property "^1.0.1"
functions-have-names "^1.2.3"
has-property-descriptors "^1.0.0"
side-channel@^1.0.4:
version "1.0.4"
resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==
dependencies:
call-bind "^1.0.0"
get-intrinsic "^1.0.2"
object-inspect "^1.9.0"
sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==
stop-iteration-iterator@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz#6a60be0b4ee757d1ed5254858ec66b10c49285e4"
integrity sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==
dependencies:
internal-slot "^1.0.4"
stream-transform@^0.1.1:
version "0.1.2"
resolved "https://registry.npmjs.org/stream-transform/-/stream-transform-0.1.2.tgz#7d8e6b4e03ac4781778f8c79517501bfb0762a9f"
integrity sha512-3HXId/0W8sktQnQM6rOZf2LuDDMbakMgAjpViLk758/h0br+iGqZFFfUxxJSqEvGvT742PyFr4v/TBXUtowdCg==
stream@0.0.2:
version "0.0.2"
resolved "https://registry.npmjs.org/stream/-/stream-0.0.2.tgz#7f5363f057f6592c5595f00bc80a27f5cec1f0ef"
integrity sha512-gCq3NDI2P35B2n6t76YJuOp7d6cN/C7Rt0577l91wllh0sY9ZBuw9KaSGqH/b0hzn3CWWJbpbW0W0WvQ1H/Q7g==
dependencies:
emitter-component "^1.1.1"
string-to-stream@^1.1.0:
version "1.1.1"
resolved "https://registry.npmjs.org/string-to-stream/-/string-to-stream-1.1.1.tgz#aba78f73e70661b130ee3e1c0192be4fef6cb599"
integrity sha512-QySF2+3Rwq0SdO3s7BAp4x+c3qsClpPQ6abAmb0DGViiSBAkT5kL6JT2iyzEVP+T1SmzHrQD1TwlP9QAHCc+Sw==
dependencies:
inherits "^2.0.1"
readable-stream "^2.1.0"
string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
dependencies:
safe-buffer "~5.1.0"
tslib@^1.9.0:
version "1.14.1"
resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@^2.0.0, tslib@^2.3.0, tslib@^2.4.1:
version "2.6.2"
resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
which-boxed-primitive@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==
dependencies:
is-bigint "^1.0.1"
is-boolean-object "^1.1.0"
is-number-object "^1.0.4"
is-string "^1.0.5"
is-symbol "^1.0.3"
which-collection@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906"
integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==
dependencies:
is-map "^2.0.1"
is-set "^2.0.1"
is-weakmap "^2.0.1"
is-weakset "^2.0.1"
which-typed-array@^1.1.11, which-typed-array@^1.1.13:
version "1.1.13"
resolved "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz#870cd5be06ddb616f504e7b039c4c24898184d36"
integrity sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==
dependencies:
available-typed-arrays "^1.0.5"
call-bind "^1.0.4"
for-each "^0.3.3"
gopd "^1.0.1"
has-tostringtag "^1.0.0"
xmldoc@^0.4.0:
version "0.4.0"
resolved "https://registry.npmjs.org/xmldoc/-/xmldoc-0.4.0.tgz#d257224be8393eaacbf837ef227fd8ec25b36888"
integrity sha512-rJ/+/UzYCSlFNuAzGuRyYgkH2G5agdX1UQn4+5siYw9pkNC3Hu/grYNDx/dqYLreeSjnY5oKg74CMBKxJHSg6Q==
dependencies:
sax "~1.1.1"
zone.js@~0.11.8:
version "0.11.8"
resolved "https://registry.npmjs.org/zone.js/-/zone.js-0.11.8.tgz#40dea9adc1ad007b5effb2bfed17f350f1f46a21"
integrity sha512-82bctBg2hKcEJ21humWIkXRlLBBmrc3nN7DFh5LGGhcyycO2S7FN8NmdvlcKaGFDNVL4/9kFLmwmInTavdJERA==
dependencies:
tslib "^2.3.0"