diff --git a/src/lib/components/app/FetchSoF.svelte b/src/lib/components/app/FetchSoF.svelte index e72851b..df20d52 100644 --- a/src/lib/components/app/FetchSoF.svelte +++ b/src/lib/components/app/FetchSoF.svelte @@ -40,7 +40,7 @@ try { if (sofHost) { try { - authorize(sofHost.url, sofHost.clientId); + authorize(sofHost.url, sofHost.clientId, sofHost.clientSecret ?? undefined); authDispatch('sof-auth-init'); } catch (e) { authDispatch('sof-auth-fail') diff --git a/src/lib/config.ts b/src/lib/config.ts index fae2b8c..8abcd81 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -43,18 +43,26 @@ export const SOF_HOSTS = [ clientId: "", note: "Credentials provided" }, - // { - // id: "c4bb", - // name: "CARIN Blue Button", - // // url: "https://chpdc-api-sita.carefirst.com/v1/fhir/patientaccess", - // // url: "https://chpmd-api-sita.carefirst.com/v1/fhir/patientaccess", - // // url: "https://dsnp-api-sita.carefirst.com/v1/fhir/patientaccess", - // // url: "https://mapd-api-sita.carefirst.com/v1/fhir/patientaccess", - // // url: "https://mhbe-api-sita.carefirst.com/v1/fhir/patientaccess", - // url: "https://egwp-api-sita.carefirst.com/v1/fhir/patientaccess", - // clientId: "0oaf5w78xfWspNqeA1d7", - // note: "Credentials provided" - // } + { + id: "aetna", + name: "AETNA Insurance Sandbox", + url: "https://vteapif1.aetna.com/fhirdemo/v1/patientaccess", + clientId: "09cbb76344009c25a2ec587b39ebc303", + clientSecret: "4fe39eccbfc586647407ea19f408521f", + note: "VTETestUser01 / FHIRdemo2020" + }, + { + id: "c4bb", + name: "CARIN Blue Button", + // url: "https://chpdc-api-sita.carefirst.com/v1/fhir/patientaccess", + // url: "https://chpmd-api-sita.carefirst.com/v1/fhir/patientaccess", + // url: "https://dsnp-api-sita.carefirst.com/v1/fhir/patientaccess", + // url: "https://mapd-api-sita.carefirst.com/v1/fhir/patientaccess", + // url: "https://mhbe-api-sita.carefirst.com/v1/fhir/patientaccess", + url: "https://egwp-api-sita.carefirst.com/v1/fhir/patientaccess", + clientId: "0oaf5w78xfWspNqeA1d7", + note: "Credentials provided" + }, ]; export const BEARER_AUTHORIZATION = { @@ -87,19 +95,19 @@ export const SOF_PATIENT_RESOURCES = [ 'AllergyIntolerance', // 'MedicationStatement', // Not in EPIC USCDI R4 'MedicationRequest', - // 'Medication', // can't search by patient; "Only an _ID search is allowed." + // 'Medication', // Pulled in via references - can't search by patient; "Only an _ID search is allowed." 'Condition', 'Encounter', - // 'Observation', // "Must have either code or category." - // 'Organization', // can't search by patient; "Only an _ID search is allowed." + // 'Observation', // Handle specially for IPS codes - "Must have either code or category." + // 'Organization', // Pulled in via references - can't search by patient; "Only an _ID search is allowed." 'Immunization', // 'Device', // 'DeviceUseStatement', // Not in EPIC USCDI R4 'DiagnosticReport', // TODO change to subject // 'ImagingStudy', // Not in EPIC USCDI R4 // 'Media', // Not in EPIC USCDI R4 - // 'Practitioner', // can't search by patient; "Either name, family, or identifier is a required parameter." - // 'PractitionerRole', // can't search by patient; "An identifier, practitioner, organization, location, or specialty parameter is required." + // 'Practitioner', // Pulled in via references - can't search by patient; "Either name, family, or identifier is a required parameter." + // 'PractitionerRole', // Pulled in via references - can't search by patient; "An identifier, practitioner, organization, location, or specialty parameter is required." 'Procedure', // TODO change to subject // 'Specimen', // Not in EPIC USCDI R4 ]; diff --git a/src/lib/types.ts b/src/lib/types.ts new file mode 100644 index 0000000..057a468 --- /dev/null +++ b/src/lib/types.ts @@ -0,0 +1,87 @@ +export type Bundle = any; +export interface SHLSubmitEvent { + shcs: SHCFile[]; + label?: string; + // content: Bundle; + passcode?: string; + exp?: number; + patientName?: string; +} + +export interface ResourceRetrieveEvent { + resources: Array | undefined; + source?: string | undefined; +} +export interface SHCRetrieveEvent { + shc: SHCFile | undefined; + source?: string | undefined; +} +export interface IPSRetrieveEvent { + ips: Bundle | undefined; + source?: string | undefined; +} +export interface SOFAuthEvent { + data: any | undefined; +} +export interface SHCFile { + verifiableCredential: string[]; +} + +export interface SOFHost { + id:string; + name:string; + url:string; + clientId:string; + clientSecret?: string; + note:string | undefined; +} + +export class ResourceHelper { + tempId: string; + original_resource: any; + simple_resource: any; + resource: any; + include: boolean = true; + + // Constructor + constructor(resource:any) { + this.original_resource = resource; + this.simple_resource = this.simplify(resource); + this.resource = resource; + this.tempId = this.hash(this.simple_resource); + } + + hash(value:any) { + return JSON.stringify(value); + // return crypto.createHash('sha1').update(value).digest('hex'); + } + + simplify(resource:any) { + let simpleResource = JSON.parse(JSON.stringify(resource)); + delete simpleResource.id; + delete simpleResource.meta; + delete simpleResource.text; + // delete simpleResource.patient; + // delete simpleResource.subject; + // delete simpleResource.encounter; + // delete simpleResource.requester; + return this.removeEntries(simpleResource, "reference"); + } + + removeEntries(obj:any, key:string) { + if (typeof obj === "object") { + for (let k in obj) { + if (k === key) { + delete obj[k]; + } else { + obj[k] = this.removeEntries(obj[k], key); + } + } + } else if (obj instanceof Array) { + for (let i=0; i < obj.length; i++) { + obj[i] = this.removeEntries(obj[i], key); + } + } + return obj; + } +} diff --git a/src/lib/utils/sofClient.js b/src/lib/utils/sofClient.js index 4e9ddf4..991207e 100644 --- a/src/lib/utils/sofClient.js +++ b/src/lib/utils/sofClient.js @@ -7,7 +7,8 @@ const patientResourceScope = SOF_PATIENT_RESOURCES.map(resourceType => `patient/ const resourceScope = patientResourceScope.join(" "); const config = { clientId: '(ehr client id, populated later)', // clientId() is ignored at smit - scope: `openid fhirUser launch/patient ${resourceScope}`, + // scope: `openid fhirUser launch/patient ${resourceScope}`, + scope: `launch/patient patient/*.read`, iss: '(authorization url, populated later)', redirect_uri: SOF_REDIRECT_URI }; @@ -70,9 +71,8 @@ async function requestResources(client, resourceType) { async function activePatient() { if (client === undefined) { client = await FHIR.oauth2.ready(); - return client.getPatientId(); } - return null + return client.getPatientId() ?? undefined; } async function getResources() { @@ -85,6 +85,7 @@ async function getResources() { // Establish resource display methods let resources; if (client.state.clientId === "XfubBaEQzzHCOvgeB9Q7qZbg4QcK3Jro_65w5VWFRP8") { + // Minimum required requests for eClinicalWorks HIMSS 2024 demo resources = (await Promise.allSettled(['Patient', 'Immunization'].map((resourceType) => { return requestResources(client, resourceType); }))).filter(x => x.status == "fulfilled").map(x => x.value);