@morgan-stanley/composeui-messaging-message-router 0.1.0-alpha.10

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.
@@ -0,0 +1,860 @@
1
+ (function () {
2
+ 'use strict';
3
+
4
+ class AsyncDisposableWrapper {
5
+ constructor(messageRouterClient, serviceName) {
6
+ this.messageRouterClient = messageRouterClient;
7
+ this.serviceName = serviceName;
8
+ }
9
+ [Symbol.asyncDispose]() {
10
+ return this.messageRouterClient.unregisterService(this.serviceName);
11
+ }
12
+ }
13
+
14
+ /*
15
+ * Morgan Stanley makes this available to you under the Apache License,
16
+ * Version 2.0 (the "License"). You may obtain a copy of the License at
17
+ * http://www.apache.org/licenses/LICENSE-2.0.
18
+ * See the NOTICE file distributed with this work for additional information
19
+ * regarding copyright ownership. Unless required by applicable law or agreed
20
+ * to in writing, software distributed under the License is distributed on an
21
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
22
+ * or implied. See the License for the specific language governing permissions
23
+ * and limitations under the License.
24
+ *
25
+ */
26
+ /**
27
+ * Implementation of IMessaging interface using MessageRouter.
28
+ * Provides messaging capabilities through the MessageRouter client for ComposeUI applications.
29
+ */
30
+ class MessageRouterMessaging {
31
+ /**
32
+ * Creates a new instance of MessageRouterMessaging.
33
+ * @param messageRouterClient The MessageRouter client instance to use for communication.
34
+ */
35
+ constructor(messageRouterClient) {
36
+ this.messageRouterClient = messageRouterClient;
37
+ }
38
+ /**
39
+ * Subscribes to messages on a specific topic.
40
+ * @param topic The topic to subscribe to.
41
+ * @param subscriber Callback function that will be invoked with each received message.
42
+ * @param cancellationToken Optional signal to cancel the subscription setup.
43
+ * @returns A Promise that resolves to an Unsubscribable object for managing the subscription.
44
+ * @remarks If a message is received without a payload, a warning will be logged and the subscriber will not be called.
45
+ */
46
+ subscribe(topic, subscriber, cancellationToken) {
47
+ return this.messageRouterClient.subscribe(topic, (message) => {
48
+ if (!message.payload) {
49
+ console.warn(`Received empty message on topic ${topic}`);
50
+ return;
51
+ }
52
+ subscriber(message.payload);
53
+ });
54
+ }
55
+ /**
56
+ * Publishes a message to a specific topic.
57
+ * @param topic The topic to publish to.
58
+ * @param message The message content to publish.
59
+ * @param cancellationToken Optional signal to cancel the publish operation.
60
+ * @returns A Promise that resolves when the message has been published.
61
+ */
62
+ publish(topic, message, cancellationToken) {
63
+ return this.messageRouterClient.publish(topic, message);
64
+ }
65
+ /**
66
+ * Registers a service handler for a specific service name.
67
+ * @param serviceName The name of the service to register.
68
+ * @param serviceHandler The handler function that will process service requests.
69
+ * @param cancellationToken Optional signal to cancel the service registration.
70
+ * @returns A Promise that resolves to an AsyncDisposable for managing the service registration.
71
+ * @remarks The service handler will receive the payload from the request and should return a response.
72
+ * Both the payload and response can be null.
73
+ */
74
+ async registerService(serviceName, serviceHandler, cancellationToken) {
75
+ await this.messageRouterClient.registerService(serviceName, async (endpoint, payload, context) => {
76
+ const result = await serviceHandler(payload);
77
+ return result;
78
+ });
79
+ const disposable = new AsyncDisposableWrapper(this.messageRouterClient, serviceName);
80
+ return disposable;
81
+ }
82
+ /**
83
+ * Invokes a registered service.
84
+ * @param serviceName The name of the service to invoke.
85
+ * @param payload Optional payload to send with the service request.
86
+ * @param cancellationToken Optional signal to cancel the service invocation.
87
+ * @returns A Promise that resolves to the service response or null if no response is received.
88
+ * @remarks If the payload is null, the service will be invoked without a payload.
89
+ * The response will be null if the service doesn't return a response or if an error occurs.
90
+ */
91
+ async invokeService(serviceName, payload, cancellationToken) {
92
+ if (payload == null) {
93
+ const result = await this.messageRouterClient.invoke(serviceName);
94
+ if (!result) {
95
+ return null;
96
+ }
97
+ return result;
98
+ }
99
+ const response = await this.messageRouterClient.invoke(serviceName, payload);
100
+ if (!response) {
101
+ return null;
102
+ }
103
+ return response;
104
+ }
105
+ }
106
+
107
+ /**
108
+ * @license
109
+ * author: Morgan Stanley
110
+ * composeui-messaging-client.js v0.1.0-alpha.10
111
+ * Released under the Apache-2.0 license.
112
+ */
113
+
114
+ /*
115
+ * Morgan Stanley makes this available to you under the Apache License,
116
+ * Version 2.0 (the "License"). You may obtain a copy of the License at
117
+ * http://www.apache.org/licenses/LICENSE-2.0.
118
+ * See the NOTICE file distributed with this work for additional information
119
+ * regarding copyright ownership. Unless required by applicable law or agreed
120
+ * to in writing, software distributed under the License is distributed on an
121
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
122
+ * or implied. See the License for the specific language governing permissions
123
+ * and limitations under the License.
124
+ *
125
+ */
126
+ const ErrorNames = {
127
+ duplicateEndpoint: "DuplicateEndpoint",
128
+ duplicateRequestId: "DuplicateRequestId",
129
+ invalidEndpoint: "InvalidEndpoint",
130
+ invalidTopic: "InvalidTopic",
131
+ unknownEndpoint: "UnknownEndpoint",
132
+ connectionClosed: "ConnectionClosed",
133
+ connectionAborted: "ConnectionAborted",
134
+ connectionFailed: "ConnectionFailed",
135
+ };
136
+
137
+ /*
138
+ * Morgan Stanley makes this available to you under the Apache License,
139
+ * Version 2.0 (the "License"). You may obtain a copy of the License at
140
+ * http://www.apache.org/licenses/LICENSE-2.0.
141
+ * See the NOTICE file distributed with this work for additional information
142
+ * regarding copyright ownership. Unless required by applicable law or agreed
143
+ * to in writing, software distributed under the License is distributed on an
144
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
145
+ * or implied. See the License for the specific language governing permissions
146
+ * and limitations under the License.
147
+ *
148
+ */
149
+ class WebSocketConnection {
150
+ options;
151
+ constructor(options) {
152
+ this.options = options;
153
+ }
154
+ connect() {
155
+ return new Promise((resolve, reject) => {
156
+ this.websocket = new WebSocket(this.options.url);
157
+ this.websocket.addEventListener('open', () => {
158
+ this.isConnected = true;
159
+ resolve();
160
+ });
161
+ this.websocket.addEventListener('message', ev => {
162
+ const message = WebSocketConnection.deserializeMessage(ev.data);
163
+ this._onMessage?.call(undefined, message);
164
+ });
165
+ this.websocket.addEventListener('error', ev => {
166
+ if (!this.isConnected) {
167
+ reject();
168
+ }
169
+ else {
170
+ this.isConnected = false;
171
+ this._onError?.call(undefined, new Error());
172
+ }
173
+ });
174
+ this.websocket.addEventListener('close', () => {
175
+ this.isConnected = false;
176
+ delete this.websocket;
177
+ this._onClose?.call(undefined);
178
+ });
179
+ });
180
+ }
181
+ send(message) {
182
+ if (!this.websocket) {
183
+ return Promise.reject();
184
+ }
185
+ this.websocket.send(JSON.stringify(message));
186
+ return Promise.resolve();
187
+ }
188
+ close() {
189
+ if (this.isConnected) {
190
+ this.websocket?.close(1000, "Closed by client");
191
+ this.isConnected = false;
192
+ delete this.websocket;
193
+ }
194
+ return Promise.resolve();
195
+ }
196
+ onMessage(callback) {
197
+ this._onMessage = callback;
198
+ }
199
+ onError(callback) {
200
+ this._onError = callback;
201
+ }
202
+ onClose(callback) {
203
+ this._onClose = callback;
204
+ }
205
+ static deserializeMessage(data) {
206
+ const msg = JSON.parse(data);
207
+ return msg;
208
+ }
209
+ websocket;
210
+ isConnected = false;
211
+ messageQueue = [];
212
+ _onMessage;
213
+ _onError;
214
+ _onClose;
215
+ }
216
+
217
+ /*
218
+ * Morgan Stanley makes this available to you under the Apache License,
219
+ * Version 2.0 (the "License"). You may obtain a copy of the License at
220
+ * http://www.apache.org/licenses/LICENSE-2.0.
221
+ * See the NOTICE file distributed with this work for additional information
222
+ * regarding copyright ownership. Unless required by applicable law or agreed
223
+ * to in writing, software distributed under the License is distributed on an
224
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
225
+ * or implied. See the License for the specific language governing permissions
226
+ * and limitations under the License.
227
+ *
228
+ */
229
+ class Deferred {
230
+ constructor() {
231
+ this.promise = new Promise((resolve, reject) => {
232
+ this.resolve =
233
+ (value) => {
234
+ this.settle();
235
+ resolve(value);
236
+ };
237
+ this.reject =
238
+ (reason) => {
239
+ this.settle();
240
+ reject(reason);
241
+ };
242
+ });
243
+ }
244
+ resolve = () => { };
245
+ reject = () => { };
246
+ promise;
247
+ settle() {
248
+ const noop = () => { };
249
+ this.resolve = noop;
250
+ this.reject = noop;
251
+ }
252
+ }
253
+
254
+ /*
255
+ * Morgan Stanley makes this available to you under the Apache License,
256
+ * Version 2.0 (the "License"). You may obtain a copy of the License at
257
+ * http://www.apache.org/licenses/LICENSE-2.0.
258
+ * See the NOTICE file distributed with this work for additional information
259
+ * regarding copyright ownership. Unless required by applicable law or agreed
260
+ * to in writing, software distributed under the License is distributed on an
261
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
262
+ * or implied. See the License for the specific language governing permissions
263
+ * and limitations under the License.
264
+ *
265
+ */
266
+ function isProtocolError(err) {
267
+ return (typeof err === "object") && ("name" in err);
268
+ }
269
+
270
+ /*
271
+ * Morgan Stanley makes this available to you under the Apache License,
272
+ * Version 2.0 (the "License"). You may obtain a copy of the License at
273
+ * http://www.apache.org/licenses/LICENSE-2.0.
274
+ * See the NOTICE file distributed with this work for additional information
275
+ * regarding copyright ownership. Unless required by applicable law or agreed
276
+ * to in writing, software distributed under the License is distributed on an
277
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
278
+ * or implied. See the License for the specific language governing permissions
279
+ * and limitations under the License.
280
+ *
281
+ */
282
+ class MessageRouterError extends Error {
283
+ constructor(err, message, stack) {
284
+ let [name, msg] = isProtocolError(err) ? [err.name, err.message] : [err, message];
285
+ super(msg);
286
+ this.name = name;
287
+ if (stack) {
288
+ this.stack = stack;
289
+ }
290
+ }
291
+ }
292
+ function createProtocolError(err) {
293
+ if (typeof err === "string")
294
+ return {
295
+ name: "Error",
296
+ message: err
297
+ };
298
+ return {
299
+ name: err.name ?? "Error",
300
+ message: err.message ?? `Unknown error (${err})`
301
+ };
302
+ }
303
+
304
+ /*
305
+ * Morgan Stanley makes this available to you under the Apache License,
306
+ * Version 2.0 (the "License"). You may obtain a copy of the License at
307
+ * http://www.apache.org/licenses/LICENSE-2.0.
308
+ * See the NOTICE file distributed with this work for additional information
309
+ * regarding copyright ownership. Unless required by applicable law or agreed
310
+ * to in writing, software distributed under the License is distributed on an
311
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
312
+ * or implied. See the License for the specific language governing permissions
313
+ * and limitations under the License.
314
+ *
315
+ */
316
+ class ThrowHelper {
317
+ static duplicateEndpoint(endpoint) {
318
+ return new MessageRouterError({ name: ErrorNames.duplicateEndpoint, message: `Duplicate endpoint registration: '${endpoint}'` });
319
+ }
320
+ static duplicateRequestId() {
321
+ return new MessageRouterError({ name: ErrorNames.duplicateRequestId, message: "Duplicate request ID" });
322
+ }
323
+ static invalidEndpoint(endpoint) {
324
+ return new MessageRouterError({ name: ErrorNames.invalidEndpoint, message: `Invalid endpoint: '${endpoint}'` });
325
+ }
326
+ static invalidTopic(topic) {
327
+ return new MessageRouterError({ name: ErrorNames.invalidTopic, message: `Invalid topic: '${topic}'` });
328
+ }
329
+ static unknownEndpoint(endpoint) {
330
+ return new MessageRouterError({ name: ErrorNames.unknownEndpoint, message: `Unknown endpoint: ${endpoint}` });
331
+ }
332
+ static connectionClosed() {
333
+ return new MessageRouterError({ name: ErrorNames.connectionClosed, message: "The connection has been closed" });
334
+ }
335
+ static connectionFailed(message) {
336
+ return new MessageRouterError({ name: ErrorNames.connectionFailed, message: `Connection failed with message ${message}` });
337
+ }
338
+ static connectionAborted() {
339
+ return new MessageRouterError({ name: ErrorNames.connectionAborted, message: "The connection dropped unexpectedly" });
340
+ }
341
+ }
342
+
343
+ /*
344
+ * Morgan Stanley makes this available to you under the Apache License,
345
+ * Version 2.0 (the "License"). You may obtain a copy of the License at
346
+ * http://www.apache.org/licenses/LICENSE-2.0.
347
+ * See the NOTICE file distributed with this work for additional information
348
+ * regarding copyright ownership. Unless required by applicable law or agreed
349
+ * to in writing, software distributed under the License is distributed on an
350
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
351
+ * or implied. See the License for the specific language governing permissions
352
+ * and limitations under the License.
353
+ *
354
+ */
355
+ function isResponse(message) {
356
+ return (message.type === "InvokeResponse"
357
+ || message.type === "RegisterServiceResponse"
358
+ || message.type === "UnregisterServiceResponse"
359
+ || message.type === "SubscribeResponse"
360
+ || message.type === "UnsubscribeResponse"
361
+ || message.type === "PublishResponse");
362
+ }
363
+
364
+ /*
365
+ * Morgan Stanley makes this available to you under the Apache License,
366
+ * Version 2.0 (the "License"). You may obtain a copy of the License at
367
+ * http://www.apache.org/licenses/LICENSE-2.0.
368
+ * See the NOTICE file distributed with this work for additional information
369
+ * regarding copyright ownership. Unless required by applicable law or agreed
370
+ * to in writing, software distributed under the License is distributed on an
371
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
372
+ * or implied. See the License for the specific language governing permissions
373
+ * and limitations under the License.
374
+ *
375
+ */
376
+ function isConnectResponse(message) {
377
+ return message.type == "ConnectResponse";
378
+ }
379
+
380
+ /*
381
+ * Morgan Stanley makes this available to you under the Apache License,
382
+ * Version 2.0 (the "License"). You may obtain a copy of the License at
383
+ * http://www.apache.org/licenses/LICENSE-2.0.
384
+ * See the NOTICE file distributed with this work for additional information
385
+ * regarding copyright ownership. Unless required by applicable law or agreed
386
+ * to in writing, software distributed under the License is distributed on an
387
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
388
+ * or implied. See the License for the specific language governing permissions
389
+ * and limitations under the License.
390
+ *
391
+ */
392
+ function isInvokeRequest(message) {
393
+ return message.type == "Invoke";
394
+ }
395
+
396
+ /*
397
+ * Morgan Stanley makes this available to you under the Apache License,
398
+ * Version 2.0 (the "License"). You may obtain a copy of the License at
399
+ * http://www.apache.org/licenses/LICENSE-2.0.
400
+ * See the NOTICE file distributed with this work for additional information
401
+ * regarding copyright ownership. Unless required by applicable law or agreed
402
+ * to in writing, software distributed under the License is distributed on an
403
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
404
+ * or implied. See the License for the specific language governing permissions
405
+ * and limitations under the License.
406
+ *
407
+ */
408
+ function isTopicMessage(message) {
409
+ return message.type == "Topic";
410
+ }
411
+
412
+ /*
413
+ * Morgan Stanley makes this available to you under the Apache License,
414
+ * Version 2.0 (the "License"). You may obtain a copy of the License at
415
+ * http://www.apache.org/licenses/LICENSE-2.0.
416
+ * See the NOTICE file distributed with this work for additional information
417
+ * regarding copyright ownership. Unless required by applicable law or agreed
418
+ * to in writing, software distributed under the License is distributed on an
419
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
420
+ * or implied. See the License for the specific language governing permissions
421
+ * and limitations under the License.
422
+ *
423
+ */
424
+ var ClientState;
425
+ (function (ClientState) {
426
+ ClientState[ClientState["Created"] = 0] = "Created";
427
+ ClientState[ClientState["Connecting"] = 1] = "Connecting";
428
+ ClientState[ClientState["Connected"] = 2] = "Connected";
429
+ ClientState[ClientState["Closing"] = 3] = "Closing";
430
+ ClientState[ClientState["Closed"] = 4] = "Closed";
431
+ })(ClientState || (ClientState = {}));
432
+ class MessageRouterClient {
433
+ connection;
434
+ options;
435
+ constructor(connection, options) {
436
+ this.connection = connection;
437
+ this.options = options;
438
+ this.options = options ?? {};
439
+ }
440
+ _clientId;
441
+ get clientId() {
442
+ return this._clientId;
443
+ }
444
+ connect() {
445
+ switch (this._state) {
446
+ case ClientState.Connected:
447
+ return Promise.resolve();
448
+ case ClientState.Created:
449
+ return this.connectCore();
450
+ case ClientState.Connecting:
451
+ return this.connected.promise;
452
+ }
453
+ return Promise.reject(ThrowHelper.connectionClosed());
454
+ }
455
+ close() {
456
+ return this.closeCore();
457
+ }
458
+ async subscribe(topicName, subscriber) {
459
+ this.checkState();
460
+ if (this.pendingUnsubscriptions[topicName]) {
461
+ await this.pendingUnsubscriptions[topicName];
462
+ }
463
+ let needsSubscription = false;
464
+ let topic = this.topics[topicName];
465
+ if (!topic) {
466
+ this.topics[topicName] = topic = new Topic(() => this.unsubscribe(topicName));
467
+ needsSubscription = true;
468
+ }
469
+ if (typeof subscriber === "function") {
470
+ subscriber = { next: subscriber };
471
+ }
472
+ const subscription = topic.subscribe(subscriber);
473
+ if (needsSubscription) {
474
+ try {
475
+ await this.sendRequest({
476
+ requestId: this.getRequestId(),
477
+ type: "Subscribe",
478
+ topic: topicName
479
+ });
480
+ }
481
+ catch (error) {
482
+ delete this.topics[topicName];
483
+ throw error;
484
+ }
485
+ }
486
+ return subscription;
487
+ }
488
+ async publish(topic, payload, options) {
489
+ this.checkState();
490
+ await this.sendRequest({
491
+ type: "Publish",
492
+ requestId: this.getRequestId(),
493
+ topic,
494
+ payload,
495
+ correlationId: options?.correlationId
496
+ });
497
+ }
498
+ async invoke(endpoint, payload, options) {
499
+ this.checkState();
500
+ const response = await this.sendRequest({
501
+ type: "Invoke",
502
+ requestId: this.getRequestId(),
503
+ endpoint,
504
+ payload,
505
+ correlationId: options?.correlationId
506
+ });
507
+ return response.payload;
508
+ }
509
+ async registerService(endpoint, handler, descriptor) {
510
+ this.checkState();
511
+ if (this.endpointHandlers[endpoint])
512
+ throw ThrowHelper.duplicateEndpoint(endpoint);
513
+ this.endpointHandlers[endpoint] = handler;
514
+ try {
515
+ await this.sendRequest({
516
+ type: "RegisterService",
517
+ requestId: this.getRequestId(),
518
+ endpoint,
519
+ descriptor
520
+ });
521
+ }
522
+ catch (error) {
523
+ delete this.endpointHandlers[endpoint];
524
+ throw error;
525
+ }
526
+ }
527
+ async unregisterService(endpoint) {
528
+ this.checkState();
529
+ if (!this.endpointHandlers[endpoint])
530
+ return;
531
+ await this.sendRequest({
532
+ type: "UnregisterService",
533
+ requestId: this.getRequestId(),
534
+ endpoint
535
+ });
536
+ delete this.endpointHandlers[endpoint];
537
+ }
538
+ registerEndpoint(endpoint, handler, descriptor) {
539
+ this.checkState();
540
+ if (this.endpointHandlers[endpoint])
541
+ throw ThrowHelper.duplicateEndpoint(endpoint);
542
+ this.endpointHandlers[endpoint] = handler;
543
+ return Promise.resolve();
544
+ }
545
+ unregisterEndpoint(endpoint) {
546
+ this.checkState();
547
+ delete this.endpointHandlers[endpoint];
548
+ return Promise.resolve();
549
+ }
550
+ get state() {
551
+ return this._state;
552
+ }
553
+ _state = ClientState.Created;
554
+ lastRequestId = 0;
555
+ topics = {};
556
+ connected = new Deferred();
557
+ closed = new Deferred();
558
+ pendingRequests = {};
559
+ endpointHandlers = {};
560
+ pendingUnsubscriptions = {};
561
+ async connectCore() {
562
+ this._state = ClientState.Connecting;
563
+ try {
564
+ this.connection.onMessage(this.handleMessage.bind(this));
565
+ this.connection.onError(this.handleError.bind(this));
566
+ this.connection.onClose(this.handleClose.bind(this));
567
+ await this.connection.connect();
568
+ const req = {
569
+ type: "Connect",
570
+ accessToken: this.options.accessToken
571
+ };
572
+ await this.connection.send(req);
573
+ // This must be the last statement before catch so that awaiting `connected`
574
+ // has the same effect as awaiting `connect()`. `close()` also rejects this promise.
575
+ await this.connected.promise;
576
+ }
577
+ catch (error) {
578
+ if (error instanceof MessageRouterError) {
579
+ throw error;
580
+ }
581
+ else {
582
+ await this.closeCore(error);
583
+ throw ThrowHelper.connectionFailed(error.message || error);
584
+ }
585
+ }
586
+ }
587
+ async closeCore(error) {
588
+ error ??= ThrowHelper.connectionClosed();
589
+ switch (this._state) {
590
+ case ClientState.Created:
591
+ {
592
+ this._state = ClientState.Closed;
593
+ return;
594
+ }
595
+ case ClientState.Closed:
596
+ return;
597
+ case ClientState.Closing:
598
+ await this.closed.promise;
599
+ return;
600
+ case ClientState.Connecting:
601
+ {
602
+ this._state = ClientState.Closed;
603
+ this.connected.reject(ThrowHelper.connectionClosed());
604
+ return;
605
+ }
606
+ }
607
+ this._state = ClientState.Closing;
608
+ this.failPendingRequests(error);
609
+ this.failSubscribers(error);
610
+ try {
611
+ await this.connection.close();
612
+ }
613
+ catch (e) {
614
+ console.error(e);
615
+ }
616
+ this._state = ClientState.Closed;
617
+ this.closed.resolve();
618
+ }
619
+ failPendingRequests(error) {
620
+ for (let requestId in this.pendingRequests) {
621
+ this.pendingRequests[requestId].reject(error);
622
+ delete this.pendingRequests[requestId];
623
+ }
624
+ }
625
+ async failSubscribers(error) {
626
+ for (let topicName in this.topics) {
627
+ const topic = this.topics[topicName];
628
+ topic.error(error);
629
+ }
630
+ }
631
+ async sendMessage(message) {
632
+ await this.connect();
633
+ await this.connection.send(message);
634
+ }
635
+ async sendRequest(request) {
636
+ const deferred = this.pendingRequests[request.requestId] = new Deferred();
637
+ try {
638
+ await this.sendMessage(request);
639
+ }
640
+ catch (error) {
641
+ delete this.pendingRequests[request.requestId];
642
+ throw error;
643
+ }
644
+ return await deferred.promise;
645
+ }
646
+ handleMessage(message) {
647
+ if (isTopicMessage(message)) {
648
+ this.handleTopicMessage(message);
649
+ return;
650
+ }
651
+ if (isResponse(message)) {
652
+ this.handleResponse(message);
653
+ return;
654
+ }
655
+ if (isInvokeRequest(message)) {
656
+ this.handleInvokeRequest(message);
657
+ return;
658
+ }
659
+ if (isConnectResponse(message)) {
660
+ this.handleConnectResponse(message);
661
+ return;
662
+ }
663
+ }
664
+ handleTopicMessage(message) {
665
+ const topic = this.topics[message.topic];
666
+ if (!topic)
667
+ return;
668
+ topic.next({
669
+ topic: message.topic,
670
+ payload: message.payload,
671
+ context: {
672
+ sourceId: message.sourceId,
673
+ correlationId: message.correlationId
674
+ }
675
+ });
676
+ }
677
+ handleResponse(message) {
678
+ const request = this.pendingRequests[message.requestId];
679
+ if (!request)
680
+ return;
681
+ if (message.error) {
682
+ request.reject(new MessageRouterError(message.error));
683
+ }
684
+ else {
685
+ request.resolve(message);
686
+ }
687
+ }
688
+ async handleInvokeRequest(message) {
689
+ try {
690
+ const handler = this.endpointHandlers[message.endpoint];
691
+ if (!handler)
692
+ throw ThrowHelper.unknownEndpoint(message.endpoint);
693
+ const result = await handler(message.endpoint, message.payload, {
694
+ sourceId: message.sourceId,
695
+ correlationId: message.correlationId
696
+ });
697
+ await this.sendMessage({
698
+ type: "InvokeResponse",
699
+ requestId: message.requestId,
700
+ payload: typeof result === "string" ? result : undefined
701
+ });
702
+ }
703
+ catch (error) {
704
+ await this.sendMessage({
705
+ type: "InvokeResponse",
706
+ requestId: message.requestId,
707
+ error: createProtocolError(error)
708
+ });
709
+ }
710
+ }
711
+ handleConnectResponse(message) {
712
+ if (message.error) {
713
+ this._state = ClientState.Closed;
714
+ this.connected.reject(new MessageRouterError(message.error));
715
+ }
716
+ else {
717
+ this._clientId = message.clientId;
718
+ this._state = ClientState.Connected;
719
+ this.connected.resolve();
720
+ }
721
+ }
722
+ checkState() {
723
+ if (this._state == ClientState.Closed || this._state == ClientState.Closing) {
724
+ throw ThrowHelper.connectionClosed();
725
+ }
726
+ }
727
+ handleError(error) {
728
+ switch (this._state) {
729
+ case ClientState.Closing:
730
+ case ClientState.Closed:
731
+ return;
732
+ }
733
+ this.closeCore(error);
734
+ }
735
+ handleClose() {
736
+ this.handleError(ThrowHelper.connectionAborted());
737
+ }
738
+ async unsubscribe(topicName) {
739
+ let topic = this.topics[topicName];
740
+ if (!topic)
741
+ return;
742
+ if (this.pendingUnsubscriptions[topicName]) {
743
+ await this.pendingUnsubscriptions[topicName];
744
+ }
745
+ this.pendingUnsubscriptions[topicName] = this.sendRequest({
746
+ requestId: this.getRequestId(),
747
+ type: "Unsubscribe",
748
+ topic: topicName
749
+ })
750
+ .then(() => {
751
+ delete this.topics[topicName];
752
+ })
753
+ .catch(error => {
754
+ console.error("Exception thrown while unsubscribing.", error);
755
+ throw error;
756
+ })
757
+ .finally(() => {
758
+ delete this.pendingUnsubscriptions[topicName];
759
+ });
760
+ await this.pendingUnsubscriptions[topicName];
761
+ }
762
+ getRequestId() {
763
+ return '' + (++this.lastRequestId);
764
+ }
765
+ }
766
+ class Topic {
767
+ constructor(onUnsubscribe) {
768
+ this.onUnsubscribe = onUnsubscribe;
769
+ }
770
+ subscribe(subscriber) {
771
+ if (this.isCompleted)
772
+ return {
773
+ unsubscribe: () => { }
774
+ };
775
+ this.subscribers.push(subscriber);
776
+ return {
777
+ unsubscribe: () => this.unsubscribe(subscriber)
778
+ };
779
+ }
780
+ unsubscribe(subscriber) {
781
+ if (this.isCompleted)
782
+ return;
783
+ const idx = this.subscribers.lastIndexOf(subscriber);
784
+ if (idx < 0)
785
+ return;
786
+ this.subscribers.splice(idx, 1);
787
+ if (this.subscribers.length == 0) {
788
+ this.onUnsubscribe();
789
+ }
790
+ }
791
+ next(message) {
792
+ if (this.isCompleted) {
793
+ return;
794
+ }
795
+ for (let subscriber of this.subscribers) {
796
+ try {
797
+ subscriber.next?.call(subscriber, message);
798
+ }
799
+ catch (error) {
800
+ console.error(error);
801
+ }
802
+ }
803
+ }
804
+ error(error) {
805
+ if (this.isCompleted)
806
+ return;
807
+ this.isCompleted = true;
808
+ for (let subscriber of this.subscribers) {
809
+ try {
810
+ subscriber.error?.call(subscriber, error);
811
+ }
812
+ catch (e) {
813
+ console.error(e);
814
+ }
815
+ }
816
+ }
817
+ complete() {
818
+ if (this.isCompleted)
819
+ return;
820
+ for (let subscriber of this.subscribers) {
821
+ try {
822
+ subscriber.complete?.call(subscriber);
823
+ }
824
+ catch (e) {
825
+ console.error(e);
826
+ }
827
+ }
828
+ }
829
+ isCompleted = false;
830
+ onUnsubscribe;
831
+ subscribers = [];
832
+ }
833
+
834
+ /*
835
+ * Morgan Stanley makes this available to you under the Apache License,
836
+ * Version 2.0 (the "License"). You may obtain a copy of the License at
837
+ * http://www.apache.org/licenses/LICENSE-2.0.
838
+ * See the NOTICE file distributed with this work for additional information
839
+ * regarding copyright ownership. Unless required by applicable law or agreed
840
+ * to in writing, software distributed under the License is distributed on an
841
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
842
+ * or implied. See the License for the specific language governing permissions
843
+ * and limitations under the License.
844
+ *
845
+ */
846
+ function createMessageRouter(config) {
847
+ config ??= window.composeui?.messageRouterConfig;
848
+ if (config?.webSocket) {
849
+ const connection = new WebSocketConnection(config.webSocket);
850
+ return new MessageRouterClient(connection, config);
851
+ }
852
+ throw ConfigNotFound();
853
+ function ConfigNotFound() {
854
+ return new Error("Unable to create the MessageRouter client, configuration is missing.");
855
+ }
856
+ }
857
+
858
+ window.composeui.messaging.communicator = new MessageRouterMessaging(createMessageRouter());
859
+
860
+ })();