@medplum/agent 2.1.6 → 2.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.cjs +151 -100
- package/package.json +1 -1
- package/src/main.test.ts +114 -9
- package/src/main.ts +136 -92
package/dist/cjs/index.cjs
CHANGED
|
@@ -3523,7 +3523,7 @@ var require_websocket_server = __commonJS({
|
|
|
3523
3523
|
socket.once("finish", socket.destroy);
|
|
3524
3524
|
socket.end(
|
|
3525
3525
|
`HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r
|
|
3526
|
-
` + Object.keys(headers).map((
|
|
3526
|
+
` + Object.keys(headers).map((h3) => `${h3}: ${headers[h3]}`).join("\r\n") + "\r\n\r\n" + message
|
|
3527
3527
|
);
|
|
3528
3528
|
}
|
|
3529
3529
|
function abortHandshakeOrEmitwsClientError(server, req, socket, code, message) {
|
|
@@ -3652,9 +3652,6 @@ function K(r4) {
|
|
|
3652
3652
|
function $t(r4) {
|
|
3653
3653
|
return r4.resourceType + "/" + r4.id;
|
|
3654
3654
|
}
|
|
3655
|
-
function So(r4) {
|
|
3656
|
-
return r4?.reference?.split("/")[1];
|
|
3657
|
-
}
|
|
3658
3655
|
function un(r4) {
|
|
3659
3656
|
return r4.resourceType === "Patient" || r4.resourceType === "Practitioner" || r4.resourceType === "RelatedPerson";
|
|
3660
3657
|
}
|
|
@@ -4576,7 +4573,7 @@ function ue() {
|
|
|
4576
4573
|
}
|
|
4577
4574
|
var ri = ue();
|
|
4578
4575
|
var Cr = ((p2) => (p2.BOOLEAN = "BOOLEAN", p2.NUMBER = "NUMBER", p2.QUANTITY = "QUANTITY", p2.TEXT = "TEXT", p2.REFERENCE = "REFERENCE", p2.CANONICAL = "CANONICAL", p2.DATE = "DATE", p2.DATETIME = "DATETIME", p2.PERIOD = "PERIOD", p2.UUID = "UUID", p2))(Cr || {});
|
|
4579
|
-
var ui = ((
|
|
4576
|
+
var ui = ((g2) => (g2.EQUALS = "eq", g2.NOT_EQUALS = "ne", g2.GREATER_THAN = "gt", g2.LESS_THAN = "lt", g2.GREATER_THAN_OR_EQUALS = "ge", g2.LESS_THAN_OR_EQUALS = "le", g2.STARTS_AFTER = "sa", g2.ENDS_BEFORE = "eb", g2.APPROXIMATELY = "ap", g2.CONTAINS = "contains", g2.EXACT = "exact", g2.TEXT = "text", g2.NOT = "not", g2.ABOVE = "above", g2.BELOW = "below", g2.IN = "in", g2.NOT_IN = "not-in", g2.OF_TYPE = "of-type", g2.MISSING = "missing", g2.IDENTIFIER = "identifier", g2.ITERATE = "iterate", g2))(ui || {});
|
|
4580
4577
|
var ki = ((x) => (x.READ = "read", x.VREAD = "vread", x.UPDATE = "update", x.PATCH = "patch", x.DELETE = "delete", x.HISTORY = "history", x.HISTORY_INSTANCE = "history-instance", x.HISTORY_TYPE = "history-type", x.HISTORY_SYSTEM = "history-system", x.CREATE = "create", x.SEARCH = "search", x.SEARCH_TYPE = "search-type", x.SEARCH_SYSTEM = "search-system", x.SEARCH_COMPARTMENT = "search-compartment", x.CAPABILITIES = "capabilities", x.TRANSACTION = "transaction", x.BATCH = "batch", x.OPERATION = "operation", x))(ki || {});
|
|
4581
4578
|
function Mr(r4) {
|
|
4582
4579
|
if (typeof window < "u")
|
|
@@ -4858,20 +4855,21 @@ var Vt = class {
|
|
|
4858
4855
|
return Array.from(this.data.keys())[e];
|
|
4859
4856
|
}
|
|
4860
4857
|
};
|
|
4861
|
-
var Ki = "
|
|
4862
|
-
var zi =
|
|
4863
|
-
var Ji =
|
|
4858
|
+
var Ki = F.FHIR_JSON + ", */*; q=0.1";
|
|
4859
|
+
var zi = "https://api.medplum.com/";
|
|
4860
|
+
var Ji = 1e3;
|
|
4861
|
+
var Yi = 6e4;
|
|
4864
4862
|
var Gr = "Binary/";
|
|
4865
4863
|
var Kr = { resourceType: "Device", id: "system", deviceName: [{ name: "System" }] };
|
|
4866
|
-
var
|
|
4867
|
-
var
|
|
4868
|
-
var
|
|
4864
|
+
var Xi = ((o) => (o.ClientCredentials = "client_credentials", o.AuthorizationCode = "authorization_code", o.RefreshToken = "refresh_token", o.JwtBearer = "urn:ietf:params:oauth:grant-type:jwt-bearer", o.TokenExchange = "urn:ietf:params:oauth:grant-type:token-exchange", o))(Xi || {});
|
|
4865
|
+
var Zi = ((o) => (o.AccessToken = "urn:ietf:params:oauth:token-type:access_token", o.RefreshToken = "urn:ietf:params:oauth:token-type:refresh_token", o.IdToken = "urn:ietf:params:oauth:token-type:id_token", o.Saml1Token = "urn:ietf:params:oauth:token-type:saml1", o.Saml2Token = "urn:ietf:params:oauth:token-type:saml2", o))(Zi || {});
|
|
4866
|
+
var eo = ((e) => (e.JwtBearer = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", e))(eo || {});
|
|
4869
4867
|
var zr = class extends fe {
|
|
4870
4868
|
constructor(t) {
|
|
4871
4869
|
super();
|
|
4872
4870
|
if (t?.baseUrl && !t.baseUrl.startsWith("http"))
|
|
4873
4871
|
throw new Error("Base URL must start with http or https");
|
|
4874
|
-
if (this.options = t ?? {}, this.fetch = t?.fetch ??
|
|
4872
|
+
if (this.options = t ?? {}, this.fetch = t?.fetch ?? to(), this.storage = t?.storage ?? new ze(), this.createPdfImpl = t?.createPdf, this.baseUrl = Yr(t?.baseUrl ?? zi), this.fhirBaseUrl = Yr(Je(this.baseUrl, t?.fhirUrlPath ?? "fhir/R4/")), this.authorizeUrl = Je(this.baseUrl, t?.authorizeUrl ?? "oauth2/authorize"), this.tokenUrl = Je(this.baseUrl, t?.tokenUrl ?? "oauth2/token"), this.logoutUrl = Je(this.baseUrl, t?.logoutUrl ?? "oauth2/logout"), this.clientId = t?.clientId ?? "", this.clientSecret = t?.clientSecret ?? "", this.onUnauthenticated = t?.onUnauthenticated, this.cacheTime = t?.cacheTime ?? Yi, this.cacheTime > 0 ? this.requestCache = new Qe(t?.resourceCacheSize ?? Ji) : this.requestCache = void 0, t?.autoBatchTime ? (this.autoBatchTime = t.autoBatchTime, this.autoBatchQueue = []) : (this.autoBatchTime = 0, this.autoBatchQueue = void 0), t?.accessToken)
|
|
4875
4873
|
this.setAccessToken(t.accessToken);
|
|
4876
4874
|
else {
|
|
4877
4875
|
let n = this.getActiveLogin();
|
|
@@ -5256,7 +5254,7 @@ var zr = class extends fe {
|
|
|
5256
5254
|
}
|
|
5257
5255
|
async uploadMedia(t, n, i2, o, s) {
|
|
5258
5256
|
let a = await this.createBinary(t, i2, n);
|
|
5259
|
-
return this.createResource({
|
|
5257
|
+
return this.createResource({ resourceType: "Media", status: "completed", content: { contentType: n, url: Gr + a.id, title: i2 }, ...o }, s);
|
|
5260
5258
|
}
|
|
5261
5259
|
async bulkExport(t = "", n, i2, o) {
|
|
5262
5260
|
let s = t && `${t}/`, a = this.fhirUrl(`${s}$export`);
|
|
@@ -5376,7 +5374,7 @@ var zr = class extends fe {
|
|
|
5376
5374
|
}
|
|
5377
5375
|
addFetchOptionsDefaults(t) {
|
|
5378
5376
|
let n = t.headers;
|
|
5379
|
-
n || (n = {}, t.headers = n), n.Accept =
|
|
5377
|
+
n || (n = {}, t.headers = n), n.Accept || (n.Accept = Ki), n["X-Medplum"] = "extended", t.body && !n["Content-Type"] && (n["Content-Type"] = F.FHIR_JSON), this.accessToken ? n.Authorization = "Bearer " + this.accessToken : this.basicAuth && (n.Authorization = "Basic " + this.basicAuth), t.cache || (t.cache = "no-cache"), t.credentials || (t.credentials = "include");
|
|
5380
5378
|
}
|
|
5381
5379
|
setRequestContentType(t, n) {
|
|
5382
5380
|
t.headers || (t.headers = {});
|
|
@@ -5505,7 +5503,7 @@ var zr = class extends fe {
|
|
|
5505
5503
|
throw i2;
|
|
5506
5504
|
}
|
|
5507
5505
|
};
|
|
5508
|
-
function
|
|
5506
|
+
function to() {
|
|
5509
5507
|
if (!globalThis.fetch)
|
|
5510
5508
|
throw new Error("Fetch not available in this environment");
|
|
5511
5509
|
return globalThis.fetch.bind(globalThis);
|
|
@@ -5534,10 +5532,10 @@ function Zr(r4) {
|
|
|
5534
5532
|
let e = r4.entry?.map((t) => t.resource) ?? [];
|
|
5535
5533
|
return Object.assign(e, { bundle: r4 });
|
|
5536
5534
|
}
|
|
5537
|
-
var
|
|
5538
|
-
var
|
|
5539
|
-
var
|
|
5540
|
-
var
|
|
5535
|
+
var ro = [...ce, "->", "<<", ">>"];
|
|
5536
|
+
var no = ue().registerInfix("->", { precedence: f.Arrow }).registerInfix(";", { precedence: f.Semicolon });
|
|
5537
|
+
var io = [...ce, "eq", "ne", "co"];
|
|
5538
|
+
var ao = ue();
|
|
5541
5539
|
var G = class {
|
|
5542
5540
|
constructor(e = "\r", t = "|", n = "^", i2 = "~", o = "\\", s = "&") {
|
|
5543
5541
|
this.segmentSeparator = e;
|
|
@@ -5578,7 +5576,7 @@ var nn = class r {
|
|
|
5578
5576
|
}
|
|
5579
5577
|
buildAck() {
|
|
5580
5578
|
let e = /* @__PURE__ */ new Date(), t = this.getSegment("MSH"), n = t?.getField(3)?.toString() ?? "", i2 = t?.getField(4)?.toString() ?? "", o = t?.getField(5)?.toString() ?? "", s = t?.getField(6)?.toString() ?? "", a = t?.getField(10)?.toString() ?? "", c2 = t?.getField(12)?.toString() ?? "2.5.1";
|
|
5581
|
-
return new r([new me(["MSH", this.context.getMsh2(), o, s, n, i2,
|
|
5579
|
+
return new r([new me(["MSH", this.context.getMsh2(), o, s, n, i2, uo(e), "", this.buildAckMessageType(t), e.getTime().toString(), "P", c2], this.context), new me(["MSA", "AA", a, "OK"], this.context)]);
|
|
5582
5580
|
}
|
|
5583
5581
|
buildAckMessageType(e) {
|
|
5584
5582
|
let t = e?.getField(9), n = t?.getComponent(2), i2 = t?.getComponent(3), o = "ACK";
|
|
@@ -5639,13 +5637,14 @@ var oe = class r3 {
|
|
|
5639
5637
|
return new r3(e.split(t.repetitionSeparator).map((n) => n.split(t.componentSeparator)), t);
|
|
5640
5638
|
}
|
|
5641
5639
|
};
|
|
5642
|
-
function
|
|
5640
|
+
function uo(r4) {
|
|
5643
5641
|
let e = r4 instanceof Date ? r4 : new Date(r4), n = e.toISOString().replace(/[-:T]/g, "").replace(/(\.\d+)?Z$/, ""), i2 = e.getUTCMilliseconds();
|
|
5644
5642
|
return i2 > 0 && (n += "." + i2.toString()), n;
|
|
5645
5643
|
}
|
|
5646
5644
|
|
|
5647
5645
|
// ../hl7/dist/esm/index.mjs
|
|
5648
|
-
var import_net =
|
|
5646
|
+
var import_net = require("net");
|
|
5647
|
+
var import_net2 = __toESM(require("net"), 1);
|
|
5649
5648
|
var i = class extends EventTarget {
|
|
5650
5649
|
addEventListener(n, e, t) {
|
|
5651
5650
|
super.addEventListener(n, e, t);
|
|
@@ -5705,12 +5704,34 @@ var c = class extends i {
|
|
|
5705
5704
|
this.socket.end(), this.socket.destroy();
|
|
5706
5705
|
}
|
|
5707
5706
|
};
|
|
5707
|
+
var h2 = class extends i {
|
|
5708
|
+
constructor(e) {
|
|
5709
|
+
super();
|
|
5710
|
+
this.options = e, this.host = this.options.host, this.port = this.options.port;
|
|
5711
|
+
}
|
|
5712
|
+
connect() {
|
|
5713
|
+
return this.connection ? Promise.resolve(this.connection) : new Promise((e) => {
|
|
5714
|
+
let t = (0, import_net.connect)({ host: this.host, port: this.port }, () => {
|
|
5715
|
+
this.connection = new c(t), e(this.connection);
|
|
5716
|
+
});
|
|
5717
|
+
});
|
|
5718
|
+
}
|
|
5719
|
+
async send(e) {
|
|
5720
|
+
return (await this.connect()).send(e);
|
|
5721
|
+
}
|
|
5722
|
+
async sendAndWait(e) {
|
|
5723
|
+
return (await this.connect()).sendAndWait(e);
|
|
5724
|
+
}
|
|
5725
|
+
close() {
|
|
5726
|
+
this.connection && (this.connection.close(), delete this.connection);
|
|
5727
|
+
}
|
|
5728
|
+
};
|
|
5708
5729
|
var E2 = class {
|
|
5709
5730
|
constructor(n) {
|
|
5710
5731
|
this.handler = n;
|
|
5711
5732
|
}
|
|
5712
5733
|
start(n, e) {
|
|
5713
|
-
let t =
|
|
5734
|
+
let t = import_net2.default.createServer((o) => {
|
|
5714
5735
|
let s = new c(o, e);
|
|
5715
5736
|
this.handler(s);
|
|
5716
5737
|
});
|
|
@@ -5734,80 +5755,28 @@ var App = class {
|
|
|
5734
5755
|
constructor(medplum, agentId) {
|
|
5735
5756
|
this.medplum = medplum;
|
|
5736
5757
|
this.agentId = agentId;
|
|
5758
|
+
this.webSocketQueue = [];
|
|
5759
|
+
this.channels = /* @__PURE__ */ new Map();
|
|
5760
|
+
this.hl7Queue = [];
|
|
5761
|
+
this.live = false;
|
|
5737
5762
|
this.log = {
|
|
5738
5763
|
info: console.log,
|
|
5739
5764
|
warn: console.warn,
|
|
5740
5765
|
error: console.error
|
|
5741
5766
|
};
|
|
5742
|
-
this.channels = [];
|
|
5743
|
-
}
|
|
5744
|
-
async start() {
|
|
5745
|
-
this.log.info("Medplum service starting...");
|
|
5746
|
-
const agent = await this.medplum.readResource("Agent", this.agentId);
|
|
5747
|
-
for (const definition of agent.channel) {
|
|
5748
|
-
const endpoint = await this.medplum.readReference(definition.endpoint);
|
|
5749
|
-
const channel = new AgentHl7Channel(this, definition, endpoint);
|
|
5750
|
-
channel.start();
|
|
5751
|
-
this.channels.push(channel);
|
|
5752
|
-
}
|
|
5753
|
-
this.log.info("Medplum service started successfully");
|
|
5754
|
-
}
|
|
5755
|
-
stop() {
|
|
5756
|
-
this.log.info("Medplum service stopping...");
|
|
5757
|
-
this.channels.forEach((channel) => channel.stop());
|
|
5758
|
-
this.log.info("Medplum service stopped successfully");
|
|
5759
|
-
}
|
|
5760
|
-
};
|
|
5761
|
-
var AgentHl7Channel = class {
|
|
5762
|
-
constructor(app, definition, endpoint) {
|
|
5763
|
-
this.app = app;
|
|
5764
|
-
this.definition = definition;
|
|
5765
|
-
this.endpoint = endpoint;
|
|
5766
|
-
this.connections = [];
|
|
5767
|
-
this.server = new E2((connection) => {
|
|
5768
|
-
this.app.log.info("HL7 connection established");
|
|
5769
|
-
this.connections.push(new AgentHl7ChannelConnection(this, connection));
|
|
5770
|
-
});
|
|
5771
|
-
}
|
|
5772
|
-
start() {
|
|
5773
|
-
const address = new URL(this.endpoint.address);
|
|
5774
|
-
this.app.log.info(`Channel starting on ${address}`);
|
|
5775
|
-
this.server.start(parseInt(address.port, 10));
|
|
5776
|
-
this.app.log.info("Channel started successfully");
|
|
5777
|
-
}
|
|
5778
|
-
stop() {
|
|
5779
|
-
this.app.log.info("Channel stopping...");
|
|
5780
|
-
for (const connection of this.connections) {
|
|
5781
|
-
connection.close();
|
|
5782
|
-
}
|
|
5783
|
-
this.server.stop();
|
|
5784
|
-
this.app.log.info("Channel stopped successfully");
|
|
5785
|
-
}
|
|
5786
|
-
};
|
|
5787
|
-
var AgentHl7ChannelConnection = class {
|
|
5788
|
-
constructor(channel, hl7Connection) {
|
|
5789
|
-
this.channel = channel;
|
|
5790
|
-
this.hl7Connection = hl7Connection;
|
|
5791
|
-
this.webSocketQueue = [];
|
|
5792
|
-
this.hl7ConnectionQueue = [];
|
|
5793
|
-
this.live = false;
|
|
5794
|
-
const app = channel.app;
|
|
5795
|
-
const medplum = app.medplum;
|
|
5796
|
-
this.hl7Connection.addEventListener("message", (event) => this.handler(event));
|
|
5797
5767
|
const webSocketUrl = new URL(medplum.getBaseUrl());
|
|
5798
5768
|
webSocketUrl.protocol = webSocketUrl.protocol === "https:" ? "wss:" : "ws:";
|
|
5799
5769
|
webSocketUrl.pathname = "/ws/agent";
|
|
5800
|
-
|
|
5770
|
+
this.log.info(`Connecting to WebSocket: ${webSocketUrl.href}`);
|
|
5801
5771
|
this.webSocket = new wrapper_default(webSocketUrl);
|
|
5802
5772
|
this.webSocket.binaryType = "nodebuffer";
|
|
5803
|
-
this.webSocket.addEventListener("error",
|
|
5773
|
+
this.webSocket.addEventListener("error", (err) => this.log.error(err.message));
|
|
5804
5774
|
this.webSocket.addEventListener("open", () => {
|
|
5805
5775
|
this.webSocket.send(
|
|
5806
5776
|
JSON.stringify({
|
|
5807
5777
|
type: "connect",
|
|
5808
5778
|
accessToken: medplum.getAccessToken(),
|
|
5809
|
-
agentId
|
|
5810
|
-
botId: So(channel.definition.targetReference)
|
|
5779
|
+
agentId
|
|
5811
5780
|
})
|
|
5812
5781
|
);
|
|
5813
5782
|
});
|
|
@@ -5815,7 +5784,7 @@ var AgentHl7ChannelConnection = class {
|
|
|
5815
5784
|
try {
|
|
5816
5785
|
const data = e.data;
|
|
5817
5786
|
const str = data.toString("utf8");
|
|
5818
|
-
|
|
5787
|
+
this.log.info(`Received from WebSocket: ${str.replaceAll("\r", "\n")}`);
|
|
5819
5788
|
const command = JSON.parse(str);
|
|
5820
5789
|
switch (command.type) {
|
|
5821
5790
|
case "connected":
|
|
@@ -5823,24 +5792,40 @@ var AgentHl7ChannelConnection = class {
|
|
|
5823
5792
|
this.trySendToWebSocket();
|
|
5824
5793
|
break;
|
|
5825
5794
|
case "transmit":
|
|
5826
|
-
this.
|
|
5827
|
-
|
|
5795
|
+
this.addToHl7Queue(command);
|
|
5796
|
+
break;
|
|
5797
|
+
case "push":
|
|
5798
|
+
this.pushMessage(command);
|
|
5828
5799
|
break;
|
|
5829
5800
|
}
|
|
5830
5801
|
} catch (err) {
|
|
5831
|
-
|
|
5802
|
+
this.log.error(`WebSocket error: ${qn(err)}`);
|
|
5832
5803
|
}
|
|
5833
5804
|
});
|
|
5834
5805
|
}
|
|
5835
|
-
async
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
this.
|
|
5840
|
-
this
|
|
5841
|
-
|
|
5842
|
-
|
|
5806
|
+
async start() {
|
|
5807
|
+
this.log.info("Medplum service starting...");
|
|
5808
|
+
const agent = await this.medplum.readResource("Agent", this.agentId);
|
|
5809
|
+
for (const definition of agent.channel) {
|
|
5810
|
+
const endpoint = await this.medplum.readReference(definition.endpoint);
|
|
5811
|
+
const channel = new AgentHl7Channel(this, definition, endpoint);
|
|
5812
|
+
channel.start();
|
|
5813
|
+
this.channels.set(definition.name, channel);
|
|
5843
5814
|
}
|
|
5815
|
+
this.log.info("Medplum service started successfully");
|
|
5816
|
+
}
|
|
5817
|
+
stop() {
|
|
5818
|
+
this.log.info("Medplum service stopping...");
|
|
5819
|
+
this.channels.forEach((channel) => channel.stop());
|
|
5820
|
+
this.log.info("Medplum service stopped successfully");
|
|
5821
|
+
}
|
|
5822
|
+
addToWebSocketQueue(message) {
|
|
5823
|
+
this.webSocketQueue.push(message);
|
|
5824
|
+
this.trySendToWebSocket();
|
|
5825
|
+
}
|
|
5826
|
+
addToHl7Queue(message) {
|
|
5827
|
+
this.hl7Queue.push(message);
|
|
5828
|
+
this.trySendToHl7Connection();
|
|
5844
5829
|
}
|
|
5845
5830
|
trySendToWebSocket() {
|
|
5846
5831
|
if (this.live) {
|
|
@@ -5850,8 +5835,8 @@ var AgentHl7ChannelConnection = class {
|
|
|
5850
5835
|
this.webSocket.send(
|
|
5851
5836
|
JSON.stringify({
|
|
5852
5837
|
type: "transmit",
|
|
5853
|
-
|
|
5854
|
-
|
|
5838
|
+
accessToken: this.medplum.getAccessToken(),
|
|
5839
|
+
...msg
|
|
5855
5840
|
})
|
|
5856
5841
|
);
|
|
5857
5842
|
}
|
|
@@ -5859,16 +5844,82 @@ var AgentHl7ChannelConnection = class {
|
|
|
5859
5844
|
}
|
|
5860
5845
|
}
|
|
5861
5846
|
trySendToHl7Connection() {
|
|
5862
|
-
while (this.
|
|
5863
|
-
const msg = this.
|
|
5847
|
+
while (this.hl7Queue.length > 0) {
|
|
5848
|
+
const msg = this.hl7Queue.shift();
|
|
5864
5849
|
if (msg) {
|
|
5865
|
-
this.
|
|
5850
|
+
const channel = this.channels.get(msg.channel);
|
|
5851
|
+
if (channel) {
|
|
5852
|
+
const connection = channel.connections.get(msg.remote);
|
|
5853
|
+
if (connection) {
|
|
5854
|
+
connection.hl7Connection.send(nn.parse(msg.body));
|
|
5855
|
+
}
|
|
5856
|
+
}
|
|
5866
5857
|
}
|
|
5867
5858
|
}
|
|
5868
5859
|
}
|
|
5860
|
+
pushMessage(message) {
|
|
5861
|
+
const address = new URL(message.remote);
|
|
5862
|
+
const client = new h2({
|
|
5863
|
+
host: address.hostname,
|
|
5864
|
+
port: parseInt(address.port, 10)
|
|
5865
|
+
});
|
|
5866
|
+
client.sendAndWait(nn.parse(message.body)).then((response) => {
|
|
5867
|
+
this.log.info(`Response: ${response.toString().replaceAll("\r", "\n")}`);
|
|
5868
|
+
}).catch((err) => {
|
|
5869
|
+
this.log.error(`HL7 error: ${qn(err)}`);
|
|
5870
|
+
}).finally(() => {
|
|
5871
|
+
client.close();
|
|
5872
|
+
});
|
|
5873
|
+
}
|
|
5874
|
+
};
|
|
5875
|
+
var AgentHl7Channel = class {
|
|
5876
|
+
constructor(app, definition, endpoint) {
|
|
5877
|
+
this.app = app;
|
|
5878
|
+
this.definition = definition;
|
|
5879
|
+
this.endpoint = endpoint;
|
|
5880
|
+
this.connections = /* @__PURE__ */ new Map();
|
|
5881
|
+
this.server = new E2((connection) => this.handleNewConnection(connection));
|
|
5882
|
+
}
|
|
5883
|
+
start() {
|
|
5884
|
+
const address = new URL(this.endpoint.address);
|
|
5885
|
+
this.app.log.info(`Channel starting on ${address}`);
|
|
5886
|
+
this.server.start(parseInt(address.port, 10));
|
|
5887
|
+
this.app.log.info("Channel started successfully");
|
|
5888
|
+
}
|
|
5889
|
+
stop() {
|
|
5890
|
+
this.app.log.info("Channel stopping...");
|
|
5891
|
+
this.connections.forEach((connection) => connection.close());
|
|
5892
|
+
this.server.stop();
|
|
5893
|
+
this.app.log.info("Channel stopped successfully");
|
|
5894
|
+
}
|
|
5895
|
+
handleNewConnection(connection) {
|
|
5896
|
+
const c2 = new AgentHl7ChannelConnection(this, connection);
|
|
5897
|
+
this.app.log.info(`HL7 connection established: ${c2.remote}`);
|
|
5898
|
+
this.connections.set(c2.remote, c2);
|
|
5899
|
+
}
|
|
5900
|
+
};
|
|
5901
|
+
var AgentHl7ChannelConnection = class {
|
|
5902
|
+
constructor(channel, hl7Connection) {
|
|
5903
|
+
this.channel = channel;
|
|
5904
|
+
this.hl7Connection = hl7Connection;
|
|
5905
|
+
this.remote = `${hl7Connection.socket.remoteAddress}:${hl7Connection.socket.remotePort}`;
|
|
5906
|
+
this.hl7Connection.addEventListener("message", (event) => this.handler(event));
|
|
5907
|
+
}
|
|
5908
|
+
async handler(event) {
|
|
5909
|
+
try {
|
|
5910
|
+
this.channel.app.log.info("Received:");
|
|
5911
|
+
this.channel.app.log.info(event.message.toString().replaceAll("\r", "\n"));
|
|
5912
|
+
this.channel.app.addToWebSocketQueue({
|
|
5913
|
+
channel: this.channel.definition.name,
|
|
5914
|
+
remote: this.remote,
|
|
5915
|
+
body: event.message.toString()
|
|
5916
|
+
});
|
|
5917
|
+
} catch (err) {
|
|
5918
|
+
this.channel.app.log.error(`HL7 error: ${qn(err)}`);
|
|
5919
|
+
}
|
|
5920
|
+
}
|
|
5869
5921
|
close() {
|
|
5870
5922
|
this.hl7Connection.close();
|
|
5871
|
-
this.webSocket.close();
|
|
5872
5923
|
}
|
|
5873
5924
|
};
|
|
5874
5925
|
if (typeof require !== "undefined" && require.main === module) {
|
package/package.json
CHANGED
package/src/main.test.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { allOk, createReference, Hl7Message } from '@medplum/core';
|
|
1
|
+
import { allOk, createReference, Hl7Message, sleep } from '@medplum/core';
|
|
2
2
|
import { Agent, Bot, Endpoint, Resource } from '@medplum/fhirtypes';
|
|
3
|
-
import { Hl7Client } from '@medplum/hl7';
|
|
3
|
+
import { Hl7Client, Hl7Server } from '@medplum/hl7';
|
|
4
4
|
import { MockClient } from '@medplum/mock';
|
|
5
|
-
import { Server } from 'mock-socket';
|
|
5
|
+
import { Client, Server } from 'mock-socket';
|
|
6
6
|
import { App } from './main';
|
|
7
7
|
|
|
8
8
|
jest.mock('node-windows');
|
|
@@ -23,11 +23,28 @@ describe('Agent', () => {
|
|
|
23
23
|
|
|
24
24
|
endpoint = await medplum.createResource<Endpoint>({
|
|
25
25
|
resourceType: 'Endpoint',
|
|
26
|
-
address: 'mllp://0.0.0.0:
|
|
26
|
+
address: 'mllp://0.0.0.0:57000',
|
|
27
27
|
});
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
test('Runs successfully', async () => {
|
|
31
|
+
const mockServer = new Server('wss://example.com/ws/agent');
|
|
32
|
+
|
|
33
|
+
mockServer.on('connection', (socket) => {
|
|
34
|
+
socket.on('message', (data) => {
|
|
35
|
+
const command = JSON.parse((data as Buffer).toString('utf8'));
|
|
36
|
+
if (command.type === 'connect') {
|
|
37
|
+
socket.send(
|
|
38
|
+
Buffer.from(
|
|
39
|
+
JSON.stringify({
|
|
40
|
+
type: 'connected',
|
|
41
|
+
})
|
|
42
|
+
)
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
31
48
|
const agent = await medplum.createResource<Agent>({
|
|
32
49
|
resourceType: 'Agent',
|
|
33
50
|
channel: [
|
|
@@ -42,6 +59,7 @@ describe('Agent', () => {
|
|
|
42
59
|
await app.start();
|
|
43
60
|
app.stop();
|
|
44
61
|
app.stop();
|
|
62
|
+
mockServer.stop();
|
|
45
63
|
});
|
|
46
64
|
|
|
47
65
|
test('Send and receive', async () => {
|
|
@@ -61,13 +79,15 @@ describe('Agent', () => {
|
|
|
61
79
|
}
|
|
62
80
|
|
|
63
81
|
if (command.type === 'transmit') {
|
|
64
|
-
const hl7Message = Hl7Message.parse(command.
|
|
82
|
+
const hl7Message = Hl7Message.parse(command.body);
|
|
65
83
|
const ackMessage = hl7Message.buildAck();
|
|
66
84
|
socket.send(
|
|
67
85
|
Buffer.from(
|
|
68
86
|
JSON.stringify({
|
|
69
87
|
type: 'transmit',
|
|
70
|
-
|
|
88
|
+
channel: command.channel,
|
|
89
|
+
remote: command.remote,
|
|
90
|
+
body: ackMessage.toString(),
|
|
71
91
|
})
|
|
72
92
|
)
|
|
73
93
|
);
|
|
@@ -79,6 +99,7 @@ describe('Agent', () => {
|
|
|
79
99
|
resourceType: 'Agent',
|
|
80
100
|
channel: [
|
|
81
101
|
{
|
|
102
|
+
name: 'test',
|
|
82
103
|
endpoint: createReference(endpoint),
|
|
83
104
|
targetReference: createReference(bot),
|
|
84
105
|
},
|
|
@@ -90,11 +111,9 @@ describe('Agent', () => {
|
|
|
90
111
|
|
|
91
112
|
const client = new Hl7Client({
|
|
92
113
|
host: 'localhost',
|
|
93
|
-
port:
|
|
114
|
+
port: 57000,
|
|
94
115
|
});
|
|
95
116
|
|
|
96
|
-
await client.connect();
|
|
97
|
-
|
|
98
117
|
const response = await client.sendAndWait(
|
|
99
118
|
Hl7Message.parse(
|
|
100
119
|
'MSH|^~\\&|ADT1|MCM|LABADT|MCM|198808181126|SECURITY|ADT^A01|MSG00001|P|2.2\r' +
|
|
@@ -112,4 +131,90 @@ describe('Agent', () => {
|
|
|
112
131
|
app.stop();
|
|
113
132
|
mockServer.stop();
|
|
114
133
|
});
|
|
134
|
+
|
|
135
|
+
test('Push', async () => {
|
|
136
|
+
const mockServer = new Server('wss://example.com/ws/agent');
|
|
137
|
+
let mySocket: Client | undefined = undefined;
|
|
138
|
+
|
|
139
|
+
mockServer.on('connection', (socket) => {
|
|
140
|
+
mySocket = socket;
|
|
141
|
+
socket.on('message', (data) => {
|
|
142
|
+
const command = JSON.parse((data as Buffer).toString('utf8'));
|
|
143
|
+
if (command.type === 'connect') {
|
|
144
|
+
socket.send(
|
|
145
|
+
Buffer.from(
|
|
146
|
+
JSON.stringify({
|
|
147
|
+
type: 'connected',
|
|
148
|
+
})
|
|
149
|
+
)
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const agent = await medplum.createResource<Agent>({
|
|
156
|
+
resourceType: 'Agent',
|
|
157
|
+
channel: [
|
|
158
|
+
{
|
|
159
|
+
endpoint: createReference(endpoint),
|
|
160
|
+
targetReference: createReference(bot),
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Start an HL7 listener
|
|
166
|
+
const hl7Messages = [];
|
|
167
|
+
const hl7Server = new Hl7Server((conn) => {
|
|
168
|
+
conn.addEventListener('message', ({ message }) => {
|
|
169
|
+
hl7Messages.push(message);
|
|
170
|
+
conn.send(message.buildAck());
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
hl7Server.start(57001);
|
|
174
|
+
|
|
175
|
+
// Wait for server to start listening
|
|
176
|
+
while (!hl7Server.server?.listening) {
|
|
177
|
+
await sleep(100);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Start the app
|
|
181
|
+
const app = new App(medplum, agent.id as string);
|
|
182
|
+
await app.start();
|
|
183
|
+
|
|
184
|
+
// Wait for the WebSocket to connect
|
|
185
|
+
// eslint-disable-next-line no-unmodified-loop-condition
|
|
186
|
+
while (!mySocket) {
|
|
187
|
+
await sleep(100);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// At this point, we expect the websocket to be connected
|
|
191
|
+
expect(mySocket).toBeDefined();
|
|
192
|
+
|
|
193
|
+
// Send a push message
|
|
194
|
+
const wsClient = mySocket as unknown as Client;
|
|
195
|
+
wsClient.send(
|
|
196
|
+
Buffer.from(
|
|
197
|
+
JSON.stringify({
|
|
198
|
+
type: 'push',
|
|
199
|
+
body:
|
|
200
|
+
'MSH|^~\\&|ADT1|MCM|LABADT|MCM|198808181126|SECURITY|ADT^A01|MSG00001|P|2.2\r' +
|
|
201
|
+
'PID|||PATID1234^5^M11||JONES^WILLIAM^A^III||19610615|M-\r' +
|
|
202
|
+
'NK1|1|JONES^BARBARA^K|SPO|||||20011105\r' +
|
|
203
|
+
'PV1|1|I|2000^2012^01||||004777^LEBAUER^SIDNEY^J.|||SUR||-||1|A0-',
|
|
204
|
+
remote: 'mllp://localhost:57001',
|
|
205
|
+
})
|
|
206
|
+
)
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
// Wait for the HL7 message to be received
|
|
210
|
+
while (hl7Messages.length < 1) {
|
|
211
|
+
await sleep(100);
|
|
212
|
+
}
|
|
213
|
+
expect(hl7Messages.length).toBe(1);
|
|
214
|
+
|
|
215
|
+
// Shutdown everything
|
|
216
|
+
hl7Server.stop();
|
|
217
|
+
app.stop();
|
|
218
|
+
mockServer.stop();
|
|
219
|
+
});
|
|
115
220
|
});
|
package/src/main.ts
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
|
-
import { Hl7Message, MedplumClient,
|
|
2
|
-
import { AgentChannel,
|
|
3
|
-
import { Hl7Connection, Hl7MessageEvent, Hl7Server } from '@medplum/hl7';
|
|
1
|
+
import { Hl7Message, MedplumClient, normalizeErrorString } from '@medplum/core';
|
|
2
|
+
import { AgentChannel, Endpoint, Reference } from '@medplum/fhirtypes';
|
|
3
|
+
import { Hl7Client, Hl7Connection, Hl7MessageEvent, Hl7Server } from '@medplum/hl7';
|
|
4
4
|
import { EventLogger } from 'node-windows';
|
|
5
5
|
import WebSocket from 'ws';
|
|
6
6
|
|
|
7
|
+
interface QueueItem {
|
|
8
|
+
channel: string;
|
|
9
|
+
remote: string;
|
|
10
|
+
body: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
7
13
|
export class App {
|
|
8
14
|
readonly log: EventLogger;
|
|
9
|
-
readonly
|
|
15
|
+
readonly webSocket: WebSocket;
|
|
16
|
+
readonly webSocketQueue: QueueItem[] = [];
|
|
17
|
+
readonly channels = new Map<string, AgentHl7Channel>();
|
|
18
|
+
readonly hl7Queue: QueueItem[] = [];
|
|
19
|
+
live = false;
|
|
10
20
|
|
|
11
21
|
constructor(
|
|
12
22
|
readonly medplum: MedplumClient,
|
|
@@ -18,7 +28,46 @@ export class App {
|
|
|
18
28
|
error: console.error,
|
|
19
29
|
} as EventLogger;
|
|
20
30
|
|
|
21
|
-
|
|
31
|
+
const webSocketUrl = new URL(medplum.getBaseUrl());
|
|
32
|
+
webSocketUrl.protocol = webSocketUrl.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
33
|
+
webSocketUrl.pathname = '/ws/agent';
|
|
34
|
+
this.log.info(`Connecting to WebSocket: ${webSocketUrl.href}`);
|
|
35
|
+
|
|
36
|
+
this.webSocket = new WebSocket(webSocketUrl);
|
|
37
|
+
this.webSocket.binaryType = 'nodebuffer';
|
|
38
|
+
this.webSocket.addEventListener('error', (err) => this.log.error(err.message));
|
|
39
|
+
this.webSocket.addEventListener('open', () => {
|
|
40
|
+
this.webSocket.send(
|
|
41
|
+
JSON.stringify({
|
|
42
|
+
type: 'connect',
|
|
43
|
+
accessToken: medplum.getAccessToken(),
|
|
44
|
+
agentId,
|
|
45
|
+
})
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
this.webSocket.addEventListener('message', (e) => {
|
|
50
|
+
try {
|
|
51
|
+
const data = e.data as Buffer;
|
|
52
|
+
const str = data.toString('utf8');
|
|
53
|
+
this.log.info(`Received from WebSocket: ${str.replaceAll('\r', '\n')}`);
|
|
54
|
+
const command = JSON.parse(str);
|
|
55
|
+
switch (command.type) {
|
|
56
|
+
case 'connected':
|
|
57
|
+
this.live = true;
|
|
58
|
+
this.trySendToWebSocket();
|
|
59
|
+
break;
|
|
60
|
+
case 'transmit':
|
|
61
|
+
this.addToHl7Queue(command);
|
|
62
|
+
break;
|
|
63
|
+
case 'push':
|
|
64
|
+
this.pushMessage(command);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
} catch (err) {
|
|
68
|
+
this.log.error(`WebSocket error: ${normalizeErrorString(err)}`);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
22
71
|
}
|
|
23
72
|
|
|
24
73
|
async start(): Promise<void> {
|
|
@@ -30,7 +79,7 @@ export class App {
|
|
|
30
79
|
const endpoint = await this.medplum.readReference(definition.endpoint as Reference<Endpoint>);
|
|
31
80
|
const channel = new AgentHl7Channel(this, definition, endpoint);
|
|
32
81
|
channel.start();
|
|
33
|
-
this.channels.
|
|
82
|
+
this.channels.set(definition.name as string, channel);
|
|
34
83
|
}
|
|
35
84
|
|
|
36
85
|
this.log.info('Medplum service started successfully');
|
|
@@ -41,21 +90,80 @@ export class App {
|
|
|
41
90
|
this.channels.forEach((channel) => channel.stop());
|
|
42
91
|
this.log.info('Medplum service stopped successfully');
|
|
43
92
|
}
|
|
93
|
+
|
|
94
|
+
addToWebSocketQueue(message: QueueItem): void {
|
|
95
|
+
this.webSocketQueue.push(message);
|
|
96
|
+
this.trySendToWebSocket();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
addToHl7Queue(message: QueueItem): void {
|
|
100
|
+
this.hl7Queue.push(message);
|
|
101
|
+
this.trySendToHl7Connection();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private trySendToWebSocket(): void {
|
|
105
|
+
if (this.live) {
|
|
106
|
+
while (this.webSocketQueue.length > 0) {
|
|
107
|
+
const msg = this.webSocketQueue.shift();
|
|
108
|
+
if (msg) {
|
|
109
|
+
this.webSocket.send(
|
|
110
|
+
JSON.stringify({
|
|
111
|
+
type: 'transmit',
|
|
112
|
+
accessToken: this.medplum.getAccessToken(),
|
|
113
|
+
...msg,
|
|
114
|
+
})
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private trySendToHl7Connection(): void {
|
|
122
|
+
while (this.hl7Queue.length > 0) {
|
|
123
|
+
const msg = this.hl7Queue.shift();
|
|
124
|
+
if (msg) {
|
|
125
|
+
const channel = this.channels.get(msg.channel);
|
|
126
|
+
if (channel) {
|
|
127
|
+
const connection = channel.connections.get(msg.remote);
|
|
128
|
+
if (connection) {
|
|
129
|
+
connection.hl7Connection.send(Hl7Message.parse(msg.body));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private pushMessage(message: QueueItem): void {
|
|
137
|
+
const address = new URL(message.remote);
|
|
138
|
+
const client = new Hl7Client({
|
|
139
|
+
host: address.hostname,
|
|
140
|
+
port: parseInt(address.port, 10),
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
client
|
|
144
|
+
.sendAndWait(Hl7Message.parse(message.body))
|
|
145
|
+
.then((response) => {
|
|
146
|
+
this.log.info(`Response: ${response.toString().replaceAll('\r', '\n')}`);
|
|
147
|
+
})
|
|
148
|
+
.catch((err) => {
|
|
149
|
+
this.log.error(`HL7 error: ${normalizeErrorString(err)}`);
|
|
150
|
+
})
|
|
151
|
+
.finally(() => {
|
|
152
|
+
client.close();
|
|
153
|
+
});
|
|
154
|
+
}
|
|
44
155
|
}
|
|
45
156
|
|
|
46
157
|
export class AgentHl7Channel {
|
|
47
158
|
readonly server: Hl7Server;
|
|
48
|
-
readonly connections
|
|
159
|
+
readonly connections = new Map<string, AgentHl7ChannelConnection>();
|
|
49
160
|
|
|
50
161
|
constructor(
|
|
51
162
|
readonly app: App,
|
|
52
163
|
readonly definition: AgentChannel,
|
|
53
164
|
readonly endpoint: Endpoint
|
|
54
165
|
) {
|
|
55
|
-
this.server = new Hl7Server((connection) =>
|
|
56
|
-
this.app.log.info('HL7 connection established');
|
|
57
|
-
this.connections.push(new AgentHl7ChannelConnection(this, connection));
|
|
58
|
-
});
|
|
166
|
+
this.server = new Hl7Server((connection) => this.handleNewConnection(connection));
|
|
59
167
|
}
|
|
60
168
|
|
|
61
169
|
start(): void {
|
|
@@ -67,111 +175,47 @@ export class AgentHl7Channel {
|
|
|
67
175
|
|
|
68
176
|
stop(): void {
|
|
69
177
|
this.app.log.info('Channel stopping...');
|
|
70
|
-
|
|
71
|
-
connection.close();
|
|
72
|
-
}
|
|
178
|
+
this.connections.forEach((connection) => connection.close());
|
|
73
179
|
this.server.stop();
|
|
74
180
|
this.app.log.info('Channel stopped successfully');
|
|
75
181
|
}
|
|
182
|
+
|
|
183
|
+
private handleNewConnection(connection: Hl7Connection): void {
|
|
184
|
+
const c = new AgentHl7ChannelConnection(this, connection);
|
|
185
|
+
this.app.log.info(`HL7 connection established: ${c.remote}`);
|
|
186
|
+
this.connections.set(c.remote, c);
|
|
187
|
+
}
|
|
76
188
|
}
|
|
77
189
|
|
|
78
190
|
export class AgentHl7ChannelConnection {
|
|
79
|
-
readonly
|
|
80
|
-
readonly webSocketQueue: Hl7Message[] = [];
|
|
81
|
-
readonly hl7ConnectionQueue: Hl7Message[] = [];
|
|
82
|
-
live = false;
|
|
191
|
+
readonly remote: string;
|
|
83
192
|
|
|
84
193
|
constructor(
|
|
85
194
|
readonly channel: AgentHl7Channel,
|
|
86
195
|
readonly hl7Connection: Hl7Connection
|
|
87
196
|
) {
|
|
88
|
-
|
|
89
|
-
const medplum = app.medplum;
|
|
197
|
+
this.remote = `${hl7Connection.socket.remoteAddress}:${hl7Connection.socket.remotePort}`;
|
|
90
198
|
|
|
91
199
|
// Add listener immediately to handle incoming messages
|
|
92
200
|
this.hl7Connection.addEventListener('message', (event) => this.handler(event));
|
|
93
|
-
|
|
94
|
-
const webSocketUrl = new URL(medplum.getBaseUrl());
|
|
95
|
-
webSocketUrl.protocol = webSocketUrl.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
96
|
-
webSocketUrl.pathname = '/ws/agent';
|
|
97
|
-
console.log('Connecting to WebSocket:', webSocketUrl.href);
|
|
98
|
-
|
|
99
|
-
this.webSocket = new WebSocket(webSocketUrl);
|
|
100
|
-
this.webSocket.binaryType = 'nodebuffer';
|
|
101
|
-
this.webSocket.addEventListener('error', console.error);
|
|
102
|
-
this.webSocket.addEventListener('open', () => {
|
|
103
|
-
this.webSocket.send(
|
|
104
|
-
JSON.stringify({
|
|
105
|
-
type: 'connect',
|
|
106
|
-
accessToken: medplum.getAccessToken(),
|
|
107
|
-
agentId: channel.app.agentId,
|
|
108
|
-
botId: resolveId(channel.definition.targetReference as Reference<Bot>),
|
|
109
|
-
})
|
|
110
|
-
);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
this.webSocket.addEventListener('message', (e) => {
|
|
114
|
-
try {
|
|
115
|
-
const data = e.data as Buffer;
|
|
116
|
-
const str = data.toString('utf8');
|
|
117
|
-
console.log('Received from WebSocket:', str.replaceAll('\r', '\n'));
|
|
118
|
-
const command = JSON.parse(str);
|
|
119
|
-
switch (command.type) {
|
|
120
|
-
case 'connected':
|
|
121
|
-
this.live = true;
|
|
122
|
-
this.trySendToWebSocket();
|
|
123
|
-
break;
|
|
124
|
-
case 'transmit':
|
|
125
|
-
this.hl7ConnectionQueue.push(Hl7Message.parse(command.message));
|
|
126
|
-
this.trySendToHl7Connection();
|
|
127
|
-
break;
|
|
128
|
-
}
|
|
129
|
-
} catch (err) {
|
|
130
|
-
console.log('WebSocket error', err);
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
201
|
}
|
|
134
202
|
|
|
135
203
|
private async handler(event: Hl7MessageEvent): Promise<void> {
|
|
136
204
|
try {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
this.
|
|
140
|
-
|
|
205
|
+
this.channel.app.log.info('Received:');
|
|
206
|
+
this.channel.app.log.info(event.message.toString().replaceAll('\r', '\n'));
|
|
207
|
+
this.channel.app.addToWebSocketQueue({
|
|
208
|
+
channel: this.channel.definition.name as string,
|
|
209
|
+
remote: this.remote,
|
|
210
|
+
body: event.message.toString(),
|
|
211
|
+
});
|
|
141
212
|
} catch (err) {
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
private trySendToWebSocket(): void {
|
|
147
|
-
if (this.live) {
|
|
148
|
-
while (this.webSocketQueue.length > 0) {
|
|
149
|
-
const msg = this.webSocketQueue.shift();
|
|
150
|
-
if (msg) {
|
|
151
|
-
this.webSocket.send(
|
|
152
|
-
JSON.stringify({
|
|
153
|
-
type: 'transmit',
|
|
154
|
-
forwardedFor: this.hl7Connection.socket.remoteAddress,
|
|
155
|
-
message: msg.toString(),
|
|
156
|
-
})
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
private trySendToHl7Connection(): void {
|
|
164
|
-
while (this.hl7ConnectionQueue.length > 0) {
|
|
165
|
-
const msg = this.hl7ConnectionQueue.shift();
|
|
166
|
-
if (msg) {
|
|
167
|
-
this.hl7Connection.send(msg);
|
|
168
|
-
}
|
|
213
|
+
this.channel.app.log.error(`HL7 error: ${normalizeErrorString(err)}`);
|
|
169
214
|
}
|
|
170
215
|
}
|
|
171
216
|
|
|
172
217
|
close(): void {
|
|
173
218
|
this.hl7Connection.close();
|
|
174
|
-
this.webSocket.close();
|
|
175
219
|
}
|
|
176
220
|
}
|
|
177
221
|
|