diff --git a/icc-api/api/XHR.ts b/icc-api/api/XHR.ts index 14233d1b..b1571e26 100644 --- a/icc-api/api/XHR.ts +++ b/icc-api/api/XHR.ts @@ -38,12 +38,7 @@ export namespace XHR { url: string, init: RequestInit, timeout = 10000, - fetchImpl: (input: RequestInfo, init?: RequestInit) => Promise = typeof window !== - "undefined" - ? window.fetch - : typeof self !== "undefined" - ? self.fetch - : fetch + fetchImpl?: (input: RequestInfo, init?: RequestInit) => Promise ): Promise { return new Promise((resolve, reject) => { // Set timeout timer @@ -51,7 +46,7 @@ export namespace XHR { () => reject({ message: "Request timed out", status: "Request timed out" }), timeout ) - fetchImpl(url, init) + ;(fetchImpl || window.fetch)(url, init) .then(response => { clearTimeout(timer) resolve(response) @@ -68,12 +63,7 @@ export namespace XHR { url: string, headers: Array
| null, data: string | any = "", - fetchImpl: (input: RequestInfo, init?: RequestInit) => Promise = typeof window !== - "undefined" - ? window.fetch - : typeof self !== "undefined" - ? self.fetch - : fetch, + fetchImpl?: (input: RequestInfo, init?: RequestInit) => Promise, contentTypeOverride?: "application/json" | "text/plain" | "application/octet-stream" ): Promise { const contentType = @@ -124,7 +114,9 @@ export namespace XHR { ? response.json() : ct.startsWith("application/xml") || ct.startsWith("text/") ? response.text() - : response.arrayBuffer() + : response.arrayBuffer + ? response.arrayBuffer() + : response.blob().then(blob => new Response(blob).arrayBuffer()) ).then(d => new Data(response.status, ct, d)) }) } diff --git a/icc-api/api/iccApplicationsettingsApi.ts b/icc-api/api/iccApplicationSettingsApi.ts similarity index 100% rename from icc-api/api/iccApplicationsettingsApi.ts rename to icc-api/api/iccApplicationSettingsApi.ts diff --git a/icc-api/api/iccBedrugsApi.ts b/icc-api/api/iccBeDrugsApi.ts similarity index 100% rename from icc-api/api/iccBedrugsApi.ts rename to icc-api/api/iccBeDrugsApi.ts diff --git a/icc-api/api/iccBeefactApi.ts b/icc-api/api/iccBeEfactApi.ts similarity index 100% rename from icc-api/api/iccBeefactApi.ts rename to icc-api/api/iccBeEfactApi.ts diff --git a/icc-api/api/iccBekmehrApi.ts b/icc-api/api/iccBeKmehrApi.ts similarity index 97% rename from icc-api/api/iccBekmehrApi.ts rename to icc-api/api/iccBeKmehrApi.ts index aa402ad7..12f242a5 100644 --- a/icc-api/api/iccBekmehrApi.ts +++ b/icc-api/api/iccBeKmehrApi.ts @@ -84,6 +84,7 @@ export class iccBeKmehrApi { date?: number, language?: string, recipientNihii?: string, + recipientSsin?: string, recipientFirstName?: string, recipientLastName?: string, mimeType?: string, @@ -102,6 +103,7 @@ export class iccBeKmehrApi { (date ? "&date=" + date : "") + (language ? "&language=" + language : "") + (recipientNihii ? "&recipientNihii=" + recipientNihii : "") + + (recipientSsin ? "&recipientSsin=" + recipientSsin : "") + (recipientFirstName ? "&recipientFirstName=" + recipientFirstName : "") + (recipientLastName ? "&recipientLastName=" + recipientLastName : "") + (mimeType ? "&mimeType=" + mimeType : "") @@ -141,6 +143,7 @@ export class iccBeKmehrApi { date?: number, language?: string, recipientNihii?: string, + recipientSsin?: string, recipientFirstName?: string, recipientLastName?: string, mimeType?: string, @@ -159,6 +162,7 @@ export class iccBeKmehrApi { (date ? "&date=" + date : "") + (language ? "&language=" + language : "") + (recipientNihii ? "&recipientNihii=" + recipientNihii : "") + + (recipientSsin ? "&recipientSsin=" + recipientSsin : "") + (recipientFirstName ? "&recipientFirstName=" + recipientFirstName : "") + (recipientLastName ? "&recipientLastName=" + recipientLastName : "") + (mimeType ? "&mimeType=" + mimeType : "") @@ -202,6 +206,7 @@ export class iccBeKmehrApi { date?: number, language?: string, recipientNihii?: string, + recipientSsin?: string, recipientFirstName?: string, recipientLastName?: string, mimeType?: string, @@ -220,6 +225,7 @@ export class iccBeKmehrApi { (date ? "&date=" + date : "") + (language ? "&language=" + language : "") + (recipientNihii ? "&recipientNihii=" + recipientNihii : "") + + (recipientSsin ? "&recipientSsin=" + recipientSsin : "") + (recipientFirstName ? "&recipientFirstName=" + recipientFirstName : "") + (recipientLastName ? "&recipientLastName=" + recipientLastName : "") + (mimeType ? "&mimeType=" + mimeType : "") @@ -237,6 +243,7 @@ export class iccBeKmehrApi { date?: number, language?: string, recipientNihii?: string, + recipientSsin?: string, recipientFirstName?: string, recipientLastName?: string, mimeType?: string, @@ -255,6 +262,7 @@ export class iccBeKmehrApi { (date ? "&date=" + date : "") + (language ? "&language=" + language : "") + (recipientNihii ? "&recipientNihii=" + recipientNihii : "") + + (recipientSsin ? "&recipientSsin=" + recipientSsin : "") + (recipientFirstName ? "&recipientFirstName=" + recipientFirstName : "") + (recipientLastName ? "&recipientLastName=" + recipientLastName : "") + (mimeType ? "&mimeType=" + mimeType : "") @@ -272,6 +280,7 @@ export class iccBeKmehrApi { date?: number, language?: string, recipientNihii?: string, + recipientSsin?: string, recipientFirstName?: string, recipientLastName?: string, mimeType?: string, @@ -290,6 +299,7 @@ export class iccBeKmehrApi { (date ? "&date=" + date : "") + (language ? "&language=" + language : "") + (recipientNihii ? "&recipientNihii=" + recipientNihii : "") + + (recipientSsin ? "&recipientSsin=" + recipientSsin : "") + (recipientFirstName ? "&recipientFirstName=" + recipientFirstName : "") + (recipientLastName ? "&recipientLastName=" + recipientLastName : "") + (mimeType ? "&mimeType=" + mimeType : "") @@ -307,6 +317,7 @@ export class iccBeKmehrApi { date?: number, language?: string, recipientNihii?: string, + recipientSsin?: string, recipientFirstName?: string, recipientLastName?: string, mimeType?: string, @@ -325,6 +336,7 @@ export class iccBeKmehrApi { (date ? "&date=" + date : "") + (language ? "&language=" + language : "") + (recipientNihii ? "&recipientNihii=" + recipientNihii : "") + + (recipientSsin ? "&recipientSsin=" + recipientSsin : "") + (recipientFirstName ? "&recipientFirstName=" + recipientFirstName : "") + (recipientLastName ? "&recipientLastName=" + recipientLastName : "") + (mimeType ? "&mimeType=" + mimeType : "") @@ -342,6 +354,7 @@ export class iccBeKmehrApi { date?: number, language?: string, recipientNihii?: string, + recipientSsin?: string, recipientFirstName?: string, recipientLastName?: string, mimeType?: string, @@ -360,6 +373,7 @@ export class iccBeKmehrApi { (date ? "&date=" + date : "") + (language ? "&language=" + language : "") + (recipientNihii ? "&recipientNihii=" + recipientNihii : "") + + (recipientSsin ? "&recipientSsin=" + recipientSsin : "") + (recipientFirstName ? "&recipientFirstName=" + recipientFirstName : "") + (recipientLastName ? "&recipientLastName=" + recipientLastName : "") + (mimeType ? "&mimeType=" + mimeType : "") diff --git a/icc-api/api/iccBemikronoApi.ts b/icc-api/api/iccBeMikronoApi.ts similarity index 100% rename from icc-api/api/iccBemikronoApi.ts rename to icc-api/api/iccBeMikronoApi.ts diff --git a/icc-api/api/iccBeresultexportApi.ts b/icc-api/api/iccBeResultExportApi.ts similarity index 100% rename from icc-api/api/iccBeresultexportApi.ts rename to icc-api/api/iccBeResultExportApi.ts diff --git a/icc-api/api/iccBeresultimportApi.ts b/icc-api/api/iccBeResultImportApi.ts similarity index 100% rename from icc-api/api/iccBeresultimportApi.ts rename to icc-api/api/iccBeResultImportApi.ts diff --git a/icc-api/api/iccClusterApi.ts b/icc-api/api/iccClusterApi.ts deleted file mode 100644 index 7effcf9e..00000000 --- a/icc-api/api/iccClusterApi.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * - * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen) - * - * OpenAPI spec version: 1.0.2 - * - * - * NOTE: This class is auto generated by the swagger code generator program. - * https://github.com/swagger-api/swagger-codegen.git - * Do not edit the class manually. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { XHR } from "./XHR" -import * as models from "../model/models" - -export class iccClusterApi { - host: string - headers: Array - fetchImpl?: (input: RequestInfo, init?: RequestInit) => Promise - - constructor( - host: string, - headers: any, - fetchImpl?: (input: RequestInfo, init?: RequestInit) => Promise - ) { - this.host = host - this.headers = Object.keys(headers).map(k => new XHR.Header(k, headers[k])) - this.fetchImpl = fetchImpl - } - - setHeaders(h: Array) { - this.headers = h - } - - handleError(e: XHR.Data) { - if (e.status == 401) throw Error("auth-failed") - else throw Error("api-error" + e.status) - } - - groupSyncStatus(): Promise { - let _body = null - - const _url = this.host + "/cluster/gsyncs" + "?ts=" + new Date().getTime() - let headers = this.headers - headers = headers - .filter(h => h.header !== "Content-Type") - .concat(new XHR.Header("Content-Type", "application/json")) - return XHR.sendCommand("GET", _url, headers, _body, this.fetchImpl) - .then(doc => new models.CodePaginatedList(doc.body as JSON)) - .catch(err => this.handleError(err)) - } -} diff --git a/icc-api/api/iccEntitytemplateApi.ts b/icc-api/api/iccEntitytemplateApi.ts index 1dc3c052..686dafaa 100644 --- a/icc-api/api/iccEntitytemplateApi.ts +++ b/icc-api/api/iccEntitytemplateApi.ts @@ -62,6 +62,26 @@ export class iccEntitytemplateApi { .then(doc => new models.EntityTemplateDto(doc.body as JSON)) .catch(err => this.handleError(err)) } + + deleteEntityTemplate(entityTemplateIds: string): Promise { + let _body = null + + const _url = + this.host + + "/entitytemplate/{entityTemplateIds}".replace( + "{entityTemplateIds}", + entityTemplateIds + "" + ) + + "?ts=" + + new Date().getTime() + let headers = this.headers + headers = headers + .filter(h => h.header !== "Content-Type") + .concat(new XHR.Header("Content-Type", "application/json")) + return XHR.sendCommand("DELETE", _url, headers, _body, this.fetchImpl) + .catch(err => this.handleError(err)) + } + findAllEntityTemplates( type: string, searchString?: string, diff --git a/icc-api/api/iccGroupApi.ts b/icc-api/api/iccGroupApi.ts deleted file mode 100644 index eb179833..00000000 --- a/icc-api/api/iccGroupApi.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** - * - * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen) - * - * OpenAPI spec version: 1.0.2 - * - * - * NOTE: This class is auto generated by the swagger code generator program. - * https://github.com/swagger-api/swagger-codegen.git - * Do not edit the class manually. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { XHR } from "./XHR" -import * as models from "../model/models" - -export class iccGroupApi { - host: string - headers: Array - fetchImpl?: (input: RequestInfo, init?: RequestInit) => Promise - - constructor( - host: string, - headers: any, - fetchImpl?: (input: RequestInfo, init?: RequestInit) => Promise - ) { - this.host = host - this.headers = Object.keys(headers).map(k => new XHR.Header(k, headers[k])) - this.fetchImpl = fetchImpl - } - - setHeaders(h: Array) { - this.headers = h - } - - handleError(e: XHR.Data) { - if (e.status == 401) throw Error("auth-failed") - else throw Error("api-error" + e.status) - } - - createGroup( - id: string, - name?: string, - password?: string, - body?: models.ReplicationDto - ): Promise { - let _body = null - _body = body - - const _url = - this.host + - "/group/{id}".replace("{id}", id + "") + - "?ts=" + - new Date().getTime() + - (name ? "&name=" + name : "") + - (password ? "&password=" + password : "") - let headers = this.headers - headers = headers - .filter(h => h.header !== "Content-Type") - .concat(new XHR.Header("Content-Type", "application/json")) - return XHR.sendCommand("POST", _url, headers, _body, this.fetchImpl) - .then(doc => new models.GroupDto(doc.body as JSON)) - .catch(err => this.handleError(err)) - } -} diff --git a/icc-api/api/iccIcureApi.ts b/icc-api/api/iccIcureApi.ts index 7bf48bc7..8726a7bd 100644 --- a/icc-api/api/iccIcureApi.ts +++ b/icc-api/api/iccIcureApi.ts @@ -238,14 +238,15 @@ export class iccIcureApi { .then(doc => true) .catch(err => this.handleError(err)) } - updateDesignDoc(entityName: string): Promise { + updateDesignDoc(entityName: string, warmup = false): Promise { let _body = null const _url = this.host + "/icure/dd/{entityName}".replace("{entityName}", entityName + "") + "?ts=" + - new Date().getTime() + new Date().getTime() + + (!!warmup ? "&warmup=" + "true" : "false") let headers = this.headers headers = headers .filter(h => h.header !== "Content-Type") diff --git a/icc-api/api/iccMedicallocationApi.ts b/icc-api/api/iccMedicalLocationApi.ts similarity index 100% rename from icc-api/api/iccMedicallocationApi.ts rename to icc-api/api/iccMedicalLocationApi.ts diff --git a/icc-api/api/iccMessageApi.ts b/icc-api/api/iccMessageApi.ts index 226f925e..6d44c14d 100644 --- a/icc-api/api/iccMessageApi.ts +++ b/icc-api/api/iccMessageApi.ts @@ -165,9 +165,9 @@ export class iccMessageApi { new Date().getTime() + (fromAddress ? "&fromAddress=" + fromAddress : "") + (startKey ? "&startKey=" + startKey : "") + - (startDocumentId ? "&startDocumentId=" + startDocumentId : "") + + (startDocumentId ? "&startDocumentId=" + encodeURIComponent(startDocumentId) : "") + (limit ? "&limit=" + limit : "") + - (hcpId ? "&hcpId=" + hcpId : "") + (hcpId ? "&hcpId=" + encodeURIComponent(hcpId) : "") let headers = this.headers headers = headers .filter(h => h.header !== "Content-Type") @@ -193,10 +193,10 @@ export class iccMessageApi { new Date().getTime() + (toAddress ? "&toAddress=" + toAddress : "") + (startKey ? "&startKey=" + startKey : "") + - (startDocumentId ? "&startDocumentId=" + startDocumentId : "") + + (startDocumentId ? "&startDocumentId=" + encodeURIComponent(startDocumentId) : "") + (limit ? "&limit=" + limit : "") + (reverse ? "&reverse=" + reverse : "") + - (hcpId ? "&hcpId=" + hcpId : "") + (hcpId ? "&hcpId=" + encodeURIComponent(hcpId) : "") let headers = this.headers headers = headers .filter(h => h.header !== "Content-Type") @@ -223,9 +223,9 @@ export class iccMessageApi { (transportGuid ? "&transportGuid=" + transportGuid : "") + (received ? "&received=" + received : "") + (startKey ? "&startKey=" + startKey : "") + - (startDocumentId ? "&startDocumentId=" + startDocumentId : "") + + (startDocumentId ? "&startDocumentId=" + encodeURIComponent(startDocumentId) : "") + (limit ? "&limit=" + limit : "") + - (hcpId ? "&hcpId=" + hcpId : "") + (hcpId ? "&hcpId=" + encodeURIComponent(hcpId) : "") let headers = this.headers headers = headers .filter(h => h.header !== "Content-Type") @@ -254,9 +254,9 @@ export class iccMessageApi { (from ? "&from=" + from : "") + (to ? "&to=" + to : "") + (startKey ? "&startKey=" + startKey : "") + - (startDocumentId ? "&startDocumentId=" + startDocumentId : "") + + (startDocumentId ? "&startDocumentId=" + encodeURIComponent(startDocumentId) : "") + (limit ? "&limit=" + limit : "") + - (hcpId ? "&hcpId=" + hcpId : "") + (hcpId ? "&hcpId=" + encodeURIComponent(hcpId) : "") let headers = this.headers headers = headers .filter(h => h.header !== "Content-Type") diff --git a/icc-api/api/iccUserApi.ts b/icc-api/api/iccUserApi.ts index 5a8a4650..3c77326d 100644 --- a/icc-api/api/iccUserApi.ts +++ b/icc-api/api/iccUserApi.ts @@ -164,18 +164,6 @@ export class iccUserApi { .then(doc => new models.UserDto(doc.body as JSON)) .catch(err => this.handleError(err)) } - getMatchingUsers(): Promise | any> { - let _body = null - - const _url = this.host + "/user/matches" + "?ts=" + new Date().getTime() - let headers = this.headers - headers = headers - .filter(h => h.header !== "Content-Type") - .concat(new XHR.Header("Content-Type", "application/json")) - return XHR.sendCommand("GET", _url, headers, _body, this.fetchImpl) - .then(doc => (doc.body as Array).map(it => new models.UserDto(it))) - .catch(err => this.handleError(err)) - } getUser(userId: string): Promise { let _body = null diff --git a/icc-api/iccApi.ts b/icc-api/iccApi.ts index 788826c8..14b9ba8f 100644 --- a/icc-api/iccApi.ts +++ b/icc-api/iccApi.ts @@ -14,7 +14,6 @@ export * from "./api/iccCalendarItemApi" export * from "./api/iccCalendarItemTypeApi" export * from "./api/iccClassificationApi" export * from "./api/iccClassificationTemplateApi" -export * from "./api/iccClusterApi" export * from "./api/iccCodeApi" export * from "./api/iccContactApi" export * from "./api/iccDoctemplateApi" @@ -24,7 +23,6 @@ export * from "./api/iccEntitytemplateApi" export * from "./api/iccFormApi" export * from "./api/iccFrontendmigrationApi" export * from "./api/iccGenericApi" -export * from "./api/iccGroupApi" export * from "./api/iccHcpartyApi" export * from "./api/iccHelementApi" export * from "./api/iccIcureApi" diff --git a/icc-api/model/AccessLogDto.ts b/icc-api/model/AccessLogDto.ts index 888c665a..ddab5bd2 100644 --- a/icc-api/model/AccessLogDto.ts +++ b/icc-api/model/AccessLogDto.ts @@ -64,6 +64,8 @@ export class AccessLogDto { date?: number + patientId?: string + objectId?: string user?: string diff --git a/icc-api/model/AddressDto.ts b/icc-api/model/AddressDto.ts index 39375a9b..101a841f 100644 --- a/icc-api/model/AddressDto.ts +++ b/icc-api/model/AddressDto.ts @@ -53,13 +53,21 @@ export class AddressDto { telecoms?: Array } export namespace AddressDto { - export enum AddressTypeEnum { - Home = "home", - Work = "work", - Vacation = "vacation", - Hospital = "hospital", - Clinic = "clinic", - Hq = "hq", - Other = "other" + export type AddressTypeEnum = + | "home" + | "work" + | "vacation" + | "hospital" + | "clinic" + | "hq" + | "other" + export const AddressTypeEnum = { + Home: "home" as AddressTypeEnum, + Work: "work" as AddressTypeEnum, + Vacation: "vacation" as AddressTypeEnum, + Hospital: "hospital" as AddressTypeEnum, + Clinic: "clinic" as AddressTypeEnum, + Hq: "hq" as AddressTypeEnum, + Other: "other" as AddressTypeEnum } } diff --git a/icc-api/model/AmppDto.ts b/icc-api/model/AmppDto.ts index 10dc8b07..83890849 100644 --- a/icc-api/model/AmppDto.ts +++ b/icc-api/model/AmppDto.ts @@ -96,9 +96,9 @@ export class AmppDto { dmpps?: Array - singleUse?: boolean - orphan?: boolean + + singleUse?: boolean } export namespace AmppDto { export enum StatusEnum { diff --git a/icc-api/model/ContentDto.ts b/icc-api/model/ContentDto.ts index ab49a3dc..f4a52fcc 100644 --- a/icc-api/model/ContentDto.ts +++ b/icc-api/model/ContentDto.ts @@ -44,5 +44,7 @@ export class ContentDto { medicationValue?: models.MedicationDto + compoundValue?: Array + binaryValue?: Array } diff --git a/icc-api/model/GroupDto.ts b/icc-api/model/GroupDto.ts deleted file mode 100644 index d051266d..00000000 --- a/icc-api/model/GroupDto.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * - * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen) - * - * OpenAPI spec version: 1.0.2 - * - * - * NOTE: This class is auto generated by the swagger code generator program. - * https://github.com/swagger-api/swagger-codegen.git - * Do not edit the class manually. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import * as models from "./models" - -export class GroupDto { - constructor(json: JSON | any) { - Object.assign(this as GroupDto, json) - } - name?: string - - password?: string - - attachments?: { [key: string]: models.Attachment } - - deleted?: number - - id?: string - - rev?: string - - revsInfo?: Array - - conflicts?: Array - - javaType?: string - - revHistory?: { [key: string]: string } -} diff --git a/icc-api/model/HealthElementDto.ts b/icc-api/model/HealthElementDto.ts index 53961c35..8bd2160f 100644 --- a/icc-api/model/HealthElementDto.ts +++ b/icc-api/model/HealthElementDto.ts @@ -82,9 +82,17 @@ export class HealthElementDto { idService?: string + laterality?: HealthElementDto.LateralityEnum + plansOfAction?: Array episodes?: Array careTeam?: Array } +export namespace HealthElementDto { + export enum LateralityEnum { + Left = "left", + Right = "right" + } +} diff --git a/icc-api/model/ImportResultDto.ts b/icc-api/model/ImportResultDto.ts index 8c7bcc32..9ea5f582 100644 --- a/icc-api/model/ImportResultDto.ts +++ b/icc-api/model/ImportResultDto.ts @@ -43,4 +43,6 @@ export class ImportResultDto { hcps?: Array documents?: Array + + attachments?: { [key: string]: models.Attachment } } diff --git a/icc-api/model/InvoiceDto.ts b/icc-api/model/InvoiceDto.ts index 9a30043f..b96a0997 100644 --- a/icc-api/model/InvoiceDto.ts +++ b/icc-api/model/InvoiceDto.ts @@ -159,6 +159,8 @@ export class InvoiceDto { receipts?: { [key: string]: string } idDocument?: models.IdentityDocumentReader + + options?: { [key: string]: string } } export namespace InvoiceDto { export enum PaymentTypeEnum { diff --git a/icc-api/model/InvoiceItem.ts b/icc-api/model/InvoiceItem.ts index 6e15d249..bb177b71 100644 --- a/icc-api/model/InvoiceItem.ts +++ b/icc-api/model/InvoiceItem.ts @@ -1,110 +1,101 @@ /** + * iCure Cloud API Documentation + * Spring shop sample application * - * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen) - * - * OpenAPI spec version: 1.0.2 + * OpenAPI spec version: v0.0.1 * * * NOTE: This class is auto generated by the swagger code generator program. * https://github.com/swagger-api/swagger-codegen.git * Do not edit the class manually. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. */ - -import * as models from "./models" +import { EIDItem } from "./EIDItem" export class InvoiceItem { constructor(json: JSON | any) { Object.assign(this as InvoiceItem, json) } - dateCode?: number + dateCode?: number codeNomenclature?: number - relatedCode?: number - - eidItem?: models.EIDItem - + eidItem?: EIDItem insuranceRef?: string - insuranceRefDate?: number - units?: number - reimbursedAmount?: number - patientFee?: number - doctorSupplement?: number - sideCode?: InvoiceItem.SideCodeEnum - timeOfDay?: InvoiceItem.TimeOfDayEnum - override3rdPayerCode?: number - gnotionNihii?: string - derogationMaxNumber?: InvoiceItem.DerogationMaxNumberEnum - prescriberNorm?: InvoiceItem.PrescriberNormEnum - prescriberNihii?: string - personalInterventionCoveredByThirdPartyCode?: number - doctorIdentificationNumber?: string - invoiceRef?: string - percentNorm?: InvoiceItem.PercentNormEnum } export namespace InvoiceItem { - export enum SideCodeEnum { - None = "None", - Left = "Left", - Right = "Right" + export type SideCodeEnum = "None" | "Left" | "Right" + export const SideCodeEnum = { + None: "None" as SideCodeEnum, + Left: "Left" as SideCodeEnum, + Right: "Right" as SideCodeEnum } - export enum TimeOfDayEnum { - Other = "Other", - Night = "Night", - Weekend = "Weekend", - Bankholiday = "Bankholiday", - Urgent = "Urgent" + export type TimeOfDayEnum = "Other" | "Night" | "Weekend" | "Bankholiday" | "Urgent" + export const TimeOfDayEnum = { + Other: "Other" as TimeOfDayEnum, + Night: "Night" as TimeOfDayEnum, + Weekend: "Weekend" as TimeOfDayEnum, + Bankholiday: "Bankholiday" as TimeOfDayEnum, + Urgent: "Urgent" as TimeOfDayEnum } - export enum DerogationMaxNumberEnum { - Other = "Other", - DerogationMaxNumber = "DerogationMaxNumber", - OtherPrescription = "OtherPrescription", - SecondPrestationOfDay = "SecondPrestationOfDay", - ThirdAndNextPrestationOfDay = "ThirdAndNextPrestationOfDay" + export type DerogationMaxNumberEnum = + | "Other" + | "DerogationMaxNumber" + | "OtherPrescription" + | "SecondPrestationOfDay" + | "ThirdAndNextPrestationOfDay" + export const DerogationMaxNumberEnum = { + Other: "Other" as DerogationMaxNumberEnum, + DerogationMaxNumber: "DerogationMaxNumber" as DerogationMaxNumberEnum, + OtherPrescription: "OtherPrescription" as DerogationMaxNumberEnum, + SecondPrestationOfDay: "SecondPrestationOfDay" as DerogationMaxNumberEnum, + ThirdAndNextPrestationOfDay: "ThirdAndNextPrestationOfDay" as DerogationMaxNumberEnum } - export enum PrescriberNormEnum { - None = "None", - OnePrescriber = "OnePrescriber", - SelfPrescriber = "SelfPrescriber", - AddedCode = "AddedCode", - ManyPrescribers = "ManyPrescribers" + export type PrescriberNormEnum = + | "None" + | "OnePrescriber" + | "SelfPrescriber" + | "AddedCode" + | "ManyPrescribers" + export const PrescriberNormEnum = { + None: "None" as PrescriberNormEnum, + OnePrescriber: "OnePrescriber" as PrescriberNormEnum, + SelfPrescriber: "SelfPrescriber" as PrescriberNormEnum, + AddedCode: "AddedCode" as PrescriberNormEnum, + ManyPrescribers: "ManyPrescribers" as PrescriberNormEnum } - export enum PercentNormEnum { - None = "None", - SurgicalAid1 = "SurgicalAid1", - SurgicalAid2 = "SurgicalAid2", - ReducedFee = "ReducedFee", - Ah1n1 = "Ah1n1", - HalfPriceSecondAct = "HalfPriceSecondAct", - InvoiceException = "InvoiceException", - ForInformation = "ForInformation" + export type PercentNormEnum = + | "None" + | "SurgicalAid1" + | "SurgicalAid2" + | "ReducedFee" + | "Ah1n1" + | "HalfPriceSecondAct" + | "InvoiceException" + | "ForInformation" + export const PercentNormEnum = { + None: "None" as PercentNormEnum, + SurgicalAid1: "SurgicalAid1" as PercentNormEnum, + SurgicalAid2: "SurgicalAid2" as PercentNormEnum, + ReducedFee: "ReducedFee" as PercentNormEnum, + Ah1n1: "Ah1n1" as PercentNormEnum, + HalfPriceSecondAct: "HalfPriceSecondAct" as PercentNormEnum, + InvoiceException: "InvoiceException" as PercentNormEnum, + ForInformation: "ForInformation" as PercentNormEnum } -} +} \ No newline at end of file diff --git a/icc-api/model/InvoicingCodeDto.ts b/icc-api/model/InvoicingCodeDto.ts index 9a699644..8214cb09 100644 --- a/icc-api/model/InvoicingCodeDto.ts +++ b/icc-api/model/InvoicingCodeDto.ts @@ -70,7 +70,7 @@ export class InvoicingCodeDto { units?: number - side?: number + side?: InvoicingCodeDto.SideEnum timeOfDay?: number @@ -136,4 +136,10 @@ export namespace InvoicingCodeDto { Paypal = "paypal", Bitcoin = "bitcoin" } + export type SideEnum = "None" | "Left" | "Right" + export const SideEnum = { + None: "None" as SideEnum, + Left: "Left" as SideEnum, + Right: "Right" as SideEnum + } } diff --git a/icc-api/model/MedicalLocationDto.ts b/icc-api/model/MedicalLocationDto.ts index 52e5e565..30dac492 100644 --- a/icc-api/model/MedicalLocationDto.ts +++ b/icc-api/model/MedicalLocationDto.ts @@ -54,5 +54,7 @@ export class MedicalLocationDto { agendaIds?: Array + options?: { [key: string]: string } + guardPost?: boolean } diff --git a/icc-api/model/MedicationDto.ts b/icc-api/model/MedicationDto.ts index 58338492..f09130b6 100644 --- a/icc-api/model/MedicationDto.ts +++ b/icc-api/model/MedicationDto.ts @@ -74,6 +74,8 @@ export class MedicationDto { agreements?: { [key: string]: models.ParagraphAgreementDto } + suspension?: Array + medicationSchemeIdOnSafe?: string medicationSchemeSafeVersion?: number diff --git a/icc-api/model/ParagraphAgreementDto.ts b/icc-api/model/ParagraphAgreementDto.ts index a46fd0a6..c23a9961 100644 --- a/icc-api/model/ParagraphAgreementDto.ts +++ b/icc-api/model/ParagraphAgreementDto.ts @@ -74,7 +74,7 @@ export class ParagraphAgreementDto { canceled?: boolean - inTreatment?: boolean - accepted?: boolean + + inTreatment?: boolean } diff --git a/icc-api/model/Property.ts b/icc-api/model/Property.ts index b32f5049..745aa3cb 100644 --- a/icc-api/model/Property.ts +++ b/icc-api/model/Property.ts @@ -32,6 +32,8 @@ export class Property { typedValue?: models.TypedValue + encryptedSelf?: string + attachments?: { [key: string]: models.Attachment } deleted?: number diff --git a/icc-api/model/PropertyDto.ts b/icc-api/model/PropertyDto.ts index 2f1fe8da..5b700376 100644 --- a/icc-api/model/PropertyDto.ts +++ b/icc-api/model/PropertyDto.ts @@ -37,4 +37,6 @@ export class PropertyDto { type?: models.PropertyTypeDto typedValue?: models.TypedValueDto + + encryptedSelf?: string } diff --git a/icc-api/model/SumehrExportInfoDto.ts b/icc-api/model/SumehrExportInfoDto.ts index 4add58a0..885a628f 100644 --- a/icc-api/model/SumehrExportInfoDto.ts +++ b/icc-api/model/SumehrExportInfoDto.ts @@ -1,9 +1,9 @@ /** - * + * * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen) * * OpenAPI spec version: 1.0.2 - * + * * * NOTE: This class is auto generated by the swagger code generator program. * https://github.com/swagger-api/swagger-codegen.git @@ -24,7 +24,7 @@ import * as models from "./models" -export class SumehrExportInfoDto { +export class SumehrExportInfoDto { constructor(json: JSON | any) { Object.assign(this as SumehrExportInfoDto, json) } @@ -41,4 +41,8 @@ export class SumehrExportInfoDto { comment?: string includeIrrelevantInformation?: boolean + + services?: Array + + healthElements?: Array } diff --git a/icc-api/model/CareMemberDto.ts b/icc-api/model/SuspensionDto.ts similarity index 86% rename from icc-api/model/CareMemberDto.ts rename to icc-api/model/SuspensionDto.ts index fcafd57a..43c2cd08 100644 --- a/icc-api/model/CareMemberDto.ts +++ b/icc-api/model/SuspensionDto.ts @@ -24,11 +24,13 @@ import * as models from "./models" -export class CareMemberDto { +export class SuspensionDto { constructor(json: JSON | any) { - Object.assign(this as CareMemberDto, json) + Object.assign(this as SuspensionDto, json) } - healthcarePartyId?: string + beginMoment?: number - quality?: models.CodeStub + endMoment?: number + + suspensionReason?: string } diff --git a/icc-api/model/TelecomDtoEmbed.ts b/icc-api/model/TelecomDtoEmbed.ts index 43410cf8..fc410e4d 100644 --- a/icc-api/model/TelecomDtoEmbed.ts +++ b/icc-api/model/TelecomDtoEmbed.ts @@ -37,18 +37,31 @@ export class TelecomDtoEmbed { encryptedSelf?: string } export namespace TelecomDtoEmbed { - export enum TelecomTypeEnum { - Mobile = "mobile", - Phone = "phone", - Email = "email", - Fax = "fax", - Skype = "skype", - Im = "im", - Medibridge = "medibridge", - Ehealthbox = "ehealthbox", - Apicrypt = "apicrypt", - Web = "web", - Print = "print", - Disk = "disk" + export type TelecomTypeEnum = + | "mobile" + | "phone" + | "email" + | "fax" + | "skype" + | "im" + | "medibridge" + | "ehealthbox" + | "apicrypt" + | "web" + | "print" + | "disk" + export const TelecomTypeEnum = { + Mobile: "mobile" as TelecomTypeEnum, + Phone: "phone" as TelecomTypeEnum, + Email: "email" as TelecomTypeEnum, + Fax: "fax" as TelecomTypeEnum, + Skype: "skype" as TelecomTypeEnum, + Im: "im" as TelecomTypeEnum, + Medibridge: "medibridge" as TelecomTypeEnum, + Ehealthbox: "ehealthbox" as TelecomTypeEnum, + Apicrypt: "apicrypt" as TelecomTypeEnum, + Web: "web" as TelecomTypeEnum, + Print: "print" as TelecomTypeEnum, + Disk: "disk" as TelecomTypeEnum } } diff --git a/icc-api/model/UserDto.ts b/icc-api/model/UserDto.ts index 01fd42d6..bf6706a1 100644 --- a/icc-api/model/UserDto.ts +++ b/icc-api/model/UserDto.ts @@ -84,9 +84,9 @@ export class UserDto { applicationTokens?: { [key: string]: string } - virtualHostDependency?: UserDto.VirtualHostDependencyEnum - virtualHosts?: Array + + virtualHostDependency?: UserDto.VirtualHostDependencyEnum } export namespace UserDto { export enum TypeEnum { diff --git a/icc-api/model/UserStubDto.ts b/icc-api/model/UserStubDto.ts index ed82a81c..60fc5aa8 100644 --- a/icc-api/model/UserStubDto.ts +++ b/icc-api/model/UserStubDto.ts @@ -44,9 +44,9 @@ export class UserStubDto { autoDelegations?: { [key: string]: Array } - virtualHostDependency?: UserStubDto.VirtualHostDependencyEnum - virtualHosts?: Array + + virtualHostDependency?: UserStubDto.VirtualHostDependencyEnum } export namespace UserStubDto { export enum VirtualHostDependencyEnum { diff --git a/icc-api/model/models.ts b/icc-api/model/models.ts index 3dab8fab..f114bf04 100644 --- a/icc-api/model/models.ts +++ b/icc-api/model/models.ts @@ -80,7 +80,6 @@ export * from "./Formula" export * from "./FrontEndMigrationDto" export * from "./FullTextSearchResult" export * from "./GalInfos" -export * from "./GroupDto" export * from "./HcPartyPaginatedList" export * from "./HealthElementDto" export * from "./HealthcarePartyDto" @@ -194,6 +193,7 @@ export * from "./Suggest" export * from "./SumehrContentDto" export * from "./SumehrExportInfoDto" export * from "./SumehrValidityDto" +export * from "./SuspensionDto" export * from "./Tag" export * from "./TarificationDto" export * from "./TarificationPaginatedList" diff --git a/icc-x-api/crypto/utils.ts b/icc-x-api/crypto/utils.ts index 934cd906..b31627d1 100644 --- a/icc-x-api/crypto/utils.ts +++ b/icc-x-api/crypto/utils.ts @@ -4,17 +4,11 @@ import { Moment } from "moment" import * as _ from "lodash" export class UtilsClass { - private crypto: Crypto - - constructor( - crypto: Crypto = typeof window !== "undefined" - ? window.crypto - : typeof self !== "undefined" - ? self.crypto - : ({} as Crypto) - ) { - this.crypto = crypto - } + private textDecoder = TextDecoder ? new TextDecoder() : null + private textEncoder = TextEncoder ? new TextEncoder() : null + + constructor() {} + /** * String to Uint8Array * @@ -148,6 +142,10 @@ export class UtilsClass { } utf82ua(str: string): Uint8Array { + if (this.textEncoder) { + return this.textEncoder.encode(str) + } + const utf8 = new Uint8Array(4 * str.length) let j = 0 for (var i = 0; i < str.length; i++) { @@ -185,10 +183,17 @@ export class UtilsClass { } ua2utf8(arrBuf: Uint8Array | ArrayBuffer): string { + if (this.textDecoder) { + // if arrBuf is undefined, imitate the JS implementation below which returns an empty string + return arrBuf ? this.textDecoder.decode(arrBuf) : "" + } + var out, i, len, c, u var char2, char3, char4 - const array = new Uint8Array(arrBuf) + // avoid applying the Uint8Array constructor: on ArrayBuffer it creates a + // view but on Uint8Array it creates a copy + const array = ArrayBuffer.isView(arrBuf) ? arrBuf : new Uint8Array(arrBuf) out = "" len = array.length || array.byteLength @@ -240,6 +245,25 @@ export class UtilsClass { return out } + /** + * Provide a view over the given Uint8Array where any trailing null bytes at + * the end are truncated. + * + * This can be used to ignore null bytes at the end of a padded UTF-8 string + * without needing to copy that string, assuming code point U+0000 is encoded + * in one null byte according to standards rather than in a multi-byte + * overlong form. + */ + truncateTrailingNulls(a: Uint8Array) { + let end = a.byteLength - 1 + while (a[end] === 0 && end >= 0) { + end-- + } + // end is now either the last non-null position in a or -1; in the latter + // case the returned array will have length 0. + return a.subarray(0, end + 1) + } + base64url(b: Uint8Array): string { return base64js .fromByteArray(b) diff --git a/icc-x-api/icc-accesslog-x-api.ts b/icc-x-api/icc-accesslog-x-api.ts index 6e51b065..711e72a8 100644 --- a/icc-x-api/icc-accesslog-x-api.ts +++ b/icc-x-api/icc-accesslog-x-api.ts @@ -5,6 +5,11 @@ import * as models from "../icc-api/model/models" import * as _ from "lodash" import { utils } from "./crypto/utils" +import { AccessLogDto } from "../icc-api/model/models" + +export interface AccessLogWithPatientId extends AccessLogDto { + patientId: string +} export class IccAccesslogXApi extends iccAccesslogApi { crypto: IccCryptoXApi @@ -37,7 +42,8 @@ export class IccAccesslogXApi extends iccAccesslogApi { codes: [], tags: [], user: user.id, - accessType: "USER_ACCESS" + accessType: "USER_ACCESS", + patientId: patient.id }, h || {} ) @@ -110,23 +116,22 @@ export class IccAccesslogXApi extends iccAccesslogApi { findBy(hcpartyId: string, patient: models.PatientDto) { return this.crypto .extractDelegationsSFKs(patient, hcpartyId) - .then( - secretForeignKeys => - secretForeignKeys && - secretForeignKeys.extractedKeys && - secretForeignKeys.extractedKeys.length > 0 - ? this.findByHCPartyPatientSecretFKeys( - secretForeignKeys.hcpartyId!, - secretForeignKeys.extractedKeys.join(",") - ) - : Promise.resolve([]) + .then(secretForeignKeys => + secretForeignKeys && + secretForeignKeys.extractedKeys && + secretForeignKeys.extractedKeys.length > 0 + ? this.findByHCPartyPatientSecretFKeys( + secretForeignKeys.hcpartyId!, + secretForeignKeys.extractedKeys.join(",") + ) + : Promise.resolve([]) ) } findByHCPartyPatientSecretFKeys( hcPartyId: string, secretFKeys?: string - ): Promise | any> { + ): Promise> { return super .findByHCPartyPatientSecretFKeys(hcPartyId, secretFKeys) .then(accesslogs => this.decrypt(hcPartyId, accesslogs)) @@ -361,4 +366,72 @@ export class IccAccesslogXApi extends iccAccesslogApi { ) ) } + + async findLatestAccessLogsOfPatientsWithUser( + user: models.UserDto, + userId: string, + limit: number = 100, + startDate?: number + ): Promise { + let foundAccessLogs: AccessLogWithPatientId[] = [], + nextKeyPair: models.PaginatedDocumentKeyIdPair | undefined = undefined + const numberRequestedAccessLogs = 100 + const MAX_WHILE_ITERATIONS = 5 + + for ( + let currentIteration = 0; + foundAccessLogs.length < limit && currentIteration < MAX_WHILE_ITERATIONS; + currentIteration++ + ) { + const currentLimit = limit - foundAccessLogs.length + const { + rows: logs, + nextKeyPair: newNextKeyPair + }: models.AccessLogPaginatedList = await super.findByUserAfterDate( + userId, + "USER_ACCESS", + startDate, + nextKeyPair && JSON.stringify(nextKeyPair.startKey!), + nextKeyPair && nextKeyPair.startKeyDocId!, + numberRequestedAccessLogs, + true + ) + const logsWithPatientId: AccessLogWithPatientId[] = await this.decrypt( + (user.healthcarePartyId || user.patientId)!, + logs as AccessLogDto[] + ).then(decryptedLogs => + Promise.all( + _.map(decryptedLogs, decryptedLog => { + return this.crypto + .extractCryptedFKs(decryptedLog, user.healthcarePartyId as string) + .then( + keys => + ({ + ...decryptedLog, + patientId: _.head(keys.extractedKeys) + } as AccessLogWithPatientId) + ) + }) + ) + ) + + const uniqueLogs: AccessLogWithPatientId[] = _.chain(logsWithPatientId) + .reject(log => _.some(foundAccessLogs, ({ patientId }) => patientId === log.patientId)) + .uniqBy((log: AccessLogWithPatientId) => log.patientId) + .value() + .slice(0, currentLimit) + + foundAccessLogs = [...foundAccessLogs, ...uniqueLogs] + + if ((logs || []).length < numberRequestedAccessLogs) { + break + } else if (newNextKeyPair) { + nextKeyPair = newNextKeyPair + } else { + break + } + } + + return foundAccessLogs + } } diff --git a/icc-x-api/icc-bekmehr-x-api.ts b/icc-x-api/icc-bekmehr-x-api.ts index b4c63344..bb74aec2 100644 --- a/icc-x-api/icc-bekmehr-x-api.ts +++ b/icc-x-api/icc-bekmehr-x-api.ts @@ -4,6 +4,7 @@ import { XHR } from "../icc-api/api/XHR" import * as models from "../icc-api/model/models" import { IccContactXApi } from "./icc-contact-x-api" import { IccHelementXApi } from "./icc-helement-x-api" +import { ContactDto } from "../icc-api/model/models" export class IccBekmehrXApi extends iccBeKmehrApi { private readonly ctcApi: IccContactXApi @@ -35,7 +36,10 @@ export class IccBekmehrXApi extends iccBeKmehrApi { healthcarePartyId: string, resolve: (value?: Promise) => void, reject: (reason?: any) => void, - progressCallback?: (progress: number) => void + progressCallback?: (progress: number) => void, + servicesPatcher?: (response: any[]) => Promise, + contactsPatcher?: (response: any[]) => Promise, + healthElementsPatcher?: (response: any[]) => Promise ) { const that = this return (event: MessageEvent) => { @@ -46,6 +50,7 @@ export class IccBekmehrXApi extends iccBeKmehrApi { if (msg.type === "ContactDto") { that.ctcApi .decrypt(healthcarePartyId, msg.body) + .then(res => (contactsPatcher ? contactsPatcher(res) : Promise.resolve(res))) .then(res => socket.send( JSON.stringify({ command: "decryptResponse", uuid: msg.uuid, body: res }) @@ -54,6 +59,9 @@ export class IccBekmehrXApi extends iccBeKmehrApi { } else if (msg.type === "HealthElementDto") { that.helementApi .decrypt(healthcarePartyId, msg.body) + .then(res => + healthElementsPatcher ? healthElementsPatcher(res) : Promise.resolve(res) + ) .then(res => socket.send( JSON.stringify({ command: "decryptResponse", uuid: msg.uuid, body: res }) @@ -62,16 +70,21 @@ export class IccBekmehrXApi extends iccBeKmehrApi { } else { that.ctcApi .decryptServices(healthcarePartyId, msg.body) + .then(res => (servicesPatcher ? servicesPatcher(res) : Promise.resolve(res))) .then(res => socket.send( JSON.stringify({ command: "decryptResponse", uuid: msg.uuid, body: res }) ) ) } - } else if ((msg.command = "progress")) { + } else if (msg.command === "progress") { if (progressCallback && msg.body && msg.body[0]) { progressCallback(msg.body[0].progress) } + } else { + console.error("error received from backend:" + event.data) + reject("websocket error: " + event.data) + socket.close(4000, "backend error") } } else { resolve(event.data) @@ -86,7 +99,9 @@ export class IccBekmehrXApi extends iccBeKmehrApi { language: string, body: models.SoftwareMedicalFileExportDto, progressCallback?: (progress: number) => void, - sessionId?: string + sessionId?: string, + contactsPatcher?: (response: ContactDto[]) => Promise, + healthElementsPatcher?: (response: any[]) => Promise ): Promise { return new Promise((resolve, reject) => { const socket = new WebSocket( @@ -101,7 +116,16 @@ export class IccBekmehrXApi extends iccBeKmehrApi { // Listen for messages socket.addEventListener( "message", - this.socketEventListener(socket, healthcarePartyId, resolve, reject, progressCallback) + this.socketEventListener( + socket, + healthcarePartyId, + resolve, + reject, + progressCallback, + undefined, + contactsPatcher, + healthElementsPatcher + ) ) }) } @@ -185,6 +209,7 @@ export class IccBekmehrXApi extends iccBeKmehrApi { recipientSafe: string, version: number, body: models.MedicationSchemeExportInfoDto, + patcher: (response: ContactDto[]) => Promise, sessionId?: string ): Promise { return new Promise((resolve, reject) => { @@ -209,7 +234,7 @@ export class IccBekmehrXApi extends iccBeKmehrApi { // Listen for messages socket.addEventListener( "message", - this.socketEventListener(socket, healthcarePartyId, resolve, reject) + this.socketEventListener(socket, healthcarePartyId, resolve, reject, undefined, patcher) ) }) } diff --git a/icc-x-api/icc-calendar-item-x-api.ts b/icc-x-api/icc-calendar-item-x-api.ts index cd9dac82..05615d9c 100755 --- a/icc-x-api/icc-calendar-item-x-api.ts +++ b/icc-x-api/icc-calendar-item-x-api.ts @@ -403,6 +403,9 @@ export class IccCalendarItemXApi extends iccCalendarItemApi { ) return {} } + }).catch(err => { + console.log('Error during AES decryption', err); + return {}; }) ) ) diff --git a/icc-x-api/icc-contact-x-api.ts b/icc-x-api/icc-contact-x-api.ts index 7a024eba..af53e204 100644 --- a/icc-x-api/icc-contact-x-api.ts +++ b/icc-x-api/icc-contact-x-api.ts @@ -164,8 +164,9 @@ export class IccContactXApi extends iccContactApi { * @param patient (Promise) */ findBy(hcpartyId: string, patient: models.PatientDto) { - return this.crypto.extractSFKsHierarchyFromDelegations(patient, hcpartyId).then( - secretForeignKeys => + return this.crypto + .extractSFKsHierarchyFromDelegations(patient, hcpartyId) + .then(secretForeignKeys => secretForeignKeys && secretForeignKeys.length > 0 ? Promise.all( secretForeignKeys @@ -189,7 +190,7 @@ export class IccContactXApi extends iccContactApi { ) ).then(results => _.uniqBy(_.flatMap(results), x => x.id)) : Promise.resolve([]) - ) + ) } async findByPatientSFKs( @@ -321,7 +322,7 @@ export class IccContactXApi extends iccContactApi { return super .filterBy(startKey, startDocumentId, limit, body) .then(ctcs => - this.decrypt(user.healthcarePartyId!, ctcs.rows).then(decryptedRows => + this.decrypt(user.healthcarePartyId! || user.patientId!, ctcs.rows).then(decryptedRows => Object.assign(ctcs, { rows: decryptedRows }) ) ) @@ -337,10 +338,11 @@ export class IccContactXApi extends iccContactApi { ): Promise { return super .listContactsByOpeningDate(startKey, endKey, hcpartyid, startDocumentId, limit) - .then(ctcs => { - ctcs.rows = this.decrypt((user.healthcarePartyId || user.patientId)!, ctcs.rows) - return ctcs - }) + .then(ctcs => + this.decrypt(user.healthcarePartyId! || user.patientId!, ctcs.rows).then(decryptedRows => + Object.assign(ctcs, { rows: decryptedRows }) + ) + ) } findByHCPartyFormIdWithUser( @@ -419,55 +421,54 @@ export class IccContactXApi extends iccContactApi { const bypassEncryption = false //Used for debug return Promise.all( - ctcs.map( - ctc => - bypassEncryption //Prevent encryption for test ctc - ? ctc - : (ctc.encryptionKeys && Object.keys(ctc.encryptionKeys || {}).length - ? Promise.resolve(ctc) - : this.initEncryptionKeys(user, ctc) - ) - .then(ctc => - this.crypto.extractKeysFromDelegationsForHcpHierarchy( - hcpartyId, - ctc.id!, - ctc.encryptionKeys! - ) + ctcs.map(ctc => + bypassEncryption //Prevent encryption for test ctc + ? ctc + : (ctc.encryptionKeys && Object.keys(ctc.encryptionKeys || {}).length + ? Promise.resolve(ctc) + : this.initEncryptionKeys(user, ctc) + ) + .then(ctc => + this.crypto.extractKeysFromDelegationsForHcpHierarchy( + hcpartyId, + ctc.id!, + ctc.encryptionKeys! ) - .then((sfks: { extractedKeys: Array; hcpartyId: string }) => - this.crypto.AES.importKey( - "raw", - utils.hex2ua(sfks.extractedKeys[0].replace(/-/g, "")) - ) + ) + .then((sfks: { extractedKeys: Array; hcpartyId: string }) => + this.crypto.AES.importKey( + "raw", + utils.hex2ua(sfks.extractedKeys[0].replace(/-/g, "")) ) - .then((key: CryptoKey) => - Promise.all( - ctc.services!.map(svc => - this.crypto.AES.encrypt( - key, - utils.utf82ua(JSON.stringify({ content: svc.content })) - ) + ) + .then((key: CryptoKey) => + Promise.all( + ctc.services!.map(svc => + this.crypto.AES.encrypt( + key, + utils.utf82ua(JSON.stringify({ content: svc.content })) ) ) - .then(eSvcs => { - console.log("eSvcs ", eSvcs) - ctc.services!.forEach((svc, idx) => { - svc.encryptedSelf = btoa(utils.ua2text(eSvcs[idx])) - delete svc.content - }) + ) + .then(eSvcs => { + console.log("eSvcs ", eSvcs) + ctc.services!.forEach((svc, idx) => { + svc.encryptedSelf = btoa(utils.ua2text(eSvcs[idx])) + delete svc.content }) - .then(() => - this.crypto.AES.encrypt( - key, - utils.utf82ua(JSON.stringify({ descr: ctc.descr })) - ) + }) + .then(() => + this.crypto.AES.encrypt( + key, + utils.utf82ua(JSON.stringify({ descr: ctc.descr })) ) - .then(es => { - ctc.encryptedSelf = btoa(utils.ua2text(es)) - delete ctc.descr - return ctc - }) - ) + ) + .then(es => { + ctc.encryptedSelf = btoa(utils.ua2text(es)) + delete ctc.descr + return ctc + }) + ) ) ) } @@ -501,7 +502,9 @@ export class IccContactXApi extends iccContactApi { c => { let jsonContent try { - jsonContent = utils.ua2utf8(c!).replace(/\0+$/g, "") + jsonContent = utils.ua2utf8( + utils.truncateTrailingNulls(new Uint8Array(c!)) + ) resolve(c && { content: JSON.parse(jsonContent) }) } catch (e) { console.log( @@ -518,30 +521,32 @@ export class IccContactXApi extends iccContactApi { } ) : svc.encryptedSelf - ? this.crypto.AES.decrypt( - key, - utils.text2ua(atob(svc.encryptedSelf!)) - ).then( - s => { - let jsonContent - try { - jsonContent = utils.ua2utf8(s!).replace(/\0+$/g, "") - resolve(s && JSON.parse(jsonContent)) - } catch (e) { - console.log( - "Cannot parse service", - svc.id, - jsonContent || "<- Invalid encoding" - ) - resolve(null) - } - }, - () => { - console.log("Cannot decrypt service", svc.id) + ? this.crypto.AES.decrypt( + key, + utils.text2ua(atob(svc.encryptedSelf!)) + ).then( + s => { + let jsonContent + try { + jsonContent = utils.ua2utf8( + utils.truncateTrailingNulls(new Uint8Array(s!)) + ) + resolve(s && JSON.parse(jsonContent)) + } catch (e) { + console.log( + "Cannot parse service", + svc.id, + jsonContent || "<- Invalid encoding" + ) resolve(null) } - ) - : resolve(null) + }, + () => { + console.log("Cannot decrypt service", svc.id) + resolve(null) + } + ) + : resolve(null) }) ) .then(decrypted => { @@ -605,69 +610,72 @@ export class IccContactXApi extends iccContactApi { svc.id!, _.size(svc.encryptionKeys) ? svc.encryptionKeys! : svc.delegations! ) - .then( - ({ extractedKeys: sfks }) => - svc.encryptedContent || svc.encryptedSelf - ? this.crypto.AES.importKey("raw", utils.hex2ua(sfks[0].replace(/-/g, ""))) - .then( - (key: CryptoKey) => - new Promise((resolve: (value: any) => any) => { - svc.encryptedContent - ? this.crypto.AES.decrypt( - key, - utils.text2ua(atob(svc.encryptedContent!)) - ).then( - c => { - let jsonContent - try { - jsonContent = utils.ua2utf8(c!).replace(/\0+$/g, "") - resolve(c && { content: JSON.parse(jsonContent) }) - } catch (e) { - console.log( - "Cannot parse service", - svc.id, - jsonContent || "<- Invalid encoding" - ) - resolve(null) - } - }, - () => { - console.log("Cannot decrypt service", svc.id) + .then(({ extractedKeys: sfks }) => + svc.encryptedContent || svc.encryptedSelf + ? this.crypto.AES.importKey("raw", utils.hex2ua(sfks[0].replace(/-/g, ""))) + .then( + (key: CryptoKey) => + new Promise((resolve: (value: any) => any) => { + svc.encryptedContent + ? this.crypto.AES.decrypt( + key, + utils.text2ua(atob(svc.encryptedContent!)) + ).then( + c => { + let jsonContent + try { + jsonContent = utils.ua2utf8( + utils.truncateTrailingNulls(new Uint8Array(c!)) + ) + resolve(c && { content: JSON.parse(jsonContent) }) + } catch (e) { + console.log( + "Cannot parse service", + svc.id, + jsonContent || "<- Invalid encoding" + ) resolve(null) } - ) - : svc.encryptedSelf - ? this.crypto.AES.decrypt( - key, - utils.text2ua(atob(svc.encryptedSelf!)) - ).then( - s => { - let jsonContent - try { - jsonContent = utils.ua2utf8(s!).replace(/\0+$/g, "") - resolve(s && JSON.parse(jsonContent)) - } catch (e) { - console.log( - "Cannot parse service", - svc.id, - jsonContent || "<- Invalid encoding" - ) - resolve(null) - } - }, - () => { - console.log("Cannot decrypt service", svc.id) - resolve(null) - } - ) - : resolve(null) - }) - ) - .then(decrypted => { - decrypted && _.assign(svc, decrypted) - return svc - }) - : svc + }, + () => { + console.log("Cannot decrypt service", svc.id) + resolve(null) + } + ) + : svc.encryptedSelf + ? this.crypto.AES.decrypt( + key, + utils.text2ua(atob(svc.encryptedSelf!)) + ).then( + s => { + let jsonContent + try { + jsonContent = utils.ua2utf8( + utils.truncateTrailingNulls(new Uint8Array(s!)) + ) + resolve(s && JSON.parse(jsonContent)) + } catch (e) { + console.log( + "Cannot parse service", + svc.id, + jsonContent || "<- Invalid encoding" + ) + resolve(null) + } + }, + () => { + console.log("Cannot decrypt service", svc.id) + resolve(null) + } + ) + : resolve(null) + }) + ) + .then(decrypted => { + decrypted && _.assign(svc, decrypted) + return svc + }) + : svc ) ) ) @@ -689,13 +697,15 @@ export class IccContactXApi extends iccContactApi { filteredServices(ctcs: Array, filter: any): Array { const byIds: { [key: string]: models.ServiceDto } = {} ctcs.forEach(c => - (c.services || []).filter(s => filter(s, c)).forEach(s => { - const ps = byIds[s.id!] - if (!ps || !ps.modified || (s.modified && ps.modified < s.modified)) { - byIds[s.id!] = s - s.contactId = c.id - } - }) + (c.services || []) + .filter(s => filter(s, c)) + .forEach(s => { + const ps = byIds[s.id!] + if (!ps || !ps.modified || (s.modified && ps.modified < s.modified)) { + byIds[s.id!] = s + s.contactId = c.id + } + }) ) return _.values(byIds).filter((s: any) => !s.deleted && !s.endOfLife) } @@ -996,8 +1006,8 @@ export class IccContactXApi extends iccContactApi { return m && m.compoundPrescription ? m.compoundPrescription : m && m.substanceProduct - ? myself.productToString(m && m.substanceProduct) - : myself.productToString(m && m.medicinalProduct) + ? myself.productToString(m && m.substanceProduct) + : myself.productToString(m && m.medicinalProduct) }, reimbursementReasonToString: (m: any, lang: string) => { return m && @@ -1067,8 +1077,8 @@ export class IccContactXApi extends iccContactApi { ) ? this.i18n[lang].weekly : m.regimen.find((r: any) => r.date) - ? this.i18n[lang].monthly - : this.i18n[lang].daily + ? this.i18n[lang].monthly + : this.i18n[lang].daily return `${quantityUnit}, ${m.regimen.length} x ${dayPeriod}, ${_.sortBy( m.regimen, @@ -1079,12 +1089,11 @@ export class IccContactXApi extends iccContactApi { (r.timeOfDay ? r.timeOfDay : r.dayPeriod && r.dayPeriod.code - ? (regimenScores[r.dayPeriod.code] as number) - : 0) + ? (regimenScores[r.dayPeriod.code] as number) + : 0) ) - .map( - r => - cplxRegimen ? myself.regimenToExtString(r, lang) : myself.regimenToString(r, lang) + .map(r => + cplxRegimen ? myself.regimenToExtString(r, lang) : myself.regimenToString(r, lang) ) .join(", ")}` } @@ -1100,8 +1109,8 @@ export class IccContactXApi extends iccContactApi { const dayPeriod = m.regimen.find((r: any) => r.weekday !== null && r.weekday !== undefined) ? this.i18n[lang].weekly : m.regimen.find((r: any) => r.date) - ? this.i18n[lang].monthly - : this.i18n[lang].daily + ? this.i18n[lang].monthly + : this.i18n[lang].daily return `${m.regimen.length} x ${dayPeriod}` }, @@ -1123,10 +1132,10 @@ export class IccContactXApi extends iccContactApi { let res = r.date ? `${this.i18n[lang].the} ${moment(r.date).format("DD/MM/YYYY")}` : r.dayNumber - ? `${this.i18n[lang].onDay} ${r.dayNumber}` - : r.weekday && r.weekday.weekday - ? `${this.i18n[lang].on} ${r.weekday.weekday}` - : null + ? `${this.i18n[lang].onDay} ${r.dayNumber}` + : r.weekday && r.weekday.weekday + ? `${this.i18n[lang].on} ${r.weekday.weekday}` + : null if (r.dayPeriod && r.dayPeriod.code && r.dayPeriod.code.length) { res = res ? `${res} ${this.i18n[lang][r.dayPeriod.code] || @@ -1157,11 +1166,8 @@ export class IccContactXApi extends iccContactApi { (this.i18n[lang][s.toLowerCase()] && this.i18n[lang][s.toLowerCase()] .split("") - .map( - (c: string, idx: number) => - idx >= s.length || s[idx].toLocaleLowerCase() === s[idx] - ? c - : c.toLocaleUpperCase() + .map((c: string, idx: number) => + idx >= s.length || s[idx].toLocaleLowerCase() === s[idx] ? c : c.toLocaleUpperCase() ) .join("")) || s diff --git a/icc-x-api/icc-crypto-x-api.ts b/icc-x-api/icc-crypto-x-api.ts index a590a0cc..16ef7031 100644 --- a/icc-x-api/icc-crypto-x-api.ts +++ b/icc-x-api/icc-crypto-x-api.ts @@ -1,12 +1,11 @@ -import { iccHcpartyApi, iccPatientApi } from "../icc-api/iccApi" -import { AES, AESUtils } from "./crypto/AES" -import { RSA, RSAUtils } from "./crypto/RSA" -import { utils, UtilsClass } from "./crypto/utils" -import { shamir, ShamirClass } from "./crypto/shamir" - import * as _ from "lodash" +import { iccHcpartyApi, iccPatientApi } from "../icc-api/iccApi" import * as models from "../icc-api/model/models" import { DelegationDto, HealthcarePartyDto, PatientDto } from "../icc-api/model/models" +import { AESUtils } from "./crypto/AES" +import { RSAUtils } from "./crypto/RSA" +import { ShamirClass } from "./crypto/shamir" +import { utils, UtilsClass } from "./crypto/utils" export class IccCryptoXApi { get shamir(): ShamirClass { @@ -70,7 +69,7 @@ export class IccCryptoXApi { } private getCachedHcpOrPatientType(hcpartyId: string): string | null | undefined { - return (this.hcPartiesRequestsCache[hcpartyId] || {}).entityType + return (this.hcPartiesRequestsCache[hcpartyId] || { entityType: undefined }).entityType } /** @@ -99,8 +98,16 @@ export class IccCryptoXApi { ]).then(([a, b]) => Object.assign({}, a, b)) } - keychainLocalStoreIdPrefix: String = "org.taktik.icure.ehealth.keychain." - hcpPreferenceKeyEhealthCert: string = "eHealthCRT" + /** + * @deprecated only encrypted version of the certificate should be stored, if this key is present, + * it and its value should be deleted from the hcp.options dict + */ + hcpPreferenceKeyEhealthCert = "eHealthCRT" + + keychainLocalStoreIdPrefix = "org.taktik.icure.ehealth.keychain." + keychainValidityDateLocalStoreIdPrefix = "org.taktik.icure.ehealth.keychain-date." + hcpPreferenceKeyEhealthCertEncrypted = "eHealthCRTCrypt" + hcpPreferenceKeyEhealthCertDate = "eHealthCRTDate" private hcpartyBaseApi: iccHcpartyApi private patientBaseApi: iccPatientApi @@ -119,8 +126,8 @@ export class IccCryptoXApi { crypto: Crypto = typeof window !== "undefined" ? window.crypto : typeof self !== "undefined" - ? self.crypto - : ({} as Crypto) + ? self.crypto + : ({} as Crypto) ) { this.hcpartyBaseApi = hcpartyBaseApi this.patientBaseApi = patientBaseApi @@ -128,7 +135,7 @@ export class IccCryptoXApi { this._AES = new AESUtils(crypto) this._RSA = new RSAUtils(crypto) - this._utils = new UtilsClass(crypto) + this._utils = new UtilsClass() this._shamir = new ShamirClass(crypto) } @@ -341,11 +348,10 @@ export class IccCryptoXApi { delegatorIds[delegationItem.owner!] = true //TODO: why is set to true? }) } else if (fallbackOnParent) { - return this.getHcpOrPatient(healthcarePartyId).then( - hcp => - (hcp as any).parentId - ? this.decryptAndImportAesHcPartyKeysInDelegations((hcp as any).parentId, delegations) - : Promise.resolve([]) + return this.getHcpOrPatient(healthcarePartyId).then(hcp => + (hcp as any).parentId + ? this.decryptAndImportAesHcPartyKeysInDelegations((hcp as any).parentId, delegations) + : Promise.resolve([]) ) } @@ -515,9 +521,7 @@ export class IccCryptoXApi { .decrypt(importedAESHcPartyKey.key, this._utils.hex2ua(d.key)) .catch(() => { console.log( - `Cannot decrypt delegation from ${d.owner} to ${ - d.delegatedTo - } for object with id ${modifiedObject.id}:`, + `Cannot decrypt delegation from ${d.owner} to ${d.delegatedTo} for object with id ${modifiedObject.id}:`, modifiedObject ) return null @@ -533,9 +537,7 @@ export class IccCryptoXApi { .decrypt(importedAESHcPartyKey.key, this._utils.hex2ua(d.key)) .catch(() => { console.log( - `Cannot decrypt cryptedForeignKeys from ${d.owner} to ${ - d.delegatedTo - } for object with id ${modifiedObject.id}:`, + `Cannot decrypt cryptedForeignKeys from ${d.owner} to ${d.delegatedTo} for object with id ${modifiedObject.id}:`, modifiedObject ) return null @@ -739,9 +741,7 @@ export class IccCryptoXApi { d.owner === ownerId && this._AES.decrypt(decryptedHcPartyKey.key, this._utils.hex2ua(d.key)).catch(() => { console.log( - `Cannot decrypt encryption key from ${d.owner} to ${ - d.delegatedTo - } for object with id ${modifiedObject.id}:`, + `Cannot decrypt encryption key from ${d.owner} to ${d.delegatedTo} for object with id ${modifiedObject.id}:`, modifiedObject ) return null @@ -869,20 +869,19 @@ export class IccCryptoXApi { ) : Promise.resolve({ delegations: {}, cryptedForeignKeys: {} }) ) - .then( - extendedChildObjectSPKsAndCFKs => - secretEncryptionKey - ? this.appendEncryptionKeys(child, ownerId, delegateId, secretEncryptionKey).then( - //TODO: extendedDelegationsAndCryptedForeignKeys and appendEncryptionKeys can be done in parallel - extendedChildObjectEKs => ({ - extendedSPKsAndCFKs: extendedChildObjectSPKsAndCFKs, - extendedEKs: extendedChildObjectEKs - }) - ) - : Promise.resolve({ + .then(extendedChildObjectSPKsAndCFKs => + secretEncryptionKey + ? this.appendEncryptionKeys(child, ownerId, delegateId, secretEncryptionKey).then( + //TODO: extendedDelegationsAndCryptedForeignKeys and appendEncryptionKeys can be done in parallel + extendedChildObjectEKs => ({ extendedSPKsAndCFKs: extendedChildObjectSPKsAndCFKs, - extendedEKs: { encryptionKeys: {} } + extendedEKs: extendedChildObjectEKs }) + ) + : Promise.resolve({ + extendedSPKsAndCFKs: extendedChildObjectSPKsAndCFKs, + extendedEKs: { encryptionKeys: {} } + }) ) .then( ({ @@ -1128,17 +1127,16 @@ export class IccCryptoXApi { } ) : Promise.resolve([]) - ).then( - extractedKeys => - (hcp as HealthcarePartyDto).parentId - ? this.extractKeysHierarchyFromDelegationLikes( - (hcp as HealthcarePartyDto).parentId!, - objectId, - delegations - ).then(parentResponse => - parentResponse.concat({ extractedKeys: extractedKeys, hcpartyId: hcpartyId }) - ) - : [{ extractedKeys: extractedKeys, hcpartyId: hcpartyId }] + ).then(extractedKeys => + (hcp as HealthcarePartyDto).parentId + ? this.extractKeysHierarchyFromDelegationLikes( + (hcp as HealthcarePartyDto).parentId!, + objectId, + delegations + ).then(parentResponse => + parentResponse.concat({ extractedKeys: extractedKeys, hcpartyId: hcpartyId }) + ) + : [{ extractedKeys: extractedKeys, hcpartyId: hcpartyId }] ) ) } @@ -1178,19 +1176,18 @@ export class IccCryptoXApi { } ) : Promise.resolve([]) - ).then( - extractedKeys => - (hcp as HealthcarePartyDto).parentId - ? this.extractKeysFromDelegationsForHcpHierarchy( - (hcp as HealthcarePartyDto).parentId!, - objectId, - delegations - ).then(parentResponse => - _.assign(parentResponse, { - extractedKeys: parentResponse.extractedKeys.concat(extractedKeys) - }) - ) - : { extractedKeys: extractedKeys, hcpartyId: hcpartyId } + ).then(extractedKeys => + (hcp as HealthcarePartyDto).parentId + ? this.extractKeysFromDelegationsForHcpHierarchy( + (hcp as HealthcarePartyDto).parentId!, + objectId, + delegations + ).then(parentResponse => + _.assign(parentResponse, { + extractedKeys: parentResponse.extractedKeys.concat(extractedKeys) + }) + ) + : { extractedKeys: extractedKeys, hcpartyId: hcpartyId } ) ) } @@ -1251,9 +1248,7 @@ export class IccCryptoXApi { }) .catch(err => { console.log( - `Could not decrypt generic delegation in object with ID: ${masterId} from ${ - genericDelegationItem.owner - } to ${genericDelegationItem.delegatedTo}: ${err}` + `Could not decrypt generic delegation in object with ID: ${masterId} from ${genericDelegationItem.owner} to ${genericDelegationItem.delegatedTo}: ${err}` ) return undefined }) @@ -1312,7 +1307,7 @@ export class IccCryptoXApi { // noinspection JSUnusedGlobalSymbols loadKeyPairsInBrowserLocalStorage(healthcarePartyId: string, file: Blob) { const fr = new FileReader() - return new Promise((resolve: (() => void), reject) => { + return new Promise((resolve: () => void, reject) => { fr.onerror = reject fr.onabort = reject fr.onload = (e: any) => { @@ -1334,31 +1329,131 @@ export class IccCryptoXApi { ) } + // noinspection JSUnusedGlobalSymbols + saveKeychainValidityDateInBrowserLocalStorage(id: string, date: string) { + if (!id) return + + if (!date) { + localStorage.removeItem(this.keychainValidityDateLocalStoreIdPrefix + id) + } else { + localStorage.setItem(this.keychainValidityDateLocalStoreIdPrefix + id, date) + } + } + + // noinspection JSUnusedGlobalSymbols saveKeychainInBrowserLocalStorageAsBase64(id: string, keyChainB64: string) { - localStorage.setItem(this.keychainLocalStoreIdPrefix + id, keyChainB64) + if (!id) return + + if (!keyChainB64) { + localStorage.removeItem(this.keychainLocalStoreIdPrefix + id) + } else { + localStorage.setItem(this.keychainLocalStoreIdPrefix + id, keyChainB64) + } } - saveKeyChainInHCPFromLocalStorage(hcpId: string): Promise { - return this.hcpartyBaseApi + /** + * Populate the HCP.options dict with an encrypted eHealth certificate and unencryped expiry date. + * Any potentially unencrypted certificates will be pruned from the HCP. + * @param hcpId Id of the hcp to modify + * @returns modified HCP + */ + async saveKeyChainInHCPFromLocalStorage(hcpId: string): Promise { + return await this.hcpartyBaseApi .getHealthcareParty(hcpId) - .then(hcp => { - const crt = this.getKeychainInBrowserLocalStorageAsBase64(hcp.id!!) + .then(async (hcp: HealthcarePartyDto) => { + let aesKey: CryptoKey | null = null + try { + aesKey = _.find( + await this.decryptAndImportAesHcPartyKeysForDelegators([hcp.id!], hcp.id!), + delegator => delegator.delegatorId === hcp.id + )!.key + } catch (e) { + console.error("Error while importing the AES key.") + } + if (!aesKey) { + console.error("No encryption key!") + } + const opts = hcp.options || {} - _.set(opts, this.hcpPreferenceKeyEhealthCert, crt) + + const crt = this.getKeychainInBrowserLocalStorageAsBase64(hcp.id!!) + if (!!aesKey && !!crt) { + let crtEncrypted: ArrayBuffer | null = null + try { + crtEncrypted = await this.AES.encrypt(aesKey, new Uint8Array(utils.text2ua(atob(crt)))) + } catch (e) { + console.error("Error while encrypting the certificate", e) + } + + // remove any unencrypted certificates, both key and value + delete opts[this.hcpPreferenceKeyEhealthCert] + + // add the encrypted certificate to the options + _.set( + opts, + this.hcpPreferenceKeyEhealthCertEncrypted, + utils.ua2text(new Uint8Array(crtEncrypted!)) + ) + } + + const crtValidityDate = this.getKeychainValidityDateInBrowserLocalStorage(hcp.id!!) + if (!!crtValidityDate) { + _.set(opts, this.hcpPreferenceKeyEhealthCertDate, crtValidityDate) + } + hcp.options = opts return hcp }) - .then(hcp => { - return this.hcpartyBaseApi.modifyHealthcareParty(hcp) - }) } importKeychainInBrowserFromHCP(hcpId: string): Promise { - return this.hcpartyBaseApi.getHealthcareParty(hcpId).then(hcp => { - const crt = _.get(hcp.options, this.hcpPreferenceKeyEhealthCert) - if (crt) { - this.saveKeychainInBrowserLocalStorageAsBase64(hcp.id!!, crt) + return this.hcpartyBaseApi.getHealthcareParty(hcpId).then(async (hcp: HealthcarePartyDto) => { + let crtCryp: Uint8Array | null = null; + if (!!hcp.options && !!hcp.options[this.hcpPreferenceKeyEhealthCertEncrypted]) { + crtCryp = this.utils.text2ua(hcp.options[this.hcpPreferenceKeyEhealthCertEncrypted]); + } + + const crtValidityDate = _.get(hcp.options, this.hcpPreferenceKeyEhealthCertDate) + + // store the validity date + if (!!crtValidityDate) { + this.saveKeychainValidityDateInBrowserLocalStorage(hcp.id!!, crtValidityDate) + } + + let crt: ArrayBuffer | null = null + let decryptionKey: CryptoKey | null = null + try { + decryptionKey = _.find( + await this.decryptAndImportAesHcPartyKeysForDelegators([hcp.id!], hcp.id!), + delegator => delegator.delegatorId === hcp.id + )!.key + } catch (e) { + console.error("Error while importing the AES key.") + } + if (!decryptionKey) { + throw new Error("No encryption key! eHealth certificate cannot be decrypted.") + } + + if (!!crtCryp && decryptionKey) { + try { + crt = await this.AES.decrypt(decryptionKey, crtCryp) + } catch (e) { + console.error(e) + } + } + + if (!crt) { + throw new Error( + `Error while saving certificate in browser local storage! Hcp ${hcp.id} has no certificate.` + ) + } else { + this.saveKeychainInBrowserLocalStorageAsBase64( + hcp.id!!, + btoa(String.fromCharCode.apply(null, new Uint8Array(crt))) + ) } + + return }) } @@ -1368,7 +1463,7 @@ export class IccCryptoXApi { */ syncEhealthCertificate(hcpId: string): Promise { return this.hcpartyBaseApi.getHealthcareParty(hcpId).then(hcp => { - const crtHCP = _.get(hcp.options, this.hcpPreferenceKeyEhealthCert) + const crtHCP = _.get(hcp.options, this.hcpPreferenceKeyEhealthCertEncrypted) const crtLC = this.getKeychainInBrowserLocalStorageAsBase64(hcp.id!!) const xor_hcp_localstorage = !(crtHCP && crtLC) && (crtHCP || crtLC) @@ -1392,9 +1487,13 @@ export class IccCryptoXApi { return localStorage.getItem(this.keychainLocalStoreIdPrefix + id) } + getKeychainValidityDateInBrowserLocalStorage(id: string) { + return localStorage.getItem(this.keychainValidityDateLocalStoreIdPrefix + id) + } + // noinspection JSUnusedGlobalSymbols loadKeychainFromBrowserLocalStorage(id: String) { - const lsItem = localStorage.getItem("org.taktik.icure.ehealth.keychain." + id) + const lsItem = localStorage.getItem(this.keychainLocalStoreIdPrefix + id) return lsItem !== null ? this._utils.base64toByteArray(lsItem) : null } diff --git a/icc-x-api/icc-doctemplate-x-api.ts b/icc-x-api/icc-doctemplate-x-api.ts index 92bf321f..8a0ca30e 100644 --- a/icc-x-api/icc-doctemplate-x-api.ts +++ b/icc-x-api/icc-doctemplate-x-api.ts @@ -94,7 +94,8 @@ export class IccDoctemplateXApi extends iccDoctemplateApi { return enc.decode(arr) } else if ( doc.contentType.startsWith("text/plain") || - doc.contentType.startsWith("text/html") + doc.contentType.startsWith("text/html") || + doc.contentType.startsWith("text/xml") ) { return doc.body } else { diff --git a/icc-x-api/icc-helement-x-api.ts b/icc-x-api/icc-helement-x-api.ts index a0ed8751..508dbe3d 100644 --- a/icc-x-api/icc-helement-x-api.ts +++ b/icc-x-api/icc-helement-x-api.ts @@ -51,9 +51,7 @@ export class IccHelementXApi extends iccHelementApi { .then(key => { if (!key) { console.error( - `SFK cannot be found for HealthElement ${ - helement.id - }. The health element will not be reachable from the patient side` + `SFK cannot be found for HealthElement ${helement.id}. The health element will not be reachable from the patient side` ) } @@ -118,31 +116,30 @@ export class IccHelementXApi extends iccHelementApi { findBy(hcpartyId: string, patient: models.PatientDto, keepObsoleteVersions: boolean = false) { return this.crypto .extractSFKsHierarchyFromDelegations(patient, hcpartyId) - .then( - secretForeignKeys => - secretForeignKeys && secretForeignKeys.length > 0 - ? Promise.all( - secretForeignKeys - .reduce( - (acc, level) => { - return acc.concat([ - { - hcpartyId: level.hcpartyId, - extractedKeys: level.extractedKeys.filter( - key => - !acc.some(previousLevel => previousLevel.extractedKeys.includes(key)) - ) - } - ]) - }, - [] as Array<{ hcpartyId: string; extractedKeys: Array }> - ) - .filter(l => l.extractedKeys.length > 0) - .map(({ hcpartyId, extractedKeys }) => - this.findByHCPartyPatientSecretFKeys(hcpartyId, extractedKeys.join(",")) - ) - ).then(results => _.uniqBy(_.flatMap(results), x => x.id)) - : Promise.resolve([]) + .then(secretForeignKeys => + secretForeignKeys && secretForeignKeys.length > 0 + ? Promise.all( + secretForeignKeys + .reduce( + (acc, level) => { + return acc.concat([ + { + hcpartyId: level.hcpartyId, + extractedKeys: level.extractedKeys.filter( + key => + !acc.some(previousLevel => previousLevel.extractedKeys.includes(key)) + ) + } + ]) + }, + [] as Array<{ hcpartyId: string; extractedKeys: Array }> + ) + .filter(l => l.extractedKeys.length > 0) + .map(({ hcpartyId, extractedKeys }) => + this.findByHCPartyPatientSecretFKeys(hcpartyId, extractedKeys.join(",")) + ) + ).then(results => _.uniqBy(_.flatMap(results), x => x.id)) + : Promise.resolve([]) ) .then((decryptedHelements: Array) => { const byIds: { [key: string]: models.HealthElementDto } = {} diff --git a/icc-x-api/icc-message-x-api.ts b/icc-x-api/icc-message-x-api.ts index ec75af26..86a8d29d 100644 --- a/icc-x-api/icc-message-x-api.ts +++ b/icc-x-api/icc-message-x-api.ts @@ -30,7 +30,7 @@ import { uuidBase36Half } from "./utils/efact-util" import { timeEncode } from "./utils/formatting-util" -import { fhcEfactcontrollerApi, EfactSendResponse } from "fhc-api" +import { fhcEfactApi, EfactSendResponse } from "fhc-api" import { utils } from "./crypto/utils" import { EfactMessage } from "fhc-api" import { @@ -1049,7 +1049,7 @@ export class IccMessageXApi extends iccMessageApi { xFHCKeystoreId: string, xFHCTokenId: string, xFHCPassPhrase: string, - efactApi: fhcEfactcontrollerApi, + efactApi: fhcEfactApi, fhcServer: string | undefined = undefined, prefixer?: (fed: InsuranceDto, hcpId: string) => Promise, isConnectedAsPmg: boolean = false, @@ -1099,8 +1099,7 @@ export class IccMessageXApi extends iccMessageApi { xFHCKeystoreId, xFHCTokenId, xFHCPassPhrase, - batch, - isConnectedAsPmg + batch ) //.then(() => { throw "ERREUR FORCEE" }) .catch(err => { diff --git a/icc-x-api/icc-patient-x-api.ts b/icc-x-api/icc-patient-x-api.ts index 74e8a9a2..c1abbcc8 100644 --- a/icc-x-api/icc-patient-x-api.ts +++ b/icc-x-api/icc-patient-x-api.ts @@ -782,13 +782,12 @@ export class IccPatientXApi extends iccPatientApi { } } return retry(() => this.getPatientWithUser(user, patId)) - .then( - (patient: models.PatientDto) => - patient.encryptionKeys && Object.keys(patient.encryptionKeys || {}).length - ? Promise.resolve(patient) - : this.initEncryptionKeys(user, patient).then((patient: models.PatientDto) => - this.modifyPatientWithUser(user, patient) - ) + .then((patient: models.PatientDto) => + patient.encryptionKeys && Object.keys(patient.encryptionKeys || {}).length + ? Promise.resolve(patient) + : this.initEncryptionKeys(user, patient).then((patient: models.PatientDto) => + this.modifyPatientWithUser(user, patient) + ) ) .then((patient: models.PatientDto | null) => { if (!patient) { @@ -807,82 +806,76 @@ export class IccPatientXApi extends iccPatientApi { retry(() => this.helementApi .findDelegationsStubsByHCPartyPatientSecretFKeys(ownerId, delSfks.join(",")) - .then( - hes => - parentId - ? this.helementApi - .findDelegationsStubsByHCPartyPatientSecretFKeys( - parentId, - delSfks.join(",") - ) - .then(moreHes => _.uniqBy(hes.concat(moreHes), "id")) - : hes + .then(hes => + parentId + ? this.helementApi + .findDelegationsStubsByHCPartyPatientSecretFKeys( + parentId, + delSfks.join(",") + ) + .then(moreHes => _.uniqBy(hes.concat(moreHes), "id")) + : hes ) ) as Promise>, retry(() => this.formApi .findDelegationsStubsByHCPartyPatientSecretFKeys(ownerId, delSfks.join(",")) - .then( - frms => - parentId - ? this.formApi - .findDelegationsStubsByHCPartyPatientSecretFKeys( - parentId, - delSfks.join(",") - ) - .then(moreFrms => _.uniqBy(frms.concat(moreFrms), "id")) - : frms + .then(frms => + parentId + ? this.formApi + .findDelegationsStubsByHCPartyPatientSecretFKeys( + parentId, + delSfks.join(",") + ) + .then(moreFrms => _.uniqBy(frms.concat(moreFrms), "id")) + : frms ) ) as Promise>, retry(() => this.contactApi .findByHCPartyPatientSecretFKeys(ownerId, delSfks.join(",")) - .then( - ctcs => - parentId - ? this.contactApi - .findByHCPartyPatientSecretFKeys(parentId, delSfks.join(",")) - .then(moreCtcs => _.uniqBy(ctcs.concat(moreCtcs), "id")) - : ctcs + .then(ctcs => + parentId + ? this.contactApi + .findByHCPartyPatientSecretFKeys(parentId, delSfks.join(",")) + .then(moreCtcs => _.uniqBy(ctcs.concat(moreCtcs), "id")) + : ctcs ) ) as Promise>, retry(() => this.invoiceApi .findDelegationsStubsByHCPartyPatientSecretFKeys(ownerId, delSfks.join(",")) - .then( - ivs => - parentId - ? this.invoiceApi - .findDelegationsStubsByHCPartyPatientSecretFKeys( - parentId, - delSfks.join(",") - ) - .then(moreIvs => _.uniqBy(ivs.concat(moreIvs), "id")) - : ivs + .then(ivs => + parentId + ? this.invoiceApi + .findDelegationsStubsByHCPartyPatientSecretFKeys( + parentId, + delSfks.join(",") + ) + .then(moreIvs => _.uniqBy(ivs.concat(moreIvs), "id")) + : ivs ) ) as Promise>, retry(() => this.classificationApi .findByHCPartyPatientSecretFKeys(ownerId, delSfks.join(",")) - .then( - cls => - parentId - ? this.classificationApi - .findByHCPartyPatientSecretFKeys(parentId, delSfks.join(",")) - .then(moreCls => _.uniqBy(cls.concat(moreCls), "id")) - : cls + .then(cls => + parentId + ? this.classificationApi + .findByHCPartyPatientSecretFKeys(parentId, delSfks.join(",")) + .then(moreCls => _.uniqBy(cls.concat(moreCls), "id")) + : cls ) ) as Promise>, retry(() => this.calendarItemApi .findByHCPartyPatientSecretFKeys(ownerId, delSfks.join(",")) - .then( - cls => - parentId - ? this.calendarItemApi - .findByHCPartyPatientSecretFKeys(parentId, delSfks.join(",")) - .then(moreCls => _.uniqBy(cls.concat(moreCls), "id")) - : cls + .then(cls => + parentId + ? this.calendarItemApi + .findByHCPartyPatientSecretFKeys(parentId, delSfks.join(",")) + .then(moreCls => _.uniqBy(cls.concat(moreCls), "id")) + : cls ) ) as Promise> ]).then(([hes, frms, ctcs, ivs, cls, cis]) => { @@ -1217,13 +1210,12 @@ export class IccPatientXApi extends iccPatientApi { const parentId = hcp.parentId return retry(() => this.getPatientWithUser(user, patId)) - .then( - (patient: models.PatientDto) => - patient.encryptionKeys && Object.keys(patient.encryptionKeys || {}).length - ? Promise.resolve(patient) - : this.initEncryptionKeys(user, patient).then((patient: models.PatientDto) => - this.modifyPatientWithUser(user, patient) - ) + .then((patient: models.PatientDto) => + patient.encryptionKeys && Object.keys(patient.encryptionKeys || {}).length + ? Promise.resolve(patient) + : this.initEncryptionKeys(user, patient).then((patient: models.PatientDto) => + this.modifyPatientWithUser(user, patient) + ) ) .then((patient: models.PatientDto | null) => { if (!patient) { @@ -1238,75 +1230,83 @@ export class IccPatientXApi extends iccPatientApi { retry(() => this.helementApi .findByHCPartyPatientSecretFKeys(ownerId, delSfks.join(",")) - .then( - hes => - parentId - ? this.helementApi - .findByHCPartyPatientSecretFKeys(parentId, delSfks.join(",")) - .then(moreHes => _.uniqBy(hes.concat(moreHes), "id")) - : hes + .then(hes => + parentId + ? this.helementApi + .findByHCPartyPatientSecretFKeys(parentId, delSfks.join(",")) + .then(moreHes => _.uniqBy(hes.concat(moreHes), "id")) + : hes ) ) as Promise>, retry(() => this.formApi .findByHCPartyPatientSecretFKeys(ownerId, delSfks.join(",")) - .then( - frms => - parentId - ? this.formApi - .findByHCPartyPatientSecretFKeys(parentId, delSfks.join(",")) - .then(moreFrms => _.uniqBy(frms.concat(moreFrms), "id")) - : frms + .then(frms => + parentId + ? this.formApi + .findByHCPartyPatientSecretFKeys(parentId, delSfks.join(",")) + .then(moreFrms => _.uniqBy(frms.concat(moreFrms), "id")) + : frms ) ) as Promise>, retry(() => this.contactApi .findByHCPartyPatientSecretFKeys(ownerId, delSfks.join(",")) - .then( - ctcs => - parentId - ? this.contactApi - .findByHCPartyPatientSecretFKeys(parentId, delSfks.join(",")) - .then(moreCtcs => _.uniqBy(ctcs.concat(moreCtcs), "id")) - : ctcs + .then(ctcs => + parentId + ? this.contactApi + .findByHCPartyPatientSecretFKeys(parentId, delSfks.join(",")) + .then(moreCtcs => _.uniqBy(ctcs.concat(moreCtcs), "id")) + : ctcs ) ) as Promise>, retry(() => this.invoiceApi .findByHCPartyPatientSecretFKeys(ownerId, delSfks.join(",")) - .then( - ivs => - parentId - ? this.invoiceApi - .findByHCPartyPatientSecretFKeys(parentId, delSfks.join(",")) - .then(moreIvs => _.uniqBy(ivs.concat(moreIvs), "id")) - : ivs + .then(ivs => + parentId + ? this.invoiceApi + .findByHCPartyPatientSecretFKeys(parentId, delSfks.join(",")) + .then(moreIvs => _.uniqBy(ivs.concat(moreIvs), "id")) + : ivs ) ) as Promise>, retry(() => this.classificationApi .findByHCPartyPatientSecretFKeys(ownerId, delSfks.join(",")) - .then( - cls => - parentId - ? this.classificationApi - .findByHCPartyPatientSecretFKeys(parentId, delSfks.join(",")) - .then(moreCls => _.uniqBy(cls.concat(moreCls), "id")) - : cls + .then(cls => + parentId + ? this.classificationApi + .findByHCPartyPatientSecretFKeys(parentId, delSfks.join(",")) + .then(moreCls => _.uniqBy(cls.concat(moreCls), "id")) + : cls ) ) as Promise>, - retry(() => - this.calendarItemApi - .findByHCPartyPatientSecretFKeys(ownerId, delSfks.join(",")) - .then( - cls => - parentId - ? this.calendarItemApi - .findByHCPartyPatientSecretFKeys(parentId, delSfks.join(",")) - .then(moreCls => _.uniqBy(cls.concat(moreCls), "id")) - : cls + retry(async () => { + const delegationSFKs = delSfks.join(",") + try { + let calendarItems = await this.calendarItemApi.findByHCPartyPatientSecretFKeys( + ownerId, + delegationSFKs ) - ) as Promise> + + if (parentId) { + const moreCalendarItems = await this.calendarItemApi.findByHCPartyPatientSecretFKeys( + parentId, + delegationSFKs + ) + + calendarItems = _.uniqBy(calendarItems.concat(moreCalendarItems), "id") + } + + return calendarItems + } catch (ex) { + console.log( + `exception occured exporting calendarItem for ownerId: ${ownerId} - ${ex}` + ) + //throw ex + } + }) as Promise> ]).then(([hes, frms, ctcs, ivs, cls, cis]) => { const docIds: { [key: string]: number } = {} ctcs.forEach( @@ -1397,7 +1397,11 @@ export class IccPatientXApi extends iccPatientApi { } async getPatientIdOfChildDocumentForHcpAndHcpParents( - childDocument: models.InvoiceDto | models.CalendarItemDto | models.ContactDto, + childDocument: + | models.InvoiceDto + | models.CalendarItemDto + | models.ContactDto + | models.AccessLogDto, hcpId: string ): Promise { const parentIdsArray = (await this.crypto.extractCryptedFKs(childDocument, hcpId)).extractedKeys diff --git a/icc-x-api/index.ts b/icc-x-api/index.ts index 19b3d9c4..e7d67eeb 100644 --- a/icc-x-api/index.ts +++ b/icc-x-api/index.ts @@ -1,3 +1,4 @@ +export * from "./icc-accesslog-x-api" export * from "./icc-bedrugs-x-api" export * from "./icc-bekmehr-x-api" export * from "./icc-calendar-item-x-api" diff --git a/icc-x-api/utils/efact-util.ts b/icc-x-api/utils/efact-util.ts index 1163d53d..6b76487f 100644 --- a/icc-x-api/utils/efact-util.ts +++ b/icc-x-api/utils/efact-util.ts @@ -329,7 +329,7 @@ function toInvoiceItem( invoiceItem.prescriberNorm = getPrescriberNorm(invoicingCode.prescriberNorm || 0) invoiceItem.reimbursedAmount = Number(((invoicingCode.reimbursement || 0) * 100).toFixed(0)) invoiceItem.relatedCode = Number(invoicingCode.relatedCode || 0) - invoiceItem.sideCode = getSideCode(invoicingCode.side || 0) + invoiceItem.sideCode = invoicingCode.side invoiceItem.timeOfDay = getTimeOfDay(invoicingCode.timeOfDay || 0) invoiceItem.units = invoicingCode.units || 1 invoiceItem.derogationMaxNumber = getDerogationMaxNumber(invoicingCode.derogationMaxNumber || 0) diff --git a/test/crypto-utils.ts b/test/crypto-utils.ts new file mode 100644 index 00000000..eaa3ad38 --- /dev/null +++ b/test/crypto-utils.ts @@ -0,0 +1,31 @@ +import { UtilsClass } from "../icc-x-api/crypto/utils" +import { expect } from "chai" +import "mocha" + +describe("ArrayBuffer methods", () => { + let utils: UtilsClass + + before(() => { + utils = new UtilsClass() + }) + + describe("truncateTrailingNulls", () => { + it("should truncate trailing nulls out of an Uint8Array without copying", () => { + const bytes = [72, 101, 108, 108, 111, 33] + const originalArray = Uint8Array.from([...bytes, 0, 0]) + const truncatedArray = utils.truncateTrailingNulls(originalArray) + expect(truncatedArray.buffer).to.equal(originalArray.buffer) + expect(Array.from(truncatedArray)).to.eql(bytes) + }) + + it("should preserve the offset into the buffer", () => { + const bytes = [72, 101, 108, 108, 111, 33] + const originalBuffer = new Uint8Array([0, 0, ...bytes, 0, 0]).buffer + const originalArray = new Uint8Array(originalBuffer, 2, bytes.length) + const truncatedArray = utils.truncateTrailingNulls(originalArray) + expect(truncatedArray.buffer).to.equal(originalArray.buffer) + expect(truncatedArray.byteOffset).to.equal(originalArray.byteOffset) + expect(Array.from(truncatedArray)).to.eql(bytes) + }) + }) +})