http-request-manager 18.10.0 → 18.10.2
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.
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, Injectable, APP_ID, Inject, InjectionToken, Injector, Optional, signal, computed, effect, EventEmitter, Input, Output, ViewEncapsulation, Component, NgModule, ViewChild } from '@angular/core';
|
|
2
|
+
import { inject, Injectable, APP_ID, Inject, InjectionToken, isDevMode, Injector, Optional, signal, computed, effect, EventEmitter, Input, Output, ViewEncapsulation, Component, NgModule, ViewChild } from '@angular/core';
|
|
3
3
|
import { ComponentStore } from '@ngrx/component-store';
|
|
4
4
|
import { map, catchError, filter, tap, finalize, takeWhile, retry, startWith, mergeMap, takeUntil, withLatestFrom, switchMap, delay, concatMap, take, scan, distinctUntilChanged } from 'rxjs/operators';
|
|
5
5
|
import { HttpClient, HttpHeaders, HttpEventType, HttpHeaderResponse, HttpErrorResponse, HTTP_INTERCEPTORS } from '@angular/common/http';
|
|
@@ -803,6 +803,75 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
803
803
|
}]
|
|
804
804
|
}], ctorParameters: () => [] });
|
|
805
805
|
|
|
806
|
+
/**
|
|
807
|
+
* LoggerService for http-request-manager library
|
|
808
|
+
*
|
|
809
|
+
* Automatically enables debug logging in development mode,
|
|
810
|
+
* disables it in production mode.
|
|
811
|
+
*
|
|
812
|
+
* Usage:
|
|
813
|
+
* ```typescript
|
|
814
|
+
* constructor(private logger: LoggerService) {}
|
|
815
|
+
*
|
|
816
|
+
* this.logger.debug('WebSocket', 'Connected successfully');
|
|
817
|
+
* this.logger.info('HTTP', 'Request completed', { status: 200 });
|
|
818
|
+
* this.logger.warn('State', 'Cache miss for key', { key: 'user' });
|
|
819
|
+
* this.logger.error('Database', 'Connection failed', error);
|
|
820
|
+
* ```
|
|
821
|
+
*/
|
|
822
|
+
class LoggerService {
|
|
823
|
+
constructor() {
|
|
824
|
+
// Automatically detect development vs production mode
|
|
825
|
+
this.debugMode = isDevMode();
|
|
826
|
+
}
|
|
827
|
+
/**
|
|
828
|
+
* Debug level logging - only shown in development mode
|
|
829
|
+
* Use for: Detailed diagnostic information, state changes, connection events
|
|
830
|
+
*/
|
|
831
|
+
debug(context, message, data) {
|
|
832
|
+
if (this.debugMode) {
|
|
833
|
+
console.log(`[HTTP-MGR DEBUG] ${context}: ${message}`, data ?? '');
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Info level logging - only shown in development mode
|
|
838
|
+
* Use for: Important operational messages, successful operations
|
|
839
|
+
*/
|
|
840
|
+
info(context, message, data) {
|
|
841
|
+
if (this.debugMode) {
|
|
842
|
+
console.info(`[HTTP-MGR INFO] ${context}: ${message}`, data ?? '');
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Warning level logging - always shown
|
|
847
|
+
* Use for: Potential issues, deprecated usage, recoverable errors
|
|
848
|
+
*/
|
|
849
|
+
warn(context, message, data) {
|
|
850
|
+
console.warn(`[HTTP-MGR WARN] ${context}: ${message}`, data ?? '');
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Error level logging - always shown
|
|
854
|
+
* Use for: Actual errors, failures, exceptions
|
|
855
|
+
*/
|
|
856
|
+
error(context, message, data) {
|
|
857
|
+
console.error(`[HTTP-MGR ERROR] ${context}: ${message}`, data ?? '');
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Check if debug mode is enabled (development mode)
|
|
861
|
+
*/
|
|
862
|
+
isDebugEnabled() {
|
|
863
|
+
return this.debugMode;
|
|
864
|
+
}
|
|
865
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LoggerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
866
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LoggerService, providedIn: 'root' }); }
|
|
867
|
+
}
|
|
868
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LoggerService, decorators: [{
|
|
869
|
+
type: Injectable,
|
|
870
|
+
args: [{
|
|
871
|
+
providedIn: 'root'
|
|
872
|
+
}]
|
|
873
|
+
}], ctorParameters: () => [] });
|
|
874
|
+
|
|
806
875
|
/**
|
|
807
876
|
* Stateful processor for managing streaming data and events
|
|
808
877
|
*/
|
|
@@ -1202,8 +1271,48 @@ var CommunicationType;
|
|
|
1202
1271
|
CommunicationType[CommunicationType["OTHER"] = 2] = "OTHER";
|
|
1203
1272
|
})(CommunicationType || (CommunicationType = {}));
|
|
1204
1273
|
|
|
1274
|
+
var ChannelType$1;
|
|
1275
|
+
(function (ChannelType) {
|
|
1276
|
+
ChannelType["STATE"] = "SYS";
|
|
1277
|
+
ChannelType["PUBLIC"] = "PUB";
|
|
1278
|
+
ChannelType["NOTIFICATION"] = "MES";
|
|
1279
|
+
ChannelType["CUSTOM"] = ""; // No prefix (raw channel name)
|
|
1280
|
+
})(ChannelType$1 || (ChannelType$1 = {}));
|
|
1281
|
+
|
|
1282
|
+
class StateMessage {
|
|
1283
|
+
constructor(sessionId = { id: '' }, content = {}) {
|
|
1284
|
+
this.sessionId = sessionId;
|
|
1285
|
+
this.content = content;
|
|
1286
|
+
}
|
|
1287
|
+
static adapt(item) {
|
|
1288
|
+
return new StateMessage(item?.sessionId, item?.content);
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
class PublicMessage {
|
|
1293
|
+
constructor(sessionId = { id: '' }, content = { message: '' }) {
|
|
1294
|
+
this.sessionId = sessionId;
|
|
1295
|
+
this.content = content;
|
|
1296
|
+
}
|
|
1297
|
+
static adapt(item) {
|
|
1298
|
+
return new PublicMessage(item?.sessionId, item?.content);
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
class NotificationMessage {
|
|
1303
|
+
constructor(sessionId = { id: '' }, message = '', additionalProperties = {}) {
|
|
1304
|
+
this.sessionId = sessionId;
|
|
1305
|
+
this.message = message;
|
|
1306
|
+
Object.assign(this, additionalProperties);
|
|
1307
|
+
}
|
|
1308
|
+
static adapt(item) {
|
|
1309
|
+
return new NotificationMessage(item?.sessionId, item?.message, item);
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1205
1313
|
class WebsocketService {
|
|
1206
1314
|
constructor() {
|
|
1315
|
+
this.logger = new LoggerService();
|
|
1207
1316
|
this.socket = null;
|
|
1208
1317
|
this.messages = new BehaviorSubject(null);
|
|
1209
1318
|
this.messages$ = this.messages.asObservable();
|
|
@@ -1241,24 +1350,28 @@ class WebsocketService {
|
|
|
1241
1350
|
current.add(channelName);
|
|
1242
1351
|
this.subscribedChannels.next(current);
|
|
1243
1352
|
this.isSubscribed = true; // Keep for backward compatibility
|
|
1244
|
-
|
|
1353
|
+
this.logger.debug('WebSocket', `Sent initial subscription to: ${channelName}`);
|
|
1245
1354
|
}
|
|
1246
1355
|
else {
|
|
1247
|
-
|
|
1356
|
+
this.logger.warn('WebSocket', `Subscription prevented`, {
|
|
1357
|
+
open: this.socket?.readyState === WebSocket.OPEN,
|
|
1358
|
+
channel: channelName,
|
|
1359
|
+
alreadySubscribed
|
|
1360
|
+
});
|
|
1248
1361
|
}
|
|
1249
1362
|
}
|
|
1250
1363
|
connect(options, jwtToken) {
|
|
1251
1364
|
if (this.socket) {
|
|
1252
1365
|
if (this.socket.readyState === WebSocket.OPEN) {
|
|
1253
|
-
|
|
1366
|
+
this.logger.debug('WebSocket', 'Connection is already OPEN');
|
|
1254
1367
|
return;
|
|
1255
1368
|
}
|
|
1256
1369
|
if (this.socket.readyState === WebSocket.CONNECTING) {
|
|
1257
|
-
|
|
1370
|
+
this.logger.debug('WebSocket', 'Already CONNECTING. Waiting for handshake');
|
|
1258
1371
|
return;
|
|
1259
1372
|
}
|
|
1260
1373
|
if (this.socket.readyState === WebSocket.CLOSING || this.socket.readyState === WebSocket.CLOSED) {
|
|
1261
|
-
|
|
1374
|
+
this.logger.debug('WebSocket', `Cleaning up stale socket`, { state: this.socket.readyState });
|
|
1262
1375
|
this.socket.close();
|
|
1263
1376
|
this.socket = null;
|
|
1264
1377
|
}
|
|
@@ -1270,7 +1383,7 @@ class WebsocketService {
|
|
|
1270
1383
|
const URL = (jwtToken) ? `${options.wsServer}?token=${jwtToken}&sessionId=${sessionId}` : `${options.wsServer}?sessionId=${sessionId}`;
|
|
1271
1384
|
this.socket = new WebSocket(URL);
|
|
1272
1385
|
this.socket.onopen = () => {
|
|
1273
|
-
|
|
1386
|
+
this.logger.info('WebSocket', '📡 Connected to WebSocket');
|
|
1274
1387
|
this.connectionStatus.next(true);
|
|
1275
1388
|
this.lastOptions = options;
|
|
1276
1389
|
// Subscribe to primary channel
|
|
@@ -1308,7 +1421,7 @@ class WebsocketService {
|
|
|
1308
1421
|
try {
|
|
1309
1422
|
const data = JSON.parse(event.data);
|
|
1310
1423
|
if (data.error && data.error === 'JWT_INVALID') {
|
|
1311
|
-
|
|
1424
|
+
this.logger.error('WebSocket', 'JWT validation failed. Authentication error!');
|
|
1312
1425
|
this.messages.next(data);
|
|
1313
1426
|
this.connectionStatus.next(false);
|
|
1314
1427
|
this.socket?.close();
|
|
@@ -1317,16 +1430,16 @@ class WebsocketService {
|
|
|
1317
1430
|
this.messages.next(data);
|
|
1318
1431
|
}
|
|
1319
1432
|
catch (error) {
|
|
1320
|
-
|
|
1433
|
+
this.logger.error('WebSocket', 'Error parsing WebSocket message', { data: event.data });
|
|
1321
1434
|
}
|
|
1322
1435
|
};
|
|
1323
1436
|
this.socket.onclose = () => {
|
|
1324
|
-
|
|
1437
|
+
this.logger.debug('WebSocket', 'Connection closed');
|
|
1325
1438
|
this.connectionStatus.next(false);
|
|
1326
1439
|
this.socket = null;
|
|
1327
1440
|
};
|
|
1328
1441
|
this.socket.onerror = (error) => {
|
|
1329
|
-
|
|
1442
|
+
this.logger.error('WebSocket', 'WebSocket error', error);
|
|
1330
1443
|
this.connectionStatus.next(false);
|
|
1331
1444
|
};
|
|
1332
1445
|
}
|
|
@@ -1349,14 +1462,14 @@ class WebsocketService {
|
|
|
1349
1462
|
const current = new Set(this.subscribedChannels.value);
|
|
1350
1463
|
current.add(channelName);
|
|
1351
1464
|
this.subscribedChannels.next(current);
|
|
1352
|
-
|
|
1465
|
+
this.logger.debug('WebSocket', `📝 Subscribed to channel`, { channel: channelName });
|
|
1353
1466
|
}
|
|
1354
1467
|
catch (error) {
|
|
1355
|
-
|
|
1468
|
+
this.logger.error('WebSocket', `Failed to subscribe to channel`, { channel: channelName, error });
|
|
1356
1469
|
}
|
|
1357
1470
|
}
|
|
1358
1471
|
else {
|
|
1359
|
-
|
|
1472
|
+
this.logger.warn('WebSocket', `Cannot subscribe to channel - WebSocket not open`, { channel: channelName });
|
|
1360
1473
|
}
|
|
1361
1474
|
}
|
|
1362
1475
|
subscribeToChannels(channelNames) {
|
|
@@ -1364,7 +1477,7 @@ class WebsocketService {
|
|
|
1364
1477
|
channelNames.forEach(channel => this.subscribeToChannel(channel));
|
|
1365
1478
|
}
|
|
1366
1479
|
else {
|
|
1367
|
-
|
|
1480
|
+
this.logger.warn('WebSocket', 'Cannot subscribe to channels - WebSocket not open');
|
|
1368
1481
|
}
|
|
1369
1482
|
}
|
|
1370
1483
|
unsubscribeFromChannel(channel) {
|
|
@@ -1374,10 +1487,10 @@ class WebsocketService {
|
|
|
1374
1487
|
const current = new Set(this.subscribedChannels.value);
|
|
1375
1488
|
current.delete(channel);
|
|
1376
1489
|
this.subscribedChannels.next(current);
|
|
1377
|
-
|
|
1490
|
+
this.logger.debug('WebSocket', `💬 Unsubscribed from channel`, { channel });
|
|
1378
1491
|
}
|
|
1379
1492
|
else {
|
|
1380
|
-
|
|
1493
|
+
this.logger.error('WebSocket', 'Cannot unsubscribe - WebSocket not open');
|
|
1381
1494
|
}
|
|
1382
1495
|
}
|
|
1383
1496
|
unsubscribeToChannel(channel) {
|
|
@@ -1390,19 +1503,19 @@ class WebsocketService {
|
|
|
1390
1503
|
sendBroadcast(content) {
|
|
1391
1504
|
if (this.socket?.readyState === WebSocket.OPEN) {
|
|
1392
1505
|
this.socket.send(JSON.stringify({ type: 'broadcast', content }));
|
|
1393
|
-
|
|
1506
|
+
this.logger.debug('WebSocket', `📢 Send broadcast`, { content });
|
|
1394
1507
|
}
|
|
1395
1508
|
else {
|
|
1396
|
-
|
|
1509
|
+
this.logger.error('WebSocket', 'Cannot send broadcast - WebSocket not open');
|
|
1397
1510
|
}
|
|
1398
1511
|
}
|
|
1399
1512
|
sendMessageInChannel(channel, content) {
|
|
1400
1513
|
if (this.socket?.readyState === WebSocket.OPEN) {
|
|
1401
1514
|
this.socket.send(JSON.stringify({ type: 'stateMangerMessage', subscribedChannel: channel, content }));
|
|
1402
|
-
|
|
1515
|
+
this.logger.debug('WebSocket', `💬 Send message`, { channel, content });
|
|
1403
1516
|
}
|
|
1404
1517
|
else {
|
|
1405
|
-
|
|
1518
|
+
this.logger.error('WebSocket', 'Cannot send message - WebSocket not open');
|
|
1406
1519
|
}
|
|
1407
1520
|
}
|
|
1408
1521
|
/**
|
|
@@ -1412,10 +1525,10 @@ class WebsocketService {
|
|
|
1412
1525
|
sendChannelMessage(channel, content) {
|
|
1413
1526
|
if (this.socket?.readyState === WebSocket.OPEN) {
|
|
1414
1527
|
this.socket.send(JSON.stringify({ type: 'message', subscribedChannel: channel, content }));
|
|
1415
|
-
|
|
1528
|
+
this.logger.debug('WebSocket', `💬 Send channel message`, { channel, content });
|
|
1416
1529
|
}
|
|
1417
1530
|
else {
|
|
1418
|
-
|
|
1531
|
+
this.logger.error('WebSocket', 'Cannot send channel message - WebSocket not open');
|
|
1419
1532
|
}
|
|
1420
1533
|
}
|
|
1421
1534
|
/**
|
|
@@ -1431,10 +1544,10 @@ class WebsocketService {
|
|
|
1431
1544
|
channels: channels,
|
|
1432
1545
|
data: content
|
|
1433
1546
|
}));
|
|
1434
|
-
|
|
1547
|
+
this.logger.debug('WebSocket', `💬 Send channel message to channels`, { channels, content });
|
|
1435
1548
|
}
|
|
1436
1549
|
catch (error) {
|
|
1437
|
-
|
|
1550
|
+
this.logger.error('WebSocket', 'Failed to send channelMessage batch', error);
|
|
1438
1551
|
}
|
|
1439
1552
|
// Legacy fallback - send individual messages to each channel
|
|
1440
1553
|
try {
|
|
@@ -1443,56 +1556,56 @@ class WebsocketService {
|
|
|
1443
1556
|
});
|
|
1444
1557
|
}
|
|
1445
1558
|
catch (err) {
|
|
1446
|
-
|
|
1559
|
+
this.logger.warn('WebSocket', 'Legacy fallback failed sending individual messages', err);
|
|
1447
1560
|
}
|
|
1448
1561
|
}
|
|
1449
1562
|
else {
|
|
1450
|
-
|
|
1563
|
+
this.logger.error('WebSocket', 'Cannot send message to channels - WebSocket not open');
|
|
1451
1564
|
}
|
|
1452
1565
|
}
|
|
1453
1566
|
sendMessageToUser(user, content) {
|
|
1454
1567
|
if (this.socket?.readyState === WebSocket.OPEN) {
|
|
1455
1568
|
this.socket.send(JSON.stringify({ type: 'userMessage', subscribedChannel: user, content }));
|
|
1456
|
-
|
|
1569
|
+
this.logger.debug('WebSocket', `💬 Send message to user`, { user, content });
|
|
1457
1570
|
}
|
|
1458
1571
|
else {
|
|
1459
|
-
|
|
1572
|
+
this.logger.error('WebSocket', 'Cannot send message to user - WebSocket not open');
|
|
1460
1573
|
}
|
|
1461
1574
|
}
|
|
1462
1575
|
getAllChannels() {
|
|
1463
1576
|
if (this.socket?.readyState === WebSocket.OPEN) {
|
|
1464
1577
|
this.socket.send(JSON.stringify({ type: 'getChannels' }));
|
|
1465
|
-
|
|
1578
|
+
this.logger.debug('WebSocket', '🗂️ Requested list of all channels');
|
|
1466
1579
|
}
|
|
1467
1580
|
else {
|
|
1468
|
-
|
|
1581
|
+
this.logger.error('WebSocket', 'Cannot request channels - WebSocket not open');
|
|
1469
1582
|
}
|
|
1470
1583
|
}
|
|
1471
1584
|
createChannel(channel) {
|
|
1472
1585
|
if (this.socket?.readyState === WebSocket.OPEN) {
|
|
1473
1586
|
this.socket.send(JSON.stringify({ type: 'createChannel', content: channel }));
|
|
1474
|
-
|
|
1587
|
+
this.logger.debug('WebSocket', '🗂️ Created channel', { channel });
|
|
1475
1588
|
}
|
|
1476
1589
|
else {
|
|
1477
|
-
|
|
1590
|
+
this.logger.error('WebSocket', 'Cannot create channel - WebSocket not open');
|
|
1478
1591
|
}
|
|
1479
1592
|
}
|
|
1480
1593
|
deleteChannel(channel) {
|
|
1481
1594
|
if (this.socket?.readyState === WebSocket.OPEN) {
|
|
1482
1595
|
this.socket.send(JSON.stringify({ type: 'deleteChannel', content: channel }));
|
|
1483
|
-
|
|
1596
|
+
this.logger.debug('WebSocket', '🗂️ Delete channel', { channel });
|
|
1484
1597
|
}
|
|
1485
1598
|
else {
|
|
1486
|
-
|
|
1599
|
+
this.logger.error('WebSocket', 'Cannot delete channel - WebSocket not open');
|
|
1487
1600
|
}
|
|
1488
1601
|
}
|
|
1489
1602
|
getUsersInChannel(channel) {
|
|
1490
1603
|
if (this.socket?.readyState === WebSocket.OPEN) {
|
|
1491
1604
|
this.socket.send(JSON.stringify({ type: 'getUsers', subscribedChannel: channel }));
|
|
1492
|
-
|
|
1605
|
+
this.logger.debug('WebSocket', `👥 Requested users in channel`, { channel });
|
|
1493
1606
|
}
|
|
1494
1607
|
else {
|
|
1495
|
-
|
|
1608
|
+
this.logger.error('WebSocket', 'Cannot request users - WebSocket not open');
|
|
1496
1609
|
}
|
|
1497
1610
|
}
|
|
1498
1611
|
// =====================
|
|
@@ -1516,28 +1629,21 @@ class WebsocketService {
|
|
|
1516
1629
|
getNotificationChannels() {
|
|
1517
1630
|
if (this.socket?.readyState === WebSocket.OPEN) {
|
|
1518
1631
|
this.socket.send(JSON.stringify({ type: 'getNotificationChannels' }));
|
|
1519
|
-
|
|
1632
|
+
this.logger.debug('WebSocket', '📢 Requested notification channels list');
|
|
1520
1633
|
}
|
|
1521
1634
|
else {
|
|
1522
|
-
|
|
1635
|
+
this.logger.error('WebSocket', 'Cannot request notification channels - WebSocket not open');
|
|
1523
1636
|
}
|
|
1524
1637
|
}
|
|
1525
|
-
/**
|
|
1526
|
-
* Get today's notification channels from database
|
|
1527
|
-
* Returns unique channels that have notifications posted today
|
|
1528
|
-
*/
|
|
1529
1638
|
getTodaysNotificationChannels() {
|
|
1530
1639
|
if (this.socket?.readyState === WebSocket.OPEN) {
|
|
1531
1640
|
this.socket.send(JSON.stringify({ type: 'getTodaysNotificationChannels' }));
|
|
1532
|
-
|
|
1641
|
+
this.logger.debug('WebSocket', "📢 Requested today's notification channels from DB");
|
|
1533
1642
|
}
|
|
1534
1643
|
else {
|
|
1535
|
-
|
|
1644
|
+
this.logger.error('WebSocket', "Cannot request today's notification channels - WebSocket not open");
|
|
1536
1645
|
}
|
|
1537
1646
|
}
|
|
1538
|
-
/**
|
|
1539
|
-
* Subscribe to a notification channel with optional date filters
|
|
1540
|
-
*/
|
|
1541
1647
|
subscribeToNotificationChannel(channel, options, user) {
|
|
1542
1648
|
if (this.socket?.readyState === WebSocket.OPEN) {
|
|
1543
1649
|
this.socket.send(JSON.stringify({
|
|
@@ -1548,30 +1654,24 @@ class WebsocketService {
|
|
|
1548
1654
|
...user
|
|
1549
1655
|
}
|
|
1550
1656
|
}));
|
|
1551
|
-
|
|
1657
|
+
this.logger.debug('WebSocket', `📢 Subscribed to notification channel`, { channel, options });
|
|
1552
1658
|
}
|
|
1553
1659
|
else {
|
|
1554
|
-
|
|
1660
|
+
this.logger.error('WebSocket', 'Cannot subscribe to notification channel - WebSocket not open');
|
|
1555
1661
|
}
|
|
1556
1662
|
}
|
|
1557
|
-
/**
|
|
1558
|
-
* Unsubscribe from a notification channel
|
|
1559
|
-
*/
|
|
1560
1663
|
unsubscribeFromNotificationChannel(channel) {
|
|
1561
1664
|
if (this.socket?.readyState === WebSocket.OPEN) {
|
|
1562
1665
|
this.socket.send(JSON.stringify({
|
|
1563
1666
|
type: 'unsubscribeNotifications',
|
|
1564
1667
|
subscribedChannel: channel
|
|
1565
1668
|
}));
|
|
1566
|
-
|
|
1669
|
+
this.logger.debug('WebSocket', `📢 Unsubscribed from notification channel`, { channel });
|
|
1567
1670
|
}
|
|
1568
1671
|
else {
|
|
1569
|
-
|
|
1672
|
+
this.logger.error('WebSocket', 'Cannot unsubscribe from notification channel - WebSocket not open');
|
|
1570
1673
|
}
|
|
1571
1674
|
}
|
|
1572
|
-
/**
|
|
1573
|
-
* Send a notification to a channel
|
|
1574
|
-
*/
|
|
1575
1675
|
sendNotification(channel, content) {
|
|
1576
1676
|
if (this.socket?.readyState === WebSocket.OPEN) {
|
|
1577
1677
|
this.socket.send(JSON.stringify({
|
|
@@ -1579,10 +1679,10 @@ class WebsocketService {
|
|
|
1579
1679
|
subscribedChannel: channel,
|
|
1580
1680
|
content
|
|
1581
1681
|
}));
|
|
1582
|
-
|
|
1682
|
+
this.logger.debug('WebSocket', `📢 Sent notification to channel`, { channel, content });
|
|
1583
1683
|
}
|
|
1584
1684
|
else {
|
|
1585
|
-
|
|
1685
|
+
this.logger.error('WebSocket', 'Cannot send notification - WebSocket not open');
|
|
1586
1686
|
}
|
|
1587
1687
|
}
|
|
1588
1688
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WebsocketService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
@@ -2193,6 +2293,124 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
2193
2293
|
}]
|
|
2194
2294
|
}] });
|
|
2195
2295
|
|
|
2296
|
+
/**
|
|
2297
|
+
* WebSocketMessageService - Unified service for sending WebSocket messages
|
|
2298
|
+
*
|
|
2299
|
+
* Provides type-safe methods for sending messages with automatic prefix management.
|
|
2300
|
+
* Supports three message types: State (SYS-), Public (PUB-), and Notification (MES-).
|
|
2301
|
+
*
|
|
2302
|
+
* Features:
|
|
2303
|
+
* - Type-safe message models
|
|
2304
|
+
* - Automatic prefix management
|
|
2305
|
+
* - Runtime validation
|
|
2306
|
+
* - Boolean return values for success/failure
|
|
2307
|
+
* - Silent failure with console errors
|
|
2308
|
+
*/
|
|
2309
|
+
class WebSocketMessageService {
|
|
2310
|
+
constructor() {
|
|
2311
|
+
this.wsManagerService = inject(WebSocketManagerService);
|
|
2312
|
+
this.logger = new LoggerService();
|
|
2313
|
+
}
|
|
2314
|
+
/**
|
|
2315
|
+
* Send state message (SYS- prefix)
|
|
2316
|
+
* Used for CRUD operations and state synchronization
|
|
2317
|
+
*
|
|
2318
|
+
* @param path - Path array (e.g., ['ai', 'tests'])
|
|
2319
|
+
* @param payload - StateMessage with sessionId and content
|
|
2320
|
+
* @returns true if sent successfully, false otherwise
|
|
2321
|
+
*/
|
|
2322
|
+
sendStateMessage(path, payload) {
|
|
2323
|
+
// Validate payload
|
|
2324
|
+
if (!payload?.sessionId?.id) {
|
|
2325
|
+
this.logger.error('WebSocketMessageService', 'StateMessage must have sessionId.id');
|
|
2326
|
+
return false;
|
|
2327
|
+
}
|
|
2328
|
+
if (!WebSocketManagerService.isConnected()) {
|
|
2329
|
+
this.logger.error('WebSocketMessageService', 'Cannot send state message - not connected');
|
|
2330
|
+
return false;
|
|
2331
|
+
}
|
|
2332
|
+
const channel = `${ChannelType$1.STATE}-${path.join('/')}`;
|
|
2333
|
+
this.wsManagerService.sendMessageInChannel(channel, payload);
|
|
2334
|
+
return true;
|
|
2335
|
+
}
|
|
2336
|
+
/**
|
|
2337
|
+
* Send public message (PUB- prefix)
|
|
2338
|
+
* Used for chat, broadcast, general messaging
|
|
2339
|
+
*
|
|
2340
|
+
* @param channel - Channel name (without prefix)
|
|
2341
|
+
* @param payload - PublicMessage with sessionId and content.message
|
|
2342
|
+
* @returns true if sent successfully, false otherwise
|
|
2343
|
+
*/
|
|
2344
|
+
sendPublicMessage(channel, payload) {
|
|
2345
|
+
// Validate payload
|
|
2346
|
+
if (!payload?.sessionId?.id) {
|
|
2347
|
+
this.logger.error('WebSocketMessageService', 'PublicMessage must have sessionId.id');
|
|
2348
|
+
return false;
|
|
2349
|
+
}
|
|
2350
|
+
if (!payload?.content?.message) {
|
|
2351
|
+
this.logger.error('WebSocketMessageService', 'PublicMessage must have content.message');
|
|
2352
|
+
return false;
|
|
2353
|
+
}
|
|
2354
|
+
if (!WebSocketManagerService.isConnected()) {
|
|
2355
|
+
this.logger.error('WebSocketMessageService', 'Cannot send public message - not connected');
|
|
2356
|
+
return false;
|
|
2357
|
+
}
|
|
2358
|
+
const fullChannel = `${ChannelType$1.PUBLIC}-${channel}`;
|
|
2359
|
+
this.wsManagerService.sendMessageInChannel(fullChannel, payload);
|
|
2360
|
+
return true;
|
|
2361
|
+
}
|
|
2362
|
+
/**
|
|
2363
|
+
* Send notification (MES- prefix)
|
|
2364
|
+
* Used for persistent notifications with history
|
|
2365
|
+
*
|
|
2366
|
+
* @param channel - Channel name (without prefix)
|
|
2367
|
+
* @param payload - NotificationMessage with sessionId and message
|
|
2368
|
+
* @returns true if sent successfully, false otherwise
|
|
2369
|
+
*/
|
|
2370
|
+
sendNotification(channel, payload) {
|
|
2371
|
+
// Validate payload
|
|
2372
|
+
if (!payload?.sessionId?.id) {
|
|
2373
|
+
this.logger.error('WebSocketMessageService', 'NotificationMessage must have sessionId.id');
|
|
2374
|
+
return false;
|
|
2375
|
+
}
|
|
2376
|
+
if (!payload?.message) {
|
|
2377
|
+
this.logger.error('WebSocketMessageService', 'NotificationMessage must have message');
|
|
2378
|
+
return false;
|
|
2379
|
+
}
|
|
2380
|
+
if (!WebSocketManagerService.isConnected()) {
|
|
2381
|
+
this.logger.error('WebSocketMessageService', 'Cannot send notification - not connected');
|
|
2382
|
+
return false;
|
|
2383
|
+
}
|
|
2384
|
+
const fullChannel = `${ChannelType$1.NOTIFICATION}-${channel}`;
|
|
2385
|
+
this.wsManagerService.sendMessageInChannel(fullChannel, payload);
|
|
2386
|
+
return true;
|
|
2387
|
+
}
|
|
2388
|
+
/**
|
|
2389
|
+
* Send to custom channel (no prefix)
|
|
2390
|
+
* User provides full channel name
|
|
2391
|
+
*
|
|
2392
|
+
* @param channel - Full channel name
|
|
2393
|
+
* @param payload - Any message payload
|
|
2394
|
+
* @returns true if sent successfully, false otherwise
|
|
2395
|
+
*/
|
|
2396
|
+
sendToCustomChannel(channel, payload) {
|
|
2397
|
+
if (!WebSocketManagerService.isConnected()) {
|
|
2398
|
+
this.logger.error('WebSocketMessageService', 'Cannot send custom message - not connected');
|
|
2399
|
+
return false;
|
|
2400
|
+
}
|
|
2401
|
+
this.wsManagerService.sendMessageInChannel(channel, payload);
|
|
2402
|
+
return true;
|
|
2403
|
+
}
|
|
2404
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WebSocketMessageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2405
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WebSocketMessageService, providedIn: 'root' }); }
|
|
2406
|
+
}
|
|
2407
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WebSocketMessageService, decorators: [{
|
|
2408
|
+
type: Injectable,
|
|
2409
|
+
args: [{ providedIn: 'root' }]
|
|
2410
|
+
}] });
|
|
2411
|
+
|
|
2412
|
+
// ChannelType is already exported from request-manager-state-service to avoid conflicts
|
|
2413
|
+
|
|
2196
2414
|
class RequestService extends WebsocketService {
|
|
2197
2415
|
constructor() {
|
|
2198
2416
|
super(...arguments);
|
|
@@ -4844,6 +5062,7 @@ class HTTPManagerStateService extends ComponentStore {
|
|
|
4844
5062
|
this.dbManagerService = inject(DatabaseManagerService);
|
|
4845
5063
|
this.localStorageManagerService = inject(LocalStorageManagerService);
|
|
4846
5064
|
this.utils = inject(UtilsService);
|
|
5065
|
+
this.logger = inject(LoggerService);
|
|
4847
5066
|
this.error$ = this.httpManagerService.error$;
|
|
4848
5067
|
this.isPending$ = this.httpManagerService.isPending$.pipe(delay(1));
|
|
4849
5068
|
// PAGINATION
|
|
@@ -4926,7 +5145,7 @@ class HTTPManagerStateService extends ComponentStore {
|
|
|
4926
5145
|
this.messages.next([...currentMessages, message]);
|
|
4927
5146
|
console.log('Received:', message);
|
|
4928
5147
|
// Debug: Log all message types
|
|
4929
|
-
|
|
5148
|
+
this.logger.debug('StateStore', '📨 Message type', { type: message.type });
|
|
4930
5149
|
if (message.error === 'JWT_INVALID') {
|
|
4931
5150
|
this.shouldRetry = false;
|
|
4932
5151
|
this.httpManagerService.disconnect();
|
|
@@ -4939,9 +5158,9 @@ class HTTPManagerStateService extends ComponentStore {
|
|
|
4939
5158
|
}
|
|
4940
5159
|
switch (message.type) {
|
|
4941
5160
|
case 'channelsList':
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
5161
|
+
this.logger.debug('StateStore', '💬 Channels', { channels: message.channels });
|
|
5162
|
+
this.logger.debug('StateStore', '🔍 channelsList received, checking connection status...');
|
|
5163
|
+
this.logger.debug('StateStore', '🔍 WebSocket connected', { connected: WebSocketManagerService.isConnected() });
|
|
4945
5164
|
// Extract channel names from metadata objects (new format) or use strings directly (old format)
|
|
4946
5165
|
const channelNames = message.channels.map((c) => {
|
|
4947
5166
|
// Handle new format: {name: string, canSubscribe: boolean}
|
|
@@ -4953,35 +5172,35 @@ class HTTPManagerStateService extends ComponentStore {
|
|
|
4953
5172
|
});
|
|
4954
5173
|
// Auto-subscribe to all channels from the list
|
|
4955
5174
|
if (channelNames && channelNames.length > 0) {
|
|
4956
|
-
|
|
5175
|
+
this.logger.debug('StateStore', '📥 Auto-subscribing to channels', { count: channelNames.length });
|
|
4957
5176
|
this.subscribeToChannels(channelNames);
|
|
4958
5177
|
}
|
|
4959
5178
|
else {
|
|
4960
|
-
|
|
5179
|
+
this.logger.warn('StateStore', '⚠️ No channels to subscribe to');
|
|
4961
5180
|
}
|
|
4962
5181
|
this.channels.next(channelNames);
|
|
4963
5182
|
break;
|
|
4964
5183
|
case 'success':
|
|
4965
5184
|
// Success messages - check for subscription confirmation
|
|
4966
|
-
|
|
5185
|
+
this.logger.info('StateStore', `✅ Success`, { message: message.message });
|
|
4967
5186
|
if (message.message?.includes('Subscribed to channel:')) {
|
|
4968
5187
|
// Extract channel name from message: "Subscribed to channel: PUB-chat"
|
|
4969
5188
|
const channelName = message.message.split('Subscribed to channel:')[1]?.trim();
|
|
4970
|
-
|
|
5189
|
+
this.logger.debug('StateStore', `✅ Subscription confirmed for channel`, { channel: channelName });
|
|
4971
5190
|
WebSocketManagerService.addSubscribedChannel(channelName);
|
|
4972
5191
|
}
|
|
4973
5192
|
break;
|
|
4974
5193
|
case 'subscribed':
|
|
4975
|
-
|
|
5194
|
+
this.logger.debug('StateStore', `✅ Subscription confirmed`, { channel: message.channel });
|
|
4976
5195
|
// Track as subscribed now that server confirmed
|
|
4977
5196
|
WebSocketManagerService.addSubscribedChannel(message.channel);
|
|
4978
5197
|
break;
|
|
4979
5198
|
case 'unsubscribed':
|
|
4980
|
-
|
|
5199
|
+
this.logger.debug('StateStore', `🔓 Unsubscription confirmed`, { channel: message.channel });
|
|
4981
5200
|
break;
|
|
4982
5201
|
case 'info':
|
|
4983
5202
|
// Already subscribed or other info messages
|
|
4984
|
-
|
|
5203
|
+
this.logger.info('StateStore', `ℹ️ Info`, { message: message.message });
|
|
4985
5204
|
// If it's an "Already subscribed" message, treat it as subscription confirmation
|
|
4986
5205
|
if (message.message?.includes('Already subscribed to channel:')) {
|
|
4987
5206
|
// Extract channel name from message: "Already subscribed to channel: PUB-chat"
|
|
@@ -5470,7 +5689,7 @@ class HTTPManagerStateService extends ComponentStore {
|
|
|
5470
5689
|
}
|
|
5471
5690
|
}
|
|
5472
5691
|
catch (error) {
|
|
5473
|
-
|
|
5692
|
+
this.logger.error('HTTPManagerStateService', 'Error initializing', error);
|
|
5474
5693
|
// Initialize with safe defaults
|
|
5475
5694
|
this.databaseOptions = undefined;
|
|
5476
5695
|
this.maxRetries = 3;
|
|
@@ -5510,7 +5729,7 @@ class HTTPManagerStateService extends ComponentStore {
|
|
|
5510
5729
|
return this.stripChannelPrefix(channel);
|
|
5511
5730
|
}
|
|
5512
5731
|
setApiRequestOptions(apiOptions, dataType, database) {
|
|
5513
|
-
|
|
5732
|
+
this.logger.debug('StateStore', '🔧 setApiRequestOptions called', {
|
|
5514
5733
|
hasWs: !!apiOptions?.ws,
|
|
5515
5734
|
wsId: apiOptions?.ws?.id,
|
|
5516
5735
|
wsServer: apiOptions?.ws?.wsServer,
|
|
@@ -5527,21 +5746,21 @@ class HTTPManagerStateService extends ComponentStore {
|
|
|
5527
5746
|
// Auto-prefix channel ID for private state manager channels
|
|
5528
5747
|
// This ensures state manager channels are separate from user-defined channels
|
|
5529
5748
|
this.apiOptions.ws.id = this.prefixChannel(this.apiOptions.ws.id, ChannelType.STATE);
|
|
5530
|
-
|
|
5749
|
+
this.logger.debug('StateStore', `🔒 Private state channel configured`, { channelId: this.apiOptions.ws.id });
|
|
5531
5750
|
// Store our own sessionId for filtering incoming messages
|
|
5532
5751
|
this.ownSessionId = sessionStorage.getItem('WSID') || null;
|
|
5533
|
-
|
|
5534
|
-
|
|
5752
|
+
this.logger.debug('StateStore', `🆔 Stored own sessionId for message filtering`, { sessionId: this.ownSessionId });
|
|
5753
|
+
this.logger.debug('StateStore', `🔍 WebSocket configuration`, {
|
|
5535
5754
|
channelId: this.apiOptions.ws.id,
|
|
5536
5755
|
wsServer: this.apiOptions.ws.wsServer,
|
|
5537
5756
|
sessionId: this.ownSessionId,
|
|
5538
5757
|
path: this.apiOptions.path
|
|
5539
5758
|
});
|
|
5540
5759
|
// Initialize WebSocket connection if not already connected
|
|
5541
|
-
|
|
5542
|
-
|
|
5760
|
+
this.logger.debug('StateStore', '🔌 Checking WebSocket connection status...');
|
|
5761
|
+
this.logger.debug('StateStore', '🔌 Is connected?', { connected: WebSocketManagerService.isConnected() });
|
|
5543
5762
|
if (!WebSocketManagerService.isConnected()) {
|
|
5544
|
-
|
|
5763
|
+
this.logger.debug('StateStore', '🔌 WebSocket not connected, will initialize via effect');
|
|
5545
5764
|
// initWS is an effect that triggers when setApiRequestOptions is called with ws config
|
|
5546
5765
|
// The effect is already triggered by calling setApiRequestOptions above
|
|
5547
5766
|
}
|
|
@@ -5552,7 +5771,7 @@ class HTTPManagerStateService extends ComponentStore {
|
|
|
5552
5771
|
}
|
|
5553
5772
|
// Validate wsServer before attempting connection
|
|
5554
5773
|
if (!this.apiOptions.ws.wsServer || this.apiOptions.ws.wsServer === '') {
|
|
5555
|
-
|
|
5774
|
+
this.logger.error('StateStore', 'WSOptions invalid: wsServer is missing or empty');
|
|
5556
5775
|
return;
|
|
5557
5776
|
}
|
|
5558
5777
|
// Clean up previous subscription to prevent duplicate handlers
|
|
@@ -5563,23 +5782,23 @@ class HTTPManagerStateService extends ComponentStore {
|
|
|
5563
5782
|
// Setup connection status monitoring (internal subscription to drive retry counters)
|
|
5564
5783
|
this.connectionStatusSubscription = this.setupConnectionStatus().subscribe();
|
|
5565
5784
|
// Make initial connection attempt
|
|
5566
|
-
|
|
5785
|
+
this.logger.debug('StateStore', '🔄 Initial WebSocket connection attempt...');
|
|
5567
5786
|
this.httpManagerService.connect(this.apiOptions.ws, this.apiOptions.ws.jwtToken || '');
|
|
5568
5787
|
// Initialize WS effect to handle messages
|
|
5569
5788
|
this.initWS(this.apiOptions.ws);
|
|
5570
5789
|
}
|
|
5571
5790
|
else {
|
|
5572
|
-
|
|
5791
|
+
this.logger.warn('StateStore', 'WSOptions invalid Id: empty');
|
|
5573
5792
|
}
|
|
5574
5793
|
}
|
|
5575
5794
|
// WebSocket
|
|
5576
5795
|
setupConnectionStatus() {
|
|
5577
5796
|
return this.httpManagerService.connectionStatus$.pipe(distinctUntilChanged(), tap(status => {
|
|
5578
5797
|
if (status === true) {
|
|
5579
|
-
|
|
5798
|
+
this.logger.debug('StateStore', '🟢 WebSocket connection is open');
|
|
5580
5799
|
}
|
|
5581
5800
|
else {
|
|
5582
|
-
|
|
5801
|
+
this.logger.debug('StateStore', '🔴 WebSocket connection is closed');
|
|
5583
5802
|
}
|
|
5584
5803
|
}), switchMap(status => {
|
|
5585
5804
|
if (status === true) {
|
|
@@ -6467,6 +6686,212 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
6467
6686
|
}]
|
|
6468
6687
|
}] });
|
|
6469
6688
|
|
|
6689
|
+
class DisplayConfig {
|
|
6690
|
+
constructor(type = 'snackbar', supportsMarkdown, stackable, queueBehavior, autoDismiss, width, height) {
|
|
6691
|
+
this.type = type;
|
|
6692
|
+
this.supportsMarkdown = supportsMarkdown;
|
|
6693
|
+
this.stackable = stackable;
|
|
6694
|
+
this.queueBehavior = queueBehavior;
|
|
6695
|
+
this.autoDismiss = autoDismiss;
|
|
6696
|
+
this.width = width;
|
|
6697
|
+
this.height = height;
|
|
6698
|
+
}
|
|
6699
|
+
static adapt(item) {
|
|
6700
|
+
return new DisplayConfig(item?.type, item?.supportsMarkdown, item?.stackable, item?.queueBehavior, item?.autoDismiss, item?.width, item?.height);
|
|
6701
|
+
}
|
|
6702
|
+
}
|
|
6703
|
+
|
|
6704
|
+
class DisplayRule {
|
|
6705
|
+
constructor(id = '', name, match = () => false, display = DisplayConfig.adapt()) {
|
|
6706
|
+
this.id = id;
|
|
6707
|
+
this.name = name;
|
|
6708
|
+
this.match = match;
|
|
6709
|
+
this.display = display;
|
|
6710
|
+
}
|
|
6711
|
+
static adapt(item) {
|
|
6712
|
+
return new DisplayRule(item?.id, item?.name, item?.match, item?.display ? DisplayConfig.adapt(item.display) : DisplayConfig.adapt());
|
|
6713
|
+
}
|
|
6714
|
+
}
|
|
6715
|
+
|
|
6716
|
+
class Slide {
|
|
6717
|
+
constructor(title = '', message = '', image, icon) {
|
|
6718
|
+
this.title = title;
|
|
6719
|
+
this.message = message;
|
|
6720
|
+
this.image = image;
|
|
6721
|
+
this.icon = icon;
|
|
6722
|
+
}
|
|
6723
|
+
static adapt(item) {
|
|
6724
|
+
return new Slide(item?.title, item?.message, item?.image, item?.icon);
|
|
6725
|
+
}
|
|
6726
|
+
}
|
|
6727
|
+
|
|
6728
|
+
class Action {
|
|
6729
|
+
constructor(label = '', action = 'close', target, callback, primary) {
|
|
6730
|
+
this.label = label;
|
|
6731
|
+
this.action = action;
|
|
6732
|
+
this.target = target;
|
|
6733
|
+
this.callback = callback;
|
|
6734
|
+
this.primary = primary;
|
|
6735
|
+
}
|
|
6736
|
+
static adapt(item) {
|
|
6737
|
+
return new Action(item?.label, item?.action, item?.target, item?.callback, item?.primary);
|
|
6738
|
+
}
|
|
6739
|
+
}
|
|
6740
|
+
|
|
6741
|
+
class MessageContent {
|
|
6742
|
+
constructor(displayConfig, title, message = '', slides, image, images, actions, messageType, icon, data) {
|
|
6743
|
+
this.displayConfig = displayConfig;
|
|
6744
|
+
this.title = title;
|
|
6745
|
+
this.message = message;
|
|
6746
|
+
this.slides = slides;
|
|
6747
|
+
this.image = image;
|
|
6748
|
+
this.images = images;
|
|
6749
|
+
this.actions = actions;
|
|
6750
|
+
this.messageType = messageType;
|
|
6751
|
+
this.icon = icon;
|
|
6752
|
+
this.data = data;
|
|
6753
|
+
}
|
|
6754
|
+
static adapt(item) {
|
|
6755
|
+
return new MessageContent(item?.displayConfig ? DisplayConfig.adapt(item.displayConfig) : undefined, item?.title, item?.message, item?.slides ? item.slides.map((slideItem) => Slide.adapt(slideItem)) : [], item?.image, item?.images, item?.actions ? item.actions.map((actionItem) => Action.adapt(actionItem)) : [], item?.messageType, item?.icon, item?.data);
|
|
6756
|
+
}
|
|
6757
|
+
}
|
|
6758
|
+
class CommunicationMessage {
|
|
6759
|
+
constructor(type = '', messageId, channel, sessionId, timestamp, content = MessageContent.adapt()) {
|
|
6760
|
+
this.type = type;
|
|
6761
|
+
this.messageId = messageId;
|
|
6762
|
+
this.channel = channel;
|
|
6763
|
+
this.sessionId = sessionId;
|
|
6764
|
+
this.timestamp = timestamp;
|
|
6765
|
+
this.content = content;
|
|
6766
|
+
}
|
|
6767
|
+
static adapt(item) {
|
|
6768
|
+
return new CommunicationMessage(item?.type, item?.messageId, item?.channel, item?.sessionId, item?.timestamp, item?.content ? MessageContent.adapt(item.content) : MessageContent.adapt());
|
|
6769
|
+
}
|
|
6770
|
+
}
|
|
6771
|
+
|
|
6772
|
+
const defaultDisplayRules = [
|
|
6773
|
+
{
|
|
6774
|
+
id: 'carousel',
|
|
6775
|
+
name: 'Carousel',
|
|
6776
|
+
match: (message) => message.content?.displayConfig?.type === 'carousel',
|
|
6777
|
+
display: {
|
|
6778
|
+
type: 'carousel',
|
|
6779
|
+
supportsMarkdown: true,
|
|
6780
|
+
stackable: false
|
|
6781
|
+
}
|
|
6782
|
+
},
|
|
6783
|
+
{
|
|
6784
|
+
id: 'dialog',
|
|
6785
|
+
name: 'Dialog',
|
|
6786
|
+
match: (message) => message.content?.displayConfig?.type === 'dialog',
|
|
6787
|
+
display: {
|
|
6788
|
+
type: 'dialog',
|
|
6789
|
+
supportsMarkdown: true,
|
|
6790
|
+
stackable: true
|
|
6791
|
+
}
|
|
6792
|
+
},
|
|
6793
|
+
{
|
|
6794
|
+
id: 'default',
|
|
6795
|
+
name: 'Snackbar',
|
|
6796
|
+
match: () => true,
|
|
6797
|
+
display: {
|
|
6798
|
+
type: 'snackbar',
|
|
6799
|
+
supportsMarkdown: false,
|
|
6800
|
+
stackable: true,
|
|
6801
|
+
autoDismiss: 3000
|
|
6802
|
+
}
|
|
6803
|
+
}
|
|
6804
|
+
];
|
|
6805
|
+
|
|
6806
|
+
class SnackbarStrategy {
|
|
6807
|
+
constructor() {
|
|
6808
|
+
this.name = 'snackbar';
|
|
6809
|
+
this.toastService = inject(ToastMessageDisplayService);
|
|
6810
|
+
}
|
|
6811
|
+
canHandle(message) {
|
|
6812
|
+
return true; // Always can handle (fallback strategy)
|
|
6813
|
+
}
|
|
6814
|
+
display(message, config) {
|
|
6815
|
+
const messageText = this.extractMessage(message.content);
|
|
6816
|
+
this.toastService.toastMessage({
|
|
6817
|
+
message: messageText,
|
|
6818
|
+
color: ToastColors.INFO,
|
|
6819
|
+
icon: 'info',
|
|
6820
|
+
action: 'DONE'
|
|
6821
|
+
}, config.autoDismiss ?? 3000);
|
|
6822
|
+
}
|
|
6823
|
+
extractMessage(content) {
|
|
6824
|
+
if (!content)
|
|
6825
|
+
return '';
|
|
6826
|
+
// Try nested content structure (backward compatibility)
|
|
6827
|
+
if (content.content?.message) {
|
|
6828
|
+
return content.content.message;
|
|
6829
|
+
}
|
|
6830
|
+
// Try direct message
|
|
6831
|
+
if (content.message) {
|
|
6832
|
+
return content.message;
|
|
6833
|
+
}
|
|
6834
|
+
return '';
|
|
6835
|
+
}
|
|
6836
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SnackbarStrategy, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
6837
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SnackbarStrategy }); }
|
|
6838
|
+
}
|
|
6839
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SnackbarStrategy, decorators: [{
|
|
6840
|
+
type: Injectable
|
|
6841
|
+
}] });
|
|
6842
|
+
|
|
6843
|
+
class MessageDisplayRouterService {
|
|
6844
|
+
constructor() {
|
|
6845
|
+
this.snackbarStrategy = inject(SnackbarStrategy);
|
|
6846
|
+
// All available strategies
|
|
6847
|
+
this.strategies = [
|
|
6848
|
+
this.snackbarStrategy
|
|
6849
|
+
// DialogStrategy will be added in Phase 2
|
|
6850
|
+
// CarouselStrategy will be added in Phase 3
|
|
6851
|
+
];
|
|
6852
|
+
// Rules are evaluated in order - first match wins
|
|
6853
|
+
this.rules = defaultDisplayRules;
|
|
6854
|
+
}
|
|
6855
|
+
display(message) {
|
|
6856
|
+
console.log('📨 MessageDisplayRouterService: Processing message:', message);
|
|
6857
|
+
// Find FIRST matching rule (order matters!)
|
|
6858
|
+
const matchingRule = this.rules.find(rule => rule.match(message));
|
|
6859
|
+
// Get the rule (will be 'default' if nothing else matches)
|
|
6860
|
+
const rule = matchingRule || this.rules[this.rules.length - 1];
|
|
6861
|
+
console.log(`🎯 MessageDisplayRouterService: Matched rule '${rule.id}' (${rule.name})`);
|
|
6862
|
+
console.log(` → Display type: ${rule.display.type}`);
|
|
6863
|
+
// Get strategy for the display type
|
|
6864
|
+
const strategy = this.strategies.find(s => s.name === rule.display.type);
|
|
6865
|
+
if (!strategy) {
|
|
6866
|
+
console.warn(`⚠️ MessageDisplayRouterService: No strategy found for type '${rule.display.type}', falling back to snackbar`);
|
|
6867
|
+
// Fallback to snackbar if strategy not found
|
|
6868
|
+
this.snackbarStrategy.display(message, rule.display);
|
|
6869
|
+
return;
|
|
6870
|
+
}
|
|
6871
|
+
// Execute display
|
|
6872
|
+
console.log(`🚀 MessageDisplayRouterService: Executing ${strategy.name} strategy`);
|
|
6873
|
+
strategy.display(message, rule.display);
|
|
6874
|
+
}
|
|
6875
|
+
registerStrategies(strategies) {
|
|
6876
|
+
strategies.forEach(strategy => {
|
|
6877
|
+
if (!this.strategies.find(s => s.name === strategy.name)) {
|
|
6878
|
+
this.strategies.push(strategy);
|
|
6879
|
+
console.log(`✅ Registered strategy: ${strategy.name}`);
|
|
6880
|
+
}
|
|
6881
|
+
});
|
|
6882
|
+
}
|
|
6883
|
+
setRules(rules) {
|
|
6884
|
+
this.rules = rules;
|
|
6885
|
+
console.log(`📋 MessageDisplayRouterService: Rules updated (${rules.length} rules)`);
|
|
6886
|
+
}
|
|
6887
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageDisplayRouterService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
6888
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageDisplayRouterService, providedIn: 'root' }); }
|
|
6889
|
+
}
|
|
6890
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageDisplayRouterService, decorators: [{
|
|
6891
|
+
type: Injectable,
|
|
6892
|
+
args: [{ providedIn: 'root' }]
|
|
6893
|
+
}] });
|
|
6894
|
+
|
|
6470
6895
|
let ClientInfo$2 = class ClientInfo {
|
|
6471
6896
|
constructor(domain = '', service = '', id = 0, name = '') {
|
|
6472
6897
|
this.domain = domain;
|
|
@@ -6566,7 +6991,7 @@ class RequestManagerBasicDemoComponent {
|
|
|
6566
6991
|
this.streamType = 'Auto';
|
|
6567
6992
|
this.downloadRequest = ApiRequest.adapt({
|
|
6568
6993
|
server: 'assets/images',
|
|
6569
|
-
path: ['
|
|
6994
|
+
path: ['lego.png'],
|
|
6570
6995
|
saveAs: 'john.jpg', // Optional
|
|
6571
6996
|
headers: { 'Authorization': 'Bearer Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6InJ0c0ZULWItN0x1WTdEVlllU05LY0lKN1ZuYyJ9.eyJhdWQiOiI4ODhhMzFjZS01OWQzLTRhMTItYTU5Ni04ZDYyZjY0MWI1MDUiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vNmY0MTE5ODItY2YyMy00ZTQ1LTk0NDktNGI2MDdiN2E4OGVjL3YyLjAiLCJpYXQiOjE3NjU4MzQxNDksIm5iZiI6MTc2NTgzNDE0OSwiZXhwIjoxNzY1ODM5NzQ2LCJhY2N0IjowLCJhaW8iOiJBV1FBbS84YUFBQUFVUEZOYUhSZkZ4ZVkwRWthcFBzdWZaNG1QYXV6YVRoODRRZUtQRzkrbEFIVE5XanVwQy9ZSjEwK2IrMktWVlJSSXZRNFpwS2xyWTFGd0xRZmtXOTNLbkRNckxSeEMzWTVOOGlQREZ4b1liZExucW1QL1N1ZE1pRW1Va05tTklSWSIsImF6cCI6Ijg4OGEzMWNlLTU5ZDMtNGExMi1hNTk2LThkNjJmNjQxYjUwNSIsImF6cGFjciI6IjAiLCJncm91cHMiOlsiZWE2ZDk5YjAtNDgyNC00MDU0LTk0MTQtZDNhOTZkZDA3MjRiIl0sIm5hbWUiOiJHVExDIEFwcCBTdXBlciBBZG1pbiIsIm9pZCI6IjI2NzUxY2I2LWNlMDEtNDMzMC05OTc0LWZjMzgxMjQ3YTEzYyIsInByZWZlcnJlZF91c2VybmFtZSI6Imd0bGMwMDNAZ3RsYy5jYSIsInJoIjoiMS5BUzRBZ2hsQmJ5UFBSVTZVU1V0Z2UzcUk3TTR4aW9qVFdSSktwWmFOWXZaQnRRVm1BYTR1QUEuIiwicm9sZXMiOlsiZ3RsYy5hY2wuc3VwZXJhZG1pbiJdLCJzY3AiOiJHVExDX0FwcGxpY2F0aW9uIiwic2lkIjoiMDAzZjVhZDktZmJmNi1jZTkyLTg3MjItMWEzNDExYjMzMDJiIiwic3ViIjoiS0RYQ3drVlhUbTRUUmcwaTkxbXZDZzgtQ29uLXpWbk5FQ2Y5LVg0dkpzUSIsInRpZCI6IjZmNDExOTgyLWNmMjMtNGU0NS05NDQ5LTRiNjA3YjdhODhlYyIsInVwbiI6Imd0bGMwMDNAZ3RsYy5jYSIsInV0aSI6InpBaTJoMmpVUUVHbVY0RTJXMWxqQUEiLCJ2ZXIiOiIyLjAiLCJ3aWRzIjpbImI3OWZiZjRkLTNlZjktNDY4OS04MTQzLTc2YjE5NGU4NTUwOSJdLCJ4bXNfZnRkIjoiUDJraW1ZM0tqc1BTRndxNHlZdmJ0bGhrSUk3d2tXNmFpcW1FQ1BaNEdja0JkWE5sWVhOMExXUnpiWE0ifQ.LzppoggMm27smSAy9SamtPN95vCzdELCAfhtOj5n_T_H6g9xCmNRLS9FaUFQMau6Qvl0lROKl7WDklTswLFkfxbIxCBWtXdL-LTqT5cDURSJAll8vC3zlN3Hg9pAFBUVZFRolt6Z7LvPdI3pvUOQs0yFwVzp9k6cLF8aemKdwKQrMX3XXua1MfBWZcqQ4WiBVNmKh8w6yQB35I4u5WqdFnu33nUGb-kvc18SOpoUfiJnlV-PudaEzFXdU3CjAaMEcuPFv5xLwWJKuhU73dNH4EyQDFMVGtcIHNnieOfiY_nK2_0-5DM6aI40UIRK6Bt-HmMQpnbhLps5y3ep6Z7RNw' } // Optional
|
|
6572
6997
|
});
|
|
@@ -7305,7 +7730,7 @@ class RequestManagerDemoComponent {
|
|
|
7305
7730
|
this.streamType = 'Auto';
|
|
7306
7731
|
this.downloadRequest = ApiRequest.adapt({
|
|
7307
7732
|
server: 'assets/images',
|
|
7308
|
-
path: ['
|
|
7733
|
+
path: ['lego.png'],
|
|
7309
7734
|
saveAs: 'john.jpg', // Optional
|
|
7310
7735
|
headers: { 'Authorization': 'Bearer Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6InJ0c0ZULWItN0x1WTdEVlllU05LY0lKN1ZuYyJ9.eyJhdWQiOiI4ODhhMzFjZS01OWQzLTRhMTItYTU5Ni04ZDYyZjY0MWI1MDUiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vNmY0MTE5ODItY2YyMy00ZTQ1LTk0NDktNGI2MDdiN2E4OGVjL3YyLjAiLCJpYXQiOjE3NjU4MzQxNDksIm5iZiI6MTc2NTgzNDE0OSwiZXhwIjoxNzY1ODM5NzQ2LCJhY2N0IjowLCJhaW8iOiJBV1FBbS84YUFBQUFVUEZOYUhSZkZ4ZVkwRWthcFBzdWZaNG1QYXV6YVRoODRRZUtQRzkrbEFIVE5XanVwQy9ZSjEwK2IrMktWVlJSSXZRNFpwS2xyWTFGd0xRZmtXOTNLbkRNckxSeEMzWTVOOGlQREZ4b1liZExucW1QL1N1ZE1pRW1Va05tTklSWSIsImF6cCI6Ijg4OGEzMWNlLTU5ZDMtNGExMi1hNTk2LThkNjJmNjQxYjUwNSIsImF6cGFjciI6IjAiLCJncm91cHMiOlsiZWE2ZDk5YjAtNDgyNC00MDU0LTk0MTQtZDNhOTZkZDA3MjRiIl0sIm5hbWUiOiJHVExDIEFwcCBTdXBlciBBZG1pbiIsIm9pZCI6IjI2NzUxY2I2LWNlMDEtNDMzMC05OTc0LWZjMzgxMjQ3YTEzYyIsInByZWZlcnJlZF91c2VybmFtZSI6Imd0bGMwMDNAZ3RsYy5jYSIsInJoIjoiMS5BUzRBZ2hsQmJ5UFBSVTZVU1V0Z2UzcUk3TTR4aW9qVFdSSktwWmFOWXZaQnRRVm1BYTR1QUEuIiwicm9sZXMiOlsiZ3RsYy5hY2wuc3VwZXJhZG1pbiJdLCJzY3AiOiJHVExDX0FwcGxpY2F0aW9uIiwic2lkIjoiMDAzZjVhZDktZmJmNi1jZTkyLTg3MjItMWEzNDExYjMzMDJiIiwic3ViIjoiS0RYQ3drVlhUbTRUUmcwaTkxbXZDZzgtQ29uLXpWbk5FQ2Y5LVg0dkpzUSIsInRpZCI6IjZmNDExOTgyLWNmMjMtNGU0NS05NDQ5LTRiNjA3YjdhODhlYyIsInVwbiI6Imd0bGMwMDNAZ3RsYy5jYSIsInV0aSI6InpBaTJoMmpVUUVHbVY0RTJXMWxqQUEiLCJ2ZXIiOiIyLjAiLCJ3aWRzIjpbImI3OWZiZjRkLTNlZjktNDY4OS04MTQzLTc2YjE5NGU4NTUwOSJdLCJ4bXNfZnRkIjoiUDJraW1ZM0tqc1BTRndxNHlZdmJ0bGhrSUk3d2tXNmFpcW1FQ1BaNEdja0JkWE5sWVhOMExXUnpiWE0ifQ.LzppoggMm27smSAy9SamtPN95vCzdELCAfhtOj5n_T_H6g9xCmNRLS9FaUFQMau6Qvl0lROKl7WDklTswLFkfxbIxCBWtXdL-LTqT5cDURSJAll8vC3zlN3Hg9pAFBUVZFRolt6Z7LvPdI3pvUOQs0yFwVzp9k6cLF8aemKdwKQrMX3XXua1MfBWZcqQ4WiBVNmKh8w6yQB35I4u5WqdFnu33nUGb-kvc18SOpoUfiJnlV-PudaEzFXdU3CjAaMEcuPFv5xLwWJKuhU73dNH4EyQDFMVGtcIHNnieOfiY_nK2_0-5DM6aI40UIRK6Bt-HmMQpnbhLps5y3ep6Z7RNw' } // Optional
|
|
7311
7736
|
});
|
|
@@ -8175,6 +8600,7 @@ class WsDataControlComponent {
|
|
|
8175
8600
|
constructor() {
|
|
8176
8601
|
this.path = ['ai', 'tests'];
|
|
8177
8602
|
this.stateDataRequestService = inject(StateDataRequestService);
|
|
8603
|
+
this.webSocketMessageService = inject(WebSocketMessageService);
|
|
8178
8604
|
this.user$ = this.stateDataRequestService.user$;
|
|
8179
8605
|
this.users$ = this.stateDataRequestService.userList$;
|
|
8180
8606
|
this.userAction$ = this.stateDataRequestService.userAction$
|
|
@@ -8209,12 +8635,70 @@ class WsDataControlComponent {
|
|
|
8209
8635
|
const lastRec = data[data.length - 1];
|
|
8210
8636
|
this.stateDataRequestService.deleteData(lastRec);
|
|
8211
8637
|
}
|
|
8638
|
+
/**
|
|
8639
|
+
* Test direct state message via WebSocketMessageService
|
|
8640
|
+
* Sends an UPDATE message that should trigger fetchRecord() in the state manager
|
|
8641
|
+
*
|
|
8642
|
+
* @param recordId - The record ID to update (default: 63)
|
|
8643
|
+
* @param useFakeSessionId - If true, uses a fake sessionId to avoid filtering (default: true)
|
|
8644
|
+
* @param customSessionId - Optional custom sessionId (overrides useFakeSessionId if provided)
|
|
8645
|
+
*/
|
|
8646
|
+
onTestDirectStateMessage(recordId = 63, useFakeSessionId = true, customSessionId) {
|
|
8647
|
+
// Get actual sessionId from sessionStorage (stored by HTTPManagerStateService)
|
|
8648
|
+
const actualSessionId = sessionStorage.getItem('WSID') || 'unknown';
|
|
8649
|
+
// Determine sessionId to use
|
|
8650
|
+
let sessionId;
|
|
8651
|
+
if (customSessionId) {
|
|
8652
|
+
sessionId = customSessionId;
|
|
8653
|
+
}
|
|
8654
|
+
else if (useFakeSessionId) {
|
|
8655
|
+
sessionId = `test-${Date.now()}`;
|
|
8656
|
+
}
|
|
8657
|
+
else {
|
|
8658
|
+
// Use actual sessionId (will be filtered out by receiver)
|
|
8659
|
+
sessionId = actualSessionId;
|
|
8660
|
+
}
|
|
8661
|
+
// Build StateMessage with same structure as CRUD operations
|
|
8662
|
+
const stateMessage = {
|
|
8663
|
+
sessionId: {
|
|
8664
|
+
id: sessionId,
|
|
8665
|
+
name: useFakeSessionId ? 'Test Sender' : (this.user?.name || 'Current User')
|
|
8666
|
+
},
|
|
8667
|
+
content: {
|
|
8668
|
+
method: 'UPDATE',
|
|
8669
|
+
path: ['ai', 'tests', recordId],
|
|
8670
|
+
user: {
|
|
8671
|
+
id: sessionId,
|
|
8672
|
+
name: useFakeSessionId ? 'Test Sender' : (this.user?.name || 'Current User')
|
|
8673
|
+
}
|
|
8674
|
+
}
|
|
8675
|
+
};
|
|
8676
|
+
console.log('🧪 [TEST] Sending direct state message:', {
|
|
8677
|
+
recordId,
|
|
8678
|
+
sessionId,
|
|
8679
|
+
actualSessionId,
|
|
8680
|
+
useFakeSessionId,
|
|
8681
|
+
stateMessage
|
|
8682
|
+
});
|
|
8683
|
+
const success = this.webSocketMessageService.sendStateMessage(['ai', 'tests'], stateMessage);
|
|
8684
|
+
console.log('🧪 [TEST] Message sent:', {
|
|
8685
|
+
success,
|
|
8686
|
+
actualSessionId,
|
|
8687
|
+
sentSessionId: sessionId,
|
|
8688
|
+
expectedBehavior: success
|
|
8689
|
+
? 'Server will broadcast to SYS-ai/tests subscribers'
|
|
8690
|
+
: 'Failed to send (check connection)',
|
|
8691
|
+
receiverBehavior: sessionId !== actualSessionId
|
|
8692
|
+
? 'Receiver will process message and call fetchRecord()'
|
|
8693
|
+
: 'Receiver will filter out (sessionId match)'
|
|
8694
|
+
});
|
|
8695
|
+
}
|
|
8212
8696
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsDataControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
8213
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: WsDataControlComponent, selector: "app-ws-data-control", inputs: { server: "server", wsServer: "wsServer", jwtToken: "jwtToken", user: "user", path: "path" }, ngImport: i0, template: "@if ((data$ | async); as data) {\n <div style=\"margin: 1rem;\">\n @if ((users$ |async); as users) {\n <div>\n @if (users.length > 0) {\n <h3 style=\"margin: 0;\">Connected Users</h3>\n } @else {\n <h3 style=\"margin: 0;\">No Users</h3>\n }\n <mat-chip-set>\n @for (user of users; track $index) {\n <mat-chip\n [class.user-chip--primary]=\"isUser(user, (user$ | async))\"\n [style.color]=\"isUser(user, (user$ | async)) ? '#fff' : null\"\n [disableRipple]=\"true\"\n >\n {{ user.name || user.ldap || user.id || 'Anonymous' }}\n </mat-chip>\n }\n </mat-chip-set>\n </div>\n }\n <div style=\"margin-top: 1rem; margin-bottom: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <div class=\"box\" style=\"margin-bottom: 1rem;\" *ngIf=\"(userAction$ | async) as userAction\">\n {{ userAction?.content?.user?.name }} has {{ userAction?.content?.method }}\n </div>\n\n <h3 style=\"margin: 0;\">Data Actions</h3>\n <div style=\"display: flex; gap: 1rem; margin-bottom: 1rem;\">\n <button mat-stroked-button (click)=\"onGetData()\">Get Data</button>\n <div style=\"flex:1\"></div>\n <button mat-stroked-button color=\"accent\" (click)=\"onUpdateData(data)\">Update Data</button>\n <button mat-stroked-button color=\"warn\" (click)=\"onRemoveData(data)\">Remove Data</button>\n <button mat-stroked-button color=\"primary\" (click)=\"onAddData()\">Add Data</button>\n </div>\n @if (data.length > 0) {\n <div>\n <table mat-table [dataSource]=\"data\" style=\"border: 1px solid grey;\">\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef> ID </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.id}} </td>\n </ng-container>\n <ng-container matColumnDef=\"spiffe\">\n <th mat-header-cell *matHeaderCellDef> Spiffe </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.spiffe}} </td>\n </ng-container>\n <ng-container matColumnDef=\"name\">\n <th mat-header-cell *matHeaderCellDef> Name </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.first_name}} {{element.last_name}}</td>\n </ng-container>\n <ng-container matColumnDef=\"email\">\n <th mat-header-cell *matHeaderCellDef> Email </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.email}} </td>\n </ng-container>\n <tr mat-header-row *matHeaderRowDef=\"['id', 'spiffe', 'name', 'email']\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: ['id', 'spiffe', 'name', 'email'];\"></tr>\n </table>\n <div style=\"border: 1px solid grey; padding: .5rem; border-top: none;\">\n <h3 style=\"margin: 0;\">Total Records {{ data.length }}</h3>\n </div>\n </div>\n } @else {\n <div style=\"margin-top: 1rem; font-style: italic;\">\n No Data Available\n </div>\n }\n </div>\n}\n\n", styles: [".user-chip--primary{background-color:var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5))!important;color:#fff!important;--mdc-evolution-chip-container-color: var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5));--mdc-evolution-chip-label-text-color: #fff}.user-chip--primary :is(.mdc-evolution-chip__text-label,.mdc-evolution-chip__action,.mdc-evolution-chip__cell,.mat-mdc-chip-action-label){color:#fff!important}.user-chip--primary,.user-chip--primary *{color:#fff!important}:host ::ng-deep .user-chip--primary{background-color:var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5))!important;color:#fff!important;--mdc-evolution-chip-container-color: var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5));--mdc-evolution-chip-label-text-color: #fff}:host ::ng-deep .user-chip--primary .mdc-evolution-chip__text-label,:host ::ng-deep .user-chip--primary .mdc-evolution-chip__action,:host ::ng-deep .user-chip--primary .mdc-evolution-chip__cell,:host ::ng-deep .user-chip--primary .mat-mdc-chip-action-label,:host ::ng-deep .user-chip--primary *{color:#fff!important}.box{padding:.5rem;border:1px solid rgb(174,174,13);background-color:#ececaf}\n"], dependencies: [{ kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i3$1.MatChip, selector: "mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]", inputs: ["role", "id", "aria-label", "aria-description", "value", "color", "removable", "highlighted", "disableRipple", "disabled"], outputs: ["removed", "destroyed"], exportAs: ["matChip"] }, { kind: "component", type: i3$1.MatChipSet, selector: "mat-chip-set", inputs: ["disabled", "role", "tabIndex"] }, { kind: "component", type: i9.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i9.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i9.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i9.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i9.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i9.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i9.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i9.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i9.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i9.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i12.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }] }); }
|
|
8697
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: WsDataControlComponent, selector: "app-ws-data-control", inputs: { server: "server", wsServer: "wsServer", jwtToken: "jwtToken", user: "user", path: "path" }, ngImport: i0, template: "@if ((data$ | async); as data) {\n <div style=\"margin: 1rem;\">\n @if ((users$ |async); as users) {\n <div>\n @if (users.length > 0) {\n <h3 style=\"margin: 0;\">Connected Users</h3>\n } @else {\n <h3 style=\"margin: 0;\">No Users</h3>\n }\n <mat-chip-set>\n @for (user of users; track $index) {\n <mat-chip\n [class.user-chip--primary]=\"isUser(user, (user$ | async))\"\n [style.color]=\"isUser(user, (user$ | async)) ? '#fff' : null\"\n [disableRipple]=\"true\"\n >\n {{ user.name || user.ldap || user.id || 'Anonymous' }}\n </mat-chip>\n }\n </mat-chip-set>\n </div>\n }\n <div style=\"margin-top: 1rem; margin-bottom: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <div class=\"box\" style=\"margin-bottom: 1rem;\" *ngIf=\"(userAction$ | async) as userAction\">\n {{ userAction?.content?.user?.name }} has {{ userAction?.content?.method }}\n </div>\n\n <h3 style=\"margin: 0;\">Data Actions</h3>\n <div style=\"display: flex; gap: 1rem; margin-bottom: 1rem;\">\n <button mat-stroked-button (click)=\"onGetData()\">Get Data</button>\n <div style=\"flex:1\"></div>\n <button mat-stroked-button color=\"accent\" (click)=\"onUpdateData(data)\">Update Data</button>\n <button mat-stroked-button color=\"warn\" (click)=\"onRemoveData(data)\">Remove Data</button>\n <button mat-stroked-button color=\"primary\" (click)=\"onAddData()\">Add Data</button>\n </div>\n\n <h3 style=\"margin: 0; margin-top: 1rem;\">WebSocketMessageService Test</h3>\n\n <p style=\"font-size: 0.875rem; color: #666; margin: 0;\">\n <strong>Note:</strong> \"Fake SessionId\" will be received and processed. \"Current SessionId\" will be filtered out (self-message).\n @if (data.length > 0) {\n <div>\n <table mat-table [dataSource]=\"data\" style=\"border: 1px solid grey;\">\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef> ID </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.id}} </td>\n </ng-container>\n <ng-container matColumnDef=\"spiffe\">\n <th mat-header-cell *matHeaderCellDef> Spiffe </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.spiffe}} </td>\n </ng-container>\n <ng-container matColumnDef=\"name\">\n <th mat-header-cell *matHeaderCellDef> Name </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.first_name}} {{element.last_name}}</td>\n </ng-container>\n <ng-container matColumnDef=\"email\">\n <th mat-header-cell *matHeaderCellDef> Email </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.email}} </td>\n </ng-container>\n <tr mat-header-row *matHeaderRowDef=\"['id', 'spiffe', 'name', 'email']\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: ['id', 'spiffe', 'name', 'email'];\"></tr>\n </table>\n <div style=\"border: 1px solid grey; padding: .5rem; border-top: none;\">\n <h3 style=\"margin: 0;\">Total Records {{ data.length }}</h3>\n </div>\n </div>\n } @else {\n <div style=\"margin-top: 1rem; font-style: italic;\">\n No Data Available\n </div>\n }\n\n <div style=\"margin-bottom: 1rem; margin-top: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"display: flex; gap: 0.5rem; margin-bottom: 1rem;\">\n <button mat-flat-button color=\"primary\" (click)=\"onTestDirectStateMessage(63, true)\">\n Test UPDATE (Fake Id)\n </button>\n <button mat-stroked-button (click)=\"onTestDirectStateMessage(63, false)\">\n Test UPDATE (Current Id)\n </button>\n <button mat-stroked-button (click)=\"onTestDirectStateMessage(63, false, 'custom-session-123')\">\n Test UPDATE (Custom Id)\n </button>\n </div>\n\n </div>\n}\n\n", styles: [".user-chip--primary{background-color:var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5))!important;color:#fff!important;--mdc-evolution-chip-container-color: var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5));--mdc-evolution-chip-label-text-color: #fff}.user-chip--primary :is(.mdc-evolution-chip__text-label,.mdc-evolution-chip__action,.mdc-evolution-chip__cell,.mat-mdc-chip-action-label){color:#fff!important}.user-chip--primary,.user-chip--primary *{color:#fff!important}:host ::ng-deep .user-chip--primary{background-color:var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5))!important;color:#fff!important;--mdc-evolution-chip-container-color: var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5));--mdc-evolution-chip-label-text-color: #fff}:host ::ng-deep .user-chip--primary .mdc-evolution-chip__text-label,:host ::ng-deep .user-chip--primary .mdc-evolution-chip__action,:host ::ng-deep .user-chip--primary .mdc-evolution-chip__cell,:host ::ng-deep .user-chip--primary .mat-mdc-chip-action-label,:host ::ng-deep .user-chip--primary *{color:#fff!important}.box{padding:.5rem;border:1px solid rgb(174,174,13);background-color:#ececaf}\n"], dependencies: [{ kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i3$1.MatChip, selector: "mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]", inputs: ["role", "id", "aria-label", "aria-description", "value", "color", "removable", "highlighted", "disableRipple", "disabled"], outputs: ["removed", "destroyed"], exportAs: ["matChip"] }, { kind: "component", type: i3$1.MatChipSet, selector: "mat-chip-set", inputs: ["disabled", "role", "tabIndex"] }, { kind: "component", type: i9.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i9.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i9.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i9.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i9.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i9.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i9.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i9.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i9.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i9.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i12.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }] }); }
|
|
8214
8698
|
}
|
|
8215
8699
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsDataControlComponent, decorators: [{
|
|
8216
8700
|
type: Component,
|
|
8217
|
-
args: [{ selector: 'app-ws-data-control', standalone: false, template: "@if ((data$ | async); as data) {\n <div style=\"margin: 1rem;\">\n @if ((users$ |async); as users) {\n <div>\n @if (users.length > 0) {\n <h3 style=\"margin: 0;\">Connected Users</h3>\n } @else {\n <h3 style=\"margin: 0;\">No Users</h3>\n }\n <mat-chip-set>\n @for (user of users; track $index) {\n <mat-chip\n [class.user-chip--primary]=\"isUser(user, (user$ | async))\"\n [style.color]=\"isUser(user, (user$ | async)) ? '#fff' : null\"\n [disableRipple]=\"true\"\n >\n {{ user.name || user.ldap || user.id || 'Anonymous' }}\n </mat-chip>\n }\n </mat-chip-set>\n </div>\n }\n <div style=\"margin-top: 1rem; margin-bottom: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <div class=\"box\" style=\"margin-bottom: 1rem;\" *ngIf=\"(userAction$ | async) as userAction\">\n {{ userAction?.content?.user?.name }} has {{ userAction?.content?.method }}\n </div>\n\n <h3 style=\"margin: 0;\">Data Actions</h3>\n <div style=\"display: flex; gap: 1rem; margin-bottom: 1rem;\">\n <button mat-stroked-button (click)=\"onGetData()\">Get Data</button>\n <div style=\"flex:1\"></div>\n <button mat-stroked-button color=\"accent\" (click)=\"onUpdateData(data)\">Update Data</button>\n <button mat-stroked-button color=\"warn\" (click)=\"onRemoveData(data)\">Remove Data</button>\n <button mat-stroked-button color=\"primary\" (click)=\"onAddData()\">Add Data</button>\n </div>\n @if (data.length > 0) {\n <div>\n <table mat-table [dataSource]=\"data\" style=\"border: 1px solid grey;\">\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef> ID </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.id}} </td>\n </ng-container>\n <ng-container matColumnDef=\"spiffe\">\n <th mat-header-cell *matHeaderCellDef> Spiffe </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.spiffe}} </td>\n </ng-container>\n <ng-container matColumnDef=\"name\">\n <th mat-header-cell *matHeaderCellDef> Name </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.first_name}} {{element.last_name}}</td>\n </ng-container>\n <ng-container matColumnDef=\"email\">\n <th mat-header-cell *matHeaderCellDef> Email </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.email}} </td>\n </ng-container>\n <tr mat-header-row *matHeaderRowDef=\"['id', 'spiffe', 'name', 'email']\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: ['id', 'spiffe', 'name', 'email'];\"></tr>\n </table>\n <div style=\"border: 1px solid grey; padding: .5rem; border-top: none;\">\n <h3 style=\"margin: 0;\">Total Records {{ data.length }}</h3>\n </div>\n </div>\n } @else {\n <div style=\"margin-top: 1rem; font-style: italic;\">\n No Data Available\n </div>\n }\n </div>\n}\n\n", styles: [".user-chip--primary{background-color:var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5))!important;color:#fff!important;--mdc-evolution-chip-container-color: var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5));--mdc-evolution-chip-label-text-color: #fff}.user-chip--primary :is(.mdc-evolution-chip__text-label,.mdc-evolution-chip__action,.mdc-evolution-chip__cell,.mat-mdc-chip-action-label){color:#fff!important}.user-chip--primary,.user-chip--primary *{color:#fff!important}:host ::ng-deep .user-chip--primary{background-color:var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5))!important;color:#fff!important;--mdc-evolution-chip-container-color: var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5));--mdc-evolution-chip-label-text-color: #fff}:host ::ng-deep .user-chip--primary .mdc-evolution-chip__text-label,:host ::ng-deep .user-chip--primary .mdc-evolution-chip__action,:host ::ng-deep .user-chip--primary .mdc-evolution-chip__cell,:host ::ng-deep .user-chip--primary .mat-mdc-chip-action-label,:host ::ng-deep .user-chip--primary *{color:#fff!important}.box{padding:.5rem;border:1px solid rgb(174,174,13);background-color:#ececaf}\n"] }]
|
|
8701
|
+
args: [{ selector: 'app-ws-data-control', standalone: false, template: "@if ((data$ | async); as data) {\n <div style=\"margin: 1rem;\">\n @if ((users$ |async); as users) {\n <div>\n @if (users.length > 0) {\n <h3 style=\"margin: 0;\">Connected Users</h3>\n } @else {\n <h3 style=\"margin: 0;\">No Users</h3>\n }\n <mat-chip-set>\n @for (user of users; track $index) {\n <mat-chip\n [class.user-chip--primary]=\"isUser(user, (user$ | async))\"\n [style.color]=\"isUser(user, (user$ | async)) ? '#fff' : null\"\n [disableRipple]=\"true\"\n >\n {{ user.name || user.ldap || user.id || 'Anonymous' }}\n </mat-chip>\n }\n </mat-chip-set>\n </div>\n }\n <div style=\"margin-top: 1rem; margin-bottom: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <div class=\"box\" style=\"margin-bottom: 1rem;\" *ngIf=\"(userAction$ | async) as userAction\">\n {{ userAction?.content?.user?.name }} has {{ userAction?.content?.method }}\n </div>\n\n <h3 style=\"margin: 0;\">Data Actions</h3>\n <div style=\"display: flex; gap: 1rem; margin-bottom: 1rem;\">\n <button mat-stroked-button (click)=\"onGetData()\">Get Data</button>\n <div style=\"flex:1\"></div>\n <button mat-stroked-button color=\"accent\" (click)=\"onUpdateData(data)\">Update Data</button>\n <button mat-stroked-button color=\"warn\" (click)=\"onRemoveData(data)\">Remove Data</button>\n <button mat-stroked-button color=\"primary\" (click)=\"onAddData()\">Add Data</button>\n </div>\n\n <h3 style=\"margin: 0; margin-top: 1rem;\">WebSocketMessageService Test</h3>\n\n <p style=\"font-size: 0.875rem; color: #666; margin: 0;\">\n <strong>Note:</strong> \"Fake SessionId\" will be received and processed. \"Current SessionId\" will be filtered out (self-message).\n @if (data.length > 0) {\n <div>\n <table mat-table [dataSource]=\"data\" style=\"border: 1px solid grey;\">\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef> ID </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.id}} </td>\n </ng-container>\n <ng-container matColumnDef=\"spiffe\">\n <th mat-header-cell *matHeaderCellDef> Spiffe </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.spiffe}} </td>\n </ng-container>\n <ng-container matColumnDef=\"name\">\n <th mat-header-cell *matHeaderCellDef> Name </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.first_name}} {{element.last_name}}</td>\n </ng-container>\n <ng-container matColumnDef=\"email\">\n <th mat-header-cell *matHeaderCellDef> Email </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.email}} </td>\n </ng-container>\n <tr mat-header-row *matHeaderRowDef=\"['id', 'spiffe', 'name', 'email']\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: ['id', 'spiffe', 'name', 'email'];\"></tr>\n </table>\n <div style=\"border: 1px solid grey; padding: .5rem; border-top: none;\">\n <h3 style=\"margin: 0;\">Total Records {{ data.length }}</h3>\n </div>\n </div>\n } @else {\n <div style=\"margin-top: 1rem; font-style: italic;\">\n No Data Available\n </div>\n }\n\n <div style=\"margin-bottom: 1rem; margin-top: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"display: flex; gap: 0.5rem; margin-bottom: 1rem;\">\n <button mat-flat-button color=\"primary\" (click)=\"onTestDirectStateMessage(63, true)\">\n Test UPDATE (Fake Id)\n </button>\n <button mat-stroked-button (click)=\"onTestDirectStateMessage(63, false)\">\n Test UPDATE (Current Id)\n </button>\n <button mat-stroked-button (click)=\"onTestDirectStateMessage(63, false, 'custom-session-123')\">\n Test UPDATE (Custom Id)\n </button>\n </div>\n\n </div>\n}\n\n", styles: [".user-chip--primary{background-color:var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5))!important;color:#fff!important;--mdc-evolution-chip-container-color: var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5));--mdc-evolution-chip-label-text-color: #fff}.user-chip--primary :is(.mdc-evolution-chip__text-label,.mdc-evolution-chip__action,.mdc-evolution-chip__cell,.mat-mdc-chip-action-label){color:#fff!important}.user-chip--primary,.user-chip--primary *{color:#fff!important}:host ::ng-deep .user-chip--primary{background-color:var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5))!important;color:#fff!important;--mdc-evolution-chip-container-color: var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5));--mdc-evolution-chip-label-text-color: #fff}:host ::ng-deep .user-chip--primary .mdc-evolution-chip__text-label,:host ::ng-deep .user-chip--primary .mdc-evolution-chip__action,:host ::ng-deep .user-chip--primary .mdc-evolution-chip__cell,:host ::ng-deep .user-chip--primary .mat-mdc-chip-action-label,:host ::ng-deep .user-chip--primary *{color:#fff!important}.box{padding:.5rem;border:1px solid rgb(174,174,13);background-color:#ececaf}\n"] }]
|
|
8218
8702
|
}], propDecorators: { server: [{
|
|
8219
8703
|
type: Input
|
|
8220
8704
|
}], wsServer: [{
|
|
@@ -8234,7 +8718,7 @@ class WsMessagingComponent {
|
|
|
8234
8718
|
this.fb = inject(FormBuilder);
|
|
8235
8719
|
this.messageService = inject(MessageServiceDemo);
|
|
8236
8720
|
this.stateService = inject(StateServiceDemo);
|
|
8237
|
-
this.
|
|
8721
|
+
this.messageDisplayService = inject(MessageDisplayRouterService);
|
|
8238
8722
|
// Only show public channels (starting with "PUB-"), strip prefix for display
|
|
8239
8723
|
// SYS- channels are private/internal and hidden from users
|
|
8240
8724
|
this.channels$ = this.messageService.channels$.pipe(map$1(channels => channels
|
|
@@ -8283,20 +8767,15 @@ class WsMessagingComponent {
|
|
|
8283
8767
|
this.messageService.getAllChannels();
|
|
8284
8768
|
}, 500); // 500ms delay to ensure subscription is processed
|
|
8285
8769
|
});
|
|
8286
|
-
// Subscribe to latest messages and
|
|
8770
|
+
// Subscribe to latest messages and display using rule-based routing
|
|
8287
8771
|
this.latestCommunicationMessages$.pipe(filter$1(message => !!message), takeUntil$1(this.destroy$)).subscribe((message) => {
|
|
8288
|
-
console.log('🔔
|
|
8289
|
-
|
|
8290
|
-
//
|
|
8291
|
-
|
|
8292
|
-
|
|
8293
|
-
|
|
8294
|
-
|
|
8295
|
-
color: ToastColors.INFO,
|
|
8296
|
-
icon: 'chat',
|
|
8297
|
-
action: 'DONE'
|
|
8298
|
-
}), -1 // Stay until dismissed
|
|
8299
|
-
);
|
|
8772
|
+
console.log('🔔 Message received, routing to display:', message);
|
|
8773
|
+
// NEW: Delegate to MessageDisplayRouterService
|
|
8774
|
+
// The message payload determines the display type via rules
|
|
8775
|
+
this.messageDisplayService.display(message);
|
|
8776
|
+
// OLD: Hardcoded snackbar (removed)
|
|
8777
|
+
// const messageContent = message.content?.content?.message || message.content?.message || '';
|
|
8778
|
+
// this.toastService.toastMessage(...);
|
|
8300
8779
|
});
|
|
8301
8780
|
}
|
|
8302
8781
|
ngOnDestroy() {
|
|
@@ -9163,7 +9642,7 @@ class RequestSignalsManagerDemoComponent {
|
|
|
9163
9642
|
this.questionControl = this.fb.control("", [Validators.required]);
|
|
9164
9643
|
this.downloadRequest = ApiRequest.adapt({
|
|
9165
9644
|
server: 'assets/images',
|
|
9166
|
-
path: ['
|
|
9645
|
+
path: ['lego.png'],
|
|
9167
9646
|
// saveAs: 'john.jpg' // Optional
|
|
9168
9647
|
});
|
|
9169
9648
|
// downloadRequest = ApiRequest.adapt({
|
|
@@ -9491,7 +9970,9 @@ class HttpRequestManagerModule {
|
|
|
9491
9970
|
{ provide: HTTP_INTERCEPTORS, useClass: ProxyDebuggerInterceptor, multi: true },
|
|
9492
9971
|
{ provide: CONFIG_SETTINGS_TOKEN, useValue: ConfigOptions.adapt() },
|
|
9493
9972
|
HTTPManagerService, LocalStorageManagerService,
|
|
9494
|
-
ToastMessageDisplayService
|
|
9973
|
+
ToastMessageDisplayService,
|
|
9974
|
+
MessageDisplayRouterService,
|
|
9975
|
+
SnackbarStrategy
|
|
9495
9976
|
], imports: [CommonModule,
|
|
9496
9977
|
ToastMessageDisplayModule,
|
|
9497
9978
|
FormsModule,
|
|
@@ -9576,7 +10057,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
9576
10057
|
{ provide: HTTP_INTERCEPTORS, useClass: ProxyDebuggerInterceptor, multi: true },
|
|
9577
10058
|
{ provide: CONFIG_SETTINGS_TOKEN, useValue: ConfigOptions.adapt() },
|
|
9578
10059
|
HTTPManagerService, LocalStorageManagerService,
|
|
9579
|
-
ToastMessageDisplayService
|
|
10060
|
+
ToastMessageDisplayService,
|
|
10061
|
+
MessageDisplayRouterService,
|
|
10062
|
+
SnackbarStrategy
|
|
9580
10063
|
],
|
|
9581
10064
|
}]
|
|
9582
10065
|
}] });
|
|
@@ -9589,5 +10072,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
9589
10072
|
* Generated bundle index. Do not edit.
|
|
9590
10073
|
*/
|
|
9591
10074
|
|
|
9592
|
-
export { ApiRequest, AppService, AsymmetricalEncryptionService, CONFIG_SETTINGS_TOKEN,
|
|
10075
|
+
export { ApiRequest, AppService, AsymmetricalEncryptionService, CONFIG_SETTINGS_TOKEN, ChannelType, ConfigHTTPOptions, ConfigOptions, DataType, DatabaseDataDemoComponent, DatabaseManagerService, DatabaseStorage, DbService, ErrorDisplaySettings, GlobalStoreOptions, HTTPManagerService, HTTPManagerSignalsService, HTTPManagerStateService, HeadersService, HttpRequestManagerModule, HttpRequestServicesDemoComponent, LocalStorageDemoComponent, LocalStorageManagerService, LocalStorageOptions, LocalStorageSignalsManagerService, LoggerService, NotificationMessage, PathQueryService, PublicMessage, Random, RandomHSLColor, RandomHexColor, RandomNumber, RandomNumbers, RandomNumbersUnique, RandomPaletteColor, RandomSignature, RandomStr, RandomVisibleColor, RequestErrorInterceptor, RequestHeadersInterceptor, RequestManagerDemoComponent, RequestManagerStateDemoComponent, RequestOptions, RequestService, RequestSignalsService, RetryOptions, SettingOptions, StateMessage, StateStorageOptions, StorageData, StorageOption, StorageType, StoreStateManagerService, StreamType, SymmetricalEncryptionService, TableSchemaDef, UUID, UUID_STR, UserData, UtilsService, WSOptions, WebSocketMessageService, WithCredentialsInterceptor, countdown, createChannelName, delayedRetry, requestPolling, requestStreaming, streamAI, streamAuto, streamEvents, streamJSON, streamNDJSON };
|
|
9593
10076
|
//# sourceMappingURL=http-request-manager.mjs.map
|